kawa extras

Import/Export settings injected as a SigMod tab

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         kawa extras
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Import/Export settings injected as a SigMod tab
// @author       kawasaki
// @license      MIT
// @match        *://one.sigmally.com/*
// @icon         https://i.imgur.com/U6RhZAt.png
// @grant        none
// @run-at       document-idle
// ==/UserScript==
(function() {
    'use strict';

    let excludeBinds = localStorage.getItem('se_exclude_binds') === 'true';

    // ── known sigfix keybind keys to strip when excluding binds ──
    const SIGFIX_BIND_KEYS = [
        'multibox','respawnKey','horizontalLineKey','verticalLineKey',
        'fixedLineKey','doubleKey','tripleKey','quadKey','rapidFeedKey',
        'splitKey','feedKey','macroSplitKey','lineSplitKey',
    ];

    function exportSettings() {
        const out = { version: '1.0', timestamp: new Date().toISOString() };

        // SigMod
        try {
            const raw = localStorage.getItem('SigModClient-settings');
            if (raw) {
                let d = JSON.parse(raw);
                if (excludeBinds) {
                    d = JSON.parse(JSON.stringify(d));
                    if (d.macros)   { d.macros.keys  = {}; d.macros.mouse = {}; }
                    if (d.keybinds) { d.keybinds = {}; }
                }
                out.sigmod = { key: 'SigModClient-settings', data: d };
            }
        } catch(e) {}

        // SigFix
        try {
            const raw = localStorage.getItem('sigfix');
            if (raw) {
                let d = JSON.parse(raw);
                if (excludeBinds) {
                    d = JSON.parse(JSON.stringify(d));
                    SIGFIX_BIND_KEYS.forEach(k => delete d[k]);
                }
                out.sigfix = { key: 'sigfix', data: d };
            }
        } catch(e) {}


        const blob = new Blob([JSON.stringify(out, null, 2)], { type: 'application/json' });
        const url  = URL.createObjectURL(blob);
        const a    = document.createElement('a');
        a.href     = url;
        a.download = `sigmally-settings-${Date.now()}.json`;
        a.click();
        URL.revokeObjectURL(url);
        setStatus('✓ Exported!', '#4caf50');
    }

    function importSettings(file) {
        const reader = new FileReader();
        reader.onload = (e) => {
            try {
                const data = JSON.parse(e.target.result);

                if (data.sigmod?.key && data.sigmod?.data) {
                    localStorage.setItem(data.sigmod.key, JSON.stringify(data.sigmod.data));
                    try { if (window.sigmod?.settings) Object.assign(window.sigmod.settings, data.sigmod.data); } catch(e) {}
                }

                if (data.sigfix?.key && data.sigfix?.data) {
                    localStorage.setItem(data.sigfix.key, JSON.stringify(data.sigfix.data));
                    try {
                        if (window.sigfix?.settings) {
                            Object.assign(window.sigfix.settings, data.sigfix.data);
                            window.sigfix.settings.refresh?.();
                        }
                    } catch(e) {}
                }

                setStatus('✓ Applied! Some settings may need a reload.', '#4caf50');
            } catch(err) {
                setStatus('✗ Invalid file.', '#f44336');
            }
        };
        reader.readAsText(file);
    }

    function setStatus(msg, color) {
        const el = document.getElementById('se-status');
        if (!el) return;
        el.textContent  = msg;
        el.style.color  = color;
        setTimeout(() => { el.textContent = ''; }, 4000);
    }

    // ── Tab content HTML ──
    const tabHTML = `
        <div id="se-tab-content" class="mod_tab scroll" style="display:none;opacity:0;flex-direction:column;gap:14px;padding:14px;font-family:Ubuntu,Arial,sans-serif;color:#e0e0ff;box-sizing:border-box;">

            <!-- header -->
            <div style="display:flex;align-items:center;gap:10px;padding-bottom:10px;border-bottom:1px solid #1a1a2e;">
                <img src="https://i.imgur.com/U6RhZAt.png" style="width:28px;height:28px;border-radius:6px;flex-shrink:0;" />
                <div>
                    <div style="font-size:14px;font-weight:bold;color:#aaccff;">kawa extras</div>
                    <div style="font-size:11px;color:#556677;">Import &amp; Export your settings</div>
                </div>
            </div>

            <!-- export card -->
            <div style="background:#0a0a0a;border:1px solid #1e1e1e;border-radius:10px;padding:12px;display:flex;flex-direction:column;gap:8px;">
                <div style="font-size:12px;font-weight:bold;color:#aaccff;">Export Settings</div>
                <div style="font-size:11px;color:#556677;line-height:1.5;">
                    Saves all your SigMod, Sigmally Fixes and other settings to a JSON file you can restore later.
                </div>

                <!-- exclude binds checkbox -->
                <label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;color:#aaa;padding:6px 8px;background:#111;border-radius:7px;border:1px solid #222;">
                    <input type="checkbox" id="se-exclude-binds" ${excludeBinds ? 'checked' : ''} style="width:14px;height:14px;cursor:pointer;accent-color:#5a44eb;" />
                    Exclude keybinds from export
                </label>

                <button id="se-export-btn" style="
                    background:#1b3a5c;border:1px solid #336699;border-radius:7px;
                    color:#fff;padding:9px;cursor:pointer;font-size:13px;
                    font-family:Ubuntu,Arial,sans-serif;transition:opacity 0.15s;
                ">⬆ Export Settings</button>
            </div>

            <!-- import card -->
            <div style="background:#0a0a0a;border:1px solid #1e1e1e;border-radius:10px;padding:12px;display:flex;flex-direction:column;gap:8px;">
                <div style="font-size:12px;font-weight:bold;color:#aaccff;">Import Settings</div>
                <div style="font-size:11px;color:#556677;line-height:1.5;">
                    Load a previously exported JSON file. Settings are applied live where possible.
                </div>
                <label id="se-import-label" style="
                    background:#1b3a5c;border:1px solid #336699;border-radius:7px;
                    color:#fff;padding:9px;cursor:pointer;font-size:13px;
                    text-align:center;display:block;font-family:Ubuntu,Arial,sans-serif;
                    transition:opacity 0.15s;
                ">
                    ⬇ Import Settings
                    <input type="file" id="se-import-input" accept=".json" style="display:none;" />
                </label>
            </div>

            <!-- what gets saved -->
            <div style="background:#0a0a0a;border:1px solid #1e1e1e;border-radius:10px;padding:10px;font-size:11px;color:#556677;line-height:1.8;">
                ✓ &nbsp;SigMod settings &nbsp;·&nbsp; ✓ &nbsp;Sigmally Fixes settings
            </div>

            <span id="se-status" style="font-size:12px;text-align:center;min-height:16px;"></span>
        </div>
    `;

    // ── CSS ──
    const css = `
        #se-tab-content button:hover,
        #se-import-label:hover {
            opacity: 0.82;
        }
        #se-tab-content .mod_tab {
            /* prevent inheriting unwanted mod_tab overrides */
        }
    `;

    function injectTab() {
        const nav     = document.querySelector('.mod_menu_navbar');
        const content = document.querySelector('.mod_menu_content');
        if (!nav || !content) return false;
        if (document.getElementById('se-nav-btn')) return true;

        // inject CSS
        const styleEl = document.createElement('style');
        styleEl.textContent = css;
        document.head.appendChild(styleEl);

        // ── nav button ──
        const navBtn = document.createElement('button');
        navBtn.id        = 'se-nav-btn';
        navBtn.className = 'mod_nav_btn';
        navBtn.style.cssText = 'display:flex;align-items:center;justify-content:center;gap:5px;width:100%;box-sizing:border-box;line-height:1;padding-top:0;padding-bottom:0;';
        navBtn.innerHTML = `
            <img src="https://i.imgur.com/U6RhZAt.png"
                 style="width:16px;height:16px;border-radius:3px;flex-shrink:0;display:block;margin:0;" />
            <span style="line-height:1;display:block;">Sig extras</span>
        `;
        nav.appendChild(navBtn);

        // ── tab div ──
        const tabWrapper = document.createElement('div');
        tabWrapper.innerHTML = tabHTML;
        content.appendChild(tabWrapper);
        const tabDiv = document.getElementById('se-tab-content');

        // nav button click
        navBtn.addEventListener('click', () => {
            // hide all other tabs
            document.querySelectorAll('.mod_tab').forEach(t => {
                t.style.opacity = '0';
                setTimeout(() => { if (t !== tabDiv) t.style.display = 'none'; }, 200);
            });
            // deselect all nav buttons
            document.querySelectorAll('.mod_nav_btn').forEach(b => b.classList.remove('mod_selected'));
            navBtn.classList.add('mod_selected');

            setTimeout(() => {
                tabDiv.style.display = 'flex';
                setTimeout(() => { tabDiv.style.opacity = '1'; }, 10);
            }, 200);
        });

        // ── wire up controls ──

        document.getElementById('se-exclude-binds').addEventListener('change', (e) => {
            excludeBinds = e.target.checked;
            localStorage.setItem('se_exclude_binds', excludeBinds);
        });

        document.getElementById('se-export-btn').addEventListener('click', exportSettings);

        document.getElementById('se-import-input').addEventListener('change', (e) => {
            const file = e.target.files[0];
            if (file) importSettings(file);
            e.target.value = '';
        });

        return true;
    }

    // poll until SigMod nav is ready
    const interval = setInterval(() => {
        if (injectTab()) clearInterval(interval);
    }, 300);

})();