AC-必应取词+翻译

一个可以在浏览器中自由使用的屏幕取词脚本-alt+鼠标翻译,或者选中按Q翻译;必应智能翻译,翻译精准

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name  AC-必应取词+翻译
// @description 一个可以在浏览器中自由使用的屏幕取词脚本-alt+鼠标翻译,或者选中按Q翻译;必应智能翻译,翻译精准
// @version 2.6
// @namespace youdao
// @license GPL-3.0-only
// @author  AC -modified from :Liu Yuyang([email protected])
// @include *
// @icon    https://gitee.com/remixAC/GM_script/raw/master/images/head.jpg
// @require https://greasyfork.org/scripts/10783-md5/code/md5.js?version=60242
// @run-at  document-end
// @note    V2.6 2020-07-19 修复翻译API失效的问题
// @note    V2.5 2019-12-05 修复由于之前没注意的点导致的部分网站运行异常的问题
// @note    V2.3 2019-09-06 搜狗翻译彻底失效,由于搜狗采用的方案越来越狗血,无法较好的获取到实际的sign,因此放弃使用搜狗,转为使用必应来进行翻译
// @note    V2.2 2019-08-28 更新搜狗翻译失效的问题,修改为搜狗的api翻译
// @note    V2.1 2019-05-25 继续修复翻译失效的问题,后来发现是Resouece无法即时的刷新,所以现在采用脚本自动的获取数据,同时也支持了其他脚本的运行
// @note    V2.0 2019-03-24 只支持Tampermonkey。由于GM_getResourceText在GM下的不支持的原因,所以暂时只支持Tampermonkey的运行
// @note    V1.9 2019-02-21 继续更新搜狗API更新导致的无法翻译的bug -- 变更为库文件更新模式,可以很方便的更新token
// @note    V1.8 2018-12-19 更新搜狗API的key,且修复了部分特殊关键词无法翻译的bug
// @note    V1.7 2018-12-04 修复搜狗搜索API更新的问题--已经更换为搜狗的翻译API;如果搜狗更新的话,那估计又要换回必应翻译了
// @note    V1.6 2018-05-23 修复部分英文单词翻译不出来的bug,同时修复特殊的BOLD标签
// @note    V1.5 2018-05-16 修复英文翻译单词的bug,同时修复语音的问题,暂时就这样了,最近不更新了,有需要的话提出来,以后再说
// @note    V1.4 2018-05-16 修复上一次更新导致的bug,同时修复多种语言翻译,结果却翻译成了英文的bug
// @note    V1.3 2018-05-16 修复word词组的时候翻译出错的问题
// @note    V1.2 2018-05-16 修复没有添加connect的错误问题
// @note    V1.1 2018-05-12 有道自己不更新~~HTTPS证书过期,现在使用搜狗翻译吧,老司机们说搜狗翻译还是很有优势的,至于百度翻译,再说吧
// @note    V1.0 2017-11-22 修复firefox57的翻译的BUG
// @note    V0.9 2017-11-15 修复了一大堆,优化了下界面的展示问题
// @note    V0.8 2017-2-21 修正了由于z-index过低导致的淘宝页面翻译过于底层而看不见
// @note    V0.7 2016-9-29 修正了部分格式错误,换行的时候稍微好了点
// @note    V0.6 2016-1-12 终于修正了,之前只改了句子翻译中的文字颜色,才发现单词翻译后的颜色简直看不清。。。
// @note    V0.5 2016-1-9 修改默认背景色
// @note    V0.4 2016-1-8 修正chrome的支持
// @note    V0.3 2016-1-8 修改窗体默认大小,尽可能的显示回车功能,避免了以前的全部显示在同一行--依旧有bug
// @note    V0.2 2015-11-26 修改部分地方适合自己使用,选中文字,按下Q翻译,或者alt+鼠标选中,自动翻译
// @grant   GM_xmlhttpRequest
// @connect fanyi.youdao.com
// @connect fanyi.sogou.com
// @connect xbaidu.ntaow.com
// @connect cn.bing.com
// @connect www.bing.com
// @grant   GM_addStyle
// @grant   GM_getValue
// @grant   GM.getValue
// @grant   GM_setValue
// @grant   GM.setValue
// ==/UserScript==
(function(){

	var curX;
	var curY;
	var timeout;
	var transResult;
	var Config = {}; // 之前忘了加上参数的定义,导致了污染全局的变量

	if (typeof(GM) == "undefined") {
		// 这个是ViolentMonkey的支持选项
		GM = {};
		GM.setValue = GM_setValue;
		GM.getValue = GM_getValue;
	}

	Promise.all([GM.getValue("Config")]).then(function (data) {
		if (data[0] != null) {
			Config = data[0];
		}else{
			Config = {};
		}
		// 初始化完成之后才能调用正常函数
		callback();
	}).catch(function (except) {
		console.log(except);
	});

	function callback() {
		document.body.addEventListener("mousemove", function(e){curX = e.clientX; curY = e.clientY;}, false);
		document.body.addEventListener("keypress", callbackWrapper, false);
		document.body.addEventListener("mouseup", callbackWrapper, false);
		document.body.addEventListener("dblclick", callbackWrapper, false);
		GM_addStyle("bold{font-weight: bold;}");
	}

	function callbackWrapper(e) {
		if(e.type === 'dblclick' && isInACTransPop()){
			// console.log("双击操作:复制目标文本");
			// console.log(transResult);
			return;
		}
		if(e.charCode == 113  || e.type == "mouseup"){ //Q
			// remove previous .ACyoudaoPopup if exists
			var previous = document.querySelector(".ACyoudaoPopup");
			if(previous && isInACTransPop()){
				document.body.removeChild(previous);
				transResult = "";
			}
			// quick fix. with ctrl key held
			if (e.type == "mouseup" && !e.altKey) {
				return;
			}
			clearTimeout(timeout);
			timeout = setTimeout(function(){
				translate();
			}, 120);
		}
	}

	function isInACTransPop(){
		var checkNode = document.getSelection().anchorNode.parentNode;
		return checkNode && (""+checkNode.className).indexOf("ACyoudao") < 0;
	}
	function translate() {
		var selectObj = document.getSelection();
		if (selectObj.anchorNode.nodeType == 3) {
			var word = selectObj.toString();
			if (word == "") {
				return;
			}
			word = word.replace(new RegExp('(\r\n)+', 'g'), 'QWER');// 特殊标记便于替换
			var ts = new Date().getTime();
			var mx = curX;
			var my = curY;
			translate(word, ts);
		}

		function popup(mx, my, result) {
			var youdaoWindow = document.createElement('div');
			youdaoWindow.classList.toggle("ACyoudaoPopup");
			// parse
			var res = JSON.parse(result);
			var dictJSON = res[0]['translations'][0];

			var query = dictJSON['displayTarget'] || dictJSON['text'];
			// var errorCode = dictJSON['errorCode'];
			if (dictJSON['dictionary']) {
				word();
			} else {
				sentence();
			}
			// main window
			// first insert into dom then there is offsetHeight!IMPORTANT!

			document.body.appendChild(youdaoWindow);
			youdaoWindow.style = "color: #000; text-align: left; position: fixed; background: rgb(237, 246, 255); border-radius: 3px; box-shadow: 0px 0px 5px 0px; opacity: 1; min-width: 300px; max-width: 550px; overflow-wrap: break-word; font-size: 15px;padding: 3px; z-index: 2147483647;display:block !important;";
			youdaoWindow.style.left = mx + 10 + "px";
			if (mx + 200 + 30 >= window.innerWidth) {
				youdaoWindow.style.left = parseInt(youdaoWindow.style.left) - 200 + "px";
			}
			//alert(window.innerHeight);
			if (my + youdaoWindow.offsetHeight + 30 >= window.innerHeight) {
				youdaoWindow.style.bottom = "20px";
			} else {
				youdaoWindow.style.top = my + "px";
			}

			function word() {
				function play(soundURL) {
					if(soundURL.indexOf("http") != 0) soundURL = "https:" + soundURL;
					function playSound(buffer) {
						var source = context.createBufferSource();
						source.buffer = buffer;
						source.connect(context.destination);
						source.start(0);
					}
					var context = new AudioContext();
					// var soundUrl = 'https://dict.youdao.com/dictvoice?type=2&audio='+encodeURIComponent(word);
					console.log(soundURL);
					var soundUrl = soundURL;
					var p = new Promise(function(resolve, reject) {
						var ret = GM_xmlhttpRequest({
							method: "GET",
							url: soundUrl,
							responseType: 'arraybuffer',
							onload: function(res) {
								try {
									context.decodeAudioData(res.response, function(buffer) {
										resolve(buffer);
									});
								} catch(e) {
									reject(e);
								}
							}
						});
					});
					p.then(playSound, function(e) {
						console.log(e);
					});
				}
				// var basic = dictJSON['basic'] != null ? dictJSON['basic'] : dictJSON['dictionary']['content'][0];
				var basic = dictJSON;
				var header = document.createElement('div');
				var phoneticText = [];
				header.className = "ACyoudaoPopupDIV";
				// header
				var span = document.createElement('span');
				span.className = "ACyoudaoPopupSpan";
				span.innerHTML = query;
				header.appendChild(span);
				// phonetic if there is
				var phonetic = basic['phonetic'];
				var transWord;
				try{
					transWord = basic['category'][0]['sense'][0]['description'];
				}catch (e) {
					transWord =  basic['usual'][0]['values'];
				}
				if(typeof(phonetic) == "object"){
					var i = 0;
					phonetic.map(function(one){
						phoneticText[i] = one['text']+";";
						i++;
					});
				}else{
					phoneticText[0] = phonetic;
				}
				for(var i = 0; i < phoneticText.length; i++){
					var phoneticNode = document.createElement('span');
					if(i == phoneticText.length-1)
						phoneticNode.innerHTML = '[' + phoneticText[i] + ']:'+transWord;
					else
						phoneticNode.innerHTML = '[' + phoneticText[i] + ']';
					phoneticNode.style.cursor = "pointer";
					header.appendChild(phoneticNode);
					var playLogo = document.createElement('span');
					header.appendChild(phoneticNode);
					(function(index){
						phoneticNode.addEventListener('mouseup', function(e){
							if (e.target === phoneticNode) { }
							e.stopPropagation();
							play(phonetic[index]['filename']);
						}, false);
					})(i);
				}
				header.style.lineHeight = "1.8";
				header.style.color = "black";
				header.style.margin = "0";
				header.style.padding = "0";
				span.style.fontSize = "15px";
				span.style.color = "black";

				youdaoWindow.appendChild(header);
				var hr = document.createElement('hr');
				hr.className = "ACyoudaoPopupHR";
				hr.style.margin = "0";
				hr.style.padding = "0";
				hr.style.height = "1px";
				hr.style.borderTop = "dashed 1px black";
				youdaoWindow.appendChild(hr);
				var ul = document.createElement('ul');
				ul.className = "ACyoudaoPopupUL";
				// ul style
				ul.style.margin = "0";
				ul.style.padding = "0";
				var mapNode;
				try{
					mapNode = basic['category'][0]['sense'][0]['example'];
				}catch (e) {
					mapNode = basic['content'][0]['item']['core'];
				}
				mapNode.map(function(trans) {
					var li = document.createElement('li');
					li.className = "ACyoudaoPopupLi";
					li.style.listStyle = "none";
					li.style.margin = "0";
					li.style.padding = "0";
					li.style.background = "none";
					li.style.color = "#008CD8";
					var innerHTML = "";
					if(trans['source'] != undefined){
						innerHTML = trans['source']+":"+trans['target'];
					}else{
						innerHTML = trans['detail']['zh']+":" + trans['detail']['en']+":";
					}
					var divNode = document.createElement("div");
					divNode.innerHTML = innerHTML;
					li.appendChild(divNode);
					ul.appendChild(li);
				});
				youdaoWindow.appendChild(ul);
			}

			function sentence() {
				var ul = document.createElement('ul');
				ul.className = "ACyoudaoPopupUL";
				// ul style
				ul.style.margin = "0";
				ul.style.padding = "0";
				var trans = dictJSON['displayTarget'] || dictJSON['text']; // 翻译结果
				// var oriText = dictJSON['translate']['text']; // 原始文本-找不到
				var li = document.createElement('li');
				li.style.listStyle = "none";
				li.style.margin = "0";
				li.style.padding = "0";
				li.style.background = "none";
				li.style.color = "#008CD8";
				li.className = "ACyoudaoPopupLi";
				trans = trans.replaceAll('QWER', '\n').replaceAll(",", ", ");
				console.log(trans);
				// if(oriText.length < 70 && !/\n/.test(oriText)) // 如果长度比较短,并且没有回车符
				// 	trans = oriText+"\n"+trans;
				transResult += trans;
				var reg = new RegExp("\n","gm");
				while(reg.exec(trans) != null){// 如果找不到回车了,那么最后重复一次新建<p>
					//找到一个回车,那么添加一个新的<p>,内容为:trans.sub(0, ?); trans = trans.sub(?);
					var newNode = document.createElement("li");
					newNode.className = "ACyoudaoPopupLi";
					newNode.style.listStyle = "none";
					newNode.style.margin = "0";
					newNode.style.padding = "0";
					newNode.style.background = "none";
					newNode.style.color = "#008CD8";
					newNode.style.fontSize = "15px";
					newNode.innerHTML = trans.substring(0, reg.lastIndex-1);
					li.appendChild(newNode);
					trans = trans.substring(reg.lastIndex);
				}
				var newNode = document.createElement("div");
				newNode.className = "ACyoudaoPopupDiv";
				newNode.innerHTML = trans;
				newNode.style = "line-height: 1.8;color:#008CD8 !important;font-size:15px !important;margin-left:10px;margin-right:10px;";
				li.appendChild(newNode);
				ul.appendChild(li);
				youdaoWindow.appendChild(ul);
			}
		}
		function ToCDB(str) {
			var tmp = "";
			for(var i=0;i<str.length;i++)
			{
				if(str.charCodeAt(i)>65248&&str.charCodeAt(i)<65375)
				{
					tmp += String.fromCharCode(str.charCodeAt(i)-65248);
				}
				else
				{
					tmp += String.fromCharCode(str.charCodeAt(i));
				}
			}
			return tmp
		}
		function translate(word, ts) {
			var ToLANGUAGE = "en";
			if(!/[\u4E00-\u9FA5]/g.test(word)) // 如果没有中文,那么说明是外文--> to=中文
				ToLANGUAGE = "zh-Hans";
			var data = `fromLang=auto-detect&to=${ToLANGUAGE}&text=${encodeURIComponent(word)}`;
			console.log(data);
			var ret = GM_xmlhttpRequest({
				url: "https://www.bing.com/ttranslatev3",
				method: "POST",
				data: data,
				binary: true,
				headers: {"Accept": "application/json", "Content-Type": "application/x-www-form-urlencoded"},  // can be omitted...
				onload: function(res) {
					var retContent = res.response;
					console.log(retContent);
					retContent = ToCDB(retContent);
					//console.log(retContent)
					popup(mx, my, retContent);
				},
				onerror: function(res) {
					console.log("error");
				}
			});
		}
		String.prototype.replaceAll = function(s1,s2){
			return this.replace(new RegExp(s1,"gm"),s2);
		};
	}
})();