微软翻译组件 右下角点击翻译
// ==UserScript== // @name 微软翻译组件 // @description 微软翻译组件 右下角点击翻译 // @supportURL https://greasyfork.org/zh-CN/scripts/26027-%E5%BE%AE%E8%BD%AF%E7%BF%BB%E8%AF%91%E7%BB%84%E4%BB%B6/feedback // @include * // @exclude *.jpg // @exclude *.png // @exclude *.jpeg // @exclude *.gif // @exclude *.pdf // @exclude *115.com/* // @require https://cdnjs.cloudflare.com/ajax/libs/js-cookie/2.1.3/js.cookie.min.js // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @grant GM_getResourceText // @grant GM_getResourceUrl // @grant GM_xmlhttpRequest // @resource httpJs http://www.microsofttranslator.com/ajax/v3/WidgetV3.ashx?siteData=ueOIGRSKkd965FeEGM5JtQ**&ctf=False&ui=false&settings=Manual&from= // @resource httpsJs https://ssl.microsofttranslator.com/ajax/v3/WidgetV3.ashx?siteData=ueOIGRSKkd965FeEGM5JtQ**&ctf=False&ui=false&settings=Manual&from= // @author aogg // @version 2.3.25 // @namespace https://greasyfork.org/users/25818 // ==/UserScript== (function (){ var width = '79', height = '23'; var divId = 'MicrosoftTranslatorWidget', divMenu = null, // 菜单div divOne = null, // 一键翻译div selectNode = null, // 选择框 divNode = null, // 主div translatorSwitch = function (){}, // 执行翻译函数 localStorageLocalsKey = divId + 'localStorageLocals', localStorageAppCloseKey = divId + 'localStorageClose', localStorageAppOneKey = divId + 'localStorageOne', appOneFunc = { // 一键翻译处理函数 get: function (){ return GM_getValue(localStorageAppOneKey); }, set: function (val){ GM_setValue(localStorageAppOneKey, val); if (divOne){ // 修改文案 divOne.innerText = divOne.oneTitleFunc(); } } }; GM_registerMenuCommand('切换微软一键翻译', function (){ if (divOne){ if (divNode){ divNode.changeMenu('show'); // 显示菜单 setTimeout(function (){ divNode.changeMenu('hide'); }, 500); // 关闭菜单 } divOne.click(); } }); if ( parent !== parent.parent || document.documentElement.clientWidth <= width * 2 || document.documentElement.clientHeight <= height * 2 || // iframe过多,或者屏幕过小 checkOnlyItem() || checkVideoFull() || // 检测视屏是否占满屏 localStorage.getItem(localStorageAppCloseKey) ){ // 控制层次,避免无限调用,如:http://www.w3school.com.cn/html/html_entities.asp // 控制宽高小的不显示 return; } // 重置cookie try{ Cookies.remove('mstto'); console.log('remove cookie mstto'); }catch(e){ console.log(e); } var locals = ''; // zh-chs var source = null; var mainStatus = false; // 是否执行了main方法 /** var noTranslator = ['wangpan']; if (self.frameElement && noTranslator.indexOf(self.frameElement.name) !== -1){ return; } */ if (navigator.userAgent.indexOf('Maxthon') > -1){ // 遨游浏览器 var scriptNode = document.createElement('script'); scriptNode.innerHTML = "setTimeout(function(){{var s=document.createElement('script');s.type='text/javascript';s.charset='UTF-8';s.src=((location && location.href && location.href.indexOf('https') == 0)?'https://ssl.microsofttranslator.com':'http://www.microsofttranslator.com')+'/ajax/v3/WidgetV3.ashx?siteData=ueOIGRSKkd965FeEGM5JtQ**&ctf=False&ui=false&settings=Manual&from=';var p=document.head[0]||document.documentElement;p.insertBefore(s,p.firstChild); }},0);"; document.head.appendChild(scriptNode); setTimeout(start,0); console.log('目前遨游浏览器因GM_xmlhttpRequest对于CSP的网站无法正确处理错误'); }else{ (function (){ var url =((location && location.href && location.href.indexOf('https') == 0)?'https://ssl.microsofttranslator.com':'http://www.microsofttranslator.com')+ '/ajax/v3/WidgetV3.ashx?siteData=ueOIGRSKkd965FeEGM5JtQ**&ctf=False&ui=false&settings=Manual&from='; try{ GM_xmlhttpRequest({ method: "GET", url: url, onload: function(response) { try{ if (response.responseText.length < 1000){ console.error('获取微软js失败,请ctrl+F5访问:' + url); } unsafeWindow.eval(response.responseText); start(); }catch(e){ console.error('无法执行eval,如github的CSP策略,可改用chrome自带的google翻译'); } } }); }catch(e){ console.error('无法执行eval,如github的CSP策略,可改用chrome自带的google翻译'); return; } })() } /** // 有缓存 var code = location && location.href && location.href.indexOf('https') == 0?GM_getResourceText('httpsJs'):GM_getResourceText('httpJs'); try{ unsafeWindow.eval(code); setTimeout(start,0); }catch(e){ console.log('无法执行eval,如github的CSP策略,可改用chrome自带的google翻译'); return; } */ divNode = document.createElement('div'); divNode.id = divId; divNode.style.color = 'white'; divNode.style.backgroundColor = '#555555'; divNode.style.position = 'fixed'; divNode.style.right = '0'; divNode.style.bottom = '1px'; divNode.style.zIndex = '9999999'; divNode.style.fontSize = '13px'; divNode.title = '翻译为,或双击隐藏'; divNode.style.width= width + 'px'; divNode.ondblclick = function(){ // 双击隐藏 translatorHide(); }; divNode.changeMenu = function (action){ var showArr = {'none':'block', 'block':'none'}; if (divMenu){ if (action === 'show'){ // 显示 divMenu.style.display = 'block'; }else if (action === 'hide'){ // 隐藏 divMenu.style.display = 'none'; }else { divMenu.style.display = showArr[divMenu.style.display] ? showArr[divMenu.style.display] : 'none'; } } }; divNode.oncontextmenu = function (event){ // 设置菜单 event.preventDefault(); this.changeMenu(); } divMenu = menu(divNode); function start(){ mutationStart(); document.body.appendChild(divNode); document.onreadystatechange = main; setTimeout(main, 500); // 最少500ms内显示 } // 隐藏全部 var translatorHide = (function(div){ return function (){ div.style.display = 'none'; } })(divNode) function main(){ if (mainStatus || (document.readyState !== 'complete' && document.readyState !== 'interactive')){ return; } mainStatus = true; // 多语言翻译 var selectHtml = document.createElement('select'), selected = appOneFunc.get() || '', status = false, // 翻译状态,false为未翻译 translateFunc = function (locals, setNodeNot){ // var option = selectHtml.selectedOptions; source = selectHtml.getAttribute('data-source') || null; setLocals(locals, setNodeNot); translateStart(); }; selectHtml.style.backgroundColor = 'rgb(178, 178, 178)'; selectHtml.ondblclick = function(){ // 双击隐藏 this.parentNode.style.display = 'none'; }; selectHtml.style.margin = 0; selectHtml.style.padding = 0; selectHtml.style.fontSize = '13.3px'; selectHtml.style.width= width + 'px'; selectHtml.style.height= height + 'px'; selectHtml.onclick = (function (){ return function (event){ // 一键翻译 var selected = appOneFunc.get(); // 每次重新获取 if (selected){ // 需要一键翻译 if (!status){ // 翻译 event.preventDefault(); translateFunc(selected) }else{ // 关闭 closeTranslator(); } status = !status; } } })(); // Microsoft.Translator.Widget.GetLanguagesForTranslateLocalized()获取所有支持的翻译选项 selectHtml.innerHTML = "\ <option id='MicrosoftTranslatorWidget-option-select' value=''>翻译为</option>\ <option value='zh-chs'>简体中文</option>\ <option value='zh-cht'>繁体中文</option>\ <option value='yue'>粤语</option>\ <option value='ja'>日文</option>\ <option id='MicrosoftTranslatorWidget-option-en' value='en'>英文</option>\ "; if (selected){ // 修改默认值 for (var i in selectHtml.options){ if (selectHtml.options[i] && selectHtml.options[i].value == selected){ selectHtml.options[i].selected = true; } } // selectHtml.value = selected; 此方法无效 } selectHtml.onchange = function (){ if (!this.value){ // 选择翻译为时,关闭翻译 closeTranslator(); } status = !!this.value; translateFunc(this.value, true); }; var parentDiv = document.body.children.namedItem(divId); // parentDiv.style.display = 'block'; parentDiv.appendChild(selectHtml); selectNode = selectHtml; // 放置到外部变量 translateStart(); } function translateStart(){ if (mainStatus && locals && unsafeWindow['Microsoft']){ // null, 'es', onProgress, onError, onComplete, onRestoreOriginal, 2000 Microsoft.Translator.Widget.Translate(source, locals, null, null, onComplete); } } function onComplete(){ var option = selectNode.options; option.namedItem('MicrosoftTranslatorWidget-option-en').innerText = '英文'; option.namedItem('MicrosoftTranslatorWidget-option-select').innerText = '翻译为'; } function addGlobalStyle(css) { var head, style; head = document.getElementsByTagName('head')[0]; if (!head) { return; } style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = css; head.appendChild(style); } console.log('完成'); // 右击层 function menu(parentDiv){ var div = document.createElement('div'); div.id= divId + "-menu"; div.style.display = 'none'; div.oncontextmenu = function (event){ event.preventDefault(); this.style.display = 'none'; } // 当前网站永久隐藏 var divHide = document.createElement('div'); divHide.innerText = '当前网站隐藏'; divHide.height = '20px'; divHide.onclick = function (){ if(window.confirm('确定要当前网站隐藏?')){ localStorage.setItem(localStorageAppCloseKey, 1); translatorHide(); } } div.appendChild(divHide); // 切换为一键翻译样式 divOne = document.createElement('div'); divOne.oneTitleFunc = function (){ return (appOneFunc.get()?'取消':'') + '一键翻译' }; divOne.innerText = divOne.oneTitleFunc(); divOne.height = '20px'; divOne.onclick = function (){ var localValue = appOneFunc.get(), val = localValue ? '' : (locals || 'zh-chs'); appOneFunc.set(val); } div.appendChild(divOne); parentDiv.appendChild(div); return div; } function setLocals(value, nodeNot){ locals = value; localStorage.setItem(localStorageLocalsKey, value); !nodeNot && (selectNode.value = value); } // 取消翻译 function closeTranslator(){ unsafeWindow['Microsoft'] && Microsoft.Translator && Microsoft.Translator.FloaterOnClose(); if (divOne){ // 恢复文案 divOne.innerText = divOne.oneTitleFunc(); } } function findVideo(){ var names = ['object', 'embed', 'video']; var ele = []; for (var i in names){ ele = document.getElementsByTagName(names[i]); if (ele.length){ return ele; } } return ele; } function checkVideoFull(){ // 检测视频是否占满屏 var ele = findVideo(); if (ele.length){ for (var i = 0; i < ele.length; ++i){ if (ele.item(i).clientHeight == document.documentElement.clientHeight && ele.item(i).clientWidth == document.documentElement.clientWidth) { return true } } } return false; } function checkOnlyItem(){ if (document.body.childElementCount === 1){ var firstNodeName = document.body.firstElementChild.nodeName, checkFirstNodeNameArr = ['SCRIPT', 'IMG']; for (var i in checkFirstNodeNameArr){ if(checkFirstNodeNameArr[i] === firstNodeName){ return true; } } }else if (document.body.childElementCount === 2){ // 只有img和script if ( (document.body.childNodes.item(0).nodeName === 'IMG' && document.body.childNodes.item(1).nodeName === 'SCRIPT') || (document.body.childNodes.item(0).nodeName === 'SCRIPT' && document.body.childNodes.item(1).nodeName === 'IMG') ){ return true; } } return false; } function mutationStart(){ function mutationFunc(mutations){ if (checkVideoFull() || checkOnlyItem()){ observer.disconnect(); translatorHide(); } } // Firefox和Chrome早期版本中带有前缀 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver // 选择目标节点 var target = document.body; // 创建观察者对象 var observer = new MutationObserver(debounce(mutationFunc, 50)); // 配置观察选项: var config = { childList: true, subtree: true } // 传入目标节点和观察选项 observer.observe(target, config); // 先执行一次 mutationFunc(); } })() // 函数去抖 function debounce(func, wait, immediate) { var _ = {}; _.now = Date.now || function() { return new Date().getTime(); }; var timeout, args, context, timestamp, r###lt; var later = function() { // 定时器设置的回调 later 方法的触发时间,和连续事件触发的最后一次时间戳的间隔 // 如果间隔为 wait(或者刚好大于 wait),则触发事件 var last = _.now() - timestamp; // 时间间隔 last 在 [0, wait) 中 // 还没到触发的点,则继续设置定时器 // last 值应该不会小于 0 吧? if (last < wait && last >= 0) { timeout = setTimeout(later, wait - last); } else { // 到了可以触发的时间点 timeout = null; // 可以触发了 // 并且不是设置为立即触发的 // 因为如果是立即触发(callNow),也会进入这个回调中 // 主要是为了将 timeout 值置为空,使之不影响下次连续事件的触发 // 如果不是立即执行,随即执行 func 方法 if (!immediate) { // 执行 func 函数 r###lt = func.apply(context, args); // 这里的 timeout 一定是 null 了吧 // 感觉这个判断多余了 if (!timeout) context = args = null; } } }; // 嗯,闭包返回的函数,是可以传入参数的 return function() { // 可以指定 this 指向 context = this; args = arguments; // 每次触发函数,更新时间戳 // later 方法中取 last 值时用到该变量 // 判断距离上次触发事件是否已经过了 wait seconds 了 // 即我们需要距离最后一次触发事件 wait seconds 后触发这个回调方法 timestamp = _.now(); // 立即触发需要满足两个条件 // immediate 参数为 true,并且 timeout 还没设置 // immediate 参数为 true 是显而易见的 // 如果去掉 !timeout 的条件,就会一直触发,而不是触发一次 // 因为第一次触发后已经设置了 timeout,所以根据 timeout 是否为空可以判断是否是首次触发 var callNow = immediate && !timeout; // 设置 wait seconds 后触发 later 方法 // 无论是否 callNow(如果是 callNow,也进入 later 方法,去 later 方法中判断是否执行相应回调函数) // 在某一段的连续触发中,只会在第一次触发时进入这个 if 分支中 if (!timeout) // 设置了 timeout,所以以后不会进入这个 if 分支了 timeout = setTimeout(later, wait); // 如果是立即触发 if (callNow) { // func 可能是有返回值的 r###lt = func.apply(context, args); // 解除引用 context = args = null; } return r###lt; }; };