您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Translator of sent and received messages Gartic
// ==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; } })();