DeepCo Croaker

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

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==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");

})();