您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
一键翻译Content内容到中文
/* eslint-disable no-multi-spaces */ // ==UserScript== // @name Kemono中文翻译按钮 // @namespace Kemono-Translate // @version 0.6.1 // @description 一键翻译Content内容到中文 // @author PY-DNG // @license GPL-v3 // @match https://kemono.party/fanbox/user/*/post/* // @match https://kemono.party/patreon/user/*/post/* // @match https://kemono.su/fanbox/user/*/post/* // @match https://kemono.su/patreon/user/*/post/* // @require https://greasyfork.org/scripts/456034-basic-functions-for-userscripts/code/script.js?version=1226884 // @require https://greasyfork.org/scripts/452362-baidu-translate/code/Baidu%20Translate.js?version=1175971 // @connect baidu.com // @icon data:image/png;base64,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 // @grant GM_xmlhttpRequest // @run-at document-start // ==/UserScript== /* global LogLevel DoLog Err $ $All $CrE $AEL $$CrE addStyle detectDom destroyEvent copyProp copyProps parseArgs escJsStr replaceText getUrlArgv dl_browser dl_GM AsyncManager */ /* global bdTransReady baidu_translate */ (function __MAIN__() { 'use strict'; // Constances const CONST = { TextAllLang: { DEFAULT: 'zh-CN', 'zh-CN': { Initing: '初始化...', Translate: '翻译', Translating: '翻译中...', ShowOriginal: '显示原文', ShowTrans: '显示译文' } } }; // Init language const i18n = !Object.keys(CONST.TextAllLang).includes(navigator.language) ? navigator.language : CONST.TextAllLang.DEFAULT; CONST.Text = CONST.TextAllLang[i18n]; detectDom('.post__footer', content => detectDom('.post__actions>*:nth-child(2)', fav => main())); function main() { const content = $('.post__content'); if (!content) {return false;} // Content const container = $('.post__actions'); const button = container.children[1].cloneNode(true); button.children[0].remove(); const button_text = button.children[0]; button_text.innerText = CONST.Text.Initing; container.appendChild(button); transButton(button, button_text, content); // Comment const comments = $All('.comment'); for (const comment of comments) { const header = $(comment, '.comment__header'); const btn = $$CrE({ tagName: 'span', styles: { float: 'right', cursor: 'pointer' }, props: { innerText: CONST.Text.Initing } }); const content = $(comment, '.comment__body'); header.appendChild(btn); transButton(btn, btn, content); } function transButton(button, button_text, content) { let instantTrans = false; $AEL(button, 'click', function() { instantTrans = true; }, {once: true}); bdTransReady(function() { button_text.innerText = CONST.Text.Translate; const Translator = new NodeTranslator(); let tranlated = false; let hasTrans = false; $AEL(button, 'click', switchTrans); instantTrans && switchTrans(); function switchTrans() { (tranlated ? Translator.recover : Translator.translate)(content, onTrans); function onTrans(status) { const node = this; if (node === content) { switch (status) { case -1: { button_text.innerText = hasTrans ? CONST.Text.ShowTrans : CONST.Text.Translate; tranlated = false; break; } case 0: { button_text.innerText = CONST.Text.Translating; break; } case 1: { button_text.innerText = CONST.Text.ShowOriginal; tranlated = true; hasTrans = true; break; } } } } } }); } } function NodeTranslator() { const NT = this; NT.transMap = new Map(); NT.translate = translate; NT.recover = recover; // Translate whole node tree // callback: [all nodes in tree].forEach(node => callback.call(node, status)); // status = {-1: untranslated, 0: translating, 1: translated} // Returns a boolean repersents whether translate progress can start. (if already translated or during translating, retuns false) function translate(node, callback) { // Get node text obj const text = getTextObj(node); // Translate if (text.status !== 0) { if (node.childNodes.length) { const AM = new AsyncManager(); for (const childNode of node.childNodes) { translate(childNode, function(status) { status === 1 && AM.finish(); callback.apply(this, arguments); if (text.status === -1 && status === 0) { text.status = status; callback.call(node, text.status); } }) && AM.add(); } AM.onfinish = function() { text.status = 1; callback.call(node, 1); } AM.finishEvent = true; return true; } else { return transTerminalNode(node, callback); } } else { return false; } } // Show original text for whole node tree // callback: [all nodes in tree].forEach(node => callback.call(node, status)); // status = {-1: untranslated, 0: translating, 1: translated} // Returns a boolean repersents whether recover progress can start. (if not translated or during translating, retuns false) function recover(node, callback) { // Get node text obj const text = getTextObj(node); // Recover if (text.status !== 0) { if (node.childNodes.length) { const AM = new AsyncManager(); for (const childNode of node.childNodes) { recover(childNode, function(status) { status === -1 && AM.finish(); callback.apply(this, arguments); }) && AM.add(); } AM.onfinish = function() { text.status = -1; callback.call(node, text.status); } AM.finishEvent = true; return true; } else { return recoverTerminalNode(node, callback); } } else { return false; } } // Translate single terminal node // callback: callback.call(node, status=1); // status = {-1: untranslated, 0: translating, 1: translated} // Returns a boolean repersents whether translate progress can start. (if already translated or during translating, retuns false) function transTerminalNode(node, callback) { // No need to translate if nothing more than whitespaces if (!node.nodeValue || !node.nodeValue.trim()) {return false;} // No translating for links if (node.nodeValue.trim().match(/^https?:\/\//)) {return false;} // Get node text obj const text = getTextObj(node); // Returns true only when untranslated if (text.status === -1) { if (text.hasTrans) { node.nodeValue = text.trans; text.status = 1; // translated, callback setTimeout(callback.bind(node, text.status), 0); } else { baidu_translate({ text: text.ori, dst: 'zh', callback: function(result_text) { text.trans = node.nodeValue = result_text; text.status = 1; text.hasTrans = true; // translated, callback callback.call(node, text.status); }, onerror: function(reason) { DoLog(LogLevel.Error, 'translate error'); } }); text.status = 0; // translating, callback callback.call(node, text.status); } return true; } else { return false; } } // Show original text of single terminal node // callback.call(node, status=-1); // status = {-1: untranslated, 0: translating, 1: translated} // Returns a boolean repersents whether recover progress can start. (if not translated or during translating, retuns false) function recoverTerminalNode(node, callback) { // Get node text obj const text = getTextObj(node); // Returns true only when translated if (text.status === 1) { node.nodeValue = text.ori; text.status = -1; // recovered, callback setTimeout(callback.bind(node, text.status), 0); return true; } else { return false; } } function getTextObj(node) { // Init node text obj !NT.transMap.has(node) && NT.transMap.set(node, { ori: node.nodeValue, trans: null, status: -1, hasTrans: false }); // Get node text obj return NT.transMap.get(node); } } })();