feat: support subtitle translate for userscript

This commit is contained in:
Gabe
2025-10-15 21:41:09 +08:00
parent 5e67e15842
commit ecab4ab634
9 changed files with 89 additions and 70 deletions

View File

@@ -91,7 +91,6 @@ const userscriptWebpack = (config, env) => {
// @grant GM.getValue
// @grant GM.deleteValue
// @grant GM.info
// @grant GM.addElement
// @grant unsafeWindow
// @connect translate.googleapis.com
// @connect translate-pa.googleapis.com
@@ -132,7 +131,6 @@ const userscriptWebpack = (config, env) => {
config.entry = {
main: paths.appIndexJs,
options: paths.appSrc + "/options.js",
injector: paths.appSrc + "/injector.js",
"kiss-translator.user": paths.appSrc + "/userscript.js",
};

View File

@@ -20,6 +20,7 @@ import { trySyncAllSubRules } from "./libs/subRules";
import { isInBlacklist } from "./libs/blacklist";
import { runSubtitle } from "./subtitle/subtitle";
import { logger } from "./libs/log";
import { injectInlineJs } from "./libs/injector";
/**
* 油猴脚本设置页面
@@ -35,9 +36,10 @@ function runSettingPage() {
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);
injectInlineJs(
`(${injectScript})("${ping}")`,
"kiss-translator-options-injector"
);
}
}

View File

@@ -1,19 +1,3 @@
(function () {
const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (...args) {
const url = args[1];
if (typeof url === "string" && url.includes("timedtext")) {
this.addEventListener("load", function () {
window.postMessage(
{
type: "KISS_XHR_DATA_YOUTUBE",
url: this.responseURL,
response: this.responseText,
},
window.location.origin
);
});
}
return originalOpen.apply(this, args);
};
})();
import { XMLHttpRequestInjector } from "./subtitle/XMLHttpRequestInjector";
XMLHttpRequestInjector();

View File

@@ -1,28 +1,29 @@
// Function to inject inline JavaScript code
export const injectInlineJs = (code) => {
const el = document.createElement("script");
el.setAttribute("data-source", "kiss-inject injectInlineJs");
el.setAttribute("type", "text/javascript");
el.textContent = code;
document.body?.appendChild(el);
};
import { trustedTypesHelper } from "./trustedTypes";
// Function to inject external JavaScript file
export const injectExternalJs = (src, id = "kiss-translator-injector") => {
// Function to inject inline JavaScript code
export const injectInlineJs = (code, id = "kiss-translator-inline-js") => {
if (document.getElementById(id)) {
return;
}
// const el = document.createElement("script");
// el.setAttribute("data-source", "kiss-inject injectExternalJs");
// el.setAttribute("type", "text/javascript");
// el.setAttribute("src", src);
// el.setAttribute("id", id);
// document.body?.appendChild(el);
const script = document.createElement("script");
script.id = id;
script.src = src;
(document.head || document.documentElement).appendChild(script);
const el = document.createElement("script");
el.type = "text/javascript";
el.id = id;
el.textContent = trustedTypesHelper.createScript(code);
(document.head || document.documentElement).appendChild(el);
};
// Function to inject external JavaScript file
export const injectExternalJs = (src, id = "kiss-translator-external-js") => {
if (document.getElementById(id)) {
return;
}
const el = document.createElement("script");
el.type = "text/javascript";
el.id = id;
el.src = trustedTypesHelper.createScriptURL(src);
(document.head || document.documentElement).appendChild(el);
};
// Function to inject internal CSS code

View File

@@ -37,6 +37,7 @@ import { browser } from "./browser";
import { isIframe, sendIframeMsg } from "./iframe";
import { TransboxManager } from "./tranbox";
import { InputTranslator } from "./inputTranslate";
import { trustedTypesHelper } from "./trustedTypes";
/**
* @class Translator
@@ -1025,7 +1026,7 @@ export class Translator {
translatedText,
placeholderMap
);
const trustedHTML = this.#createTrustedHTML(htmlString);
const trustedHTML = trustedTypesHelper.createHTML(htmlString);
// const parser = new DOMParser();
// const doc = parser.parseFromString(trustedHTML, "text/html");
@@ -1076,19 +1077,6 @@ export class Translator {
}
}
#createTrustedHTML(html) {
if (window.trustedTypes && window.trustedTypes.createPolicy) {
const policy = window.trustedTypes.createPolicy(
"kiss-translator-policy#html",
{
createHTML: (input) => input,
}
);
return policy.createHTML(html);
}
return html;
}
// 处理节点转为翻译字符串
#serializeForTranslation(nodes) {
let replaceCounter = 0; // {{n}}
@@ -1404,7 +1392,8 @@ export class Translator {
injectJs && sendBgMsg(MSG_INJECT_JS, injectJs);
injectCss && sendBgMsg(MSG_INJECT_CSS, injectCss);
} else {
injectJs && injectInlineJs(injectJs);
injectJs &&
injectInlineJs(injectJs, "kiss-translator-userinit-injector");
injectCss && injectInternalCss(injectCss);
}
} catch (err) {

33
src/libs/trustedTypes.js Normal file
View File

@@ -0,0 +1,33 @@
export const trustedTypesHelper = (() => {
const POLICY_NAME = "kiss-translator-policy";
let policy = null;
if (globalThis.trustedTypes && globalThis.trustedTypes.createPolicy) {
try {
policy = globalThis.trustedTypes.createPolicy(POLICY_NAME, {
createHTML: (string) => string,
createScript: (string) => string,
createScriptURL: (string) => string,
});
} catch (err) {
if (err.message.includes("already exists")) {
policy = globalThis.trustedTypes.policies.get(POLICY_NAME);
} else {
console.error("cont create Trusted Types", err);
}
}
}
return {
createHTML: (htmlString) => {
return policy ? policy.createHTML(htmlString) : htmlString;
},
createScript: (scriptString) => {
return policy ? policy.createScript(scriptString) : scriptString;
},
createScriptURL: (urlString) => {
return policy ? policy.createScriptURL(urlString) : urlString;
},
isEnabled: () => policy !== null,
};
})();

View File

@@ -0,0 +1,19 @@
export const XMLHttpRequestInjector = () => {
const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (...args) {
const url = args[1];
if (typeof url === "string" && url.includes("timedtext")) {
this.addEventListener("load", function () {
window.postMessage(
{
type: "KISS_XHR_DATA_YOUTUBE",
url: this.responseURL,
response: this.responseText,
},
window.location.origin
);
});
}
return originalOpen.apply(this, args);
};
};

View File

@@ -38,7 +38,6 @@ class YouTubeCaptionProvider {
initialize() {
window.addEventListener("message", (event) => {
if (event.source !== window) return;
if (event.data?.type === MSG_XHR_DATA_YOUTUBE) {
const { url, response } = event.data;
if (url && response) {

View File

@@ -5,6 +5,8 @@ import { DEFAULT_API_SETTING } from "../config/api.js";
import { DEFAULT_SUBTITLE_SETTING } from "../config/setting.js";
import { injectExternalJs } from "../libs/injector.js";
import { logger } from "../libs/log.js";
import { XMLHttpRequestInjector } from "./XMLHttpRequestInjector.js";
import { injectInlineJs } from "../libs/injector.js";
const providers = [
{ pattern: "https://www.youtube.com", start: YouTubeInitializer },
@@ -19,18 +21,10 @@ export function runSubtitle({ href, setting, isUserscript }) {
const provider = providers.find((item) => isMatch(href, item.pattern));
if (provider) {
const id = "kiss-translator-xmlHttp-injector";
if (isUserscript) {
GM.addElement("script", {
src: "https://github.com/fishjar/kiss-translator/blob/gh-pages/injector.js",
// src: "http://127.0.0.1:8000/injector.js",
type: "text/javascript",
}).onload = function () {
console.log(
"Script successfully injected and loaded via GM_addElement."
);
};
injectInlineJs(`(${XMLHttpRequestInjector})()`, id);
} else {
const id = "kiss-translator-injector";
const src = browser.runtime.getURL("injector.js");
injectExternalJs(src, id);
}