Torn Crimes Scamming Timers

Show how much cooldown & expiration is left for targets

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Torn Crimes Scamming Timers
// @namespace    https://github.com/SOLiNARY
// @version      0.3.2
// @description  Show how much cooldown & expiration is left for targets
// @author       Ramin Quluzade, Silmaril [2665762]
// @license      MIT License
// @match        https://www.torn.com/loader.php?sid=crimes*
// @match        https://torn.com/loader.php?sid=crimes*
// @match        https://www.torn.com/page.php?sid=crimes*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @grant        GM_addStyle
// @grant        unsafeWindow
// @run-at       document-start
// ==/UserScript==

(async function() {
    'use strict';

    const showExpiration = true;
    const showCooldown = true;
    const timeElementsToShow = 2; // Options: 1,2,3,4
    const scammingCrimesUrl = 'loader.php?sid=crimesData&step=crimesList&rfcv='
    const observerConfig = { childList: true, subtree: true };
    let foundTargets = false;
    let noTargets = false;
    let targets = {};
    const isTampermonkeyEnabled = typeof unsafeWindow !== 'undefined';
    const isMobileView = window.innerWidth <= 784;
    const { fetch: originalFetch } = isTampermonkeyEnabled ? unsafeWindow : window;
    const customFetch = async (...args) => {
        let [resource, config] = args;
        let response = await originalFetch(resource, config);
        if (response.url.indexOf(scammingCrimesUrl) >= 0 && window.location.href.includes('crimes#/scamming')) {
            targets = {};
            try {
                const jsonData = await response.clone().json();
                jsonData.DB.crimesByType.targets.forEach((target, index) => {
                    let expireTimestamp = target.expire.toString().length == 10 ? target.expire : target.expire.toString().substr(0, 10); // Fix for Torn's long timestamps bug
                    let cooldownTimestamp = null;
                    if (target.cooldown > 0){
                        cooldownTimestamp = target.cooldown.toString().length == 10 ? target.cooldown : target.cooldown.toString().substr(0, 10); // Fix for Torn's long timestamps bug
                    }
                    targets[index] = {
                        email: target.email,
                        expire: expireTimestamp,
                        cooldown: cooldownTimestamp
                    };
                });

                response.json = async () => jsonData;
                response.text = async () => JSON.stringify(jsonData);
            } catch (error) {
                noTargets = true;
                console.log('[TornCrimesScammingTimers] No targets, skipping the script init', error);
            }
        }

        return response;
    };

    if (isTampermonkeyEnabled){
        unsafeWindow.fetch = customFetch;
    } else {
        window.fetch = customFetch;
    }

    await addStyle();

    const timeDeltas = {
        "After": 1,
        "Ago": -1
    };
    const timerTypes = {
        "Expiration": 1,
        "Cooldown": 2
    }


    setInterval(addTimeSpans, 2 * 1_000);

    function addTimeSpans(){
        let targetEmailSpans = document.querySelectorAll('div.crimes-app div.crime-root.scamming-root div[class^=virtualList___] div[class^=virtualItem___] span[class^=email___]');
        Object.values(targets).forEach(target => {
            var match = Object.values(targetEmailSpans).find(targetRow => targetRow.innerText == target.email);
            if (match != null) {
                if (showCooldown){
                    let timeElement = match.parentNode.querySelector('span.silmaril-torn-crimes-scamming-timers-cooldown');
                    if (timeElement == null) {
                        timeElement = document.createElement('span');
                        timeElement.className = 'silmaril-torn-crimes-scamming-timers-cooldown';
                        timeElement.innerText = calculateTimeDelta(target.cooldown, timeDeltas.After, timerTypes.Cooldown, !isMobileView);
                        match.insertAdjacentElement('afterEnd', timeElement);
                    } else {
                        timeElement.innerText = calculateTimeDelta(target.cooldown, timeDeltas.After, timerTypes.Cooldown, !isMobileView);
                    }
                }
                if (showExpiration){
                    let timeElement = match.parentNode.querySelector('span.silmaril-torn-crimes-scamming-timers-expiration');
                    if (timeElement == null) {
                        timeElement = document.createElement('span');
                        timeElement.className = 'silmaril-torn-crimes-scamming-timers-expiration';
                        timeElement.innerText = calculateTimeDelta(target.expire, timeDeltas.After, timerTypes.Expiration, !isMobileView);
                        match.insertAdjacentElement('afterEnd', timeElement);
                    } else {
                        timeElement.innerText = calculateTimeDelta(target.expire, timeDeltas.After, timerTypes.Expiration, !isMobileView);
                    }
                }
            }
        })
    }

    function calculateTimeDelta(timestamp, timeDeltaType, timerType, isDesktopView) {
        const now = Math.floor(Date.now() / 1000); // Current timestamp in seconds
        let timeDelta = timestamp - now; // Time remaining in seconds

        if (timeDeltaType === timeDeltas.After && timeDelta <= 0) {
            switch (timerType){
                case timerTypes.Cooldown:
                    return "";
                case timerTypes.Expire:
                default:
                    return "Expired";
            }
        }
        if (timeDeltaType === timeDeltas.Ago) {
            timeDelta = Math.abs(timeDelta);
            if (timeDelta <= 0) {
                return "Just now";
            }
        }

        const days = Math.floor(timeDelta / 86400); // 86400 seconds in a day
        const hours = Math.floor((timeDelta % 86400) / 3600); // 3600 seconds in an hour
        const minutes = Math.floor((timeDelta % 3600) / 60); // 60 seconds in a minute
        const seconds = timeDelta % 60;

        let prefix = '';
        switch (timerType){
            case timerTypes.Cooldown:
                prefix = "Cooldown in ";
                break;
            case timerTypes.Expire:
            default:
                prefix = "Expires in ";
                break;
        }

        let timeElementsAdded = 0;
        let timeDeltaText = isDesktopView ? timeDeltaType === timeDeltas.After ? prefix : "Created " : timeDeltaType === timeDeltas.After ? "In " : "";
        if (days != 0 && timeElementsAdded < timeElementsToShow) {
            timeDeltaText += isDesktopView ? `${days} d` : `${days} day${days === 1 ? '' : 's'}`;
            timeElementsAdded += 1;
        }
        if (hours != 0 && timeElementsAdded < timeElementsToShow) {
            if (days !== 0){
                timeDeltaText += ', ';
            }
            timeDeltaText += isDesktopView ? `${hours} h` : `${hours} hour${hours === 1 ? '' : 's'}`;
            timeElementsAdded += 1;
        }
        if (minutes != 0 && timeElementsAdded < timeElementsToShow) {
            if (days !== 0 || hours !== 0){
                timeDeltaText += ', ';
            }
            timeDeltaText += isDesktopView ? `${minutes} m` : `${minutes} minute${minutes === 1 ? '' : 's'}`;
            timeElementsAdded += 1;
        }
        if (seconds != 0 && timeElementsAdded < timeElementsToShow) {
            if (days !== 0 || hours !== 0 || minutes !== 0){
                timeDeltaText += ', ';
            }
            timeDeltaText += isDesktopView ? `${seconds} s` : `${seconds} second${seconds === 1 ? '' : 's'}`;
            timeElementsAdded += 1;
        }
        if (timeDeltaType === timeDeltas.Ago) {
            timeDeltaText += ' ago';
        }

        return timeDeltaText;
    }

    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    async function addStyle() {
        const styles = `
        span.silmaril-torn-crimes-scamming-timers-cooldown {
            color: yellowgreen;
            font-size: xx-small;
            display: ${showCooldown ? 'block' : 'none'};
        }
        span.silmaril-torn-crimes-scamming-timers-expiration {
            color: gray;
            font-size: xx-small;
            display: ${showExpiration ? 'block' : 'none'};
        }
        `;

        if (isTampermonkeyEnabled){
            GM_addStyle(styles);
        } else {
            let style = document.createElement("style");
            style.type = "text/css";
            style.innerHTML = styles;
            while (document.head == null){
                await sleep(50);
            }
            document.head.appendChild(style);
        }
    }
})();