// ==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, result;
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 函数
result = 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 可能是有返回值的
result = func.apply(context, args);
// 解除引用
context = args = null;
}
return result;
};
};