Add gtranslate script
This commit is contained in:
parent
b549d619e7
commit
41f0a7e01d
7 changed files with 156 additions and 11 deletions
|
@ -2,6 +2,10 @@ import { browser, Runtime, Tabs } from "webextension-polyfill-ts";
|
|||
import { GTranslateScraper } from "./gtranslate_scraper";
|
||||
|
||||
console.log("Background script loaded");
|
||||
|
||||
const scraper = new GTranslateScraper();
|
||||
console.log(scraper);
|
||||
|
||||
const getTab = async (s: Runtime.MessageSender): Promise<Tabs.Tab> => {
|
||||
if (s.tab) return s.tab;
|
||||
let tabs = await browser.tabs.query({
|
||||
|
@ -47,6 +51,8 @@ browser.runtime.onMessage.addListener(
|
|||
return setEnabledCommand(c.value, s);
|
||||
case commands.getEnabled:
|
||||
return getEnabledCommand(s);
|
||||
case commands.translate:
|
||||
return scraper.translate(c.toTranslate);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -58,5 +64,3 @@ browser.tabs.onUpdated.addListener(async (tabID, changeInfo) => {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
new GTranslateScraper();
|
||||
|
|
46
src/background/gtranslate_content_script.ts
Normal file
46
src/background/gtranslate_content_script.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { browser } from "webextension-polyfill-ts";
|
||||
import "./gtranslate_types";
|
||||
declare interface Window {
|
||||
hasRun: boolean;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
if (window.location.host != "translate.google.com" || window.hasRun) return;
|
||||
window.hasRun = true;
|
||||
|
||||
const conn = browser.runtime.connect(<any>{
|
||||
name: "gtranslate_scraper_conn",
|
||||
});
|
||||
|
||||
const src = <HTMLTextAreaElement>document.querySelector("textarea#source");
|
||||
|
||||
const result = <HTMLDivElement>(
|
||||
document.querySelector("div.results-container")
|
||||
);
|
||||
|
||||
const isTranslating = () => result.classList.contains("translating");
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
console.log(mutations);
|
||||
const wasTranslating = mutations.some((m) =>
|
||||
m.oldValue.split(" ").some((c) => c == "translating")
|
||||
);
|
||||
if (wasTranslating && !isTranslating()) {
|
||||
sendTranslation();
|
||||
}
|
||||
});
|
||||
observer.observe(result, {
|
||||
attributes: true,
|
||||
attributeOldValue: true,
|
||||
attributeFilter: ["class"],
|
||||
});
|
||||
|
||||
const sendTranslation = () => {
|
||||
conn.postMessage({ src: src.value, result: result.innerText.trim() });
|
||||
};
|
||||
|
||||
conn.onMessage.addListener((to_translate: string) => {
|
||||
if (src.value == to_translate && !isTranslating()) sendTranslation();
|
||||
else src.value = to_translate;
|
||||
});
|
||||
})();
|
77
src/background/gtranslate_scraper.ts
Normal file
77
src/background/gtranslate_scraper.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
import "./gtranslate_types";
|
||||
import { browser, WebRequest, Runtime } from "webextension-polyfill-ts";
|
||||
|
||||
export class GTranslateScraper {
|
||||
iframe: HTMLIFrameElement;
|
||||
conn?: Runtime.Port;
|
||||
resolveFn?: (value: TranslationResponse) => void;
|
||||
rejectFn?: (reason: String) => void;
|
||||
|
||||
constructor() {
|
||||
browser.runtime.onConnect.addListener((p) => {
|
||||
if (p.name != "gtranslate_scraper_conn") return;
|
||||
this.conn = p;
|
||||
p.onMessage.addListener((m) => this.onTranslationRecieved(m));
|
||||
});
|
||||
|
||||
//This allows us to create the google translate iframe by removing the x-frame-options header
|
||||
browser.webRequest.onHeadersReceived.addListener(
|
||||
allowIFrameAccess,
|
||||
|
||||
{
|
||||
urls: ["<all_urls>"],
|
||||
types: ["sub_frame"],
|
||||
},
|
||||
["blocking", "responseHeaders"]
|
||||
);
|
||||
let iframe = <HTMLIFrameElement>document.createElement("iframe");
|
||||
|
||||
//TODO make the language customizable
|
||||
iframe.src = "https://translate.google.com/?op=translate&sl=de&tl=en";
|
||||
this.iframe = iframe;
|
||||
|
||||
//Registers a temp content script, because we cannot inject scripts into iframes created on the background html page, because they have no tabId
|
||||
const js = {
|
||||
file: "gtranslate_scraper.bundle.js",
|
||||
};
|
||||
browser.contentScripts
|
||||
.register({
|
||||
matches: ["<all_urls>"],
|
||||
allFrames: true,
|
||||
js: [js],
|
||||
runAt: "document_end",
|
||||
})
|
||||
.then((r) => {
|
||||
iframe.addEventListener("load", r.unregister);
|
||||
document.body.appendChild(iframe);
|
||||
});
|
||||
}
|
||||
onTranslationRecieved(message: TranslationResponse) {
|
||||
this.resolveFn(message);
|
||||
this.resolveFn = null;
|
||||
this.rejectFn = null;
|
||||
}
|
||||
|
||||
translate(to_translate: string): Promise<TranslationResponse> {
|
||||
if (this.rejectFn) this.rejectFn("Got a different translation request");
|
||||
if (!this.conn) return Promise.reject("The translator is not yet ready");
|
||||
|
||||
this.conn.postMessage(to_translate);
|
||||
return new Promise((resolve, reject) => {
|
||||
this.resolveFn = resolve;
|
||||
this.rejectFn = reject;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const allowIFrameAccess = (info: WebRequest.OnHeadersReceivedDetailsType) => {
|
||||
//Make sure that only the background page can access this iframe
|
||||
if (info.documentUrl != document.URL) return;
|
||||
|
||||
let headers = info.responseHeaders;
|
||||
headers = headers.filter((header) => {
|
||||
let h = header.name.toString().toLowerCase();
|
||||
h != "x-frame-options" && h != "frame-options";
|
||||
});
|
||||
return { responseHeaders: headers };
|
||||
};
|
5
src/background/gtranslate_types.ts
Normal file
5
src/background/gtranslate_types.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
type TranslationRequest = string;
|
||||
interface TranslationResponse {
|
||||
src: string;
|
||||
result: string;
|
||||
}
|
17
src/types.d.ts
vendored
17
src/types.d.ts
vendored
|
@ -1,11 +1,12 @@
|
|||
declare module "*?file" {
|
||||
const file: string;
|
||||
export = file;
|
||||
declare module "*?raw" {
|
||||
const contents: string;
|
||||
export = contents;
|
||||
}
|
||||
|
||||
declare const enum commands {
|
||||
setEnabled = "toggleCommand",
|
||||
setEnabled = "setEnabled",
|
||||
getEnabled = "getEnabled",
|
||||
translate = "translate",
|
||||
}
|
||||
interface setEnabled {
|
||||
commandKind: commands.setEnabled;
|
||||
|
@ -15,4 +16,10 @@ interface setEnabled {
|
|||
interface getEnabled {
|
||||
commandKind: commands.getEnabled;
|
||||
}
|
||||
type command = setEnabled | getEnabled;
|
||||
|
||||
interface translate {
|
||||
commandKind: commands.translate;
|
||||
toTranslate: string;
|
||||
}
|
||||
|
||||
type command = setEnabled | getEnabled | translate;
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
"noImplicitAny": true,
|
||||
"module": "CommonJS",
|
||||
"target": "es6",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true
|
||||
|
|
|
@ -16,6 +16,12 @@ let options = {
|
|||
"content_script",
|
||||
"content_script.ts"
|
||||
),
|
||||
gtranslate_scraper: path.join(
|
||||
__dirname,
|
||||
"src",
|
||||
"background",
|
||||
"gtranslate_content_script.ts"
|
||||
),
|
||||
background: path.join(__dirname, "src", "background", "background.ts"),
|
||||
},
|
||||
output: {
|
||||
|
@ -39,7 +45,8 @@ let options = {
|
|||
oneOf: [
|
||||
{
|
||||
resourceQuery: /file/,
|
||||
use: ["ts-loader", "file-loader"],
|
||||
use: ["file-loader", "ts-loader"],
|
||||
type: "javascript/esm",
|
||||
},
|
||||
{
|
||||
use: "ts-loader",
|
||||
|
@ -51,8 +58,8 @@ let options = {
|
|||
test: /\.(c|s[ac])ss$/i,
|
||||
oneOf: [
|
||||
{
|
||||
resourceQuery: /file/,
|
||||
use: ["file-loader", "css-loader", "sass-loader"],
|
||||
resourceQuery: /raw/,
|
||||
use: ["raw-loader", "sass-loader"],
|
||||
},
|
||||
{
|
||||
use: ["style-loader", "css-loader", "sass-loader"],
|
||||
|
|
Loading…
Reference in a new issue