From 872ca6d576e707e2279df108e875485ec07ac8ec Mon Sep 17 00:00:00 2001 From: bad Date: Thu, 21 Apr 2022 20:01:59 +0200 Subject: [PATCH] Update dependencies --- .eslintrc.js | 1 - .prettierrc.json | 3 +- package.json | 57 +- src/background/background.ts | 16 +- src/background/gtranslate_commands.ts | 76 - src/background/gtranslate_content_script.ts | 65 - src/background/gtranslate_scraper.ts | 177 - src/background/translate.ts | 42 + src/background_frontend_commands.ts | 8 +- src/communicator.ts | 4 +- src/frontend/content_script/Translated.svelte | 1 - src/frontend/options/Flashcard.svelte | 6 +- src/frontend/options/Options.svelte | 13 +- src/frontend/popup/LanguageSelection.svelte | 9 +- src/frontend/popup/Popup.svelte | 3 +- src/global.d.ts | 1 + tsconfig.json => src/tsconfig.json | 3 +- webpack.config.js | 7 + yarn.lock | 6884 ++++++----------- 19 files changed, 2356 insertions(+), 5020 deletions(-) delete mode 100644 src/background/gtranslate_commands.ts delete mode 100644 src/background/gtranslate_content_script.ts delete mode 100644 src/background/gtranslate_scraper.ts create mode 100644 src/background/translate.ts create mode 100644 src/global.d.ts rename tsconfig.json => src/tsconfig.json (67%) diff --git a/.eslintrc.js b/.eslintrc.js index 6bb1bf1..732ac10 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,7 +6,6 @@ module.exports = { extends: [ "eslint:recommended", "plugin:@typescript-eslint/recommended", - "prettier/@typescript-eslint", ], rules: { "@typescript-eslint/no-non-null-assertion": "off", diff --git a/.prettierrc.json b/.prettierrc.json index 37de190..58f451f 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,5 +1,4 @@ { "useTabs": true, - "semi": true, - "svelteSortOrder": "scripts-markup-styles" + "semi": true } diff --git a/package.json b/package.json index 39fcaef..923c80a 100644 --- a/package.json +++ b/package.json @@ -4,42 +4,44 @@ "description": "Lingo is a small browser extension to help you learn languages", "main": "webpack.config.js", "dependencies": { + "@asmagin/google-translate-api": "^8.0.2", "@sveltejs/svelte-virtual-list": "^3.0.1", - "svelte-feather-icons": "^3.2.2", - "svelte-select": "^3.11.0", - "tippy.js": "^6.2.5" + "svelte-feather-icons": "^4.0.0", + "svelte-select": "^4.4.7", + "tippy.js": "^6.2.5", + "webextension-polyfill": "^0.9.0" }, "devDependencies": { - "@tsconfig/svelte": "^1.0.8", - "@typescript-eslint/eslint-plugin": "^3.9.1", - "@typescript-eslint/parser": "^3.9.1", - "copy-webpack-plugin": "^6.0.3", - "css-loader": "^4.0.0", - "eslint": "^7.7.0", - "eslint-config-prettier": "^6.11.0", + "@tsconfig/svelte": "^3.0.0", + "@types/webextension-polyfill": "^0.8.3", + "@typescript-eslint/eslint-plugin": "^5.20.0", + "@typescript-eslint/parser": "^5.20.0", + "copy-webpack-plugin": "^10.2.4", + "css-loader": "^6.7.1", + "eslint": "^8.13.0", + "eslint-config-prettier": "^8.5.0", "file-loader": "^6.0.0", - "html-loader": "^1.1.0", - "html-webpack-plugin": "4.3.0", + "html-loader": "^3.1.0", + "html-webpack-plugin": "^5.5.0", "husky": ">=4", "lint-staged": ">=10", "prettier": "^2.0.5", - "prettier-plugin-svelte": "^1.1.0", + "prettier-plugin-svelte": "^2.7.0", "raw-loader": "^4.0.1", "sass": "^1.26.10", - "sass-loader": "^9.0.2", - "style-loader": "^1.2.1", + "sass-loader": "^12.6.0", + "style-loader": "^3.3.1", "svelte": "^3.24.1", - "svelte-check": "^1.0.11", - "svelte-loader": "^2.13.6", + "svelte-check": "^2.7.0", + "svelte-loader": "^3.1.2", "svelte-preprocess": "^4.0.10", - "ts-loader": "^8.0.1", - "typescript": "^3.9.7", - "web-ext": "^4.3.0", - "webextension-polyfill-ts": "^0.19.0", - "webpack": "^4.43.0", - "webpack-cli": "^3.3.12", - "worklet-loader": "^1.0.0", - "zip-webpack-plugin": "^3.0.0" + "ts-loader": "^9.2.8", + "typescript": "^4.6.3", + "web-ext": "^6.8.0", + "webpack": "^5.72.0", + "webpack-cli": "^4.9.2", + "worklet-loader": "^2.0.0", + "zip-webpack-plugin": "^4.0.1" }, "scripts": { "build": "webpack", @@ -48,11 +50,6 @@ "keywords": [], "author": "", "license": "ISC", - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, "lint-staged": { "*/**": [ "eslint --fix ", diff --git a/src/background/background.ts b/src/background/background.ts index bb65c86..21304c8 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -1,21 +1,17 @@ -import { browser, Runtime } from "webextension-polyfill-ts"; -import { GTranslateScraper } from "./gtranslate_scraper"; +import browser, { type Runtime } from "webextension-polyfill"; +import { GTranslate } from "./translate"; import { Flashcards } from "./database"; import { BackgroundMessenger } from "../background_frontend_commands"; import content_script from "../frontend/content_script/content_script.ts?file"; const con = new BackgroundMessenger(); -const scraper = new GTranslateScraper(); +const scraper = new GTranslate(); con.addMessageListener("translate", async (toTranslate, sender) => { - return scraper.translate( - toTranslate, - await getCurrentLanguages(), - sender!.tab?.id - ); + return scraper.translate(toTranslate, await getCurrentLanguages()); }); -con.addMessageListener("getLanguages", () => scraper.languages); +con.addMessageListener("getLanguages", () => scraper.get_languages()); const db = new Flashcards(); window.flashcardDB = db; @@ -23,7 +19,7 @@ con.addMessageListener("addFlashcard", (c) => db.addFlashcard(c)); con.addMessageListener("removeFlashcard", (c) => db.removeFlashcard(c)); const getCurrentLanguages = async () => { - const langs = await scraper.languages; + const langs = await scraper.get_languages(); const srcLangCode = (await browser.storage.local.get("srcLang"))["srcLang"] ?? "de"; const dstLangCode = diff --git a/src/background/gtranslate_commands.ts b/src/background/gtranslate_commands.ts deleted file mode 100644 index ae5753e..0000000 --- a/src/background/gtranslate_commands.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { browser, Runtime } from "webextension-polyfill-ts"; -import { Communicator, commandList, commandFunction } from "../communicator"; -import { newPromise } from "../utils"; - -interface BackgroundCommands extends commandList { - translationFinished: TranslationMessage; - languageList: LanguageListMessage; -} - -interface ContentScriptCommands extends commandList { - translationRequest: TranslationRequest; -} - -interface TranslationMessage extends commandFunction { - args: Translation; -} - -interface LanguageListMessage extends commandFunction { - args: Array; -} - -interface TranslationRequest extends commandFunction { - args: string; -} - -export class BackgroundMessenger extends Communicator< - BackgroundCommands, - ContentScriptCommands -> { - conn: Promise; - private resolvConn: (p: Runtime.Port) => void; - constructor() { - super(); - - [this.conn, this.resolvConn] = newPromise(); - - browser.runtime.onConnect.addListener((p) => { - if (p.name != "gtranslate_scraper_conn") return; - this.resolvConn(p); - p.onDisconnect.addListener( - () => ([this.conn, this.resolvConn] = newPromise()) - ); - p.onMessage.addListener((m) => this.onMessage(m, p.sender!)); - }); - } - - async runCommand( - command: K, - args: ContentScriptCommands[K]["args"] - ) { - const msg = this.getCommandMessage(command, args); - (await this.conn).postMessage(msg); - } -} - -export class ContentScriptMessenger extends Communicator< - ContentScriptCommands, - BackgroundCommands -> { - conn: Runtime.Port; - constructor() { - super(); - this.conn = browser.runtime.connect({ - name: "gtranslate_scraper_conn", - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any); //The declaration file is wrong - this.conn.onMessage.addListener((m) => this.onMessage(m)); - } - async runCommand( - command: K, - args: BackgroundCommands[K]["args"] - ) { - const msg = this.getCommandMessage(command, args); - return this.conn.postMessage(msg); - } -} diff --git a/src/background/gtranslate_content_script.ts b/src/background/gtranslate_content_script.ts deleted file mode 100644 index b2836bb..0000000 --- a/src/background/gtranslate_content_script.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { ContentScriptMessenger } from "./gtranslate_commands"; - -(async () => { - if (window.location.host != "translate.google.com" || window.hasRun) return; - window.hasRun = true; - - const conn = new ContentScriptMessenger(); - - const src = document.querySelector("textarea#source"); - const result = ( - document.querySelector("div.results-container") - ); - - const [srcLang, dstLang] = Array.from( - document.querySelectorAll(".jfk-button-checked") - ).map((d) => ({ - code: d.getAttribute("value")!, - name: d.textContent!, - })); - - const isTranslating = () => result.classList.contains("translating"); - - const observer = new MutationObserver((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.runCommand("translationFinished", { - src: src.value, - result: result.innerText.trim(), - languages: { - srcLang: srcLang, - dstLang: dstLang, - }, - }); - }; - - conn.addMessageListener("translate", (toTranslate) => { - if (src.value == toTranslate && !isTranslating()) sendTranslation(); - else src.value = toTranslate; - }); - - const getLanguages = () => { - const langs = document.querySelectorAll( - "div.language_list_section:nth-child(3) > .language_list_item_wrapper" - ); - return Array.from(langs).map((l) => { - const code = l.classList[1].split("-").pop(); - const name = l.querySelector(".language_list_item_language_name")! - .textContent; - return { code: code!, name: name! }; - }); - }; - conn.runCommand("languageList", getLanguages()); -})(); diff --git a/src/background/gtranslate_scraper.ts b/src/background/gtranslate_scraper.ts deleted file mode 100644 index f447750..0000000 --- a/src/background/gtranslate_scraper.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { browser, WebRequest } from "webextension-polyfill-ts"; -import content_script from "./gtranslate_content_script.ts?file"; -import { BackgroundMessenger } from "./gtranslate_commands"; -import { newPromise } from "../utils"; - -interface TranslationRequest { - tabID?: number; - resolveFn: (value: Translation) => void; - rejectFn: (reason: string) => void; - toTranslate: string; - lang: LanguagePair; -} - -export class GTranslateScraper { - private queue: Array = []; - private current?: TranslationRequest; - private iframe = document.createElement("iframe"); - private current_lang: LanguagePair | undefined; - - languages: Promise>; - msg: BackgroundMessenger; - - constructor() { - let languagesResolve: (value: Array) => void; - [this.languages, languagesResolve] = newPromise>(); - - this.msg = new BackgroundMessenger(); - this.msg.addMessageListener("languageList", languagesResolve); - this.msg.addMessageListener("translationFinished", (trans) => - this.onTranslationRecieved(trans) - ); - - //This allows us to create the google translate iframe by removing the x-frame-options header - browser.webRequest.onHeadersReceived.addListener( - allowIFrameAccess, - { - urls: ["https://translate.google.com/?*"], - types: ["sub_frame"], - }, - ["blocking", "responseHeaders"] - ); - - this.ChangeLanguage("de", "en"); //Sample languages so we can get the language list - } - - private async ChangeLanguage( - srcLangCode: string, - dstLangCode: string - ): Promise { - if (!document.body.contains(this.iframe)) - document.body.appendChild(this.iframe); - - const [connectedPromise, onConnected] = newPromise(); - - //Registers a temp content script, because we cannot inject scripts into iframes created on the background html page, because they have no tabId - browser.contentScripts - .register({ - matches: ["https://translate.google.com/?*"], - allFrames: true, - js: [ - { - file: content_script, - }, - ], - runAt: "document_end", - }) - .then((r) => { - this.iframe.addEventListener( - "load", - () => { - r.unregister; - this.msg.conn.then(() => onConnected()); - }, - { once: true } - ); - this.iframe.src = `https://translate.google.com/?op=translate&sl=${srcLangCode}&tl=${dstLangCode}`; - }); - - return connectedPromise; - } - - private onTranslationRecieved(trans: Translation) { - if (this.current?.toTranslate !== trans.src) - //I don't know how to code well so I gotta put in sanity checks like this to make debugging easier - console.error( - "Current request changed before OnTranslationRecieved was called. This should never happen" - ); - if (this.current) this.current.resolveFn(trans); - this.current = undefined; - this.processFromQueue(); - } - - private async processFromQueue() { - if (this.current) return; - this.current = this.queue.pop(); - if (!this.current) return; - - this.processCurrent(); - } - - private async processCurrent() { - if (!this.current) - throw new Error( - "ProcessCurrent called while current translation request is undefined" - ); - const cur = this.current; - - let promise = Promise.resolve(); - if (this.current_lang != this.current.lang) { - this.current_lang = cur.lang; - - promise = this.ChangeLanguage( - this.current_lang.srcLang.code, - this.current_lang.dstLang.code - ); - } - await promise; - if (cur == this.current) this.msg.runCommand("translate", cur.toTranslate); - else - throw new Error( - "The current request changed while processCurrent was running. This should never happen" - ); - } - - async translate( - toTranslate: string, - langs: LanguagePair, - tabID?: number - ): Promise { - toTranslate = toTranslate.trim(); - if (toTranslate == "") - return Promise.resolve({ - src: "", - result: "", - languages: langs, - }); - - const [promise, resolve, reject] = newPromise(); - - const req = { - toTranslate: toTranslate, - resolveFn: resolve, - rejectFn: reject, - tabID: tabID, - lang: langs, - }; - //Remove the requests from the same tab - if (tabID) - this.queue = this.queue.filter((r) => { - if (r.tabID !== tabID) { - r.rejectFn("Got another request from the same tab"); - return true; - } - }); - - this.queue.push(req); - - if (this.queue.length == 1 && !this.current) { - this.current = this.queue.pop(); - await this.msg.conn; - this.processCurrent(); - } - return promise; - } -} - -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) => { - const h = header.name.toString().toLowerCase(); - h != "x-frame-options" && h != "frame-options"; - }); - return { responseHeaders: headers }; -}; diff --git a/src/background/translate.ts b/src/background/translate.ts new file mode 100644 index 0000000..3e76c8f --- /dev/null +++ b/src/background/translate.ts @@ -0,0 +1,42 @@ +import { newPromise } from "../utils"; +import googleTranslateApi from "@asmagin/google-translate-api"; +import translate from "@asmagin/google-translate-api"; + +const languages = googleTranslateApi.languages; + +export abstract class Translator { + abstract translate( + toTranslate: string, + langs: LanguagePair + ): Promise; + + abstract get_languages(): Promise; +} + +export class GTranslate extends Translator { + async translate( + toTranslate: string, + langs: LanguagePair + ): Promise { + const resp = await translate(toTranslate, { + from: langs.srcLang.code, + to: langs.dstLang.code, + }); + return { + src: resp.from.text.value, + languages: langs, + result: resp.text, + }; + } + + async get_languages() { + const res: Language[] = Object.keys(languages).map((k) => { + const v = (languages as any)[k as any] as string; + return { + code: k, + name: v, + }; + }); + return Promise.resolve(res); + } +} diff --git a/src/background_frontend_commands.ts b/src/background_frontend_commands.ts index c3e9056..7b1ab2e 100644 --- a/src/background_frontend_commands.ts +++ b/src/background_frontend_commands.ts @@ -1,5 +1,9 @@ -import { Communicator, commandFunction, commandList } from "./communicator"; -import { browser } from "webextension-polyfill-ts"; +import { + Communicator, + type commandFunction, + type commandList, +} from "./communicator"; +import browser from "webextension-polyfill"; interface BackgroundCommands extends commandList { setEnabled: setEnabled; diff --git a/src/communicator.ts b/src/communicator.ts index 6326212..0c642df 100644 --- a/src/communicator.ts +++ b/src/communicator.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { Runtime } from "webextension-polyfill-ts"; +import type { Runtime } from "webextension-polyfill"; export interface commandFunction { args: any; @@ -29,7 +29,7 @@ export abstract class Communicator< listener >(); - abstract async runCommand( + abstract runCommand( command: K, args: SendCommands[K]["args"], ...rest: unknown[] diff --git a/src/frontend/content_script/Translated.svelte b/src/frontend/content_script/Translated.svelte index ed1a730..87e8d25 100644 --- a/src/frontend/content_script/Translated.svelte +++ b/src/frontend/content_script/Translated.svelte @@ -33,7 +33,6 @@ card {/if} -