to-userscript
Version:
Converts simple browser extensions to userscripts
1,381 lines (1,360 loc) • 112 kB
JavaScript
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