to-userscript
Version:
Converts simple browser extensions to userscripts
1,286 lines (1,282 loc) • 57 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);
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);