英语阅读助手

点击开始,获取p 标签的单词,然后获取解释,鼠标悬停到段落便可以显示解释,也可以选择fixed 模式,然后点击段落,便可以显示单词列表,可以通过设置style_element 的内容设置此脚本面板的样式

目前為 2019-06-26 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         英语阅读助手
// @namespace    http://tampermonkey.net/
// @version      0.7
// @description  点击开始,获取p 标签的单词,然后获取解释,鼠标悬停到段落便可以显示解释,也可以选择fixed 模式,然后点击段落,便可以显示单词列表,可以通过设置style_element 的内容设置此脚本面板的样式
// @author       lavaf
// @match        http://127.0.0.1:8848/TestyoudaoTranslate/pages/testyouhou.html
// @match        http://www.51voa.com/*
// @match        http://phantomjs.org/*
// @match        https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.form.clientsize?view=netframework-4.8
// @match 		 http://localhost/pages/testyouhou.html
// @match		 https://developer.android.google.cn/reference/android/support/design/widget/FloatingActionButton?hl=en
// @grant        none
// @require  https://cdn.bootcss.com/jquery/3.4.1/jquery.js
// @require https://cdn.jsdelivr.net/gh/emn178/js-sha256/build/sha256.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js
// ==/UserScript==
(function() {
    'use strict';
	var appKey = '28aabe54f8ad1add';
	var Secret = 'Uf2mY5g5x6OyCIE0EPmDCJFilo4cWqvB'; //注意:暴露appSecret,有被盗用造成损失的风险
	var from_l = 'en';
	var to = 'zh-CHS';
	var no_translate = ['a', 'an', 'the', 'than', 'would', 'rather', 'may', 'woman', 'women', 'man', 'other', 'any', 'many',
		'some',
		'have', 'has', 'for', 'on', 'to', 'of', 'in', 'from',
		'first', 'second',
		'am', 'is', 'are', 'were', 'be', 'was', 'will', 'did', 'do', 'does',
		'no', 'not', 's', 'n',
		'you', 'us', 'myself', 'herself', 'his', 'he', 'her', 'i', 'me', 'him', 'they', 'it', 'it\'s',
		'so', 'or', 'and', 'where', 'there', 'this', 'that', 'what', 'done', 'with', 'at', 'she'
	]
	var no_idom = ['of'] //不需要翻译短语
	var textPanel; //全局的单词列表面板对象
	var result_count = 0; //已经获得的单词的数量
	var result_table = {} //全部已经获得的单词
	var deleted_table = {} //已经删除不再显示的单词
	var speech_table = {} //存放音标和发音
	
	var setting_item_name = ['show_delete_word_button', 'show_ph', 'show_ph_button']
	var setting = {
		'show_delete_word_button': false,
		'show_ph': false,
		'show_ph_button': false
	}
	var style = {
		controlpanel: {
			style: {},
			child: {
				'move-button': {},
				'start-button': {}
			}
		}
	}
	var adw = new addWin('50px', '100px');
	/*
	加载存储在local storage 中的数据
	*/
	if (localStorage) {
		var a = [
			'result-table',
			'speech-table',
			'deleted-table',
			'setting'
		]
		var objects = [null, null, null, null]
		for (let var1 in a) {
			let current = a[var1]
			var result_temp = localStorage.getItem(current);
			if (result_temp != null) {
	
				objects[var1] = JSON.parse(result_temp);
	
				console.log("从localStorage中加载缓存" + current);
				adw.show("从localStorage中加载缓存" + current);
			}
		}
		if (objects[0] != null) result_table = objects[0]
		if (objects[1] != null) speech_table = objects[1]
		if (objects[2] != null) deleted_table = objects[2]
		if (objects[3] != null) setting = objects[3]
	
	}
	/*
	获取已经保存的单词数目
	*/
	for (let s in result_table) {
		result_count++;
	}
	var offestX, offestY;
	var movable = false;
	var panel = createControlPanel();
	var audio = $("<audio>", {
		src: ""
	}).appendTo("body");
	var style_element = $('<style>').appendTo('head').text('#lavaf-start-get-word-button{color:red}'+
	
	'#lavaf-control-panel{background-color:white;z-index:100}')
	
	// var script_element=$('<script>').appendTo('body').text('function lavaf_play(src) {if (src == undefined) {return;}var audio=$("audio");audio.attr(\'src\', src);audio.get(0).play()};'+
	// "function delete_word(word_name) {if (deleted_table[word_name] == null) {let t = new Date();deleted_table[word_name] = {'date': Date(),'m': t.getTime()}} else {delete deleted_table[word_name];}localStorage.setItem('deleted-table', JSON.stringify(deleted_table));}")
	
	/**
	 * 获取上一次选择的类型
	 */
	function getLastSelectionType() {
		var show_type_last_selection = "title";
		if (localStorage) {
			var save_show_type = localStorage.getItem("save-show-type");
			if (save_show_type != null) {
				show_type_last_selection = save_show_type;
			}
		}
		return show_type_last_selection;
	}
	/**
	 * 创建控制面板
	 */
	function createControlPanel() {
		let div = $("<div>", {
			'id': 'lavaf-control-panel'
		}).css({
			'position': 'absolute',
			'left': '10px',
			'top': '100px',
			'border': '#000000 solid 1px',
			'padding': '0 0 10px 0',
			'z-index':100
		})
		let taskBarOfControlPanel=$('<button>').text("恢复").css({
			'display':'none',
			'position':'absolute',
			'bottom':'0',
			'left':'0'
		}).appendTo('body').click(function(){
			$(this).css('display','none')
			div.css('display','block')
		})
		let controlHead=$('<div>',{
			'id':'lavaf-control-head'
		}).appendTo(div);
		var move_button = $("<button>", {
			'id': "lavaf-move-button",
			'title':'点击这里可以移动'
		}).text("+++++++++").css({
			'width':'80%'
		}).mousedown(function(e) {
			offestX = e.clientX - div.offset().left
			offestY = e.clientY - div.offset().top;
			movable = true;
		}).appendTo(controlHead);
		var minibButton=$('<button>',{
			'title':'最小化'
		}).text("-").css({
			'width':'15%'
		}).click(function(){
			div.css('display','none')
			taskBarOfControlPanel.css('display','inline');
			adw.show("看屏幕左下角")
		});
		controlHead.append(minibButton)
		let controlBody=$("<div>",{
			'id':"lavaf-control-body"
		}).css({
			'padding':'10px',
			
		}).appendTo(div)
		appendMouseMoveEvents(div)
		/*
		显示保存的单词数量
		*/
		$("<div>", {
			'id': 'lavaf-show-saved-word-num'
		}).text(`保存的单词:${result_count}`).appendTo(controlBody);
		//获取上次的选择
		var show_type_last_selection = getLastSelectionType();
		var selection_type_show = $("<select>", {
			id: "lavaf-selection-type-show"
		}).appendTo(controlBody);
		var option1 = $("<option>", {
			'id': 'lavaf-selection-type-option-itme-1',
			'class': 'lavaf-selection-type-option-item',
			value: "title"
		}).text("title");
		var option2 = $("<option>", {
			'id': 'lavaf-selection-type-option-itme-2',
			'class': 'lavaf-selection-type-option-item',
			value: 'fixed'
		}).text("fixed");
		if (show_type_last_selection === "title") {
			option1.attr('selected', "selected")
		} else {
			option2.attr('selected', "selected")
		}
		selection_type_show.append(option1).append(option2);
		var mutil_container = $("<div>", {
			id: 'lavaf-setting-multiple-select-container'
		});
		var need_show_component = $("<select>", {
			'id': 'lavaf-setting-multiple-select',
			'multiple': 'multiple'
		}).change(function() {
			let child = need_show_component.children()
			if (child.get(3).selected) {
				child.each(function(index, element) {
					if (index !== 3) {
						element.selected = false;
						element.blur()
						for (let s of setting_item_name) {
							setting[s] = false;
						}
					}
				})
			} else {
				child.each(function(index, element) {
					if (index != 3) {
						setting[setting_item_name[index]] = element.selected;
					}
				})
			}
			localStorage.setItem('setting', JSON.stringify(setting))
		}).appendTo(mutil_container);
		/*
		为 select 标签添加数据
		*/
		var component_array = ["显示删除单词按钮", "显示音标", "显示发音按钮", "无"]
		for (let item in component_array) {
	
			var option_show_delete_word = $("<option>", {
				id: 'lavaf-setting-multiple-select-option-' + item,
				class: 'lavaf-setting-multiple-select-option-item',
				value: component_array[item]
			}).text(component_array[item]).appendTo(need_show_component);
			if (item != 3) {
				var item_selected = setting[setting_item_name[item]]
				option_show_delete_word.attr('selected', item_selected)
				if (item_selected) {
					option_show_delete_word.get(0).focus()
				} else {
					// option_show_delete_word.blur
				}
			}
		}
		controlBody.append(mutil_container);
		//显示result-table 面板
		var show_result_table_panel_button = $("<button>", {
			id: 'lavaf-show-result-table-panel-button'
		}).text("显示全部单词").appendTo(controlBody);
		show_result_table_panel_button.click(function() {
			let r = "";
			for (let var1 in result_table) {
				if (deleted_table[var1] == null)
					r += getWordListItem(var1);
			}
			showTextPanel(r);
		});
		controlBody.append('<br>')
		var show_deleted_word = $('<button>', {
			id: 'lavaf-show-deleted-word-button'
		}).text("显示已经删除的单词").appendTo(controlBody);
		show_deleted_word.click(function() {
			let r = '';
			for (let var1 in deleted_table) {
				r += getWordListItem(var1);
				r += "<p>" + JSON.stringify(deleted_table[var1]) + "</p>";
			}
			showTextPanel(r);
		});
		controlBody.append('<br>')
		var start = getButton(1);
		controlBody.append(start);
		return div.appendTo('body');
	}
	/**
	 * 有道提供查词功能
	 * @param {string} input 要查询的单词
	 */
	function getInput(input) {
		if (input.length == 0) {
			return null;
		}
		var result;
		var len = input.length;
		if (len <= 20) {
			result = input;
		} else {
			var startStr = input.substring(0, 10);
			var endStr = input.substring(len - 10, len);
			result = startStr + len + endStr;
		}
		return result;
	}
	/**
	 * 为可移动的元素添加鼠标移动的事件
	 * @param {Object} div 需要操作的元素
	 */
	function appendMouseMoveEvents(div) {
		div.mousemove(function(e) {
			// var e = e || window.event;
			if (movable) {
				var move_x = e.clientX - offestX;
				var move_y = e.clientY - offestY;
				div.css({
					'top': move_y + "px",
					'left': move_x + "px"
				})
			}
		}).mouseup(function() {
			movable = false;
		})
	}
	/**
	 * 创建显示单词列表的面板
	 * @param {Object} str 要显示的html 内容
	 */
	function createTextPanel(str) {
		var div = $("<div>").css({
			'position': 'absolute',
			'top': (100 + 10) + "px",
			'left': (100 + 10) + "px",
			'background-color': 'lightgray',
			'color': 'black',
			'z-index':90
		});
		var inner_button = $("<button>").text("◍").css({
			'padding': '10px'
		}).appendTo(div);
		inner_button.mousedown(function(e) {
			offestX = e.clientX - div.offset().left;
			offestY = e.clientY - div.offset().top
			movable = true;
		});
		appendMouseMoveEvents(div)
		var close_button = $("<button>", {
			id: 'text-panel-close-button'
		}).text("X").appendTo(div);
		close_button.click(function() {
			textPanel.css('display', 'none')
		})
		var inner_dix = $("<div>").css('padding', '10px').html(str).appendTo(div);
		return div;
	}
	
	function addWin(left, top) {
		this.timeout;
		this.win;
		this.delay_move = function() {
			this.timeout = setTimeout(() => {
				document.body.removeChild(this.win);
				this.win = null;
			}, 2000)
		}
		this.show = function(msg) {
			if (this.win != null && this.win != undefined) {
				clearTimeout(this.timeout);
				this.win.innerText = msg;
				this.delay_move()
			} else {
				this.win = document.createElement('div');
				this.win.className = 'lavaf-message';
				this.win.style.position = 'absolute';
				this.win.style.top = top || '100px';
				this.win.style.left = left || '100px';
				this.win.innerText = msg;
				this.win.style.backgroundColor = 'lightgreen';
				this.win.style.paddingLeft = '15px';
				this.win.style.paddingRight = '15px';
				this.win.style.paddingTop = '5px';
				this.win.style.paddingBottom = '5px';
				document.body.appendChild(this.win);
				this.delay_move()
			}
		}
	
	
	}
	/**
	 * 给p 添加title ,或者设置click事件
	 * @param {Object} word_table
	 * @param {HtmlElement} current_element
	 */
	function addTitleOrText(word_table, current_element) {
	
		var current_selection_index = document.getElementById("lavaf-selection-type-show").selectedIndex
		if (current_selection_index == 0) { //显示title
			let result = "";
			for (let var1 in word_table) {
				let word_name = word_table[var1];
				if (result_table[word_table[var1]] != null)
					if (deleted_table[word_name] == null)
						result += word_table[var1] + ":" + result_table[word_table[var1]] + "\n";
			}
			current_element.title = result;
			localStorage.setItem("save-show-type", "title")
		} else {
			localStorage.setItem("save-show-type", "fixed")
			current_element.onclick = function() {
				showTextPanel(getResult(word_table))
			}
			// console.log('绑定事件到');
			// console.log(current_element)
		}
	}
	
	function getResult(word_table) {
		let result = '';
		for (let var1 in word_table) { //显示文本面板
			let word_name = word_table[var1];
			if (result_table[word_name] != null) {
				if (deleted_table[word_name] == null)
					result += getWordListItem(word_name);
			}
	
		}
		// showTextPanel(result)
		return result
	}
	/**
	 * 显示单词列表框
	 * @param {Object} result
	 */
	function showTextPanel(result) {
		if (textPanel == null) {
			textPanel = createTextPanel(result);
			textPanel.appendTo($('body'))
			localStorage.setItem("save-show-type", "fixed")
		} else {
			//如果不为空就显示
			var currentTextPanelStatus = textPanel.css('display');
			textPanel.children().last().html(result)
			if (currentTextPanelStatus == 'none') {
				textPanel.css('display', 'block')
			}
	
		}
		$(".lavaf-button-play").click(function(){
			lavaf_play($(this).attr("data-u"));
		})
		$(".lavaf-button-delete").click(function(){
			delete_word($(this).attr("data-d"))
		})
	}
	/**
	 * 将单词添加到已删除列表
	 * @param {string} word_name 需要删除的单词
	 */
	function delete_word(word_name) {
		if (deleted_table[word_name] == null) {
			let t = new Date();
			deleted_table[word_name] = {
				'date': Date(),
				'm': t.getTime()
			}
	
		} else {
			delete deleted_table[word_name];
		}
		localStorage.setItem('deleted-table', JSON.stringify(deleted_table));
	}
	/**
	 * 获取单词列表详情
	 * @param {string} word_name 获取单词的解释
	 */
	function getWordListItem(word_name) {
	
		var ukph;
		var ph;
		var usph;
		var uk;
		var us;
		if (speech_table[word_name] != undefined) {
			ukph = speech_table[word_name]['uk-ph'];
			usph = speech_table[word_name]['us-ph']
			ph = speech_table[word_name]['ph'];
			uk = speech_table[word_name]['uk']
			us = speech_table[word_name]['us']
		} else {
			console.log(word_name + " 这个可能不是单词,建议去除");
		}
		var setting_1 = setting[setting_item_name[1]]
		var setting_2 = setting[setting_item_name[2]]
		var setting_0 = setting[setting_item_name[0]]
		return '<div><span style=\"color:red;\">' + word_name + "</span>" +
			(setting_1 ? '<span>【' + ph + '】</span>' : '') +
			(setting_1 ? '<span>[' + (ukph == undefined ? 'x' : ukph) + ']</span>' : '') +
			(setting_2 ? '<button class="lavaf-button-play" data-u=\"' + uk + '\"\'>o</button>' : '') +
			(setting_1 ? '<span>[' + (usph == undefined ? 'x' : usph) + ']</span>' : '') +
			(setting_2 ? '<button class="lavaf-button-play" data-u=\"' + us + '\")\'>o</button>' : '') +
			":" + result_table[word_name] +
			(setting_0 ? '<button class="lavaf-button-delete" data-d=\"' + word_name + '\")\'>x</button>' : '') +
			"</div>";
	
	
	}
	/**
	 * 播放音频
	 * @param {Object} src 音频连接
	 */
	function lavaf_play(src) {
		if (src == undefined) {
			addWin("当前单词没有发音,可能不是个单词");
			return;
		}
		audio.attr('src', src)
		audio.get(0).play()
	}
	/**
	 * 因为有的单词可能更快查找到,但是同一段的其他可能还没有
	 * 等到其他单词,也就是最后一个也查找到了,
	 * 便可以给这个段落设置监听事件了
	 * ,查看当前需要索引的单词是否都已经查找到,如果是那就开始显示
	 * @param {Object} word_table 当前的标签含有的单词表
	 * @param {Object} current_element
	 */
	function addTitleForP(word_table, current_element) {
		// console.log(word_table);
		// console.log(word_table.length);
		// console.log(result_table);
		var m = 0;
		for (; m < word_table.length; m++) {
			if (result_table[word_table[m]] == undefined) { //还有没完成的查询
				return;
			}
		}
		if (m == word_table.length) { //所有单词都完成了查询
			localStorage.setItem("result-table", JSON.stringify(result_table)); //保存数据
			localStorage.setItem("speech-table", JSON.stringify(speech_table));
			addTitleOrText(word_table, current_element);
			adw.show("单词全部获得解释,可以开始使用了")
		}
	}
	/**
	 * 为开始获取单词按钮设置事件
	 * @param {Object} button
	 * @param {Object} type
	 */
	function setOnClick(button, type) {
		button.click(function() {
			var p_array = document.getElementsByTagName("p");
			//遍历所有的 p 标签
			for (var i = 0; i < p_array.length; i++) {
				let current_element = p_array[i]; // 当前p 标签对象
				let p_text = current_element.innerText // 字符串 存储当前p 标签的内容
				let p_inner = p_text.split(/[ \s,"'():.]/); //数组 存储当前p 标签的每一个单词
				let word_table = [] //数组 存储需要索引的全部单词,这是需要翻译单词的
				let last_word = null;
				for (var j = 0; j < p_inner.length; j++) { //遍历每一个单词
					let item_query = p_inner[j];
					/*去除非单词结果*/
					let trim = item_query.trim();
					if (trim === "" || trim === "-" || trim === '.' || !/^[0-9a-zA-Z-]{1,}$/.test(item_query)) {
						if (j == p_inner.length - 1) {
							addTitleForP(word_table, current_element);
						}
						continue;
					}
					/*去除非单词部分,保险措施*/
					var last_char = item_query[item_query.length - 1];
					if (last_char === ',' || last_char === '.' || last_char === '\'' || last_char === ')') {
						console.log('去除非单词部分' + item_query);
						item_query = item_query.substring(0, item_query.length - 1)
						console.log('处理之后' + item_query)
					}
					/*去除所有格*/
					if (item_query.lastIndexOf("'s") >= 0) {
						item_query = item_query.substring(0, item_query.length - 2)
					}
					if (item_query[0] == '(') {
						item_query = item_query.substring(1, item_query.length - 1);
					}
					last_word = item_query.toLowerCase();
	
					if ($.inArray(item_query.toLowerCase(), no_translate) == -1) {
						word_table.push(item_query)
						console.log("当前操作:" + item_query);
						if (type == 1) {
							var salt = (new Date).getTime(); //随机数
							var curtime = Math.round(new Date().getTime() / 1000);
							var str1 = appKey + getInput(item_query) + salt + curtime + Secret;
							var sign = sha256(str1);
							let current_index = j;
							/*
							单词表中查找不到,需要联网获取
							*/
							if (result_table[item_query] == null || result_table[item_query] == undefined) {
								$.ajax({
									url: location.protocol+'//openapi.youdao.com/api',
									type: 'post',
									dataType: 'jsonp',
									data: {
										q: item_query,
										appKey: appKey,
										salt: salt,
										'from': from_l,
										to: to,
										curtime: curtime,
										sign: sign,
										signType: "v3"
									},
									success: function(data) {
										console.log("联网获取到" + item_query + "的翻译");
										//完成查询时会把数据放到result-table中
										if (data.basic != null && data.basic.explains != null) {
											let explains = data.basic.explains;
											let r = `[${data.translation}],${JSON.stringify(explains)};\n`;
											result_table[item_query] = r;
											let current_speech = speech_table[item_query];
											if (current_speech == null) {
												speech_table[item_query] = {
													'uk': data.basic['uk-speech'],
													'us': data.basic['us-speech'],
													'us-ph': data.basic['us-phonetic'],
													'uk-ph': data.basic['uk-phonetic'],
													'ph': data.basic['phonetic']
												}
											}
										} else {
											if (data.translation != undefined) {
												let r = "[" + data.translation + "]\n";
												result_table[item_query] = r;
											} else {
												console.log("item_quer:translation == undefined:" + item_query);
												console.log(data);
											}
										}
										addTitleForP(word_table, current_element);
									}
								}); //查找调用完毕
	
							} else {
								/*
								找到单词不必联网获取
								*/
								console.log("已获取" + item_query);
								// console.log(j+" "+p_inner.length);
								// console.log(p_inner);
								if (j == p_inner - 1) {
									addTitleForP(word_table, current_element);
								}
							}
						} //查找调用类型配置完毕,TODO:: 可以添加其他类型的查询结构
					} //其余过于简单不必翻译, 翻译调用结束
					//遍历单词结束
				} //退出遍历单词循环
	
			} //遍历段落结束
		}) //监听函数设置完毕
	}
	/**
	 * 创建按钮
	 */
	function getButton(type) {
		var btn_start = $("<button>", {
			id: 'lavaf-start-get-word-button',
			value: '开始',
			type: 'button'
		}).text('开始');
		setOnClick(btn_start, type);
		return btn_start;
	}

})();