modularization

This commit is contained in:
Jörn-Michael Miehe 2023-12-30 14:51:19 +00:00
parent fd04f4024f
commit ed1d500c23
7 changed files with 1233 additions and 76 deletions

View file

@ -4,12 +4,15 @@
"author": "Jörn-Michael Miehe <joern-michael.miehe@lenaisten.de>", "author": "Jörn-Michael Miehe <joern-michael.miehe@lenaisten.de>",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"build": "webpack --config webpack.config.js", "build": "webpack --mode=production",
"lint-ext": "web-ext lint --source-dir=dist", "lint-ext": "web-ext lint --source-dir=dist",
"build-ext": "web-ext build --source-dir=dist --artifacts-dir=dist --overwrite-dest" "build-ext": "web-ext build --source-dir=dist --artifacts-dir=dist --overwrite-dest"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.23.7",
"@babel/preset-env": "^7.23.7",
"@types/chrome": "^0.0.254", "@types/chrome": "^0.0.254",
"babel-loader": "^9.1.3",
"copy-webpack-plugin": "^11.0.0", "copy-webpack-plugin": "^11.0.0",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"ts-loader": "^9.5.1", "ts-loader": "^9.5.1",

28
src/bttv_api.ts Normal file
View file

@ -0,0 +1,28 @@
type BTTV_Emote = {
id: string;
code: string;
imageType: string;
animated: boolean;
user: any;
};
export async function bttv_get_url(
code: string,
): Promise<[string, string] | null> {
const res = await fetch(
`https://api.betterttv.net/3/emotes/shared/search?query=${code}&offset=0&limit=50`,
);
const content = await res.json();
if (!Array.isArray(content)) return null;
const matches = (content as BTTV_Emote[]).filter(
(be) => be.code.toLowerCase() == code.toLowerCase(),
);
if (matches.length <= 0) return null;
return [
code,
`//cdn.betterttv.net/emote/${matches[0].id}/1x.${matches[0].imageType}`,
];
}

12
src/helpers.ts Normal file
View file

@ -0,0 +1,12 @@
type MapFunction<In, Out> = (input: In) => Promise<[In, Out] | null>;
export async function map_parallel<In, Out>(
inputs: Iterable<In>,
map_fn: MapFunction<In, Out>,
): Promise<Map<In, Out>> {
const queue = [...inputs].map((input) => map_fn(input));
return new Map<In, Out>(
(await Promise.all(queue)).filter((job): job is [In, Out] => job !== null),
);
}

View file

@ -1,73 +1,33 @@
import { bttv_get_url } from "./bttv_api";
import { map_parallel } from "./helpers";
const emote_regex = /bttv:([^\s]+)/gi; const emote_regex = /bttv:([^\s]+)/gi;
type Emote = {
code: string;
url?: string;
};
type BTTV_Emote = {
id: string;
code: string;
imageType: string;
animated: boolean;
user: any;
};
async function bttv_get_url(code: string): Promise<Emote> {
const res = await fetch(
`https://api.betterttv.net/3/emotes/shared/search?query=${code}&offset=0&limit=50`,
);
const content = await res.json();
if (!Array.isArray(content)) return { code: code };
const matches = (content as BTTV_Emote[]).filter(
(be) => be.code.toLowerCase() == code.toLowerCase(),
);
if (matches.length <= 0) return { code: code };
return {
code: code,
url: `//cdn.betterttv.net/emote/${matches[0].id}/1x.${matches[0].imageType}`,
};
}
type EmoteMap = { [code: string]: string };
async function get_emotes(codes: Iterable<string>): Promise<EmoteMap> {
const codes_set = new Set(codes);
// queue emote jobs
const queue = [...codes_set].map((code) => bttv_get_url(code));
console.log("[lmlfc-bttv] queued", queue.length, "jobs");
// run
const result: EmoteMap = {};
for (const job of await Promise.all(queue)) {
if (job.url) result[job.code] = job.url;
}
return result;
}
async function process_text(text: string): Promise<string> { async function process_text(text: string): Promise<string> {
const matches = [...text.matchAll(emote_regex)]; const matches = [...text.matchAll(emote_regex)];
const emotes = await get_emotes(matches.map(([_, code]) => code)); const codes = new Set(matches.map(([_, code]) => code));
console.log("[lmlfc-bttv]", `searching ${[...codes].length} emotes`);
const emotes = await map_parallel(codes, bttv_get_url);
console.debug("matches", matches, "codes", codes, "emotes", emotes);
for (const emote of matches.reverse()) { for (const emote of matches.reverse()) {
const [match, code] = emote; const [match, code] = emote;
const index = emote.index ?? 0; console.debug("match", match, "code", code);
if (!(code in emotes)) continue;
if (emote.index == undefined) continue;
const index = emote.index;
if (!emotes.has(code)) continue;
const url = emotes.get(code);
console.debug("index", index, "url", url);
text = text =
text.substring(0, index) + text.substring(0, index) +
`[not]${emotes[code]}[/not]` + `[not]${url}[/not]` +
text.substring(index + match.length); text.substring(index + match.length);
} }
text = text.replace(/\[\/not\]\s+\[not\]/g, "[/not][not]"); return text.replace(/\[\/not\]\s+\[not\]/g, "[/not][not]");
return text;
} }
(() => { (() => {
@ -79,6 +39,9 @@ async function process_text(text: string): Promise<string> {
); );
if (!(cb_send instanceof HTMLInputElement)) return; if (!(cb_send instanceof HTMLInputElement)) return;
const cb_input = document.querySelector("#mgc_cb_evo_input");
if (!(cb_input instanceof HTMLInputElement)) return;
const bttv_btn = (() => { const bttv_btn = (() => {
const btn = document.createElement("a"); const btn = document.createElement("a");
btn.style.setProperty("cursor", "pointer"); btn.style.setProperty("cursor", "pointer");
@ -91,9 +54,6 @@ async function process_text(text: string): Promise<string> {
img.style.setProperty("vertical-align", "middle"); img.style.setProperty("vertical-align", "middle");
btn.addEventListener("click", async () => { btn.addEventListener("click", async () => {
const cb_input = document.querySelector("#mgc_cb_evo_input");
if (!(cb_input instanceof HTMLInputElement)) return;
cb_input.value = await process_text(cb_input.value); cb_input.value = await process_text(cb_input.value);
cb_input.focus(); cb_input.focus();
cb_send.click(); cb_send.click();
@ -105,5 +65,5 @@ async function process_text(text: string): Promise<string> {
cb_form.insertBefore(bttv_btn, cb_send); cb_form.insertBefore(bttv_btn, cb_send);
console.log("done."); console.log("[lmlfc-bttv]", `loaded`);
})(); })();

View file

@ -1,13 +1,18 @@
{ {
"compilerOptions": { "compilerOptions": {
"strict": true, "strict": true,
"module": "commonjs", "module": "CommonJS",
"target": "es6", "target": "es6",
"esModuleInterop": true,
"sourceMap": true, "sourceMap": true,
"rootDir": "src", // flags
"outDir": "dist/js", "alwaysStrict": true,
"strictNullChecks": true,
"noEmitOnError": true, "noEmitOnError": true,
"typeRoots": ["node_modules/@types"] "noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"forceConsistentCasingInFileNames": true
} }
} }

View file

@ -2,7 +2,6 @@ const path = require("path");
const CopyPlugin = require("copy-webpack-plugin"); const CopyPlugin = require("copy-webpack-plugin");
module.exports = { module.exports = {
mode: "production",
entry: { entry: {
main: path.join(__dirname, "src", "main.ts"), main: path.join(__dirname, "src", "main.ts"),
}, },
@ -16,9 +15,19 @@ module.exports = {
module: { module: {
rules: [ rules: [
{ {
test: /\.tsx?$/, test: /\.[jt]s$/,
exclude: /(node_modules)/,
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
{
loader: "ts-loader", loader: "ts-loader",
exclude: /node_modules/, },
],
}, },
], ],
}, },

1150
yarn.lock

File diff suppressed because it is too large Load diff