Gartic IO Word Collector with Auto Skip

Collects words presented during the game, saves them to a file, and auto-skips

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Gartic IO Word Collector with Auto Skip
// @namespace    http://tampermonkey.net/
// @version      2025-03-06
// @description  Collects words presented during the game, saves them to a file, and auto-skips
// @author       anonimbiri
// @license      MIT
// @match        https://gartic.io/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=gartic.io
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Modify appendChild to intercept and alter the game's script
    Node.prototype.appendChild = new Proxy(Node.prototype.appendChild, {
        apply: function(target, thisArg, argumentsList) {
            const node = argumentsList[0];
            if (node.nodeName.toLowerCase() === 'script' && node.src && node.src.includes('room')) {
                console.log('Hedef script algılandı:', node.src);
                fetch(node.src)
                    .then(response => response.text())
                    .then(scriptContent => {
                        let modifiedContent = scriptContent
                            .replace(
                                'r.created||c?Rt("input",{type:"text",name:"chat",className:"mousetrap",autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",value:i,placeholder:this._lang.chatHere,maxLength:100,enterKeyHint:"send",onChange:this.handleText,ref:this._ref}):Rt("input",{type:"text",name:"chat",className:"mousetrap",autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",value:this._lang.loginChat,maxLength:100,ref:this._ref,disabled:!0})',
                                'Rt("input",{type:"text",name:"chat",className:"mousetrap",autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",value:i,placeholder:this._lang.chatHere,maxLength:100,enterKeyHint:"send",onChange:this.handleText,ref:this._ref})'
                            )
                            .replace(
                                'this._timerAtivo=setInterval((function(){Date.now()-e._ativo>15e4&&(O(Object(f.a)(n.prototype),"emit",e).call(e,"avisoInativo"),e._ativo=Date.now())}),1e3)',
                                'this._timerAtivo=setInterval((function(){Date.now()-e._ativo>15e4&&e.active()}),1e3)'
                            )
                            .replace(
                                'e.unlock()}',
                                'e.unlock();window.game=e;setInterval(()=>{window.game=e},1000);}'
                            );
                        let blob = new Blob([modifiedContent], { type: 'application/javascript' });
                        let blobUrl = URL.createObjectURL(blob);
                        node.src = blobUrl;
                        node.textContent = '';
                        return target.apply(thisArg, [node]);
                    })
                    .catch(error => {
                        console.error('Failed to fetch/modify script:', error);
                        return target.apply(thisArg, argumentsList);
                    });
                return node;
            }
            return target.apply(thisArg, argumentsList);
        }
    });

    // Define wordList globally on window object
    window.wordList = {
        "custom": [],
        "General (en)": [],
        "General (tr)": [],
        "Anime (en)": []
    };

    // Function to save wordList to a file
    function saveWordListToFile() {
        const theme = window.game && window.game._dadosSala && window.game._dadosSala.tema ? window.game._dadosSala.tema : "custom";
        const words = window.wordList[theme].join('\n');
        const blob = new Blob([words], { type: 'text/plain;charset=utf-8' });
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = `${theme}_words.txt`;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(url);
    }

    // Inject UI for the word collector (right side, vertical)
    const collectorHTML = `
        <div id="wordCollector" style="position: fixed; top: 20px; right: 20px; width: 200px; background: rgba(30, 30, 47, 0.9); padding: 15px; border-radius: 8px; color: #fff; z-index: 1000; display: flex; flex-direction: column; gap: 10px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);">
            <h3 style="margin: 0; color: #ff69b4; font-size: 16px; text-align: center;">Word Collector</h3>
            <p style="margin: 0; font-size: 12px; text-align: center;">Collects words and skips turn</p>
            <button id="saveWordsBtn" style="background: #ff69b4; border: none; padding: 8px; border-radius: 5px; color: #1e1e2f; cursor: pointer; font-size: 14px;">Save Words</button>
            <label for="importWords" style="background: #a5e2fe; border: none; padding: 8px; border-radius: 5px; color: #1e1e2f; cursor: pointer; font-size: 14px; text-align: center;">Import Words</label>
            <input type="file" id="importWords" accept=".txt" style="display: none;">
            <div id="wordCount" style="font-size: 12px; text-align: center;"></div>
        </div>
    `;
    document.body.insertAdjacentHTML('beforeend', collectorHTML);

    // Update word count display
    function updateWordCount() {
        const theme = window.game && window.game._dadosSala && window.game._dadosSala.tema ? window.game._dadosSala.tema : "custom";
        const count = window.wordList[theme].length;
        document.getElementById('wordCount').textContent = `Words (${theme}): ${count}`;
    }

    // Add save button event listener
    document.getElementById('saveWordsBtn').addEventListener('click', () => {
        saveWordListToFile();
    });

    // Add import functionality
    document.getElementById('importWords').addEventListener('change', (e) => {
        const file = e.target.files[0];
        if (file) {
            const fileName = file.name;
            const themeMatch = fileName.match(/^(.+)_words\.txt$/);
            const theme = themeMatch ? themeMatch[1] : "custom"; // Extract theme from filename, e.g., "General (tr)"

            // Check if the theme exists in wordList, if not, create it
            if (!window.wordList[theme]) {
                window.wordList[theme] = [];
            }

            const reader = new FileReader();
            reader.onload = function(event) {
                const text = event.target.result;
                const importedWords = text.split('\n').map(word => word.trim()).filter(word => word.length > 0);

                // Add imported words to the extracted theme category, avoiding duplicates
                importedWords.forEach(word => {
                    if (!window.wordList[theme].includes(word)) {
                        window.wordList[theme].push(word);
                    }
                });

                // Update the word count display (uses current room theme)
                updateWordCount();
                alert(`Imported ${importedWords.length} words into "${theme}" category. Total unique words in "${theme}": ${window.wordList[theme].length}`);
            };
            reader.readAsText(file);
        }
    });

    // Check for game object and collect words + auto-skip
    const checkGame = setInterval(() => {
        if (window.game && window.game._socket) {
            clearInterval(checkGame);

            // Collect words when your turn comes (event 16) and auto-skip
            window.game._socket.on(16, (word1, hints1, word2, hints2) => {
                const theme = window.game._dadosSala.tema || "custom";

                // Add words to the appropriate category if not already present
                if (!window.wordList[theme].includes(word1)) {
                    window.wordList[theme].push(word1);
                }
                if (!window.wordList[theme].includes(word2)) {
                    window.wordList[theme].push(word2);
                }

                // Update the word count display
                updateWordCount();

                // Auto-skip after collecting words
                setTimeout(() => {
                    if (window.game && window.game._socket && window.game._codigo) {
                        window.game._socket.emit(25, window.game._codigo);
                    }
                }, 500); // 500ms delay to ensure words are processed
            });

            // Collect the word when a turn ends and the answer is revealed (event 18 - intervalo)
            window.game._socket.on(18, (answer) => {
                if (answer) { // Only collect if answer is provided (not null/undefined)
                    const theme = window.game._dadosSala.tema || "custom";

                    // Add the word if not already present
                    if (!window.wordList[theme].includes(answer)) {
                        window.wordList[theme].push(answer);
                    }

                    // Update the word count display
                    updateWordCount();
                }
            });

            // Initial word count update
            updateWordCount();
        }
    }, 100);

})();