Отслеживайте собственные счетчики установок Gf/Sf через API und получайте уведомления.
// ==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 instalación 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.0 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
// @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==
/* jshint esversion: 11 */
(function() {
'use strict';
const isSleazy = window.location.hostname.includes('sleazyfork.org');
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) {
console.warn(`[Tracker] No logged-in user detected. Tracker is idle.`);
return;
}
const myId = userLink.getAttribute('href').match(/\/users\/(\d+)/)?.[1];
if (!window.location.href.includes(`/users/${myId}`)) {
console.info(`[Tracker] Current page is not the user's own profile. Skipping scan.`);
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;
console.info(`[Tracker] Starting analysis for ${scriptElements.length} scripts...`);
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' });
if (response.ok) {
fetchStats(scriptId, scriptName, "api.sleazyfork.org");
} else {
fetchStats(scriptId, scriptName, "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);
console.info(`[Tracker] ID: ${scriptId} | ${scriptName} | Source: ${domain} | Total: ${currentTotal}`);
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 for ID: ${scriptId} (${scriptName}): ${e.message}`);
} finally {
checkCompletion();
}
},
onerror: () => { checkCompletion(); },
ontimeout: () => { checkCompletion(); }
});
}
function checkCompletion() {
processedCount++;
if (processedCount === scriptElements.length) {
if (totalNew > 0) {
console.info(`[Tracker] Scan complete. ${totalNew} new installs found.`);
showNotification(totalNew, updateDetails);
} else {
console.info(`[Tracker] Scan complete. No new installs found.`);
}
}
}
};
const showNotification = (total, details) => {
const fullText = details.join('\n');
GM_notification({
title: `🚀 ${total} New Installs Detected`,
text: fullText,
image: notificationIcon,
silent: true,
onclick: () => {
alert(`Full Installation Update:\n\n${fullText}`);
}
});
};
const addCoffeeButton = () => {
const contributionURL = GM_info.script.header.match(/@contributionURL\s+(.+)/)?.[1] || "";
if (contributionURL && !document.querySelector('#coffee-btn')) {
const btn = document.createElement('a');
btn.id = 'coffee-btn';
btn.href = contributionURL.trim();
btn.target = '_blank';
btn.innerText = '☕ Buy me a coffee';
btn.className = 'notranslate';
btn.style = 'margin-left: 20px; padding: 5px 12px; background: #FFDD00; color: #000; border-radius: 20px; text-decoration: none; font-weight: bold; font-size: 13px; border: 1px solid #e6c600; display: inline-block; vertical-align: middle; transition: 0.2s;';
const target = document.querySelector('#main-header h2') || document.querySelector('.width-constraint h2');
if (target) target.appendChild(btn);
}
};
window.addEventListener('load', () => {
addCoffeeButton();
setTimeout(checkStats, 2000);
});
})();