modularization
This commit is contained in:
		
							parent
							
								
									fd04f4024f
								
							
						
					
					
						commit
						ed1d500c23
					
				
					 7 changed files with 1233 additions and 76 deletions
				
			
		| 
						 | 
				
			
			@ -4,12 +4,15 @@
 | 
			
		|||
  "author": "Jörn-Michael Miehe <joern-michael.miehe@lenaisten.de>",
 | 
			
		||||
  "license": "MIT",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "build": "webpack --config webpack.config.js",
 | 
			
		||||
    "build": "webpack --mode=production",
 | 
			
		||||
    "lint-ext": "web-ext lint --source-dir=dist",
 | 
			
		||||
    "build-ext": "web-ext build --source-dir=dist --artifacts-dir=dist --overwrite-dest"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@babel/core": "^7.23.7",
 | 
			
		||||
    "@babel/preset-env": "^7.23.7",
 | 
			
		||||
    "@types/chrome": "^0.0.254",
 | 
			
		||||
    "babel-loader": "^9.1.3",
 | 
			
		||||
    "copy-webpack-plugin": "^11.0.0",
 | 
			
		||||
    "prettier": "^3.1.1",
 | 
			
		||||
    "ts-loader": "^9.5.1",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										28
									
								
								src/bttv_api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/bttv_api.ts
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										12
									
								
								src/helpers.ts
									
									
									
									
									
										Normal 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),
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										82
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										82
									
								
								src/main.ts
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -1,73 +1,33 @@
 | 
			
		|||
import { bttv_get_url } from "./bttv_api";
 | 
			
		||||
import { map_parallel } from "./helpers";
 | 
			
		||||
 | 
			
		||||
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> {
 | 
			
		||||
  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()) {
 | 
			
		||||
    const [match, code] = emote;
 | 
			
		||||
    const index = emote.index ?? 0;
 | 
			
		||||
    if (!(code in emotes)) continue;
 | 
			
		||||
    console.debug("match", match, "code", code);
 | 
			
		||||
 | 
			
		||||
    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.substring(0, index) +
 | 
			
		||||
      `[not]${emotes[code]}[/not]` +
 | 
			
		||||
      `[not]${url}[/not]` +
 | 
			
		||||
      text.substring(index + match.length);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  text = text.replace(/\[\/not\]\s+\[not\]/g, "[/not][not]");
 | 
			
		||||
 | 
			
		||||
  return text;
 | 
			
		||||
  return text.replace(/\[\/not\]\s+\[not\]/g, "[/not][not]");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
(() => {
 | 
			
		||||
| 
						 | 
				
			
			@ -79,6 +39,9 @@ async function process_text(text: string): Promise<string> {
 | 
			
		|||
  );
 | 
			
		||||
  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 btn = document.createElement("a");
 | 
			
		||||
    btn.style.setProperty("cursor", "pointer");
 | 
			
		||||
| 
						 | 
				
			
			@ -91,9 +54,6 @@ async function process_text(text: string): Promise<string> {
 | 
			
		|||
    img.style.setProperty("vertical-align", "middle");
 | 
			
		||||
 | 
			
		||||
    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.focus();
 | 
			
		||||
      cb_send.click();
 | 
			
		||||
| 
						 | 
				
			
			@ -105,5 +65,5 @@ async function process_text(text: string): Promise<string> {
 | 
			
		|||
 | 
			
		||||
  cb_form.insertBefore(bttv_btn, cb_send);
 | 
			
		||||
 | 
			
		||||
  console.log("done.");
 | 
			
		||||
  console.log("[lmlfc-bttv]", `loaded`);
 | 
			
		||||
})();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,13 +1,18 @@
 | 
			
		|||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "module": "commonjs",
 | 
			
		||||
    "module": "CommonJS",
 | 
			
		||||
    "target": "es6",
 | 
			
		||||
    "esModuleInterop": true,
 | 
			
		||||
    "sourceMap": true,
 | 
			
		||||
    "rootDir": "src",
 | 
			
		||||
    "outDir": "dist/js",
 | 
			
		||||
    // flags
 | 
			
		||||
    "alwaysStrict": true,
 | 
			
		||||
    "strictNullChecks": true,
 | 
			
		||||
    "noEmitOnError": true,
 | 
			
		||||
    "typeRoots": ["node_modules/@types"]
 | 
			
		||||
    "noFallthroughCasesInSwitch": true,
 | 
			
		||||
    "noImplicitAny": true,
 | 
			
		||||
    "noImplicitThis": true,
 | 
			
		||||
    "noImplicitReturns": true,
 | 
			
		||||
    "noUnusedLocals": true,
 | 
			
		||||
    "forceConsistentCasingInFileNames": true
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,6 @@ const path = require("path");
 | 
			
		|||
const CopyPlugin = require("copy-webpack-plugin");
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  mode: "production",
 | 
			
		||||
  entry: {
 | 
			
		||||
    main: path.join(__dirname, "src", "main.ts"),
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			@ -16,9 +15,19 @@ module.exports = {
 | 
			
		|||
  module: {
 | 
			
		||||
    rules: [
 | 
			
		||||
      {
 | 
			
		||||
        test: /\.tsx?$/,
 | 
			
		||||
        loader: "ts-loader",
 | 
			
		||||
        exclude: /node_modules/,
 | 
			
		||||
        test: /\.[jt]s$/,
 | 
			
		||||
        exclude: /(node_modules)/,
 | 
			
		||||
        use: [
 | 
			
		||||
          {
 | 
			
		||||
            loader: "babel-loader",
 | 
			
		||||
            options: {
 | 
			
		||||
              presets: ["@babel/preset-env"],
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            loader: "ts-loader",
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue