UNPKG

to-userscript

Version:

Converts simple browser extensions to userscripts

1,286 lines (1,282 loc) 57 kB
var __create = Object.create; var __getProtoOf = Object.getPrototypeOf; var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __toESM = (mod, isNodeMode, target) => { target = mod != null ? __create(__getProtoOf(mod)) : {}; const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target; for (let key of __getOwnPropNames(mod)) if (!__hasOwnProp.call(to, key)) __defProp(to, key, { get: () => mod[key], enumerable: true }); return to; }; var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports); var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res); // scratch-addons-1.42.0/libraries/thirdparty/cs/comlink.js var require_comlink = __commonJS((exports, module) => { (function(global, factory) { typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.Comlink = {})); })(exports, function(exports2) { const proxyMarker = Symbol("Comlink.proxy"); const createEndpoint = Symbol("Comlink.endpoint"); const releaseProxy = Symbol("Comlink.releaseProxy"); const finalizer = Symbol("Comlink.finalizer"); const throwMarker = Symbol("Comlink.thrown"); const isObject = (val) => typeof val === "object" && val !== null || typeof val === "function"; const proxyTransferHandler = { canHandle: (val) => isObject(val) && val[proxyMarker], serialize(obj) { const { port1, port2 } = new MessageChannel; expose(obj, port1); return [port2, [port2]]; }, deserialize(port) { port.start(); return wrap(port); } }; const throwTransferHandler = { canHandle: (value) => isObject(value) && (throwMarker in value), serialize({ value }) { let serialized; if (value instanceof Error) { serialized = { isError: true, value: { message: value.message, name: value.name, stack: value.stack } }; } else { serialized = { isError: false, value }; } return [serialized, []]; }, deserialize(serialized) { if (serialized.isError) { throw Object.assign(new Error(serialized.value.message), serialized.value); } throw serialized.value; } }; const transferHandlers = new Map([ ["proxy", proxyTransferHandler], ["throw", throwTransferHandler] ]); function isAllowedOrigin(allowedOrigins, origin) { for (const allowedOrigin of allowedOrigins) { if (origin === allowedOrigin || allowedOrigin === "*") { return true; } if (allowedOrigin instanceof RegExp && allowedOrigin.test(origin)) { return true; } } return false; } function expose(obj, ep = globalThis, allowedOrigins = ["*"]) { ep.addEventListener("message", function callback(ev) { if (!ev || !ev.data) { return; } if (!isAllowedOrigin(allowedOrigins, ev.origin)) { console.warn(`Invalid origin '${ev.origin}' for comlink proxy`); return; } const { id, type, path } = Object.assign({ path: [] }, ev.data); const argumentList = (ev.data.argumentList || []).map(fromWireValue); let returnValue; try { const parent = path.slice(0, -1).reduce((obj2, prop) => obj2[prop], obj); const rawValue = path.reduce((obj2, prop) => obj2[prop], obj); switch (type) { case "GET": { returnValue = rawValue; } break; case "SET": { parent[path.slice(-1)[0]] = fromWireValue(ev.data.value); returnValue = true; } break; case "APPLY": { returnValue = rawValue.apply(parent, argumentList); } break; case "CONSTRUCT": { const value = new rawValue(...argumentList); returnValue = proxy(value); } break; case "ENDPOINT": { const { port1, port2 } = new MessageChannel; expose(obj, port2); returnValue = transfer(port1, [port1]); } break; case "RELEASE": { returnValue = undefined; } break; default: return; } } catch (value) { returnValue = { value, [throwMarker]: 0 }; } Promise.resolve(returnValue).catch((value) => { return { value, [throwMarker]: 0 }; }).then((returnValue2) => { const [wireValue, transferables] = toWireValue(returnValue2); ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables); if (type === "RELEASE") { ep.removeEventListener("message", callback); closeEndPoint(ep); if (finalizer in obj && typeof obj[finalizer] === "function") { obj[finalizer](); } } }).catch((error) => { const [wireValue, transferables] = toWireValue({ value: new TypeError("Unserializable return value"), [throwMarker]: 0 }); ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables); }); }); if (ep.start) { ep.start(); } } function isMessagePort(endpoint) { return endpoint.constructor.name === "MessagePort"; } function closeEndPoint(endpoint) { if (isMessagePort(endpoint)) endpoint.close(); } function wrap(ep, target) { return createProxy(ep, [], target); } function throwIfProxyReleased(isReleased) { if (isReleased) { throw new Error("Proxy has been released and is not useable"); } } function releaseEndpoint(ep) { return requestResponseMessage(ep, { type: "RELEASE" }).then(() => { closeEndPoint(ep); }); } const proxyCounter = new WeakMap; const proxyFinalizers = "FinalizationRegistry" in globalThis && new FinalizationRegistry((ep) => { const newCount = (proxyCounter.get(ep) || 0) - 1; proxyCounter.set(ep, newCount); if (newCount === 0) { releaseEndpoint(ep); } }); function registerProxy(proxy2, ep) { const newCount = (proxyCounter.get(ep) || 0) + 1; proxyCounter.set(ep, newCount); if (proxyFinalizers) { proxyFinalizers.register(proxy2, ep, proxy2); } } function unregisterProxy(proxy2) { if (proxyFinalizers) { proxyFinalizers.unregister(proxy2); } } function createProxy(ep, path = [], target = function() { }) { let isProxyReleased = false; const proxy2 = new Proxy(target, { get(_target, prop) { throwIfProxyReleased(isProxyReleased); if (prop === releaseProxy) { return () => { unregisterProxy(proxy2); releaseEndpoint(ep); isProxyReleased = true; }; } if (prop === "then") { if (path.length === 0) { return { then: () => proxy2 }; } const r = requestResponseMessage(ep, { type: "GET", path: path.map((p) => p.toString()) }).then(fromWireValue); return r.then.bind(r); } return createProxy(ep, [...path, prop]); }, set(_target, prop, rawValue) { throwIfProxyReleased(isProxyReleased); const [value, transferables] = toWireValue(rawValue); return requestResponseMessage(ep, { type: "SET", path: [...path, prop].map((p) => p.toString()), value }, transferables).then(fromWireValue); }, apply(_target, _thisArg, rawArgumentList) { throwIfProxyReleased(isProxyReleased); const last = path[path.length - 1]; if (last === createEndpoint) { return requestResponseMessage(ep, { type: "ENDPOINT" }).then(fromWireValue); } if (last === "bind") { return createProxy(ep, path.slice(0, -1)); } const [argumentList, transferables] = processArguments(rawArgumentList); return requestResponseMessage(ep, { type: "APPLY", path: path.map((p) => p.toString()), argumentList }, transferables).then(fromWireValue); }, construct(_target, rawArgumentList) { throwIfProxyReleased(isProxyReleased); const [argumentList, transferables] = processArguments(rawArgumentList); return requestResponseMessage(ep, { type: "CONSTRUCT", path: path.map((p) => p.toString()), argumentList }, transferables).then(fromWireValue); } }); registerProxy(proxy2, ep); return proxy2; } function myFlat(arr) { return Array.prototype.concat.apply([], arr); } function processArguments(argumentList) { const processed = argumentList.map(toWireValue); return [processed.map((v) => v[0]), myFlat(processed.map((v) => v[1]))]; } const transferCache = new WeakMap; function transfer(obj, transfers) { transferCache.set(obj, transfers); return obj; } function proxy(obj) { return Object.assign(obj, { [proxyMarker]: true }); } function windowEndpoint(w, context = globalThis, targetOrigin = "*") { return { postMessage: (msg, transferables) => w.postMessage(msg, targetOrigin, transferables), addEventListener: context.addEventListener.bind(context), removeEventListener: context.removeEventListener.bind(context) }; } function toWireValue(value) { for (const [name, handler] of transferHandlers) { if (handler.canHandle(value)) { const [serializedValue, transferables] = handler.serialize(value); return [ { type: "HANDLER", name, value: serializedValue }, transferables ]; } } return [ { type: "RAW", value }, transferCache.get(value) || [] ]; } function fromWireValue(value) { switch (value.type) { case "HANDLER": return transferHandlers.get(value.name).deserialize(value.value); case "RAW": return value.value; } } function requestResponseMessage(ep, msg, transfers) { return new Promise((resolve) => { const id = generateUUID(); ep.addEventListener("message", function l(ev) { if (!ev.data || !ev.data.id || ev.data.id !== id) { return; } ep.removeEventListener("message", l); resolve(ev.data); }); if (ep.start) { ep.start(); } ep.postMessage(Object.assign({ id }, msg), transfers); }); } function generateUUID() { return new Array(4).fill(0).map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16)).join("-"); } exports2.createEndpoint = createEndpoint; exports2.expose = expose; exports2.finalizer = finalizer; exports2.proxy = proxy; exports2.proxyMarker = proxyMarker; exports2.releaseProxy = releaseProxy; exports2.transfer = transfer; exports2.transferHandlers = transferHandlers; exports2.windowEndpoint = windowEndpoint; exports2.wrap = wrap; }); }); // scratch-addons-1.42.0/libraries/common/cs/text-color.js var exports_text_color = {}; function parseHex(hex) { return { r: parseInt(hex.substring(1, 3), 16), g: parseInt(hex.substring(3, 5), 16), b: parseInt(hex.substring(5, 7), 16), a: hex.length >= 9 ? parseInt(hex.substring(7, 9), 16) / 255 : 1 }; } function convertComponentToHex(a) { a = Math.round(a).toString(16); if (a.length === 1) return `0${a}`; return a; } function convertToHex(obj) { const r = convertComponentToHex(obj.r); const g = convertComponentToHex(obj.g); const b = convertComponentToHex(obj.b); const a = obj.a !== undefined ? convertComponentToHex(255 * obj.a) : ""; return `#${r}${g}${b}${a}`; } function convertFromHsv({ h, s, v }) { if (s === 0) return { r: 255 * v, g: 255 * v, b: 255 * v }; h %= 360; if (h < 0) h += 360; const h1 = h / 60; const hi = Math.floor(h1); const x = v * (1 - s * (1 - h1 + hi)); const y = v * (1 - s * (h1 - hi)); const z = v * (1 - s); switch (hi) { case 0: return { r: 255 * v, g: 255 * x, b: 255 * z }; case 1: return { r: 255 * y, g: 255 * v, b: 255 * z }; case 2: return { r: 255 * z, g: 255 * v, b: 255 * x }; case 3: return { r: 255 * z, g: 255 * y, b: 255 * v }; case 4: return { r: 255 * x, g: 255 * z, b: 255 * v }; case 5: return { r: 255 * v, g: 255 * z, b: 255 * y }; } } function convertToHsv({ r, g, b }) { r /= 255; g /= 255; b /= 255; const v = Math.max(r, g, b); const d = v - Math.min(r, g, b); if (d === 0) return { h: 0, s: 0, v }; const s = d / v; const hr = (v - r) / d; const hg = (v - g) / d; const hb = (v - b) / d; let h1; if (!hr) h1 = hb - hg; else if (!hg) h1 = 2 + hr - hb; else if (!hb) h1 = 4 + hg - hr; const h = 60 * h1 % 360; return { h, s, v }; } function brightness(hex) { const { r, g, b } = parseHex(hex); return r * 0.299 + g * 0.587 + b * 0.114; } function textColor(hex, black, white, threshold) { threshold = threshold !== undefined ? threshold : 170; if (typeof threshold !== "number") threshold = brightness(threshold); if (brightness(hex) > threshold) { return black !== undefined ? black : "#575e75"; } else { return white !== undefined ? white : "#ffffff"; } } function multiply(hex, c) { const { r, g, b, a } = parseHex(hex); if (c.r === undefined) c.r = 1; if (c.g === undefined) c.g = 1; if (c.b === undefined) c.b = 1; if (c.a === undefined) c.a = 1; return convertToHex({ r: c.r * r, g: c.g * g, b: c.b * b, a: c.a * a }); } function brighten(hex, c) { const { r, g, b, a } = parseHex(hex); if (c.r === undefined) c.r = 1; if (c.g === undefined) c.g = 1; if (c.b === undefined) c.b = 1; if (c.a === undefined) c.a = 1; return convertToHex({ r: (1 - c.r) * 255 + c.r * r, g: (1 - c.g) * 255 + c.g * g, b: (1 - c.b) * 255 + c.b * b, a: a ? 1 - c.a + c.a * a : 0 }); } function alphaBlend(opaqueHex, transparentHex) { const { r: r1, g: g1, b: b1 } = parseHex(opaqueHex); const { r: r2, g: g2, b: b2, a } = parseHex(transparentHex); return convertToHex({ r: (1 - a) * r1 + a * r2, g: (1 - a) * g1 + a * g2, b: (1 - a) * b1 + a * b2 }); } function removeAlpha(hex) { return hex.substring(0, 7); } function makeHsv(hSource, sSource, vSource) { const h = typeof hSource === "number" ? hSource : convertToHsv(parseHex(hSource)).h; const s = typeof hSource !== "number" && convertToHsv(parseHex(hSource)).s === 0 ? 0 : typeof sSource === "number" ? sSource : convertToHsv(parseHex(sSource)).s; const v = typeof vSource === "number" ? vSource : convertToHsv(parseHex(vSource)).v; return convertToHex(convertFromHsv({ h, s, v })); } function recolorFilter(hex) { const { r, g, b } = parseHex(hex); return `url("data:image/svg+xml, <svg xmlns='http://www.w3.org/2000/svg'> <filter id='recolor'> <feColorMatrix color-interpolation-filters='sRGB' values=' 0 0 0 0 ${r / 255} 0 0 0 0 ${g / 255} 0 0 0 0 ${b / 255} 0 0 0 1 0 '/> </filter> </svg>#recolor ")`.split(` `).join(""); } var init_text_color = __esm(() => { globalThis.__scratchAddonsTextColor = { parseHex, convertToHex, convertFromHsv, convertToHsv, brightness, textColor, multiply, brighten, alphaBlend, removeAlpha, makeHsv, recolorFilter }; }); // scratch-addons-1.42.0/content-scripts/cs.js var exports_cs = {}; function addStyle(addon) { const allStyles = [...document.querySelectorAll(".scratch-addons-style")]; const addonStyles = allStyles.filter((el) => el.getAttribute("data-addon-id") === addon.addonId); const appendByIndex = (el, index) => { const nextElement = allStyles.find((el2) => Number(el2.getAttribute("data-addon-index") > index)); if (nextElement) document.documentElement.insertBefore(el, nextElement); else { if (document.body) document.documentElement.insertBefore(el, document.body); else document.documentElement.appendChild(el); } }; if (addon.styles.length > MAX_USERSTYLES_PER_ADDON) { console2.warn("Please increase MAX_USERSTYLES_PER_ADDON in content-scripts/cs.js, otherwise style priority is not guaranteed! Has", addon.styles.length, "styles, current max is", MAX_USERSTYLES_PER_ADDON); } for (let i = 0;i < addon.styles.length; i++) { const userstyle = addon.styles[i]; const styleIndex = addon.index * MAX_USERSTYLES_PER_ADDON + userstyle.index; if (addon.injectAsStyleElt) { const existingEl = addonStyles.find((style2) => style2.dataset.styleHref === userstyle.href); if (existingEl) { existingEl.disabled = false; continue; } const style = document.createElement("style"); style.classList.add("scratch-addons-style"); style.setAttribute("data-addon-id", addon.addonId); style.setAttribute("data-addon-index", styleIndex); style.setAttribute("data-style-href", userstyle.href); style.textContent = userstyle.text; appendByIndex(style, styleIndex); } else { const existingEl = addonStyles.find((style) => style.href === userstyle.href); if (existingEl) { existingEl.disabled = false; continue; } const link = document.createElement("link"); link.rel = "stylesheet"; link.setAttribute("data-addon-id", addon.addonId); link.setAttribute("data-addon-index", styleIndex); link.classList.add("scratch-addons-style"); link.href = userstyle.href; appendByIndex(link, styleIndex); } } } function removeAddonStyles(addonId) { document.querySelectorAll(`[data-addon-id='${addonId}']`).forEach((style) => style.disabled = true); } function removeAddonStylesPartial(addonId, stylesToRemove) { document.querySelectorAll(`[data-addon-id='${addonId}']`).forEach((style) => { if (stylesToRemove.includes(style.href || style.dataset.styleHref)) style.disabled = true; }); } function injectUserstyles(addonsWithUserstyles) { for (const addon of addonsWithUserstyles || []) { addStyle(addon); } } function setCssVariables(addonSettings, addonsWithUserstyles) { const hyphensToCamelCase = (s) => s.replace(/-([a-z])/g, (g) => g[1].toUpperCase()); const setVar = (addonId, varName, value) => { const realVarName = `--${hyphensToCamelCase(addonId)}-${varName}`; document.documentElement.style.setProperty(realVarName, value); existingCssVariables.push(realVarName); }; const removeVar = (addonId, varName) => document.documentElement.style.removeProperty(`--${hyphensToCamelCase(addonId)}-${varName}`); existingCssVariables.forEach((varName) => document.documentElement.style.removeProperty(varName)); existingCssVariables.length = 0; const addonIds = addonsWithUserstyles.map((obj) => obj.addonId); for (const addonId of addonIds) { for (const settingName of Object.keys(addonSettings[addonId] || {})) { const value = addonSettings[addonId][settingName]; if (typeof value === "string" || typeof value === "number") { setVar(addonId, hyphensToCamelCase(settingName), addonSettings[addonId][settingName]); } } } const getColor = (addonId, obj) => { if (typeof obj !== "object" || obj === null) return obj; let hex; switch (obj.type) { case "settingValue": return addonSettings[addonId][obj.settingId]; case "ternary": return getColor(addonId, obj.source) ? getColor(addonId, obj.true) : getColor(addonId, obj.false); case "map": return getColor(addonId, obj.options[getColor(addonId, obj.source)] ?? obj.default); case "textColor": { hex = getColor(addonId, obj.source); let black = getColor(addonId, obj.black); let white = getColor(addonId, obj.white); let threshold = getColor(addonId, obj.threshold); return textColorLib.textColor(hex, black, white, threshold); } case "alphaThreshold": { hex = getColor(addonId, obj.source); let { a } = textColorLib.parseHex(hex); let threshold = getColor(addonId, obj.threshold) || 0.5; if (a >= threshold) return getColor(addonId, obj.opaque); else return getColor(addonId, obj.transparent); } case "multiply": { hex = getColor(addonId, obj.source); return textColorLib.multiply(hex, obj); } case "brighten": { hex = getColor(addonId, obj.source); return textColorLib.brighten(hex, obj); } case "alphaBlend": { let opaqueHex = getColor(addonId, obj.opaqueSource); let transparentHex = getColor(addonId, obj.transparentSource); return textColorLib.alphaBlend(opaqueHex, transparentHex); } case "makeHsv": { let hSource = getColor(addonId, obj.h); let sSource = getColor(addonId, obj.s); let vSource = getColor(addonId, obj.v); return textColorLib.makeHsv(hSource, sSource, vSource); } case "recolorFilter": { hex = getColor(addonId, obj.source); return textColorLib.recolorFilter(hex); } } }; for (const addon of addonsWithUserstyles) { const addonId = addon.addonId; for (const customVar of addon.cssVariables) { const varName = customVar.name; const varValue = getColor(addonId, customVar.value); if (varValue === null && customVar.dropNull) { removeVar(addonId, varName); } else { setVar(addonId, varName, varValue); } } } } function waitForDocumentHead() { if (document.head) return Promise.resolve(); else { return new Promise((resolve) => { const observer = new MutationObserver(() => { if (document.head) { resolve(); observer.disconnect(); } }); observer.observe(document.documentElement, { subtree: true, childList: true }); }); } } function getL10NURLs() { const langCode = /scratchlanguage=([\w-]+)/.exec(document.cookie)?.[1] || navigator.language; const urls = [chrome.runtime.getURL(`addons-l10n/${langCode}`)]; if (langCode === "pt") { urls.push(chrome.runtime.getURL(`addons-l10n/pt-br`)); } if (langCode.includes("-")) { urls.push(chrome.runtime.getURL(`addons-l10n/${langCode.split("-")[0]}`)); } const enJSON = chrome.runtime.getURL("addons-l10n/en"); if (!urls.includes(enJSON)) urls.push(enJSON); return urls; } async function onInfoAvailable({ globalState: globalStateMsg, addonsWithUserscripts, addonsWithUserstyles }) { const everLoadedUserscriptAddons = new Set(addonsWithUserscripts.map((entry) => entry.addonId)); const disabledDynamicAddons = new Set; globalState = globalStateMsg; setCssVariables(globalState.addonSettings, addonsWithUserstyles); waitForDocumentHead().then(() => injectUserstyles(addonsWithUserstyles)); chrome.runtime.onMessage.addListener((request) => { if (request.dynamicAddonEnabled) { const { scripts, userstyles, cssVariables, addonId, injectAsStyleElt, index, dynamicEnable, dynamicDisable, partial } = request.dynamicAddonEnabled; disabledDynamicAddons.delete(addonId); addStyle({ styles: userstyles, addonId, injectAsStyleElt, index }); if (partial) { const addonsWithUserstylesEntry = addonsWithUserstyles.find((entry) => entry.addonId === addonId); if (addonsWithUserstylesEntry) { addonsWithUserstylesEntry.styles = userstyles; } else { addonsWithUserstyles.push({ styles: userstyles, cssVariables, addonId, injectAsStyleElt, index }); } } else { if (everLoadedUserscriptAddons.has(addonId)) { if (!dynamicDisable) return; document.querySelector(`[data-sa-hide-disabled-style=${addonId}]`).remove(); _page_.fireEvent({ name: "reenabled", addonId, target: "self" }); } else { if (!dynamicEnable) return; if (_page_) { _page_.runAddonUserscripts({ addonId, scripts, enabledLate: true }); everLoadedUserscriptAddons.add(addonId); } } addonsWithUserscripts.push({ addonId, scripts }); addonsWithUserstyles.push({ styles: userstyles, cssVariables, addonId, injectAsStyleElt, index }); } setCssVariables(globalState.addonSettings, addonsWithUserstyles); } else if (request.dynamicAddonDisable) { const { addonId, partialDynamicDisabledStyles } = request.dynamicAddonDisable; if (disabledDynamicAddons.has(addonId)) return; const scriptIndex = addonsWithUserscripts.findIndex((a) => a.addonId === addonId); const styleIndex = addonsWithUserstyles.findIndex((a) => a.addonId === addonId); if (_page_) { if (partialDynamicDisabledStyles) { removeAddonStylesPartial(addonId, partialDynamicDisabledStyles); if (styleIndex > -1) { const userstylesEntry = addonsWithUserstyles[styleIndex]; userstylesEntry.styles = userstylesEntry.styles.filter((style2) => !partialDynamicDisabledStyles.includes(style2.href)); if (userstylesEntry.styles.length || scriptIndex > -1) { return; } } } else { removeAddonStyles(addonId); } disabledDynamicAddons.add(addonId); const style = document.createElement("style"); style.dataset.saHideDisabledStyle = addonId; style.textContent = `[data-sa-hide-disabled=${addonId}] { display: none !important; }`; document.body.appendChild(style); _page_.fireEvent({ name: "disabled", addonId, target: "self" }); } else { everLoadedUserscriptAddons.delete(addonId); } if (scriptIndex !== -1) addonsWithUserscripts.splice(scriptIndex, 1); if (styleIndex !== -1) addonsWithUserstyles.splice(styleIndex, 1); setCssVariables(globalState.addonSettings, addonsWithUserstyles); } else if (request.updateUserstylesSettingsChange) { const { userstyles, addonId, injectAsStyleElt, index, dynamicEnable, dynamicDisable, addonSettings, cssVariables } = request.updateUserstylesSettingsChange; const addonIndex = addonsWithUserstyles.findIndex((addon) => addon.addonId === addonId); removeAddonStyles(addonId); if (addonIndex > -1 && userstyles.length === 0 && dynamicDisable) { addonsWithUserstyles.splice(addonIndex, 1); setCssVariables({ ...globalState.addonSettings, [addonId]: addonSettings }, addonsWithUserstyles); console2.log(`Dynamically disabling all userstyles of ${addonId} due to settings change`); return; } if (addonIndex > -1 && (dynamicDisable || dynamicEnable)) { const userstylesEntry = addonsWithUserstyles[addonIndex]; userstylesEntry.styles = userstyles; } if (addonIndex === -1 && userstyles.length > 0 && dynamicEnable) { console2.log(`Dynamically enabling userstyle addon ${addonId} due to settings change`); addonsWithUserstyles.push({ styles: userstyles, cssVariables, addonId, injectAsStyleElt, index }); disabledDynamicAddons.delete(addonId); setCssVariables({ ...globalState.addonSettings, [addonId]: addonSettings }, addonsWithUserstyles); } addStyle({ styles: userstyles, addonId, injectAsStyleElt, index }); } }); if (!_page_) { await new Promise((resolve) => { moduleScript.addEventListener("load", resolve); }); } _page_.globalState = globalState; _page_.l10njson = getL10NURLs(); _page_.addonsWithUserscripts = addonsWithUserscripts; _page_.dataReady = true; chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.newGlobalState) { _page_.globalState = request.newGlobalState; globalState = request.newGlobalState; setCssVariables(request.newGlobalState.addonSettings, addonsWithUserstyles); } else if (request.fireEvent) { _page_.fireEvent(request.fireEvent); } else if (request === "getRunningAddons") { const userscripts = addonsWithUserscripts.map((obj) => obj.addonId); const userstyles = addonsWithUserstyles.map((obj) => obj.addonId); sendResponse({ userscripts, userstyles, disabledDynamicAddons: Array.from(disabledDynamicAddons) }); } else if (request === "refetchSession") { _page_.refetchSession(); } }); } function forumWarning(key) { let postArea = document.querySelector("form#post > label"); if (postArea) { var errorList = document.querySelector("form#post > label > ul"); if (!errorList) { let typeArea = postArea.querySelector("strong"); errorList = document.createElement("ul"); errorList.classList.add("errorlist"); postArea.insertBefore(errorList, typeArea); } let addonError = document.createElement("li"); let reportLink = document.createElement("a"); const uiLanguage = chrome.i18n.getUILanguage(); const localeSlash = uiLanguage.startsWith("en") ? "" : `${uiLanguage.split("-")[0]}/`; const utm = `utm_source=extension&utm_medium=forumwarning&utm_campaign=v${chrome.runtime.getManifest().version}`; reportLink.href = `https://scratchaddons.com/${localeSlash}feedback/?ext_version=${chrome.runtime.getManifest().version}&${utm}`; reportLink.target = "_blank"; reportLink.innerText = chrome.i18n.getMessage("reportItHere"); let text1 = document.createElement("span"); text1.innerHTML = escapeHTML(chrome.i18n.getMessage(key, DOLLARS)).replace("$1", reportLink.outerHTML); addonError.appendChild(text1); errorList.appendChild(addonError); } } var MAX_USERSTYLES_PER_ADDON = 100, _realConsole, consoleOutput = (logAuthor = "[cs]") => { const style = { leftPrefix: "background: #ff7b26; color: white; border-radius: 0.5rem 0 0 0.5rem; padding: 0 0.5rem", rightPrefix: "background: #222; color: white; border-radius: 0 0.5rem 0.5rem 0; padding: 0 0.5rem; font-weight: bold", text: "" }; return [`%cSA%c${logAuthor}%c`, style.leftPrefix, style.rightPrefix, style.text]; }, console2, pseudoUrl, receivedResponse = false, onMessageBackgroundReady = (request, sender, sendResponse) => { if (request === "backgroundListenerReady" && !receivedResponse) { chrome.runtime.sendMessage({ contentScriptReady: { url: location.href } }, onResponse); } }, onResponse = (res) => { if (res && !receivedResponse) { console2.log("[Message from background]", res); chrome.runtime.onMessage.removeListener(onMessageBackgroundReady); if (res.httpStatusCode === null || String(res.httpStatusCode)[0] === "2") { onInfoAvailable(res); receivedResponse = true; } else { pseudoUrl = `https://scratch.mit.edu/${res.httpStatusCode}/`; console2.log(`Status code was not 2xx, replacing URL to ${pseudoUrl}`); chrome.runtime.sendMessage({ contentScriptReady: { url: pseudoUrl } }, onResponse); } } }, DOLLARS, promisify = (callbackFn) => (...args) => new Promise((resolve) => callbackFn(...args, resolve)), _page_ = null, globalState = null, comlinkIframesDiv, comlinkIframe1, comlinkIframe2, comlinkIframe3, comlinkIframe4, csUrlObserver, cs, moduleScript, initialUrl, path, pathArr, textColorLib, existingCssVariables, escapeHTML = (str) => str.replace(/([<>'"&])/g, (_, l) => `&#${l.charCodeAt(0)};`), showBanner = () => { const makeBr = () => document.createElement("br"); const notifOuterBody = document.createElement("div"); const notifInnerBody = Object.assign(document.createElement("div"), { id: "sa-notification", style: ` position: fixed; bottom: 20px; right: 20px; width: 700px; max-height: 270px; display: flex; align-items: center; padding: 10px; border-radius: 10px; background-color: #222; color: white; z-index: 99999; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; text-shadow: none; box-shadow: 0 0 20px 0px #0000009e; font-size: 14px; line-height: normal;` }); const notifImage = Object.assign(document.createElement("img"), { src: chrome.runtime.getURL("/images/cs/icon.png"), style: "height: 150px; border-radius: 5px; padding: 20px" }); const notifText = Object.assign(document.createElement("div"), { id: "sa-notification-text", style: "margin: 12px;" }); const notifTitle = Object.assign(document.createElement("span"), { style: "font-size: 18px; line-height: normal; display: inline-block; margin-bottom: 12px;", textContent: chrome.i18n.getMessage("extensionUpdate") }); const notifClose = Object.assign(document.createElement("img"), { style: ` float: right; cursor: pointer; width: 24px;`, title: chrome.i18n.getMessage("close"), src: chrome.runtime.getURL("../images/cs/close.svg"), draggable: false }); notifClose.addEventListener("click", () => notifInnerBody.remove(), { once: true }); const NOTIF_TEXT_STYLE = "display: block; color: white !important;"; const NOTIF_LINK_STYLE = "color: #1aa0d8; font-weight: normal; text-decoration: underline;"; const _uiLanguage = chrome.i18n.getUILanguage(); const _localeSlash = _uiLanguage.startsWith("en") ? "" : `${_uiLanguage.split("-")[0]}/`; const notifInnerText0 = Object.assign(document.createElement("span"), { style: NOTIF_TEXT_STYLE + "font-weight: bold;", textContent: chrome.i18n.getMessage("extensionHasUpdated", DOLLARS).replace(/\$(\d+)/g, (_, i) => [chrome.runtime.getManifest().version][Number(i) - 1]) }); const notifInnerText1 = Object.assign(document.createElement("span"), { style: NOTIF_TEXT_STYLE, innerHTML: escapeHTML(chrome.i18n.getMessage("extensionUpdateInfo1_v1_42", DOLLARS)).replace(/\$(\d+)/g, (_, i) => [ Object.assign(document.createElement("a"), { href: "https://scratch.mit.edu/scratch-addons-extension/settings?source=updatenotif", target: "_blank", style: NOTIF_LINK_STYLE, textContent: chrome.i18n.getMessage("scratchAddonsSettings") }).outerHTML ][Number(i) - 1]) }); const notifInnerText2 = Object.assign(document.createElement("span"), { style: NOTIF_TEXT_STYLE, textContent: chrome.i18n.getMessage("extensionUpdateInfo2_v1_42") }); const notifFooter = Object.assign(document.createElement("span"), { style: NOTIF_TEXT_STYLE }); const uiLanguage = chrome.i18n.getUILanguage(); const localeSlash = uiLanguage.startsWith("en") ? "" : `${uiLanguage.split("-")[0]}/`; const utm = `utm_source=extension&utm_medium=updatenotification&utm_campaign=v${chrome.runtime.getManifest().version}`; const notifFooterChangelog = Object.assign(document.createElement("a"), { href: `https://scratchaddons.com/${localeSlash}changelog?${utm}`, target: "_blank", style: NOTIF_LINK_STYLE, textContent: chrome.i18n.getMessage("notifChangelog") }); const notifFooterFeedback = Object.assign(document.createElement("a"), { href: `https://scratchaddons.com/${localeSlash}feedback/?ext_version=${chrome.runtime.getManifest().version}&${utm}`, target: "_blank", style: NOTIF_LINK_STYLE, textContent: chrome.i18n.getMessage("feedback") }); const notifFooterTranslate = Object.assign(document.createElement("a"), { href: "https://scratchaddons.com/translate", target: "_blank", style: NOTIF_LINK_STYLE, textContent: chrome.i18n.getMessage("translate") }); const notifFooterLegal = Object.assign(document.createElement("span"), { style: NOTIF_TEXT_STYLE + "font-size: 12px;", textContent: chrome.i18n.getMessage("notAffiliated") }); notifFooter.appendChild(notifFooterChangelog); notifFooter.appendChild(document.createTextNode(" | ")); notifFooter.appendChild(notifFooterFeedback); notifFooter.appendChild(document.createTextNode(" | ")); notifFooter.appendChild(notifFooterTranslate); notifFooter.appendChild(makeBr()); notifFooter.appendChild(notifFooterLegal); notifText.appendChild(notifTitle); notifText.appendChild(notifClose); notifText.appendChild(makeBr()); notifText.appendChild(notifInnerText0); notifText.appendChild(makeBr()); notifText.appendChild(notifInnerText1); notifText.appendChild(makeBr()); notifText.appendChild(notifInnerText2); notifText.appendChild(makeBr()); notifText.appendChild(notifFooter); notifInnerBody.appendChild(notifImage); notifInnerBody.appendChild(notifText); notifOuterBody.appendChild(notifInnerBody); document.body.appendChild(notifOuterBody); }, handleBanner = async () => { if (window.frameElement) return; const currentVersion = chrome.runtime.getManifest().version; const [major, minor, _] = currentVersion.split("."); const currentVersionMajorMinor = `${major}.${minor}`; const settings = await promisify(chrome.storage.local.get.bind(chrome.storage.local))(["bannerSettings"]); const force = !settings || !settings.bannerSettings; if (force || settings.bannerSettings.lastShown !== currentVersionMajorMinor || location.hash === "#sa-update-notif") { console2.log("Banner shown."); await promisify(chrome.storage.local.set.bind(chrome.storage.local))({ bannerSettings: Object.assign({}, settings.bannerSettings, { lastShown: currentVersionMajorMinor }) }); showBanner(); } }, isProfile, isStudio, isProject, isForums; var init_cs = __esm(() => { try { if (window.top.location.origin !== window.location.origin) throw ""; } catch { throw "Scratch Addons: cross-origin iframe ignored"; } if (window.frameElement && window.frameElement.getAttribute("src") === null) throw "Scratch Addons: iframe without src attribute ignored"; if (document.documentElement instanceof SVGElement) throw "Scratch Addons: SVG document ignored"; if (new URL(location.href).hostname === "localhost") { if (!["8333", "8601", "8602"].includes(new URL(location.href).port)) { throw "Scratch Addons: this localhost port is not supported"; } } _realConsole = window.console; console2 = { ..._realConsole, log: _realConsole.log.bind(_realConsole, ...consoleOutput()), warn: _realConsole.warn.bind(_realConsole, ...consoleOutput()), error: _realConsole.error.bind(_realConsole, ...consoleOutput()) }; chrome.runtime.onMessage.addListener(onMessageBackgroundReady); chrome.runtime.sendMessage({ contentScriptReady: { url: location.href } }, onResponse); DOLLARS = ["$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9"]; comlinkIframesDiv = document.createElement("div"); comlinkIframesDiv.id = "scratchaddons-iframes"; comlinkIframe1 = document.createElement("iframe"); comlinkIframe1.id = "scratchaddons-iframe-1"; comlinkIframe1.style.display = "none"; comlinkIframe2 = comlinkIframe1.cloneNode(); comlinkIframe2.id = "scratchaddons-iframe-2"; comlinkIframe3 = comlinkIframe1.cloneNode(); comlinkIframe3.id = "scratchaddons-iframe-3"; comlinkIframe4 = comlinkIframe1.cloneNode(); comlinkIframe4.id = "scratchaddons-iframe-4"; comlinkIframesDiv.appendChild(comlinkIframe1); comlinkIframesDiv.appendChild(comlinkIframe2); comlinkIframesDiv.appendChild(comlinkIframe3); comlinkIframesDiv.appendChild(comlinkIframe4); document.documentElement.appendChild(comlinkIframesDiv); csUrlObserver = new EventTarget; cs = { _url: location.href, get url() { return this._url; }, set url(newUrl) { this._url = newUrl; csUrlObserver.dispatchEvent(new CustomEvent("change", { detail: { newUrl } })); }, copyImage(dataURL) { return new Promise((resolve, reject) => { if (typeof Clipboard.prototype.write === "function") { reject("Use browser API instead"); return; } browser.runtime.sendMessage({ clipboardDataURL: dataURL }).then((res) => { resolve(); }, (res) => { reject(res.toString()); }); }); }, getEnabledAddons(tag) { return new Promise((resolve) => { chrome.runtime.sendMessage({ getEnabledAddons: { tag } }, (res) => resolve(res)); }); } }; Comlink.expose(cs, Comlink.windowEndpoint(comlinkIframe1.contentWindow, comlinkIframe2.contentWindow)); moduleScript = document.createElement("script"); moduleScript.type = "module"; moduleScript.src = chrome.runtime.getURL("content-scripts/inject/module.js"); (async () => { await new Promise((resolve) => { moduleScript.addEventListener("load", resolve); }); _page_ = Comlink.wrap(Comlink.windowEndpoint(comlinkIframe3.contentWindow, comlinkIframe4.contentWindow)); })(); document.documentElement.appendChild(moduleScript); initialUrl = location.href; path = new URL(initialUrl).pathname.substring(1); if (path[path.length - 1] !== "/") path += "/"; pathArr = path.split("/"); if (pathArr[0] === "scratch-addons-extension") { if (pathArr[1] === "settings") { let url = chrome.runtime.getURL(`webpages/settings/index.html${window.location.search}`); if (location.hash) url += location.hash; chrome.runtime.sendMessage({ replaceTabWithUrl: url }); } } if (path === "discuss/3/topic/add/") { window.addEventListener("load", () => forumWarning("forumWarning")); } else if (path.startsWith("discuss/topic/")) { window.addEventListener("load", () => { if (document.querySelector('div.linkst > ul > li > a[href="/discuss/18/"]')) { forumWarning("forumWarningGeneral"); } }); } chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { console2.log("[Message from background]", request); if (request === "getInitialUrl") { sendResponse(pseudoUrl || initialUrl); } }); textColorLib = __scratchAddonsTextColor; existingCssVariables = []; if (location.pathname.startsWith("/discuss/")) { const preserveBlocks = () => { document.querySelectorAll("pre.blocks").forEach((el) => { el.setAttribute("data-original", el.innerText); }); }; if (document.readyState !== "loading") { setTimeout(preserveBlocks, 0); } else { window.addEventListener("DOMContentLoaded", preserveBlocks, { once: true, capture: true }); } } if (document.readyState !== "loading") { handleBanner(); } else { window.addEventListener("DOMContentLoaded", handleBanner, { once: true }); } isProfile = pathArr[0] === "users" && pathArr[2] === ""; isStudio = pathArr[0] === "studios"; isProject = pathArr[0] === "projects"; isForums = pathArr[0] === "discuss"; if (isProfile || isStudio || isProject || isForums) { const removeReiteratedChars = (string) => string.split("").filter((char, i, charArr) => i === 0 ? true : charArr[i - 1] !== char).join(""); const shouldCaptureComment = (value) => { const trimmedValue = value.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ""); const limitedValue = removeReiteratedChars(trimmedValue.toLowerCase().replace(/[^a-z]+/g, "")); const regex = /scratchadons/; return regex.test(limitedValue); }; const extensionPolicyLink = document.createElement("a"); extensionPolicyLink.href = "https://scratch.mit.edu/discuss/topic/284272/"; extensionPolicyLink.target = "_blank"; extensionPolicyLink.innerText = chrome.i18n.getMessage("captureCommentPolicy"); Object.assign(extensionPolicyLink.style, { textDecoration: "underline", color: isForums ? "" : "white" }); const errorMsgHtml = escapeHTML(chrome.i18n.getMessage("captureCommentError", DOLLARS)).replace("$1", extensionPolicyLink.outerHTML); const sendAnywayMsg = chrome.i18n.getMessage("captureCommentPostAnyway"); const confirmMsg = chrome.i18n.getMessage("captureCommentConfirm"); if (isProfile) { window.addEventListener("click", (e) => { if (e.target.tagName !== "A" || !e.target.parentElement.matches("div.button[data-commentee-id]")) return; const form = e.target.closest("form"); if (!form) return; if (form.hasAttribute("data-sa-send-anyway")) { form.removeAttribute("data-sa-send-anyway"); return; } const textarea = form.querySelector("textarea[name=content]"); if (!textarea) return; if (shouldCaptureComment(textarea.value)) { e.stopPropagation(); e.preventDefault(); form.querySelector("[data-control=error] .text").innerHTML = errorMsgHtml + " "; const sendAnyway = document.createElement("a"); sendAnyway.onclick = () => { const res = confirm(confirmMsg); if (res) { form.setAttribute("data-sa-send-anyway", ""); form.querySelector("[data-control=post]").click(); } }; sendAnyway.textContent = sendAnywayMsg; Object.assign(sendAnyway.style, { textDecoration: "underline", color: "white" }); form.querySelector("[data-control=error] .text").appendChild(sendAnyway); form.querySelector(".control-group").classList.add("error"); } }, { capture: true }); } else if (isProject || isStudio) { window.addEventListener("click", (e) => { if (!(e.target.tagName === "SPAN" || e.target.tagName === "BUTTON")) return; if (!e.target.closest("button.compose-post")) return; const form = e.target.closest("form.full-width-form"); if (!form) return; form.parentNode.querySelector(".sa-compose-error-row")?.remove(); if (form.hasAttribute("data-sa-send-anyway")) { form.removeAttribute("data-sa-send-anyway"); return; } const textarea = form.querySelector("textarea[name=compose-comment]"); if (!textarea) return; if (shouldCaptureComment(textarea.value)) { e.stopPropagation(); const errorRow = document.createElement("div"); errorRow.className = "flex-row compose-error-row sa-compose-error-row"; const errorTip = document.createElement("div"); errorTip.className = "compose-error-tip"; const span = document.createElement("span"); span.innerHTML = errorMsgHtml + " "; const sendAnyway = document.createElement("a"); sendAnyway.onclick = () => { const res = confirm(confirmMsg); if (res) { form.setAttribute("data-sa-send-anyway", ""); form.querySelector(".compose-post")?.click(); } }; sendAnyway.textContent = sendAnywayMsg; errorTip.appendChild(span); errorTip.appendChild(sendAnyway); errorRow.appendChild(errorTip); form.parentNode.prepend(errorRow); textarea.addEventListener("input", () => { errorRow.remove(); }, { once: true }); form.querySelector(".compose-cancel").addEventListener("click", () => { errorRow.remove(); }, { once: true }); } }, { capture: true }); } else if (isForums) { window.addEventListener("click", (e) => { const potentialPostButton = e.target.closest("button[type=submit]"); if (!potentialPostButton) return; const form = e.target.closest("form"); if (!form) return; if (form.hasAttribute("data-sa-send-anyway")) { form.removeAttribute("data-sa-send-anyway"); return; } const existingWarning = form.parentElement.querySelector(".sa-extension-policy-warning"); if (existingWarning) { e.preventDefault(); existingWarning.scrollIntoView({ behavior: "smooth" }); return; } const textarea = form.querySelector("textarea.markItUpEditor"); if (!textarea) return; if (shouldCaptureComment(textarea.value)) { const errorTip = document.createElement("li"); errorTip.classList.add("errorlist", "sa-extension-policy-warning"); errorTip.style.scrollMarginTop = "50px"; errorTip.style.fontWeight = "bold"; errorTip.innerHTML = errorMsgHtml + " "; const sendAnyway = document.createElement("a"); sendAnyway.onclick = () => { const res = confirm(confirmMsg);