Gartic Translator

Translator of sent and received messages Gartic

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Gartic Translator
// @namespace    https://greasyfork.org/en/users/1353946-stragon-x
// @version      1.1
// @license      none
// @description  Translator of sent and received messages Gartic
// @author       STRAGON
// @match        *://gartic.io/*
// @grant        GM_xmlhttpRequest
// @grant        GM_notification
// @grant        GM_log
// @grant        GM_addValueChangeListener
// @grant        GM_removeValueChangeListener
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      translate.googleapis.com
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
// @icon         https://biaupload.com/do.php?imgf=org-0ae7c8b0bcad1.jpg
// ==/UserScript==

(function() {
    'use strict';

    const config = {
        targetLang: 'fa',
        maxMessages: 5,
        messageQueue: [],
        panelVisible: false,
        translationPanelVisible: false,
        currentGlobalId: ''
    };

    const languages = [
        {code: 'fa', name: 'Persian', flag: '🇮🇷'},
        {code: 'en', name: 'English', flag: '🇬🇧'},
        {code: 'tr', name: 'Turkish', flag: '🇹🇷'},
        {code: 'zh', name: 'Chinese', flag: '🇨🇳'},
        {code: 'es', name: 'Spanish', flag: '🇪🇸'},
        {code: 'ar', name: 'Arabic', flag: '🇸🇦'},
        {code: 'fr', name: 'French', flag: '🇫🇷'},
        {code: 'ru', name: 'Russian', flag: '🇷🇺'},
        {code: 'hi', name: 'Hindi', flag: '🇮🇳'},
        {code: 'pt', name: 'Portuguese', flag: '🇵🇹'},
        {code: 'bn', name: 'Bengali', flag: '🇧🇩'},
        {code: 'de', name: 'German', flag: '🇩🇪'},
        {code: 'ja', name: 'Japanese', flag: '🇯🇵'},
        {code: 'ko', name: 'Korean', flag: '🇰🇷'},
        {code: 'vi', name: 'Vietnamese', flag: '🇻🇳'},
        {code: 'it', name: 'Italian', flag: '🇮🇹'},
    ];

    createToggleButton();

    createMainPanel();

    createTranslationPanel();

    function createToggleButton() {
        const toggleButton = document.createElement('button');
        toggleButton.id = 'translator-toggle-btn';
        Object.assign(toggleButton.style, {
            position: 'fixed',
            bottom: '20px',
            right: '20px',
            zIndex: '99999',
            width: '50px',
            height: '50px',
            borderRadius: '50%',
            background: '#FD0031',
            color: 'white',
            border: 'none',
            cursor: 'pointer',
            boxShadow: '0 2px 10px rgba(0,0,0,0.2)',
            fontSize: '20px',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center'
        });
        toggleButton.textContent = 'FA';
        document.body.appendChild(toggleButton);

        toggleButton.addEventListener('click', toggleMainPanel);
    }

    function createMainPanel() {
        const panel = document.createElement('div');
        panel.id = 'translator-panel';
        Object.assign(panel.style, {
            position: 'fixed',
            bottom: '80px',
            right: '20px',
            zIndex: '99998',
            width: '350px',
            background: '#111111',
            borderRadius: '15px',
            boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
            fontFamily: 'Arial, sans-serif',
            overflow: 'hidden',
            display: 'none'
        });

        const header = document.createElement('div');
        header.id = 'translator-panel-header';
        Object.assign(header.style, {
            background: '#FD0031',
            color: 'white',
            padding: '10px 15px',
            cursor: 'move',
            position: 'relative',
            zIndex: '1'
        });
        header.innerHTML = `
            <div style="display:flex;justify-content:space-between;align-items:center">
                <span style="font-weight:bold">Gartic Translator</span>
                <div>
                    <select id="lang-select" style="padding:3px;border-radius:3px;margin-right:10px;background:#fff;color:#000">
                        ${languages.map(lang =>
                            `<option value="${lang.code}" ${lang.code === config.targetLang ? 'selected' : ''}>
                                ${lang.flag} ${lang.name}
                            </option>`
                        ).join('')}
                    </select>
                    <button id="send-message-btn" style="padding:3px 8px;border-radius:3px;border:none;cursor:pointer;background:#fff">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#FD0031" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                            <line x1="22" y1="2" x2="11" y2="13"></line>
                            <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
                        </svg>
                    </button>
                </div>
            </div>
        `;
        panel.appendChild(header);

        const body = document.createElement('div');
        body.id = 'translator-panel-body';
        Object.assign(body.style, {
            padding: '15px',
            position: 'relative',
            zIndex: '1'
        });

        const messagesPanel = document.createElement('div');
        messagesPanel.id = 'messages-panel';
        Object.assign(messagesPanel.style, {
            maxHeight: '300px',
            overflowY: 'auto',
            marginTop: '10px',
            position: 'relative'
        });
        messagesPanel.innerHTML = '<div style="color:#fff;text-align:center;padding:10px">Waiting for messages...</div>';

        const statusDiv = document.createElement('div');
        statusDiv.id = 'translator-status';
        Object.assign(statusDiv.style, {
            fontSize: '12px',
            color: '#FD0031',
            marginTop: '10px',
            textAlign: 'center',
            position: 'relative'
        });
        statusDiv.textContent = 'Active';

        body.appendChild(messagesPanel);
        body.appendChild(statusDiv);
        panel.appendChild(body);
        document.body.appendChild(panel);

        document.getElementById('lang-select').addEventListener('change', (e) => {
            config.targetLang = e.target.value;
            GM_setValue('targetLang', e.target.value);
        });

        document.getElementById('send-message-btn').addEventListener('click', toggleTranslationPanel);

        makeDraggable(panel, header);
    }

    function createTranslationPanel() {
        const panel = document.createElement('div');
        panel.id = 'translation-panel';
        Object.assign(panel.style, {
            position: 'fixed',
            bottom: '80px',
            right: '390px',
            zIndex: '99998',
            width: '300px',
            background: '#111111',
            borderRadius: '15px',
            boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
            fontFamily: 'Arial, sans-serif',
            overflow: 'hidden',
            display: 'none',
            padding: '15px'
        });

        panel.innerHTML = `
            <div style="margin-bottom:10px">
                <textarea id="translation-input" style="width:100%;height:80px;padding:8px;border-radius:5px;border:1px solid #444;background:#222;color:#fff" placeholder="Enter text to translate"></textarea>
            </div>
            <div style="margin-bottom:10px">
                <select id="target-lang-select" style="width:100%;padding:8px;border-radius:5px;border:1px solid #444;background:#222;color:#fff">
                    ${languages.map(lang =>
                        `<option value="${lang.code}">${lang.flag} ${lang.name}</option>`
                    ).join('')}
                </select>
            </div>
            <button id="translate-btn" style="width:100%;padding:8px;background:#FD0031;color:white;border:none;border-radius:5px;cursor:pointer">
                Translate
            </button>
            <div id="translation-result-container" style="margin-top:10px;">
                <div id="translation-result" style="padding:10px;background:#222;border-radius:5px;color:#fff;display:none;cursor:pointer;border:1px solid #444"></div>
                <div id="copy-notification" style="display:none;font-size:12px;color:#FD0031;text-align:center;margin-top:5px;">Copied to clipboard!</div>
            </div>
        `;
        document.body.appendChild(panel);

        document.getElementById('translate-btn').addEventListener('click', handleTranslation);

        document.getElementById('translation-result').addEventListener('click', copyTranslationToClipboard);
    }

    function showStatus(message, type) {
        const statusDiv = document.getElementById('translator-status');
        if (statusDiv) {
            statusDiv.textContent = message;
            statusDiv.style.color = type === 'error' ? '#FD0031' :
                                  type === 'success' ? '#FD0031' : '#FD0031';
        }
    }

    async function translateText(text, targetLang) {
        return new Promise((resolve, reject) => {
            const encodedText = encodeURIComponent(text);
            const apiUrl = `https://translate.googleapis.com/translate_a/single?client=gtx&dt=t&sl=auto&tl=${targetLang}&q=${encodedText}`;

            GM_xmlhttpRequest({
                method: 'GET',
                url: apiUrl,
                onload: function(response) {
                    try {
                        const data = JSON.parse(response.responseText);
                        let translatedText = '';
                        if (data && data[0]) {
                            data[0].forEach(item => {
                                if (item[0]) translatedText += item[0];
                            });
                            resolve(translatedText);
                        } else {
                            reject(new Error('Invalid response from translation server'));
                        }
                    } catch (e) {
                        reject(new Error('خطا در پردازش پاسخ ترجمه'));
                    }
                },
                onerror: reject,
                ontimeout: () => reject(new Error('Time out'))
            });
        });
    }

    function displayMessage(original, translated) {
        config.messageQueue.unshift({original, translated});

        if (config.messageQueue.length > config.maxMessages) {
            config.messageQueue.pop();
        }

        const messagesPanel = document.getElementById('messages-panel');
        if (!messagesPanel) return;

        messagesPanel.innerHTML = '';
        const container = document.createElement('div');

        config.messageQueue.forEach(msg => {
            const msgDiv = document.createElement('div');
            Object.assign(msgDiv.style, {
                marginBottom: '15px',
                padding: '10px',
                border: '1px solid #444',
                borderRadius: '10px',
                background: '#0F0F0F'
            });

            msgDiv.innerHTML = `
                <div style="margin-bottom:8px">
                    <span style="font-weight:bold;color:#FD0031">Original:</span>
                    <div style="margin-top:4px;padding:5px;background:#0F0F0F;border-radius:3px;color:#fff">${msg.original}</div>
                </div>
                <div>
                    <span style="font-weight:bold;color:#FD0031">Translated:</span>
                    <div style="margin-top:4px;padding:5px;background:#0F0F0F;border-radius:3px;color:#fff">${msg.translated}</div>
                </div>
            `;

            container.appendChild(msgDiv);
        });

        messagesPanel.appendChild(container);
    }

    function toggleMainPanel() {
        config.panelVisible = !config.panelVisible;
        const panel = document.getElementById('translator-panel');
        if (panel) {
            panel.style.display = config.panelVisible ? 'block' : 'none';
        }
    }

    function toggleTranslationPanel() {
        config.translationPanelVisible = !config.translationPanelVisible;
        const panel = document.getElementById('translation-panel');
        if (panel) {
            panel.style.display = config.translationPanelVisible ? 'block' : 'none';
        }
    }

    function copyTranslationToClipboard() {
        const resultDiv = document.getElementById('translation-result');
        const notification = document.getElementById('copy-notification');

        if (!resultDiv || !notification) return;

        const textarea = document.createElement('textarea');
        textarea.value = resultDiv.textContent;
        textarea.style.position = 'fixed';
        document.body.appendChild(textarea);
        textarea.select();

        try {
            const successful = document.execCommand('copy');
            if (successful) {
                notification.style.display = 'block';
                setTimeout(() => {
                    notification.style.display = 'none';
                }, 2000);
            }
        } catch (err) {
            console.error('Failed to copy text: ', err);
        }

        document.body.removeChild(textarea);
    }

    async function handleTranslation() {
        const text = document.getElementById('translation-input')?.value.trim();
        const targetLang = document.getElementById('target-lang-select')?.value;
        const resultDiv = document.getElementById('translation-result');
        const notification = document.getElementById('copy-notification');

        if (!text || !targetLang || !resultDiv) {
            return;
        }

        if (!text) {
            resultDiv.textContent = 'Please enter text to translate';
            resultDiv.style.display = 'block';
            resultDiv.style.color = '#FD0031';
            return;
        }

        try {
            resultDiv.textContent = 'Translating...';
            resultDiv.style.display = 'block';
            resultDiv.style.color = '#fff';
            if (notification) notification.style.display = 'none';

            const translated = await translateText(text, targetLang);
            resultDiv.textContent = translated;

            const currentGlobalId = config.currentGlobalId;



                console.log('Message sent:', {
                    original: text,
                    translated: translated,
                    targetLang: languages.find(l => l.code === targetLang).name,
                    globalId: currentGlobalId
                });

                showStatus('Message sent successfully', 'success');

        } catch (error) {
            if (resultDiv) {
                resultDiv.textContent = `Error: ${error.message}`;
                resultDiv.style.color = '#FD0031';
            }
            showStatus('Failed to send message', 'error');
        }
    }

    function makeDraggable(panel, header) {
        let isDragging = false;
        let offsetX, offsetY;

        header.addEventListener('mousedown', (e) => {
            isDragging = true;
            offsetX = e.clientX - panel.getBoundingClientRect().left;
            offsetY = e.clientY - panel.getBoundingClientRect().top;
            document.body.style.userSelect = 'none';
        });

        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                panel.style.left = `${e.clientX - offsetX}px`;
                panel.style.top = `${e.clientY - offsetY}px`;
                panel.style.right = 'auto';
                panel.style.bottom = 'auto';
            }
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
            document.body.style.userSelect = '';
        });
    }

    function setupWebSocketInterceptor() {
        const originalSend = WebSocket.prototype.send;
        let wsInstance = null;

        WebSocket.prototype.send = function(data) {
            originalSend.apply(this, arguments);

            if (!wsInstance) {
                wsInstance = this;
                setupWebSocketListener(this);
            }
        };
    }

    function setupWebSocketListener(ws) {
        ws.addEventListener("message", async (msg) => {
            try {
                window.wsObj = window.wsObj || {};
                window.wsObj.send = ws.send.bind(ws);

                if (msg.data.includes('42["11"')) {
                    const data = JSON.parse(msg.data.slice(2));

                    if (data.length > 2) {
                        const originalMessage = data[2];
                        showStatus('Translating...', 'info');

                        try {
                            const translated = await translateText(originalMessage, config.targetLang);
                            displayMessage(originalMessage, translated);
                            showStatus('Message translated', 'success');
                        } catch (error) {
                            displayMessage(originalMessage, `Translation error: ${error.message}`);
                            showStatus('Translation error', 'error');
                        }
                    }
                } else if (msg.data.startsWith('42[5,')) {
                    const data = JSON.parse(msg.data.slice(2));
                    if (data[0] == 5) {
                        window.wsObj = window.wsObj || {};
                        window.wsObj.lengthID = data[1];
                        config.currentGlobalId = data[2];
                        window.wsObj.roomCode = data[3];
                        window.wsObj.uders = data[5];

                        console.log('Updated globalId:', data[2]);
                    }
                }
            } catch (err) {
                console.error("Error processing message:", err);
                showStatus('Error processing message', 'error');
            }
        });
    }

    setupWebSocketInterceptor();

    const savedLang = GM_getValue('targetLang', 'fa');
    config.targetLang = savedLang;
    const langSelect = document.getElementById('lang-select');
    if (langSelect) {
        langSelect.value = savedLang;
    }
})();