UNPKG

to-userscript

Version:

Converts simple browser extensions to userscripts

1,381 lines (1,360 loc) 112 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); // scratch-addons-1.42.0/libraries/thirdparty/idb.js var require_idb = __commonJS((exports, module) => { (function(e, t) { typeof exports == "object" && typeof module != "undefined" ? t(exports) : typeof define == "function" && define.amd ? define(["exports"], t) : t((e = typeof globalThis != "undefined" ? globalThis : e || self).idb = {}); })(exports, function(e) { let t, n; const r = new WeakMap, o = new WeakMap, s = new WeakMap, i = new WeakMap, a = new WeakMap; let c = { get(e2, t2, n2) { if (e2 instanceof IDBTransaction) { if (t2 === "done") return o.get(e2); if (t2 === "objectStoreNames") return e2.objectStoreNames || s.get(e2); if (t2 === "store") return n2.objectStoreNames[1] ? undefined : n2.objectStore(n2.objectStoreNames[0]); } return f(e2[t2]); }, set: (e2, t2, n2) => (e2[t2] = n2, true), has: (e2, t2) => e2 instanceof IDBTransaction && (t2 === "done" || t2 === "store") || (t2 in e2) }; function d(e2) { return e2 !== IDBDatabase.prototype.transaction || "objectStoreNames" in IDBTransaction.prototype ? (n || (n = [IDBCursor.prototype.advance, IDBCursor.prototype.continue, IDBCursor.prototype.continuePrimaryKey])).includes(e2) ? function(...t2) { return e2.apply(l(this), t2), f(r.get(this)); } : function(...t2) { return f(e2.apply(l(this), t2)); } : function(t2, ...n2) { const r2 = e2.call(l(this), t2, ...n2); return s.set(r2, t2.sort ? t2.sort() : [t2]), f(r2); }; } function u(e2) { return typeof e2 == "function" ? d(e2) : (e2 instanceof IDBTransaction && function(e3) { if (o.has(e3)) return; const t2 = new Promise((t3, n3) => { const r2 = () => { e3.removeEventListener("complete", o2), e3.removeEventListener("error", s2), e3.removeEventListener("abort", s2); }, o2 = () => { t3(), r2(); }, s2 = () => { n3(e3.error || new DOMException("AbortError", "AbortError")), r2(); }; e3.addEventListener("complete", o2), e3.addEventListener("error", s2), e3.addEventListener("abort", s2); }); o.set(e3, t2); }(e2), n2 = e2, (t || (t = [IDBDatabase, IDBObjectStore, IDBIndex, IDBCursor, IDBTransaction])).some((e3) => n2 instanceof e3) ? new Proxy(e2, c) : e2); var n2; } function f(e2) { if (e2 instanceof IDBRequest) return function(e3) { const t3 = new Promise((t4, n2) => { const r2 = () => { e3.removeEventListener("success", o2), e3.removeEventListener("error", s2); }, o2 = () => { t4(f(e3.result)), r2(); }, s2 = () => { n2(e3.error), r2(); }; e3.addEventListener("success", o2), e3.addEventListener("error", s2); }); return t3.then((t4) => { t4 instanceof IDBCursor && r.set(t4, e3); }).catch(() => { }), a.set(t3, e3), t3; }(e2); if (i.has(e2)) return i.get(e2); const t2 = u(e2); return t2 !== e2 && (i.set(e2, t2), a.set(t2, e2)), t2; } const l = (e2) => a.get(e2); const p = ["get", "getKey", "getAll", "getAllKeys", "count"], D = ["put", "add", "delete", "clear"], b = new Map; function v(e2, t2) { if (!(e2 instanceof IDBDatabase) || t2 in e2 || typeof t2 != "string") return; if (b.get(t2)) return b.get(t2); const n2 = t2.replace(/FromIndex$/, ""), r2 = t2 !== n2, o2 = D.includes(n2); if (!(n2 in (r2 ? IDBIndex : IDBObjectStore).prototype) || !o2 && !p.includes(n2)) return; const s2 = async function(e3, ...t3) { const s3 = this.transaction(e3, o2 ? "readwrite" : "readonly"); let i2 = s3.store; return r2 && (i2 = i2.index(t3.shift())), (await Promise.all([i2[n2](...t3), o2 && s3.done]))[0]; }; return b.set(t2, s2), s2; } c = ((e2) => ({ ...e2, get: (t2, n2, r2) => v(t2, n2) || e2.get(t2, n2, r2), has: (t2, n2) => !!v(t2, n2) || e2.has(t2, n2) }))(c), e.deleteDB = function(e2, { blocked: t2 } = {}) { const n2 = indexedDB.deleteDatabase(e2); return t2 && n2.addEventListener("blocked", (e3) => t2(e3.oldVersion, e3)), f(n2).then(() => { }); }, e.openDB = function(e2, t2, { blocked: n2, upgrade: r2, blocking: o2, terminated: s2 } = {}) { const i2 = indexedDB.open(e2, t2), a2 = f(i2); return r2 && i2.addEventListener("upgradeneeded", (e3) => { r2(f(i2.result), e3.oldVersion, e3.newVersion, f(i2.transaction), e3); }), n2 && i2.addEventListener("blocked", (e3) => n2(e3.oldVersion, e3.newVersion, e3)), a2.then((e3) => { s2 && e3.addEventListener("close", () => s2()), o2 && e3.addEventListener("versionchange", (e4) => o2(e4.oldVersion, e4.newVersion, e4)); }).catch(() => { }), a2; }, e.unwrap = l, e.wrap = f; }); }); // scratch-addons-1.42.0/background/firefox-localhost-support.js if (typeof browser === "object" && chrome.scripting) { const manifest = chrome.runtime.getManifest(); const manifestScripts = manifest.content_scripts.filter((script) => script.matches.includes("http://localhost:8602/*")); const scripts = manifestScripts.map((scriptObj, i) => ({ id: `ff_${i}`, matches: ["http://localhost/*"], js: scriptObj.js.map((url) => new URL(url).pathname), runAt: scriptObj.run_at, allFrames: scriptObj.all_frames })); chrome.scripting.registerContentScripts(scripts).catch((e) => console.error(e)); } chrome.scripting = "sa_no_scripting"; // scratch-addons-1.42.0/background/transition.js var utm = `utm_source=extension&utm_medium=tabscreate&utm_campaign=v${chrome.runtime.getManifest().version}`; var uiLanguage = chrome.i18n.getUILanguage && chrome.i18n.getUILanguage() || navigator.language; var localeSlash = uiLanguage.startsWith("en") ? "" : `${uiLanguage.split("-")[0]}/`; chrome.runtime.onInstalled.addListener(async (details) => { const developerMode = (await chrome.management.getSelf()).installType; const currentVersion = chrome.runtime.getManifest().version; const [major, minor, _] = currentVersion.split("."); if (details.previousVersion && details.previousVersion.startsWith("0")) { chrome.tabs.create({ url: `https://scratchaddons.com/${localeSlash}scratch-messaging-transition/?${utm}` }); } else if (details.reason === "install" && !developerMode) { chrome.tabs.create({ url: `https://scratchaddons.com/${localeSlash}welcome/?${utm}` }); } if (details.reason === "install") { chrome.storage.local.set({ bannerSettings: { lastShown: `${major}.${minor}` } }); } }); chrome.management.getSelf((result) => { if (result.installType !== "development") { chrome.runtime.setUninstallURL(`https://scratchaddons.com/${localeSlash}farewell?${utm}`); } }); // scratch-addons-1.42.0/background/background.js var import_idb = __toESM(require_idb(), 1); // scratch-addons-1.42.0/background/imports/global-state.js var _globalState = { auth: { isLoggedIn: false, username: null, userId: null, xToken: null, csrfToken: null, scratchLang: null }, addonSettings: {} }; class StateProxy { constructor(name = "scratchAddons.globalState") { this.name = name; } get(target, key) { if (key === "_target") return target; if (typeof target[key] === "object" && target[key] !== null) { return new Proxy(target[key], new StateProxy(`${this.name}.${key}`)); } else { return target[key]; } } set(target, key, value) { const oldValue = target[key]; target[key] = value; if (scratchAddons.localState.allReady) { messageForAllTabs({ newGlobalState: _globalState }); if (JSON.stringify(oldValue) !== JSON.stringify(value)) { stateChange(this.name, key, value); } } return true; } } function messageForAllTabs(message) { chrome.tabs.query({}, (tabs) => tabs.forEach((tab) => tab.url && chrome.tabs.sendMessage(tab.id, message, () => void chrome.runtime.lastError))); scratchAddons.sendToPopups(message); } function stateChange(parentObjectPath, key, value) { const objectPath = `${parentObjectPath}.${key}`; const objectPathArr = objectPath.split(".").slice(2); console.log(`%c${objectPath}`, "font-weight: bold;", "is now: ", objectPathArr[0] === "auth" ? "[redacted]" : value); if (objectPathArr[0] === "addonSettings") { messageForAllTabs({ fireEvent: { target: "settings", name: "change", addonId: objectPathArr[1] } }); } } var global_state_default = new Proxy(_globalState, new StateProxy); // scratch-addons-1.42.0/background/imports/local-state.js var _localState = { ready: { manifests: false, addonSettings: false }, allReady: false, addonsEnabled: {} }; class StateProxy2 { constructor(name = "scratchAddons.localState") { this.name = name; } get(target, key) { if (key === "_target") return target; if (typeof target[key] === "object" && target[key] !== null) { return new Proxy(target[key], new StateProxy2(`${this.name}.${key}`)); } else { return target[key]; } } set(target, key, value) { const oldValue = target[key]; target[key] = value; if (JSON.stringify(oldValue) !== JSON.stringify(value)) { stateChange2(this.name, key, value); } return true; } } function stateChange2(parentObjectPath, key, value) { const objectPath = `${parentObjectPath}.${key}`; const objectPathArr = objectPath.split(".").slice(2); console.log(`%c${objectPath}`, "font-weight: bold;", "is now: ", objectPathArr[0] === "auth" ? "[redacted]" : value); if (objectPathArr[0] === "ready" && Object.values(scratchAddons.localState.ready).every((x) => x === true)) { console.log("Everything ready!"); _localState.allReady = true; scratchAddons.localEvents.dispatchEvent(new CustomEvent("ready")); } } var local_state_default = new Proxy(_localState, new StateProxy2); // scratch-addons-1.42.0/libraries/common/cs/autoescaper.js var escapeHTML = (str) => str.replace(/([<>'"&])/g, (_, l) => `&#${l.charCodeAt(0)};`); // scratch-addons-1.42.0/libraries/thirdparty/cs/icu-message-formatter.es.min.js function r(r2, n) { var t = Object.keys(r2); if (Object.getOwnPropertySymbols) { var e = Object.getOwnPropertySymbols(r2); n && (e = e.filter(function(n2) { return Object.getOwnPropertyDescriptor(r2, n2).enumerable; })), t.push.apply(t, e); } return t; } function n(n2) { for (var t = 1;t < arguments.length; t++) { var e = arguments[t] != null ? arguments[t] : {}; t % 2 ? r(Object(e), true).forEach(function(r2) { o(n2, r2, e[r2]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(n2, Object.getOwnPropertyDescriptors(e)) : r(Object(e)).forEach(function(r2) { Object.defineProperty(n2, r2, Object.getOwnPropertyDescriptor(e, r2)); }); } return n2; } function t(r2, n2) { if (!(r2 instanceof n2)) throw new TypeError("Cannot call a class as a function"); } function e(r2, n2) { for (var t2 = 0;t2 < n2.length; t2++) { var e2 = n2[t2]; e2.enumerable = e2.enumerable || false, e2.configurable = true, "value" in e2 && (e2.writable = true), Object.defineProperty(r2, e2.key, e2); } } function o(r2, n2, t2) { return n2 in r2 ? Object.defineProperty(r2, n2, { value: t2, enumerable: true, configurable: true, writable: true }) : r2[n2] = t2, r2; } function i(r2, n2) { return function(r3) { if (Array.isArray(r3)) return r3; }(r2) || function(r3, n3) { var t2 = r3 == null ? null : typeof Symbol != "undefined" && r3[Symbol.iterator] || r3["@@iterator"]; if (t2 == null) return; var e2, o2, i2 = [], a = true, u = false; try { for (t2 = t2.call(r3);!(a = (e2 = t2.next()).done) && (i2.push(e2.value), !n3 || i2.length !== n3); a = true) ; } catch (r4) { u = true, o2 = r4; } finally { try { a || t2.return == null || t2.return(); } finally { if (u) throw o2; } } return i2; }(r2, n2) || function(r3, n3) { if (!r3) return; if (typeof r3 == "string") return a(r3, n3); var t2 = Object.prototype.toString.call(r3).slice(8, -1); t2 === "Object" && r3.constructor && (t2 = r3.constructor.name); if (t2 === "Map" || t2 === "Set") return Array.from(r3); if (t2 === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t2)) return a(r3, n3); }(r2, n2) || function() { throw new TypeError(`Invalid attempt to destructure non-iterable instance. In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`); }(); } function a(r2, n2) { (n2 == null || n2 > r2.length) && (n2 = r2.length); for (var t2 = 0, e2 = new Array(n2);t2 < n2; t2++) e2[t2] = r2[t2]; return e2; } function u(r2) { for (var n2 = function(r3) { return /\s/.test(r3); }, t2 = [], e2 = {}, o2 = 0, i2 = null, a2 = false, u2 = 0;u2 < r2.length; ) { if (a2 && (n2(r2[u2]) || r2[u2] === "{")) a2 = false, i2 = r2.slice(o2, u2), r2[u2] === "{" && u2--; else if (!a2 && !n2(r2[u2])) { var c = r2[u2] === "{"; if (i2 && c) { var s = l(r2, u2); if (s === -1) throw new Error('Unbalanced curly braces in string: "'.concat(r2, '"')); e2[i2] = r2.slice(u2 + 1, s), u2 = s, i2 = null; } else i2 && (t2.push(i2), i2 = null), a2 = true, o2 = u2; } u2++; } return a2 && (i2 = r2.slice(o2)), i2 && t2.push(i2), { args: t2, cases: e2 }; } function l(r2, n2) { for (var t2 = 0, e2 = n2 + 1;e2 < r2.length; e2++) { var o2 = r2.charAt(e2); if (o2 === "}") { if (t2 === 0) return e2; t2--; } else o2 === "{" && t2++; } return -1; } function c(r2) { return s(r2.slice(1, -1), ",", 3); } function s(r2, n2, t2) { var e2 = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : []; if (!r2) return e2; if (t2 === 1) return e2.push(r2), e2; var o2 = r2.indexOf(n2); if (o2 === -1) return e2.push(r2), e2; var i2 = r2.substring(0, o2).trim(), a2 = r2.substring(o2 + n2.length + 1).trim(); return e2.push(i2), s(a2, n2, t2 - 1, e2); } function f(r2) { return r2.reduce(function(r3, n2) { return r3.concat(Array.isArray(n2) ? f(n2) : n2); }, []); } function h(r2) { var n2 = {}; return function() { for (var t2 = arguments.length, e2 = new Array(t2), o2 = 0;o2 < t2; o2++) e2[o2] = arguments[o2]; var i2 = e2.length ? e2.map(function(r3) { return r3 === null ? "null" : r3 === undefined ? "undefined" : typeof r3 == "function" ? r3.toString() : r3 instanceof Date ? r3.toISOString() : JSON.stringify(r3); }).join("|") : "_(no-args)_"; if (Object.prototype.hasOwnProperty.call(n2, i2)) return n2[i2]; var a2 = r2.apply(undefined, e2); return n2[i2] = a2, a2; }; } var v; var p = function() { function r2(n3) { var e2 = this, i2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; t(this, r2), o(this, "format", h(function(r3) { var n4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return f(e2.process(r3, n4)).join(""); })), this.locale = n3, this.typeHandlers = i2; } var n2, a2, u2; return n2 = r2, a2 = [{ key: "process", value: function(r3) { var n3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; if (!r3) return []; var t2 = r3.indexOf("{"); if (t2 !== -1) { var e2 = l(r3, t2); if (e2 === -1) throw new Error('Unbalanced curly braces in string: "'.concat(r3, '"')); var o2 = r3.substring(t2, e2 + 1); if (o2) { var a3 = [], u3 = r3.substring(0, t2); u3 && a3.push(u3); var s2 = c(o2), f2 = i(s2, 3), h2 = f2[0], v2 = f2[1], p2 = f2[2], g = n3[h2]; g == null && (g = ""); var y = v2 && this.typeHandlers[v2]; a3.push(y ? y(g, p2, this.locale, n3, this.process.bind(this)) : g); var b = r3.substring(e2 + 1); return b && a3.push(this.process(b, n3)), a3; } } return [r3]; } }], a2 && e(n2.prototype, a2), u2 && e(n2, u2), r2; }(); var g = 0; function y(r2, n2) { for (var t2 = 0, e2 = "", o2 = 0, i2 = {};t2 < r2.length; ) { if (r2[t2] !== "#" || o2) e2 += r2[t2]; else { var a2 = "__hashToken".concat(g++); e2 += "{".concat(a2, ", number}"), i2[a2] = n2; } r2[t2] === "{" ? o2++ : r2[t2] === "}" && o2--, t2++; } return { caseBody: e2, numberValues: i2 }; } function b(r2) { var t2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "", e2 = arguments.length > 2 ? arguments[2] : undefined, o2 = arguments.length > 3 ? arguments[3] : undefined, i2 = arguments.length > 4 ? arguments[4] : undefined, a2 = u(t2), l2 = a2.args, c2 = a2.cases, s2 = parseInt(r2); l2.forEach(function(r3) { r3.startsWith("offset:") && (s2 -= parseInt(r3.slice("offset:".length))); }); var f2 = []; if ("PluralRules" in Intl) { v !== undefined && v.resolvedOptions().locale === e2 || (v = new Intl.PluralRules(e2)); var h2 = v.select(s2); h2 !== "other" && f2.push(h2); } s2 === 1 && f2.push("one"), f2.push("=".concat(s2), "other"); for (var p2 = 0;p2 < f2.length; p2++) { var g2 = f2[p2]; if (g2 in c2) { var b2 = y(c2[g2], s2), d = b2.caseBody, O = b2.numberValues; return i2(d, n(n({}, o2), O)); } } return r2; } // scratch-addons-1.42.0/libraries/common/cs/l10n.js class LocalizationProvider extends EventTarget { constructor() { super(); this.messages = {}; this._reconfigure(); } _reconfigure() { const locale = this.locale; this._date = new Intl.DateTimeFormat(locale); this._datetime = new Intl.DateTimeFormat(locale, { timeStyle: "short", dateStyle: "short" }); this.formatter = new p(locale, { plural: b }); } _get(key, placeholders, messageHandler, fallback) { messageHandler = messageHandler || ((m) => m); if (Object.prototype.hasOwnProperty.call(this.messages, key)) { const rawMessage = this.messages[key]; const message = messageHandler(rawMessage.string || rawMessage); return this.formatter.format(message, placeholders); } if (!fallback) (globalThis.scratchAddons?.console || console).warn("Key missing:", key); return fallback || key; } get(key, placeholders = {}, fallback = "") { return this._get(key, placeholders, null, fallback); } escaped(key, placeholders = {}, fallback = "") { return this._get(key, placeholders, (message) => escapeHTML(message), fallback); } get locale() { return this.messages._locale || "en"; } get localeName() { return this.messages._locale_name || "English"; } date(dateObj) { return this._date.format(dateObj); } datetime(dateObj) { return this._datetime.format(dateObj); } } // scratch-addons-1.42.0/background/l10n.js class BackgroundLocalizationProvider extends LocalizationProvider { constructor() { super(); this.loaded = []; } loadFromCache({ messages, loaded }) { this.messages = messages; this._reconfigure(); this.loaded = loaded; } async load(addonIds) { addonIds = ["_general", ...addonIds].filter((addonId) => !addonId.startsWith("//") && !this.loaded.includes(addonId)); const ui = chrome.i18n.getUILanguage && chrome.i18n.getUILanguage() || navigator.language; const locales = [ui]; if (ui.includes("-")) locales.push(ui.split("-")[0]); if (ui.startsWith("pt") && ui !== "pt-br") locales.push("pt-br"); if (!locales.includes("en")) locales.push("en"); localeLoop: for (const locale of locales) { for (const addonId of addonIds) { let resp; let messages = {}; const url = `/addons-l10n/${locale}/${addonId}.json`; try { resp = await fetch(url); messages = await resp.json(); } catch (_) { if (addonId === "_general") continue localeLoop; continue; } this.messages = Object.assign(messages, this.messages); } } this._reconfigure(); this.loaded = this.loaded.concat(addonIds); chrome.storage.session?.set({ l10nCache: { messages: this.messages, loaded: this.loaded } }).catch((err) => { console.error(err); }); } } // scratch-addons-1.42.0/background/declare-scratchaddons-object.js globalThis.scratchAddons = {}; scratchAddons.localEvents = new EventTarget; scratchAddons.manifests = []; scratchAddons.dependents = {}; scratchAddons.methods = {}; scratchAddons.l10n = new BackgroundLocalizationProvider; scratchAddons.globalState = global_state_default; console.log("%cscratchAddons.globalState", "font-weight: bold;", `initialized: `, JSON.parse(JSON.stringify(scratchAddons.globalState))); scratchAddons.localState = local_state_default; console.log("%cscratchAddons.localState", "font-weight: bold;", `initialized: `, JSON.parse(JSON.stringify(scratchAddons.localState))); scratchAddons.popupPorts = {}; scratchAddons.sendToPopups = (value) => Object.values(scratchAddons.popupPorts).forEach((ports) => ports.forEach((port) => port.postMessage(value))); // scratch-addons-1.42.0/background/load-addon-manifests.js var localizeSettings = (addonId, setting, tableId) => { const settingId = tableId ? `${tableId}-${setting.id}` : setting.id; setting.name = scratchAddons.l10n.get(`${addonId}/@settings-name-${settingId}`, { commentIcon: "@comment.svg", forumIcon: "@forum.svg", heartIcon: "@heart.svg", starIcon: "@star.svg", followIcon: "@follow.svg", studioAddIcon: "@studio-add.svg", studioIcon: "@studio.svg", remixIcon: "@remix.svg", adminusersIcon: "@adminusers.svg", usersIcon: "@users.svg" }, setting.name); if (setting.description) { setting.description = scratchAddons.l10n.get(`${addonId}/@settings-description-${settingId}`, {}, setting.description); } switch (setting.type) { case "string": if (setting.default) { setting.default = scratchAddons.l10n.get(`${addonId}/@settings-default-${settingId}`, {}, setting.default); } break; case "select": setting.potentialValues = setting.potentialValues.map((value) => { value.name = scratchAddons.l10n.get(`${addonId}/@settings-select-${settingId}-${value.id}`, {}, value.name); return value; }); break; } }; (async function() { const forceEnglish = await new Promise((resolve) => { chrome.storage.local.get("forceEnglish", (obj) => { resolve(!!obj.forceEnglish); }); }); const addonIds = await (await fetch("/addons/addons.json")).json(); addonIds.forEach((addonId, i2) => { if (addonIds.lastIndexOf(addonId) !== i2) throw new Error(`Duplicated value "${addonId}" in /addons/addons.json`); }); const l10nCacheReq = await chrome.storage.session?.get("l10nCache").catch((err) => console.error(err)); const l10nCache = l10nCacheReq?.l10nCache; if (l10nCache) { scratchAddons.l10n.loadFromCache(l10nCache); } else { await scratchAddons.l10n.load(addonIds); } const useDefault = forceEnglish || scratchAddons.l10n.locale.startsWith("en"); const cacheReq = await chrome.storage.session?.get("manifests").catch((err) => console.error(err)); const cache = cacheReq?.manifests; const newCache = {}; for (const addonId of addonIds) { if (addonId.startsWith("//")) continue; let manifest; try { if (cache) { manifest = cache[addonId]; } else { const file = await (await fetch(`/addons/${addonId}/addon.json`)).json(); manifest = file; newCache[addonId] = JSON.parse(JSON.stringify(file)); } } catch (ex) { console.error(`Failed to load addon manifest for ${addonId}, crashing:`, ex); chrome.tabs.create({ url: chrome.runtime.getURL(`/webpages/error/index.html?problem=invalidManifest&addon=${addonId}`) }); throw ex; } let potentiallyNeedsMissingDynamicWarning = manifest.updateUserstylesOnSettingsChange && !(manifest.dynamicEnable && manifest.dynamicDisable); if (!useDefault) { manifest._english = {}; for (const prop of ["name", "description"]) { if (manifest[prop]) { manifest._english[prop] = manifest[prop]; manifest[prop] = scratchAddons.l10n.get(`${addonId}/@${prop}`, {}, manifest[prop]); } } if (manifest.info) { for (const info of manifest.info || []) { info.text = scratchAddons.l10n.get(`${addonId}/@info-${info.id}`, {}, info.text); } } if (manifest.credits) { for (const credit of manifest.credits || []) { if (credit.note) credit.note = scratchAddons.l10n.get(`${addonId}/@credits-${credit.id}`, {}, credit.note); } } if (manifest.popup) { manifest.popup.name = scratchAddons.l10n.get(`${addonId}/@popup-name`, {}, manifest.popup.name); } if (manifest.latestUpdate?.temporaryNotice) { manifest.latestUpdate.temporaryNotice = scratchAddons.l10n.get(`${addonId}/@update`, {}, manifest.latestUpdate.temporaryNotice); } } for (const propName of ["userscripts", "userstyles"]) { for (const injectable of manifest[propName] || []) { const { matches } = injectable; if (typeof matches === "string" && matches.startsWith("^")) { injectable._scratchDomainImplied = !matches.startsWith("^https:"); injectable.matches = new RegExp(matches, "u"); } else if (Array.isArray(matches)) { for (let i2 = matches.length;i2--; ) { const match = matches[i2]; if (typeof match === "string" && match.startsWith("^")) { matches[i2] = new RegExp(match, "u"); matches[i2]._scratchDomainImplied = !match.startsWith("^https:"); } } } if (propName === "userstyles") { if (injectable.if?.addonEnabled?.length) { if (typeof injectable.if.addonEnabled === "string") { injectable.if.addonEnabled = [injectable.if.addonEnabled]; } for (const dependency of injectable.if.addonEnabled) { if (!scratchAddons.dependents[dependency]) scratchAddons.dependents[dependency] = new Set; scratchAddons.dependents[dependency].add(addonId); } } if (potentiallyNeedsMissingDynamicWarning && Object.keys(injectable.if?.settings || {}).length > 0) { potentiallyNeedsMissingDynamicWarning = false; console.warn("Addon", addonId, "has updateUserstylesOnSettingsChange set to true without dynamic enable or disable.", "This will cause an issue as userstyle", injectable.url, "has a setting as a condition!"); } } } } if (!useDefault) { manifest._english = {}; for (const prop of ["name", "description"]) { if (manifest[prop]) { manifest._english[prop] = manifest[prop]; manifest[prop] = scratchAddons.l10n.get(`${addonId}/@${prop}`, {}, manifest[prop]); } } if (manifest.info) { for (const info of manifest.info || []) { info.text = scratchAddons.l10n.get(`${addonId}/@info-${info.id}`, {}, info.text); } } if (manifest.popup) { manifest.popup.name = scratchAddons.l10n.get(`${addonId}/@popup-name`, {}, manifest.popup.name); } const localizedSettings = []; for (const setting of manifest.settings || []) { localizeSettings(addonId, setting); if (setting.type === "string") { localizedSettings.push(setting.id); } else if (setting.type === "table") { const localizedRows = []; setting.row.forEach((row) => { localizeSettings(addonId, row, setting.id); if (row.type === "string") { localizedRows.push(row.id); } }); for (let i2 = 0;i2 < (setting.default || []).length; i2++) { const defaultValues = setting.default[i2]; for (const localizedRow of localizedRows) { defaultValues[localizedRow] = scratchAddons.l10n.get(`${addonId}/@settings-default-${setting.id}-${i2}-${localizedRows}`, {}, defaultValues[localizedRow]); } } for (let i2 = 0;i2 < (setting.presets || []).length; i2++) { const preset = setting.presets[i2]; preset.name = scratchAddons.l10n.get(`${addonId}/@preset-${setting.id}-${i2}`, {}, preset.name); for (const localizedRow of localizedRows) { preset.values[localizedRow] = scratchAddons.l10n.get(`${addonId}/@preset-value-${setting.id}-${i2}-${localizedRows}`, {}, preset.values[localizedRow]); } } } } for (const preset of manifest.presets || []) { for (const prop of ["name", "description"]) { if (preset[prop]) { preset[prop] = scratchAddons.l10n.get(`${addonId}/@preset-${prop}-${preset.id}`, {}, preset[prop]); } } for (const localizedSetting of localizedSettings) { if (typeof preset.values[localizedSetting] === "string") { preset.values[localizedSetting] = scratchAddons.l10n.get(`${addonId}/@preset-value-${preset.id}-${localizedSetting}`, {}, preset.values[localizedSetting]); } } } } scratchAddons.manifests.push({ addonId, manifest }); } if (!cache) { chrome.storage.session?.set({ manifests: newCache }); } scratchAddons.localState.ready.manifests = true; scratchAddons.localEvents.dispatchEvent(new CustomEvent("manifestsReady")); })(); // scratch-addons-1.42.0/libraries/common/minify-settings.js var STORE_1_REGEX = /\d|[a-d]/; var STORE_2_REGEX = /[e-k]/; var minify_settings_default = (settings, manifests) => { const newSettings = JSON.parse(JSON.stringify(settings)); const manifestObj = manifests && manifests.reduce((a2, b2) => { a2[b2._addonId || b2.addonId] = b2.manifest || b2; return a2; }, {}); const storageItems = { addonSettings1: {}, addonSettings2: {}, addonSettings3: {} }; for (const [addonId, setting] of Object.entries(newSettings)) { if (manifestObj && !manifestObj[addonId]) { delete newSettings[addonId]; } else { for (const settingsKey of Object.keys(setting)) { if (manifestObj && !settingsKey.startsWith("_") && !manifestObj[addonId].settings?.some((s2) => settingsKey === s2.id)) { delete setting[settingsKey]; } } if (Object.keys(setting).length === 0) { delete newSettings[addonId]; } } if (addonId[0].match(STORE_1_REGEX)) storageItems.addonSettings1[addonId] = newSettings[addonId]; else if (addonId[0].match(STORE_2_REGEX)) storageItems.addonSettings2[addonId] = newSettings[addonId]; else storageItems.addonSettings3[addonId] = newSettings[addonId]; } return storageItems; }; // scratch-addons-1.42.0/libraries/common/message-cache.js class HTTPError extends Error { constructor(message, code) { super(message); this.code = code; } static fromResponse(message, resp) { return new HTTPError(`${message}: status ${resp.status}`, resp.status); } } class IncognitoTransaction { constructor(db, stores) { this.db = db; this.stores = stores; } objectStore(name) { return { put: (value) => this.db.put(name, value) }; } get done() { return null; } } class IncognitoDatabase { constructor() { this.messages = []; this.msgCount = 0; } get(name) { switch (name) { case "cache": return this.messages.slice(); case "count": return this.msgCount; } } put(name, value) { switch (name) { case "cache": { this.messages = value; return; } case "count": { this.msgCount = value; return; } } } close() { } transaction(stores) { return new IncognitoTransaction(this, stores); } static isIncognito() { return chrome.extension.inIncognitoContext; } } var incognitoDatabase = new IncognitoDatabase; async function fetchMessageCount(username, options) { const bypassCache = options ? Boolean(options.bypassCache) : false; const url = `https://api.scratch.mit.edu/users/${username}/messages/count${!bypassCache ? "" : `?addons_bypass_cache_after_marking_read=1`}`; const fetchOptions = { credentials: "omit", cache: bypassCache ? "reload" : "default" }; const resp = await fetch(url, fetchOptions); const json = await resp.json(); const resId = bypassCache ? null : resp.headers.get("X-Amz-Cf-Id"); return { count: json.count || 0, resId }; } async function fetchMessages(username, xToken, offset) { const resp = await fetch(`https://api.scratch.mit.edu/users/${username}/messages?limit=40&offset=${offset}`, { headers: { "x-token": xToken } }); if (!resp.ok) { if (resp.status === 404) return []; throw HTTPError.fromResponse(`Fetching message offset ${offset} for ${username} failed`, resp); } return resp.json(); } async function openDatabase() { if (IncognitoDatabase.isIncognito()) return incognitoDatabase; const DB_VERSION = 1; return idb.openDB("messaging", DB_VERSION, { upgrade(d) { d.createObjectStore("cache"); d.createObjectStore("lastUpdated"); d.createObjectStore("count"); } }); } async function openMessageCache(cookieStoreId, forceClear) { const db = await openDatabase(); if (db instanceof IncognitoDatabase) return; try { const tx = await db.transaction(["cache", "lastUpdated", "count"], "readwrite"); const lastUpdated = await tx.objectStore("lastUpdated").get(cookieStoreId); if (lastUpdated === undefined || forceClear || lastUpdated + 12 * 60 * 60 * 1000 < Date.now()) { await tx.objectStore("cache").put([], cookieStoreId); await tx.objectStore("count").put(0, cookieStoreId); await tx.objectStore("count").put(null, `${cookieStoreId}_resId`); } await tx.done; } finally { await db.close(); } } async function updateMessages(cookieStoreId, forceClear, username, xToken) { await openMessageCache(cookieStoreId, forceClear); if (username === null) return []; const msgCountData = await fetchMessageCount(username); const messageCount = await getUpToDateMsgCount(cookieStoreId, msgCountData); const maxPages = Math.min(Math.ceil(messageCount / 40) + 1, 25); const db = await openDatabase(); try { const messages = await db.get("cache", cookieStoreId); const firstTimestamp = messages[0] ? new Date(messages[0].datetime_created).getTime() : 0; const newlyAdded = []; const knownIds = new Set; fetching: for (let i2 = 0;i2 < maxPages; i2++) { const pageMessages = await fetchMessages(username, xToken, i2 * 40); for (const pageMessage of pageMessages) { if (new Date(pageMessage.datetime_created).getTime() <= firstTimestamp) break fetching; if (knownIds.has(pageMessage.id)) continue; newlyAdded.push(pageMessage); knownIds.add(pageMessage.id); } } messages.unshift(...newlyAdded); messages.length = Math.min(messages.length, maxPages * 40); newlyAdded.length = Math.min(newlyAdded.length, messageCount); const tx = await db.transaction(["cache", "lastUpdated", "count"], "readwrite"); await tx.objectStore("cache").put(messages, cookieStoreId); await tx.objectStore("lastUpdated").put(Date.now(), cookieStoreId); await tx.objectStore("count").put(messageCount, cookieStoreId); if (msgCountData.resId && !(db instanceof IncognitoDatabase)) { await tx.objectStore("count").put(msgCountData.resId, `${cookieStoreId}_resId`); } await tx.done; return newlyAdded; } finally { await db.close(); } } async function getUpToDateMsgCount(cookieStoreId, { count: responseMsgCount, resId }) { const db = await openDatabase(); if (db instanceof IncognitoDatabase) return responseMsgCount; try { const lastResId = await db.get("count", `${cookieStoreId}_resId`); if (lastResId && lastResId === resId) { console.log("Ignored network-cached response for message count endpoint."); const idbCount = await db.get("count", cookieStoreId); return idbCount; } else { return responseMsgCount; } } catch (err) { console.error(err); return responseMsgCount; } finally { await db.close(); } } function markAsRead(csrfToken) { return fetch("https://scratch.mit.edu/site-api/messages/messages-clear/?sareferer", { method: "POST", headers: { "x-csrftoken": csrfToken, "x-requested-with": "XMLHttpRequest" } }).then((res) => { if (!res.ok) throw HTTPError.fromResponse("Marking messages as read failed: ", res); }); } // scratch-addons-1.42.0/libraries/common/notification-util.js var id = 0; function create(opts) { if (typeof opts !== "object") { throw new Error("ScratchAddons exception: do not specify a notification ID."); } if (scratchAddons.muted) return Promise.resolve(null); const notifId = `${opts.base}__${Date.now()}_${id++}`; let newOpts; if (!/Chrom/.test(navigator.userAgent)) { newOpts = JSON.parse(JSON.stringify(opts)); delete newOpts.buttons; delete newOpts.requireInteraction; delete newOpts.silent; } else newOpts = opts; delete newOpts.base; newOpts.contextMessage = chrome.i18n.getMessage("extensionName"); return new Promise((resolve) => { chrome.notifications.create(notifId, newOpts, () => resolve(notifId)); }); } // scratch-addons-1.42.0/addons/scratch-notifier/comment-emojis.js var comment_emojis_default = Object.assign(Object.create(null), { "meow.png": "[meow emoji]", "gobo.png": "[gobo emoji]", "waffle.png": "[waffle emoji]", "taco.png": "\uD83C\uDF2E", "sushi.png": "\uD83C\uDF63", "apple.png": "\uD83C\uDF4E", "broccoli.png": "\uD83E\uDD66", "pizza.png": "\uD83C\uDF55", "candycorn.png": "[candycorn emoji]", "10mil.png": "\uD83C\uDF89", "map.png": "\uD83D\uDDFA️", "camera.png": "\uD83D\uDCF7", "suitcase.png": "\uD83D\uDCBC", "compass.png": "\uD83E\uDDED", "binoculars.png": "[binoculars emoji]", "cupcake.png": "\uD83E\uDDC1", "cat.png": "\uD83D\uDC31", "aww-cat.png": "\uD83D\uDE00", "cool-cat.png": "\uD83D\uDE0E", "tongue-out-cat.png": "\uD83D\uDE1B", "wink-cat.png": "\uD83D\uDE1C", "lol-cat.png": "\uD83D\uDE39", "upside-down-cat.png": "\uD83D\uDE43", "huh-cat.png": "\uD83E\uDD28", "love-it-cat.png": "\uD83D\uDE3B", "fav-it-cat.png": "\uD83E\uDD29", "rainbow-cat.png": "[rainbow cat emoji]", "pizza-cat.png": "[cat eating pizza emoji]", "blm.png": "✊\uD83C\uDFFF", "pride.png": "\uD83C\uDFF3️‍\uD83C\uDF08" }); // scratch-addons-1.42.0/addons/scratch-notifier/notifier.js var emojis = { addcomment: "comment", forumpost: "forum", loveproject: "heart", favoriteproject: "star", followuser: "follow", curatorinvite: "studio-add", remixproject: "remix", studioactivity: "studio", becomeownerstudio: "adminusers", becomehoststudio: "users" }; function openDatabase2() { return idb.openDB("notifier", 1, { upgrade(d) { d.createObjectStore("urls"); } }); } async function purgeDatabase() { const db = await openDatabase2(); try { await db.clear("urls"); } finally { await db.close(); } } async function cleanupDatabase(db) { const tx = db.transaction("urls", "readwrite"); const items = await tx.store.getAllKeys(); for (const key of items) { const time = Number(key.match(/\w+__(\d+)/)[1]); if (time + 3 * 24 * 60 * 60 * 1000 < Date.now()) { await tx.store.delete(key); } } await tx.done; } async function notifyMessage({ emoji, messageType, actor, fragment, commentee, commentUrl, title, element_id, parent_title, admin_actor }) { const msg = (key, params) => scratchAddons.l10n.get(`scratch-notifier/${key}`, params, `scratch-messaging/${key}`); const settings = scratchAddons.globalState.addonSettings["scratch-notifier"] || {}; let text = ""; let url; if (messageType.startsWith("addcomment/")) { url = commentUrl; if (title.length > 20) title = `${title.substring(0, 17).trimEnd()}...`; var notificationTitle; switch (messageType.split("/")[1]) { case "ownProjectNewComment": notificationTitle = msg("notif-own-project", { actor, title }); text = fragment; break; case "projectReplyToSelf": notificationTitle = msg("notif-project-reply", { actor, title }); text = fragment; break; case "ownProjectReplyToOther": notificationTitle = msg("notif-own-project-reply", { actor, commentee, title }); text = fragment; break; case "ownProfileNewComment": notificationTitle = msg("notif-profile", { actor }); text = fragment; break; case "ownProfileReplyToSelf": notificationTitle = msg("notif-own-profile-reply", { actor }); text = fragment; break; case "ownProfileReplyToOther": notificationTitle = msg("notif-own-profile-reply-other", { actor, commentee }); text = fragment; break; case "otherProfileReplyToSelf": notificationTitle = msg("notif-profile-reply", { actor, title }); text = fragment; break; case "studio": notificationTitle = msg("notif-studio-reply", { actor, title }); text = fragment; break; default: notificationTitle = msg("notif-comment"); break; } } else { switch (messageType) { case "forumpost": notificationTitle = msg("notif-forum", { title }); url = `https://scratch.mit.edu/discuss/topic/${element_id}/unread/`; break; case "loveproject": notificationTitle = msg("notif-love", { actor, title }); url = `https://scratch.mit.edu/users/${actor}/`; break; case "favoriteproject": notificationTitle = msg("notif-fav", { actor, title }); url = `https://scratch.mit.edu/users/${actor}/`; break; case "followuser": notificationTitle = msg("notif-follow", { actor }); url = `https://scratch.mit.edu/users/${actor}/`; break; case "curatorinvite": notificationTitle = msg("notif-invite", { actor, title }); url = `https://scratch.mit.edu/studios/${element_id}/curators/`; break; case "becomeownerstudio": notificationTitle = msg("notif-promotion", { actor, title }); url = `https://scratch.mit.edu/studios/${element_id}/curators/`; break; case "becomehoststudio": notificationTitle = msg("notif-host", { actor: admin_actor ? msg("st") : actor, title }); url = `https://scratch.mit.edu/studios/${element_id}/`; break; case "remixproject": notificationTitle = msg("notif-remix", { actor, parent_title, title }); url = `https://scratch.mit.edu/projects/${element_id}/`; break; case "studioactivity": notificationTitle = msg("notif-studio", { title }); url = `https://scratch.mit.edu/studios/${element_id}/activity`; break; default: notificationTitle = msg("notif-generic"); break; } } const soundSetting = settings.notification_sound; const notifId = await create({ base: "notifier", type: "basic", title: notificationTitle, iconUrl: emoji ? `../../images/icons/${emoji}.svg` : "/images/icon.png", message: text, buttons: [ { title: msg("open") }, { title: msg("clear") } ], silent: soundSetting === "system-default" ? false : true }); if (!notifId) return; const db = await openDatabase2(); try { await db.put("urls", url, notifId); } finally { await db.close(); } } var registerHandler = () => { chrome.notifications.onClicked.addListener(async (notifId) => { if (!notifId.startsWith("notifier")) return; chrome.notifications.clear(notifId); if (scratchAddons.globalState.addonSettings["scratch-notifier"]?.mark_as_read_when_clicked === true) { try { await markAsRead(scratchAddons.globalState.auth.csrfToken); updateBadge(scratchAddons.cookieStoreId); } catch (e2) { console.error("Marking message as read failed:", e2); } } const db = await openDatabase2(); try { const url = await db.get("urls", notifId); if (url) chrome.tabs.create({ url }); await cleanupDatabase(db); await db.delete("urls", notifId); } finally { await db.close(); } }); chrome.notifications.onButtonClicked.addListener(async (notifId, buttonIndex) => { if (!notifId.startsWith("notifier")) return; chrome.notifications.clear(notifId); if (buttonIndex === 0) openMessagesPage(); else { try { await markAsRead(scratchAddons.globalState.auth.csrfToken); updateBadge(scratchAddons.cookieStoreId); } catch (e2) { console.error("Marking message as read failed:", e2); } } const db = await openDatabase2(); try { await cleanupDatabase(db); await db.delete("urls", notifId); } finally { await db.close(); } }); chrome.notifications.onClosed.addListener(async (notifId) => { if (!notifId.startsWith("notifier")) return; const db = await openDatabase2(); try { await cleanupDatabase(db); await db.delete("urls", notifId); } finally { await db.close(); } }); }; if (chrome.notifications) registerHandler(); else { chrome.permissions.onAdded.addListener((permissions) => { if (permissions.permissions?.includes("notifications")) registerHandler(); }); } async function openMessagesPage() { chrome.tabs.query({ url: "https://scratch.mit.edu/messages*" }, (tabs) => { if (tabs[0]) { chrome.windows.update(tabs[0].windowId, { focused: true }); chrome.tabs.update(tabs[0].id, { active: true, url: "https://scratch.mit.edu/messages/" }); } else { chrome.tabs.create({ url: "https://scratch.mit.edu/messages/" }); } updateBadge(scratchAddons.cookieStoreId); }); } function notifyNewMessages(messages) { const settings = scratchAddons.globalState.addonSettings["scratch-notifier"] || {}; const username = scratchAddons.globalState.auth.username; if (messages === null || messages.length === 0 || scratchAddons.muted) return; messages = messages.slice(0, 20); let anyNotified = false; for (const message of messages) { let messageType = message.type; let commentUrl; if (message.type === "addcomment") { messageType += "/"; if (message.comment_type === 0) { const replyFor = message.commentee_username; if (replyFor === null) messageType += "ownProjectNewComment"; else if (replyFor === username) messageType += "projectReplyToSelf"; else messageType += "ownProjectReplyToOther"; commentUrl = `https://scratch.mit.edu/projects/${message.comment_obj_id}/#comments-${message.comment_id}`; } else if (message.comment_type === 1) { const profile = message.comment_obj_title; const replyFor = message.commentee_username; if (profile === username) { if (replyFor === null) messageType += "ownProfileNewComment"; else if (replyFor === username) messageType += "ownProfileReplyToSelf"; else messageType += "ownProfileReplyToOther"; } else { messageType += "otherProfileReplyToSelf"; } commentUrl = `https://scratch.mit.edu/users/${message.comment_obj_title}/#comments-${message.comment_id}`; } else if (message.comment_type === 2) { messageType += "studio"; commentUrl = `https://scratch.mit.edu/studios/${message.comment_obj_id}/comments/#comments-${message.comment_id}`; } } if (message.type === "addcomment") { if (messageType === "addcomment/ownProjectNewComment" || messageType === "addcomment/ownProjectReplyToOther") { if (settings.commentsonmyprojects_notifications === false) continue; } else { if (settings.commentsforme_notifications === false) continue; } } else { try { if (settings[`${message.type}_notifications`] === false) continue; } catch { console.warn(`Unexpected message type: ${message.type}`); contin