您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
百度翻译XAnki快速制卡助手
// ==UserScript== // @name 百度翻译Anki制卡助手 // @namespace http://juexe.cn // @version 0.4 // @description 百度翻译XAnki快速制卡助手 // @author Juexe // @match https://fanyi.baidu.com/* // @grant none // @note 2020.09.07-v0.4 支持静默添加模式;Toast操作提示。 // @note 2020.05.10-v0.3 可自定义tag关键词;优化tag点击监听事件;增加记忆技巧。 // @note 2020.04.24-v0.2 添加简明释义 // @note 2020.04.13-v0.1 初始版本 // ==/UserScript== (function() { 'use strict'; let config = { 'apiAddress': 'http://localhost:8765', 'deckName': '1.1 英语生词', 'modelName': '英语生词', 'frontName': '例句', 'backName': '翻译', 'backNoteName': '背面备注', 'apiKey': 'juexe', 'autoClose': false, 'keywordStyleL': '<u>', 'keywordStyleR': '</u>', 'silentMode': true //静默添加卡片,否则弹出添加对话框 }; let anki_status = false; let word_brief = ''; console.log('百度翻译Anki助手正在运行……'); initAnki(); /** * 尝试 Anki 连接 * @desc 成功连接到anki才进行后续注入动作 */ function initAnki() { sendToAnki({ "action": "deckNames", "version": 6 }).then(function(data) { anki_status = true; console.log('连接 AnkiConnect 成功'); injectCss(); waitReady(2000); }).catch(function(err) { anki_status = false; console.log('连接 AnkiConnect 失败', err); }) } /** * 等待关键位置加载完成 * @param interval 检查频率(毫秒) */ function waitReady(interval = 1000) { if (document.querySelector('.anki-send') != null) { console.log('重复操作 waitReady()'); return; } let hd = setInterval(function() { let flag = document.querySelector('.double-sample ol li .sample-source'); // console.log('find result', flag); if (flag != null) { // console.log('found!'); clearInterval(hd); inject(); injectTagButton(); addTagItemClicker(); } }, interval); } /** * 在关键位置注入插件按钮 */ function inject() { if (document.querySelector('.anki-send') != null) { console.log('重复操作 inject()'); return; } console.log('百度翻译Anki助手已载入。'); dealBriefInfo(); let samples = document.querySelectorAll('.double-sample ol li'); samples.forEach(function(sample) { let inDom = document.createElement('a'); inDom.className = 'anki-send'; inDom.setAttribute('href', 'javascript:void(0)'); inDom.innerText = '🚀'; inDom.onclick = function() { getSentence(this); }; sample.querySelector('div:last-child').prepend(inDom); }); } /** * 注入标签按钮 */ function injectTagButton() { if (document.querySelector('.tag-add-btn') != null) { console.log('重复操作 injectTagButton()'); return; } let inDom = document.createElement('button'); inDom.className = 'tag-add-btn'; inDom.setAttribute('href', 'javascript:void(0)'); inDom.innerText = '🎨'; inDom.onclick = function(ev) { let key = prompt('输入关键词'); if (key) { console.log('自定义关键词', key); let tagDom = document.createElement('span'); tagDom.className = 'sample-tagitem'; tagDom.innerText = key; document.querySelector('.sample-tagnav').append(tagDom); } ev.stopPropagation(); return false; }; document.querySelector('.section-header').prepend(inDom); } /** * 获取单词简明释义 */ function dealBriefInfo() { let infoLines = document.querySelectorAll('.dictionary-comment p'); word_brief = ''; infoLines.forEach(function(p) { word_brief += p.innerText.replace('\n', '') + '<br>'; }); let momory_skill = document.querySelector('.momory-skill'); if (momory_skill) { word_brief += '[记忆] ' + momory_skill.innerText; } } /* * 按钮动作:获取句子信息 */ function getSentence(dom) { // console.log('dom', dom); let sentenceDom = dom.parentNode.querySelector('a~.sample-source'); let sentence = sentenceDom.innerText; let keywords = sentenceDom.querySelectorAll('.high-light'); let boldText = ''; keywords.forEach(function(keyword) { boldText += keyword.innerText; }); boldText = boldText.trim(); // console.log('highlight', boldText); if (sentence.indexOf(boldText) > -1) sentence = sentence.replace(boldText, config.keywordStyleL + boldText + config.keywordStyleR); sentence = sentence.trim(); if (sentence.charAt(sentence.length - 1) !== '.') sentence += '.'; // console.log('sentence', sentence); let trans = dom.parentNode.querySelector('a~.sample-target').innerText; // console.log('trans', trans); let resource = dom.parentNode.querySelector('a~.sample-resource').innerText; // console.log('resource', resource); if(config.silentMode){ addNoteSilent(sentence, trans, resource); }else{ addCard(sentence, trans, resource); } } /** * 添加卡片 * @param front * @param backend * @param chapter */ function addCard(front, backend, chapter = 'Anki 助手') { sendToAnki({ "action": "guiAddCards", "version": 6, "params": { "note": { "deckName": config['deckName'], "modelName": config['modelName'], "fields": { [config.frontName]: front, [config.backName]: backend, [config.backNoteName]: word_brief }, "options": { "closeAfterAdding": config.autoClose }, "tags": [] } } }).then(function(data) {}).catch(function(err) { anki_status = false; alert('连接 AnkiConnect 失败'); console.log(err); }) } /** * 添加卡片 * @param front * @param backend * @param chapter */ function addNoteSilent(front, backend, chapter = 'Anki 助手') { sendToAnki({ "action": "addNote", "version": 6, "params": { "note": { "deckName": config['deckName'], "modelName": config['modelName'], "fields": { [config.frontName]: front, [config.backName]: backend, [config.backNoteName]: word_brief }, "options": { "allowDuplicate": false, "duplicateScope": "deck" }, "tags": [] } } }).then(function(data) { Toast("添加成功!", 1000); }).catch(function(err) { anki_status = false; alert('连接 AnkiConnect 失败'); console.log(err); }); } /** * 封装 fetch 实现 post 请求 * @param req * @returns {Promise<fetch>} */ function sendToAnki(req) { req['key'] = config.apiKey; return new Promise((resolve, reject) => fetch(config.apiAddress, { method: 'POST', mode: 'cors', body: JSON.stringify(req), }) .then(res => res.json()) .then(data => { let erro = data['error']; if (erro != null) { alert('Anki助手请求失败:' + erro); console.log(erro); } else { resolve(data); } }) .catch(err => reject(err)) ) } // 监听 url 发生变化 let refreshTimeout; window.addEventListener('hashchange', function() { if (anki_status === true) { // console.log('url change'); clearTimeout(refreshTimeout); refreshTimeout = setTimeout(waitReady, 3000); } }, false); // 释义标签点击触发 function addTagItemClicker() { let tagnav = document.querySelector(".sample-tagnav"); // 补充缺失的tagnav if (!tagnav) { tagnav = document.createElement('div'); tagnav.className = 'sample-tagnav'; document.querySelector('.sample-wrap').prepend(tagnav); let tag1 = document.createElement('span'); tag1.className = 'sample-tagitem sample-all sample-current'; tag1.innerText = '全部'; tagnav.prepend(tag1); console.log('自动补全 tagnav'); } tagnav.onclick = function(ev) { setTimeout(inject, 1000); }; } // css function injectCss() { var dom = document.createElement('style'), dom_body = document.getElementsByTagName("body")[0]; dom.innerHTML += '.anki-send{display:block; position:absolute; left:4px;}'; dom_body.appendChild(dom); } // 简单Toast function Toast(msg,duration=3000){ var m = document.createElement('div'); m.innerHTML = msg; m.style.cssText="max-width:60%;min-width: 150px;padding:0 14px;height: 40px;color: rgb(255, 255, 255);line-height: 40px;text-align: center;border-radius: 4px;position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);z-index: 999999;background: rgba(0, 0, 0,.7);font-size: 16px;"; document.body.appendChild(m); setTimeout(function() { var d = 0.5; m.style.webkitTransition = '-webkit-transform ' + d + 's ease-in, opacity ' + d + 's ease-in'; m.style.opacity = '0'; setTimeout(function() { document.body.removeChild(m) }, d * 1000); }, duration); } })();