Torn City Universal Streak Tracker

A minimalist, TCT-synchronized streak tracker. Works with Public Access API keys. Resets if inactive for 24h.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Torn City Universal Streak Tracker
// @namespace    PlethoraGaming
// @version      1.9
// @description  A minimalist, TCT-synchronized streak tracker. Works with Public Access API keys. Resets if inactive for 24h.
// @author       PlethoraGaming [2284368]
// @license      MIT
// @match        https://www.torn.com/index.php*
// @match        https://www.torn.com/item.php*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// ==/UserScript==

(function() {
    'use strict';

    // Fetch the saved API key
    let apiKey = GM_getValue('torn_streak_api_key', '');

    // Initial setup for new users
    if (!apiKey) {
        apiKey = prompt("Please enter your Torn Public Access API Key to begin tracking your streak:");
        if (apiKey) {
            GM_setValue('torn_streak_api_key', apiKey);
            // Streak starts the moment they provide a key
            GM_setValue('streakAnchor', new Date().toISOString());
            alert("API Key saved! Your streak starts today.");
        }
    }

    // Menu option to update key
    GM_registerMenuCommand("Update API Key", () => {
        let newKey = prompt("Enter new Public API Key:", GM_getValue('torn_streak_api_key', ''));
        if (newKey) {
            GM_setValue('torn_streak_api_key', newKey);
            location.reload();
        }
    });

    // Create the Top-Middle UI box
    const streakBox = document.createElement('div');
    streakBox.id = 'pg-streak-box';
    streakBox.style = `
        position: fixed; top: 0; left: 50%; transform: translateX(-50%);
        background: rgba(10, 10, 10, 0.9); color: #00ff00;
        padding: 3px 15px; border-radius: 0 0 6px 6px;
        border: 1px solid #444; border-top: none;
        font-family: "Courier New", monospace; font-size: 11px;
        z-index: 999999; pointer-events: none;
        box-shadow: 0 0 8px rgba(0,0,0,0.5);
    `;
    streakBox.innerHTML = 'SYNCING...';
    document.body.appendChild(streakBox);

    function checkStreak() {
        if (!apiKey) {
            streakBox.innerHTML = 'SET API KEY';
            return;
        }

        GM_xmlhttpRequest({
            method: "GET",
            url: `https://api.torn.com/user/?selections=profile&key=${apiKey}`,
            headers: {
                "User-Agent": "TornUniversalStreakTracker",
                "Accept": "application/json"
            },
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data.error) {
                        streakBox.innerHTML = 'API ERROR';
                        return;
                    }

                    const now = new Date();
                    let anchorDateStr = GM_getValue('streakAnchor', now.toISOString());
                    let anchorDate = new Date(anchorDateStr);

                    // RESET LOGIC: Check last_action timestamp
                    const lastActionTCT = new Date(data.last_action.timestamp * 1000);
                    const hoursSinceActive = (now - lastActionTCT) / (1000 * 60 * 60);

                    // If more than 24 hours have passed since last activity, reset the anchor to now
                    if (hoursSinceActive > 24) {
                        anchorDate = now;
                        GM_setValue('streakAnchor', now.toISOString());
                        streakBox.style.color = "#ff4444"; // Visual indicator of reset
                    }

                    // Calculate days since anchor
                    const diffTime = Math.abs(now - anchorDate);
                    const currentStreak = Math.floor(diffTime / (1000 * 60 * 60 * 24)) + 1;

                    // Birthday UI (Jan 8)
                    if (now.getUTCMonth() === 0 && now.getUTCDate() === 8) {
                        streakBox.style.background = "#d4af37";
                        streakBox.style.color = "#000";
                        streakBox.innerHTML = `🎂 HAPPY BIRTHDAY! STREAK: ${currentStreak} 🎂`;
                    } else {
                        streakBox.innerHTML = `STREAK: ${currentStreak} DAYS`;
                    }
                } catch (e) {
                    streakBox.innerHTML = 'OFFLINE';
                }
            }
        });
    }

    checkStreak();
})();