GreasyFork/SleazyFork Install Tracker

Track your own Gf/Sf install counts and get desktop notifications via API.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==UserScript==
// @name            GreasyFork/SleazyFork Install Tracker
// @name:de         GreasyFork/SleazyFork Installations-Tracker
// @name:fr         GreasyFork/SleazyFork Install Tracker
// @name:es         GreasyFork/SleazyFork Rastreador de Instalaciones
// @name:it         GreasyFork/SleazyFork Tracker di Installazioni
// @name:ru         GreasyFork/SleazyFork Трекер установок
// @name:zh-CN      GreasyFork/SleazyFork 安装 Tracker
//
// @description     Track your own Gf/Sf install counts and get desktop notifications via API.
// @description:de  Behalte deine eigenen Gf/Sf-Installationszahlen via API im Blick und erhalte Desktop-Benachrichtigungen.
// @description:fr  Suivez vos propres compteurs d'installation Gf/Sf via API et recevez des notifications de bureau.
// @description:es  Siga sus propios recuentos de installation de Gf/Sf a través de la API und reciba notificaciones.
// @description:it  Tieni traccia dei tuoi conteggi di installazione di Gf/Sf tramite API e ricevi notifiche desktop.
// @description:ru  Отслеживайте собственные счетчики установок Gf/Sf через API und получайте уведомления.
// @description:zh-CN 通过 API 跟踪您 eigenen Gf/Sf 安装计数 und 获取桌面通知.
//
// @version         0.1.2 beta
// @author          Wack.3gp (https://greasyfork.org/users/4792)
// @copyright       2026+, Wack.3gp
// @namespace       https://greasyfork.org/users/4792
// @license         CC BY-NC-SA-4.0; https://creativecommons.org/licenses/by-nc-sa/4.0/
// @icon            https://greasyfork.org/vite/assets/blacklogo16-DftkYuVe.png
//
// @match           https://greasyfork.org/*users/*
// @match           https://sleazyfork.org/*users/*
//
// @grant           GM_setValue
// @grant           GM_getValue
// @grant           GM_notification
// @grant           GM_xmlhttpRequest
// @grant           GM_info
// @connect         api.greasyfork.org
// @connect         api.sleazyfork.org
//
// @supportURL      https://greasyfork.org/scripts/575950/feedback
// @compatible      Chrome tested with Tampermonkey
// @contributionURL https://www.paypal.com/donate?hosted_button_id=BYW9D395KJWZ2
// @contributionAmount €1.00
// ==/UserScript==

(function() {
    'use strict';

    const isSleazy = window.location.hostname.includes('sleazyfork.org');
    const contributionURL = "https://www.paypal.com/donate?hosted_button_id=BYW9D395KJWZ2";

    const notificationIcon = isSleazy ?
        'https://sleazyfork.org/vite/assets/blacklogo96-CxYTSM_T.png' :
        'https://greasyfork.org/vite/assets/blacklogo96-CxYTSM_T.png';

    const checkStats = async () => {
        const userLink = document.querySelector('#user-control-links .user-profile-link a, .user-profile-link a');
        if (!userLink) return;

        const myId = userLink.getAttribute('href').match(/\/users\/(\d+)/)?.[1];
        if (!window.location.href.includes(`/users/${myId}`)) return;

        const scriptElements = Array.from(document.querySelectorAll('#user-script-list li[data-script-id], #user-unlisted-script-list li[data-script-id]'));
        if (scriptElements.length === 0) return;

        let totalNew = 0;
        let updateDetails = [];
        let processedCount = 0;

        for (const s of scriptElements) {
            const scriptId = s.getAttribute('data-script-id');
            const scriptName = s.querySelector('.script-link')?.innerText.trim() || "Unknown";
            const scriptUrl = s.querySelector('.script-link')?.href;

            if (isSleazy) {
                try {
                    const response = await fetch(scriptUrl, { method: 'HEAD' });
                    fetchStats(scriptId, scriptName, response.ok ? "api.sleazyfork.org" : "api.greasyfork.org");
                } catch (e) {
                    fetchStats(scriptId, scriptName, "api.greasyfork.org");
                }
            } else {
                fetchStats(scriptId, scriptName, "api.greasyfork.org");
            }
        }

        function fetchStats(scriptId, scriptName, domain) {
            const apiUrl = `https://${domain}/scripts/${scriptId}/stats.json`;
            GM_xmlhttpRequest({
                method: "GET",
                url: apiUrl,
                timeout: 10000,
                onload: function(response) {
                    if (!isSleazy && response.status === 404 && domain === "api.greasyfork.org") {
                        fetchStats(scriptId, scriptName, "api.sleazyfork.org");
                        return;
                    }
                    try {
                        if (response.status !== 200) throw new Error(`HTTP ${response.status}`);
                        const stats = JSON.parse(response.responseText);
                        let currentTotal = 0;
                        for (let date in stats) {
                            if (stats[date] && typeof stats[date].installs === 'number') {
                                currentTotal += stats[date].installs;
                            }
                        }
                        const lastTotal = GM_getValue("api_sum_id_" + scriptId, -1);
                        if (lastTotal !== -1 && currentTotal > lastTotal) {
                            const diff = currentTotal - lastTotal;
                            totalNew += diff;
                            updateDetails.push(`${scriptName}: +${diff}`);
                        }
                        GM_setValue("api_sum_id_" + scriptId, currentTotal);
                    } catch (e) {
                        console.error(`[Tracker] Error: ${e.message}`);
                    } finally {
                        checkCompletion();
                    }
                },
                onerror: checkCompletion,
                ontimeout: checkCompletion
            });
        }

        function checkCompletion() {
            processedCount++;
            if (processedCount === scriptElements.length && totalNew > 0) {
                showNotification(totalNew, updateDetails);
            }
        }
    };

    const showNotification = (total, details) => {
        const fullText = details.join('\n');
        GM_notification({
            title: `🚀 ${total} New Installs Detected`,
            text: `Click to view details!`,
            image: notificationIcon,
            silent: true,
            onclick: () => {
                showCentralModal(total, fullText);
            }
        });
    };

    const showCentralModal = (total, detailsText) => {
        const oldOverlay = document.querySelector('#tracker-modal-overlay');
        if (oldOverlay) oldOverlay.remove();

        const overlay = document.createElement('div');
        overlay.id = 'tracker-modal-overlay';
        overlay.style = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.75); display: flex; align-items: center; justify-content: center; z-index: 999999; font-family: sans-serif; backdrop-filter: blur(4px);';
        
        overlay.innerHTML = `
            <div style="background: #fff; width: 95%; max-width: 480px; border-radius: 16px; overflow: hidden; box-shadow: 0 25px 50px rgba(0,0,0,0.5); animation: tracker-pop 0.3s ease-out; position: relative;">
                <button id="tracker-x-btn" style="position: absolute; top: 15px; right: 15px; background: none; border: none; color: #aaa; cursor: pointer; font-size: 24px; line-height: 1;">&times;</button>
                <div style="padding: 20px; background: #f9f9f9; border-bottom: 1px solid #eee; text-align: center;">
                    <h2 style="margin: 0; color: #111; font-size: 22px;">Installation Update</h2>
                </div>
                <div style="padding: 25px; color: #333; line-height: 1.6;">
                    <p style="margin-top: 0; font-weight: bold; font-size: 17px; color: #2d89ef;">You have ${total} new installs!</p>
                    <div style="background: #f4f4f4; padding: 15px; border-radius: 10px; max-height: 160px; overflow-y: auto; font-family: monospace; font-size: 13px; margin-bottom: 20px; white-space: pre-wrap; border: 1px solid #e0e0e0;">${detailsText}</div>
                    <p style="font-size: 14px; text-align: center; color: #666; margin-bottom: 0;">Support my work with a coffee? ☕</p>
                </div>
                <div style="padding: 20px; display: flex; gap: 12px; background: #fff;">
                    <button id="tracker-ok-btn" style="flex: 1; background: #eee; border: none; padding: 12px; border-radius: 10px; cursor: pointer; font-weight: bold; font-size: 15px; color: #444;">OK</button>
                    <a href="${contributionURL}" target="_blank" id="tracker-coffee-btn" style="flex: 2; text-align: center; background: #FFDD00; color: #000; text-decoration: none; padding: 12px; border-radius: 10px; font-weight: bold; font-size: 15px; border: 1px solid #e6c600;">☕ Buy me a coffee</a>
                </div>
            </div>
            <style>
                @keyframes tracker-pop {
                    from { transform: scale(0.9); opacity: 0; }
                    to { transform: scale(1); opacity: 1; }
                }
                #tracker-x-btn:hover { color: #333 !important; }
                #tracker-ok-btn:hover { background: #e2e2e2 !important; }
                #tracker-coffee-btn:hover { background: #f7d000 !important; }
            </style>
        `;

        document.body.appendChild(overlay);

        const close = () => overlay.remove();
        overlay.querySelector('#tracker-x-btn').onclick = close;
        overlay.querySelector('#tracker-ok-btn').onclick = close;
        overlay.querySelector('#tracker-coffee-btn').onclick = close;
        overlay.onclick = (e) => { if (e.target === overlay) close(); };
    };

    window.addEventListener('load', () => {
        setTimeout(checkStats, 2000);
    });
})();