Greasy Fork is available in English.

GC - Restock Logger

Logs restock events from Grundos Cafe to discord and virtupets.net.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Advertisement:

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

Advertisement:

// ==UserScript==
// @name         GC - Restock Logger
// @namespace    https://greasyfork.org/en/users/1278031-crystalflame
// @version      1.0.1
// @description  Logs restock events from Grundos Cafe to discord and virtupets.net.
// @author       CrystalFlame
// @license      MIT
// @match        *://*.grundos.cafe/buyitem/*
// @match        *://*.grundos.cafe/help/*
// @grant        GM.getValue
// @grant        GM.setValue
// @icon         https://www.google.com/s2/favicons?sz=64&domain=grundos.cafe
// @require      https://update.greasyfork.org/scripts/514423/1554918/GC%20-%20Universal%20Userscripts%20Settings.js
// ==/UserScript==

(function () {
    "use strict";

    const INGEST_ENDPOINT = "https://virtupets.net/api/restock";
    const GUILDS_SETTING_KEY = "guilds";

    function parseEventType(pageHtml) {
        if (pageHtml.includes("accept your offer of")) return "bought";
        if (pageHtml.includes("has been added to your inventory")) return "bought";
        if (pageHtml.includes("is SOLD OUT!")) return "sold_out";
        return null;
    }

    function parsePrice(pageText) {
        const match = /I accept your offer of (.*?) Neopoints!'/g.exec(pageText);
        if (!match || !match[1]) return null;
        return parseInt(match[1].replaceAll(",", ""), 10);
    }

    function parseUid(url) {
        const match = /buyitem\/(.*?)\//.exec(url);
        return match ? match[1] : "unknown_uid";
    }

    function parseUsername() {
        const el = document.getElementById("user-info-username");
        return el ? el.innerText.trim() : "unknown_user";
    }

    function parseItem(pageText, eventType) {
        if (eventType === "bought") {
            const match = /Buying : (.*?)\n/.exec(pageText);
            return match ? match[1] : "unknown_item";
        }
        const soldOutMatch = /(.+?) is SOLD OUT!/.exec(pageText);
        return soldOutMatch ? soldOutMatch[1].trim() : "unknown_item";
    }

    function parseDescription() {
        const content = document.getElementById("page_content");
        const em = content ? content.getElementsByTagName("em") : [];
        return em.length > 0 ? em[0].innerText : "No description";
    }

    async function sendEvent(payload) {
        const controller = new AbortController();
        const timeout = setTimeout(() => controller.abort(), 5000);
        try {
            const response = await fetch(INGEST_ENDPOINT, {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify(payload),
                keepalive: true,
                signal: controller.signal,
            });
            if (!response.ok) {
                const body = await response.text();
                throw new Error(`Ingest failed: ${response.status} ${body}`);
            }
        } finally {
            clearTimeout(timeout);
        }
    }

    async function registerSettings() {
        if (!document.URL.includes("/help/userscripts/")) return;
        await addTextInput({
            categoryName: "Restock Logger",
            settingName: GUILDS_SETTING_KEY,
            labelText: "Guild Names",
            labelTooltip: "Add the guild names to post a log to here. Separate multiple with commas, e.g. myguild,otherguild",
            defaultSetting: "",
        });
    }

    async function main() {
        registerSettings();
        if (!document.URL.includes("/buyitem/")) return;

        const pageHtml = document.body.innerHTML;
        const pageText = document.body.innerText;
        const eventType = parseEventType(pageHtml);
        if (!eventType) return;

        const guildsRaw = await GM.getValue(GUILDS_SETTING_KEY, "");
        const guilds = guildsRaw.split(",").map(s => s.trim()).filter(Boolean);

        const payload = {
            guilds,
            eventType,
            uid: parseUid(document.URL),
            username: parseUsername(),
            item: parseItem(pageText, eventType),
            price: parsePrice(pageText),
            timestamp: new Date().toISOString(),
            description: parseDescription(),
            pageContext: {
                url: document.URL
            }
        };

        try {
            await sendEvent(payload);
        } catch (err) {
            console.error(`[RestockLogger] Failed to send event:`, err);
        }
    }

    main();
})();