feat: support subtitle translate for userscript
This commit is contained in:
@@ -91,7 +91,6 @@ const userscriptWebpack = (config, env) => {
|
|||||||
// @grant GM.getValue
|
// @grant GM.getValue
|
||||||
// @grant GM.deleteValue
|
// @grant GM.deleteValue
|
||||||
// @grant GM.info
|
// @grant GM.info
|
||||||
// @grant GM.addElement
|
|
||||||
// @grant unsafeWindow
|
// @grant unsafeWindow
|
||||||
// @connect translate.googleapis.com
|
// @connect translate.googleapis.com
|
||||||
// @connect translate-pa.googleapis.com
|
// @connect translate-pa.googleapis.com
|
||||||
@@ -132,7 +131,6 @@ const userscriptWebpack = (config, env) => {
|
|||||||
config.entry = {
|
config.entry = {
|
||||||
main: paths.appIndexJs,
|
main: paths.appIndexJs,
|
||||||
options: paths.appSrc + "/options.js",
|
options: paths.appSrc + "/options.js",
|
||||||
injector: paths.appSrc + "/injector.js",
|
|
||||||
"kiss-translator.user": paths.appSrc + "/userscript.js",
|
"kiss-translator.user": paths.appSrc + "/userscript.js",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { trySyncAllSubRules } from "./libs/subRules";
|
|||||||
import { isInBlacklist } from "./libs/blacklist";
|
import { isInBlacklist } from "./libs/blacklist";
|
||||||
import { runSubtitle } from "./subtitle/subtitle";
|
import { runSubtitle } from "./subtitle/subtitle";
|
||||||
import { logger } from "./libs/log";
|
import { logger } from "./libs/log";
|
||||||
|
import { injectInlineJs } from "./libs/injector";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 油猴脚本设置页面
|
* 油猴脚本设置页面
|
||||||
@@ -35,9 +36,10 @@ function runSettingPage() {
|
|||||||
const ping = genEventName();
|
const ping = genEventName();
|
||||||
window.addEventListener(ping, handlePing);
|
window.addEventListener(ping, handlePing);
|
||||||
// window.eval(`(${injectScript})("${ping}")`); // eslint-disable-line
|
// window.eval(`(${injectScript})("${ping}")`); // eslint-disable-line
|
||||||
const script = document.createElement("script");
|
injectInlineJs(
|
||||||
script.textContent = `(${injectScript})("${ping}")`;
|
`(${injectScript})("${ping}")`,
|
||||||
document.head.append(script);
|
"kiss-translator-options-injector"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,3 @@
|
|||||||
(function () {
|
import { XMLHttpRequestInjector } from "./subtitle/XMLHttpRequestInjector";
|
||||||
const originalOpen = XMLHttpRequest.prototype.open;
|
|
||||||
XMLHttpRequest.prototype.open = function (...args) {
|
XMLHttpRequestInjector();
|
||||||
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);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|||||||
@@ -1,28 +1,29 @@
|
|||||||
// Function to inject inline JavaScript code
|
import { trustedTypesHelper } from "./trustedTypes";
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to inject external JavaScript file
|
// Function to inject inline JavaScript code
|
||||||
export const injectExternalJs = (src, id = "kiss-translator-injector") => {
|
export const injectInlineJs = (code, id = "kiss-translator-inline-js") => {
|
||||||
if (document.getElementById(id)) {
|
if (document.getElementById(id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// const el = document.createElement("script");
|
const el = document.createElement("script");
|
||||||
// el.setAttribute("data-source", "kiss-inject injectExternalJs");
|
el.type = "text/javascript";
|
||||||
// el.setAttribute("type", "text/javascript");
|
el.id = id;
|
||||||
// el.setAttribute("src", src);
|
el.textContent = trustedTypesHelper.createScript(code);
|
||||||
// el.setAttribute("id", id);
|
(document.head || document.documentElement).appendChild(el);
|
||||||
// document.body?.appendChild(el);
|
};
|
||||||
const script = document.createElement("script");
|
|
||||||
script.id = id;
|
// Function to inject external JavaScript file
|
||||||
script.src = src;
|
export const injectExternalJs = (src, id = "kiss-translator-external-js") => {
|
||||||
(document.head || document.documentElement).appendChild(script);
|
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
|
// Function to inject internal CSS code
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import { browser } from "./browser";
|
|||||||
import { isIframe, sendIframeMsg } from "./iframe";
|
import { isIframe, sendIframeMsg } from "./iframe";
|
||||||
import { TransboxManager } from "./tranbox";
|
import { TransboxManager } from "./tranbox";
|
||||||
import { InputTranslator } from "./inputTranslate";
|
import { InputTranslator } from "./inputTranslate";
|
||||||
|
import { trustedTypesHelper } from "./trustedTypes";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Translator
|
* @class Translator
|
||||||
@@ -1025,7 +1026,7 @@ export class Translator {
|
|||||||
translatedText,
|
translatedText,
|
||||||
placeholderMap
|
placeholderMap
|
||||||
);
|
);
|
||||||
const trustedHTML = this.#createTrustedHTML(htmlString);
|
const trustedHTML = trustedTypesHelper.createHTML(htmlString);
|
||||||
|
|
||||||
// const parser = new DOMParser();
|
// const parser = new DOMParser();
|
||||||
// const doc = parser.parseFromString(trustedHTML, "text/html");
|
// 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) {
|
#serializeForTranslation(nodes) {
|
||||||
let replaceCounter = 0; // {{n}}
|
let replaceCounter = 0; // {{n}}
|
||||||
@@ -1404,7 +1392,8 @@ export class Translator {
|
|||||||
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 && injectInlineJs(injectJs);
|
injectJs &&
|
||||||
|
injectInlineJs(injectJs, "kiss-translator-userinit-injector");
|
||||||
injectCss && injectInternalCss(injectCss);
|
injectCss && injectInternalCss(injectCss);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
33
src/libs/trustedTypes.js
Normal file
33
src/libs/trustedTypes.js
Normal 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,
|
||||||
|
};
|
||||||
|
})();
|
||||||
19
src/subtitle/XMLHttpRequestInjector.js
Normal file
19
src/subtitle/XMLHttpRequestInjector.js
Normal 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);
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -38,7 +38,6 @@ class YouTubeCaptionProvider {
|
|||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
window.addEventListener("message", (event) => {
|
window.addEventListener("message", (event) => {
|
||||||
if (event.source !== window) return;
|
|
||||||
if (event.data?.type === MSG_XHR_DATA_YOUTUBE) {
|
if (event.data?.type === MSG_XHR_DATA_YOUTUBE) {
|
||||||
const { url, response } = event.data;
|
const { url, response } = event.data;
|
||||||
if (url && response) {
|
if (url && response) {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { DEFAULT_API_SETTING } from "../config/api.js";
|
|||||||
import { DEFAULT_SUBTITLE_SETTING } from "../config/setting.js";
|
import { DEFAULT_SUBTITLE_SETTING } from "../config/setting.js";
|
||||||
import { injectExternalJs } from "../libs/injector.js";
|
import { injectExternalJs } from "../libs/injector.js";
|
||||||
import { logger } from "../libs/log.js";
|
import { logger } from "../libs/log.js";
|
||||||
|
import { XMLHttpRequestInjector } from "./XMLHttpRequestInjector.js";
|
||||||
|
import { injectInlineJs } from "../libs/injector.js";
|
||||||
|
|
||||||
const providers = [
|
const providers = [
|
||||||
{ pattern: "https://www.youtube.com", start: YouTubeInitializer },
|
{ 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));
|
const provider = providers.find((item) => isMatch(href, item.pattern));
|
||||||
if (provider) {
|
if (provider) {
|
||||||
|
const id = "kiss-translator-xmlHttp-injector";
|
||||||
if (isUserscript) {
|
if (isUserscript) {
|
||||||
GM.addElement("script", {
|
injectInlineJs(`(${XMLHttpRequestInjector})()`, id);
|
||||||
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."
|
|
||||||
);
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
const id = "kiss-translator-injector";
|
|
||||||
const src = browser.runtime.getURL("injector.js");
|
const src = browser.runtime.getURL("injector.js");
|
||||||
injectExternalJs(src, id);
|
injectExternalJs(src, id);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user