Greasy Fork

英语阅读助手

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

当前为 2019-06-20 提交的版本,查看 最新版本

// ==UserScript==
// @name         英语阅读助手
// @namespace    http://tampermonkey.net/
// @version      0.6
// @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
// @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==
var appKey = '';
var Secret = ''; //注意:暴露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}')

/**
 * 获取上一次选择的类型
 */
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': '10px'
	})
	var move_button = $("<button>", {
		'id': "lavaf-move-button"
	}).text("◉").css({
		'margin-right': '10px'
	}).mousedown(function(e) {
		offestX = e.clientX - div.offset().left
		offestY = e.clientY - div.offset().top;
		movable = true;
	}).appendTo(div)
	appendMouseMoveEvents(div)
	/*
	显示保存的单词数量
	*/
	$("<div>", {
		'id': 'lavaf-show-saved-word-num'
	}).text(`保存的单词:${result_count}`).appendTo(div);
	//获取上次的选择
	var show_type_last_selection = getLastSelectionType();
	var selection_type_show = $("<select>", {
		id: "lavaf-selection-type-show"
	}).appendTo(div);
	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
			}
		}
	}
	div.append(mutil_container);
	//显示result-table 面板
	var show_result_table_panel_button = $("<button>", {
		id: 'lavaf-show-result-table-panel-button'
	}).text("显示全部单词").appendTo(div);
	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);
	});
	div.append('<br>')
	var show_deleted_word = $('<button>', {
		id: 'lavaf-show-deleted-word-button'
	}).text("显示已经删除的单词").appendTo(div);
	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);
	});
	div.append('<br>')
	var start = getButton(1);
	div.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'
	});
	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')
		}

	}

}
/**
 * 将单词添加到已删除列表
 * @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 onclick=\'play(\"' + uk + '\")\'>o</button>' : '') +
		(setting_1 ? '<span>[' + (usph == undefined ? 'x' : usph) + ']</span>' : '') +
		(setting_2 ? '<button onclick=\'play(\"' + us + '\")\'>o</button>' : '') +
		":" + result_table[word_name] +
		(setting_0 ? '<button onclick=\'delete_word(\"' + word_name + '\")\'>x</button>' : '') +
		"</div>";


}
/**
 * 播放音频
 * @param {Object} src 音频连接
 */
function 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: 'http://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;
}