您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
从Wiki获取EhTagTranslater数据库,将E绅士TAG翻译为中文,并注射到E站
当前为
// ==UserScript== // @name EhTagSyringe // @name:zh-CN E绅士翻译注射器? // @namespace http://www.mapaler.com/ // @description Build EhTagTranslater from Wiki. // @description:zh-CN 从Wiki获取EhTagTranslater数据库,将E绅士TAG翻译为中文,并注射到E站 // @include *://github.com/Mapaler/EhTagTranslator* // @include *://exhentai.org/* // @include *://e-hentai.org/* // @connect raw.githubusercontent.com // @connect github.com // @icon http://exhentai.org/favicon.ico // @require https://cdn.bootcss.com/angular.js/1.4.6/angular.min.js // @resource template https://raw.githubusercontent.com/Mapaler/EhTagTranslator/master/template/ets-builder-menu.html?v=12 // @resource ets-prompt https://raw.githubusercontent.com/Mapaler/EhTagTranslator/master/template/ets-prompt.html?v=18 // @version 1.0.0 // @run-at document-start // @grant unsafeWindow // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @grant GM_deleteValue // @grant GM_listValues // @grant GM_info // @grant GM_getResourceText // @grant GM_addValueChangeListener // @grant GM_setClipboard // @copyright 2017+, Mapaler <[email protected]> , xioxin <[email protected]> // ==/UserScript== (function() { 'use strict'; window.requestAnimationFrame = unsafeWindow.requestAnimationFrame; unsafeWindow.wikiUpdate = autoUpdate; var wiki_URL="https://github.com/Mapaler/EhTagTranslator/wiki"; //GitHub wiki 的地址 var wiki_raw_URL="https://raw.githubusercontent.com/wiki/Mapaler/EhTagTranslator"; //GitHub wiki 的地址 var rows_title="rows"; //行名的地址 var pluginVersion = typeof(GM_info)!="undefined" ? GM_info.script.version.replace(/(^\s*)|(\s*$)/g, "") : "未获取到版本"; //本程序的版本 var pluginName = typeof(GM_info)!="undefined" ? (GM_info.script.localizedName ? GM_info.script.localizedName : GM_info.script.name) : "EhTagSyringe"; //本程序的名称 var rootScope = null; const headLoaded = new Promise(function (resolve, reject) { if(unsafeWindow.document.head && unsafeWindow.document.head.nodeName == "HEAD"){ resolve(unsafeWindow.document.head); }else{ //监听DOM变化 MutationObserver = window.MutationObserver; var observer = new MutationObserver(function(mutations) { for(let i in mutations){ let mutation = mutations[i]; //监听到HEAD 结束 if(mutation.target.nodeName == "HEAD"){ observer.disconnect(); resolve(mutation.target); break; } } }); observer.observe(document, {childList: true, subtree: true, attributes: true}); } }); function AddGlobalStyle(css) { //等待head加载完毕 headLoaded.then(function (head) { GM_addStyle(css); }) } AddGlobalStyle(`<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>`); var defaultConfig = { 'showDescription':true, 'imageLimit':3, 'showIcon':true, 'syringe':true, 'searchHelper':true, 'magnetHelper':true, 'style':{ 'public':`div#taglist { overflow: visible; min-height: 295px; height: auto; } div#gmid { min-height: 330px; height:auto; position: static; } #taglist a{ background:inherit; } #taglist a::before{ font-size:12px; overflow: hidden; line-height: 20px; height: 20px; } #taglist a::after{ display: block; color:#FF8E8E; font-size:14px; background: inherit; border: 1px solid #000; border-radius:5px; position:absolute; float: left; z-index:999; padding:8px; box-shadow: 3px 3px 10px #000; min-width:150px; max-width:500px; white-space:pre-wrap; opacity: 0; transition: opacity 0.2s; transform: translate(-50%,20px); top:0; left: 50%; pointer-events:none; padding-top: 8px; font-weight: 400; line-height: 20px; } #taglist a:hover::after,#taglist a:focus::after{ opacity: 1; pointer-events:auto; } #taglist a:focus::before, #taglist a:hover::before { font-size: 12px; position: relative; background-color: inherit; border: 1px solid #000; border-width: 1px 1px 0 1px; margin: -4px -5px; padding: 3px 4px; color:inherit; border-radius: 5px 5px 0 0; } div.gt, div.gtw, div.gtl{ line-height: 20px; height: 20px; } #taglist a:hover::after{ z-index: 9999998; } #taglist a:focus::after { z-index: 9999996; } #taglist a:hover::before{ z-index: 9999999; } #taglist a:focus::before { z-index: 9999997; }`, 'ex':`#taglist a::after{ color:#fff; }`, 'eh':`#taglist a::after{ color:#000; }`, } }; var etbConfig = GM_getValue('config'); if(!etbConfig){ /*默认配置 json转换是用来深拷贝 切断关联 */ etbConfig = JSON.parse(JSON.stringify(defaultConfig)); // 不用存储 反正是默认的 // GM_setValue('config',etbConfig); } // 配置自动升级 for(var i in defaultConfig){ if(typeof etbConfig[i] === "undefined"){ etbConfig[i] = JSON.parse(JSON.stringify(defaultConfig[i])); } } console.log('ets config:',etbConfig); //UI控制方法等等 function EhTagBuilder(){ console.log('EhTagBuilder'); var buttonInserPlace = document.querySelector(".pagehead-actions");//按钮插入位置 var li = document.createElement("li"); li.id = 'etb'; li.setAttribute('ng-csp','ng-csp'); li.innerHTML = GM_getResourceText('template'); var app = angular.module("etb",[]); app.controller("etb",function($rootScope,$scope,$location,$anchorScroll){ // console.log(); $scope.pluginVersion = pluginVersion; $scope.pluginName = pluginName; $scope.config = etbConfig; $scope.nowPage = "menu"; $scope.menuShow = false; rootScope = $rootScope; $scope.dataset = false; $scope.wikiVersion = false; var backdrop = document.querySelector(".modal-backdrop"); if(backdrop)backdrop.addEventListener('click',function(){ $scope.closeMenu(); $scope.$apply(); }); //xx时间前转换方法 $scope.timetime = timeInterval; //打开菜单按钮 $scope.openMenu = function () { $scope.nowPage = "menu"; $scope.menuShow = true; }; //关闭菜单按钮 $scope.closeMenu = function () { $scope.menuShow = false; }; //开始获取 $scope.startProgram = async function () { $scope.nowPage = "getData"; await startProgram($scope); //增加一个延迟 因为处理css时候会卡住 导致加载完毕的ui无法显示 setTimeout(function(){ var css = buildCSS($scope.dataset,$scope.wikiVersion); // 存储 $scope.css = css; $scope.cssStylish = buildStylishCSS(css,$scope.config); $scope.nowPage = 'css'; $scope.$apply(); },0); }; //存储css样式 $scope.saveCss = function () { GM_setValue('tags',{ css:$scope.css, data:$scope.dataset, version:$scope.wikiVersion, update_time:new Date().getTime() }); myNotification('保存完毕'); }; $scope.copyStylishCss = function () { GM_setClipboard($scope.cssStylish) myNotification('复制完毕'); }; $scope.copyCss = function () { GM_setClipboard($scope.css) myNotification('复制完毕'); }; //打开设置界面 $scope.openOption = function () { $scope.nowPage = "option"; }; //保存设置 $scope.optionSave = function () { GM_setValue('config',etbConfig); myNotification('保存成功'); }; //重置设置 $scope.optionReset = function () { if(confirm('确定要重置配置吗?')){ $scope.config = etbConfig = JSON.parse(JSON.stringify(defaultConfig)); GM_setValue('config',etbConfig); myNotification('已重置'); } }; $rootScope.$on('$locationChangeSuccess', function(event){ if( $location.path() == "/ets-open-option" ){ $scope.openMenu(); $scope.openOption(); $anchorScroll('etb') $location.path("/"); } if( $location.path() == "/ets-open-menu" ){ $scope.openMenu(); $anchorScroll('etb') $location.path("/"); } if( $location.path() == "/ets-auto-update" ){ $scope.openMenu(); $scope.startProgram().then(function () { $scope.saveCss(); }) $anchorScroll('etb'); $location.path("/"); } if( $location.path() == "/ets-set-config" ){ let s = $location.search(); for(var i in s){ var v = s[i]; if(v === 'true'){ v = true; } if(v === 'false'){ v = false; } etbConfig[i] = v; } GM_setValue('config',etbConfig); myNotification('配置已修改',{body:JSON.stringify(s)}); $location.path("/").search({}); } if( $location.path() == "/ets-reset-config" ){ $scope.optionReset(); $location.path("/"); } }); }); angular.bootstrap(li,['etb']); // unsafeWindow.etbApp = app; buttonInserPlace.insertBefore(li,buttonInserPlace.querySelector("li")); console.log('EhTagBuilder loaded') } //样式写入方法 enema syringe function EhTagSyringe(){ console.time('EhTagSyringe Load Enema'); let tags = GM_getValue('tags'); console.timeEnd('EhTagSyringe Load Enema'); if(!tags)return; console.time('EhTagSyringe Infusion'); unsafeWindow.tags = tags; AddGlobalStyle(tags.css); AddGlobalStyle(etbConfig.style.public); if((/(exhentai\.org)/).test(unsafeWindow.location.href)){ AddGlobalStyle(etbConfig.style.ex); } if((/(e-hentai\.org)/).test(unsafeWindow.location.href)){ AddGlobalStyle(etbConfig.style.eh); } //临时隐藏翻译用的样式 AddGlobalStyle(` .hideTranslate #taglist a{font-size:12px !important;} .hideTranslate #taglist a::before{display:none !important;} .hideTranslate #taglist a::after{display:none !important;} `); console.timeEnd('EhTagSyringe Infusion'); } //EH站更新提示 function EhTagVersion() { console.log('EhTagVersion'); var buttonInserPlace = document.querySelector("#nb"); //按钮插入位置 if(!buttonInserPlace)return; var span = document.createElement("span"); var iconImg = "https://exhentai.org/img/mr.gif"; if((/(exhentai\.org)/).test(unsafeWindow.location.href)){ iconImg="https://ehgt.org/g/mr.gif"; span.className=span.className+" isEX"; } var etsPrompt = GM_getResourceText('ets-prompt'); // etsPrompt = ``; span.innerHTML = `${etsPrompt}`; var app = angular.module("etb",[]); app.controller("etb",function($rootScope,$scope){ $scope.pluginVersion = pluginVersion; $scope.pluginName = pluginName; $scope.iconImg = iconImg; $scope.config = etbConfig; $scope.noData = false; let tags = GM_getValue('tags'); if(!tags){ $scope.noData =true; } $scope.nowPage = ""; $scope.menuShow = false; rootScope = $rootScope; $scope.dataset = false; $scope.wikiVersion = {}; if(tags){ $scope.wikiVersion = tags.version; $scope.update_time = tags.update_time; } $scope.hide = false; //xx时间前转换方法 $scope.timetime = timeInterval; //打开菜单按钮 $scope.openMenu = function () { console.log('openMenu'); $scope.nowPage = "menu"; $scope.menuShow = !$scope.menuShow; }; $scope.showRow = {}; $scope.showRow.value = false; $scope.showRow.change = function(value){ if(value){ document.body.className = "hideTranslate" }else{ document.body.className = ""; } }; $scope.VersionCheck = function () { getWikiVersion().then(function (Version) { $scope.lastVersionCheck = { time:new Date().getTime(), version:Version, }; GM_setValue('lastVersionCheck',$scope.lastVersionCheck); $scope.newVersion = Version; $scope.$apply(); //这是个秘密 if(etbConfig.autoUpdate){ if($scope.newVersion.code != $scope.wikiVersion.code){ autoUpdate().then(function () { myNotification('更新完毕,刷新页面生效'); }); } } console.log(Version); }); }; let lastVersionCheck = GM_getValue('lastVersionCheck'); $scope.lastVersionCheck = lastVersionCheck; if(!lastVersionCheck){ console.log('auto VersionCheck1'); $scope.VersionCheck(); }else{ $scope.newVersion = lastVersionCheck.version; //限制20分钟检查一次版本 if(new Date().getTime() - lastVersionCheck.time > 20*60*1000 ){ console.log('auto VersionCheck'); $scope.VersionCheck(); } } unsafeWindow.r = function () { $scope.$apply(); }; }); angular.bootstrap(span,['etb']); unsafeWindow.etsApp = app; buttonInserPlace.appendChild(span); } //搜索输入框助手 function EhTagInputHelper() { if(!etbConfig.searchHelper){ return; } let tags = GM_getValue('tags'); // console.log(tags); if(!tags)return; console.time('add datalist'); let stdinput = document.querySelector('.stdinput'); if(!stdinput){return} stdinput.setAttribute("list", "tbs-tags"); var datalist = document.createElement("datalist"); datalist.setAttribute("id", "tbs-tags"); stdinput.parentNode.insertBefore(datalist,stdinput.nextSibling); //调整加载顺序 作家在前面影响搜索 let loadOrder = [ 'female', 'male', 'language', 'character', 'reclass', 'misc', 'parody', 'artist' ]; var tagsk = {}; tags.data.forEach(function (row) { tagsk[row.name] = row; }); loadOrder.forEach(function (key) { let row = tagsk[key]; let type = row.name; let typeName = row.cname; row.tags.forEach(function (tag) { if(tag.name){ let z = document.createElement("OPTION"); z.setAttribute("value", `${type}:"${tag.name}$"`); z.setAttribute("label", `${typeName}:${mdImg2cssImg(tag.cname,0)}`); datalist.appendChild(z); } }); }) console.timeEnd('add datalist'); } //磁力链复制助手 function EhTagMagnetHelper() { if(!(/gallerytorrents\.php/).test(unsafeWindow.location.href)){ return; } console.log('EhTagMagnetHelper'); let tableList = document.querySelectorAll("#torrentinfo form table"); if(tableList&&tableList.length)tableList.forEach(function (table) { console.log(table); let href = ''; let a = table.querySelector('a'); if(a)href = a.href; if(!href)return; let magnet = href.replace(/.*?([0-9a-f]{40}).*$/i,"magnet:?xt=urn:btih:$1") ; if(magnet.length != 60)return; let insertionPoint = table.querySelector('input'); if(!insertionPoint)return; var button = document.createElement("input"); button.type = "button"; button.value = "复制磁力链"; button.className = 'stdbtn'; button.onclick = function () { GM_setClipboard(magnet); myNotification('复制成功',{ body:magnet }); }; console.log(magnet); // let parent = ; insertionPoint.parentNode.insertBefore( button, insertionPoint ); }) } //获取数据 async function startProgram($scope) { console.log('startProgram'); //存放承诺 var pp = { wikiVersion:getWikiVersion(), rows:getRows(), tags:[] }; //获取 版本与row var [wikiVersion,rows] = await Promise.all([pp.wikiVersion,pp.rows]); $scope.dataset = rows; $scope.wikiVersion = wikiVersion; $scope.$apply(); //构建获取tag任务 并执行 rows.forEach(function (row) { var temp = getTags(row.name); temp.then(function (mdText) { row.tags = parseTable(mdText,row.name); $scope.$apply(); }); pp.tags.push(temp); }); //等待获取完毕 await Promise.all(pp.tags); console.log(rows); return rows; } //构建css function buildCSS(dataset,wikiVersion) { console.time('生成css样式'); var css = ""; css+=` /* update_time:${wikiVersion.update_time} */ /* hash:${wikiVersion.code} */ `; dataset.forEach(function (row) { css+= `\n/* ${row.name} ${row.cname} */\n`; // console.log(row.tags); row.tags.forEach(function (tag) { if(tag.name){ var tagid = (row.name=="misc"?"":row.name + ":") + tag.name.replace(/\s/ig,"_"); var cname = mdImg2cssImg(specialCharToCss(tag.cname),etbConfig.imageLimit<0?Infinity:etbConfig.imageLimit); if(!tag.info)tag.info=""; var content = mdImg2cssImg(htmlBr2cssBr(specialCharToCss(tag.info)),etbConfig.imageLimit<0?Infinity:etbConfig.imageLimit); css += ` a[id="ta_${tagid}"]{ font-size:0px; } a[id="ta_${tagid}"]::before{ content:"${cname}"; } `; if(content)css+=`a[id="ta_${tagid}"]::after{ content:"${content}"; }`; }else{ css += `\n/* ${row.cname} */\n`; } }); }); console.timeEnd('生成css样式'); return css; } //Stylish css function buildStylishCSS(css,config) { var cssStylish = "@namespace url(http://www.w3.org/1999/xhtml);\n"; cssStylish+=`@-moz-document domain('exhentai.org'), domain('e-hentai.org') { /* 通用样式 */ ${config.style.public} } `; cssStylish+=`@-moz-document domain('e-hentai.org') { /* 表站样式 */ ${config.style.eh} } `; cssStylish+=`@-moz-document domain('exhentai.org') { /* 里站样式 */ ${config.style.ex} } `; cssStylish+=`@-moz-document domain('exhentai.org'), domain('e-hentai.org') { body{ } /* 翻译样式 */ ${css} }`; return cssStylish; } //转换换行 function htmlBr2cssBr(mdText){ return mdText.replace(/<br[ \t]*(\/)?>/igm,"\\A "); } //转换图片 function mdImg2cssImg(mdText,max=Infinity){ var n = 0; return mdText.replace(/\!\[(.*?)\]\((.*?)\)/igm,function (text,alt,href,index) { n++; if( max >= n){ var h = trim(href); if(h.slice(0,1) == "#"){ h = h.replace(/# +\\?['"](.*?)\\?['"]/igm,"$1"); }else if(h.slice(h.length-1,h.length).toLowerCase() == 'h'){ h = h.slice(0,-1); } return `"url("${h}")"`; }else{ return ""; } }); } function specialCharToCss(str) { var strn = str; strn = strn.replace("\\","\\\\"); strn = strn.replace("\"","\\\""); strn = strn.replace("\r",""); strn = strn.replace("\n","\\A "); return strn; } //获取版本 function getWikiVersion(){ return new Promise(function (resolve, reject) { PromiseRequest.get(wiki_URL+'/_history?t='+new Date().getTime()).then(function (response) { var parser = new DOMParser(); var PageDOM = parser.parseFromString(response, "text/html"); var lastDOM = PageDOM.querySelector('#version-form table tr:nth-child(1)'); if(!lastDOM){ reject(); return; } var code = ""; var time = 0; var commit = ""; var timeDOM = lastDOM.querySelector(".date relative-time"); if(timeDOM)time = Date.parse(new Date(timeDOM.getAttribute('datetime'))); var codeDOM = lastDOM.querySelector(".commit-meta code"); if(codeDOM)code = codeDOM.innerText.replace(/(^\s*)|(\s*$)/g, ""); var commitDOM = lastDOM.querySelector(".commit code"); if(commitDOM)commit = commitDOM.innerText.replace(/(^\s*)|(\s*$)/g, ""); var v = { update_time:time, code:code, commit:commit }; resolve(v); },function () { reject(); }); }); } //去除两端空白 function trim(s){ if(typeof s == 'string'){ return s.replace(/(^\s*)|(\s*$)/g, ""); }else{ return s; } } function timeInterval (time) { if(!time){ return ''; } var now = (new Date).valueOf(); now = Math.floor(now/1000); time = Math.floor(time/1000); var t = now-time; if(!t){ return '刚刚'; } var f = [ [31536000,'年'], [2592000,'个月'], [604800,'星期'], [86400,'天'], [3600,'小时'], [60,'分钟'], [1,'秒'] ]; var c = 0; for(var i in f){ var k = f[i][0]; var v = f[i][1]; c = Math.floor(t/k); if (0 != c) { return c+v+'前'; } } } //获取行 并解析 function getRows() { return new Promise(async function (resolve, reject) { var url = `${wiki_raw_URL}/${rows_title}.md`+"?t="+new Date().getTime(); console.log(url); var data = await PromiseRequest.get(url); /*剔除表格以外的内容*/ var re = (/^\|.*\|$/gm); var table = ""; resolve( parseTable(data) ); }); } //获取标签 并解析 function getTags(row) { return new Promise(async function (resolve, reject) { var url = `${wiki_raw_URL}/tags/${row}.md`+"?t="+new Date().getTime(); console.log(url); console.time(`加载 ${row}`); var data = await PromiseRequest.get(url); console.timeEnd(`加载 ${row}`); resolve(data); }); } function parseTable(data,name) { /*剔除表格以外的内容*/ var re = (/^\s*(\|.*\|)\s*$/gm); var table = ""; var temp = ""; while( temp = re.exec(data) ){ if(table)table+="\n"; table+=temp[1]; } table = table.replace(/\\\|/igm,"{~Line~}"); let tableArr = table.split("\n").map( (row)=>row.split("|").map( (t)=>t.replace("{~Line~}","|") ) ); let tags = []; var count = []; tableArr.forEach(function (tr,index) { if(index>1){ let t = { name : trim(tr[1]||""), cname : trim(tr[2]||""), info : trim(tr[3]||"") }; tags.push(t); if(t.name){count++}; } }); console.log(name,count); return tags; } async function autoUpdate() { var $scope = {}; $scope.$apply = function(){}; await startProgram($scope); var css = buildCSS($scope.dataset,$scope.wikiVersion); GM_setValue('tags',{ css:css, data:$scope.dataset, version:$scope.wikiVersion, update_time:new Date().getTime() }); return true; } async function myNotification(title,options) { let permission = await Notification.requestPermission(); if(permission == 'granted'){ return new Notification(title, options); }else{ return false; } } //承诺封装的异步请求 function PromiseRequest(option) { return new Promise(function (resolve, reject) { option.onload = function (response) { resolve(response.responseText); }; option.onerror = function (response) { reject(response); }; // if(rootScope && rootScope.$broadcast){ // // } // option.onprogress = function (response,response2) { // // var info = { // // loaded:response.loaded, // // position:response.position, // // total:response.total, // // totalSize:response.totalSize, // // }; // // console.info('onprogress',info,response,response2); // }; GM_xmlhttpRequest(option); }); } //助手 快速get post PromiseRequest.get = function (url) { return PromiseRequest({ method: "GET", url: url, }); }; PromiseRequest.post = function (url,data) { var post = ""; for(var i in data){ if(post)post+="&"; post+= encodeURIComponent(i)+"="+encodeURIComponent(data[i]); } return PromiseRequest({ method: "POST", url: url, data: post }); }; var bootstrap = function(){ //在github页面下添加生成工具 if((/github\.com/).test(unsafeWindow.location.href)){ EhTagBuilder(); } //在EH站点下添加版本提示功能 if ((/(exhentai\.org|e-hentai\.org)/).test(unsafeWindow.location.href)) { if(etbConfig.syringe)EhTagVersion(); if(etbConfig.searchHelper)EhTagInputHelper(); if(etbConfig.magnetHelper)EhTagMagnetHelper(); } }; if (/loaded|complete/.test(document.readyState)){ bootstrap(); }else{ document.addEventListener('DOMContentLoaded',bootstrap,false); } //注射器总开关 if(etbConfig.syringe){ //注入css 不需要等待页面 if((/(exhentai\.org\/g\/|e-hentai\.org\/g\/)/).test(unsafeWindow.location.href)){ EhTagSyringe(); } } })();