const emote_regex = /bttv:([^\s]+)/g; 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 { 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): Promise { 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 { const matches = [...text.matchAll(emote_regex)]; const emotes = await get_emotes(matches.map(([_, code]) => code)); for (const emote of matches.reverse()) { const [match, code] = emote; const index = emote.index ?? 0; if (!(code in emotes)) continue; text = text.substring(0, index) + `[not]${emotes[code]}[/not]` + text.substring(index + match.length); } text = text.replace(/\[\/not\]\s+\[not\]/g, "[/not][not]"); return text; } (() => { const cb_form = document.querySelector("#mgc_cb_evo_form"); if (!(cb_form instanceof HTMLFormElement)) return; const bttv_btn = (() => { const btn = document.createElement("a"); btn.style.setProperty("cursor", "pointer"); btn.style.setProperty("margin-right", "4px"); const img = document.createElement("img"); img.setAttribute("src", chrome.runtime.getURL("img/sb_button.png")); img.style.setProperty("vertical-align", "middle"); btn.addEventListener("click", async (event) => { 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(); }); btn.appendChild(img); return btn; })(); cb_form.insertBefore( bttv_btn, document.querySelector("#mgc_cb_evo_form > input[type=image]:nth-child(2)"), ); console.log("done."); })();