// ==UserScript== // @namespace ATGT // @name bing-dict // @name:zh-CN 必应词典 // @description Select any word in any web to show it's definition from Bing Dict. // @description:zh-CN 划词翻译,使用必应词典 // @version 1.4 // @author StrongOp // @supportURL https://github.com/strongop/user-scripts/issues // @match http://*/* // @match https://*/* // -match https://github.com/* // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js // @grant GM.xmlHttpRequest // @grant GM_xmlhttpRequest // @grant GM.setValue // @grant GM.getValue // @grant GM_setValue // @grant GM_getValue // @connect www.bing.com // @icon https://www.bing.com/favicon.ico // @run-at document-end // @downloadURL none // ==/UserScript== /* eslint: */ /* global GM GM_xmlhttpRequest GM_setValue GM_getValue */ /* Change Log: v1.4: 31 Jan 2019, Refactor with class v1.3.8: 30 Jan 2019, Need user click on checkbox to enable translate. v1.3.5: 21 Dec 2018, Fix GM_xmlhttpRequest not def in Greasemonkey, GM not define in Tampermonkey v1.3.4: 28 Nov 2018, Fix GM.xmlHttpRequest not def in chrome. v1.3.3: 28 Jan 2018, Fix dict provider overlap with result. Add test cases. v1.3.2: 27 Jan 2018, Add dict provider name: Bing Dict. v1.3.1: 19 Jan 2018, Escape suggested words. v1.3: 19 Jan 2018, refactor & parse search suggestion. v1.2: 14 Jan 2018, Escape search word and result. v1.1: 13 Jan 2018, Reset style for result div. v1.0: 12 Jan 2018, Initial version. */ /* if (typeof GM_xmlhttpRequest !== 'undefined') var GM = { setValue: GM_setValue, getValue: GM_getValue }; */ console.log('!!!!!!!!!!!!!!!!!!!!!bing-dict!!!!!!!!!!!!!!!!!!!!!!!!'); const DICT_RESULT_CSS = ` div#ATGT-bing-dict-result-wrapper-reset { all: initial; * { all: initial; } } div#ATGT-bing-dict-result-wrapper { display: block; position: fixed; left: 2px; bottom: 2px; max-width: 32%; z-index: 2100000000; padding: 0; margin: 0; color: black; background-color: rgba(255,255,255,0.9); font-size: small; font-family: sans-serif; white-space: normal; } div#ATGT-bing-dict-result-wrapper .dict-provider { font-size: xx-small; float: right; margin-left: 0.5rem; /* position: absolute; top: 2px; right: 2px; */ } div#ATGT-bing-dict-result-wrapper .dict-provider input { display: inline; vertical-align: bottom; transform: scale(0.7); margin: 0 0; border: solid 1px; -webkit-appearance: checkbox; } div#ATGT-bing-dict-result-wrapper .search_suggest_area { font-size: xx-small; } div#ATGT-bing-dict-result-wrapper .error { color: red; } div#ATGT-bing-dict-result-wrapper .headword { font-weight: bold; font-size: medium; } div#ATGT-bing-dict-result-wrapper .div_title { font-weight: bold; } div#ATGT-bing-dict-result-wrapper .suggest_word { margin-right: 5px; } div#ATGT-bing-dict-result-wrapper .mach_trans { font-style: italic; font-size: x-small; } div#ATGT-bing-dict-result-wrapper a:link { color: #37a; text-decoration: none; } div#ATGT-bing-dict-result-wrapper a:hover { color: white; background-color: #37a; } div#ATGT-bing-dict-result-wrapper .pronuce { color: gray; } div#ATGT-bing-dict-result-wrapper .pronuce a:hover { color: white; background-color: white; } div#ATGT-bing-dict-result-wrapper .mach_trans_result { color: gray; } div#ATGT-bing-dict-result-wrapper ul { list-style-type: none; padding: 1px; margin: 0px; } div#ATGT-bing-dict-result-wrapper ul li{ margin-top: 1px; } div#ATGT-bing-dict-result-wrapper ul li span { float:left; color: white; background-color: gray; text-align: center; padding: 0 2px; margin-right: 3px; } div#ATGT-bing-dict-result-wrapper a img.audioPlayer:hover { opacity: 0.8; } div#ATGT-bing-dict-result-wrapper img.audioPlayer { width: 1rem; height: 1rem; } `; class DictResultView { //dictResultDiv; constructor(prefs) { this.prefs = prefs; this.addStyleSheet(); this.createDictResultDiv(); this.transEnableShown = false; } addStyleSheet() { let style = document.createElement('STYLE'); style.type = 'text/css'; style.appendChild(document.createTextNode(DICT_RESULT_CSS)); document.head.appendChild(style); } createDictResultDiv() { this.addStyleSheet(); let div_wrapper_reset = document.createElement('DIV'); div_wrapper_reset.id = 'ATGT-bing-dict-result-wrapper-reset'; let div = document.createElement('DIV'); div.id = 'ATGT-bing-dict-result-wrapper'; div_wrapper_reset.appendChild(div); document.body.appendChild(div_wrapper_reset); this.dictResultDiv = div; } setProvider(provider) { this.dictProvider = provider; } setResult(defs) { this.dictResultDiv.innerHTML = this.dictProvider + defs; this.dictResultDiv.style.display = 'block'; this.showEnableTransChoice(); } hideResult() { this.dictResultDiv.style.display = 'none'; //this.dictResultDiv.innerHTML = ''; this.transEnableShown = false; } mouseEventInView(event) { // if Mouse is inside result element let divRect = this.dictResultDiv.getBoundingClientRect(); let isInView = (event.clientX >= divRect.left && event.clientX <= divRect.right && event.clientY >= divRect.top && event.clientY <= divRect.bottom); if (isInView) console.log('mouse in result area'); return isInView; } showEnableTransChoice() { this.transEnableShown = true; let prefs = this.prefs; let view = this; function enableTransChoiceHandler(event) { console.log('enableTransChoiceHandler called, translate enable ', event.target.checked); prefs.updateTransEnabledList(location.host, !!event.target.checked); prefs.transEnabledOnPage = event.target.checked; if (prefs.transEnabledOnPage && CurrentSelWord.length > 0) setTimeout(bingDict.search.bind(bingDict), 0, CurrentSelWord); else view.hideResult(); } let enableCheckbox = document.querySelector('div#ATGT-bing-dict-result-wrapper input#enableTrans'); enableCheckbox.checked = this.prefs.transEnabledOnPage; enableCheckbox.onclick = enableTransChoiceHandler; } } var dictCache = {}; var entityMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/', '`': '`', '=': '=' }; function escapeHtml(string) { return String(string).replace(/[&<>"'`=/]/g, function (s) { return entityMap[s]; }); } class DictProvider { //resultView; constructor(resultView) { this.resultView = resultView; let dictProvider = '
No Dict Provider
'; this.resultView.setProvider(dictProvider); } search(word) { console.log(`base DictProvider::search ${word}`); return 'not implement yet.'; } } class BingDictProvider extends DictProvider { constructor(resultView) { super(resultView); let dictProvider = '
Bing Dict
'; this.resultView.setProvider(dictProvider); this.baseURL = 'https://www.bing.com/'; } search(word) { let self = this; function playAudio(audioLink) { let audio = new Audio(audioLink); audio.play(); } function parseVoiceLink(elem, id) { let voiceLink; let voiceIcon; try { let linkElem = elem.childNodes[0]; console.log("PronuceLink", linkElem); let handler = linkElem.attributes['onclick'].value; let matches = handler.match(/'(https?:\/\/[^ ']*)'\s*,\s*'([^']*)'/m); console.log("matches ", matches); voiceLink = matches[1]; voiceIcon = matches[2]; } catch (e) { console.log('parseVoiceLink', e); } let speakerIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAZCAYAAAAv3j5gAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4wICACYBOBTr1QAAAvZJREFUSMed1k2IVlUYB/Dfe8dRabqS9mFiEn3ZqQxC2lQU5a6FEBE4tAhaJUqrK0HRxkWLoBNupJA2UlCuigipdtXCwCyirEsLw4omM8aak5jizNui5x1ub+/YzDxwuefc8/F//s/n7VlA+vvv0Nt9HJSctuENNDhUN+3cqDO/70vGZv8Z102r5DQ/rkYdKDnp7T6u5DRectqJ97EBD2BsIeUuXpyDFdhaclpbN+38WjUKpLNhF17BeMxvGHVmIFft+Q4ewqd4tuQ0PmBWdQE6lFeXnPZi39Bd16I3rNiQXBmK7cS2/5huwKLktB3v4fkRSm8cwagX59bG/F28hho7Sk5rYEXJqcKLuB03hS/WLGCd1SPM3C85PYVnSk731k17quR0EE/gMbyAmQrPYQ8exq2XAIHLB4wGviw5TeBJ3IhXY98JfBSstpScehW2d02wBKlKTusxi/04i0dKTtfhZ3wZ++7DWIWblwiwKgLgLnyFl/AWTsf6ZPj7+5hvGQCtWyLQxnhfj6tD4zU42mEAv+H8ICUqS5e50PjzMNEtWN8x1eZ4z+BPXIPecoDmAXEGl0XenIrvg2A6H8+ES2X5ImQlNmE6LuwNBVW/U32Wx6jk1MPdof3X+DXMB390FBnHOfSXBVQ3bR8f420crpt2OoDh23hPhFmn0V+BC4G+WPkrwKbwaDDcgHti/YNOzZvAMcxV+GmJhH4ZUUx3RahP49BQGrQDoKPL8FG3lcBhTOHpumlnSk7ronaKljFbYS9OLhJjFv1OpR/47Aju77DZgAdjfKxu2rkqqN0WBXNrZ/MoudAJ2+F2faJu2tmS0yrsiET9MJiqIoLO4WzdtF/UTTsZLXtqBNDJLlC3j3U/4fEYH6yb9vR8wtZN22186qb9BHfi+NAlZ4aBRv2j4GUcwDsLdOF/t+eS06aS05GSUz+eN0tOKxcZMFX3rmqBhNQ/sFndtD9iEq/H0jcREJeMyLhjrhudvUVqd0X8aHyGH0b45X/lb9cUC+N7uVGjAAAAAElFTkSuQmCC'; let voiceHTML = ` ` return voiceHTML; } function parsePronuce(elem) { let prUS = elem.querySelector('.hd_prUS'); let prUK = elem.querySelector('.hd_pr'); let pronText = ''; if (prUS) pronText = escapeHtml(prUS.innerText) + parseVoiceLink(prUS.nextElementSibling, 'voiceUS') + ' ' + escapeHtml(prUK.innerText) + parseVoiceLink(prUK.nextElementSibling, 'voiceUK'); else pronText = escapeHtml(elem.innerText); return pronText; } function parseDefinition(page, url) { //console.log('parseDefinition'); let qdef = page.querySelector('.qdef'); //console.log('qdef ', qdef); let hd_area = qdef.childNodes[0]; let headword = ''; let pronuce = ''; try { headword = escapeHtml(hd_area.querySelector('#headword').innerText); pronuce = parsePronuce(hd_area.querySelector('.hd_tf_lh')); } catch (e) { } headword = `
${headword ? headword : word}
`; pronuce = `
${pronuce}
`; let defs = ''; try { let def_area = qdef.childNodes[1]; let def_list = def_area.querySelectorAll('li'); defs = ''; } catch (e) { defs = ''; } return headword + pronuce + defs; } function parseMachTrans(page, url) { //console.log('parseMachTrans'); try { let trans_area = page.querySelector('.lf_area'); let smt_hw_elem = trans_area.querySelector('.smt_hw'); let smt_hw = escapeHtml(smt_hw_elem.innerText); let headword = escapeHtml(smt_hw_elem.nextElementSibling.innerText); let trans_result = escapeHtml(smt_hw_elem.nextElementSibling.nextElementSibling.innerText); smt_hw = `
${smt_hw}
`; headword = `
${headword}
`; trans_result = `
${trans_result}
`; return smt_hw + headword + trans_result; } catch (e) { console.error('parseMachTrans error'); return ''; } } function parseBingDymArea(dym) { let suggest = escapeHtml(dym.querySelector('.df_wb_a').innerText); suggest = `
${suggest}
`; let defs = ''; return suggest + defs; } function parseSearchSuggest(page, url) { //console.log('parseSearchSuggest'); let trans_area = page.querySelector('.lf_area'); let headword = escapeHtml(trans_area.querySelector('.dym_p').innerText); let suggest = escapeHtml(trans_area.querySelector('.p2-2').innerText); headword = `
${headword}
`; suggest = `
${suggest}
`; let defs = ''; for (let dym of trans_area.querySelectorAll('.dym_area')) { defs += parseBingDymArea(dym); } return `
${headword}${suggest}${defs}
`; } function parseDictResultDom(page, url) { //console.log('page ', page); let qdef = page.querySelector('.qdef'); let smt_hw = page.querySelector('.smt_hw'); let search_suggest = page.querySelector('.dym_area') && page.querySelector('.df_wb_c'); //let no_result = page.querySelector('.no_results'); if (qdef) return parseDefinition(page, url); else if (smt_hw) return parseMachTrans(page, url); else if (search_suggest) return parseSearchSuggest(page, url); else return ''; } const FAILURE_MSG = `No result.
Try Microsoft Translator.`; function parseDictResult(word, response) { //console.log('search dict ok', response); let defs = ''; let url = response.finalUrl; try { let doc = (new DOMParser()).parseFromString(response.responseText, 'text/html'); defs = parseDictResultDom(doc, url); if (!defs) defs = FAILURE_MSG; dictCache[word] = defs; } catch (e) { console.log('parseDictResult failed:\n ', e, e.stack); defs = `Error parsing result of ` + `${escapeHtml(word)},
${FAILURE_MSG}`; } self.resultView.setResult(defs); } function searchDictFail(word, response) { //console.log('search dict fail ', response); let url = response.finalUrl; let status = (response.status ? response.status : '') + ' ' + (response.statusText ? response.statusText : ''); self.resultView.setResult(`Errorsearching ${escapeHtml(word)},` + `${status}
${FAILURE_MSG}`); } function searchBingDict(word) { if (word in dictCache && dictCache[word]) { console.log(`cache hit '${word}'`); self.resultView.setResult(dictCache[word]); return; } else { console.log('cache miss'); } let url = 'http://www.bing.com/dict/search?q=' + encodeURIComponent(word); console.log(url); self.resultView.setResult(`Searching ${escapeHtml(word)}`); (typeof GM_xmlhttpRequest != 'undefined' && GM_xmlhttpRequest || GM.xmlHttpRequest)({ url: url, method: 'GET', onload: (response) => parseDictResult(word, response), onerror: (response) => searchDictFail(word, response), }); } searchBingDict(word); } } var CurrentSelWord = ''; class DictPrefs { constructor() { this.transEnabledOnPage = false; } updateTransEnabledList(key, value) { GM.getValue('transEnabledList', {}).then((transEnabledList) => { console.log(`update ${key} => ${value} into transEnabledList`, transEnabledList); transEnabledList[key] = value; GM.setValue('transEnabledList', transEnabledList); //console.log(`updated ${key} => ${value} into transEnabledList`, transEnabledList); }); } checkEnableTrans() { GM.getValue('transEnabledList', {}).then((transEnabledList) => { console.log('transEnabledList', transEnabledList); if (!transEnabledList) return; if (typeof transEnabledList === 'string') GM.setValue('transEnabledList', {}); if (transEnabledList[location.host] === true) this.transEnabledOnPage = true; }); } } var dictPrefs = new DictPrefs(); dictPrefs.checkEnableTrans(); var dictResultView = new DictResultView(dictPrefs); var bingDict = new BingDictProvider(dictResultView); document.addEventListener('mouseup', function (event) { CurrentSelWord = window.getSelection().toString().replace(/^\s*|\s*$/g, ''); console.log(`selected: '${CurrentSelWord}', length ${CurrentSelWord.length}`); if (dictResultView.mouseEventInView(event)) return; if (CurrentSelWord.length == 0) { dictResultView.hideResult(); return; } if (!dictPrefs.transEnabledOnPage) { if (!dictResultView.transEnableShown) dictResultView.setResult(''); console.log('translate not enabled.'); return; } bingDict.search(CurrentSelWord); }); function dictTest() { let testWords = [ 'tunnel', //'hello', // word definition //'你好', //'hello, this is world', //machine translation //'ndalo', // ambigous //'DNS queries', // example sentence only //'