// ==UserScript==
// @name Chat Translator
// @namespace https://hordes.io
// @version 0.50.13
// @description Hordes.io chat translator
// @license FU!
// @author ChatGPT-6
// @match https://hordes.io/play
// @icon https://www.google.com/s2/favicons?sz=64&domain=hordes.io
// @grant none
// ==/UserScript==
/* Version: 0.50.12 - January 9, 2024 17:21:16 */
'use strict';
!(() => {
const VERSION = '0.50.13';
const CHAT_SELECTOR = '#chat';
const loader = {
start() {
let interval = setInterval(() => {
if (document.querySelector(CHAT_SELECTOR)) {
clearInterval(interval);
this.guard();
this.init();
}
}, 100);
},
guard() {
new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes[0]?.className == 'l-ui' && this.init();
});
}).observe(document.body, { childList: true });
},
init() {
config.init();
style.init();
chat.init();
control.init();
chatinput.init();
}
};
const translate = (text, lang, handler, text_el) => {
fetch(atob('aHR0cHM6Ly90cmFuc2xhdGUuZ29vZ2xlYXBpcy5jb20vdHJhbnNsYXRlX2Evc2luZ2xlP2NsaWVudD1ndHgmc2w9YXV0byZ0bD0=') + lang + "&dt=t&q=" + encodeURI(text))
.then(response => response.json())
.then(result => {
config.tr_cnt += 1;
config.save();
handler(result, text_el);
});
};
const languages = JSON.parse(atob('eyJhZiI6IkFmcmlrYWFucyIsInNxIjoiQWxiYW5pYW4iLCJhciI6IkFyYWJpYyIsImF6IjoiQXplcmJhaWphbmkiLCJldSI6IkJhc3F1ZSIsImJuIjoiQmVuZ2FsaSIsImJlIjoiQmVsYXJ1c2lhbiIsImJnIjoiQnVsZ2FyaWFuIiwiY2EiOiJDYXRhbGFuIiwiemgtQ04iOiAiQ2hpbmVzZSBTaW1wbGlmaWVkIiwiemgtVFciOiAiQ2hpbmVzZSBUcmFkaXRpb25hbCIsImhyIjoiQ3JvYXRpYW4iLCJjcyI6IkN6ZWNoIiwiZGEiOiJEYW5pc2giLCJubCI6IkR1dGNoIiwiZW4iOiJFbmdsaXNoIiwiZW8iOiJFc3BlcmFudG8iLCJldCI6IkVzdG9uaWFuIiwidGwiOiJGaWxpcGlubyIsImZpIjoiRmlubmlzaCIsImZyIjoiRnJlbmNoIiwiZ2wiOiJHYWxpY2lhbiIsImthIjoiR2VvcmdpYW4iLCJkZSI6Ikdlcm1hbiIsImVsIjoiR3JlZWsiLCJndSI6Ikd1amFyYXRpIiwiaHQiOiJIYWl0aWFuIENyZW9sZSIsIml3IjoiSGVicmV3IiwiaGkiOiJIaW5kaSIsImh1IjoiSHVuZ2FyaWFuIiwiaXMiOiJJY2VsYW5kaWMiLCJpZCI6IkluZG9uZXNpYW4iLCJnYSI6IklyaXNoIiwiaXQiOiJJdGFsaWFuIiwiamEiOiJKYXBhbmVzZSIsImtuIjoiS2FubmFkYSIsImtvIjoiS29yZWFuIiwibGEiOiJMYXRpbiIsImx2IjoiTGF0dmlhbiIsImx0IjoiTGl0aHVhbmlhbiIsIm1rIjoiTWFjZWRvbmlhbiIsIm1zIjoiTWFsYXkiLCJtdCI6Ik1hbHRlc2UiLCJubyI6Ik5vcndlZ2lhbiIsImZhIjoiUGVyc2lhbiIsInBsIjoiUG9saXNoIiwicHQiOiJQb3J0dWd1ZXNlIiwicm8iOiJSb21hbmlhbiIsInJ1IjoiUnVzc2lhbiIsInNyIjoiU2VyYmlhbiIsInNrIjoiU2xvdmFrICIsInNsIjoiU2xvdmVuaWFuIiwiZXMiOiJTcGFuaXNoIiwic3ciOiJTd2FoaWxpIiwic3YiOiJTd2VkaXNoIiwidGEiOiJUYW1pbCIsInRlIjoiVGVsdWd1ICIsInRoIjoiVGhhaSIsInRyIjoiVHVya2lzaCIsInVrIjoiVWtyYWluaWFuIiwidXIiOiJVcmR1IiwidmkiOiJWaWV0bmFtZXNlIiwiY3kiOiJXZWxzaCIsInlpIjoiWWlkZGlzaCJ9'));
const chatinput = {
msg_replace(result, text_el) {
text_el.value = result[0][0][0];
text_el.dataset.translated = 1;
if (config.direct_send) {
text_el.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, keyCode: 13 }));
text_el.dataset.translated = 0;
}
},
enable_input_translation(lang_code) {
config.translate_message = true;
config.translate_message_to = lang_code;
this.btn.innerText = `⇄ ${lang_code}`;
this.btn.classList.remove('textgrey');
this.btn.classList.add('textgreen');
config.save();
},
lang_select_frame: 0,
lang_select() {
let chat_node = document.getElementById('chat');
this.lang_select_frame = chat_node.parentElement.appendChild(el('div', { className: "panel-black", style: "position:absolute;bottom:50px;right:0px;display:block;z-index:999;" }));
this.lang_select_frame.appendChild(el('div', { className: "panel textprimary title", innerText: 'Translate message' }));
let menu = this.lang_select_frame.appendChild(el('div', { className: "menu panel-black grid four" }));
Object.keys(languages).forEach(e =>
menu.appendChild(el("small", { className: `btn border black ${e == config.translate_message_to && "textgreen" || "textgrey"}`, innerText: languages[e] }, { id: e })));
this.lang_select_frame.addEventListener('mouseleave', e => {
this.lang_select_frame = this.lang_select_frame.remove();
});
this.lang_select_frame.addEventListener('mousedown', e => {
let lang_code = e.target.dataset.id;
if (lang_code) {
this.lang_select_frame = this.lang_select_frame.remove();
this.enable_input_translation(lang_code);
}
});
},
init() {
let chatinput = document.getElementById('chatinput');
chatinput.style = "grid-template-columns: auto 1fr auto";
this.input = chatinput.querySelector('input');
this.btn = chatinput.appendChild(el('div', { className: `btn border black ${config.translate_message && 'textgreen' || 'textgrey'}`, innerText: `⇄ ${config.translate_message_to}` }));
this.btn.addEventListener('mouseup', e => {
if (e.button == 0) {
['textgrey', 'textgreen'].forEach(c => e.target.classList.toggle(c));
config.translate_message ^= 1;
config.save();
}
else if (e.button == 2 && !this.lang_select_frame) {
this.lang_select();
}
else if (this.lang_select_frame) {
this.lang_select_frame = this.lang_select_frame.remove();
}
});
this.input.addEventListener('keydown', e => {
if (e.keyCode == 13 && this.input.dataset.translated == 1) {
this.input.dataset.translated = 0;
}
else if (e.keyCode == 13 && config.translate_message && this.input.dataset.translated != 1 && this.input.value.length > 0) {
e.preventDefault();
e.stopPropagation();
let text = this.input.value.trim();
if (text.length > 0) {
translate(text, config.translate_message_to, this.msg_replace, this.input);
}
}
else if (e.keyCode == 186) {
if (this.input.value.length == 1 && this.input.value == ':') {
e.preventDefault();
this.input.value = '';
['textgrey', 'textgreen'].forEach(c => this.btn.classList.toggle(c));
config.translate_message ^= 1;
}
if (this.input.value.length == 2) {
e.preventDefault();
let lc = this.input.value.slice(0, 2);
if (languages.hasOwnProperty(lc)) {
this.input.value = '';
config.translate_message_to = lc;
this.btn.innerText = `⇄ ${lc}`;
config.translate_message = true;
this.btn.classList.remove('textgrey');
this.btn.classList.add('textgreen');
}
}
}
});
}
};
const control = {
init() {
let channelselect = document.querySelector('.channelselect');
this.btn = channelselect.appendChild(el('small', { className: `btn border black ${config.translate_chat && 'textgreen' || 'textgrey'} svelte-16y0b84`, innerText: `Translate` }));
this.btn.addEventListener('mouseup', e => {
if (e.button == 0) {
['textgrey', 'textgreen'].forEach(c => e.target.classList.toggle(c));
config.translate_chat ^= 1;
config.save();
}
});
}
};
const chat = {
channels: new Set(['Global', 'Faction', 'Party', 'Clan', 'From', 'To']),
size() {
let chat_container_node = document.querySelector(".l-corner-ll.container.uiscaled.svelte-16y0b84");
chat_container_node.style.height = config.chat_height + "px";
chat_container_node.style.width = config.chat_width + "px";
},
init() {
this.size();
this.node = document.getElementById('chat');
let mutation_observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.addedNodes.length > 0) {
let line_node = mutation.addedNodes[0].childNodes[0];
let channel_node = line_node.childNodes[1].childNodes[0];
if (this.channels.has(channel_node.innerText)) {
let sender_node = line_node.childNodes[1].childNodes[2];
let text_node = line_node.childNodes[2];
let sender_info_node = sender_node.childNodes[0];
let sender_s_icon_node = sender_info_node.childNodes.length == 4 && sender_info_node.childNodes[0];
sender_info_node.childNodes[sender_s_icon_node && 1 || 0];
if (config.shrink_channel_name) {
channel_node.innerText = channel_node.innerText.slice(0, 1);
}
if (config.remove_supporter_icon && sender_s_icon_node) {
sender_s_icon_node.remove();
}
if (config.translate_chat) {
translate(text_node.innerText, config.translate_chat_to, this.chat_replace, text_node);
}
else {
text_node.classList.add("htr");
text_node.dataset.htr = config.tr_cnt;
}
}
}
});
});
if (this.node) {
mutation_observer.observe(this.node, { childList: true });
let tt = undefined;
['mouseover', 'mouseout'].forEach(event_type =>
this.node.addEventListener(event_type, e => {
if (e.target.dataset.htt) {
if(event_type == 'mouseover') {
let c = e.target.getBoundingClientRect();
tt = document.querySelector('div.l-ui.layout.svelte-k3qmu8').appendChild(el('div', {
className: 'window panel-black',
innerText: e.target.dataset.htt,
style: `max-width:300px;z-index: 12;position: absolute;left: ${c.right}px;top: ${c.top-25}px;`
}));
}
else if(event_type == 'mouseout') {
tt = tt.remove();
}
}
}, false)
);
this.node.addEventListener('mousedown', e => {
if (e.button == 0)
if (e.target.dataset.htr) {
translate(e.target.innerText, config.translate_chat_to, this.chat_replace, e.target);
}
else if (e.target.dataset.htt) {
e.preventDefault();
config.translate_message_to = e.target.dataset.htl;
chatinput.btn.innerText = ` ⇄ ${e.target.dataset.htl} `;
document.body.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, keyCode: 13 }));
config.save();
}
}, false);
}
},
chat_replace(result, text_el) {
if (result[2] != config.translate_chat_to && languages.hasOwnProperty(result[2]) && result[6] > 0.3) {
text_el.before(el('i', { className: "htr", innerText: `${result[2]}:` }, { htt: text_el.innerText, htl: result[2] }));
text_el.removeAttribute('data-htr');
text_el.classList.remove('htr');
text_el.innerText = `${result[0][0][0]}`;
}
},
};
const el = (tag, options = {}, dataset = {}) => {
let a = document.createElement(tag);
for (let [t, e] of Object.entries(options)) {
a[t] = e;
}
for (let [t, e] of Object.entries(dataset)) {
a.dataset[t] = e;
}
return a
};
const config = {
version: VERSION,
translate_chat: true,
translate_message: false,
translate_chat_to: 'en',
translate_message_to: 'en',
shrink_channel_name: true,
remove_supporter_icon: true,
direct_send: true,
tr_cnt: 0,
chat_height: 240,
chat_width: 450,
_node: undefined,
save() {
window.localStorage.setItem('mod_translator', JSON.stringify(this));
},
load() {
let s = JSON.parse(window.localStorage.getItem('mod_translator'));
if (s && s.version == this.version) {
for (const [k, v] of Object.entries(s) || {})
this[k] = v;
}
},
add_group(header_text) {
this._node.appendChild(el("div", { className: "textprimary", innerText: header_text }));
this._node.appendChild(el("div"));
},
add_line(iname, ivalue) {
this._node.appendChild(el("div", { innerText: iname && iname || '' }));
this._node.appendChild(el("div", { innerText: ivalue && ivalue || '' }));
},
inject(settings_container) {
let settings_divide = settings_container.childNodes[0].childNodes[1].childNodes[0];
let panel = settings_divide.appendChild(el("div", { className: `menu panel-black scrollbar svelte-ntyx09` }, { mod: "Chat Translator" }));
panel.style.display = 'none';
panel.appendChild(el("h3", { className: 'textprimary', innerText: 'Translator' }));
let settings = panel.appendChild(el("div", { className: 'settings svelte-ntyx09' }));
this._node = settings;
this.save();
settings.appendChild(el("div", { innerText: 'Language' }))
.appendChild(el("br")).parentElement
.appendChild(el("small", { className: " textgrey", innerText: 'Translates chat into this language' }));
let lo = settings.appendChild(el("select"));
Object.keys(languages).forEach(e =>
lo.appendChild(el("option", { value: e, innerText: languages[e], selected: config.translate_chat_to == e && true || false })));
lo.addEventListener('change', e => {
config.translate_chat_to = e.target.value;
config.save();
});
this.add_line();
this.add_group('Chat style');
settings.appendChild(el("div", { innerText: 'Width' }));
let cwv = settings.appendChild(el("input", { type: 'number', placeholder: "450", min: "450", value: this.chat_width }));
settings.appendChild(el("div", { innerText: 'Height' }));
let chv = settings.appendChild(el("input", { type: 'number', placeholder: "240", min: "240", value: this.chat_height }));
cwv.addEventListener('input', e => {
this.chat_width = e.target.value;
chat.size();
this.save();
});
chv.addEventListener('input', e => {
this.chat_height = e.target.value;
chat.size();
this.save();
});
settings.appendChild(el("div", { innerText: 'Channels name shortening' }));
let shrink_channel_name = settings.appendChild(el("div", { className: `btn checkbox ${this.shrink_channel_name && "active"}` }));
shrink_channel_name.addEventListener('mouseup', e => {
if (e.button == 0) {
e.target.classList.toggle('active');
this.shrink_channel_name ^= 1;
this.save();
}
});
settings.appendChild(el("div", { innerText: 'Remove supporter icons' }));
let remove_supporter_icon = settings.appendChild(el("div", { className: `btn checkbox ${this.remove_supporter_icon && "active"}` }));
remove_supporter_icon.addEventListener('mouseup', e => {
if (e.button == 0) {
e.target.classList.toggle('active');
this.remove_supporter_icon ^= 1;
this.save();
}
});
this.add_line();
this.add_group('Usage');
this.add_line('On/off shortcut', '::');
this.add_line('Language shortcuts', 'ko: es: en: ...');
this.add_line('Emoji Windows', '⊞ + .');
this.add_line('Emoji Linux', 'ctrl + .');
this.add_line('Emoji Mac', 'Fn + E, 🌐 + E');
this.add_line();
this.add_group('Info');
this.add_line('Translator version', this.version);
this.add_line('Total translation requests', this.tr_cnt);
let menu = settings_divide.childNodes[0];
menu.appendChild(el("div", { className: `choice`, innerText: 'Chat Translator' }, { mod: "translator" }));
menu.addEventListener('mouseup', e => {
menu.childNodes.forEach(e => e.classList.remove('active'));
e.target.classList.add('active');
if (e.target.dataset.mod == "translator") {
e.target.parentElement.parentElement.childNodes.forEach((e, k) => { e.style.display = k > 0 && 'none'; });
panel.removeAttribute("style");
}
else {
e.target.parentElement.parentElement.childNodes.forEach((e, k) => { e.style.display = k > 0 && ""; });
panel.style.display = "none";
}
}, false);
},
init() {
this.load();
let settings_container = document.body.querySelector(".container.svelte-ntyx09");
settings_container && this.inject(settings_container);
let mutation_observer = new MutationObserver(mutations => {
mutations.forEach(mutation => mutation.addedNodes[0]?.className == 'container svelte-ntyx09' && this.inject(mutation.addedNodes[0]));
});
let layout_container = document.body.querySelector(".container.svelte-k3qmu8");
layout_container && mutation_observer.observe(layout_container, { childList: true });
},
};
const style = {
rules: `
.htr{pointer-events:all;cursor:pointer;margin-right:.35em;font-style:normal;}
.htb{font-style: normal;filter: grayscale(1) sepia(29%) saturate(406%) hue-rotate(143deg) brightness(50%) contrast(87%);}
`,
init() {
let styleSheet = new CSSStyleSheet();
document.adoptedStyleSheets = [...document.adoptedStyleSheets, styleSheet];
this.rules.split('}').forEach((rule)=> {
if (rule.trim() !== '') {
styleSheet.insertRule(rule + '}', styleSheet.cssRules.length);
}
});
},
};
loader.start();
})();