diff --git a/src/common.js b/src/common.js index 3f6387f..d91b314 100644 --- a/src/common.js +++ b/src/common.js @@ -8,27 +8,83 @@ import { MSG_TRANS_TOGGLE_STYLE, MSG_TRANS_GETRULE, MSG_TRANS_PUTRULE, + MSG_TRANSLATE_SELECTED, + MSG_OPEN_TRANBOX, APP_LCNAME, DEFAULT_TRANBOX_SETTING, } from "./config"; -import { getRulesWithDefault, getFabWithDefault } from "./libs/storage"; +import { getFabWithDefault, getSettingWithDefault } from "./libs/storage"; import { Translator } from "./libs/translator"; -import { sendIframeMsg, sendParentMsg } from "./libs/iframe"; -import { matchRule } from "./libs/rules"; +import { isIframe, sendIframeMsg, sendParentMsg } from "./libs/iframe"; import Slection from "./views/Selection"; import { touchTapListener } from "./libs/touch"; -import { debounce } from "./libs/utils"; +import { debounce, genEventName } from "./libs/utils"; +import { handlePing, injectScript } from "./libs/gm"; +import { browser } from "./libs/browser"; +import { runWebfix } from "./libs/webfix"; +import { matchRule } from "./libs/rules"; +import { trySyncAllSubRules } from "./libs/subRules"; +import { isInBlacklist } from "./libs/blacklist"; -export async function runTranslator(setting) { - const href = document.location.href; - const rules = await getRulesWithDefault(); - const rule = await matchRule(rules, href, setting); - const translator = new Translator(rule, setting); - - return { translator, rule }; +/** + * 油猴脚本设置页面 + */ +function runSettingPage() { + if (GM?.info?.script?.grant?.includes("unsafeWindow")) { + unsafeWindow.GM = GM; + unsafeWindow.APP_INFO = { + name: process.env.REACT_APP_NAME, + version: process.env.REACT_APP_VERSION, + }; + } else { + const ping = genEventName(); + window.addEventListener(ping, handlePing); + // window.eval(`(${injectScript})("${ping}")`); // eslint-disable-line + const script = document.createElement("script"); + script.textContent = `(${injectScript})("${ping}")`; + document.head.append(script); + } } -export function runIframe(setting) { +/** + * 插件监听后端事件 + * @param {*} translator + */ +function runtimeListener(translator) { + browser?.runtime.onMessage.addListener(async ({ action, args }) => { + switch (action) { + case MSG_TRANS_TOGGLE: + translator.toggle(); + sendIframeMsg(MSG_TRANS_TOGGLE); + break; + case MSG_TRANS_TOGGLE_STYLE: + translator.toggleStyle(); + sendIframeMsg(MSG_TRANS_TOGGLE_STYLE); + break; + case MSG_TRANS_GETRULE: + break; + case MSG_TRANS_PUTRULE: + translator.updateRule(args); + sendIframeMsg(MSG_TRANS_PUTRULE, args); + break; + case MSG_TRANSLATE_SELECTED: + window.dispatchEvent(new CustomEvent(MSG_TRANSLATE_SELECTED)); + break; + case MSG_OPEN_TRANBOX: + window.dispatchEvent(new CustomEvent(MSG_OPEN_TRANBOX)); + break; + default: + return { error: `message action is unavailable: ${action}` }; + } + return { data: translator.rule }; + }); +} + +/** + * iframe 页面执行 + * @param {*} setting + */ +function runIframe(setting) { let translator; window.addEventListener("message", (e) => { const { action, args } = e.data || {}; @@ -52,7 +108,12 @@ export function runIframe(setting) { sendParentMsg(MSG_TRANS_GETRULE); } -export async function showFab(translator) { +/** + * 悬浮按钮 + * @param {*} translator + * @returns + */ +async function showFab(translator) { const fab = await getFabWithDefault(); if (fab.isHide) { return; @@ -80,10 +141,12 @@ export async function showFab(translator) { ); } -export function showTransbox({ - tranboxSetting = DEFAULT_TRANBOX_SETTING, - transApis, -}) { +/** + * 划词翻译 + * @param {*} param0 + * @returns + */ +function showTransbox({ tranboxSetting = DEFAULT_TRANBOX_SETTING, transApis }) { if (!tranboxSetting?.transOpen) { return; } @@ -110,7 +173,11 @@ export function showTransbox({ ); } -export function windowListener(rule) { +/** + * 监听来自iframe页面消息 + * @param {*} rule + */ +function windowListener(rule) { window.addEventListener("message", (e) => { const { action } = e.data || {}; switch (action) { @@ -122,14 +189,23 @@ export function windowListener(rule) { }); } -export function showErr(message) { +/** + * 显示错误信息到页面顶部 + * @param {*} message + */ +function showErr(message) { const $err = document.createElement("div"); $err.innerText = `KISS-Translator: ${message}`; $err.style.cssText = "background:red; color:#fff;"; document.body.prepend($err); } -export function touchOperation(translator) { +/** + * 监听触屏操作 + * @param {*} translator + * @returns + */ +function touchOperation(translator) { const { touchTranslate = 2 } = translator.setting; if (touchTranslate === 0) { return; @@ -141,3 +217,63 @@ export function touchOperation(translator) { }); touchTapListener(handleTap, touchTranslate); } + +/** + * 入口函数 + */ +export async function run(isUserscript = false) { + try { + const href = document.location.href; + + // 设置页面 + if ( + isUserscript && + (href.includes(process.env.REACT_APP_OPTIONSPAGE_DEV) || + href.includes(process.env.REACT_APP_OPTIONSPAGE) || + href.includes(process.env.REACT_APP_OPTIONSPAGE2)) + ) { + runSettingPage(); + return; + } + + // 读取设置信息 + const setting = await getSettingWithDefault(); + + // 黑名单 + if (isInBlacklist(href, setting)) { + return; + } + + // 适配iframe + if (isIframe) { + runIframe(setting); + return; + } + + // 不规范网页修复 + await runWebfix(setting); + + // 翻译网页 + const rule = await matchRule(href, setting); + const translator = new Translator(rule, setting); + + // 监听消息 + windowListener(rule); + !isUserscript && runtimeListener(translator); + + // 划词翻译 + showTransbox(setting); + + // 浮球按钮 + await showFab(translator); + + // 触屏操作 + touchOperation(translator); + + // 同步订阅规则 + isUserscript && (await trySyncAllSubRules(setting)); + } catch (err) { + console.error("[KISS-Translator]", err); + showErr(err.message); + } +} diff --git a/src/config/i18n.js b/src/config/i18n.js index 5c48c38..a67b2cb 100644 --- a/src/config/i18n.js +++ b/src/config/i18n.js @@ -352,8 +352,8 @@ export const I18N = { en: `URL pattern`, }, pattern_helper: { - zh: `1、支持星号(*)通配符。2、多个URL支持英文逗号“,”分隔。`, - en: `1. The asterisk (*) wildcard is supported. 2. Multiple URLs can be separated by English commas ",".`, + zh: `1、支持星号(*)通配符。2、多个URL用英文逗号“,”分隔。`, + en: `1. The asterisk (*) wildcard is supported. 2. Multiple URLs separated by English commas ",".`, }, selector_helper: { zh: `1、遵循CSS选择器语法。2、留空表示采用全局设置。3、多个CSS选择器之间用“;”隔开。4、“shadow root”选择器和内部选择器用“>>>”隔开。`, @@ -683,4 +683,8 @@ export const I18N = { zh: `四指轻触`, en: `Four finger tap`, }, + translate_blacklist: { + zh: `禁用翻译名单`, + en: `Translate Blacklist`, + }, }; diff --git a/src/config/index.js b/src/config/index.js index f183a8d..65c5eb4 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -412,6 +412,13 @@ export const DEFAULT_SHORTCUTS = { export const TRANS_MIN_LENGTH = 5; // 最短翻译长度 export const TRANS_MAX_LENGTH = 5000; // 最长翻译长度 export const TRANS_NEWLINE_LENGTH = 20; // 换行字符数 +export const DEFAULT_BLACKLIST = [ + "https://fishjar.github.io/kiss-translator/options.html", + "https://translate.google.com", + "https://www.deepl.com/translator", + "oapi.dingtalk.com", + "login.dingtalk.com", +]; // 禁用翻译名单 export const DEFAULT_SETTING = { darkMode: false, // 深色模式 @@ -433,6 +440,7 @@ export const DEFAULT_SETTING = { inputRule: DEFAULT_INPUT_RULE, // 输入框设置 tranboxSetting: DEFAULT_TRANBOX_SETTING, // 划词翻译设置 touchTranslate: 2, // 触屏翻译 + blacklist: DEFAULT_BLACKLIST.join(",\n"), // 禁用翻译名单 }; export const DEFAULT_RULES = [GLOBLA_RULE]; diff --git a/src/content.js b/src/content.js index 9b7b0cf..3dc1433 100644 --- a/src/content.js +++ b/src/content.js @@ -1,89 +1,3 @@ -import { browser } from "./libs/browser"; -import { - MSG_TRANS_TOGGLE, - MSG_TRANS_TOGGLE_STYLE, - MSG_TRANSLATE_SELECTED, - MSG_OPEN_TRANBOX, - MSG_TRANS_GETRULE, - MSG_TRANS_PUTRULE, -} from "./config"; -import { getSettingWithDefault } from "./libs/storage"; -import { isIframe, sendIframeMsg } from "./libs/iframe"; -import { runWebfix } from "./libs/webfix"; -import { - runIframe, - runTranslator, - showFab, - showTransbox, - windowListener, - showErr, - touchOperation, -} from "./common"; +import { run } from "./common"; -function runtimeListener(translator) { - browser?.runtime.onMessage.addListener(async ({ action, args }) => { - switch (action) { - case MSG_TRANS_TOGGLE: - translator.toggle(); - sendIframeMsg(MSG_TRANS_TOGGLE); - break; - case MSG_TRANS_TOGGLE_STYLE: - translator.toggleStyle(); - sendIframeMsg(MSG_TRANS_TOGGLE_STYLE); - break; - case MSG_TRANS_GETRULE: - break; - case MSG_TRANS_PUTRULE: - translator.updateRule(args); - sendIframeMsg(MSG_TRANS_PUTRULE, args); - break; - case MSG_TRANSLATE_SELECTED: - window.dispatchEvent(new CustomEvent(MSG_TRANSLATE_SELECTED)); - break; - case MSG_OPEN_TRANBOX: - window.dispatchEvent(new CustomEvent(MSG_OPEN_TRANBOX)); - break; - default: - return { error: `message action is unavailable: ${action}` }; - } - return { data: translator.rule }; - }); -} - -/** - * 入口函数 - */ -(async () => { - try { - // 读取设置信息 - const setting = await getSettingWithDefault(); - - // 适配iframe - if (isIframe) { - runIframe(setting); - return; - } - - // 不规范网页修复 - await runWebfix(setting); - - // 翻译网页 - const { translator, rule } = await runTranslator(setting); - - // 监听消息 - windowListener(rule); - runtimeListener(translator); - - // 划词翻译 - showTransbox(setting); - - // 浮球按钮 - await showFab(translator); - - // 触屏操作 - touchOperation(translator); - } catch (err) { - console.error("[KISS-Translator]", err); - showErr(err.message); - } -})(); +run(); diff --git a/src/libs/blacklist.js b/src/libs/blacklist.js new file mode 100644 index 0000000..0ff4bfd --- /dev/null +++ b/src/libs/blacklist.js @@ -0,0 +1,13 @@ +import { isMatch } from "./utils"; +import { DEFAULT_BLACKLIST } from "../config"; + +/** + * 检查是否在黑名单中 + * @param {*} href + * @param {*} param1 + * @returns + */ +export const isInBlacklist = ( + href, + { blacklist = DEFAULT_BLACKLIST.join(",\n") } +) => blacklist.split(",").some((url) => isMatch(href, url.trim())); diff --git a/src/libs/rules.js b/src/libs/rules.js index 35f98e1..a4dc5ff 100644 --- a/src/libs/rules.js +++ b/src/libs/rules.js @@ -21,7 +21,6 @@ import { trySyncRules } from "./sync"; * @returns */ export const matchRule = async ( - rules, href, { injectRules = true, @@ -29,7 +28,7 @@ export const matchRule = async ( owSubrule = DEFAULT_OW_RULE, } ) => { - rules = [...rules]; + const rules = await getRulesWithDefault(); if (injectRules) { try { const selectedSub = subrulesList.find((item) => item.selected); diff --git a/src/userscript.js b/src/userscript.js index 9df5ce6..58a87b9 100644 --- a/src/userscript.js +++ b/src/userscript.js @@ -1,82 +1,3 @@ -import { getSettingWithDefault } from "./libs/storage"; -import { trySyncAllSubRules } from "./libs/subRules"; -import { isIframe } from "./libs/iframe"; -import { handlePing, injectScript } from "./libs/gm"; -import { genEventName } from "./libs/utils"; -import { runWebfix } from "./libs/webfix"; -import { - runIframe, - runTranslator, - showFab, - showTransbox, - windowListener, - showErr, - touchOperation, -} from "./common"; +import { run } from "./common"; -function runSettingPage() { - if (GM?.info?.script?.grant?.includes("unsafeWindow")) { - unsafeWindow.GM = GM; - unsafeWindow.APP_INFO = { - name: process.env.REACT_APP_NAME, - version: process.env.REACT_APP_VERSION, - }; - } else { - const ping = genEventName(); - window.addEventListener(ping, handlePing); - // window.eval(`(${injectScript})("${ping}")`); // eslint-disable-line - const script = document.createElement("script"); - script.textContent = `(${injectScript})("${ping}")`; - document.head.append(script); - } -} - -/** - * 入口函数 - */ -(async () => { - try { - // 设置页面 - if ( - document.location.href.includes(process.env.REACT_APP_OPTIONSPAGE_DEV) || - document.location.href.includes(process.env.REACT_APP_OPTIONSPAGE) || - document.location.href.includes(process.env.REACT_APP_OPTIONSPAGE2) - ) { - runSettingPage(); - return; - } - - // 读取设置信息 - const setting = await getSettingWithDefault(); - - // 适配iframe - if (isIframe) { - runIframe(setting); - return; - } - - // 不规范网页修复 - await runWebfix(setting); - - // 翻译网页 - const { translator, rule } = await runTranslator(setting); - - // 监听消息 - windowListener(rule); - - // 划词翻译 - showTransbox(setting); - - // 浮球按钮 - await showFab(translator); - - // 触屏操作 - touchOperation(translator); - - // 同步订阅规则 - await trySyncAllSubRules(setting); - } catch (err) { - console.error("[KISS-Translator]", err); - showErr(err.message); - } -})(); +run(true); diff --git a/src/views/Options/Setting.js b/src/views/Options/Setting.js index 489224d..9a10971 100644 --- a/src/views/Options/Setting.js +++ b/src/views/Options/Setting.js @@ -23,6 +23,7 @@ import { OPT_SHORTCUT_STYLE, OPT_SHORTCUT_POPUP, OPT_SHORTCUT_SETTING, + DEFAULT_BLACKLIST, } from "../../config"; import { useShortcut } from "../../hooks/Shortcut"; import ShortcutInput from "./ShortcutInput"; @@ -90,6 +91,7 @@ export default function Settings() { mouseKey = OPT_MOUSEKEY_DISABLE, detectRemote = false, touchTranslate = 2, + blacklist = DEFAULT_BLACKLIST.join(",\n"), } = setting; const { isHide = false } = fab || {}; @@ -268,6 +270,16 @@ export default function Settings() { )} + + );