// The userscript host Vencord's web build expects, for a Discord tab. build.mjs puts this in an // outer function and vendor/vencord/Vencord.user.js in a function inside it (wrapVencord), so the // two names below are what the userscript's free identifiers `unsafeWindow` and // `GM_xmlhttpRequest` resolve to. The file is // then served first-party and run by the page engine like any of the site's own scripts, so // `window`, `location` and `fetch` here are the Discord tab's. /* eslint-disable no-unused-vars */ var unsafeWindow = window; // Every request Vencord makes goes through GM_xmlhttpRequest. Only Discord's own hosts are // reached; badges.vencord.dev is answered here with an empty list so the always-on badge plugin // stays quiet; everything else (cloud sync, third-party plugin services, local RPC) is refused. var GM_xmlhttpRequest = (function () { var ALLOW = /^(?:[a-z0-9-]+\.)*(?:discord\.com|discordapp\.com|discordapp\.net|discord\.media)$/; var LOCAL = { "badges.vencord.dev": "{}" }; return function (o) { var done = false, timer = 0, ctl = new AbortController(); function fin(f, arg) { if (done) return; done = true; clearTimeout(timer); try { if (typeof f === "function") f(arg); } catch (e) { /* the caller's handler */ } } (async function () { var u = new URL(String(o.url), location.href); if (u.protocol !== "https:") throw new Error("refused"); var host = u.hostname.toLowerCase(); if (Object.prototype.hasOwnProperty.call(LOCAL, host)) { fin(o.onload, { status: 200, statusText: "OK", response: new Blob([LOCAL[host]], { type: "application/json" }), responseHeaders: "content-type: application/json", finalUrl: u.href }); return; } if (!ALLOW.test(host)) throw new Error("refused"); timer = setTimeout(function () { ctl.abort(); fin(o.ontimeout); }, o.timeout > 0 ? o.timeout : 30000); var r = await fetch(u.href, { method: o.method || "GET", headers: o.headers, body: o.data, cache: o.cache, credentials: "omit", signal: ctl.signal }); var b = await r.blob(); var h = []; r.headers.forEach(function (v, k) { h.push(k + ": " + v); }); fin(o.onload, { status: r.status, statusText: r.statusText, response: b, responseHeaders: h.join("\r\n"), finalUrl: r.url }); })().catch(function () { fin(o.onerror); }); return { abort: function () { ctl.abort(); fin(o.onabort); } }; }; })(); // Settings that are always off here, before any of Vencord runs: // - plugins that load code from other sites or talk to services other than Discord: oneko // (fetches its script from GitHub), WebRichPresence and XSOverlay (local sockets), ReviewDB, // Decor, USRBG, Translate, Dearrow, MusicRichPresence, GifProviderSwitcher, SpotifyControls, // ClearURLs, petpet and ReactErrorDecoder (each calls its own outside service); // - cloud settings sync, which sends the settings to api.vencord.dev; // - online themes: their links are stylesheets on other sites. Local QuickCSS and uploaded themes // stay. // Vencord reads its settings once, through VencordNative.settings.get(). The copy it gets has // these values pinned: writes to them are ignored, so neither saved settings nor a toggle turns // them back on, and they save as off. The plugins are also taken out of the plugin list once the // script has run, so they cannot be started from the Plugins page. var RV_FORCED_OFF = ["oneko", "WebRichPresence (arRPC)", "XSOverlay", "ReviewDB", "Decor", "USRBG", "Translate", "Dearrow", "MusicRichPresence", "GifProviderSwitcher", "SpotifyControls", "ClearURLs", "petpet", "ReactErrorDecoder"]; (function () { function pin(o, k, read) { Object.defineProperty(o, k, { get: read, set: function () {}, enumerable: true, configurable: false }); } function own(o, k) { var v = Object.prototype.hasOwnProperty.call(o, k) ? o[k] : null; return v && typeof v === "object" && !Array.isArray(v) ? v : {}; } function harden(s) { if (!s || typeof s !== "object") s = {}; pin(s, "themeLinks", function () { return []; }); var cloud = own(s, "cloud"); pin(cloud, "authenticated", function () { return false; }); pin(cloud, "settingsSync", function () { return false; }); pin(cloud, "url", function () { return "https://api.vencord.dev/"; }); pin(s, "cloud", function () { return cloud; }); var plugins = own(s, "plugins"); RV_FORCED_OFF.forEach(function (name) { var p = own(plugins, name); pin(p, "enabled", function () { return false; }); pin(plugins, name, function () { return p; }); }); pin(s, "plugins", function () { return plugins; }); return s; } var native; Object.defineProperty(window, "VencordNative", { configurable: true, get: function () { return native; }, set: function (v) { if (v && v.settings && typeof v.settings.get === "function") { var get = v.settings.get; v.settings.get = function () { return harden(get.apply(this, arguments)); }; } native = v; }, }); // After the rest of this file has run, before any of Discord's scripts. Promise.resolve().then(function () { try { var list = window.Vencord && window.Vencord.Plugins && window.Vencord.Plugins.plugins; if (list) RV_FORCED_OFF.forEach(function (name) { delete list[name]; }); } catch (e) { /* Vencord did not start */ } }); })(); // Synchronous XHR does not work inside the engines' pages. Vencord makes one: NoTrack fetches the // script that is starting a webpack runtime to see whether it is Discord's Sentry bundle. Here a // synchronous request is answered at once from the script's name instead, and asynchronous // requests are left alone. Only Vencord's own code sees this binding. var XMLHttpRequest = (function (X) { function name(u) { try { return decodeURIComponent(String(u)); } catch (e) { return String(u); } } return class extends X { open(method, url, async) { this.__rvSync = async === false ? name(url) : null; if (this.__rvSync === null) return super.open.apply(this, arguments); } send(body) { if (this.__rvSync === null) return super.send(body); var text = /\/assets\/[^?#]*sentry[^?#]*\.js/i.test(this.__rvSync) ? ".DiscordSentry=" : ""; Object.defineProperty(this, "responseText", { value: text }); Object.defineProperty(this, "status", { value: 200 }); Object.defineProperty(this, "readyState", { value: 4 }); } }; })(window.XMLHttpRequest);