Add gtranslate script

This commit is contained in:
a 2020-08-04 00:38:41 +02:00
parent b549d619e7
commit 41f0a7e01d
7 changed files with 156 additions and 11 deletions

View file

@ -2,6 +2,10 @@ import { browser, Runtime, Tabs } from "webextension-polyfill-ts";
import { GTranslateScraper } from "./gtranslate_scraper"; import { GTranslateScraper } from "./gtranslate_scraper";
console.log("Background script loaded"); console.log("Background script loaded");
const scraper = new GTranslateScraper();
console.log(scraper);
const getTab = async (s: Runtime.MessageSender): Promise<Tabs.Tab> => { const getTab = async (s: Runtime.MessageSender): Promise<Tabs.Tab> => {
if (s.tab) return s.tab; if (s.tab) return s.tab;
let tabs = await browser.tabs.query({ let tabs = await browser.tabs.query({
@ -47,6 +51,8 @@ browser.runtime.onMessage.addListener(
return setEnabledCommand(c.value, s); return setEnabledCommand(c.value, s);
case commands.getEnabled: case commands.getEnabled:
return getEnabledCommand(s); return getEnabledCommand(s);
case commands.translate:
return scraper.translate(c.toTranslate);
} }
} }
); );
@ -58,5 +64,3 @@ browser.tabs.onUpdated.addListener(async (tabID, changeInfo) => {
} }
} }
}); });
new GTranslateScraper();

View 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;
});
})();

View 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 };
};

View file

@ -0,0 +1,5 @@
type TranslationRequest = string;
interface TranslationResponse {
src: string;
result: string;
}

17
src/types.d.ts vendored
View file

@ -1,11 +1,12 @@
declare module "*?file" { declare module "*?raw" {
const file: string; const contents: string;
export = file; export = contents;
} }
declare const enum commands { declare const enum commands {
setEnabled = "toggleCommand", setEnabled = "setEnabled",
getEnabled = "getEnabled", getEnabled = "getEnabled",
translate = "translate",
} }
interface setEnabled { interface setEnabled {
commandKind: commands.setEnabled; commandKind: commands.setEnabled;
@ -15,4 +16,10 @@ interface setEnabled {
interface getEnabled { interface getEnabled {
commandKind: commands.getEnabled; commandKind: commands.getEnabled;
} }
type command = setEnabled | getEnabled;
interface translate {
commandKind: commands.translate;
toTranslate: string;
}
type command = setEnabled | getEnabled | translate;

View file

@ -4,7 +4,6 @@
"noImplicitAny": true, "noImplicitAny": true,
"module": "CommonJS", "module": "CommonJS",
"target": "es6", "target": "es6",
"jsx": "react",
"allowJs": true, "allowJs": true,
"sourceMap": true, "sourceMap": true,
"esModuleInterop": true "esModuleInterop": true

View file

@ -16,6 +16,12 @@ let options = {
"content_script", "content_script",
"content_script.ts" "content_script.ts"
), ),
gtranslate_scraper: path.join(
__dirname,
"src",
"background",
"gtranslate_content_script.ts"
),
background: path.join(__dirname, "src", "background", "background.ts"), background: path.join(__dirname, "src", "background", "background.ts"),
}, },
output: { output: {
@ -39,7 +45,8 @@ let options = {
oneOf: [ oneOf: [
{ {
resourceQuery: /file/, resourceQuery: /file/,
use: ["ts-loader", "file-loader"], use: ["file-loader", "ts-loader"],
type: "javascript/esm",
}, },
{ {
use: "ts-loader", use: "ts-loader",
@ -51,8 +58,8 @@ let options = {
test: /\.(c|s[ac])ss$/i, test: /\.(c|s[ac])ss$/i,
oneOf: [ oneOf: [
{ {
resourceQuery: /file/, resourceQuery: /raw/,
use: ["file-loader", "css-loader", "sass-loader"], use: ["raw-loader", "sass-loader"],
}, },
{ {
use: ["style-loader", "css-loader", "sass-loader"], use: ["style-loader", "css-loader", "sass-loader"],