fix: hooks & injectjs

This commit is contained in:
Gabe
2025-10-30 00:19:13 +08:00
parent 5735fee36e
commit 172dce2867
14 changed files with 155 additions and 79 deletions

View File

@@ -14,9 +14,9 @@ import {
MSG_BUILTINAI_TRANSLATE, MSG_BUILTINAI_TRANSLATE,
OPT_TRANS_BUILTINAI, OPT_TRANS_BUILTINAI,
URL_CACHE_SUBTITLE, URL_CACHE_SUBTITLE,
OPT_LANGS_TO_CODE,
} from "../config"; } from "../config";
import { sha256, withTimeout } from "../libs/utils"; import { sha256, withTimeout } from "../libs/utils";
import { kissLog } from "../libs/log";
import { import {
handleTranslate, handleTranslate,
handleSubtitle, handleSubtitle,
@@ -424,7 +424,7 @@ export const apiTranslate = async ({
usePool = true, usePool = true,
}) => { }) => {
if (!text) { if (!text) {
return ["", false]; throw new Error("The text cannot be empty.");
} }
const { apiType, apiSlug, useBatchFetch } = apiSetting; const { apiType, apiSlug, useBatchFetch } = apiSetting;
@@ -432,8 +432,7 @@ export const apiTranslate = async ({
const from = langMap.get(fromLang); const from = langMap.get(fromLang);
const to = langMap.get(toLang); const to = langMap.get(toLang);
if (!to) { if (!to) {
kissLog(`target lang: ${toLang} not support`); throw new Error(`The target lang: ${toLang} not support`);
return ["", false];
} }
// todo: 优化缓存失效因素 // todo: 优化缓存失效因素
@@ -451,7 +450,7 @@ export const apiTranslate = async ({
if (useCache) { if (useCache) {
const cache = await getHttpCachePolyfill(cacheInput); const cache = await getHttpCachePolyfill(cacheInput);
if (cache?.trText) { if (cache?.trText) {
return [cache.trText, cache.isSame]; return cache;
} }
} }
@@ -499,8 +498,12 @@ export const apiTranslate = async ({
let trText = ""; let trText = "";
let srLang = ""; let srLang = "";
let srCode = "";
if (Array.isArray(tranlation)) { if (Array.isArray(tranlation)) {
[trText, srLang = ""] = tranlation; [trText, srLang = ""] = tranlation;
if (srLang) {
srCode = OPT_LANGS_TO_CODE[apiType].get(srLang) || "";
}
} else if (typeof tranlation === "string") { } else if (typeof tranlation === "string") {
trText = tranlation; trText = tranlation;
} }
@@ -513,10 +516,10 @@ export const apiTranslate = async ({
// 插入缓存 // 插入缓存
if (useCache) { if (useCache) {
putHttpCachePolyfill(cacheInput, null, { trText, isSame, srLang }); putHttpCachePolyfill(cacheInput, null, { trText, isSame, srLang, srCode });
} }
return [trText, isSame]; return { trText, srLang, srCode, isSame };
}; };
// 字幕处理/翻译 // 字幕处理/翻译

View File

@@ -32,7 +32,7 @@ import {
import { msAuth } from "../libs/auth"; import { msAuth } from "../libs/auth";
import { genDeeplFree } from "./deepl"; import { genDeeplFree } from "./deepl";
import { genBaidu } from "./baidu"; import { genBaidu } from "./baidu";
import interpreter from "../libs/interpreter"; import { interpreter } from "../libs/interpreter";
import { parseJsonObj, extractJson } from "../libs/utils"; import { parseJsonObj, extractJson } from "../libs/utils";
import { kissLog } from "../libs/log"; import { kissLog } from "../libs/log";
import { fetchData } from "../libs/fetch"; import { fetchData } from "../libs/fetch";

View File

@@ -33,7 +33,7 @@ import { sendTabMsg } from "./libs/msg";
import { trySyncAllSubRules } from "./libs/subRules"; import { trySyncAllSubRules } from "./libs/subRules";
import { saveRule } from "./libs/rules"; import { saveRule } from "./libs/rules";
import { getCurTabId } from "./libs/msg"; import { getCurTabId } from "./libs/msg";
import { injectInlineJs, injectInternalCss } from "./libs/injector"; import { injectInlineJsBg, injectInternalCss } from "./libs/injector";
import { kissLog, logger } from "./libs/log"; import { kissLog, logger } from "./libs/log";
import { chromeDetect, chromeTranslate } from "./libs/builtinAI"; import { chromeDetect, chromeTranslate } from "./libs/builtinAI";
@@ -268,7 +268,7 @@ const messageHandlers = {
[MSG_PUT_HTTPCACHE]: (args) => putHttpCache(args), [MSG_PUT_HTTPCACHE]: (args) => putHttpCache(args),
[MSG_OPEN_OPTIONS]: () => browser.runtime.openOptionsPage(), [MSG_OPEN_OPTIONS]: () => browser.runtime.openOptionsPage(),
[MSG_SAVE_RULE]: (args) => saveRule(args), [MSG_SAVE_RULE]: (args) => saveRule(args),
[MSG_INJECT_JS]: (args) => injectToCurrentTab(injectInlineJs, args), [MSG_INJECT_JS]: (args) => injectToCurrentTab(injectInlineJsBg, args),
[MSG_INJECT_CSS]: (args) => injectToCurrentTab(injectInternalCss, args), [MSG_INJECT_CSS]: (args) => injectToCurrentTab(injectInternalCss, args),
[MSG_UPDATE_CSP]: (args) => updateCspRules(args), [MSG_UPDATE_CSP]: (args) => updateCspRules(args),
[MSG_CONTEXT_MENUS]: (args) => addContextMenus(args), [MSG_CONTEXT_MENUS]: (args) => addContextMenus(args),

View File

@@ -46,7 +46,7 @@ export const OPT_TRANS_OPENROUTER = "OpenRouter";
export const OPT_TRANS_CUSTOMIZE = "Custom"; export const OPT_TRANS_CUSTOMIZE = "Custom";
// 内置支持的翻译引擎 // 内置支持的翻译引擎
export const OPT_ALL_TYPES = [ export const OPT_ALL_TRANS_TYPES = [
OPT_TRANS_BUILTINAI, OPT_TRANS_BUILTINAI,
OPT_TRANS_GOOGLE, OPT_TRANS_GOOGLE,
OPT_TRANS_GOOGLE_2, OPT_TRANS_GOOGLE_2,
@@ -82,7 +82,7 @@ export const OPT_LANGDETECTOR_MAP = new Set(OPT_LANGDETECTOR_ALL);
// 翻译引擎特殊集合 // 翻译引擎特殊集合
export const API_SPE_TYPES = { export const API_SPE_TYPES = {
// 内置翻译 // 内置翻译
builtin: new Set(OPT_ALL_TYPES), builtin: new Set(OPT_ALL_TRANS_TYPES),
// 机器翻译 // 机器翻译
machine: new Set([ machine: new Set([
OPT_TRANS_MICROSOFT, OPT_TRANS_MICROSOFT,
@@ -557,7 +557,7 @@ const defaultApiOpts = {
}; };
// 内置翻译接口列表(带参数) // 内置翻译接口列表(带参数)
export const DEFAULT_API_LIST = OPT_ALL_TYPES.map((apiType) => ({ export const DEFAULT_API_LIST = OPT_ALL_TRANS_TYPES.map((apiType) => ({
...defaultApiOpts[apiType], ...defaultApiOpts[apiType],
apiSlug: apiType, apiSlug: apiType,
apiName: apiType, apiName: apiType,
@@ -565,4 +565,6 @@ export const DEFAULT_API_LIST = OPT_ALL_TYPES.map((apiType) => ({
})); }));
export const DEFAULT_API_TYPE = OPT_TRANS_MICROSOFT; export const DEFAULT_API_TYPE = OPT_TRANS_MICROSOFT;
export const DEFAULT_API_SETTING = DEFAULT_API_LIST[DEFAULT_API_TYPE]; export const DEFAULT_API_SETTING = DEFAULT_API_LIST.find(
(a) => a.apiType === DEFAULT_API_TYPE
);

View File

@@ -745,9 +745,33 @@ export const I18N = {
zh_TW: `注入 JS`, zh_TW: `注入 JS`,
}, },
inject_js_helper: { inject_js_helper: {
zh: `初始化时注入运行,一个页面仅运行一次。`, zh: `预加载时注入,一个页面仅运行一次。内置全局对象 KT: {
en: `Injected and run at initialization, and only run once per page.`, apiTranslate,
zh_TW: `初始化時注入運行,一個頁面僅運行一次。`, apiDectect,
apiSetting,
apisMap,
toLang,
docInfo,
glossary,
}`,
en: `Injected during preload, runs only once per page. Built-in global object KT: {
apiTranslate,
apiDectect,
apiSetting,
apisMap,
toLang,
docInfo,
glossary,
}`,
zh_TW: `預先載入時注入,一個頁面僅運行一次。內建全域物件 KT: {
apiTranslate,
apiDectect,
apiSetting,
apisMap,
toLang,
docInfo,
glossary,
}`,
}, },
inject_css: { inject_css: {
zh: `注入CSS`, zh: `注入CSS`,
@@ -1360,9 +1384,24 @@ export const I18N = {
zh_TW: `翻譯開始 Hook`, zh_TW: `翻譯開始 Hook`,
}, },
translate_start_hook_helper: { translate_start_hook_helper: {
zh: `翻译前时运行,入参为: ({hostNode, parentNode, nodes})`, zh: `翻译前时运行,入参为: {text,
en: `Run before translation, input parameters are: ({hostNode, parentNode, nodes})`, fromLang,
zh_TW: `翻譯前時運行,入參為: ({hostNode, parentNode, nodes})`, toLang,
apiSetting,
docInfo,
glossary,}`,
en: `Run before translation, input parameters are: {text,
fromLang,
toLang,
apiSetting,
docInfo,
glossary,}`,
zh_TW: `翻譯前時運行,入參為: {text,
fromLang,
toLang,
apiSetting,
docInfo,
glossary,}`,
}, },
translate_end_hook: { translate_end_hook: {
zh: `翻译完成钩子函数`, zh: `翻译完成钩子函数`,

View File

@@ -117,7 +117,7 @@ export const DEFAULT_RULE = {
parentStyle: "", // 选择器父节点样式 parentStyle: "", // 选择器父节点样式
grandStyle: "", // 选择器父节点样式 grandStyle: "", // 选择器父节点样式
injectJs: "", // 注入JS injectJs: "", // 注入JS
injectCss: "", // 注入CSS // injectCss: "", // 注入CSS (作废)
transOnly: GLOBAL_KEY, // 是否仅显示译文 transOnly: GLOBAL_KEY, // 是否仅显示译文
// transTiming: GLOBAL_KEY, // 翻译时机/鼠标悬停翻译 (暂时作废) // transTiming: GLOBAL_KEY, // 翻译时机/鼠标悬停翻译 (暂时作废)
transTag: GLOBAL_KEY, // 译文元素标签 transTag: GLOBAL_KEY, // 译文元素标签
@@ -160,7 +160,7 @@ export const GLOBLA_RULE = {
parentStyle: DEFAULT_SELECT_STYLE, // 选择器父节点样式 parentStyle: DEFAULT_SELECT_STYLE, // 选择器父节点样式
grandStyle: DEFAULT_SELECT_STYLE, // 选择器祖节点样式 grandStyle: DEFAULT_SELECT_STYLE, // 选择器祖节点样式
injectJs: "", // 注入JS injectJs: "", // 注入JS
injectCss: "", // 注入CSS // injectCss: "", // 注入CSS(作废)
transOnly: "false", // 是否仅显示译文 transOnly: "false", // 是否仅显示译文
// transTiming: OPT_TIMING_PAGESCROLL, // 翻译时机/鼠标悬停翻译 (暂时作废) // transTiming: OPT_TIMING_PAGESCROLL, // 翻译时机/鼠标悬停翻译 (暂时作废)
transTag: DEFAULT_TRANS_TAG, // 译文元素标签 transTag: DEFAULT_TRANS_TAG, // 译文元素标签

View File

@@ -13,6 +13,18 @@ export const injectInlineJs = (code, id = "kiss-translator-inline-js") => {
(document.head || document.documentElement).appendChild(el); (document.head || document.documentElement).appendChild(el);
}; };
export const injectInlineJsBg = (code, id = "kiss-translator-inline-js") => {
if (document.getElementById(id)) {
return;
}
const el = document.createElement("script");
el.type = "text/javascript";
el.id = id;
el.textContent = code;
(document.head || document.documentElement).appendChild(el);
};
// Function to inject external JavaScript file // Function to inject external JavaScript file
export const injectExternalJs = (src, id = "kiss-translator-external-js") => { export const injectExternalJs = (src, id = "kiss-translator-external-js") => {
if (document.getElementById(id)) { if (document.getElementById(id)) {

View File

@@ -193,7 +193,7 @@ export class InputTranslator {
try { try {
addLoading(node, loadingId); addLoading(node, loadingId);
const [trText, isSame] = await apiTranslate({ const { trText, isSame } = await apiTranslate({
text, text,
fromLang, fromLang,
toLang, toLang,

View File

@@ -1,6 +1,6 @@
import Sval from "sval"; import Sval from "sval";
const interpreter = new Sval({ export const interpreter = new Sval({
// ECMA Version of the code // ECMA Version of the code
// 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 // 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15
// or 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | 2022 | 2023 | 2024 // or 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | 2022 | 2023 | 2024
@@ -12,5 +12,3 @@ const interpreter = new Sval({
// Whether the code runs in a sandbox // Whether the code runs in a sandbox
sandBox: true, sandBox: true,
}); });
export default interpreter;

View File

@@ -60,7 +60,7 @@ export const matchRule = async (href, { injectRules, subrulesList }) => {
"parentStyle", "parentStyle",
"grandStyle", "grandStyle",
"injectJs", "injectJs",
"injectCss", // "injectCss",
// "fixerSelector", // "fixerSelector",
"transStartHook", "transStartHook",
"transEndHook", "transEndHook",
@@ -154,7 +154,7 @@ export const checkRules = (rules) => {
parentStyle, parentStyle,
grandStyle, grandStyle,
injectJs, injectJs,
injectCss, // injectCss,
apiSlug, apiSlug,
fromLang, fromLang,
toLang, toLang,
@@ -193,7 +193,7 @@ export const checkRules = (rules) => {
parentStyle: type(parentStyle) === "string" ? parentStyle : "", parentStyle: type(parentStyle) === "string" ? parentStyle : "",
grandStyle: type(grandStyle) === "string" ? grandStyle : "", grandStyle: type(grandStyle) === "string" ? grandStyle : "",
injectJs: type(injectJs) === "string" ? injectJs : "", injectJs: type(injectJs) === "string" ? injectJs : "",
injectCss: type(injectCss) === "string" ? injectCss : "", // injectCss: type(injectCss) === "string" ? injectCss : "",
bgColor: type(bgColor) === "string" ? bgColor : "", bgColor: type(bgColor) === "string" ? bgColor : "",
textDiyStyle: type(textDiyStyle) === "string" ? textDiyStyle : "", textDiyStyle: type(textDiyStyle) === "string" ? textDiyStyle : "",
apiSlug: apiSlug:

View File

@@ -2,8 +2,6 @@ import {
APP_UPNAME, APP_UPNAME,
APP_LCNAME, APP_LCNAME,
APP_CONSTS, APP_CONSTS,
MSG_INJECT_JS,
MSG_INJECT_CSS,
OPT_STYLE_FUZZY, OPT_STYLE_FUZZY,
GLOBLA_RULE, GLOBLA_RULE,
DEFAULT_SETTING, DEFAULT_SETTING,
@@ -16,13 +14,10 @@ import {
OPT_SPLIT_PARAGRAPH_DISABLE, OPT_SPLIT_PARAGRAPH_DISABLE,
OPT_SPLIT_PARAGRAPH_TEXTLENGTH, OPT_SPLIT_PARAGRAPH_TEXTLENGTH,
} from "../config"; } from "../config";
import interpreter from "./interpreter"; import { interpreter } from "./interpreter";
import { clearFetchPool } from "./pool"; import { clearFetchPool } from "./pool";
import { debounce, scheduleIdle, genEventName, truncateWords } from "./utils"; import { debounce, scheduleIdle, genEventName, truncateWords } from "./utils";
import { apiTranslate } from "../apis"; import { apiTranslate } from "../apis";
import { sendBgMsg } from "./msg";
import { isExt } from "./client";
import { injectInlineJs, injectInternalCss } from "./injector";
import { kissLog } from "./log"; import { kissLog } from "./log";
import { clearAllBatchQueue } from "./batchQueue"; import { clearAllBatchQueue } from "./batchQueue";
import { genTextClass } from "./style"; import { genTextClass } from "./style";
@@ -1145,7 +1140,6 @@ export class Translator {
const { const {
transTag, transTag,
textStyle, textStyle,
transStartHook,
transEndHook, transEndHook,
transOnly, transOnly,
termsStyle, termsStyle,
@@ -1164,20 +1158,6 @@ export class Translator {
const parentNode = hostNode.parentElement; const parentNode = hostNode.parentElement;
const hideOrigin = transOnly === "true"; const hideOrigin = transOnly === "true";
// 翻译开始钩子函数
if (transStartHook?.trim()) {
try {
interpreter.run(`exports.transStartHook = ${transStartHook}`);
interpreter.exports.transStartHook({
hostNode,
parentNode,
nodes,
});
} catch (err) {
kissLog("transStartHook", err);
}
}
try { try {
const [processedString, placeholderMap] = this.#serializeForTranslation( const [processedString, placeholderMap] = this.#serializeForTranslation(
nodes, nodes,
@@ -1202,10 +1182,8 @@ export class Translator {
nodes[nodes.length - 1].after(wrapper); nodes[nodes.length - 1].after(wrapper);
const currentRunId = this.#runId; const currentRunId = this.#runId;
const [translatedText, isSameLang] = await this.#translateFetch( const { trText: translatedText, isSame: isSameLang } =
processedString, await this.#translateFetch(processedString, deLang);
deLang
);
if (this.#runId !== currentRunId) { if (this.#runId !== currentRunId) {
throw new Error("Request terminated"); throw new Error("Request terminated");
} }
@@ -1391,16 +1369,39 @@ export class Translator {
// 发起翻译请求 // 发起翻译请求
#translateFetch(text, deLang = "") { #translateFetch(text, deLang = "") {
const { fromLang, toLang } = this.#rule; const { toLang, transStartHook } = this.#rule;
const fromLang = deLang || this.#rule.fromLang;
const apiSetting = { ...this.#apiSetting };
const docInfo = { ...this.#docInfo };
const glossary = { ...this.#glossary };
const apisMap = this.#apisMap;
return apiTranslate({ const args = {
text, text,
fromLang: deLang || fromLang, fromLang,
toLang, toLang,
apiSetting: this.#apiSetting, apiSetting,
docInfo: this.#docInfo, docInfo,
glossary: this.#glossary, glossary,
}); };
// 翻译开始钩子函数
if (transStartHook?.trim()) {
try {
interpreter.run(`exports.transStartHook = ${transStartHook}`);
const hookResult = interpreter.exports.transStartHook({
...args,
apisMap,
});
if (hookResult) {
Object.assign(args, ...hookResult);
}
} catch (err) {
kissLog("transStartHook", err);
}
}
return apiTranslate(args);
} }
// 查找指定节点下所有译文节点 // 查找指定节点下所有译文节点
@@ -1596,14 +1597,35 @@ export class Translator {
this.#isJsInjected = true; this.#isJsInjected = true;
try { try {
const { injectJs, injectCss } = this.#rule; // const { injectJs, injectCss } = this.#rule;
if (isExt) { // if (isExt) {
injectJs && sendBgMsg(MSG_INJECT_JS, injectJs); // injectJs && sendBgMsg(MSG_INJECT_JS, injectJs);
injectCss && sendBgMsg(MSG_INJECT_CSS, injectCss); // injectCss && sendBgMsg(MSG_INJECT_CSS, injectCss);
} else { // } else {
injectJs && // injectJs &&
injectInlineJs(injectJs, "kiss-translator-userinit-injector"); // injectInlineJs(injectJs, "kiss-translator-userinit-injector");
injectCss && injectInternalCss(injectCss); // injectCss && injectInternalCss(injectCss);
// }
const { injectJs, toLang } = this.#rule;
if (injectJs?.trim()) {
const apiSetting = { ...this.#apiSetting };
const docInfo = { ...this.#docInfo };
const glossary = { ...this.#glossary };
const apisMap = this.#apisMap;
const apiDectect = tryDetectLang;
interpreter.import({
KT: {
apiTranslate,
apiDectect,
apiSetting,
apisMap,
toLang,
docInfo,
glossary,
},
});
interpreter.run(injectJs);
} }
} catch (err) { } catch (err) {
kissLog("inject js", err); kissLog("inject js", err);
@@ -1654,8 +1676,8 @@ export class Translator {
try { try {
const deLang = await tryDetectLang(title); const deLang = await tryDetectLang(title);
const [translatedTitle] = await this.#translateFetch(title, deLang); const { trText } = await this.#translateFetch(title, deLang);
document.title = translatedTitle || title; document.title = trText || title;
} catch (err) { } catch (err) {
kissLog("tanslate title", err); kissLog("tanslate title", err);
} }

View File

@@ -38,7 +38,7 @@ import {
DEFAULT_BATCH_SIZE, DEFAULT_BATCH_SIZE,
DEFAULT_BATCH_LENGTH, DEFAULT_BATCH_LENGTH,
DEFAULT_CONTEXT_SIZE, DEFAULT_CONTEXT_SIZE,
OPT_ALL_TYPES, OPT_ALL_TRANS_TYPES,
API_SPE_TYPES, API_SPE_TYPES,
BUILTIN_STONES, BUILTIN_STONES,
BUILTIN_PLACEHOLDERS, BUILTIN_PLACEHOLDERS,
@@ -54,7 +54,7 @@ function TestButton({ api }) {
const handleApiTest = async () => { const handleApiTest = async () => {
try { try {
setLoading(true); setLoading(true);
const [text] = await apiTranslate({ const { trText } = await apiTranslate({
text: "hello world", text: "hello world",
fromLang: "en", fromLang: "en",
toLang: "zh-CN", toLang: "zh-CN",
@@ -62,7 +62,7 @@ function TestButton({ api }) {
useCache: false, useCache: false,
usePool: false, usePool: false,
}); });
if (!text) { if (!trText) {
throw new Error("empty result"); throw new Error("empty result");
} }
alert.success(i18n("test_success")); alert.success(i18n("test_success"));
@@ -775,7 +775,7 @@ export default function Apis() {
const apiTypes = useMemo( const apiTypes = useMemo(
() => () =>
OPT_ALL_TYPES.map((type) => ({ OPT_ALL_TRANS_TYPES.map((type) => ({
type, type,
label: type, label: type,
})), })),

View File

@@ -108,7 +108,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
parentStyle = "", parentStyle = "",
grandStyle = "", grandStyle = "",
injectJs = "", injectJs = "",
injectCss = "", // injectCss = "",
apiSlug, apiSlug,
fromLang, fromLang,
toLang, toLang,
@@ -695,7 +695,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
maxRows={10} maxRows={10}
/> */} /> */}
<TextField {/* <TextField
size="small" size="small"
label={i18n("inject_css")} label={i18n("inject_css")}
helperText={i18n("inject_css_helper")} helperText={i18n("inject_css_helper")}
@@ -705,7 +705,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
onChange={handleChange} onChange={handleChange}
maxRows={10} maxRows={10}
multiline multiline
/> /> */}
<TextField <TextField
size="small" size="small"
label={i18n("inject_js")} label={i18n("inject_js")}

View File

@@ -38,7 +38,7 @@ export default function TranCont({
setTrText(""); setTrText("");
setError(""); setError("");
const [trText] = await apiTranslate({ const { trText } = await apiTranslate({
text, text,
fromLang, fromLang,
toLang, toLang,