Torn Radial Utils Library

Core utilities for Torn Radial Menu

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/554536/1688185/Torn%20Radial%20Utils%20Library.js

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Torn Radial Utils Library
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  Core utilities for Torn Radial Menu
// @author       Sensimillia (2168012)
// @grant        GM_xmlhttpRequest
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // ==================== ERROR LOGGER CLASS ====================
    class ErrorLoggerClass {
        constructor() {
            this.logs = [];
            this.maxLogs = 100;
            this.loadLogs();
        }

        log(type, message, error = null) {
            const entry = {
                timestamp: new Date().toISOString(),
                type: type,
                message: message,
                error: error ? {
                    name: error.name,
                    message: error.message,
                    stack: error.stack
                } : null,
                userAgent: navigator.userAgent,
                screenSize: `${window.innerWidth}x${window.innerHeight}`
            };
            
            this.logs.push(entry);
            if (this.logs.length > this.maxLogs) this.logs.shift();
            
            try {
                localStorage.setItem('tornRadialErrorLogs', JSON.stringify(this.logs));
            } catch(e) {
                console.error('Failed to save error logs:', e);
            }
            
            console.error(`[Torn Radial] ${type.toUpperCase()}: ${message}`, error);
        }

        loadLogs() {
            try {
                const stored = localStorage.getItem('tornRadialErrorLogs');
                if (stored) this.logs = JSON.parse(stored);
            } catch(e) {
                console.error('Failed to load error logs:', e);
            }
        }

        getLogs() {
            return this.logs;
        }

        clear() {
            this.logs = [];
            try {
                localStorage.removeItem('tornRadialErrorLogs');
            } catch(e) {
                console.error('Failed to clear error logs:', e);
            }
        }

        exportLogs() {
            const logsText = JSON.stringify(this.logs, null, 2);
            const blob = new Blob([logsText], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `torn-radial-errors-${new Date().toISOString()}.json`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
        }
    }

    // ==================== STORAGE MANAGER CLASS ====================
    class StorageManager {
        get(key, defaultValue) {
            try {
                const item = localStorage.getItem(key);
                return item ? JSON.parse(item) : defaultValue;
            } catch (e) {
                console.error(`Error reading ${key} from localStorage`, e);
                return defaultValue;
            }
        }

        set(key, value) {
            try {
                localStorage.setItem(key, JSON.stringify(value));
                return true;
            } catch (e) {
                console.error(`Error saving ${key} to localStorage`, e);
                return false;
            }
        }

        remove(key) {
            try {
                localStorage.removeItem(key);
                return true;
            } catch (e) {
                console.error(`Error removing ${key} from localStorage`, e);
                return false;
            }
        }

        exportAll() {
            const data = {
                settings: this.get('tornRadialSettings', {}),
                loadouts: this.get('tornRadialLoadouts', {}),
                position: this.get('tornRadialPosition', {}),
                usageStats: this.get('tornRadialUsageStats', {}),
                timers: this.get('tornRadialTimers', []),
                notes: this.get('tornRadialNotes', ''),
                searchHistory: this.get('tornRadialSearchHistory', [])
            };
            
            const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `torn-radial-backup-${new Date().toISOString()}.json`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
        }

        importAll(jsonData) {
            try {
                const data = JSON.parse(jsonData);
                if (data.settings) this.set('tornRadialSettings', data.settings);
                if (data.loadouts) this.set('tornRadialLoadouts', data.loadouts);
                if (data.position) this.set('tornRadialPosition', data.position);
                if (data.usageStats) this.set('tornRadialUsageStats', data.usageStats);
                if (data.timers) this.set('tornRadialTimers', data.timers);
                if (data.notes) this.set('tornRadialNotes', data.notes);
                if (data.searchHistory) this.set('tornRadialSearchHistory', data.searchHistory);
                return true;
            } catch (e) {
                console.error('Failed to import data', e);
                return false;
            }
        }
    }

    // ==================== TORN API HANDLER CLASS ====================
    class TornAPI {
        constructor() {
            this.baseUrl = 'https://api.torn.com';
            this.apiKey = '';
            this.storage = new StorageManager();
            const savedKey = this.storage.get('tornRadialApiKey', '');
            if (savedKey) this.apiKey = savedKey;
        }

        setApiKey(key) {
            this.apiKey = key;
            this.storage.set('tornRadialApiKey', key);
        }

        async request(endpoint) {
            if (!this.apiKey) {
                throw new Error('API key not set');
            }

            return new Promise((resolve, reject) => {
                if (typeof GM_xmlhttpRequest === 'undefined') {
                    reject(new Error('GM_xmlhttpRequest not available'));
                    return;
                }

                GM_xmlhttpRequest({
                    method: 'GET',
                    url: `${this.baseUrl}${endpoint}&key=${this.apiKey}`,
                    onload: (response) => {
                        try {
                            const data = JSON.parse(response.responseText);
                            if (data.error) {
                                reject(new Error(data.error.error));
                            } else {
                                resolve(data);
                            }
                        } catch (e) {
                            reject(e);
                        }
                    },
                    onerror: (error) => reject(error)
                });
            });
        }

        async getUserInfo(userId) {
            return this.request(`/user/${userId}?selections=profile,timestamp`);
        }

        async getBars() {
            return this.request(`/user/?selections=bars`);
        }

        async getCooldowns() {
            return this.request(`/user/?selections=cooldowns`);
        }
    }

    // ==================== HELPER FUNCTIONS ====================
    function adjustBrightness(color, percent) {
        if (!color || typeof color !== 'string' || !color.startsWith('#')) return '#4aa3df';
        try {
            const num = parseInt(color.replace('#', ''), 16);
            if (isNaN(num)) return '#4aa3df';
            
            const amt = Math.round(2.55 * percent);
            const R = Math.min(255, Math.max(0, (num >> 16) + amt));
            const G = Math.min(255, Math.max(0, (num >> 8 & 0x00FF) + amt));
            const B = Math.min(255, Math.max(0, (num & 0x0000FF) + amt));
            return '#' + ((1 << 24) + (R << 16) + (G << 8) + B).toString(16).slice(1);
        } catch(e) {
            console.error('Color adjustment failed', e);
            return '#4aa3df';
        }
    }

    function showNotification(message, duration = 3000) {
        try {
            const notification = document.createElement('div');
            notification.className = 'torn-radial-notification';
            notification.textContent = message;
            notification.style.cssText = `
                position: fixed;
                top: 20px;
                right: 20px;
                background: rgba(27, 27, 27, 0.98);
                border: 2px solid #4aa3df;
                color: #d0d0d0;
                padding: 16px 24px;
                border-radius: 12px;
                font-size: 14px;
                font-weight: 600;
                z-index: 9999999;
                box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
                animation: slideInRight 0.3s ease-out;
            `;
            document.body.appendChild(notification);
            
            setTimeout(() => {
                notification.style.transition = 'all 0.3s ease-out';
                notification.style.opacity = '0';
                notification.style.transform = 'translateX(400px)';
                setTimeout(() => notification.remove(), 300);
            }, duration);
        } catch(e) {
            console.error('Failed to show notification', e);
        }
    }

    // ==================== EXPORT ====================
    window.TornRadialUtils = {
        ErrorLogger: new ErrorLoggerClass(),
        Storage: new StorageManager(),
        API: new TornAPI(),
        adjustBrightness: adjustBrightness,
        showNotification: showNotification
    };

})();