{isLocked ?
:
}
@@ -128,8 +156,10 @@ function VoiceChannelTooltip({ channel, isLocked }: VoiceChannelTooltipProps) {
);
}
-export interface VoiceChannelIndicatorProps {
+interface VoiceChannelIndicatorProps {
userId: string;
+ isMessageIndicator?: boolean;
+ isProfile?: boolean;
isActionButton?: boolean;
shouldHighlight?: boolean;
}
diff --git a/src/plugins/userVoiceShow/index.tsx b/src/plugins/userVoiceShow/index.tsx
index 3d119c43..5f1a05e5 100644
--- a/src/plugins/userVoiceShow/index.tsx
+++ b/src/plugins/userVoiceShow/index.tsx
@@ -20,8 +20,9 @@ import "./style.css";
import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
+import { addNicknameIcon, removeNicknameIcon } from "@api/NicknameIcons";
import { definePluginSettings } from "@api/Settings";
-import { Devs } from "@utils/constants";
+import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { VoiceChannelIndicator } from "./components";
@@ -50,39 +51,11 @@ const settings = definePluginSettings({
export default definePlugin({
name: "UserVoiceShow",
description: "Shows an indicator when a user is in a Voice Channel",
- authors: [Devs.Nuckyz, Devs.LordElias],
- dependencies: ["MemberListDecoratorsAPI", "MessageDecorationsAPI"],
+ authors: [Devs.Nuckyz, Devs.LordElias, EquicordDevs.omaw],
+ dependencies: ["NicknameIconsAPI", "MemberListDecoratorsAPI", "MessageDecorationsAPI"],
settings,
patches: [
- // User Popout, Full Size Profile, Direct Messages Side Profile
- {
- find: "#{intl::USER_PROFILE_LOAD_ERROR}",
- replacement: {
- match: /(\.fetchError.+?\?)null/,
- replace: (_, rest) => `${rest}$self.VoiceChannelIndicator({userId:arguments[0]?.userId})`
- },
- predicate: () => settings.store.showInUserProfileModal
- },
- // To use without the MemberList decorator API
- /* // Guild Members List
- {
- find: ".lostPermission)",
- replacement: {
- match: /\.lostPermission\).+?(?=avatar:)/,
- replace: "$&children:[$self.VoiceChannelIndicator({userId:arguments[0]?.user?.id})],"
- },
- predicate: () => settings.store.showVoiceChannelIndicator
- },
- // Direct Messages List
- {
- find: "PrivateChannel.renderAvatar",
- replacement: {
- match: /#{intl::CLOSE_DM}.+?}\)(?=])/,
- replace: "$&,$self.VoiceChannelIndicator({userId:arguments[0]?.user?.id})"
- },
- predicate: () => settings.store.showVoiceChannelIndicator
- }, */
// Friends List
{
find: "null!=this.peopleListItemRef.current",
@@ -95,15 +68,19 @@ export default definePlugin({
],
start() {
+ if (settings.store.showInUserProfileModal) {
+ addNicknameIcon("UserVoiceShow", ({ userId }) =>
);
+ }
if (settings.store.showInMemberList) {
addMemberListDecorator("UserVoiceShow", ({ user }) => user == null ? null :
);
}
if (settings.store.showInMessages) {
- addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null :
);
+ addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null :
);
}
},
stop() {
+ removeNicknameIcon("UserVoiceShow");
removeMemberListDecorator("UserVoiceShow");
removeMessageDecoration("UserVoiceShow");
},
diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx
index 2cc09fb6..6c45a799 100644
--- a/src/plugins/viewIcons/index.tsx
+++ b/src/plugins/viewIcons/index.tsx
@@ -190,7 +190,7 @@ export default definePlugin({
},
patches: [
- // Avatar component used in User DMs "User Profile" popup in the right and Profiles Modal pfp
+ // Avatar component used in User DMs "User Profile" popup in the right and User Profile Modal pfp
{
find: ".overlay:void 0,status:",
replacement: [
diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts
index c0fe203e..aee65252 100644
--- a/src/plugins/webContextMenus.web/index.ts
+++ b/src/plugins/webContextMenus.web/index.ts
@@ -17,11 +17,12 @@
*/
import { definePluginSettings } from "@api/Settings";
+import { copyToClipboard } from "@utils/clipboard";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { saveFile } from "@utils/web";
import { filters, mapMangledModuleLazy } from "@webpack";
-import { Clipboard, ComponentDispatch } from "@webpack/common";
+import { ComponentDispatch } from "@webpack/common";
const ctxMenuCallbacks = mapMangledModuleLazy('.tagName)==="TEXTAREA"||', {
contextMenuCallbackWeb: filters.byCode('.tagName)==="INPUT"||'),
@@ -35,17 +36,16 @@ async function fetchImage(url: string) {
return await res.blob();
}
-let result;
-switch (true) {
- case IS_VESKTOP || IS_EQUIBOP:
- case "legcord" in window:
- case "goofcord" in window:
- result = true;
- break;
- default:
- result = false;
-}
+let requiredByPlatform = false;
+let hideSetting = false;
+if (IS_VESKTOP || IS_EQUIBOP) {
+ requiredByPlatform = true;
+ hideSetting = true;
+} else if ("legcord" in window || "goofcord" in window) {
+ requiredByPlatform = true;
+ hideSetting = false;
+}
const settings = definePluginSettings({
// This needs to be all in one setting because to enable any of these, we need to make Discord use their desktop context
@@ -53,15 +53,15 @@ const settings = definePluginSettings({
addBack: {
type: OptionType.BOOLEAN,
description: "Add back the Discord context menus for images, links and the chat input bar",
- default: false,
+ default: true,
restartNeeded: true,
// Web slate menu has proper spellcheck suggestions and image context menu is also pretty good,
// so disable this by default. Vesktop just doesn't, so we force enable it there
- hidden: result,
+ hidden: hideSetting,
}
});
-const shouldAddBackMenus = () => result || settings.store.addBack;
+const shouldAddBackMenus = () => hideSetting || settings.store.addBack;
const MEDIA_PROXY_URL = "https://media.discordapp.net";
const CDN_URL = "cdn.discordapp.com";
@@ -90,7 +90,7 @@ export default definePlugin({
description: "Re-adds context menus missing in the web version of Discord: Links & Images (Copy/Open Link/Image), Text Area (Copy, Cut, Paste, SpellCheck)",
authors: [Devs.Ven],
enabledByDefault: true,
- required: result,
+ required: requiredByPlatform,
settings,
@@ -125,11 +125,18 @@ export default definePlugin({
// Fix silly Discord calling the non web support copy
{
match: /\i\.\i\.copy/,
- replace: "Vencord.Webpack.Common.Clipboard.copy"
+ replace: "Vencord.Util.copyToClipboard"
}
]
},
+ {
+ find: "Copy image not supported",
+ replacement: {
+ match: /(?<=(?:canSaveImage|canCopyImage)\(\i?\)\{.{0,50})!\i\.isPlatformEmbedded/g,
+ replace: "false"
+ }
+ },
// Add back Copy & Save Image
{
find: 'id:"copy-image"',
@@ -140,7 +147,7 @@ export default definePlugin({
replace: "false"
},
{
- match: /return\s*?\[\i\.\i\.canCopyImage\(\)/,
+ match: /return\s*?\[.{0,50}?(?=\?.{0,100}?id:"copy-image")/,
replace: "return [true"
},
{
@@ -234,7 +241,7 @@ export default definePlugin({
},
{
match: /\i\.\i\.copy(?=\(\i)/,
- replace: "Vencord.Webpack.Common.Clipboard.copy"
+ replace: "Vencord.Util.copyToClipboard"
}
],
all: true,
@@ -299,7 +306,7 @@ export default definePlugin({
const selection = document.getSelection();
if (!selection) return;
- Clipboard.copy(selection.toString());
+ copyToClipboard(selection.toString());
},
cut() {
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/plugins/youtubeAdblock.desktop/native.ts b/src/plugins/youtubeAdblock.desktop/native.ts
index ae05d646..82e89806 100644
--- a/src/plugins/youtubeAdblock.desktop/native.ts
+++ b/src/plugins/youtubeAdblock.desktop/native.ts
@@ -5,16 +5,27 @@
*/
import { RendererSettings } from "@main/settings";
-import { app } from "electron";
+import { app, WebFrameMain } from "electron";
import adguard from "file://adguard.js?minify";
+type YoutubeFrame = WebFrameMain & { executed?: boolean; };
+
app.on("browser-window-created", (_, win) => {
+ let watchTogetherFrame: YoutubeFrame | null = null;
win.webContents.on("frame-created", (_, { frame }) => {
frame?.once("dom-ready", () => {
if (!RendererSettings.store.plugins?.YoutubeAdblock?.enabled) return;
- if (frame.url.includes("youtube.com/embed/") || (frame.url.includes("discordsays") && frame.url.includes("youtube.com"))) {
+ if (frame.url.includes("youtube.com/embed")) {
frame.executeJavaScript(adguard);
+ } else if (frame.url.includes("880218394199220334.discordsays.com/")) {
+ watchTogetherFrame = frame;
+ } else if (watchTogetherFrame && frame.top?.frames.includes(watchTogetherFrame)) {
+ const youtubeEmbed = watchTogetherFrame.frames.find(frame => frame.url.includes("youtube.com/embed/")) as YoutubeFrame | undefined;
+ if (youtubeEmbed !== undefined && youtubeEmbed.executed === undefined) {
+ youtubeEmbed.executed = true;
+ youtubeEmbed.executeJavaScript(adguard);
+ }
}
});
});
diff --git a/src/utils/clipboard.ts b/src/utils/clipboard.ts
new file mode 100644
index 00000000..c098a549
--- /dev/null
+++ b/src/utils/clipboard.ts
@@ -0,0 +1,9 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2025 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+export function copyToClipboard(text: string): Promise
{
+ return IS_DISCORD_DESKTOP ? DiscordNative.clipboard.copy(text) : navigator.clipboard.writeText(text);
+}
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index a8cf16d7..fa32700b 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -494,7 +494,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "Sqaaakoi",
id: 259558259491340288n
},
- Byron: {
+ Byeoon: {
name: "byeoon",
id: 1167275288036655133n
},
@@ -709,9 +709,9 @@ export const EquicordDevs = Object.freeze({
name: "Megal",
id: 387790666484285441n
},
- Woosh: {
- name: "w00shh.",
- id: 919239894327521361n
+ Synth: {
+ name: "synthxcx",
+ id: 934393331562205195n
},
Hanzy: {
name: "hanzydev",
@@ -815,7 +815,7 @@ export const EquicordDevs = Object.freeze({
},
vmohammad: {
name: "vMohammad",
- id: 921098159348924457n
+ id: 840854894881538079n
},
SpikeHD: {
name: "SpikeHD",
@@ -1042,6 +1042,38 @@ export const EquicordDevs = Object.freeze({
name: "smuki",
id: 691517398523576331n
},
+ ItsAlex: {
+ name: "ItsAlex",
+ id: 551023598203043840n
+ },
+ Byeoon: {
+ name: "byeoon",
+ id: 1167275288036655133n
+ },
+ Skully: {
+ name: "Skully",
+ id: 150298098516754432n
+ },
+ Buzzy: {
+ name: "Buzzy",
+ id: 1273353654644117585n
+ },
+ Reycko: {
+ name: "Reycko",
+ id: 1123725368004726794n,
+ },
+ Campfire: {
+ name: "Campfire",
+ id: 376414446840578081n,
+ },
+ Cootshk: {
+ name: "Cootshk",
+ id: 921605971577548820n,
+ },
+ sliwka: {
+ name: "sliwka",
+ id: 1165286199628419129n,
+ },
} satisfies Record);
// iife so #__PURE__ works correctly
diff --git a/src/utils/discord.css b/src/utils/discord.css
deleted file mode 100644
index 8d4f811a..00000000
--- a/src/utils/discord.css
+++ /dev/null
@@ -1,24 +0,0 @@
-
-.vc-position-inherit {
- position: inherit;
-}
-
-/**
- * copy pasted from discord css. not really webpack-findable since it's the only class in the module
-**/
-
-.vc-image-modal {
- background: transparent !important;
- box-shadow: none !important;
- display: flex;
- justify-content: center;
- align-items: center;
- border-radius: 0;
-}
-
-@media(width <= 485px) {
- .vc-image-modal {
- overflow: visible;
- overflow: initial;
- }
-}
diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx
index ec96d0d4..e04ad201 100644
--- a/src/utils/discord.tsx
+++ b/src/utils/discord.tsx
@@ -16,10 +16,8 @@
* along with this program. If not, see .
*/
-import "./discord.css";
-
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";
@@ -93,7 +91,7 @@ export function getCurrentGuild(): Guild | undefined {
}
export function openPrivateChannel(userId: string) {
- PrivateChannelsStore.openPrivateChannel(userId);
+ ChannelActionCreators.openPrivateChannel(userId);
}
export const enum Theme {
@@ -143,9 +141,6 @@ export function sendMessage(
*/
export function openImageModal(item: Except, mediaModalProps?: Omit) {
return openMediaModal({
- className: "vc-image-modal",
- fit: "vc-position-inherit",
- shouldAnimateCarousel: true,
items: [{
type: "IMAGE",
original: item.original ?? item.url,
diff --git a/src/utils/index.ts b/src/utils/index.ts
index ed347fdc..70ac9d42 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -19,6 +19,7 @@
export * from "../shared/debounce";
export * from "../shared/onceDefined";
export * from "./ChangeList";
+export * from "./clipboard";
export * from "./constants";
export * from "./discord";
export * from "./guards";
diff --git a/src/utils/lazyReact.tsx b/src/utils/lazyReact.tsx
index 4896a058..0a15bf92 100644
--- a/src/utils/lazyReact.tsx
+++ b/src/utils/lazyReact.tsx
@@ -4,26 +4,28 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
-import { ComponentType } from "react";
+import type { ComponentType } from "react";
import { makeLazy } from "./lazy";
const NoopComponent = () => null;
+export type LazyComponentWrapper = ComponentType & { $$vencordGetWrappedComponent(): ComponentType; };
+
/**
* A lazy component. The factory method is called on first render.
* @param factory Function returning a Component
* @param attempts How many times to try to get the component before giving up
* @returns Result of factory function
*/
-export function LazyComponent(factory: () => React.ComponentType, attempts = 5) {
+export function LazyComponent(factory: () => ComponentType, attempts = 5): LazyComponentWrapper> {
const get = makeLazy(factory, attempts);
const LazyComponent = (props: T) => {
const Component = get() ?? NoopComponent;
return ;
};
- LazyComponent.$$vencordInternal = get;
+ LazyComponent.$$vencordGetWrappedComponent = get;
- return LazyComponent as ComponentType;
+ return LazyComponent;
}
diff --git a/src/utils/misc.ts b/src/utils/misc.ts
index eb952952..e8bfdf2a 100644
--- a/src/utils/misc.ts
+++ b/src/utils/misc.ts
@@ -16,8 +16,9 @@
* along with this program. If not, see .
*/
-import { Clipboard, Toasts } from "@webpack/common";
+import { Toasts } from "@webpack/common";
+import { copyToClipboard } from "./clipboard";
import { EquicordDevsById, VencordDevsById } from "./constants";
/**
@@ -35,12 +36,8 @@ export function sleep(ms: number): Promise {
return new Promise(r => setTimeout(r, ms));
}
-export function copyWithToast(text: string, toastMessage = "Copied to clipboard!") {
- if (Clipboard.SUPPORTS_COPY) {
- Clipboard.copy(text);
- } else {
- toastMessage = "Your browser does not support copying to clipboard";
- }
+export async function copyWithToast(text: string, toastMessage = "Copied to clipboard!") {
+ await copyToClipboard(text);
Toasts.show({
message: toastMessage,
id: Toasts.genId(),
diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx
index d06e5803..17bf3987 100644
--- a/src/utils/modal.tsx
+++ b/src/utils/modal.tsx
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-import { filters, findModuleId, mapMangledModuleLazy, proxyLazyWebpack, wreq } from "@webpack";
+import { filters, findByCodeLazy, mapMangledModuleLazy } from "@webpack";
import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react";
import { LazyComponent } from "./react";
@@ -138,16 +138,10 @@ export type MediaModalProps = {
fit?: string;
shouldRedactExplicitContent?: boolean;
shouldHideMediaOptions?: boolean;
- shouldAnimateCarousel?: boolean;
};
-export const openMediaModal: (props: MediaModalProps) => void = proxyLazyWebpack(() => {
- const mediaModalKeyModuleId = findModuleId('"Zoomed Media Modal"');
- if (mediaModalKeyModuleId == null) return;
-
- const openMediaModalModule = wreq(findModuleId(mediaModalKeyModuleId, "modalKey:") as any);
- return Object.values(openMediaModalModule).find(v => String(v).includes("modalKey:"));
-});
+// Modal key: "Media Viewer Modal"
+export const openMediaModal: (props: MediaModalProps) => void = findByCodeLazy("hasMediaOptions", "shouldHideMediaOptions");
interface ModalAPI {
/**
diff --git a/src/utils/quickCss.ts b/src/utils/quickCss.ts
index 71092e51..13848cec 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;
@@ -62,7 +61,10 @@ async function initThemes() {
const enabledlinks: string[] = [...enabledThemeLinks];
// "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 = enabledlinks
.map(rawLink => {
@@ -99,6 +101,14 @@ document.addEventListener("DOMContentLoaded", () => {
SettingsStore.addChangeListener("enabledThemeLinks", 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;
@@ -106,7 +116,4 @@ document.addEventListener("DOMContentLoaded", () => {
currentTheme = ThemeStore.theme;
initThemes();
});
-
- if (!IS_WEB)
- VencordNative.quickCss.addThemeChangeListener(initThemes);
-});
+}
diff --git a/src/utils/types.ts b/src/utils/types.ts
index 270a7a09..fcbde877 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -25,6 +25,7 @@ import { MessageAccessoryFactory } from "@api/MessageAccessories";
import { MessageDecorationFactory } from "@api/MessageDecorations";
import { MessageClickListener, MessageEditListener, MessageSendListener } from "@api/MessageEvents";
import { MessagePopoverButtonFactory } from "@api/MessagePopover";
+import { NicknameIconFactory } from "@api/NicknameIcons";
import { FluxEvents } from "@webpack/types";
import { ReactNode } from "react";
import { Promisable } from "type-fest";
@@ -176,7 +177,7 @@ export interface PluginDef {
*/
managedStyle?: string;
- userProfileBadge?: ProfileBadge;
+ userProfileBadges?: ProfileBadge[];
onMessageClick?: MessageClickListener;
onBeforeMessageSend?: MessageSendListener;
@@ -187,6 +188,7 @@ export interface PluginDef {
renderMessageDecoration?: MessageDecorationFactory;
renderMemberListDecorator?: MemberListDecoratorFactory;
+ renderNicknameIcon?: NicknameIconFactory;
renderChatBarButton?: ChatBarButtonFactory;
}
diff --git a/src/utils/updater.ts b/src/utils/updater.ts
index f99c6ca1..25fe6ee8 100644
--- a/src/utils/updater.ts
+++ b/src/utils/updater.ts
@@ -39,10 +39,15 @@ async function Unwrap(p: Promise>) {
export async function checkForUpdates() {
changes = await Unwrap(VencordNative.updater.getUpdates());
- if (changes.some(c => c.hash === gitHash)) {
- isNewer = true;
- return (isOutdated = false);
+
+ // we only want to check this for the git updater, not the http updater
+ if (!IS_STANDALONE) {
+ if (changes.some(c => c.hash === gitHash)) {
+ isNewer = true;
+ return (isOutdated = false);
+ }
}
+
return (isOutdated = changes.length > 0);
}
diff --git a/src/webpack/common/internal.tsx b/src/webpack/common/internal.tsx
index db4d2215..5e8c6052 100644
--- a/src/webpack/common/internal.tsx
+++ b/src/webpack/common/internal.tsx
@@ -16,17 +16,17 @@
* along with this program. If not, see .
*/
-import { LazyComponent } from "@utils/react";
+import { LazyComponent, LazyComponentWrapper } from "@utils/react";
import { FilterFn, filters, lazyWebpackSearchHistory, waitFor } from "@webpack";
-export function waitForComponent = React.ComponentType & Record>(name: string, filter: FilterFn | string | string[]): T {
+export function waitForComponent = React.ComponentType & Record>(name: string, filter: FilterFn | string | string[]) {
if (IS_REPORTER) lazyWebpackSearchHistory.push(["waitForComponent", Array.isArray(filter) ? filter : [filter]]);
let myValue: T = function () {
throw new Error(`Vencord could not find the ${name} Component`);
} as any;
- const lazyComponent = LazyComponent(() => myValue) as T;
+ const lazyComponent = LazyComponent(() => myValue) as LazyComponentWrapper;
waitFor(filter, (v: any) => {
myValue = v;
Object.assign(lazyComponent, v);
diff --git a/src/webpack/common/menu.ts b/src/webpack/common/menu.ts
index 32b4d565..2bb05f36 100644
--- a/src/webpack/common/menu.ts
+++ b/src/webpack/common/menu.ts
@@ -16,19 +16,27 @@
* along with this program. If not, see .
*/
-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;
// Relies on .name properties added by the MenuItemDemanglerAPI
waitFor(m => m.name === "MenuCheckboxItem", (_, id) => {
- // we have to do this manual require by ID because m is in this case the MenuCheckBoxItem instead of the entire module
- const module = wreq(id);
+ // We have to do this manual require by ID because m in this case is the MenuCheckBoxItem instead of the entire module
+ const exports = wreq(id);
- for (const e of Object.values(module)) {
- if (typeof e === "function" && e.name.startsWith("Menu")) {
- Menu[e.name] = e;
+ for (const exportKey in exports) {
+ // Some exports might have not been initialized yet due to circular imports, so try catch it.
+ try {
+ var exportValue = exports[exportKey];
+ } catch {
+ continue;
+ }
+
+ if (typeof exportValue === "function" && exportValue.name.startsWith("Menu")) {
+ Menu[exportValue.name] = exportValue;
}
}
});
diff --git a/src/webpack/common/react.ts b/src/webpack/common/react.ts
index 1bb874e8..b8b0e753 100644
--- a/src/webpack/common/react.ts
+++ b/src/webpack/common/react.ts
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-import { findByPropsLazy, waitFor } from "@webpack";
+import { findByCodeLazy, findByPropsLazy, waitFor } from "@webpack";
export let React: typeof import("react");
export let useState: typeof React.useState;
@@ -27,7 +27,9 @@ export let useRef: typeof React.useRef;
export let useReducer: typeof React.useReducer;
export let useCallback: typeof React.useCallback;
-export const ReactDOM: typeof import("react-dom") & typeof import("react-dom/client") = findByPropsLazy("createPortal", "render");
+export const ReactDOM: typeof import("react-dom") = findByPropsLazy("createPortal");
+// 299 is an error code used in createRoot and createPortal
+export const createRoot: typeof import("react-dom/client").createRoot = findByCodeLazy("(299));", ".onRecoverableError");
waitFor("useState", m => {
React = m;
diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts
index 76804488..94484f5c 100644
--- a/src/webpack/common/stores.ts
+++ b/src/webpack/common/stores.ts
@@ -32,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;
@@ -54,6 +54,7 @@ export let RelationshipStore: Stores.RelationshipStore & t.FluxStore & {
};
export let EmojiStore: t.EmojiStore;
+export let StickersStore: t.StickersStore;
export let ThemeStore: t.ThemeStore;
export let WindowStore: t.WindowStore;
export let DraftStore: t.DraftStore;
@@ -86,5 +87,10 @@ waitForStore("GuildChannelStore", m => GuildChannelStore = m);
waitForStore("MessageStore", m => MessageStore = m);
waitForStore("WindowStore", m => WindowStore = m);
waitForStore("EmojiStore", m => EmojiStore = m);
+waitForStore("StickersStore", m => StickersStore = m);
waitForStore("TypingStore", m => TypingStore = 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 7f69c793..68cbbe3a 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 { 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";
@@ -356,7 +356,7 @@ export type SearchableSelect = ComponentType>;
-export type Slider = ComponentType;
renderPopout(args: {
closePopout(): void;
isPositioned: boolean;
diff --git a/src/webpack/common/types/stores.d.ts b/src/webpack/common/types/stores.d.ts
index 67148303..c54811b0 100644
--- a/src/webpack/common/types/stores.d.ts
+++ b/src/webpack/common/types/stores.d.ts
@@ -177,6 +177,17 @@ export class EmojiStore extends FluxStore {
};
}
+export class StickersStore extends FluxStore {
+ getStickerById(id: string): Sticker | undefined;
+ getStickerPack(id: string): StickerPack | undefined;
+ getPremiumPacks(): StickerPack[];
+ isPremiumPack(id: string): boolean;
+ getRawStickersByGuild(): Map;
+ getAllStickersIterator(): IterableIterator;
+ getAllGuildStickers(): Map;
+ getStickersByGuildId(id: string): Sticker[] | undefined;
+}
+
export interface DraftObject {
channelId: string;
timestamp: number;
diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts
index a2f846bb..de0c5ea8 100644
--- a/src/webpack/common/utils.ts
+++ b/src/webpack/common/utils.ts
@@ -16,15 +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";
-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 = () => {
@@ -34,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"', {
@@ -151,11 +151,6 @@ export const ApplicationAssetUtils = mapMangledModuleLazy("getAssetImage: size m
getAssets: filters.byCode(".assets")
});
-export const Clipboard: t.Clipboard = mapMangledModuleLazy('queryCommandEnabled("copy")', {
- copy: filters.byCode(".copy("),
- SUPPORTS_COPY: e => typeof e === "boolean"
-});
-
export const NavigationRouter: t.NavigationRouter = mapMangledModuleLazy("Transitioning to ", {
transitionTo: filters.byCode("transitionTo -"),
transitionToGuild: filters.byCode("transitionToGuild -"),
@@ -184,6 +179,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 2c212a9a..e813c796 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,22 +446,41 @@ 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) {
- const exportValue = exports[exportKey];
+ 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];
+ } catch {
+ continue;
+ }
if (exportValue != null && filter(exportValue)) {
waitForSubscriptions.delete(filter);
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);
}
}
@@ -568,7 +597,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 26d9c0d7..b6a1cd27 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,10 +164,25 @@ export function _blacklistBadModules(requireCache: NonNullable