IdlePixel Group Chat

A private group chat panel

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name			IdlePixel Group Chat
// @namespace		luxferre.dev
// @version			2.1.2
// @description		A private group chat panel
// @author			Lux-Ferre
// @license			MIT
// @match			*://idle-pixel.com/login/play*
// @grant			none
// @require			https://greasyfork.org/scripts/441206-idlepixel/code/IdlePixel+.js?anticache=20220905
// @require			https://greasyfork.org/scripts/484046/code/IdlePixel%2B%20Custom%20Handling.js?anticache=20240721
// @require		 	https://greasyfork.org/scripts/491983-idlepixel-plugin-paneller/code/IdlePixel%2B%20Plugin%20Paneller.js?anticache=20240721
// ==/UserScript==
 
(function() {
	'use strict';

	class GroupChatPlugin extends IdlePixelPlusPlugin {
		constructor() {
			super("groupChat", {
				about: {
					name: GM_info.script.name,
					version: GM_info.script.version,
					author: GM_info.script.author,
					description: GM_info.script.description
				},
			});
			this.groups = {}
			this.activeGroup = undefined
			this.invitations = {}
			this.chats = {}
		}

		saveData(){
			const user = window["var_username"]
			const groupsJSON = JSON.stringify(this.groups)
			localStorage.setItem(`groupChatGroups${user}`, groupsJSON)

			const invitationsJSON = JSON.stringify(this.invitations)
			localStorage.setItem(`groupChatInvitations${user}`, invitationsJSON)
		}

		loadData(){
			const user = window["var_username"]
			const groupData = localStorage.getItem(`groupChatGroups${user}`)
			if (groupData){
				this.groups = JSON.parse(groupData)
			} else {
				this.groups = {}
			}

			const invitationData = localStorage.getItem(`groupChatInvitations${user}`)
			if (invitationData){
				this.invitations = JSON.parse(invitationData)
			} else {
				this.invitations = {}
			}
		}
 
		createPanel(){
			IdlePixelPlus.addPanel("groupchat", "Group Chat Panel", function() {
				return `
<div class="groupChatUIContainer w-100">
    <div id="groupChatInfoModule" class="row groupChatUIModule">
        <div class="col">
            <div class="row">
                <div class="col-4 text-end align-self-center groupChatInfoContainer">
                    <div class="row gx-0">
                        <div id="groupChatGroupNotification" class="col-1 text-center align-self-center groupChatGroupNotificationInactive" onclick="IdlePixelPlus.plugins.groupChat.showInvitationModal()"><span>!</span></div>
                        <div class="col-11 align-self-center"><select id="groupChatGroupSelector" class="w-100"></select></div>
                    </div>
                </div>
                <div class="col-8 d-flex groupChatInfoContainer">
                    <div id="groupChatMembersContainer" class="d-flex align-items-center"><span>Members:</span></div>
                </div>
            </div>
        </div>
    </div>
    <div id="groupChatChatModule" class="row groupChatUIModule">
        <div class="col">
            <div class="row">
                <div id="groupChatChatFormContainer" class="col">
                    <div id="groupChatChatBox" class="overflow-auto"></div>
                    <form onsubmit="event.preventDefault(); IdlePixelPlus.plugins.groupChat.sendGroupChatButton();">
                        <div class="row d-flex flex-fill">
                            <div class="col-11"><input id="groupChatChatIn" class="form-control w-100" type="text" /></div>
                            <div class="col-1"><input id="groupChatChatButton" class="w-100 h-100" type="submit" value="Send" /></div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
    <div id="groupChatSettingsModules" class="row groupChatUIModule">
        <div class="col">
            <div class="row">
                <div id="groupChatSettings" class="col d-flex justify-content-around align-self-center"><button id="groupChatCreateGroupButton" class="btn btn-info" type="button" onclick="IdlePixelPlus.plugins.groupChat.createGroupButton()">Create Group</button><button id="groupChatShareRaidButton" class="btn btn-info groupChatInGroupButton" type="button" onclick="IdlePixelPlus.plugins.groupChat.shareRaidButton()">Share Raid</button><button id="groupChatInviteButton" class="btn btn-info groupChatOwnerButton groupChatInGroupButton" type="button" onclick="IdlePixelPlus.plugins.groupChat.inviteButton()">Invite</button><button id="groupChatUninviteButton" class="btn btn-info groupChatOwnerButton groupChatInGroupButton" type="button" onclick="IdlePixelPlus.plugins.groupChat.uninviteButton()">Uninvite</button><button id="groupChatRemovePlayerButton" class="btn btn-info disabled groupChatOwnerButton groupChatInGroupButton" type="button" disabled>Remove</button></div>
            </div>
        </div>
    </div>
</div>
				`
			});
		}

		addStyles(){
			let backgroundColour
			let textColour

			if ("ui-tweaks" in IdlePixelPlus.plugins){
				backgroundColour = IdlePixelPlus.plugins["ui-tweaks"].config["color-chat-area"]
				textColour = IdlePixelPlus.plugins["ui-tweaks"].config["font-color-chat-area"]
			} else {
				backgroundColour = "white"
				textColour = "black"
			}

			$("head").append(`
				<style id="styles-groupchat">
					#groupChatInvitationsModalInner {
					  background-color: ${backgroundColour};
					  color: ${textColour};
					}

					.groupChatUIModule {
					  border: outset 2px;
					}

					.groupChatUIContainer {
					  width: 100%;
					  height: 100%;
					  padding: 5px;
					  margin: 0;
					}

					#groupChatChatBox {
					  width: 100%;
					  height: 70vh;
					  margin-top: 10px;
					}

					#groupChatChatBox {
					  border: inset 1px;
					}

					.groupChatGroupNotificationActive {
					  color: red;
					  border: outset;
					  border-color: red;
					}

					.groupChatGroupNotificationInactive {
					  color: gray;
					  border: outset;
					  border-color: gray;
					}

					.groupChatInfoContainer {
					  border: inset;
					}

					#groupChatSelectGroupDrop {
					  border: solid;
					  border-width: 1px;
					}

					.groupChatMember {
					  margin: 0px 3px;
					  border: 1px groove;
					}

					#groupChatGroupNotification {
					  cursor: pointer;
					}

					.groupChatInvitation {
					  background-color: RGBA(1, 150, 150, 0.5);
					  margin-bottom: 2px;
					}

					.groupChatInvCheck {
					  cursor: pointer;
					}

					.groupChatInvCross {
					  cursor: pointer;
					  margin-right: 5px;
					}

					.groupChatInvData {
					  margin-left: 10px;
					}

					#groupChatModalHeader {
					  padding: calc(var(--bs-modal-padding) - var(--bs-modal-header-gap) * .5);
					  background-color: var(--bs-modal-header-bg);
					  border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);
					  border-top-right-radius: var(--bs-modal-inner-border-radius);
					  border-top-left-radius: var(--bs-modal-inner-border-radius);
					}

					#groupChatInvitationsModal .modal-body {
					  overflow-y: auto;
					}
				</style>
			`)
		}
	
		onLogin() {
			Paneller.registerPanel("groupchat", "Group Chat")

			this.loadData()
			this.createPanel()
			this.addStyles()
			this.createNotification()
			this.updatePanelInfo()
			this.createInvModal()
			this.updateInvitationNotification()

			$('#groupChatGroupSelector').on('change', function() {
				IdlePixelPlus.plugins.groupChat.joinGroup(this.value)
			});

			$("#groupChatChatModule").hide()
			$(".groupChatInGroupButton").hide()
		}

		onPanelChanged(panelBefore, panelAfter){
			if (panelAfter==="groupchat"){
				$("#groupChatNotification").hide()
			}
		}
 
		onConfigsChanged() {
			this.updatePanelInfo();
		}

		createInvModal(){
			const modalString = `
				<div id="groupChatInvitationsModal" class="modal fade" role="dialog" tabindex="-1"">
				    <div class="modal-dialog modal-dialog-centered" role="document">
				        <div id="groupChatInvitationsModalInner" class="modal-content">
							<div id="groupChatModalHeader" class="modal-header text-center">
								<h3 class="modal-title w-100"><u>Pending Invitations</u></h3>
							</div>
				            <div class="modal-body">
				                <div id="groupChatInvitationsModalList" class="overflow-auto"></div>
				            </div>
				        </div>
				    </div>
				</div>
			`

			const modalElement = $.parseHTML(modalString)
			$(document.body).append(modalElement)
		}

		addInvitationsToModal(){
			const invitationsModalList = $("#groupChatInvitationsModalList")
			invitationsModalList.empty()

			for (const [groupName, inviter] of Object.entries(this.invitations)){
				const newItemString = `<div class="d-flex justify-content-between rounded-pill groupChatInvitation"><span class="groupChatInvData">${groupName} | ${inviter}</span><div><span data-groupName="${groupName}" class="groupChatInvCheck">✔️</span> | <span data-groupName="${groupName}" class="groupChatInvCross">❌</span></div></div>`
				const newItemElement = $.parseHTML(newItemString)
				invitationsModalList.append(newItemElement)
			}

			$(".groupChatInvCheck").attr("onclick", "IdlePixelPlus.plugins.groupChat.acceptInvitation(this.getAttribute('data-groupName'))")
			$(".groupChatInvCross").attr("onclick", "IdlePixelPlus.plugins.groupChat.rejectInvitation(this.getAttribute('data-groupName'))")
		}

		showInvitationModal(){
			document.body.scrollTop = document.documentElement.scrollTop = 0;

			this.addInvitationsToModal()

			$('#groupChatInvitationsModal').modal('show')
		}
 
		onCustomMessageReceived(player, content, callbackId) {
			const customData = Customs.parseCustom(player, content, callbackId)
			if(customData.plugin==="groupchat") {
				if(customData.command==="chat"){
					this.handleReceivedChat(customData)
				} else if (customData.command==="invite"){
					this.onInviteReceived(customData.player, customData.payload, customData.callbackId)
				} else if (customData.command==="accept"){
					this.onAcceptedInvite(customData.player, customData.payload)
				} else if (customData.command==="reject"){
					this.onRejectedInvite(customData.player, customData.payload)
				} else if (customData.command==="unaccept"){
					this.onUnacceptInvite(customData.player, customData.payload)
				} else if (customData.command==="join"){
					this.addGroup(customData.player, customData.payload)
				}
			}
		}

		handleReceivedChat(data){
			const player = data.player
			const splitData = data.payload.split(";")
			const groupName = splitData[0]
			const givenPassword = splitData[1]
			const message = splitData.slice(2).join(";")

			if(!(groupName in this.groups)){console.log(`Group ${groupName} doesn't exist.`); return}

			const correctPassword = this.groups[groupName].password
			if(givenPassword!==correctPassword){console.log(`Incorrct password given`); return}

			if (!(this.groups[groupName].members.includes(player))){
				this.addPlayerToGroup(player, groupName)
			}

			const newMessageString = `<div class=""><span class="color-green">${Chat._get_time()}</span><span><strong>${player}: </strong></span><span>${sanitize_input(message)}</span></div>`

			if(!(groupName in this.chats)){this.chats[groupName] = []}

			this.chats[groupName].push(newMessageString)

			if(this.activeGroup.name === groupName){
				this.addMessageToChat(newMessageString)
			}
		}
	
		updatePanelInfo(){
			this.updateGroupsList()
		}
	
		createNotification(){
			const notificationString = `
			<div id="groupChatNotification" class="notification hover" onclick="IdlePixelPlus.setPanel('groupchat')">
        		<img src="https://d1xsc8x7nc5q8t.cloudfront.net/images/meteor_radar_detector.png" class="w20" alt="">
        		<span class="font-small color-yellow">Group Chat</span>
    		</div>
			`

			const notificationElement = $.parseHTML(notificationString)
			const notificationBar = $("#notifications-area")

			notificationBar.append(notificationElement)
			$("#groupChatNotification").hide()
		}

		showNotification(){
			if(Globals.currentPanel === "panel-groupchat"){return;}
			
			$("#groupChatNotification").show()
		}

		sendGroupChat(chatMessage){
			if(!this.activeGroup){console.log("No active Group!"); return}
			const password = this.activeGroup.password
			const memberList = this.activeGroup.members
			const groupName = this.activeGroup.name

			memberList.forEach(member => {
				Customs.sendBasicCustom(member, "groupchat", "chat", `${groupName};${password};${chatMessage}`)
			})
		}

		sendGroupChatButton(){
			const chatIn = $("#groupChatChatIn")
			const chatMessage = chatIn.val()
			chatIn.val("")
			
			this.sendGroupChat(chatMessage)
		}

		addMessageToChat(messageText){
			const chatBox = $("#groupChatChatBox")

			const messageElement = $.parseHTML(messageText)
			chatBox.append(messageElement);

			chatBox.scrollTop(chatBox[0].scrollHeight);

			this.showNotification()
		}

		genPass(){
			return Math.random().toString(36).slice(2)
		}

		createGroup(groupName){
			if (groupName in this.groups){
				console.log("Group already exists")
				return
			}

			this.groups[groupName] = {
				name: groupName,
				members: [window["var_username"]],
				invited_members: [],
				password: this.genPass(),
				owner: window["var_username"]
			}

			this.updateGroupsList()
			this.saveData()
		}

		joinGroup(group_name){
			const loadedGroup = this.groups[group_name]
			this.activeGroup = {
				name: group_name,
				members: loadedGroup.members,
				password: loadedGroup.password,
				owner: loadedGroup.owner,
				online_members: new Set(),
			}

			this.updateMembersList()

			$("#groupChatChatModule").show()
			$(".groupChatInGroupButton").show()

			const ownerButtons = $(".groupChatOwnerButton")

			if (this.activeGroup.owner === window["var_username"]){
				ownerButtons.show()
			} else {
				ownerButtons.hide()
			}

			const chatBox = $("#groupChatChatBox")

			chatBox.empty()

			if(!(group_name in this.chats)){return}

			this.chats[group_name].forEach(chatMessage=>{
				this.addMessageToChat(chatMessage)
			})

			chatBox.scrollTop(chatBox[0].scrollHeight);
		}

		addPlayerToGroup(player, group){
			if (this.groups[group].members.includes(player)){return}
			this.groups[group].members.push(player)

			this.updateMembersList()
			this.saveData()
		}

		getGroups(){
			return Object.keys(this.groups)
		}

		updateGroupsList(){
			const groups = this.getGroups()
			const groupSelector = $("#groupChatGroupSelector")
			groupSelector.empty()

			groupSelector.append(`<option disabled selected value=""> -- select a group -- </option>`)

			groups.forEach(groupName=>{
				groupSelector.append(`<option value="${groupName}">${groupName}</option>`)
			})
		}

		updateMembersList(){
			const membersDisplay = $("#groupChatMembersContainer")
			membersDisplay.empty()
			membersDisplay.append(`<span>Members: </span>`)
			this.activeGroup.members.forEach(member=>{
				membersDisplay.append(`<span class="rounded-pill groupChatMember">&nbsp ${member} &nbsp</span>`)
			})
		}

		addPlayertoInvited(player, group){
			this.groups[group].invited_members.push(player)
			this.saveData()
		}

		sendInvitation(player){
			if(!this.activeGroup){return}
			if(this.activeGroup.owner !== window["var_username"]){
				console.log("Only the owner can invite players.")
				return
			}
			if(this.activeGroup.members.length >7){
				console.log("Group too big to invite more players!")
				return
			}

			IdlePixelPlus.sendCustomMessage(player, {
				content: `groupchat:invite:${this.activeGroup.name}`,
				onResponse: function(player, content, callbackId) {
					IdlePixelPlus.plugins.groupChat.addPlayertoInvited(player, content)
					console.log("Player invited!")
					return true;
					},
				onOffline: function(player, content) {
					console.log("Cannot invite offline player!")
					return true;
					},
				timeout: 2000 // callback expires after 2 seconds
			});

		}

		onInviteReceived(player, groupName, callback){
			this.invitations[groupName] = player
			this.updateInvitationNotification()
			IdlePixelPlus.sendCustomMessage(player, {
				content: `${groupName}`,
				callbackId: callback,
				onResponse: function(player, content, callbackId) {return true;},
				onOffline: function(player, content) {return true;},
				timeout: 2000 // callback expires after 2 seconds
			});
			this.saveData()
		}

		acceptInvitation(groupName){
			Customs.sendBasicCustom(this.invitations[groupName], "groupchat", "accept", groupName)
			delete this.invitations[groupName]
			this.addInvitationsToModal()
			this.updateInvitationNotification()
			this.saveData()
		}

		rejectInvitation(groupName){
			Customs.sendBasicCustom(this.invitations[groupName], "groupchat", "reject", `${groupName};Player rejected invitation.`)
			delete this.invitations[groupName]
			this.addInvitationsToModal()
			this.updateInvitationNotification()
			this.saveData()
		}

		onAcceptedInvite(player, groupName){
			if(!(groupName in this.groups)){return}
			if (this.groups[groupName].members.length >= 8){
				console.log("Group too big!")
				Customs.sendBasicCustom(player, "groupchat", "unaccept", "Group has too many members")
				return
			}

			if(!this.groups[groupName].invited_members.includes(player)){console.log("Univited player"); return;}

			this.groups[groupName].invited_members = this.groups[groupName].invited_members.filter(item => item !== player)
			this.addPlayerToGroup(player, groupName)

			let data_string = `${groupName};${this.groups[groupName]["password"]};`

			let membersString = ""
			this.groups[groupName].members.forEach(member=>{
				membersString+=`,${member}`
			})
			membersString = membersString.slice(1)
			data_string += membersString
			Customs.sendBasicCustom(player, "groupchat", "join", data_string)
			this.saveData()
		}

		onRejectedInvite(player, data){
			const splitData = data.split(";")
			const groupName = splitData[0]
			const reason = splitData.slice(1).join(";")
			this.groups[groupName].invited_members = this.groups[groupName].invited_members.filter(item => item !== player)
			console.log(`${player} could not be added to ${groupName} for reason: ${reason}`)
			this.saveData()
		}

		onUnacceptInvite(player, data){
			const splitData = data.split(";")
			const groupName = splitData[0]
			const reason = splitData.slice(1).join(";")
			console.log(`Could not be added to ${groupName} for reason: ${reason}`)
		}

		createGroupButton(){
			const newGroup = prompt("Group Name:")
			this.createGroup(newGroup)
		}

		shareRaidButton(){
			const raidCode = $("#raids-team-panel-uuid").text()
			if (raidCode===""){return}
			this.sendGroupChat(raidCode)
		}

		inviteButton(){
			const newPlayer = prompt("Player Name:")
			this.sendInvitation(newPlayer)
		}

		uninviteButton(){
			const player = prompt("Player Name:")
			const groupName = this.activeGroup.name

			this.groups[groupName].invited_members = this.groups[groupName].invited_members.filter(item => item !== player)
			this.saveData()
		}

		addGroup(player, data){
			const splitData = data.split(";")
			const groupName = splitData[0]
			const password = splitData[1]
			const membersList = splitData[2].split(",")

			if (groupName in this.groups){
				console.log("Group already exists")
				return
			}

			this.groups[groupName] = {
				name: groupName,
				members: membersList,
				invited_members: [],
				password: password,
				owner: player
			}

			this.updateGroupsList()
			this.saveData()
		}

		updateInvitationNotification(){
			const notificationButton = $("#groupChatGroupNotification")
			if (Object.keys(this.invitations).length){
				notificationButton.removeClass("groupChatGroupNotificationInactive")
				notificationButton.addClass("groupChatGroupNotificationActive")
			} else {
				notificationButton.removeClass("groupChatGroupNotificationActive")
				notificationButton.addClass("groupChatGroupNotificationInactive")
			}
		}
	}

	const plugin = new GroupChatPlugin();
	IdlePixelPlus.registerPlugin(plugin);
 
})();