DeepCo Croaker

Queue empty, async unarmed, Layer done, and bonus tooltip alerts with persistent settings, test buttons, and a live log.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         DeepCo Croaker
// @author       M3P / ChatGPT
// @namespace    local
// @version      2026-03-20
// @description  Queue empty, async unarmed, Layer done, and bonus tooltip alerts with persistent settings, test buttons, and a live log.
// @match        https://*.deepco.app/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=deepco.app
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function () {
    "use strict";

    /* ----------------- CONFIG ----------------- */
    const DEBUG = false;
    const SETTINGS_KEY = "CROAKER_ALERTS_V2";
    const SOUND_DATA = "data:audio/mpeg;base64,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VAivJFIIEd9yIhvKLOyiMYgMBkYTmhyuNNACgwWDJgYBgEBFABLABVEpYFgAQgGlbkgCV1cnssstZZVsu28+8////////////////rRbcb//BEJszwzuzOzg7ibfb62yNtJhVUUrUWsu4WVIRw9cc9WEtQj84D5bZ+5zDoGVsZqw6URUGJ5hYQAFQGrRakIKjHyY8pLNCJZMyw7/+7DE64Ax7dkh+d4AA9UzIv8xsAAeUBSUgTe0y7XMBHCIRpXaMNCxYUasyKSM6aREaaliEOy95QaEmElhyqEJFaCKKKApGlYNHoFlEojr6yybpZa+67k3C5ibocDSjGWO0/z+hQJwr1r2L7RtWOZV0wZkr0ymtKn77hZ1+d2pEOY49yw58zRyq7D8CP/Hp+UzMo73U3S5fr+ZUdzHvb+8cvabjHeNJh6JTlFj+X575/493l/////9z1jhySczx/v29Vq3S5enhlcQAABX15gIDB0lfCciZQUA921ul2zCkM0FoZupjTsKFSY0+ULemEC5gMAmCwmYcJ5kAmgUNGGQk64OChgEVqUGADyZkzaRw4DTOjlMPgMxCEUk5eaZ2xxcyGEQgYtEBzTymYgO1xWIu2uuLuW5aKBhsdGSzEYKFIOHidQwAy7MLi611NGWUS7FMC+SeogBpggFonAUAMCXag5A0Vf2KRfPTT2QMkU7a0zphxVABgUHGEBLS40tzGPFqggCN1YO1+X35fT3zI4UNVAYHJNNJWxU0H0MBAAEkoMMIgJpL8ynL7kUwxdhrDiZRyHIceuvLYFa9KmkwVEnxssphmHaWrATOrOU7HF3QPDcBMEREcutzOpbp8tbUMVYFAYYBD4gFIWBkMuHJ3Heh7EU2TPkw6o5UTaSoK03K1rlNHca3//P///////////////56GJFV////////////////pthUVMxMzcEHv7K3lvUqGD/Ti+oWuifZZEQfD9Kr7vOO4a9N1Zps2NLALtNeZc0piRhuHawZAYGVWbDENUcnyxdp3r7vRbHcczy7buaz//7oMTXgCxuDSn5vgJKkySkP5+QBHGaTtPYqsplsrm4ams/3ylt5Sv+P8/3ea/CSdrZ7x+ZlTky2X38uc3ZxmcJi3GsOb7XlnL9/X63Q3qaV9pvpqaxudsYR/CPUdg/U8spbIowUrAhADSh1aK0VZqXiquoIL7Sy3SsY2JbZM792wsLLFLAN1LE0QhKsLLVclnJjeWejDGTpjF0V5bE7JEpjd1E9PRx7t67hWk3eBDg1mjwWpIIpsrHw22zqA41bZfj5nz923Dlh2kkXBMj+WI8HbGxseLWgwIUt3OBakPFrb3H16t7na2Na+o0uN3rNX1tH3iXVIG6MDhSWvxqlt7jxKbh+9LZpesPd96prGdatSla0pSmsv36sVndX9ebeXSOCoH0+fAkGg9BhgoY0BGOpRwzaDFU20uQ3MWJDRhky1qKiOXGMWJjJAo0lQMZMQSRBRkMvlEyOKTBYyMnjox4VTVwSqGCQyBh2Y/Ghg1LmUnQWA0YCBBiWtFpDF4RKoAEgIb7SYhIhh0AKPmjwuY4EMh0YbBapkrDCIHQHmAw0YPByURdYEgRlr2W0OyYU8YTCJhMGo1mBwGYBAZgcGF7RwJGFQozlIkcApeZJ2OvZKy9YGBDTW3MBAIwAAFIUEljMVjSaCXE9OMaZMKggwqJDAYHTqMCAJoD+RiHH/f+RmYSEYGCpf19HP/7sMTNgBV9oyP094AmHsImvzfACG5yYZEYWE5eZfj+xjn6j8lcuB4IRUbxMRdj8QO0hzXkedL5YdDWLz7utaXdD0Tsu/YjL/XWcuE2Veixi9DW08C66fctht+7D6RRaCARuIQFzGo1MTBwwAAVWrGVLGaCMUzcVlMrMBBFWZjziL1gfWXZ2bkMfn+Xuc////////////////+dlvM////////////////5LM7f5d/MzNzEAHtqN9sifIeU1Nh2pGc4zZisCSmIOstpQJl0ZYK8MUyiT/P9WpaKVMCAIC0RjAaYGsSpcX2dqNX8efTVcJ2Uy2K0u7+OXcu67vVrKGlTO9DUqrXcbmss7mVb/yyyppdayx1KpdnXlMmmY3EXFd1/Wuu7g/07x/n+hqjxxx///fN1bNNa/v7q0tL3GrS83v8sccccf3ytKYzS2caWzWpv3j++Vn+h5/YdoJ6Ov7DuOOX/vHHHHH8ccf+Zhl/Y13HHHGZjMMy6VXdVV1kwAfmVtC4H++TbTMwuosNEGjdfwxicAmBCcbZkVSBkqWCLvoFEWcR4hQWWC8Tgt4kpaUbpkieImkVioShBi7SR2Q1mLqNyLDBG4QUhiZMJl9NMumqBdfUaoyezjGCyZHARYiRFRYxkSqTxmoaJVK5iakHWaMiUn84ifaormiBoXDZqL0CnKZvums+tKvQOumYMZv63pXmobHinmBICA/XQIXAkSUHYYga7Jp6GTyesTgJ1HAQeQx/Tgye+xA3eT9JIvDvBYKSAJgggdRAGLiAUC3hEYiZGPs0LomkLk+0gdJTAg4hH2P0g99GVDxjAtDIMGEoi//uQxOIAGX2jL/2MACJiqOT+nwAF6K0H3buv9WOmNBIQKJGXDxkRcCB97WHMLjSXr8IestL8LszSvMjES+iZhMMAUBbBDoYByFxYw5Etd516BuC6S46earXXaihmDgNV6dT1sQaTHl2KvksC2YZzbm473rwWYXbSHYw0BKxjary4yJEMWYeePbPXid+L134zlEP9/mRETskXWGB6Gjf0vf1SUDnl2Hnta///+Z/Yz7ykxt5/8vywUdQWL8Q0sUBC5h4KjOmepepmpMxUNKAbdj5XIJzKXTNyX9iDYIrhz/////////////////oVeY/////////////////2iub/P63MAwAALbNUhBkNSbSJZEIEVZ3N5AShOMwNMAV6sxTUVtZrStilWC7m2wmneUqaEvExEM2RcuMABbLmlQNMU8ZyluMa5e3ZlNjdjHHHeNmhnZp2X9mI6wLGzS4b7vHHGrhj3eOOOPP///dmDWuyhOZMWSR1drsw7S445Zf/4444/+OOP81KozZvRJ/oahq7OwzFZmlpaXH/s9/99//////5//ugxPWAKNoNO/mdpANAtKZ7saAAqVSmkhqRVbNa1nVs8q2e8//3jjjhlnWlMt1llTdqymmylTtO9Go0/0qpbOL//u7/xxE9UclCyQsA7mIlS2DZDUcCCZJLJM3CC3xZ7AhKIQ9ARIAdFwk9i1TAIxZLBJDbghNCgZCQpeeL66hFJqEhakp8zOu468tDoHQA99atv++8/qMsjOGZ6cDwFQVITQhFiICmVL89dm///lor1e5EqDKwKjpMoGkwDB0jkgQ6oiJkOs5/////sCWNoc9IirZLlWhjUJZX/8VXx/8v02v/QqmITMubebmbKCF1K1NKYBtialKwJR4oh1uCmdI9QkHSS7WGhPDqeuc6JiNsNTq9LCaCkXU6lZ0kokOUM1mJWEvV7C7mVqNdK7/+ZLWj6IiIDYbrGZDH1jjq/qRZIVnFcItXIUZImIhTIyYjC10fRMQci6f6qjm1I00uWUmmzUu3+WQ4URKTRqEC7Zsnenbmv1/7QU793//SVOZFJGPkByKENb/////zK7SOzdu8zJcQ+pW718WcSkAfB/0U6R6vZnJh0nHHT0/Rwj0sJysq9CgxemBJiHhwhumk4K46mZ8+fWsfuWpqcp1croUX/5x/Xtz9EikI5roxPYOoMX//9gqQD2N18Y5M1FuxqvMolTToud/RGVEkvyqjSLM/r860ZLBQCgOi//uAxOSAFP2fN+wxL6qXsmW89adM0RQei04kKhsNSP5p3/OJK1C41FJA443/8VA5MJO7s8M8RCBeSyAc0jLVWmkteZmCRKZFAJqwqGcihQlBUjBEoKsFT8//7yElLE00LK2fysMylB8DoaAWHqrSv//FkodNfK7Wq0te1z1rTObDesWqqgtTNMNK1KrAslFVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+2DE6YATBZUt55j+oXUjI7xkoiVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU=";
    const WATCHDOG_TIMEOUT = 500;   // seconds before forcing a check
    const WATCHDOG_INTERVAL = 100;  // how often watchdog scans

    /* ---------------- DEFAULT ALERTS ---------------- */

    const DEFAULT_ALERTS = {

        QUEUE_EMPTY: {
            enabled: true,
            sound: SOUND_DATA,
            soundType: "base64",
            volume: 0.7,
            speed: 1,
            message: "Queue Empty",
            initialDelay: 1000,
            repeat: 10000
        },

        ASYNC_UNARMED: {
            enabled: true,
            sound: SOUND_DATA,
            soundType: "base64",
            volume: 0.7,
            speed: 1,
            message: "Async Unarmed",
            repeat: 20000
        },

        LAYER_COMPLETE: {
            enabled: true,
            sound: SOUND_DATA,
            soundType: "base64",
            volume: 0.7,
            speed: 1,
            message: "Layer Completed",
            repeat: 20000
        },

        BONUS_TOOLTIP: {
            enabled: true,
            sound: SOUND_DATA,
            soundType: "base64",
            volume: 0.7,
            speed: 1,
            message: "Bonus Tooltip"
        }

    };
    const WATCHDOG = {
        checkQueue: { last: Date.now(), fn: checkQueue },
        checkAsync: { last: Date.now(), fn: checkAsync },
        checkLayer: { last: Date.now(), fn: checkLayer },
        checkBonus: { last: Date.now(), fn: checkBonus }
    };
    /* ---------------- UTIL ---------------- */

    const clone = v => JSON.parse(JSON.stringify(v));

    function gmGet(k,f){try{return GM_getValue(k,f);}catch{return f}}
    function gmSet(k,v){try{GM_setValue(k,v);}catch{}}

    /* ---------------- SETTINGS LOAD ---------------- */

    let ALERTS = gmGet(SETTINGS_KEY,null);

    if(!ALERTS){

        ALERTS = clone(DEFAULT_ALERTS);
        gmSet(SETTINGS_KEY,ALERTS);

    }else{

        for(const k in DEFAULT_ALERTS){

            if(!ALERTS[k]) ALERTS[k] = clone(DEFAULT_ALERTS[k]);

            for(const f in DEFAULT_ALERTS[k]){
                if(!(f in ALERTS[k])) ALERTS[k][f]=DEFAULT_ALERTS[k][f];
            }

        }

        gmSet(SETTINGS_KEY,ALERTS);

    }

    let saveTimer=null;

    function saveAlerts(){

        clearTimeout(saveTimer);

        saveTimer=setTimeout(()=>{

            gmSet(SETTINGS_KEY,ALERTS);

        },300);

    }

    /* ---------------- AUDIO CACHE ---------------- */

    const AUDIO_CACHE = new Map();

    function getAudio(src){

        if(!AUDIO_CACHE.has(src)){

            const audio = new Audio(src);
            audio.preload="auto";

            AUDIO_CACHE.set(src,audio);

        }

        return AUDIO_CACHE.get(src);

    }

    function playSound(alert,reason){

        try{

            const base = getAudio(alert.sound || SOUND_DATA);

            const audio = base.cloneNode();

            audio.volume = alert.volume ?? 1;
            audio.playbackRate = alert.speed ?? 1;

            audio.play().catch(console.warn);

            croaker(`[${alert.message}]`,1);

            log(`PLAY ${alert.message}${reason?` (${reason})`:''}`);

        }catch(e){

            console.warn(e);
            log("Audio error");

        }

    }

    /* ---------------- LOG ---------------- */

    const LOG=[];

    function log(msg){

        const line = new Date().toLocaleTimeString()+" "+msg;
        if (DEBUG) console.log(line);
        LOG.push(line);
        if(LOG.length>200)LOG.shift();

        const el=document.getElementById("croaker-log");
        if(el) el.textContent = LOG.slice().reverse().join("\n");

    }

    /* ---------------- CROAKER HEADER ---------------- */

    function croaker(message,delay){

        const anchor=document.querySelector("header.navbar div.flex-1 a");
        if(!anchor)return;

        let span=anchor.querySelector("span.croaker");

        if(!span){

            span=document.createElement("span");
            span.className="croaker";
            span.className="valuable";
            anchor.appendChild(span);

        }

        if(span.textContent) return;

        span.textContent=message;

        setTimeout(()=>{

            if(span.textContent===message) span.textContent="";

        },delay*1000);

    }

    /* ---------------- ALERT STATE ---------------- */

    const ALERT_STATE={};

    for(const k in ALERTS){

        ALERT_STATE[k]={active:false,timer:null,repeat:null};

    }

    let bonusArmed=true;

    /* ---------------- ALERT CONTROL ---------------- */

    function startAlert(key){

        const a=ALERTS[key];
        const s=ALERT_STATE[key];

        if(!a.enabled || s.active) return;

        s.active=true;

        const fire=()=>playSound(a,key);

        if(a.initialDelay){

            s.timer=setTimeout(()=>{

                fire();

                if(a.repeat) s.repeat=setInterval(fire,a.repeat);

            },a.initialDelay);

        }else{

            fire();

            if(a.repeat) s.repeat=setInterval(fire,a.repeat);

        }

        log("START "+key);

    }

    function stopAlert(key){

        const s=ALERT_STATE[key];

        if(s.timer)clearTimeout(s.timer);
        if(s.repeat)clearInterval(s.repeat);

        s.timer=null;
        s.repeat=null;
        s.active=false;

        log("STOP "+key);

    }

    /* ---------------- CONDITIONS ---------------- */

    function queueEmpty(){

        const t=document.title.toLowerCase();
        return !t.includes("queue") && !t.includes("async") ;

    }

    function asyncActive(){
        const t=document.title.toLowerCase();
        return t.includes("async");
    }

    function countTileWrappers() {
        return document.querySelectorAll('div[id^="tile_wrapper_"]').length;
    }

    function countQueuedTiles() {
        return document.querySelectorAll('div[id^="tile_inner_"].you-are-queued-here').length;
    }

    function countMiningTiles() {
        return document.querySelectorAll('div[id^="tile_inner_"].you-are-mining-here').length;
    }

    function layerComplete() {
        return document.querySelector('div#layer-complete-text') !== null
    }

    function checkLayer() {
        if (DEBUG) console.log('------- layerComplete - ' + layerComplete());
        if (layerComplete()) {
            startAlert("LAYER_COMPLETE");
        } else {
            stopAlert("LAYER_COMPLETE");
        }
    }

    function checkQueue(){
        if (DEBUG) console.log(' ===== check queue');
        //console.log('CkQ  layer-' + layerComplete() + ' queueempty-' + queueEmpty() + ' async-' + asyncActive() + ' queuedtiles-' + countQueuedTiles() + ' miningtiles-' + countMiningTiles() + ' tilescnt-' + countTileWrappers());

        if((queueEmpty() && countTileWrappers() > 0 && countMiningTiles() == 0) && !layerComplete() ) startAlert("QUEUE_EMPTY");
        else stopAlert("QUEUE_EMPTY");
    }

    function checkAsync(){
        if (DEBUG) console.log(' //// check async');
        const el=document.querySelector('span[data-role="auto-async-indicator"]');
        if(!el)return;

        const t=document.title.toLowerCase();

        if(el.classList.contains("hidden") && !t.includes("async"))
            startAlert("ASYNC_UNARMED");
        else
            stopAlert("ASYNC_UNARMED");

    }

    function checkBonus(){
        if (DEBUG) console.log(' @@@@ check bonus');
        const panel=document.querySelector("turbo-frame#bonus-panel");
        if(!panel)return;

        const tooltip=panel.querySelector("div.tooltip:not(.bonus-active)");

        if(tooltip){
            if(bonusArmed){
                playSound(ALERTS.BONUS_TOOLTIP,"BONUS");
                bonusArmed=false;
            }
        }else{
            bonusArmed=true;
        }
    }

    /* ---------------- UI ---------------- */

    GM_addStyle(`
#croaker-gear{position:fixed;bottom:10px;left:10px;background:#222;color:#fff;padding:6px;border-radius:6px;cursor:pointer;z-index:9999}
#croaker-panel{position:fixed;bottom:50px;left:10px;background:#111;color:#eee;padding:10px;border-radius:8px;width:320px;max-height:60vh;overflow:auto;z-index:9999}
#croaker-panel input[type=range]{width:120px}
#croaker-log{white-space:pre;font-family:monospace;font-size:11px;background:#000;padding:6px;margin-top:6px;max-height:150px;overflow:auto}
button{cursor:pointer}
`);

    function buildUI(){

        const gear=document.createElement("div");
        gear.id="croaker-gear";
        gear.textContent="⚙";

        const panel=document.createElement("div");
        panel.id="croaker-panel";
        panel.style.display="none";

        document.body.appendChild(gear);
        document.body.appendChild(panel);

        gear.onclick=()=>{

            panel.style.display = panel.style.display==="none"?"block":"none";

        };

        for(const key in ALERTS){

            const a=ALERTS[key];

            const row=document.createElement("div");

            row.innerHTML=`
<label>
<input type="checkbox" ${a.enabled?"checked":""}> <b>${key}</b>
</label>

<br>vol <input type="range" min="0" max="1" step="0.05" value="${a.volume}">
<br>spd <input type="range" min="0.5" max="2" step="0.1" value="${a.speed}">
<button class="test">Test</button>

<div>
<input class="url" placeholder="Sound URL" style="width:90%">
<input type="file" class="file" accept="audio/*">
<button class="default">Default</button>
</div>
<br>
`;

            const checkbox=row.querySelector("input[type=checkbox]");
            const sliders=row.querySelectorAll("input[type=range]");
            const test=row.querySelector(".test");
            const url=row.querySelector(".url");
            const file=row.querySelector(".file");
            const def=row.querySelector(".default");

            checkbox.onchange=()=>{

                a.enabled=checkbox.checked;
                saveAlerts();

            };

            sliders[0].oninput=()=>{

                a.volume=parseFloat(sliders[0].value);
                saveAlerts();

            };

            sliders[1].oninput=()=>{

                a.speed=parseFloat(sliders[1].value);
                saveAlerts();

            };

            test.onclick=()=>playSound(a,"TEST");

            url.onchange=()=>{

                a.sound=url.value.trim();
                a.soundType="url";

                saveAlerts();
                log(key+" URL sound");

            };

            file.onchange=()=>{

                const reader=new FileReader();

                reader.onload=e=>{

                    a.sound=e.target.result;
                    a.soundType="file";

                    AUDIO_CACHE.delete(a.sound);

                    saveAlerts();
                    log(key+" file sound");

                };

                reader.readAsDataURL(file.files[0]);

            };

            def.onclick=()=>{

                a.sound=DEFAULT_ALERTS[key].sound;
                a.soundType="base64";

                saveAlerts();
                log(key+" default sound");

            };

            panel.appendChild(row);

        }

        const logBox=document.createElement("div");
        logBox.id="croaker-log";
        panel.appendChild(logBox);

    }

    setInterval(()=>{
        const now = Date.now();
        for(const key in WATCHDOG){
            const w = WATCHDOG[key];
            if(now - w.last > WATCHDOG_TIMEOUT){
                log("WATCHDOG fired: "+key);
                w.last = now;
                try{
                    w.fn();
                }catch(e){
                    console.warn("watchdog error",key,e);
                }
            }
        }
    }, WATCHDOG_INTERVAL);
    /* ---------------- INIT ---------------- */

    buildUI();

    checkQueue();
    checkAsync();
    checkBonus();
    checkLayer();

    log("Croaker ready");

})();