您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A userscript library for cross-window communication via the userscript storage
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/470418/1582512/CommLinkjs.js
/* CommLink.js - Version: 1.0.3 - Author: Haka - Description: A userscript library for cross-window communication via the userscript storage - GitHub: https://github.com/AugmentedWeb/CommLink */ class CommLinkHandler { constructor(commlinkID, configObj) { this.commlinkID = commlinkID; this.singlePacketResponseWaitTime = configObj?.singlePacketResponseWaitTime || 1500; this.maxSendAttempts = configObj?.maxSendAttempts || 3; this.statusCheckInterval = configObj?.statusCheckInterval || 1; this.silentMode = configObj?.silentMode || false; this.commlinkValueIndicator = 'commlink-packet-'; this.commands = {}; this.listeners = []; this.greasy = typeof GM === 'object' ? GM : {}; const getFunction = (funcNames, methodName) => { for(const func of funcNames) { if(typeof func === 'function') { return func; } } if(!this.silentMode) { throw new Error(`No valid method found for ${methodName}`); } }; const getValueMethod = getFunction( [ typeof GM_getValue !== 'undefined' ? GM_getValue : undefined, this.greasy?.getValue, configObj?.functions?.getValue ], 'getValue' ); const setValueMethod = getFunction( [ typeof GM_setValue !== 'undefined' ? GM_setValue : undefined, this.greasy?.setValue, configObj?.functions?.setValue ], 'setValue' ); const deleteValueMethod = getFunction( [ typeof GM_deleteValue !== 'undefined' ? GM_deleteValue : undefined, this.greasy?.deleteValue, configObj?.functions?.deleteValue ], 'deleteValue' ); const listValuesMethod = getFunction( [ typeof GM_listValues !== 'undefined' ? GM_listValues : undefined, this.greasy?.listValues, configObj?.functions?.listValues ], 'listValues' ); this.storage = { getValue: async (key) => { return await getValueMethod(key); }, setValue: (key, value) => { return setValueMethod(key, value); }, deleteValue: (key) => { return deleteValueMethod(key); }, listValues: async () => { return await listValuesMethod(); } }; if(typeof GM_info !== 'undefined') { const grants = (GM_info?.script?.grant) || []; const missingGrants = ['getValue', 'setValue', 'deleteValue', 'listValues'] .filter(grant => !grants.some(g => g.endsWith(grant))); if(missingGrants.length > 0 && !this.silentMode) alert(`[CommLink] The following userscript grants are missing: ${missingGrants.join(', ')}. CommLink might not work.`); } this.removeOldPackets(); } async removeOldPackets() { const packets = await this.getStoredPackets(); packets.filter(packet => Date.now() - packet?.date > 2e4) .forEach(packet => this.removePacketByID(packet.id)); } setIntervalAsync(callback, interval = this.statusCheckInterval) { let running = true; async function loop() { while(running) { try { await callback(); await new Promise((resolve) => setTimeout(resolve, interval)); } catch (e) { continue; } } }; loop(); return { stop: () => running = false }; } getUniqueID() { return ([1e7]+-1e3+4e3+-8e3+-1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) ) } getCommKey(packetID) { return this.commlinkValueIndicator + packetID; } async getStoredPackets() { const keys = await this.storage.listValues(); const storedPackets = []; for(const key of keys) { if(key.includes(this.commlinkValueIndicator)) { const value = await this.storage.getValue(key); storedPackets.push(value); } } return storedPackets; } addPacket(packet) { this.storage.setValue(this.getCommKey(packet.id), packet); } removePacketByID(packetID) { this.storage.deleteValue(this.getCommKey(packetID)); } async findPacketByID(packetID) { return await this.storage.getValue(this.getCommKey(packetID)); } editPacket(newPacket) { this.storage.setValue(this.getCommKey(newPacket.id), newPacket); } send(platform, cmd, d) { return new Promise(async resolve => { const packetWaitTimeMs = this.singlePacketResponseWaitTime; const maxAttempts = this.maxSendAttempts; let attempts = 0; for(;;) { attempts++; const packetID = this.getUniqueID(); const attemptStartDate = Date.now(); const packet = { sender: platform, id: packetID, command: cmd, data: d, date: attemptStartDate }; if(!this.silentMode) console.log(`[CommLink Sender] Sending packet! (#${attempts} attempt):`, packet); this.addPacket(packet); for(;;) { const poolPacket = await this.findPacketByID(packetID); const packetResult = poolPacket?.result; if(poolPacket && packetResult) { if(!this.silentMode) console.log(`[CommLink Sender] Got result for a packet (${packetID}):`, packetResult); resolve(poolPacket.result); attempts = maxAttempts; // stop main loop break; } if(!poolPacket || Date.now() - attemptStartDate > packetWaitTimeMs) { break; } await new Promise(res => setTimeout(res, this.statusCheckInterval)); } this.removePacketByID(packetID); if(attempts == maxAttempts) { break; } } return resolve(null); }); } registerSendCommand(name, obj) { this.commands[name] = async data => await this.send(obj?.commlinkID || this.commlinkID , name, obj?.data || data); } registerListener(sender, commandHandler) { const listener = { sender, commandHandler, intervalObj: this.setIntervalAsync(async () => { await this.receivePackets(); }, this.statusCheckInterval), }; this.listeners.push(listener); } async receivePackets() { const packets = await this.getStoredPackets(); for(const packet of packets) { for(const listener of this.listeners) { if(packet.sender === listener.sender && !packet.hasOwnProperty('result')) { try { const result = await listener.commandHandler(packet); packet.result = result; this.editPacket(packet); if(!this.silentMode) { if(packet.result == null) console.log('[CommLink Receiver] Possibly failed to handle packet:', packet); else console.log('[CommLink Receiver] Successfully handled a packet:', packet); } } catch(error) { console.error('[CommLink Receiver] Error handling packet:', error); } } } } } kill() { this.listeners.forEach(listener => listener.intervalObj.stop()); } }