142 lines
4.7 KiB
JavaScript
142 lines
4.7 KiB
JavaScript
// ==UserScript==
|
|
// @name SN translator
|
|
// @namespace http://tampermonkey.net/
|
|
// @version 0.1
|
|
// @description Translate posts on SN
|
|
// @author ekzyis
|
|
// @match https://stacker.news/*
|
|
// @icon https://img.freepik.com/free-vector/lightning-bolt-coloured-outline_78370-517.jpg?w=826&t=st=1671940131~exp=1671940731~hmac=d04f3bd1468f1d362a864b4a25e367aef4d1941991161aa008684c5a4089cf0b
|
|
// @grant GM_xmlhttpRequest
|
|
// @grant GM_addStyle
|
|
// ==/UserScript==
|
|
|
|
/**
|
|
MIT License
|
|
|
|
Copyright (c) 2022 ekzyis
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
*/
|
|
|
|
const headers = {
|
|
origin: 'https://libretranslate.com',
|
|
accept: '*/*',
|
|
'accept-language': 'de-DE,de;q=0.9,ru-DE;q=0.8,ru;q=0.7,en-US;q=0.6,en;q=0.5',
|
|
'sec-ch-ua': '"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"',
|
|
'sec-ch-ua-mobile': '?0',
|
|
'sec-ch-ua-platform': '"Linux"',
|
|
'sec-fetch-dest': 'empty',
|
|
'sec-fetch-mode': 'cors',
|
|
'sec-fetch-site': 'same-origin',
|
|
};
|
|
|
|
function translate(text, source, target) {
|
|
const formData = new FormData();
|
|
formData.append('q', text);
|
|
formData.append('source', source);
|
|
formData.append('target', target);
|
|
return new Promise((resolve, reject) => {
|
|
GM_xmlhttpRequest({
|
|
method: 'POST',
|
|
url: 'https://libretranslate.com/translate',
|
|
data: formData,
|
|
headers,
|
|
synchronous: true,
|
|
onload: function (res) {
|
|
const body = JSON.parse(res.responseText);
|
|
if (res.status !== 200) return reject(body);
|
|
return resolve(body.translatedText);
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
const log = (msg) => console.log(`sn-translator:`, msg);
|
|
|
|
function createButton(textNode) {
|
|
const btn = document.createElement('button');
|
|
btn.innerText = 'translate';
|
|
btn.onclick = async (e) => {
|
|
const hasTranslation = !!textNode.querySelector('hr');
|
|
if (hasTranslation) return;
|
|
// TODO: let user choose target language
|
|
const t = await translate(textNode.innerText, 'auto', 'en').catch(console.error);
|
|
if (t) {
|
|
const divider = document.createElement('hr');
|
|
textNode.appendChild(divider);
|
|
const em = document.createElement('em');
|
|
const p = document.createElement('p');
|
|
p.innerText = 'Translation:';
|
|
const p2 = document.createElement('p');
|
|
p2.innerText = t;
|
|
em.appendChild(p);
|
|
em.appendChild(p2);
|
|
textNode.appendChild(em);
|
|
}
|
|
};
|
|
btn.classList.add('translate');
|
|
return btn;
|
|
}
|
|
|
|
function addButtons() {
|
|
const commentSection = document.querySelector('.item_comments__cN57K');
|
|
if (!commentSection) return;
|
|
const comments = commentSection.querySelectorAll('.comment_comment__5uvl3');
|
|
log(`Found ${comments.length} comment(s)`);
|
|
log(`Adding translate button to every comment ...`);
|
|
for (const comment of comments) {
|
|
const topBar = comment.querySelector('.item_other__qNlji');
|
|
const textNode = comment.querySelector('.comment_text__nHI0E > .text_text__fuZIZ');
|
|
const btn = createButton(textNode);
|
|
topBar.appendChild(btn);
|
|
}
|
|
log(`Done`);
|
|
}
|
|
|
|
(async function () {
|
|
// Sleep before running script on page load
|
|
// since else we might get overwritten by loading content.
|
|
const initialSleep = 1000;
|
|
await sleep(initialSleep);
|
|
|
|
GM_addStyle(`
|
|
.translate {
|
|
color: var(--theme-grey);
|
|
border: none;
|
|
cursor: pointer;
|
|
background: rgba(0,0,0,0);
|
|
}
|
|
`);
|
|
|
|
let pathname = window.location.pathname;
|
|
log(`Current location: ${pathname}`);
|
|
addButtons();
|
|
// Check if URL changed and rerun script
|
|
const scriptInterval = 1000;
|
|
setInterval(() => {
|
|
if (window.location.pathname !== pathname) {
|
|
pathname = window.location.pathname;
|
|
log(`New location detected: ${pathname}`);
|
|
addButtons();
|
|
}
|
|
}, scriptInterval);
|
|
})();
|