IdlePixel Group Chat

A private group chat panel

目前为 2024-03-07 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name IdlePixel Group Chat
  3. // @namespace lbtechnology.info
  4. // @version 2.0.0
  5. // @description A private group chat panel
  6. // @author Lux-Ferre
  7. // @license MIT
  8. // @match *://idle-pixel.com/login/play*
  9. // @grant none
  10. // @require https://greasyfork.org/scripts/441206-idlepixel/code/IdlePixel+.js?anticache=20220905
  11. // @require https://update.greasyfork.org/scripts/484046/1307183/IdlePixel%2B%20Custom%20Handling.js
  12. // ==/UserScript==
  13. (function() {
  14. 'use strict';
  15.  
  16. class GroupChatPlugin extends IdlePixelPlusPlugin {
  17. constructor() {
  18. super("groupChat", {
  19. about: {
  20. name: GM_info.script.name,
  21. version: GM_info.script.version,
  22. author: GM_info.script.author,
  23. description: GM_info.script.description
  24. },
  25. });
  26. this.groups = {}
  27. this.activeGroup = undefined
  28. this.invitations = {}
  29. this.chats = {}
  30. }
  31.  
  32. saveData(){
  33. const user = window["var_username"]
  34. const groupsJSON = JSON.stringify(this.groups)
  35. localStorage.setItem(`groupChatGroups${user}`, groupsJSON)
  36.  
  37. const invitationsJSON = JSON.stringify(this.invitations)
  38. localStorage.setItem(`groupChatInvitations${user}`, invitationsJSON)
  39. }
  40.  
  41. loadData(){
  42. const user = window["var_username"]
  43. const groupData = localStorage.getItem(`groupChatGroups${user}`)
  44. if (groupData){
  45. this.groups = JSON.parse(groupData)
  46. } else {
  47. this.groups = {}
  48. }
  49.  
  50. const invitationData = localStorage.getItem(`groupChatInvitations${user}`)
  51. if (invitationData){
  52. this.invitations = JSON.parse(invitationData)
  53. } else {
  54. this.invitations = {}
  55. }
  56. }
  57. createPanel(){
  58. IdlePixelPlus.addPanel("groupchat", "Group Chat Panel", function() {
  59. return `
  60. <div class="groupChatUIContainer w-100">
  61. <div id="groupChatInfoModule" class="row groupChatUIModule">
  62. <div class="col">
  63. <div class="row">
  64. <div class="col-4 text-end align-self-center groupChatInfoContainer">
  65. <div class="row gx-0">
  66. <div id="groupChatGroupNotification" class="col-1 text-center align-self-center groupChatGroupNotificationInactive" onclick="IdlePixelPlus.plugins.groupChat.showInvitationModal()"><span>!</span></div>
  67. <div class="col-11 align-self-center"><select id="groupChatGroupSelector" class="w-100"></select></div>
  68. </div>
  69. </div>
  70. <div class="col-8 d-flex groupChatInfoContainer">
  71. <div id="groupChatMembersContainer" class="d-flex align-items-center"><span>Members:</span></div>
  72. </div>
  73. </div>
  74. </div>
  75. </div>
  76. <div id="groupChatChatModule" class="row groupChatUIModule">
  77. <div class="col">
  78. <div class="row">
  79. <div id="groupChatChatFormContainer" class="col">
  80. <div id="groupChatChatBox" class="overflow-auto"></div>
  81. <form onsubmit="event.preventDefault(); IdlePixelPlus.plugins.groupChat.sendGroupChatButton();">
  82. <div class="row d-flex flex-fill">
  83. <div class="col-11"><input id="groupChatChatIn" class="form-control w-100" type="text" /></div>
  84. <div class="col-1"><input id="groupChatChatButton" class="w-100 h-100" type="submit" value="Send" /></div>
  85. </div>
  86. </form>
  87. </div>
  88. </div>
  89. </div>
  90. </div>
  91. <div id="groupChatSettingsModules" class="row groupChatUIModule">
  92. <div class="col">
  93. <div class="row">
  94. <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>
  95. </div>
  96. </div>
  97. </div>
  98. </div>
  99. `
  100. });
  101. }
  102.  
  103. addStyles(){
  104. let backgroundColour
  105. let textColour
  106.  
  107. if ("ui-tweaks" in IdlePixelPlus.plugins){
  108. backgroundColour = IdlePixelPlus.plugins["ui-tweaks"].config["color-chat-area"]
  109. textColour = IdlePixelPlus.plugins["ui-tweaks"].config["font-color-chat-area"]
  110. } else {
  111. backgroundColour = "white"
  112. textColour = "black"
  113. }
  114.  
  115. $("head").append(`
  116. <style id="styles-groupchat">
  117. #groupChatInvitationsModalInner {
  118. background-color: ${backgroundColour};
  119. color: ${textColour};
  120. }
  121.  
  122. .groupChatUIModule {
  123. border: outset 2px;
  124. }
  125.  
  126. .groupChatUIContainer {
  127. width: 100%;
  128. height: 100%;
  129. padding: 5px;
  130. margin: 0;
  131. }
  132.  
  133. #groupChatChatBox {
  134. width: 100%;
  135. height: 70vh;
  136. margin-top: 10px;
  137. }
  138.  
  139. #groupChatChatBox {
  140. border: inset 1px;
  141. }
  142.  
  143. .groupChatGroupNotificationActive {
  144. color: red;
  145. border: outset;
  146. border-color: red;
  147. }
  148.  
  149. .groupChatGroupNotificationInactive {
  150. color: gray;
  151. border: outset;
  152. border-color: gray;
  153. }
  154.  
  155. .groupChatInfoContainer {
  156. border: inset;
  157. }
  158.  
  159. #groupChatSelectGroupDrop {
  160. border: solid;
  161. border-width: 1px;
  162. }
  163.  
  164. .groupChatMember {
  165. margin: 0px 3px;
  166. border: 1px groove;
  167. }
  168.  
  169. #groupChatGroupNotification {
  170. cursor: pointer;
  171. }
  172.  
  173. .groupChatInvitation {
  174. background-color: RGBA(1, 150, 150, 0.5);
  175. margin-bottom: 2px;
  176. }
  177.  
  178. .groupChatInvCheck {
  179. cursor: pointer;
  180. }
  181.  
  182. .groupChatInvCross {
  183. cursor: pointer;
  184. margin-right: 5px;
  185. }
  186.  
  187. .groupChatInvData {
  188. margin-left: 10px;
  189. }
  190.  
  191. #groupChatModalHeader {
  192. padding: calc(var(--bs-modal-padding) - var(--bs-modal-header-gap) * .5);
  193. background-color: var(--bs-modal-header-bg);
  194. border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);
  195. border-top-right-radius: var(--bs-modal-inner-border-radius);
  196. border-top-left-radius: var(--bs-modal-inner-border-radius);
  197. }
  198.  
  199. #groupChatInvitationsModal .modal-body {
  200. overflow-y: auto;
  201. }
  202. </style>
  203. `)
  204. }
  205. onLogin() {
  206. const onlineCount = $(".top-bar .gold:not(#top-bar-admin-link)");
  207. onlineCount.before(`
  208. <a href="#" class="hover float-end link-no-decoration" onclick="event.preventDefault(); IdlePixelPlus.setPanel('groupchat')" title="Group Chat Panel">Group Chat&nbsp;&nbsp;&nbsp;</a>
  209. `);
  210. this.loadData()
  211. this.createPanel()
  212. this.addStyles()
  213. this.createNotification()
  214. this.updatePanelInfo()
  215. this.createInvModal()
  216. this.updateInvitationNotification()
  217.  
  218. $('#groupChatGroupSelector').on('change', function() {
  219. IdlePixelPlus.plugins.groupChat.joinGroup(this.value)
  220. });
  221.  
  222. $("#groupChatChatModule").hide()
  223. $(".groupChatInGroupButton").hide()
  224. }
  225.  
  226. onPanelChanged(panelBefore, panelAfter){
  227. if (panelAfter==="groupchat"){
  228. $("#groupChatNotification").hide()
  229. }
  230. }
  231. onConfigsChanged() {
  232. this.updatePanelInfo();
  233. }
  234.  
  235. createInvModal(){
  236. const modalString = `
  237. <div id="groupChatInvitationsModal" class="modal fade" role="dialog" tabindex="-1"">
  238. <div class="modal-dialog modal-dialog-centered" role="document">
  239. <div id="groupChatInvitationsModalInner" class="modal-content">
  240. <div id="groupChatModalHeader" class="modal-header text-center">
  241. <h3 class="modal-title w-100"><u>Pending Invitations</u></h3>
  242. </div>
  243. <div class="modal-body">
  244. <div id="groupChatInvitationsModalList" class="overflow-auto"></div>
  245. </div>
  246. </div>
  247. </div>
  248. </div>
  249. `
  250.  
  251. const modalElement = $.parseHTML(modalString)
  252. $(document.body).append(modalElement)
  253. }
  254.  
  255. addInvitationsToModal(){
  256. const invitationsModalList = $("#groupChatInvitationsModalList")
  257. invitationsModalList.empty()
  258.  
  259. for (const [groupName, inviter] of Object.entries(this.invitations)){
  260. 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>`
  261. const newItemElement = $.parseHTML(newItemString)
  262. invitationsModalList.append(newItemElement)
  263. }
  264.  
  265. $(".groupChatInvCheck").attr("onclick", "IdlePixelPlus.plugins.groupChat.acceptInvitation(this.getAttribute('data-groupName'))")
  266. $(".groupChatInvCross").attr("onclick", "IdlePixelPlus.plugins.groupChat.rejectInvitation(this.getAttribute('data-groupName'))")
  267. }
  268.  
  269. showInvitationModal(){
  270. document.body.scrollTop = document.documentElement.scrollTop = 0;
  271.  
  272. this.addInvitationsToModal()
  273.  
  274. $('#groupChatInvitationsModal').modal('show')
  275. }
  276. onCustomMessageReceived(player, content, callbackId) {
  277. const customData = Customs.parseCustom(player, content, callbackId)
  278. if(customData.plugin==="groupchat") {
  279. if(customData.command==="chat"){
  280. this.handleReceivedChat(customData)
  281. } else if (customData.command==="invite"){
  282. this.onInviteReceived(customData.player, customData.payload, customData.callbackId)
  283. } else if (customData.command==="accept"){
  284. this.onAcceptedInvite(customData.player, customData.payload)
  285. } else if (customData.command==="reject"){
  286. this.onRejectedInvite(customData.player, customData.payload)
  287. } else if (customData.command==="unaccept"){
  288. this.onUnacceptInvite(customData.player, customData.payload)
  289. } else if (customData.command==="join"){
  290. this.addGroup(customData.player, customData.payload)
  291. }
  292. }
  293. }
  294.  
  295. handleReceivedChat(data){
  296. const player = data.player
  297. const splitData = data.payload.split(";")
  298. const groupName = splitData[0]
  299. const givenPassword = splitData[1]
  300. const message = splitData.slice(2).join(";")
  301.  
  302. if(!(groupName in this.groups)){console.log(`Group ${groupName} doesn't exist.`); return}
  303.  
  304. const correctPassword = this.groups[groupName].password
  305. if(givenPassword!==correctPassword){console.log(`Incorrct password given`); return}
  306.  
  307. if (!(this.groups[groupName].members.includes(player))){
  308. this.addPlayerToGroup(player, groupName)
  309. }
  310.  
  311. const newMessageString = `<div class=""><span class="color-green">${Chat._get_time()}</span><span><strong>${player}: </strong></span><span>${sanitize_input(message)}</span></div>`
  312.  
  313. if(!(groupName in this.chats)){this.chats[groupName] = []}
  314.  
  315. this.chats[groupName].push(newMessageString)
  316.  
  317. if(this.activeGroup.name === groupName){
  318. this.addMessageToChat(newMessageString)
  319. }
  320. }
  321. updatePanelInfo(){
  322. this.updateGroupsList()
  323. }
  324. createNotification(){
  325. const notificationString = `
  326. <div id="groupChatNotification" class="notification hover" onclick="IdlePixelPlus.setPanel('groupchat')">
  327. <img src="https://d1xsc8x7nc5q8t.cloudfront.net/images/meteor_radar_detector.png" class="w20" alt="">
  328. <span class="font-small color-yellow">Group Chat</span>
  329. </div>
  330. `
  331.  
  332. const notificationElement = $.parseHTML(notificationString)
  333. const notificationBar = $("#notifications-area")
  334.  
  335. notificationBar.append(notificationElement)
  336. $("#groupChatNotification").hide()
  337. }
  338.  
  339. showNotification(){
  340. if(Globals.currentPanel === "panel-groupchat"){return;}
  341. $("#groupChatNotification").show()
  342. }
  343.  
  344. sendGroupChat(chatMessage){
  345. if(!this.activeGroup){console.log("No active Group!"); return}
  346. const password = this.activeGroup.password
  347. const memberList = this.activeGroup.members
  348. const groupName = this.activeGroup.name
  349.  
  350. memberList.forEach(member => {
  351. Customs.sendBasicCustom(member, "groupchat", "chat", `${groupName};${password};${chatMessage}`)
  352. })
  353. }
  354.  
  355. sendGroupChatButton(){
  356. const chatIn = $("#groupChatChatIn")
  357. const chatMessage = chatIn.val()
  358. chatIn.val("")
  359. this.sendGroupChat(chatMessage)
  360. }
  361.  
  362. addMessageToChat(messageText){
  363. const chatBox = $("#groupChatChatBox")
  364.  
  365. const messageElement = $.parseHTML(messageText)
  366. chatBox.append(messageElement);
  367.  
  368. chatBox.scrollTop(chatBox[0].scrollHeight);
  369.  
  370. this.showNotification()
  371. }
  372.  
  373. genPass(){
  374. return Math.random().toString(36).slice(2)
  375. }
  376.  
  377. createGroup(groupName){
  378. if (groupName in this.groups){
  379. console.log("Group already exists")
  380. return
  381. }
  382.  
  383. this.groups[groupName] = {
  384. name: groupName,
  385. members: [window["var_username"]],
  386. invited_members: [],
  387. password: this.genPass(),
  388. owner: window["var_username"]
  389. }
  390.  
  391. this.updateGroupsList()
  392. this.saveData()
  393. }
  394.  
  395. joinGroup(group_name){
  396. const loadedGroup = this.groups[group_name]
  397. this.activeGroup = {
  398. name: group_name,
  399. members: loadedGroup.members,
  400. password: loadedGroup.password,
  401. owner: loadedGroup.owner,
  402. online_members: new Set(),
  403. }
  404.  
  405. this.updateMembersList()
  406.  
  407. $("#groupChatChatModule").show()
  408. $(".groupChatInGroupButton").show()
  409.  
  410. const ownerButtons = $(".groupChatOwnerButton")
  411.  
  412. if (this.activeGroup.owner === window["var_username"]){
  413. ownerButtons.show()
  414. } else {
  415. ownerButtons.hide()
  416. }
  417.  
  418. const chatBox = $("#groupChatChatBox")
  419.  
  420. chatBox.empty()
  421.  
  422. if(!(group_name in this.chats)){return}
  423.  
  424. this.chats[group_name].forEach(chatMessage=>{
  425. this.addMessageToChat(chatMessage)
  426. })
  427.  
  428. chatBox.scrollTop(chatBox[0].scrollHeight);
  429. }
  430.  
  431. addPlayerToGroup(player, group){
  432. if (this.groups[group].members.includes(player)){return}
  433. this.groups[group].members.push(player)
  434.  
  435. this.updateMembersList()
  436. this.saveData()
  437. }
  438.  
  439. getGroups(){
  440. return Object.keys(this.groups)
  441. }
  442.  
  443. updateGroupsList(){
  444. const groups = this.getGroups()
  445. const groupSelector = $("#groupChatGroupSelector")
  446. groupSelector.empty()
  447.  
  448. groupSelector.append(`<option disabled selected value=""> -- select a group -- </option>`)
  449.  
  450. groups.forEach(groupName=>{
  451. groupSelector.append(`<option value="${groupName}">${groupName}</option>`)
  452. })
  453. }
  454.  
  455. updateMembersList(){
  456. const membersDisplay = $("#groupChatMembersContainer")
  457. membersDisplay.empty()
  458. membersDisplay.append(`<span>Members: </span>`)
  459. this.activeGroup.members.forEach(member=>{
  460. membersDisplay.append(`<span class="rounded-pill groupChatMember">&nbsp ${member} &nbsp</span>`)
  461. })
  462. }
  463.  
  464. addPlayertoInvited(player, group){
  465. this.groups[group].invited_members.push(player)
  466. this.saveData()
  467. }
  468.  
  469. sendInvitation(player){
  470. if(!this.activeGroup){return}
  471. if(this.activeGroup.owner !== window["var_username"]){
  472. console.log("Only the owner can invite players.")
  473. return
  474. }
  475. if(this.activeGroup.members.length >7){
  476. console.log("Group too big to invite more players!")
  477. return
  478. }
  479.  
  480. IdlePixelPlus.sendCustomMessage(player, {
  481. content: `groupchat:invite:${this.activeGroup.name}`,
  482. onResponse: function(player, content, callbackId) {
  483. IdlePixelPlus.plugins.groupChat.addPlayertoInvited(player, content)
  484. console.log("Player invited!")
  485. return true;
  486. },
  487. onOffline: function(player, content) {
  488. console.log("Cannot invite offline player!")
  489. return true;
  490. },
  491. timeout: 2000 // callback expires after 2 seconds
  492. });
  493.  
  494. }
  495.  
  496. onInviteReceived(player, groupName, callback){
  497. this.invitations[groupName] = player
  498. this.updateInvitationNotification()
  499. IdlePixelPlus.sendCustomMessage(player, {
  500. content: `${groupName}`,
  501. callbackId: callback,
  502. onResponse: function(player, content, callbackId) {return true;},
  503. onOffline: function(player, content) {return true;},
  504. timeout: 2000 // callback expires after 2 seconds
  505. });
  506. this.saveData()
  507. }
  508.  
  509. acceptInvitation(groupName){
  510. Customs.sendBasicCustom(this.invitations[groupName], "groupchat", "accept", groupName)
  511. delete this.invitations[groupName]
  512. this.addInvitationsToModal()
  513. this.updateInvitationNotification()
  514. this.saveData()
  515. }
  516.  
  517. rejectInvitation(groupName){
  518. Customs.sendBasicCustom(this.invitations[groupName], "groupchat", "reject", `${groupName};Player rejected invitation.`)
  519. delete this.invitations[groupName]
  520. this.addInvitationsToModal()
  521. this.updateInvitationNotification()
  522. this.saveData()
  523. }
  524.  
  525. onAcceptedInvite(player, groupName){
  526. if(!(groupName in this.groups)){return}
  527. if (this.groups[groupName].members.length >= 8){
  528. console.log("Group too big!")
  529. Customs.sendBasicCustom(player, "groupchat", "unaccept", "Group has too many members")
  530. return
  531. }
  532.  
  533. if(!this.groups[groupName].invited_members.includes(player)){console.log("Univited player"); return;}
  534.  
  535. this.groups[groupName].invited_members = this.groups[groupName].invited_members.filter(item => item !== player)
  536. this.addPlayerToGroup(player, groupName)
  537.  
  538. let data_string = `${groupName};${this.groups[groupName]["password"]};`
  539.  
  540. let membersString = ""
  541. this.groups[groupName].members.forEach(member=>{
  542. membersString+=`,${member}`
  543. })
  544. membersString = membersString.slice(1)
  545. data_string += membersString
  546. Customs.sendBasicCustom(player, "groupchat", "join", data_string)
  547. this.saveData()
  548. }
  549.  
  550. onRejectedInvite(player, data){
  551. const splitData = data.split(";")
  552. const groupName = splitData[0]
  553. const reason = splitData.slice(1).join(";")
  554. this.groups[groupName].invited_members = this.groups[groupName].invited_members.filter(item => item !== player)
  555. console.log(`${player} could not be added to ${groupName} for reason: ${reason}`)
  556. this.saveData()
  557. }
  558.  
  559. onUnacceptInvite(player, data){
  560. const splitData = data.split(";")
  561. const groupName = splitData[0]
  562. const reason = splitData.slice(1).join(";")
  563. console.log(`Could not be added to ${groupName} for reason: ${reason}`)
  564. }
  565.  
  566. createGroupButton(){
  567. const newGroup = prompt("Group Name:")
  568. this.createGroup(newGroup)
  569. }
  570.  
  571. shareRaidButton(){
  572. const raidCode = $("#raids-team-panel-uuid").text()
  573. if (raidCode===""){return}
  574. this.sendGroupChat(raidCode)
  575. }
  576.  
  577. inviteButton(){
  578. const newPlayer = prompt("Player Name:")
  579. this.sendInvitation(newPlayer)
  580. }
  581.  
  582. uninviteButton(){
  583. const player = prompt("Player Name:")
  584. const groupName = this.activeGroup.name
  585.  
  586. this.groups[groupName].invited_members = this.groups[groupName].invited_members.filter(item => item !== player)
  587. this.saveData()
  588. }
  589.  
  590. addGroup(player, data){
  591. const splitData = data.split(";")
  592. const groupName = splitData[0]
  593. const password = splitData[1]
  594. const membersList = splitData[2].split(",")
  595.  
  596. if (groupName in this.groups){
  597. console.log("Group already exists")
  598. return
  599. }
  600.  
  601. this.groups[groupName] = {
  602. name: groupName,
  603. members: membersList,
  604. invited_members: [],
  605. password: password,
  606. owner: player
  607. }
  608.  
  609. this.updateGroupsList()
  610. this.saveData()
  611. }
  612.  
  613. updateInvitationNotification(){
  614. const notificationButton = $("#groupChatGroupNotification")
  615. if (Object.keys(this.invitations).length){
  616. notificationButton.removeClass("groupChatGroupNotificationInactive")
  617. notificationButton.addClass("groupChatGroupNotificationActive")
  618. } else {
  619. notificationButton.removeClass("groupChatGroupNotificationActive")
  620. notificationButton.addClass("groupChatGroupNotificationInactive")
  621. }
  622. }
  623. }
  624.  
  625. const plugin = new GroupChatPlugin();
  626. IdlePixelPlus.registerPlugin(plugin);
  627. })();