diff --git a/browser/GMPolyfill.js b/browser/GMPolyfill.js index 387389ce..1f1b8878 100644 --- a/browser/GMPolyfill.js +++ b/browser/GMPolyfill.js @@ -17,9 +17,10 @@ */ function parseHeaders(headers) { + const result = new Headers(); if (!headers) - return {}; - const result = {}; + return result; + const headersArr = headers.trim().split("\n"); for (var i = 0; i < headersArr.length; i++) { var row = headersArr[i]; @@ -27,13 +28,7 @@ function parseHeaders(headers) { , key = row.slice(0, index).trim().toLowerCase() , value = row.slice(index + 1).trim(); - if (result[key] === undefined) { - result[key] = value; - } else if (Array.isArray(result[key])) { - result[key].push(value); - } else { - result[key] = [result[key], value]; - } + result.append(key, value); } return result; } diff --git a/browser/userscript.meta.js b/browser/userscript.meta.js index 1d986aae..be3b0dd0 100644 --- a/browser/userscript.meta.js +++ b/browser/userscript.meta.js @@ -9,6 +9,7 @@ // @license GPL-3.0 // @match *://*.discord.com/* // @grant GM_xmlhttpRequest +// @grant unsafeWindow // @run-at document-start // @compatible chrome Chrome + Tampermonkey or Violentmonkey // @compatible firefox Firefox Tampermonkey diff --git a/package.json b/package.json index 60fa7703..4c6d5023 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.12.0", + "version": "1.12.2", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs index 0d796ddb..7d21cd24 100755 --- a/scripts/build/build.mjs +++ b/scripts/build/build.mjs @@ -112,7 +112,7 @@ const buildConfigs = ([ ...nodeCommonOpts, entryPoints: ["src/main/index.ts"], outfile: "dist/patcher.js", - footer: { js: "//# sourceURL=VencordPatcher\n" + sourceMapFooter("patcher") }, + footer: { js: "//# sourceURL=file:///VencordPatcher\n" + sourceMapFooter("patcher") }, sourcemap, plugins: [ // @ts-ignore this is never undefined @@ -131,7 +131,7 @@ const buildConfigs = ([ outfile: "dist/renderer.js", format: "iife", target: ["esnext"], - footer: { js: "//# sourceURL=VencordRenderer\n" + sourceMapFooter("renderer") }, + footer: { js: "//# sourceURL=file:///VencordRenderer\n" + sourceMapFooter("renderer") }, globalName: "Vencord", sourcemap, plugins: [ @@ -148,7 +148,7 @@ const buildConfigs = ([ ...nodeCommonOpts, entryPoints: ["src/preload.ts"], outfile: "dist/preload.js", - footer: { js: "//# sourceURL=VencordPreload\n" + sourceMapFooter("preload") }, + footer: { js: "//# sourceURL=file:///VencordPreload\n" + sourceMapFooter("preload") }, sourcemap, define: { ...defines, @@ -162,7 +162,7 @@ const buildConfigs = ([ ...nodeCommonOpts, entryPoints: ["src/main/index.ts"], outfile: "dist/vencordDesktopMain.js", - footer: { js: "//# sourceURL=VencordDesktopMain\n" + sourceMapFooter("vencordDesktopMain") }, + footer: { js: "//# sourceURL=file:///VencordDesktopMain\n" + sourceMapFooter("vencordDesktopMain") }, sourcemap, plugins: [ ...nodeCommonOpts.plugins, @@ -180,7 +180,7 @@ const buildConfigs = ([ outfile: "dist/vencordDesktopRenderer.js", format: "iife", target: ["esnext"], - footer: { js: "//# sourceURL=VencordDesktopRenderer\n" + sourceMapFooter("vencordDesktopRenderer") }, + footer: { js: "//# sourceURL=file:///VencordDesktopRenderer\n" + sourceMapFooter("vencordDesktopRenderer") }, globalName: "Vencord", sourcemap, plugins: [ @@ -197,7 +197,7 @@ const buildConfigs = ([ ...nodeCommonOpts, entryPoints: ["src/preload.ts"], outfile: "dist/vencordDesktopPreload.js", - footer: { js: "//# sourceURL=VencordPreload\n" + sourceMapFooter("vencordDesktopPreload") }, + footer: { js: "//# sourceURL=file:///VencordPreload\n" + sourceMapFooter("vencordDesktopPreload") }, sourcemap, define: { ...defines, diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs index 33168ff9..824194cf 100644 --- a/scripts/build/buildWeb.mjs +++ b/scripts/build/buildWeb.mjs @@ -82,7 +82,7 @@ const buildConfigs = [ { ...commonOptions, outfile: "dist/browser.js", - footer: { js: "//# sourceURL=VencordWeb" } + footer: { js: "//# sourceURL=file:///VencordWeb" } }, { ...commonOptions, @@ -91,7 +91,7 @@ const buildConfigs = [ ...commonOptions.define, IS_EXTENSION: "true" }, - footer: { js: "//# sourceURL=VencordWeb" } + footer: { js: "//# sourceURL=file:///VencordWeb" } }, { ...commonOptions, diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index 9bcbc7f0..5c1732aa 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -182,7 +182,7 @@ export const globPlugins = kind => ({ const mod = `p${i}`; code += `import ${mod} from "./${dir}/${fileName.replace(/\.tsx?$/, "")}";\n`; pluginsCode += `[${mod}.name]:${mod},\n`; - metaCode += `[${mod}.name]:${JSON.stringify({ folderName, userPlugin })},\n`; // TODO: add excluded plugins to display in the UI? + metaCode += `[${mod}.name]:${JSON.stringify({ folderName, userPlugin })},\n`; i++; } } diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts index 9502d382..4aad058c 100644 --- a/scripts/generateReport.ts +++ b/scripts/generateReport.ts @@ -261,7 +261,7 @@ page.on("console", async e => { const [, tag, message, otherMessage] = args as Array; switch (tag) { - case "WebpackInterceptor:": + case "WebpackPatcher:": const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/); const patchSlowMatch = message.match(/Patch by (.+?) (took [\d.]+?ms) \(Module id is (.+?)\): (.+)/); const match = patchFailMatch ?? patchSlowMatch; diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index e609d564..0ca20440 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -16,10 +16,10 @@ * along with this program. If not, see . */ +import { LazyComponent, LazyComponentWrapper } from "@utils/lazyReact"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; -import { LazyComponent, LazyComponentWrapper } from "@utils/react"; -import { React } from "@webpack/common"; +import type { React } from "@webpack/common"; import { ErrorCard } from "./ErrorCard"; @@ -46,7 +46,9 @@ const NO_ERROR = {}; // We might want to import this in a place where React isn't ready yet. // Thus, wrap in a LazyComponent const ErrorBoundary = LazyComponent(() => { - return class ErrorBoundary extends React.PureComponent> { + // This component is used in a lot of files which end up importing other Webpack commons and causing circular imports. + // For this reason, use a non import access here. + return class ErrorBoundary extends Vencord.Webpack.Common.React.PureComponent> { state = { error: NO_ERROR as any, stack: "", diff --git a/src/debug/runReporter.ts b/src/debug/runReporter.ts index 21802b6a..4ee2d394 100644 --- a/src/debug/runReporter.ts +++ b/src/debug/runReporter.ts @@ -46,13 +46,13 @@ async function runReporter() { for (const patch of patches) { if (!patch.all) { - new Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`); + new Logger("WebpackPatcher").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`); } } for (const [plugin, moduleId, match, totalTime] of patchTimings) { if (totalTime > 5) { - new Logger("WebpackInterceptor").warn(`Patch by ${plugin} took ${Math.round(totalTime * 100) / 100}ms (Module id is ${String(moduleId)}): ${match}`); + new Logger("WebpackPatcher").warn(`Patch by ${plugin} took ${Math.round(totalTime * 100) / 100}ms (Module id is ${String(moduleId)}): ${match}`); } } diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index d46e67b2..b00df6b0 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -27,7 +27,7 @@ import { openContributorModal } from "@components/PluginSettings/ContributorModa import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; -import { isPluginDev } from "@utils/misc"; +import { shouldShowContributorBadge } from "@utils/misc"; import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal"; import definePlugin from "@utils/types"; import { Forms, Toasts, UserStore } from "@webpack/common"; @@ -39,7 +39,7 @@ const ContributorBadge: ProfileBadge = { description: "Vencord Contributor", image: CONTRIBUTOR_BADGE, position: BadgePosition.START, - shouldShow: ({ userId }) => isPluginDev(userId), + shouldShow: ({ userId }) => shouldShowContributorBadge(userId), onClick: (_, { userId }) => openContributorModal(UserStore.getUser(userId)) }; diff --git a/src/plugins/_api/userSettings.ts b/src/plugins/_api/userSettings.ts index 3a00bc11..559de369 100644 --- a/src/plugins/_api/userSettings.ts +++ b/src/plugins/_api/userSettings.ts @@ -30,8 +30,8 @@ export default definePlugin({ replacement: [ // Main setting definition { - match: /(?<=INFREQUENT_USER_ACTION.{0,20},)useSetting:/, - replace: "userSettingsAPIGroup:arguments[0],userSettingsAPIName:arguments[1],$&" + match: /\.updateAsync\(.+?(?=,useSetting:)/, + replace: "$&,userSettingsAPIGroup:arguments[0],userSettingsAPIName:arguments[1]" }, // Selective wrapper { diff --git a/src/plugins/accountPanelServerProfile/index.tsx b/src/plugins/accountPanelServerProfile/index.tsx index ad63ba27..f871b8ee 100644 --- a/src/plugins/accountPanelServerProfile/index.tsx +++ b/src/plugins/accountPanelServerProfile/index.tsx @@ -72,7 +72,7 @@ export default definePlugin({ group: true, replacement: [ { - match: /let{speaking:\i/, + match: /let{ref:\i,speaking:\i/, replace: "$self.useAccountPanelRef();$&" }, { diff --git a/src/plugins/anonymiseFileNames/index.tsx b/src/plugins/anonymiseFileNames/index.tsx index 21f4e5c8..34b5a5fa 100644 --- a/src/plugins/anonymiseFileNames/index.tsx +++ b/src/plugins/anonymiseFileNames/index.tsx @@ -21,12 +21,10 @@ import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { findByCodeLazy, findByPropsLazy } from "@webpack"; - -type AnonUpload = Upload & { anonymise?: boolean; }; +import { findByCodeLazy } from "@webpack"; +import { useState } from "@webpack/common"; const ActionBarIcon = findByCodeLazy(".actionBarIcon)"); -const UploadDraft = findByPropsLazy("popFirstFile", "update"); const enum Methods { Random, @@ -34,6 +32,7 @@ const enum Methods { Timestamp, } +const ANONYMISE_UPLOAD_SYMBOL = Symbol("vcAnonymise"); const tarExtMatcher = /\.tar\.\w+$/; const settings = definePluginSettings({ @@ -69,41 +68,44 @@ export default definePlugin({ name: "AnonymiseFileNames", authors: [Devs.fawn], description: "Anonymise uploaded file names", + settings, + patches: [ { - find: "instantBatchUpload:", + find: 'type:"UPLOAD_START"', replacement: { - match: /uploadFiles:(\i),/, - replace: - "uploadFiles:(...args)=>(args[0].uploads.forEach(f=>f.filename=$self.anonymise(f)),$1(...args)),", + match: /await \i\.uploadFiles\((\i),/, + replace: "$1.forEach($self.anonymise),$&" }, }, { find: 'addFilesTo:"message.attachments"', replacement: { - match: /(\i.uploadFiles\((\i),)/, - replace: "$2.forEach(f=>f.filename=$self.anonymise(f)),$1" + match: /\i.uploadFiles\((\i),/, + replace: "$1.forEach($self.anonymise),$&" } }, { find: "#{intl::ATTACHMENT_UTILITIES_SPOILER}", replacement: { match: /(?<=children:\[)(?=.{10,80}tooltip:.{0,100}#{intl::ATTACHMENT_UTILITIES_SPOILER})/, - replace: "arguments[0].canEdit!==false?$self.renderIcon(arguments[0]):null," + replace: "arguments[0].canEdit!==false?$self.AnonymiseUploadButton(arguments[0]):null," }, }, ], - settings, - renderIcon: ErrorBoundary.wrap(({ upload, channelId, draftType }: { upload: AnonUpload; draftType: unknown; channelId: string; }) => { - const anonymise = upload.anonymise ?? settings.store.anonymiseByDefault; + AnonymiseUploadButton: ErrorBoundary.wrap(({ upload }: { upload: Upload; }) => { + const [anonymise, setAnonymise] = useState(upload[ANONYMISE_UPLOAD_SYMBOL] ?? settings.store.anonymiseByDefault); + + function onToggleAnonymise() { + upload[ANONYMISE_UPLOAD_SYMBOL] = !anonymise; + setAnonymise(!anonymise); + } + return ( { - upload.anonymise = !anonymise; - UploadDraft.update(channelId, upload.id, draftType, {}); // dummy update so component rerenders - }} + onClick={onToggleAnonymise} > {anonymise ? @@ -113,25 +115,31 @@ export default definePlugin({ ); }, { noop: true }), - anonymise(upload: AnonUpload) { - if ((upload.anonymise ?? settings.store.anonymiseByDefault) === false) return upload.filename; - - const file = upload.filename; - const tarMatch = tarExtMatcher.exec(file); - const extIdx = tarMatch?.index ?? file.lastIndexOf("."); - const ext = extIdx !== -1 ? file.slice(extIdx) : ""; - - switch (settings.store.method) { - case Methods.Random: - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - return Array.from( - { length: settings.store.randomisedLength }, - () => chars[Math.floor(Math.random() * chars.length)] - ).join("") + ext; - case Methods.Consistent: - return settings.store.consistent + ext; - case Methods.Timestamp: - return Date.now() + ext; + anonymise(upload: Upload) { + if ((upload[ANONYMISE_UPLOAD_SYMBOL] ?? settings.store.anonymiseByDefault) === false) { + return; } - }, + + const originalFileName = upload.filename; + const tarMatch = tarExtMatcher.exec(originalFileName); + const extIdx = tarMatch?.index ?? originalFileName.lastIndexOf("."); + const ext = extIdx !== -1 ? originalFileName.slice(extIdx) : ""; + + const newFilename = (() => { + switch (settings.store.method) { + case Methods.Random: + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + return Array.from( + { length: settings.store.randomisedLength }, + () => chars[Math.floor(Math.random() * chars.length)] + ).join("") + ext; + case Methods.Consistent: + return settings.store.consistent + ext; + case Methods.Timestamp: + return Date.now() + ext; + } + })(); + + upload.filename = newFilename; + } }); diff --git a/src/plugins/arRPC.web/index.tsx b/src/plugins/arRPC.web/index.tsx index 94c507f3..426b9a9b 100644 --- a/src/plugins/arRPC.web/index.tsx +++ b/src/plugins/arRPC.web/index.tsx @@ -42,6 +42,7 @@ export default definePlugin({ description: "Client plugin for arRPC to enable RPC on Discord Web (experimental)", authors: [Devs.Ducko], reporterTestable: ReporterTestable.None, + hidden: IS_VESKTOP || "legcord" in window, settingsAboutComponent: () => ( <> @@ -73,9 +74,6 @@ export default definePlugin({ }, async start() { - // Legcord comes with its own arRPC implementation, so this plugin just confuses users - if ("legcord" in window) return; - if (ws) ws.close(); ws = new WebSocket("ws://127.0.0.1:1337"); // try to open WebSocket diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts index e8150f8f..feeada9e 100644 --- a/src/plugins/consoleShortcuts/index.ts +++ b/src/plugins/consoleShortcuts/index.ts @@ -74,6 +74,22 @@ function makeShortcuts() { }; } + function findStoreWrapper(findStore: typeof Webpack.findStore) { + const cache = new Map(); + + return function (storeName: string) { + const cacheKey = String(storeName); + if (cache.has(cacheKey)) return cache.get(cacheKey); + + let store: unknown; + try { + store = findStore(storeName); + } catch { } + if (store) cache.set(cacheKey, store); + return store; + }; + } + let fakeRenderWin: WeakRef | undefined; const find = newFindWrapper(f => f); const findByProps = newFindWrapper(filters.byProps); @@ -98,7 +114,7 @@ function makeShortcuts() { findComponentByCode: newFindWrapper(filters.componentByCode), findAllComponentsByCode: (...code: string[]) => findAll(filters.componentByCode(...code)), findExportedComponent: (...props: string[]) => findByProps(...props)[props[0]], - findStore: newFindWrapper(filters.byStoreName), + findStore: findStoreWrapper(Webpack.findStore), PluginsApi: { getter: () => Vencord.Plugins }, plugins: { getter: () => Vencord.Plugins.plugins }, Settings: { getter: () => Vencord.Settings }, diff --git a/src/plugins/customidle/README.md b/src/plugins/customIdle/README.md similarity index 100% rename from src/plugins/customidle/README.md rename to src/plugins/customIdle/README.md diff --git a/src/plugins/customidle/index.ts b/src/plugins/customIdle/index.ts similarity index 100% rename from src/plugins/customidle/index.ts rename to src/plugins/customIdle/index.ts diff --git a/src/plugins/emoteCloner/index.tsx b/src/plugins/expressionCloner/index.tsx similarity index 97% rename from src/plugins/emoteCloner/index.tsx rename to src/plugins/expressionCloner/index.tsx index ffc2307e..3a73489c 100644 --- a/src/plugins/emoteCloner/index.tsx +++ b/src/plugins/expressionCloner/index.tsx @@ -17,6 +17,7 @@ */ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { migratePluginSettings } from "@api/Settings"; import { CheckedTextInput } from "@components/CheckedTextInput"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; @@ -165,7 +166,7 @@ async function doClone(guildId: string, data: Sticker | Emoji) { message = JSON.parse(e.text).message; } catch { } - new Logger("EmoteCloner").error("Failed to clone", data.name, "to", guildId, e); + new Logger("ExpressionCloner").error("Failed to clone", data.name, "to", guildId, e); Toasts.show({ message: "Failed to clone: " + message, type: Toasts.Type.FAILURE, @@ -364,10 +365,11 @@ const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { t } }; +migratePluginSettings("ExpressionCloner", "EmoteCloner"); export default definePlugin({ - name: "EmoteCloner", + name: "ExpressionCloner", description: "Allows you to clone Emotes & Stickers to your own server (right click them)", - tags: ["StickerCloner"], + tags: ["StickerCloner", "EmoteCloner", "EmojiCloner"], authors: [Devs.Ven, Devs.Nuckyz], contextMenus: { "message": messageContextMenuPatch, diff --git a/src/plugins/invisibleChat.desktop/index.tsx b/src/plugins/invisibleChat.desktop/index.tsx index f5e8cbb5..ab124192 100644 --- a/src/plugins/invisibleChat.desktop/index.tsx +++ b/src/plugins/invisibleChat.desktop/index.tsx @@ -110,7 +110,7 @@ export default definePlugin({ patches: [ { // Indicator - find: "#{intl::MESSAGE_EDITED}", + find: ".SEND_FAILED,", replacement: { match: /let\{className:\i,message:\i[^}]*\}=(\i)/, replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&" diff --git a/src/plugins/lastfm/index.tsx b/src/plugins/lastfmRichPresence/index.tsx similarity index 100% rename from src/plugins/lastfm/index.tsx rename to src/plugins/lastfmRichPresence/index.tsx diff --git a/src/plugins/memberCount/OnlineMemberCountStore.ts b/src/plugins/memberCount/OnlineMemberCountStore.ts index d74bea2a..54cc4697 100644 --- a/src/plugins/memberCount/OnlineMemberCountStore.ts +++ b/src/plugins/memberCount/OnlineMemberCountStore.ts @@ -7,7 +7,7 @@ import { proxyLazy } from "@utils/lazy"; import { sleep } from "@utils/misc"; import { Queue } from "@utils/Queue"; -import { Flux, FluxDispatcher, GuildChannelStore, PrivateChannelsStore } from "@webpack/common"; +import { ChannelActionCreators, Flux, FluxDispatcher, GuildChannelStore } from "@webpack/common"; export const OnlineMemberCountStore = proxyLazy(() => { const preloadQueue = new Queue(); @@ -22,7 +22,7 @@ export const OnlineMemberCountStore = proxyLazy(() => { async _ensureCount(guildId: string) { if (onlineMemberMap.has(guildId)) return; - await PrivateChannelsStore.preload(guildId, GuildChannelStore.getDefaultChannel(guildId).id); + await ChannelActionCreators.preload(guildId, GuildChannelStore.getDefaultChannel(guildId).id); } ensureCount(guildId?: string) { diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index ffe5286e..3c7aecae 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -464,19 +464,21 @@ export default definePlugin({ { // Message content renderer + find: ".SEND_FAILED,", + replacement: { + // Render editHistory behind the message content + match: /\.isFailed]:.+?children:\[/, + replace: "$&arguments[0]?.message?.editHistory?.length>0&&$self.renderEdits(arguments[0])," + } + }, + + { find: "#{intl::MESSAGE_EDITED}", - replacement: [ - { - // Render editHistory in the deepest div for message content - match: /(\)\("div",\{id:.+?children:\[)/, - replace: "$1 (!!arguments[0].message.editHistory?.length && $self.renderEdits(arguments[0]))," - }, - { - // Make edit marker clickable - match: /"span",\{(?=className:\i\.edited,)/, - replace: "$self.EditMarker,{message:arguments[0].message," - } - ] + replacement: { + // Make edit marker clickable + match: /"span",\{(?=className:\i\.edited,)/, + replace: "$self.EditMarker,{message:arguments[0].message," + } }, { diff --git a/src/plugins/moreCommands/index.ts b/src/plugins/moreCommands/index.ts deleted file mode 100644 index 02f3c373..00000000 --- a/src/plugins/moreCommands/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated, Samu and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { ApplicationCommandInputType, findOption, OptionalMessageOption, RequiredMessageOption, sendBotMessage } from "@api/Commands"; -import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; - - -function mock(input: string): string { - let output = ""; - for (let i = 0; i < input.length; i++) { - output += i % 2 ? input[i].toUpperCase() : input[i].toLowerCase(); - } - return output; -} - -export default definePlugin({ - name: "MoreCommands", - description: "echo, lenny, mock", - authors: [Devs.Arjix, Devs.echo, Devs.Samu], - commands: [ - { - name: "echo", - description: "Sends a message as Clyde (locally)", - options: [OptionalMessageOption], - inputType: ApplicationCommandInputType.BOT, - execute: (opts, ctx) => { - const content = findOption(opts, "message", ""); - - sendBotMessage(ctx.channel.id, { content }); - }, - }, - { - name: "lenny", - description: "Sends a lenny face", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " ( ͡° ͜ʖ ͡°)" - }), - }, - { - name: "mock", - description: "mOcK PeOpLe", - options: [RequiredMessageOption], - execute: opts => ({ - content: mock(findOption(opts, "message", "")) - }), - }, - ] -}); diff --git a/src/plugins/moreKaomoji/index.ts b/src/plugins/moreKaomoji/index.ts deleted file mode 100644 index 9a691fc4..00000000 --- a/src/plugins/moreKaomoji/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { findOption, OptionalMessageOption } from "@api/Commands"; -import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; - -export default definePlugin({ - name: "MoreKaomoji", - description: "Adds more Kaomoji to discord. ヽ(´▽`)/", - authors: [Devs.JacobTm], - commands: [ - { name: "dissatisfaction", description: " >﹏<" }, - { name: "smug", description: "ಠ_ಠ" }, - { name: "happy", description: "ヽ(´▽`)/" }, - { name: "crying", description: "ಥ_ಥ" }, - { name: "angry", description: "ヽ(`Д´)ノ" }, - { name: "anger", description: "ヽ(o`皿′o)ノ" }, - { name: "joy", description: "<( ̄︶ ̄)>" }, - { name: "blush", description: "૮ ˶ᵔ ᵕ ᵔ˶ ა" }, - { name: "confused", description: "(•ิ_•ิ)?" }, - { name: "sleeping", description: "(ᴗ_ᴗ)" }, - { name: "laughing", description: "o(≧▽≦)o" }, - - ].map(data => ({ - ...data, - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + data.description - }) - })) -}); diff --git a/src/plugins/moyai/index.ts b/src/plugins/moyai/index.ts deleted file mode 100644 index 649b1fbb..00000000 --- a/src/plugins/moyai/index.ts +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { definePluginSettings } from "@api/Settings"; -import { makeRange } from "@components/PluginSettings/components/SettingSliderComponent"; -import { Devs } from "@utils/constants"; -import { sleep } from "@utils/misc"; -import definePlugin, { OptionType } from "@utils/types"; -import { RelationshipStore, SelectedChannelStore, UserStore } from "@webpack/common"; -import { Message, ReactionEmoji } from "discord-types/general"; - -interface IMessageCreate { - type: "MESSAGE_CREATE"; - optimistic: boolean; - isPushNotification: boolean; - channelId: string; - message: Message; -} - -interface IReactionAdd { - type: "MESSAGE_REACTION_ADD"; - optimistic: boolean; - channelId: string; - messageId: string; - messageAuthorId: string; - userId: "195136840355807232"; - emoji: ReactionEmoji; -} - -interface IVoiceChannelEffectSendEvent { - type: string; - emoji?: ReactionEmoji; // Just in case... - channelId: string; - userId: string; - animationType: number; - animationId: number; -} - -const MOYAI = "🗿"; -const MOYAI_URL = - "https://raw.githubusercontent.com/MeguminSama/VencordPlugins/main/plugins/moyai/moyai.mp3"; -const MOYAI_URL_HD = - "https://raw.githubusercontent.com/MeguminSama/VencordPlugins/main/plugins/moyai/moyai_hd.wav"; - -const settings = definePluginSettings({ - volume: { - description: "Volume of the 🗿🗿🗿", - type: OptionType.SLIDER, - markers: makeRange(0, 1, 0.1), - default: 0.5, - stickToMarkers: false - }, - quality: { - description: "Quality of the 🗿🗿🗿", - type: OptionType.SELECT, - options: [ - { label: "Normal", value: "Normal", default: true }, - { label: "HD", value: "HD" } - ], - }, - triggerWhenUnfocused: { - description: "Trigger the 🗿 even when the window is unfocused", - type: OptionType.BOOLEAN, - default: true - }, - ignoreBots: { - description: "Ignore bots", - type: OptionType.BOOLEAN, - default: true - }, - ignoreBlocked: { - description: "Ignore blocked users", - type: OptionType.BOOLEAN, - default: true - } -}); - -export default definePlugin({ - name: "Moyai", - authors: [Devs.Megu, Devs.Nuckyz], - description: "🗿🗿🗿🗿🗿🗿🗿🗿", - settings, - - flux: { - async MESSAGE_CREATE({ optimistic, type, message, channelId }: IMessageCreate) { - if (optimistic || type !== "MESSAGE_CREATE") return; - if (message.state === "SENDING") return; - if (settings.store.ignoreBots && message.author?.bot) return; - if (settings.store.ignoreBlocked && RelationshipStore.isBlocked(message.author?.id)) return; - if (!message.content) return; - if (channelId !== SelectedChannelStore.getChannelId()) return; - - const moyaiCount = getMoyaiCount(message.content); - - for (let i = 0; i < moyaiCount; i++) { - boom(); - await sleep(300); - } - }, - - MESSAGE_REACTION_ADD({ optimistic, type, channelId, userId, messageAuthorId, emoji }: IReactionAdd) { - if (optimistic || type !== "MESSAGE_REACTION_ADD") return; - if (settings.store.ignoreBots && UserStore.getUser(userId)?.bot) return; - if (settings.store.ignoreBlocked && RelationshipStore.isBlocked(messageAuthorId)) return; - if (channelId !== SelectedChannelStore.getChannelId()) return; - - const name = emoji.name.toLowerCase(); - if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return; - - boom(); - }, - - VOICE_CHANNEL_EFFECT_SEND({ emoji }: IVoiceChannelEffectSendEvent) { - if (!emoji?.name) return; - const name = emoji.name.toLowerCase(); - if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return; - - boom(); - } - } -}); - -function countOccurrences(sourceString: string, subString: string) { - let i = 0; - let lastIdx = 0; - while ((lastIdx = sourceString.indexOf(subString, lastIdx) + 1) !== 0) - i++; - - return i; -} - -function countMatches(sourceString: string, pattern: RegExp) { - if (!pattern.global) - throw new Error("pattern must be global"); - - let i = 0; - while (pattern.test(sourceString)) - i++; - - return i; -} - -const customMoyaiRe = //gi; - -function getMoyaiCount(message: string) { - const count = countOccurrences(message, MOYAI) - + countMatches(message, customMoyaiRe); - - return Math.min(count, 10); -} - -function boom() { - if (!settings.store.triggerWhenUnfocused && !document.hasFocus()) return; - const audioElement = document.createElement("audio"); - - audioElement.src = settings.store.quality === "HD" - ? MOYAI_URL_HD - : MOYAI_URL; - - audioElement.volume = settings.store.volume; - audioElement.play(); -} diff --git a/src/plugins/noRPC.discordDesktop/index.ts b/src/plugins/noRPC.discordDesktop/index.ts deleted file mode 100644 index 4c6319e5..00000000 --- a/src/plugins/noRPC.discordDesktop/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; - -export default definePlugin({ - name: "NoRPC", - description: "Disables Discord's RPC server.", - authors: [Devs.Cyn], - patches: [ - { - find: '.ensureModule("discord_rpc")', - replacement: { - match: /\.ensureModule\("discord_rpc"\)\.then\(\(.+?\)}\)}/, - replace: '.ensureModule("discord_rpc")}', - }, - }, - ], -}); diff --git a/src/plugins/noServerEmojis/index.ts b/src/plugins/noServerEmojis/index.ts index 6a39f55c..cd950b42 100644 --- a/src/plugins/noServerEmojis/index.ts +++ b/src/plugins/noServerEmojis/index.ts @@ -30,7 +30,7 @@ export default definePlugin({ { find: "}searchWithoutFetchingLatest(", replacement: { - match: /searchWithoutFetchingLatest.{20,300}get\((\i).{10,40}?reduce\(\((\i),(\i)\)=>\{/, + match: /\.get\((\i)\)\.nameMatchesChain\(\i\)\.reduce\(\((\i),(\i)\)=>\{/, replace: "$& if ($self.shouldSkip($1, $3)) return $2;" } } diff --git a/src/plugins/noUnblockToJump/README.md b/src/plugins/noUnblockToJump/README.md index 326bca3b..630ef7dc 100644 --- a/src/plugins/noUnblockToJump/README.md +++ b/src/plugins/noUnblockToJump/README.md @@ -1,5 +1,5 @@ # No Unblock To Jump -Removes the popup preventing you to jump to a message from a blocked/ignored user (eg: in search results) +Removes the popup preventing you to jump to a message from a blocked/ignored user or likely spammer (eg: in search results) ![A modal popup telling you to unblock a user to jump their message](https://github.com/user-attachments/assets/0e4b859d-f3b3-4101-9a83-829afb473d1e) diff --git a/src/plugins/noUnblockToJump/index.ts b/src/plugins/noUnblockToJump/index.ts index cb379bf8..b36a2e24 100644 --- a/src/plugins/noUnblockToJump/index.ts +++ b/src/plugins/noUnblockToJump/index.ts @@ -21,7 +21,7 @@ import definePlugin from "@utils/types"; export default definePlugin({ name: "NoUnblockToJump", - description: "Allows you to jump to messages of blocked users without unblocking them", + description: "Allows you to jump to messages of blocked or ignored users and likely spammers without unblocking them", authors: [Devs.dzshn], patches: [ { diff --git a/src/plugins/openInApp/index.ts b/src/plugins/openInApp/index.ts index 1c90b529..e1133234 100644 --- a/src/plugins/openInApp/index.ts +++ b/src/plugins/openInApp/index.ts @@ -106,13 +106,15 @@ export default definePlugin({ } ] }, - { - find: ".CONNECTED_ACCOUNT_VIEWED,", + + // User Profile Modal & User Profile Modal v2 + ...[".__invalid_connectedAccountOpenIconContainer", ".BLUESKY||"].map(find => ({ + find, replacement: { - match: /(?<=href:\i,onClick:(\i)=>\{)(?=.{0,10}\i=(\i)\.type,.{0,100}CONNECTED_ACCOUNT_VIEWED)/, + match: /(?<=onClick:(\i)=>\{)(?=.{0,100}\.CONNECTED_ACCOUNT_VIEWED)(?<==(\i)\.metadata.+?)/, replace: "if($self.handleAccountView($1,$2.type,$2.id)) return;" } - } + })) ], async handleLink(data: { href: string; }, event?: MouseEvent) { diff --git a/src/plugins/partyMode/index.ts b/src/plugins/partyMode/index.ts deleted file mode 100644 index f7cddbf9..00000000 --- a/src/plugins/partyMode/index.ts +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { definePluginSettings } from "@api/Settings"; -import { Devs } from "@utils/constants"; -import definePlugin, { OptionType, ReporterTestable } from "@utils/types"; -import { FluxDispatcher } from "@webpack/common"; - -const enum Intensity { - Normal, - Better, - ProjectX, -} - -const settings = definePluginSettings({ - superIntensePartyMode: { - description: "Party intensity", - type: OptionType.SELECT, - options: [ - { label: "Normal", value: Intensity.Normal, default: true }, - { label: "Better", value: Intensity.Better }, - { label: "Project X", value: Intensity.ProjectX }, - ], - restartNeeded: false, - onChange: setSettings - }, -}); - -export default definePlugin({ - name: "PartyMode", - description: "Allows you to use party mode cause the party never ends ✨", - authors: [Devs.UwUDev], - reporterTestable: ReporterTestable.None, - settings, - - start() { - setPoggerState(true); - setSettings(settings.store.superIntensePartyMode); - }, - - stop() { - setPoggerState(false); - }, -}); - -function setPoggerState(state: boolean) { - FluxDispatcher.dispatch({ - type: "POGGERMODE_SETTINGS_UPDATE", - settings: { - enabled: state, - settingsVisible: state - } - }); -} - -function setSettings(intensity: Intensity) { - const state = { - screenshakeEnabledLocations: { 0: true, 1: true, 2: true }, - shakeIntensity: 1, - confettiSize: 16, - confettiCount: 5, - combosRequiredCount: 1 - }; - - switch (intensity) { - case Intensity.Normal: { - Object.assign(state, { - screenshakeEnabledLocations: { 0: true, 1: false, 2: false }, - combosRequiredCount: 5 - }); - break; - } - case Intensity.Better: { - Object.assign(state, { - confettiSize: 12, - confettiCount: 8, - }); - break; - } - case Intensity.ProjectX: { - Object.assign(state, { - shakeIntensity: 20, - confettiSize: 25, - confettiCount: 15, - }); - break; - } - } - - FluxDispatcher.dispatch({ - type: "POGGERMODE_SETTINGS_UPDATE", - settings: state - }); -} diff --git a/src/plugins/permissionsViewer/index.tsx b/src/plugins/permissionsViewer/index.tsx index 8d0cd4b1..3fd4428c 100644 --- a/src/plugins/permissionsViewer/index.tsx +++ b/src/plugins/permissionsViewer/index.tsx @@ -26,7 +26,7 @@ import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { Button, ChannelStore, Dialog, GuildMemberStore, GuildStore, match, Menu, PermissionsBits, Popout, TooltipContainer, UserStore } from "@webpack/common"; +import { Button, ChannelStore, Dialog, GuildMemberStore, GuildStore, match, Menu, PermissionsBits, Popout, TooltipContainer, useRef, UserStore } from "@webpack/common"; import type { Guild, GuildMember } from "discord-types/general"; import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions"; @@ -173,32 +173,38 @@ export default definePlugin({ } ], - ViewPermissionsButton: ErrorBoundary.wrap(({ guild, guildMember }: { guild: Guild; guildMember: GuildMember; }) => ( - ( - - - - )} - > - {popoutProps => ( - - - - )} - - ), { noop: true }), + ViewPermissionsButton: ErrorBoundary.wrap(({ guild, guildMember }: { guild: Guild; guildMember: GuildMember; }) => { + const buttonRef = useRef(null); + + return ( + ( + + + + )} + > + {popoutProps => ( + + + + )} + + ); + }, { noop: true }), contextMenus: { "user-context": makeContextMenuPatch("roles", MenuItemParentType.User), diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index 71f87b13..dc60bb64 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -153,7 +153,7 @@ export default definePlugin({ }, // Messages { - find: "#{intl::MESSAGE_EDITED}", + find: ".SEND_FAILED,", replacement: { match: /(?<=isUnsupported\]:(\i)\.isUnsupported\}\),)(?=children:\[)/, replace: "style:$self.useMessageColorsStyle($1)," diff --git a/src/plugins/shikiCodeblocks.desktop/api/languages.ts b/src/plugins/shikiCodeblocks.desktop/api/languages.ts index f14a4dc2..baf7a548 100644 --- a/src/plugins/shikiCodeblocks.desktop/api/languages.ts +++ b/src/plugins/shikiCodeblocks.desktop/api/languages.ts @@ -19,7 +19,7 @@ import { ILanguageRegistration } from "@vap/shiki"; export const VPC_REPO = "Vap0r1ze/vapcord"; -export const VPC_REPO_COMMIT = "88a7032a59cca40da170926651b08201ea3b965a"; +export const VPC_REPO_COMMIT = "4d0e4b420fb1e4358852bbd18c804a6f5e54c0d7"; export const vpcRepoAssets = `https://raw.githubusercontent.com/${VPC_REPO}/${VPC_REPO_COMMIT}/assets/shiki-codeblocks`; export const vpcRepoGrammar = (fileName: string) => `${vpcRepoAssets}/${fileName}`; export const vpcRepoLanguages = `${vpcRepoAssets}/languages.json`; @@ -46,7 +46,7 @@ export interface LanguageJson { export const languages: Record = {}; export const loadLanguages = async () => { - const langsJson: LanguageJson[] = await fetch(vpcRepoLanguages).then(res => res.json()); + const langsJson: LanguageJson[] = await fetch(vpcRepoLanguages).then(res => res.ok ? res.json() : []); const loadedLanguages = Object.fromEntries( langsJson.map(lang => [lang.id, { ...lang, diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 7a3dd9fb..cd1b19ba 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -356,15 +356,10 @@ export default definePlugin({ find: "#{intl::CHANNEL_CALL_CURRENT_SPEAKER}", replacement: [ { - // Remove the divider and the open chat button for the HiddenChannelLockScreen - match: /"more-options-popout"\)\),(?<=channel:(\i).+?inCall:(\i).+?)/, - replace: (m, channel, inCall) => `${m}${inCall}||!$self.isHiddenChannel(${channel},true)&&` - }, - { - // Remove invite users button for the HiddenChannelLockScreen - match: /"popup".{0,100}?if\((?<=channel:(\i).+?inCall:(\i).+?)/, - replace: (m, channel, inCall) => `${m}(${inCall}||!$self.isHiddenChannel(${channel},true))&&` - }, + // Remove the open chat button for the HiddenChannelLockScreen + match: /(?<=&&)\i\.push\(.{0,120}"chat-spacer"/, + replace: "(arguments[0]?.inCall||!$self.isHiddenChannel(arguments[0]?.channel,true))&&$&" + } ] }, { @@ -397,8 +392,8 @@ export default definePlugin({ replacement: [ { // Render our HiddenChannelLockScreen component instead of the main stage channel component - match: /"124px".+?children:(?<=let \i,{channel:(\i).+?)(?=.{0,20}?}\)}function)/, - replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?$self.HiddenChannelLockScreen(${channel}):` + match: /screenMessage:(\i)\?.+?children:(?=!\1)(?<=let \i,{channel:(\i).+?)/, + replace: (m, _isPopoutOpen, channel) => `${m}$self.isHiddenChannel(${channel})?$self.HiddenChannelLockScreen(${channel}):` }, { // Disable useless components for the HiddenChannelLockScreen of stage channels @@ -427,8 +422,8 @@ export default definePlugin({ }, { // Remove the open chat button for the HiddenChannelLockScreen - match: /"recents".+?&&(?=\(.+?channelId:(\i)\.id,showRequestToSpeakSidebar)/, - replace: (m, channel) => `${m}!$self.isHiddenChannel(${channel})&&` + match: /(?<=&&)\(0,\i\.jsxs?\).{0,180}\.buttonIcon/, + replace: "!$self.isHiddenChannel(arguments[0]?.channel,true)&&$&" } ] }, diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx index c59df00a..754af009 100644 --- a/src/plugins/vencordToolbox/index.tsx +++ b/src/plugins/vencordToolbox/index.tsx @@ -24,7 +24,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { findComponentByCodeLazy } from "@webpack"; -import { Menu, Popout, useState } from "@webpack/common"; +import { Menu, Popout, useRef, useState } from "@webpack/common"; import type { ReactNode } from "react"; const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"'); @@ -95,6 +95,7 @@ function VencordPopoutIcon(isShown: boolean) { } function VencordPopoutButton() { + const buttonRef = useRef(null); const [show, setShow] = useState(false); return ( @@ -104,10 +105,12 @@ function VencordPopoutButton() { animation={Popout.Animation.NONE} shouldShow={show} onRequestClose={() => setShow(false)} + targetElementRef={buttonRef} renderPopout={() => VencordPopout(() => setShow(false))} > {(_, { isShown }) => ( setShow(v => !v)} tooltip={isShown ? null : "Vencord Toolbox"} diff --git a/src/plugins/webScreenShareFixes.web/index.ts b/src/plugins/webScreenShareFixes.web/index.ts index 8d1ab582..1d2be2c0 100644 --- a/src/plugins/webScreenShareFixes.web/index.ts +++ b/src/plugins/webScreenShareFixes.web/index.ts @@ -21,8 +21,12 @@ export default definePlugin({ replace: '"x-google-max-bitrate=".concat("80_000")' }, { - match: /;level-asymmetry-allowed=1/, + match: ";level-asymmetry-allowed=1", replace: ";b=AS:800000;level-asymmetry-allowed=1" + }, + { + match: "useinbandfec=1", + replace: "useinbandfec=1;stereo=1;sprop-stereo=1" } ] } diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index ba60d98a..e04ad201 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -17,7 +17,7 @@ */ import { MessageObject } from "@api/MessageEvents"; -import { ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, IconUtils, InviteActions, MessageActions, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; +import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, IconUtils, InviteActions, MessageActions, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; import { Channel, Guild, Message, User } from "discord-types/general"; import { Except } from "type-fest"; @@ -91,7 +91,7 @@ export function getCurrentGuild(): Guild | undefined { } export function openPrivateChannel(userId: string) { - PrivateChannelsStore.openPrivateChannel(userId); + ChannelActionCreators.openPrivateChannel(userId); } export const enum Theme { diff --git a/src/utils/misc.ts b/src/utils/misc.ts index 7f9f6e59..7028e00b 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -92,6 +92,7 @@ export function identity(value: T): T { export const isMobile = navigator.userAgent.includes("Mobi"); export const isPluginDev = (id: string) => Object.hasOwn(DevsById, id); +export const shouldShowContributorBadge = (id: string) => isPluginDev(id) && DevsById[id].badge !== false; export function pluralise(amount: number, singular: string, plural = singular + "s") { return amount === 1 ? `${amount} ${singular}` : `${amount} ${plural}`; diff --git a/src/utils/quickCss.ts b/src/utils/quickCss.ts index c1e11759..f32ae7e6 100644 --- a/src/utils/quickCss.ts +++ b/src/utils/quickCss.ts @@ -19,7 +19,6 @@ import { Settings, SettingsStore } from "@api/Settings"; import { ThemeStore } from "@webpack/common"; - let style: HTMLStyleElement; let themesStyle: HTMLStyleElement; @@ -61,7 +60,10 @@ async function initThemes() { const { themeLinks, enabledThemes } = Settings; // "darker" and "midnight" both count as dark - const activeTheme = ThemeStore.theme === "light" ? "light" : "dark"; + // This function is first called on DOMContentLoaded, so ThemeStore may not have been loaded yet + const activeTheme = ThemeStore == null + ? undefined + : ThemeStore.theme === "light" ? "light" : "dark"; const links = themeLinks .map(rawLink => { @@ -98,6 +100,14 @@ document.addEventListener("DOMContentLoaded", () => { SettingsStore.addChangeListener("themeLinks", initThemes); SettingsStore.addChangeListener("enabledThemes", initThemes); + if (!IS_WEB) { + VencordNative.quickCss.addThemeChangeListener(initThemes); + } +}); + +export function initQuickCssThemeStore() { + initThemes(); + let currentTheme = ThemeStore.theme; ThemeStore.addChangeListener(() => { if (currentTheme === ThemeStore.theme) return; @@ -105,7 +115,4 @@ document.addEventListener("DOMContentLoaded", () => { currentTheme = ThemeStore.theme; initThemes(); }); - - if (!IS_WEB) - VencordNative.quickCss.addThemeChangeListener(initThemes); -}); +} diff --git a/src/webpack/common/internal.tsx b/src/webpack/common/internal.tsx index 090d9898..5e8c6052 100644 --- a/src/webpack/common/internal.tsx +++ b/src/webpack/common/internal.tsx @@ -17,9 +17,7 @@ */ import { LazyComponent, LazyComponentWrapper } from "@utils/react"; - -// eslint-disable-next-line path-alias/no-relative -import { FilterFn, filters, lazyWebpackSearchHistory, waitFor } from "../webpack"; +import { FilterFn, filters, lazyWebpackSearchHistory, waitFor } from "@webpack"; export function waitForComponent = React.ComponentType & Record>(name: string, filter: FilterFn | string | string[]) { if (IS_REPORTER) lazyWebpackSearchHistory.push(["waitForComponent", Array.isArray(filter) ? filter : [filter]]); diff --git a/src/webpack/common/menu.ts b/src/webpack/common/menu.ts index 5b1056dd..2bb05f36 100644 --- a/src/webpack/common/menu.ts +++ b/src/webpack/common/menu.ts @@ -16,8 +16,8 @@ * along with this program. If not, see . */ -// eslint-disable-next-line path-alias/no-relative -import { filters, mapMangledModuleLazy, waitFor, wreq } from "../webpack"; +import { filters, mapMangledModuleLazy, waitFor, wreq } from "@webpack"; + import type * as t from "./types/menu"; export const Menu = {} as t.Menu; diff --git a/src/webpack/common/react.ts b/src/webpack/common/react.ts index 89b19506..b8b0e753 100644 --- a/src/webpack/common/react.ts +++ b/src/webpack/common/react.ts @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -// eslint-disable-next-line path-alias/no-relative -import { findByCodeLazy, findByPropsLazy, waitFor } from "../webpack"; +import { findByCodeLazy, findByPropsLazy, waitFor } from "@webpack"; export let React: typeof import("react"); export let useState: typeof React.useState; diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index 518f13e2..1dcda187 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -16,10 +16,9 @@ * along with this program. If not, see . */ +import { findByCodeLazy, findByPropsLazy } from "@webpack"; import type * as Stores from "discord-types/stores"; -// eslint-disable-next-line path-alias/no-relative -import { findByCodeLazy, findByPropsLazy } from "../webpack"; import { waitForStore } from "./internal"; import * as t from "./types/stores"; @@ -33,7 +32,7 @@ export let MessageStore: Omit & GenericStore getMessages(chanId: string): any; }; -// this is not actually a FluxStore +// TODO: The correct name for this is ChannelActionCreators and it has already been exported again from utils. Remove this export once enough time has passed export const PrivateChannelsStore = findByPropsLazy("openPrivateChannel"); export let PermissionStore: GenericStore; export let GuildChannelStore: GenericStore; @@ -86,4 +85,8 @@ waitForStore("GuildChannelStore", m => GuildChannelStore = m); waitForStore("MessageStore", m => MessageStore = m); waitForStore("WindowStore", m => WindowStore = m); waitForStore("EmojiStore", m => EmojiStore = m); -waitForStore("ThemeStore", m => ThemeStore = m); +waitForStore("ThemeStore", m => { + ThemeStore = m; + // Importing this directly can easily cause circular imports. For this reason, use a non import access here. + Vencord.QuickCss.initQuickCssThemeStore(); +}); diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts index b5f4ff5c..7a9e848b 100644 --- a/src/webpack/common/types/components.d.ts +++ b/src/webpack/common/types/components.d.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import type { ComponentClass, ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PointerEvent, PropsWithChildren, ReactNode, Ref } from "react"; +import type { ComponentClass, ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PointerEvent, PropsWithChildren, ReactNode, Ref, RefObject } from "react"; export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code"; @@ -426,6 +426,7 @@ export type Popout = ComponentType<{ } ): ReactNode; shouldShow?: boolean; + targetElementRef: RefObject; renderPopout(args: { closePopout(): void; isPositioned: boolean; diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 0396f0f3..05eb5729 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -16,16 +16,15 @@ * along with this program. If not, see . */ +import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "@webpack"; import type { Channel } from "discord-types/general"; -// eslint-disable-next-line path-alias/no-relative -import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "../webpack"; import type * as t from "./types/utils"; export let FluxDispatcher: t.FluxDispatcher; waitFor(["dispatch", "subscribe"], m => { FluxDispatcher = m; - // Non import call to avoid circular dependency + // Non import access to avoid circular dependency Vencord.Plugins.subscribeAllPluginsFluxEvents(m); const cb = () => { @@ -35,7 +34,7 @@ waitFor(["dispatch", "subscribe"], m => { m.subscribe("CONNECTION_OPEN", cb); }); -export let ComponentDispatch; +export let ComponentDispatch: any; waitFor(["dispatchToLastSubscribed"], m => ComponentDispatch = m); export const Constants: t.Constants = mapMangledModuleLazy('ME:"/users/@me"', { @@ -177,6 +176,7 @@ export const MessageActions = findByPropsLazy("editMessage", "sendMessage"); export const MessageCache = findByPropsLazy("clearCache", "_channelMessages"); export const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal"); export const InviteActions = findByPropsLazy("resolveInvite"); +export const ChannelActionCreators = findByPropsLazy("openPrivateChannel"); export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getUserAvatarURL"); diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index 4f5899bc..d9b35f01 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -60,7 +60,7 @@ export function getFactoryPatchedBy(moduleId: PropertyKey, webpackRequire = wreq return webpackRequire.m[moduleId]?.[SYM_PATCHED_BY]; } -const logger = new Logger("WebpackInterceptor", "#8caaee"); +const logger = new Logger("WebpackPatcher", "#8caaee"); /** Whether we tried to fallback to the WebpackRequire of the factory, or disabled patches */ let wreqFallbackApplied = false; @@ -106,6 +106,13 @@ define(Function.prototype, "m", { const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1]; + // Currently, sentry and libdiscore Webpack instances are not meant to be patched. + // As an extra measure, take advatange of the fact their files include the names and return early if it's one of them. + // Later down we also include other measures to avoid patching them. + if (["sentry", "libdiscore"].some(name => fileName?.toLowerCase()?.includes(name))) { + return; + } + // Define a setter for the bundlePath property of WebpackRequire. Only Webpack instances which include chunk loading functionality, // like the main Discord Webpack, have this property. // So if the setter is called with the Discord bundlePath, this means we should patch this instance and initialize the internal references to WebpackRequire. @@ -116,7 +123,10 @@ define(Function.prototype, "m", { define(this, "p", { value: bundlePath }); clearTimeout(bundlePathTimeout); - if (bundlePath !== "/assets/") { + // libdiscore init Webpack instance always returns a constant string for the js filename of a chunk. + // In that case, avoid patching this instance, + // as it runs before the main Webpack instance and will make the WebpackRequire fallback not work properly, or init an wrongful main WebpackRequire. + if (bundlePath !== "/assets/" || /(?:=>|{return)"[^"]/.exec(String(this.u))) { return; } @@ -129,9 +139,9 @@ define(Function.prototype, "m", { } }); - // In the past, the sentry Webpack instance which we also wanted to patch used to rely on chunks being loaded before initting sentry. + // In the past, the sentry Webpack instance which we also wanted to patch used to rely on chunks being loaded before initing sentry. // This Webpack instance did not include actual chunk loading, and only awaited for them to be loaded, which means it did not include the bundlePath property. - // To keep backwards compability, in case this is ever the case again, and keep patching this type of instance, we explicity patch instances which include wreq.O and not wreq.p. + // To keep backwards compability, if this is ever the case again, and keep patching this type of instance, we explicity patch instances which include wreq.O and not wreq.p. // Since we cannot check what is the bundlePath of the instance to filter for the Discord bundlePath, we only patch it if wreq.p is not included, // which means the instance relies on another instance which does chunk loading, and that makes it very likely to only target Discord Webpack instances like the old sentry. @@ -436,12 +446,21 @@ function runFactoryWithWrap(patchedFactory: PatchedModuleFactory, thisArg: unkno callback(exports, module.id); continue; } + } catch (err) { + logger.error( + "Error while filtering or firing callback for Webpack waitFor subscription:\n", err, + "\n\nModule exports:", exports, + "\n\nFilter:", filter, + "\n\nCallback:", callback + ); + } - if (typeof exports !== "object") { - continue; - } + if (typeof exports !== "object") { + continue; + } - for (const exportKey in exports) { + for (const exportKey in exports) { + try { // Some exports might have not been initialized yet due to circular imports, so try catch it. try { var exportValue = exports[exportKey]; @@ -454,9 +473,14 @@ function runFactoryWithWrap(patchedFactory: PatchedModuleFactory, thisArg: unkno callback(exportValue, module.id); break; } + } catch (err) { + logger.error( + "Error while filtering or firing callback for Webpack waitFor subscription:\n", err, + "\n\nExport value:", exports, + "\n\nFilter:", filter, + "\n\nCallback:", callback + ); } - } catch (err) { - logger.error("Error while firing callback for Webpack waitFor subscription:\n", err, filter, callback); } } @@ -567,7 +591,7 @@ function patchFactory(moduleId: PropertyKey, originalFactory: AnyModuleFactory): } code = newCode; - patchedSource = `// Webpack Module ${String(moduleId)} - Patched by ${pluginsList.join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${String(moduleId)}`; + patchedSource = `// Webpack Module ${String(moduleId)} - Patched by ${pluginsList.join(", ")}\n${newCode}\n//# sourceURL=file:///WebpackModule${String(moduleId)}`; patchedFactory = (0, eval)(patchedSource); if (!patchedBy.has(patch.plugin)) { diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index c1847474..98e7d4d1 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -145,9 +145,17 @@ function makePropertyNonEnumerable(target: Record, key: Proper } export function _blacklistBadModules(requireCache: NonNullable, exports: ModuleExports, moduleId: PropertyKey) { - if (shouldIgnoreValue(exports)) { - makePropertyNonEnumerable(requireCache, moduleId); - return true; + try { + if (shouldIgnoreValue(exports)) { + makePropertyNonEnumerable(requireCache, moduleId); + return true; + } + } catch (err) { + logger.error( + "Error while blacklisting module:\n", err, + "\n\nModule id:", moduleId, + "\n\nModule exports:", exports, + ); } if (typeof exports !== "object") { @@ -156,17 +164,25 @@ export function _blacklistBadModules(requireCache: NonNullable