Greasy Fork

Greasy Fork is available in English.

TriX Core Library

Core logic library for the TriX Executor. Not intended for direct installation.

当前为 2025-07-02 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/541461/1618069/TriX%20Core%20Library.js

// ==UserScript==
// @name        TriX Core Library
// @namespace   https://github.com/YourUsername/TriX-Executor
// @version     1.5.0
// @description Core logic library for the TriX Executor. Not intended for direct installation.
// @author      You
// @license     MIT
// ==/UserScript==

const TriX_Core = (function() {
    'use strict';

    // --- Configuration & Constants ---
    const SCRIPT_PREFIX = 'trix_script_';
    const BROADCAST_CHANNEL = 'trix_broadcast_channel';
    const TAB_LIST_KEY = 'trix_active_tabs';
    const HEARTBEAT_INTERVAL = 5000;
    const STALE_TAB_TIMEOUT = 15000;
    const TAB_ID = `tab_${Date.now().toString(36)}_${Math.random().toString(36).substring(2)}`;
    const LOAD_TIME = Date.now();

    // --- TabManager Module ---
    const TabManager = {
        tabs:[], myTabInfo:{}, uiInitialized:false, isMaster:false,
        init(username){this.myTabInfo={id:TAB_ID,username:username,loadTime:LOAD_TIME,lastSeen:Date.now()};GM_addValueChangeListener(TAB_LIST_KEY,(name,old_value,new_value,remote)=>{if(remote)this.pruneAndRefresh(new_value)});this.register();setInterval(()=>this.register(),HEARTBEAT_INTERVAL);window.addEventListener('beforeunload',()=>this.unregister())},
        async register(){let currentTabs=await GM_getValue(TAB_LIST_KEY,[]);const now=Date.now();currentTabs=currentTabs.filter(tab=>now-tab.lastSeen<STALE_TAB_TIMEOUT);this.myTabInfo.lastSeen=now;const myIndex=currentTabs.findIndex(tab=>tab.id===TAB_ID);if(myIndex>-1){currentTabs[myIndex]=this.myTabInfo}else{currentTabs.push(this.myTabInfo)}await GM_setValue(TAB_LIST_KEY,currentTabs);this.pruneAndRefresh(currentTabs)},
        async unregister(){let currentTabs=await GM_getValue(TAB_LIST_KEY,[]);currentTabs=currentTabs.filter(tab=>tab.id!==TAB_ID);await GM_setValue(TAB_LIST_KEY,currentTabs)},
        pruneAndRefresh(tabList){this.tabs=tabList;if(this.uiInitialized)TriX_UI.updateTabCountUI(tabList.length);this.checkMasterStatus()},
        checkMasterStatus(){this.isMaster=this.amIMaster();if(this.isMaster&&!this.uiInitialized){this.initUI()}},
        amIMaster(){if(!this.myTabInfo.username||this.myTabInfo.username.startsWith('Guest_'))return true;const competingTabs=this.tabs.filter(tab=>tab.username===this.myTabInfo.username);if(competingTabs.length===0)return true;const earliestLoadTime=Math.min(...competingTabs.map(tab=>tab.loadTime));return this.myTabInfo.loadTime===earliestLoadTime},
        initUI(){
            if(this.uiInitialized)return;
            console.log('[TriX Core] This tab is the master. Initializing UI.');
            this.uiInitialized=true;
            // FIX: Call the globally exposed UI library object, not a private one.
            TriX_UI.init();
            MultiTab.init();
        }
    };

    // --- ScriptManager, Executor, MultiTab Modules ---
    const ScriptManager = {
        async saveScriptFromEditor() { const name = document.getElementById('trix-save-name').value.trim(); const code = document.getElementById('trix-editor').value; if (!name) return TriX_UI.log('Cannot save: Name is required.', 'error'); if (!code) return TriX_UI.log('Cannot save: Editor is empty.', 'warn'); await GM_setValue(SCRIPT_PREFIX + name, code); TriX_UI.log(`Script '${name}' saved.`, 'info'); this.populateScriptList(SCRIPT_PREFIX + name); },
        async loadScriptToEditor(key) { if (!key) return TriX_UI.log('Invalid script key.', 'error'); const code = await GM_getValue(key, ''); const name = key.replace(SCRIPT_PREFIX, ''); document.getElementById('trix-editor').value = code; document.getElementById('trix-save-name').value = name; TriX_UI.log(`Loaded script: ${name}`, 'info'); TriX_UI.currentScriptName = name; TriX_UI.setActiveScriptItem(key); },
        async deleteCurrentScript() { const name = TriX_UI.currentScriptName; if (!name) return TriX_UI.log('No script loaded to delete.', 'warn'); if (confirm(`Are you sure you want to delete '${name}'?`)) { await GM_deleteValue(SCRIPT_PREFIX + name); TriX_UI.log(`Script '${name}' deleted.`, 'info'); document.getElementById('trix-editor').value = ''; document.getElementById('trix-save-name').value = ''; TriX_UI.currentScriptName = ''; this.populateScriptList(); } },
        async populateScriptList(activeKey = null) { const listEl = document.getElementById('trix-script-list'); listEl.innerHTML = ''; const allKeys = (await GM_listValues()).filter(k => k.startsWith(SCRIPT_PREFIX)); allKeys.sort().forEach(key => { const item = document.createElement('div'); item.className = 'trix-script-item'; item.textContent = key.replace(SCRIPT_PREFIX, ''); item.dataset.scriptKey = key; listEl.appendChild(item); }); TriX_UI.setActiveScriptItem(activeKey || TriX_UI.currentScriptName ? SCRIPT_PREFIX + TriX_UI.currentScriptName : null); }
    };
    const Executor = {
        execute(code) { if (!code.trim()) return TriX_UI.log('Execution skipped: script is empty.', 'warn'); TriX_UI.log('Executing script...', 'info'); try { const TriX = this.createAPI(); const scriptFunction = new Function('TriX', code); scriptFunction(TriX); } catch (e) { TriX_UI.log(`Execution Error: ${e.message}`, 'error'); console.error("TriX Executor Error:", e); } },
        createAPI: () => ({ log: (message, type = 'log') => { const msgStr = typeof message === 'object' ? JSON.stringify(message) : String(message); TriX_UI.log(msgStr, type); }, broadcast: (payload) => { MultiTab.broadcast(payload); }, query: (selector, dataType = 'text') => { const element = document.querySelector(selector); if (!element) return null; switch (dataType) { case 'html': return element.innerHTML; case 'value': return element.value; case 'element': return element; case 'text': default: return element.textContent; } }, queryAll: (selector, dataType = 'text') => { const elements = document.querySelectorAll(selector); return Array.from(elements).map(el => { switch (dataType) { case 'html': return el.innerHTML; case 'value': return el.value; case 'element': return el; case 'text': default: return el.textContent; } }); } })
    };
    const MultiTab = {
        init() { GM_addValueChangeListener(BROADCAST_CHANNEL, this.listener); },
        listener(key, oldValue, newValue, remote) { if (remote && newValue.senderId !== TAB_ID) { TriX_UI.log(`Received broadcast: ${JSON.stringify(newValue.payload)}`, 'broadcast'); } },
        broadcast(payload) { const message = { senderId: TAB_ID, timestamp: Date.now(), payload: payload }; GM_setValue(BROADCAST_CHANNEL, message); TriX_UI.log(`Broadcast sent: ${JSON.stringify(payload)}`, 'broadcast'); }
    };

    return { TabManager, ScriptManager, Executor, MultiTab };
})();