IdlePixel+

Idle-Pixel plugin framework

目前為 2022-03-09 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/441206/1025986/IdlePixel%2B.js

  1. // ==UserScript==
  2. // @name IdlePixel+
  3. // @namespace com.anwinity.idlepixel
  4. // @version 0.0.1
  5. // @description Idle-Pixel plugin framework
  6. // @author Anwinity
  7. // @match https://idle-pixel.com/play.php*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. class IdlePixelPlusPlugin {
  15.  
  16. constructor(id, name, opts) {
  17. if(typeof id !== "string" || typeof name !== "string") {
  18. throw new TypeError("IdlePixelPlusPlugin constructor takes the following arguments: (id:string, name:string, opts?:object)");
  19. }
  20. this.id = id;
  21. this.name = name;
  22. this.opts = opts || {};
  23. }
  24.  
  25. onLogin() { }
  26. onMessageReceived(data) { }
  27. onVariableSet(key, valueBefore, valueAfter) { }
  28. onChat(data) { }
  29. onPanelChanged(panelBefore, panelAfter) { }
  30.  
  31. }
  32.  
  33. class IdlePixelPlus {
  34.  
  35. constructor() {
  36. this.version = "0.0.1";
  37. this.plugins = {};
  38. this.debug = false;
  39. }
  40.  
  41. init() {
  42. const self = this;
  43.  
  44. // hook into websocket messages
  45. const original_open_websocket = window.open_websocket;
  46. window.open_websocket = function() {
  47. original_open_websocket.apply(this, arguments);
  48. const original_onmessage = window.websocket.websocket.onmessage;
  49. window.websocket.websocket.onmessage = function(event) {
  50. original_onmessage.apply(window.websocket.websocket, arguments);
  51. self.onMessageReceived(event.data);
  52. }
  53. }
  54.  
  55. // hook into Items.set, which is where var_ values are set
  56. const original_items_set = Items.set;
  57. Items.set = function(key, value) {
  58. let valueBefore = window["var_"+key];
  59. original_items_set.apply(this, arguments);
  60. let valueAfter = window["var_"+key];
  61. self.onVariableSet(key, valueBefore, valueAfter);
  62. }
  63.  
  64. // hook into switch_panels, which is called when the main panel is changed. This is also used for custom panels.
  65. const original_switch_panels = window.switch_panels;
  66. window.switch_panels = function(id) {
  67. let panelBefore = Globals.currentPanel;
  68. if(panelBefore && panelBefore.startsWith("panel-")) {
  69. panelBefore = panelBefore.substring("panel-".length);
  70. }
  71. self.hideCustomPanels();
  72. original_switch_panels.apply(this, arguments);
  73. let panelAfter = Globals.currentPanel;
  74. if(panelAfter && panelAfter.startsWith("panel-")) {
  75. panelAfter = panelAfter.substring("panel-".length);
  76. }
  77. self.onPanelChanged(panelBefore, panelAfter);
  78. }
  79.  
  80. console.log("IdlePixelPlus initialized");
  81. }
  82.  
  83. registerPlugin(plugin) {
  84. if(!(plugin instanceof IdlePixelPlusPlugin)) {
  85. throw new TypeError("IdlePixelPlus.registerPlugin takes the following arguments: (plugin:IdlePixelPlusPlugin)");
  86. }
  87. if(plugin.id in this.plugins) {
  88. throw new Error(`IdlePixelPlusPlugin with id "${plugin.id}" is already registered. Make sure your plugin id is unique!`);
  89. }
  90.  
  91. // TODO: easy config system
  92. // TODO: custom panels
  93.  
  94. this.plugins[plugin.id] = plugin;
  95. console.log(`IdlePixelPlus registered plugin "${plugin.id}" (${plugin.name})`);
  96. }
  97.  
  98. forEachPlugin(f) {
  99. if(typeof f !== "function") {
  100. throw new TypeError("IdlePixelPlus.forEachPlugin takes the following arguments: (f:function)");
  101. }
  102. Object.values(this.plugins).forEach(plugin => {
  103. try {
  104. f(plugin);
  105. }
  106. catch(err) {
  107. console.error(`Error occurred while executing function for plugin "${plugin.id}."`);
  108. console.error(err);
  109. }
  110. });
  111. }
  112.  
  113. setPanel(panel) {
  114. if(typeof panel !== "string") {
  115. throw new TypeError("IdlePixelPlus.setPanel takes the following arguments: (panel:string)");
  116. }
  117. window.switch_panels(panel);
  118. }
  119.  
  120. sendMessage(message) {
  121. if(typeof message !== "string") {
  122. throw new TypeError("IdlePixelPlus.sendMessage takes the following arguments: (message:string)");
  123. }
  124. if(window.websocket && window.websocket.websocket && window.websocket.websocket.readyState==1) {
  125. window.websocket.websocket.send(message);
  126. }
  127. }
  128.  
  129. hideCustomPanels() {
  130. this.forEachPlugin((plugin) => {
  131. if(plugin.opts.panel) {
  132. let panels = plugin.opts.panel;
  133. if(!Array.isArray(panels)) {
  134. panels = [panels];
  135. }
  136. panels.forEach(panel => {
  137. if(panel.id) {
  138. const el = document.getElementById(`panel-${panel.id}`);
  139. if(el) {
  140. el.style.display = "none";
  141. }
  142. }
  143. });
  144. }
  145. });
  146. }
  147.  
  148. onMessageReceived(data) {
  149. if(this.debug) {
  150. console.log(`IP+ onMessageReceived: ${data}`);
  151. }
  152. if(data) {
  153. this.forEachPlugin((plugin) => {
  154. if(typeof plugin.onMessageReceived === "function") {
  155. plugin.onMessageReceived(data);
  156. }
  157. });
  158. if(data.startsWith("VALID_LOGIN")) {
  159. this.onLogin();
  160. }
  161. else if(data.startsWith("CHAT=")) {
  162. const split = data.substring("CHAT=".length).split("~");
  163. const chatData = {
  164. username: split[0],
  165. tag: null,
  166. sigil: null,
  167. level: split[3],
  168. message: split[4]
  169. };
  170. // CHAT=anwinity~none~none~1565~test
  171. // TODO: none and none, probably for tag and sigil
  172. }
  173. }
  174. }
  175.  
  176. onLogin() {
  177. if(this.debug) {
  178. console.log(`IP+ onLogin`);
  179. }
  180. this.forEachPlugin((plugin) => {
  181. if(typeof plugin.onLogin === "function") {
  182. plugin.onLogin();
  183. }
  184. });
  185. }
  186.  
  187. onVariableSet(key, valueBefore, valueAfter) {
  188. if(this.debug) {
  189. console.log(`IP+ onVariableSet "${key}": "${valueBefore}" -> "${valueAfter}"`);
  190. }
  191. this.forEachPlugin((plugin) => {
  192. if(typeof plugin.onVariableSet === "function") {
  193. plugin.onVariableSet(key, valueBefore, valueAfter);
  194. }
  195. });
  196. }
  197.  
  198. onChat(data) {
  199. if(this.debug) {
  200. console.log(`IP+ onChat`, data);
  201. }
  202. this.forEachPlugin((plugin) => {
  203. if(typeof plugin.onChat === "function") {
  204. plugin.onChat(data);
  205. }
  206. });
  207. }
  208.  
  209. onPanelChanged(panelBefore, panelAfter) {
  210. if(this.debug) {
  211. console.log(`IP+ onPanelChanged "${panelBefore}" -> "${panelAfter}"`);
  212. }
  213. this.forEachPlugin((plugin) => {
  214. if(typeof plugin.onPanelChanged === "function") {
  215. plugin.onPanelChanged(panelBefore, panelAfter);
  216. }
  217. });
  218. }
  219.  
  220. }
  221.  
  222. // Add to window and init
  223. window.IdlePixelPlusPlugin = IdlePixelPlusPlugin;
  224. window.IdlePixelPlus = new IdlePixelPlus();
  225. window.IdlePixelPlus.init();
  226.  
  227. })();