diff --git a/src/plugins/spotifyControls/SpotifyStore.ts b/src/plugins/spotifyControls/SpotifyStore.ts
index 5c0c5fe4e..65ee2c154 100644
--- a/src/plugins/spotifyControls/SpotifyStore.ts
+++ b/src/plugins/spotifyControls/SpotifyStore.ts
@@ -77,7 +77,7 @@ export const SpotifyStore = proxyLazyWebpack(() => {
class SpotifyStore extends Store {
public mPosition = 0;
- private start = 0;
+ public _start = 0;
public track: Track | null = null;
public device: Device | null = null;
@@ -100,26 +100,26 @@ export const SpotifyStore = proxyLazyWebpack(() => {
public get position(): number {
let pos = this.mPosition;
if (this.isPlaying) {
- pos += Date.now() - this.start;
+ pos += Date.now() - this._start;
}
return pos;
}
public set position(p: number) {
this.mPosition = p;
- this.start = Date.now();
+ this._start = Date.now();
}
prev() {
- this.req("post", "/previous");
+ this._req("post", "/previous");
}
next() {
- this.req("post", "/next");
+ this._req("post", "/next");
}
setVolume(percent: number) {
- this.req("put", "/volume", {
+ this._req("put", "/volume", {
query: {
volume_percent: Math.round(percent)
}
@@ -131,17 +131,17 @@ export const SpotifyStore = proxyLazyWebpack(() => {
}
setPlaying(playing: boolean) {
- this.req("put", playing ? "/play" : "/pause");
+ this._req("put", playing ? "/play" : "/pause");
}
setRepeat(state: Repeat) {
- this.req("put", "/repeat", {
+ this._req("put", "/repeat", {
query: { state }
});
}
setShuffle(state: boolean) {
- this.req("put", "/shuffle", {
+ this._req("put", "/shuffle", {
query: { state }
}).then(() => {
this.shuffle = state;
@@ -154,7 +154,7 @@ export const SpotifyStore = proxyLazyWebpack(() => {
this.isSettingPosition = true;
- return this.req("put", "/seek", {
+ return this._req("put", "/seek", {
query: {
position_ms: Math.round(ms)
}
@@ -164,7 +164,7 @@ export const SpotifyStore = proxyLazyWebpack(() => {
});
}
- private req(method: "post" | "get" | "put", route: string, data: any = {}) {
+ _req(method: "post" | "get" | "put", route: string, data: any = {}) {
if (this.device?.is_active)
(data.query ??= {}).device_id = this.device.id;
diff --git a/src/plugins/spotifyControls/spotifyStyles.css b/src/plugins/spotifyControls/spotifyStyles.css
index 893dc8175..d6cbad459 100644
--- a/src/plugins/spotifyControls/spotifyStyles.css
+++ b/src/plugins/spotifyControls/spotifyStyles.css
@@ -30,22 +30,17 @@
background-color: var(--background-modifier-selected);
}
-.vc-spotify-button svg {
+.vc-spotify-button-icon {
height: 24px;
width: 24px;
}
-[class*="vc-spotify-shuffle"] > svg,
-[class*="vc-spotify-repeat"] > svg {
+.vc-spotify-shuffle .vc-spotify-button-icon,
+.vc-spotify-repeat .vc-spotify-button-icon {
width: 22px;
height: 22px;
}
-.vc-spotify-button svg path {
- width: 100%;
- height: 100%;
-}
-
/* .vc-spotify-button:hover {
filter: brightness(1.3);
} */
@@ -87,12 +82,19 @@
gap: 0.5em;
}
-#vc-spotify-info-wrapper img {
+#vc-spotify-album-image {
height: 90%;
object-fit: contain;
+ border-radius: 3px;
+ transition: filter 0.2s;
}
-#vc-spotify-album-expanded-wrapper img {
+#vc-spotify-album-image:hover {
+ filter: brightness(1.2);
+ cursor: pointer;
+}
+
+#vc-spotify-album-expanded-wrapper #vc-spotify-album-image {
width: 100%;
object-fit: contain;
}
@@ -137,16 +139,6 @@
cursor: pointer;
}
-#vc-spotify-album-image {
- border-radius: 3px;
- transition: filter 0.2s;
-}
-
-#vc-spotify-album-image:hover {
- filter: brightness(1.2);
- cursor: pointer;
-}
-
#vc-spotify-progress-bar {
position: relative;
color: var(--text-normal);
diff --git a/src/plugins/startupTimings/index.tsx b/src/plugins/startupTimings/index.tsx
index aabc786a4..ac7f3f0c8 100644
--- a/src/plugins/startupTimings/index.tsx
+++ b/src/plugins/startupTimings/index.tsx
@@ -27,12 +27,22 @@ export default definePlugin({
authors: [Devs.Megu],
patches: [{
find: "#{intl::ACTIVITY_SETTINGS}",
- replacement: {
- match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\)}\))/,
- replace: (_, commaOrSemi, settings, elements) => "" +
- `${commaOrSemi}${settings}?.[0]==="CHANGELOG"` +
- `&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`
- }
+ replacement: [
+ {
+ // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
+ match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\)}\))/,
+ replace: (_, commaOrSemi, settings, elements) => "" +
+ `${commaOrSemi}${settings}?.[0]==="CHANGELOG"` +
+ `&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`,
+ noWarn: true
+ },
+ {
+ match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+\)\)\}\))(?=\)\})/,
+ replace: (_, commaOrSemi, settings, elements) => "" +
+ `${commaOrSemi}${settings}?.[0]==="CHANGELOG"` +
+ `&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`,
+ },
+ ]
}],
StartupTimingPage
});
diff --git a/src/plugins/textReplace/index.tsx b/src/plugins/textReplace/index.tsx
index 615477d07..d5d6f4dc8 100644
--- a/src/plugins/textReplace/index.tsx
+++ b/src/plugins/textReplace/index.tsx
@@ -17,13 +17,11 @@
*/
import { DataStore } from "@api/index";
-import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
import { definePluginSettings } from "@api/Settings";
import { Flex } from "@components/Flex";
import { DeleteIcon } from "@components/Icons";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
-import { useForceUpdater } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
import { Button, Forms, React, TextInput, useState } from "@webpack/common";
@@ -35,8 +33,6 @@ type Rule = Record<"find" | "replace" | "onlyIfIncludes", string>;
interface TextReplaceProps {
title: string;
rulesArray: Rule[];
- rulesKey: string;
- update: () => void;
}
const makeEmptyRule: () => Rule = () => ({
@@ -46,34 +42,35 @@ const makeEmptyRule: () => Rule = () => ({
});
const makeEmptyRuleArray = () => [makeEmptyRule()];
-let stringRules = makeEmptyRuleArray();
-let regexRules = makeEmptyRuleArray();
-
const settings = definePluginSettings({
replace: {
type: OptionType.COMPONENT,
- description: "",
component: () => {
- const update = useForceUpdater();
+ const { stringRules, regexRules } = settings.use(["stringRules", "regexRules"]);
+
return (
<>
>
);
}
},
+ stringRules: {
+ type: OptionType.CUSTOM,
+ default: makeEmptyRuleArray(),
+ },
+ regexRules: {
+ type: OptionType.CUSTOM,
+ default: makeEmptyRuleArray(),
+ }
});
function stringToRegex(str: string) {
@@ -120,28 +117,24 @@ function Input({ initialValue, onChange, placeholder }: {
);
}
-function TextReplace({ title, rulesArray, rulesKey, update }: TextReplaceProps) {
+function TextReplace({ title, rulesArray }: TextReplaceProps) {
const isRegexRules = title === "Using Regex";
async function onClickRemove(index: number) {
if (index === rulesArray.length - 1) return;
rulesArray.splice(index, 1);
-
- await DataStore.set(rulesKey, rulesArray);
- update();
}
async function onChange(e: string, index: number, key: string) {
- if (index === rulesArray.length - 1)
+ if (index === rulesArray.length - 1) {
rulesArray.push(makeEmptyRule());
+ }
rulesArray[index][key] = e;
- if (rulesArray[index].find === "" && rulesArray[index].replace === "" && rulesArray[index].onlyIfIncludes === "" && index !== rulesArray.length - 1)
+ if (rulesArray[index].find === "" && rulesArray[index].replace === "" && rulesArray[index].onlyIfIncludes === "" && index !== rulesArray.length - 1) {
rulesArray.splice(index, 1);
-
- await DataStore.set(rulesKey, rulesArray);
- update();
+ }
}
return (
@@ -208,29 +201,26 @@ function TextReplaceTesting() {
}
function applyRules(content: string): string {
- if (content.length === 0)
+ if (content.length === 0) {
return content;
-
- if (stringRules) {
- for (const rule of stringRules) {
- if (!rule.find) continue;
- if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
-
- content = ` ${content} `.replaceAll(rule.find, rule.replace.replaceAll("\\n", "\n")).replace(/^\s|\s$/g, "");
- }
}
- if (regexRules) {
- for (const rule of regexRules) {
- if (!rule.find) continue;
- if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
+ for (const rule of settings.store.stringRules) {
+ if (!rule.find) continue;
+ if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
- try {
- const regex = stringToRegex(rule.find);
- content = content.replace(regex, rule.replace.replaceAll("\\n", "\n"));
- } catch (e) {
- new Logger("TextReplace").error(`Invalid regex: ${rule.find}`);
- }
+ content = ` ${content} `.replaceAll(rule.find, rule.replace.replaceAll("\\n", "\n")).replace(/^\s|\s$/g, "");
+ }
+
+ for (const rule of settings.store.regexRules) {
+ if (!rule.find) continue;
+ if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
+
+ try {
+ const regex = stringToRegex(rule.find);
+ content = content.replace(regex, rule.replace.replaceAll("\\n", "\n"));
+ } catch (e) {
+ new Logger("TextReplace").error(`Invalid regex: ${rule.find}`);
}
}
@@ -244,22 +234,27 @@ export default definePlugin({
name: "TextReplace",
description: "Replace text in your messages. You can find pre-made rules in the #textreplace-rules channel in Vencord's Server",
authors: [Devs.AutumnVN, Devs.TheKodeToad],
- dependencies: ["MessageEventsAPI"],
settings,
- async start() {
- stringRules = await DataStore.get(STRING_RULES_KEY) ?? makeEmptyRuleArray();
- regexRules = await DataStore.get(REGEX_RULES_KEY) ?? makeEmptyRuleArray();
-
- this.preSend = addPreSendListener((channelId, msg) => {
- // Channel used for sharing rules, applying rules here would be messy
- if (channelId === TEXT_REPLACE_RULES_CHANNEL_ID) return;
- msg.content = applyRules(msg.content);
- });
+ onBeforeMessageSend(channelId, msg) {
+ // Channel used for sharing rules, applying rules here would be messy
+ if (channelId === TEXT_REPLACE_RULES_CHANNEL_ID) return;
+ msg.content = applyRules(msg.content);
},
- stop() {
- removePreSendListener(this.preSend);
+ async start() {
+ // TODO(OptionType.CUSTOM Related): Remove DataStore rules migrations once enough time has passed
+ const oldStringRules = await DataStore.get
(STRING_RULES_KEY);
+ if (oldStringRules != null) {
+ settings.store.stringRules = oldStringRules;
+ await DataStore.del(STRING_RULES_KEY);
+ }
+
+ const oldRegexRules = await DataStore.get(REGEX_RULES_KEY);
+ if (oldRegexRules != null) {
+ settings.store.regexRules = oldRegexRules;
+ await DataStore.del(REGEX_RULES_KEY);
+ }
}
});
diff --git a/src/plugins/translate/TranslateIcon.tsx b/src/plugins/translate/TranslateIcon.tsx
index fa1d9abf6..1b77fb94c 100644
--- a/src/plugins/translate/TranslateIcon.tsx
+++ b/src/plugins/translate/TranslateIcon.tsx
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-import { ChatBarButton } from "@api/ChatButtons";
+import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
import { classes } from "@utils/misc";
import { openModal } from "@utils/modal";
import { Alerts, Forms, Tooltip, useEffect, useState } from "@webpack/common";
@@ -40,7 +40,7 @@ export function TranslateIcon({ height = 24, width = 24, className }: { height?:
export let setShouldShowTranslateEnabledTooltip: undefined | ((show: boolean) => void);
-export const TranslateChatBarIcon: ChatBarButton = ({ isMainChat }) => {
+export const TranslateChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
const { autoTranslate, showChatBarButton } = settings.use(["autoTranslate", "showChatBarButton"]);
const [shouldShowTranslateEnabledTooltip, setter] = useState(false);
diff --git a/src/plugins/translate/TranslateModal.tsx b/src/plugins/translate/TranslateModal.tsx
index 7a32d1b75..786f81054 100644
--- a/src/plugins/translate/TranslateModal.tsx
+++ b/src/plugins/translate/TranslateModal.tsx
@@ -76,7 +76,7 @@ export function TranslateModal({ rootProps }: { rootProps: ModalProps; }) {
return (
-
+
Translate
diff --git a/src/plugins/translate/TranslationAccessory.tsx b/src/plugins/translate/TranslationAccessory.tsx
index 8e8f4c174..9b6393cc9 100644
--- a/src/plugins/translate/TranslationAccessory.tsx
+++ b/src/plugins/translate/TranslationAccessory.tsx
@@ -55,7 +55,7 @@ export function TranslationAccessory({ message }: { message: Message; }) {
return (
-
+
{Parser.parse(translation.text)}
{" "}
(translated from {translation.sourceLanguage} - setTranslation(undefined)} />)
diff --git a/src/plugins/translate/index.tsx b/src/plugins/translate/index.tsx
index de61cef9c..363897d1b 100644
--- a/src/plugins/translate/index.tsx
+++ b/src/plugins/translate/index.tsx
@@ -18,11 +18,7 @@
import "./styles.css";
-import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
-import { addAccessory, removeAccessory } from "@api/MessageAccessories";
-import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
-import { addButton, removeButton } from "@api/MessagePopover";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { ChannelStore, Menu } from "@webpack/common";
@@ -51,11 +47,12 @@ const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) =>
));
};
+let tooltipTimeout: any;
+
export default definePlugin({
name: "Translate",
description: "Translate messages with Google Translate or DeepL",
authors: [Devs.Ven, Devs.AshtonMemer],
- dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI", "ChatInputButtonAPI"],
settings,
contextMenus: {
"message": messageCtxPatch
@@ -63,45 +60,34 @@ export default definePlugin({
// not used, just here in case some other plugin wants it or w/e
translate,
- start() {
- addAccessory("vc-translation", props => );
+ renderMessageAccessory: props => ,
- addChatBarButton("vc-translate", TranslateChatBarIcon);
+ renderChatBarButton: TranslateChatBarIcon,
- addButton("vc-translate", message => {
- if (!message.content) return null;
+ renderMessagePopoverButton(message) {
+ if (!message.content) return null;
- return {
- label: "Translate",
- icon: TranslateIcon,
- message,
- channel: ChannelStore.getChannel(message.channel_id),
- onClick: async () => {
- const trans = await translate("received", message.content);
- handleTranslate(message.id, trans);
- }
- };
- });
-
- let tooltipTimeout: any;
- this.preSend = addPreSendListener(async (_, message) => {
- if (!settings.store.autoTranslate) return;
- if (!message.content) return;
-
- setShouldShowTranslateEnabledTooltip?.(true);
- clearTimeout(tooltipTimeout);
- tooltipTimeout = setTimeout(() => setShouldShowTranslateEnabledTooltip?.(false), 2000);
-
- const trans = await translate("sent", message.content);
- message.content = trans.text;
-
- });
+ return {
+ label: "Translate",
+ icon: TranslateIcon,
+ message,
+ channel: ChannelStore.getChannel(message.channel_id),
+ onClick: async () => {
+ const trans = await translate("received", message.content);
+ handleTranslate(message.id, trans);
+ }
+ };
},
- stop() {
- removePreSendListener(this.preSend);
- removeChatBarButton("vc-translate");
- removeButton("vc-translate");
- removeAccessory("vc-translation");
- },
+ async onBeforeMessageSend(_, message) {
+ if (!settings.store.autoTranslate) return;
+ if (!message.content) return;
+
+ setShouldShowTranslateEnabledTooltip?.(true);
+ clearTimeout(tooltipTimeout);
+ tooltipTimeout = setTimeout(() => setShouldShowTranslateEnabledTooltip?.(false), 2000);
+
+ const trans = await translate("sent", message.content);
+ message.content = trans.text;
+ }
});
diff --git a/src/plugins/translate/styles.css b/src/plugins/translate/styles.css
index 64b6c9b9f..c07c9e362 100644
--- a/src/plugins/translate/styles.css
+++ b/src/plugins/translate/styles.css
@@ -6,7 +6,7 @@
place-content: center space-between;
}
-.vc-trans-modal-header h1 {
+.vc-trans-modal-title {
margin: 0;
}
@@ -17,7 +17,7 @@
font-weight: 400;
}
-.vc-trans-accessory svg {
+.vc-trans-accessory-icon {
margin-right: 0.25em;
}
diff --git a/src/plugins/typingIndicator/index.tsx b/src/plugins/typingIndicator/index.tsx
index e6a1b3b4f..e6903bcde 100644
--- a/src/plugins/typingIndicator/index.tsx
+++ b/src/plugins/typingIndicator/index.tsx
@@ -23,12 +23,12 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
-import { findComponentByCodeLazy, findExportedComponentLazy, findStoreLazy } from "@webpack";
+import { findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { GuildMemberStore, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
import { buildSeveralUsers } from "../typingTweaks";
-const ThreeDots = findExportedComponentLazy("Dots", "AnimatedDots");
+const ThreeDots = findComponentByCodeLazy(".dots,", "dotRadius:");
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
const TypingStore = findStoreLazy("TypingStore");
@@ -100,16 +100,24 @@ function TypingIndicator({ channelId, guildId }: { channelId: string; guildId: s
{props => (
{((settings.store.indicatorMode & IndicatorMode.Avatars) === IndicatorMode.Avatars) && (
-
UserStore.getUser(id))}
- guildId={guildId}
- renderIcon={false}
- max={3}
- showDefaultAvatarsForNullUsers
- showUserPopout
- size={16}
- className="vc-typing-indicator-avatars"
- />
+ {
+ e.stopPropagation();
+ e.preventDefault();
+ }}
+ onKeyPress={e => e.stopPropagation()}
+ >
+ UserStore.getUser(id))}
+ guildId={guildId}
+ renderIcon={false}
+ max={3}
+ showDefaultAvatarsForNullUsers
+ showUserPopout
+ size={16}
+ className="vc-typing-indicator-avatars"
+ />
+
)}
{((settings.store.indicatorMode & IndicatorMode.Dots) === IndicatorMode.Dots) && (
diff --git a/src/plugins/unindent/index.ts b/src/plugins/unindent/index.ts
index a197ef4e9..d8853a93f 100644
--- a/src/plugins/unindent/index.ts
+++ b/src/plugins/unindent/index.ts
@@ -16,7 +16,7 @@
* along with this program. If not, see
.
*/
-import { addPreEditListener, addPreSendListener, MessageObject, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
+import { MessageObject } from "@api/MessageEvents";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
@@ -24,7 +24,7 @@ export default definePlugin({
name: "Unindent",
description: "Trims leading indentation from codeblocks",
authors: [Devs.Ven],
- dependencies: ["MessageEventsAPI"],
+
patches: [
{
find: "inQuote:",
@@ -55,13 +55,11 @@ export default definePlugin({
});
},
- start() {
- this.preSend = addPreSendListener((_, msg) => this.unindentMsg(msg));
- this.preEdit = addPreEditListener((_cid, _mid, msg) => this.unindentMsg(msg));
+ onBeforeMessageSend(_, msg) {
+ return this.unindentMsg(msg);
},
- stop() {
- removePreSendListener(this.preSend);
- removePreEditListener(this.preEdit);
+ onBeforeMessageEdit(_cid, _mid, msg) {
+ return this.unindentMsg(msg);
}
});
diff --git a/src/plugins/unsuppressEmbeds/index.tsx b/src/plugins/unsuppressEmbeds/index.tsx
index 16debf711..2df64b72e 100644
--- a/src/plugins/unsuppressEmbeds/index.tsx
+++ b/src/plugins/unsuppressEmbeds/index.tsx
@@ -21,12 +21,18 @@ import { ImageInvisible, ImageVisible } from "@components/Icons";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { Constants, Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common";
+import { MessageSnapshot } from "@webpack/types";
+
const EMBED_SUPPRESSED = 1 << 2;
-const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, embeds, flags, id: messageId } }) => {
+const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, messageSnapshots, embeds, flags, id: messageId } }) => {
const isEmbedSuppressed = (flags & EMBED_SUPPRESSED) !== 0;
- if (!isEmbedSuppressed && !embeds.length) return;
+ const hasEmbedsInSnapshots = messageSnapshots.some(
+ (snapshot: MessageSnapshot) => snapshot?.message.embeds.length
+ );
+
+ if (!isEmbedSuppressed && !embeds.length && !hasEmbedsInSnapshots) return;
const hasEmbedPerms = channel.isPrivate() || !!(PermissionStore.getChannelPermissions({ id: channel.id }) & PermissionsBits.EMBED_LINKS);
if (author.id === UserStore.getCurrentUser().id && !hasEmbedPerms) return;
diff --git a/src/plugins/userMessagesPronouns/index.ts b/src/plugins/userMessagesPronouns/index.ts
index 27b162b90..1699251ee 100644
--- a/src/plugins/userMessagesPronouns/index.ts
+++ b/src/plugins/userMessagesPronouns/index.ts
@@ -41,11 +41,20 @@ export default definePlugin({
},
{
find: '="SYSTEM_TAG"',
- replacement: {
- // Add next to username (compact mode)
- match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\),(?=\i)/g,
- replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0]),"
- }
+ replacement: [
+ {
+ // Add next to username (compact mode)
+ // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
+ match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\),(?=\i)/g,
+ replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0]),",
+ noWarn: true
+ },
+ {
+ // Add next to username (compact mode)
+ match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\)\),(?=\i)/g,
+ replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0]),",
+ },
+ ]
}
],
diff --git a/src/plugins/userVoiceShow/components.tsx b/src/plugins/userVoiceShow/components.tsx
index e8924f829..9029cdc58 100644
--- a/src/plugins/userVoiceShow/components.tsx
+++ b/src/plugins/userVoiceShow/components.tsx
@@ -22,7 +22,7 @@ const VoiceStateStore = findStoreLazy("VoiceStateStore");
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
const Avatar = findComponentByCodeLazy(".status)/2):0");
-const GroupDMAvatars = findComponentByCodeLazy(".AvatarSizeSpecs[", "getAvatarURL");
+const GroupDMAvatars = findComponentByCodeLazy("frontSrc:", "getAvatarURL");
const ActionButtonClasses = findByPropsLazy("actionButton", "highlight");
@@ -128,17 +128,15 @@ function VoiceChannelTooltip({ channel, isLocked }: VoiceChannelTooltipProps) {
);
}
-interface VoiceChannelIndicatorProps {
+export interface VoiceChannelIndicatorProps {
userId: string;
- isMessageIndicator?: boolean;
- isProfile?: boolean;
isActionButton?: boolean;
shouldHighlight?: boolean;
}
const clickTimers = {} as Record
;
-export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isMessageIndicator, isProfile, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => {
+export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => {
const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId as string | undefined);
const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId);
@@ -182,7 +180,7 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isMessageIndi
{props => {
const iconProps: IconProps = {
...props,
- className: classes(isMessageIndicator && cl("message-indicator"), (!isProfile && !isActionButton) && cl("speaker-margin"), isActionButton && ActionButtonClasses.actionButton, shouldHighlight && ActionButtonClasses.highlight),
+ className: classes(isActionButton && ActionButtonClasses.actionButton, shouldHighlight && ActionButtonClasses.highlight),
size: isActionButton ? 20 : undefined,
onClick
};
diff --git a/src/plugins/userVoiceShow/index.tsx b/src/plugins/userVoiceShow/index.tsx
index e0d5d8abd..3d119c433 100644
--- a/src/plugins/userVoiceShow/index.tsx
+++ b/src/plugins/userVoiceShow/index.tsx
@@ -18,8 +18,8 @@
import "./style.css";
-import { addDecorator, removeDecorator } from "@api/MemberListDecorators";
-import { addDecoration, removeDecoration } from "@api/MessageDecorations";
+import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
+import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
@@ -60,7 +60,7 @@ export default definePlugin({
find: "#{intl::USER_PROFILE_LOAD_ERROR}",
replacement: {
match: /(\.fetchError.+?\?)null/,
- replace: (_, rest) => `${rest}$self.VoiceChannelIndicator({userId:arguments[0]?.userId,isProfile:true})`
+ replace: (_, rest) => `${rest}$self.VoiceChannelIndicator({userId:arguments[0]?.userId})`
},
predicate: () => settings.store.showInUserProfileModal
},
@@ -96,16 +96,16 @@ export default definePlugin({
start() {
if (settings.store.showInMemberList) {
- addDecorator("UserVoiceShow", ({ user }) => user == null ? null : );
+ addMemberListDecorator("UserVoiceShow", ({ user }) => user == null ? null : );
}
if (settings.store.showInMessages) {
- addDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : );
+ addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : );
}
},
stop() {
- removeDecorator("UserVoiceShow");
- removeDecoration("UserVoiceShow");
+ removeMemberListDecorator("UserVoiceShow");
+ removeMessageDecoration("UserVoiceShow");
},
VoiceChannelIndicator
diff --git a/src/plugins/userVoiceShow/style.css b/src/plugins/userVoiceShow/style.css
index d172975b8..f9fd56fbf 100644
--- a/src/plugins/userVoiceShow/style.css
+++ b/src/plugins/userVoiceShow/style.css
@@ -13,16 +13,6 @@
color: var(--interactive-hover);
}
-.vc-uvs-speaker-margin {
- margin-left: 4px;
-}
-
-.vc-uvs-message-indicator {
- display: inline-flex;
- top: 2.5px;
- position: relative;
-}
-
.vc-uvs-tooltip-container {
max-width: 300px;
}
diff --git a/src/plugins/usrbg/index.css b/src/plugins/usrbg/index.css
deleted file mode 100644
index 69c5b1857..000000000
--- a/src/plugins/usrbg/index.css
+++ /dev/null
@@ -1,12 +0,0 @@
-:is([class*="userProfile"], [class*="userPopout"]) [class*="bannerPremium"] {
- background: center / cover no-repeat;
-}
-
-[class*="NonPremium"]:has([class*="bannerPremium"]) [class*="avatarPositionNormal"],
-[class*="PremiumWithoutBanner"]:has([class*="bannerPremium"]) [class*="avatarPositionPremiumNoBanner"] {
- top: 76px;
-}
-
-[style*="background-image"] [class*="background_"] {
- background-color: transparent !important;
-}
diff --git a/src/plugins/usrbg/index.tsx b/src/plugins/usrbg/index.tsx
index 788a79ae7..df823102a 100644
--- a/src/plugins/usrbg/index.tsx
+++ b/src/plugins/usrbg/index.tsx
@@ -17,13 +17,10 @@
*/
import { definePluginSettings } from "@api/Settings";
-import { enableStyle } from "@api/Styles";
import { Link } from "@components/Link";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
-import style from "./index.css?managed";
-
const API_URL = "https://usrbg.is-hardly.online/users";
interface UsrbgApiReturn {
@@ -115,8 +112,6 @@ export default definePlugin({
},
async start() {
- enableStyle(style);
-
const res = await fetch(API_URL);
if (res.ok) {
this.data = await res.json();
diff --git a/src/plugins/vencordToolbox/index.css b/src/plugins/vencordToolbox/index.css
index a1c85ad50..642375b4a 100644
--- a/src/plugins/vencordToolbox/index.css
+++ b/src/plugins/vencordToolbox/index.css
@@ -1,16 +1,16 @@
.vc-toolbox-btn,
-.vc-toolbox-btn>svg {
+.vc-toolbox-icon {
-webkit-app-region: no-drag;
}
-.vc-toolbox-btn>svg {
+.vc-toolbox-icon {
color: var(--interactive-normal);
}
-.vc-toolbox-btn[class*="selected"]>svg {
+.vc-toolbox-btn[class*="selected"] .vc-toolbox-icon {
color: var(--interactive-active);
}
-.vc-toolbox-btn:hover>svg {
+.vc-toolbox-btn:hover .vc-toolbox-icon {
color: var(--interactive-hover);
}
diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx
index 00805fbd3..c59df00a5 100644
--- a/src/plugins/vencordToolbox/index.tsx
+++ b/src/plugins/vencordToolbox/index.tsx
@@ -23,11 +23,11 @@ import { Settings, useSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
-import { findExportedComponentLazy } from "@webpack";
+import { findComponentByCodeLazy } from "@webpack";
import { Menu, Popout, useState } from "@webpack/common";
import type { ReactNode } from "react";
-const HeaderBarIcon = findExportedComponentLazy("Icon", "Divider");
+const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"');
function VencordPopout(onClose: () => void) {
const { useQuickCss } = useSettings(["useQuickCss"]);
@@ -88,7 +88,7 @@ function VencordPopout(onClose: () => void) {
function VencordPopoutIcon(isShown: boolean) {
return (
-
+
);
diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx
index c53116b4b..afd9d48c5 100644
--- a/src/plugins/viewIcons/index.tsx
+++ b/src/plugins/viewIcons/index.tsx
@@ -193,10 +193,18 @@ export default definePlugin({
// Avatar component used in User DMs "User Profile" popup in the right and Profiles Modal pfp
{
find: ".overlay:void 0,status:",
- replacement: {
- match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",{...\2,/,
- replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},"
- },
+ replacement: [
+ {
+ // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
+ match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",{...\2,/,
+ replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},",
+ noWarn: true
+ },
+ {
+ match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",.{0,100}className:\i,/,
+ replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},",
+ }
+ ],
all: true
},
// Banners
@@ -220,16 +228,16 @@ export default definePlugin({
{
find: ".cursorPointer:null,children",
replacement: {
- match: /.Avatar,.+?src:(.+?\))(?=[,}])/,
- replace: (m, avatarUrl) => `${m},onClick:()=>$self.openAvatar(${avatarUrl})`
+ match: /(?=,src:(\i.getAvatarURL\(.+?[)]))/,
+ replace: (_, avatarUrl) => `,onClick:()=>$self.openAvatar(${avatarUrl})`
}
},
// User Dms top large icon
{
find: 'experimentLocation:"empty_messages"',
replacement: {
- match: /.Avatar,.+?src:(.+?\))(?=[,}])/,
- replace: (m, avatarUrl) => `${m},onClick:()=>$self.openAvatar(${avatarUrl})`
+ match: /(?<=SIZE_80,)(?=src:(.+?\))[,}])/,
+ replace: (_, avatarUrl) => `onClick:()=>$self.openAvatar(${avatarUrl}),`
}
}
]
diff --git a/src/plugins/viewRaw/index.tsx b/src/plugins/viewRaw/index.tsx
index 8ee1ca8d7..ddcbd3b46 100644
--- a/src/plugins/viewRaw/index.tsx
+++ b/src/plugins/viewRaw/index.tsx
@@ -17,18 +17,17 @@
*/
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
-import { addButton, removeButton } from "@api/MessagePopover";
import { definePluginSettings } from "@api/Settings";
import { CodeBlock } from "@components/CodeBlock";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants";
-import { getIntlMessage } from "@utils/discord";
+import { getCurrentGuild, getIntlMessage } from "@utils/discord";
import { Margins } from "@utils/margins";
import { copyWithToast } from "@utils/misc";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
-import { Button, ChannelStore, Forms, Menu, Text } from "@webpack/common";
+import { Button, ChannelStore, Forms, GuildStore, Menu, Text } from "@webpack/common";
import { Message } from "discord-types/general";
@@ -119,7 +118,7 @@ const settings = definePluginSettings({
}
});
-function MakeContextCallback(name: "Guild" | "User" | "Channel"): NavContextMenuPatchCallback {
+function MakeContextCallback(name: "Guild" | "Role" | "User" | "Channel"): NavContextMenuPatchCallback {
return (children, props) => {
const value = props[name.toLowerCase()];
if (!value) return;
@@ -145,58 +144,71 @@ function MakeContextCallback(name: "Guild" | "User" | "Channel"): NavContextMenu
};
}
+const devContextCallback: NavContextMenuPatchCallback = (children, { id }: { id: string; }) => {
+ const guild = getCurrentGuild();
+ if (!guild) return;
+
+ const role = GuildStore.getRole(guild.id, id);
+ if (!role) return;
+
+ children.push(
+ openViewRawModal(JSON.stringify(role, null, 4), "Role")}
+ icon={CopyIcon}
+ />
+ );
+};
+
export default definePlugin({
name: "ViewRaw",
description: "Copy and view the raw content/data of any message, channel or guild",
authors: [Devs.KingFish, Devs.Ven, Devs.rad, Devs.ImLvna],
- dependencies: ["MessagePopoverAPI"],
settings,
+
contextMenus: {
"guild-context": MakeContextCallback("Guild"),
+ "guild-settings-role-context": MakeContextCallback("Role"),
"channel-context": MakeContextCallback("Channel"),
"thread-context": MakeContextCallback("Channel"),
"gdm-context": MakeContextCallback("Channel"),
- "user-context": MakeContextCallback("User")
+ "user-context": MakeContextCallback("User"),
+ "dev-context": devContextCallback
},
- start() {
- addButton("ViewRaw", msg => {
- const handleClick = () => {
- if (settings.store.clickMethod === "Right") {
- copyWithToast(msg.content);
- } else {
- openViewRawModalMessage(msg);
- }
- };
+ renderMessagePopoverButton(msg) {
+ const handleClick = () => {
+ if (settings.store.clickMethod === "Right") {
+ copyWithToast(msg.content);
+ } else {
+ openViewRawModalMessage(msg);
+ }
+ };
- const handleContextMenu = e => {
- if (settings.store.clickMethod === "Left") {
- e.preventDefault();
- e.stopPropagation();
- copyWithToast(msg.content);
- } else {
- e.preventDefault();
- e.stopPropagation();
- openViewRawModalMessage(msg);
- }
- };
+ const handleContextMenu = e => {
+ if (settings.store.clickMethod === "Left") {
+ e.preventDefault();
+ e.stopPropagation();
+ copyWithToast(msg.content);
+ } else {
+ e.preventDefault();
+ e.stopPropagation();
+ openViewRawModalMessage(msg);
+ }
+ };
- const label = settings.store.clickMethod === "Right"
- ? "Copy Raw (Left Click) / View Raw (Right Click)"
- : "View Raw (Left Click) / Copy Raw (Right Click)";
+ const label = settings.store.clickMethod === "Right"
+ ? "Copy Raw (Left Click) / View Raw (Right Click)"
+ : "View Raw (Left Click) / Copy Raw (Right Click)";
- return {
- label,
- icon: CopyIcon,
- message: msg,
- channel: ChannelStore.getChannel(msg.channel_id),
- onClick: handleClick,
- onContextMenu: handleContextMenu
- };
- });
- },
-
- stop() {
- removeButton("ViewRaw");
+ return {
+ label,
+ icon: CopyIcon,
+ message: msg,
+ channel: ChannelStore.getChannel(msg.channel_id),
+ onClick: handleClick,
+ onContextMenu: handleContextMenu
+ };
}
});
diff --git a/src/plugins/voiceMessages/styles.css b/src/plugins/voiceMessages/styles.css
index 1e2b1433a..4f2e1d57e 100644
--- a/src/plugins/voiceMessages/styles.css
+++ b/src/plugins/voiceMessages/styles.css
@@ -9,10 +9,6 @@
margin-bottom: 1em;
}
-.vc-vmsg-modal audio {
- width: 100%;
-}
-
.vc-vmsg-preview {
color: var(--text-normal);
border-radius: 24px;
diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts
index 661238052..0af47ded8 100644
--- a/src/plugins/webContextMenus.web/index.ts
+++ b/src/plugins/webContextMenus.web/index.ts
@@ -20,10 +20,13 @@ import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { saveFile } from "@utils/web";
-import { findByPropsLazy } from "@webpack";
+import { filters, mapMangledModuleLazy } from "@webpack";
import { Clipboard, ComponentDispatch } from "@webpack/common";
-const ctxMenuCallbacks = findByPropsLazy("contextMenuCallbackNative");
+const ctxMenuCallbacks = mapMangledModuleLazy('.tagName)==="TEXTAREA"||', {
+ contextMenuCallbackWeb: filters.byCode('.tagName)==="INPUT"||'),
+ contextMenuCallbackNative: filters.byCode('.tagName)==="TEXTAREA"||')
+});
async function fetchImage(url: string) {
const res = await fetch(url);
diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx
index 803cc7156..aea57fef2 100644
--- a/src/plugins/whoReacted/index.tsx
+++ b/src/plugins/whoReacted/index.tsx
@@ -93,7 +93,7 @@ function makeRenderMoreUsers(users: User[]) {
};
}
-function handleClickAvatar(event: React.MouseEvent) {
+function handleClickAvatar(event: React.UIEvent) {
event.stopPropagation();
}
@@ -165,7 +165,7 @@ export default definePlugin({
-
+
{
win.webContents.on("frame-created", (_, { frame }) => {
- frame.once("dom-ready", () => {
+ 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"))) {
diff --git a/src/shared/SettingsStore.ts b/src/shared/SettingsStore.ts
index 4109704bc..0b6aa25b6 100644
--- a/src/shared/SettingsStore.ts
+++ b/src/shared/SettingsStore.ts
@@ -6,6 +6,9 @@
import { LiteralUnion } from "type-fest";
+export const SYM_IS_PROXY = Symbol("SettingsStore.isProxy");
+export const SYM_GET_RAW_TARGET = Symbol("SettingsStore.getRawTarget");
+
// Resolves a possibly nested prop in the form of "some.nested.prop" to type of T.some.nested.prop
type ResolvePropDeep = P extends `${infer Pre}.${infer Suf}`
? Pre extends keyof T
@@ -28,6 +31,11 @@ interface SettingsStoreOptions {
// merges the SettingsStoreOptions type into the class
export interface SettingsStore extends SettingsStoreOptions { }
+interface ProxyContext {
+ root: T;
+ path: string;
+}
+
/**
* The SettingsStore allows you to easily create a mutable store that
* has support for global and path-based change listeners.
@@ -35,6 +43,90 @@ export interface SettingsStore extends SettingsStoreOptions {
export class SettingsStore {
private pathListeners = new Map void>>();
private globalListeners = new Set<(newData: T, path: string) => void>();
+ private readonly proxyContexts = new WeakMap>();
+
+ private readonly proxyHandler: ProxyHandler = (() => {
+ const self = this;
+
+ return {
+ get(target, key: any, receiver) {
+ if (key === SYM_IS_PROXY) {
+ return true;
+ }
+
+ if (key === SYM_GET_RAW_TARGET) {
+ return target;
+ }
+
+ let v = Reflect.get(target, key, receiver);
+
+ const proxyContext = self.proxyContexts.get(target);
+ if (proxyContext == null) {
+ return v;
+ }
+
+ const { root, path } = proxyContext;
+
+ if (!(key in target) && self.getDefaultValue != null) {
+ v = self.getDefaultValue({
+ target,
+ key,
+ root,
+ path
+ });
+ }
+
+ if (typeof v === "object" && v !== null && !v[SYM_IS_PROXY]) {
+ const getPath = `${path}${path && "."}${key}`;
+ return self.makeProxy(v, root, getPath);
+ }
+
+ return v;
+ },
+ set(target, key: string, value) {
+ if (value?.[SYM_IS_PROXY]) {
+ value = value[SYM_GET_RAW_TARGET];
+ }
+
+ if (target[key] === value) {
+ return true;
+ }
+
+ if (!Reflect.set(target, key, value)) {
+ return false;
+ }
+
+ const proxyContext = self.proxyContexts.get(target);
+ if (proxyContext == null) {
+ return true;
+ }
+
+ const { root, path } = proxyContext;
+
+ const setPath = `${path}${path && "."}${key}`;
+ self.notifyListeners(setPath, value, root);
+
+ return true;
+ },
+ deleteProperty(target, key: string) {
+ if (!Reflect.deleteProperty(target, key)) {
+ return false;
+ }
+
+ const proxyContext = self.proxyContexts.get(target);
+ if (proxyContext == null) {
+ return true;
+ }
+
+ const { root, path } = proxyContext;
+
+ const deletePath = `${path}${path && "."}${key}`;
+ self.notifyListeners(deletePath, undefined, root);
+
+ return true;
+ }
+ };
+ })();
/**
* The store object. Making changes to this object will trigger the applicable change listeners
@@ -51,39 +143,35 @@ export class SettingsStore {
Object.assign(this, options);
}
- private makeProxy(object: any, root: T = object, path: string = "") {
- const self = this;
-
- return new Proxy(object, {
- get(target, key: string) {
- let v = target[key];
-
- if (!(key in target) && self.getDefaultValue) {
- v = self.getDefaultValue({
- target,
- key,
- root,
- path
- });
- }
-
- if (typeof v === "object" && v !== null && !Array.isArray(v))
- return self.makeProxy(v, root, `${path}${path && "."}${key}`);
-
- return v;
- },
- set(target, key: string, value) {
- if (target[key] === value) return true;
-
- Reflect.set(target, key, value);
- const setPath = `${path}${path && "."}${key}`;
-
- self.globalListeners.forEach(cb => cb(value, setPath));
- self.pathListeners.get(setPath)?.forEach(cb => cb(value));
-
- return true;
- }
+ private makeProxy(object: any, root: T = object, path = "") {
+ this.proxyContexts.set(object, {
+ root,
+ path
});
+
+ return new Proxy(object, this.proxyHandler);
+ }
+
+ private notifyListeners(pathStr: string, value: any, root: T) {
+ const paths = pathStr.split(".");
+
+ // Because we support any type of settings with OptionType.CUSTOM, and those objects get proxied recursively,
+ // the path ends up including all the nested paths (plugins.pluginName.settingName.example.one).
+ // So, we need to extract the top-level setting path (plugins.pluginName.settingName),
+ // to be able to notify globalListeners and top-level setting name listeners (let { settingName } = settings.use(["settingName"]),
+ // with the new value
+ if (paths.length > 2 && paths[0] === "plugins") {
+ const settingPath = paths.slice(0, 3);
+ const settingPathStr = settingPath.join(".");
+ const settingValue = settingPath.reduce((acc, curr) => acc[curr], root);
+
+ this.globalListeners.forEach(cb => cb(root, settingPathStr));
+ this.pathListeners.get(settingPathStr)?.forEach(cb => cb(settingValue));
+ } else {
+ this.globalListeners.forEach(cb => cb(root, pathStr));
+ }
+
+ this.pathListeners.get(pathStr)?.forEach(cb => cb(value));
}
/**
diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts
index 22a381360..aec7292a3 100644
--- a/src/utils/Logger.ts
+++ b/src/utils/Logger.ts
@@ -32,7 +32,7 @@ export class Logger {
constructor(public name: string, public color: string = "white") { }
private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[], customFmt = "") {
- if (IS_REPORTER && IS_WEB) {
+ if (IS_REPORTER && IS_WEB && !IS_VESKTOP) {
console[level]("[Vencord]", this.name + ":", ...args);
return;
}
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index e75825912..6760d4d06 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -16,9 +16,14 @@
* along with this program. If not, see .
*/
-export const WEBPACK_CHUNK = "webpackChunkdiscord_app";
export const REACT_GLOBAL = "Vencord.Webpack.Common.React";
+export const VENBOT_USER_ID = "1017176847865352332";
+export const VENCORD_GUILD_ID = "1015060230222131221";
+export const DONOR_ROLE_ID = "1042507929485586532";
+export const CONTRIB_ROLE_ID = "1026534353167208489";
+export const REGULAR_ROLE_ID = "1026504932959977532";
export const SUPPORT_CHANNEL_ID = "1026515880080842772";
+export const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920";
export interface Dev {
name: string;
@@ -579,6 +584,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "jamesbt365",
id: 158567567487795200n,
},
+ samsam: {
+ name: "samsam",
+ id: 836452332387565589n,
+ },
} satisfies Record);
// iife so #__PURE__ works correctly
diff --git a/src/utils/discord.css b/src/utils/discord.css
index 12d15694b..746fb564b 100644
--- a/src/utils/discord.css
+++ b/src/utils/discord.css
@@ -17,7 +17,6 @@
@media(width <= 485px) {
.vc-image-modal {
- display: relative;
overflow: visible;
overflow: initial;
}
diff --git a/src/utils/lazy.ts b/src/utils/lazy.ts
index e46e44ad7..526c5514b 100644
--- a/src/utils/lazy.ts
+++ b/src/utils/lazy.ts
@@ -20,9 +20,9 @@ export function makeLazy(factory: () => T, attempts = 5): () => T {
let tries = 0;
let cache: T;
return () => {
- if (!cache && attempts > tries++) {
+ if (cache === undefined && attempts > tries++) {
cache = factory();
- if (!cache && attempts === tries)
+ if (cache === undefined && attempts === tries)
console.error("Lazy factory failed:", factory);
}
return cache;
diff --git a/src/utils/misc.ts b/src/utils/misc.ts
index 28c371c5b..adca15d3b 100644
--- a/src/utils/misc.ts
+++ b/src/utils/misc.ts
@@ -100,6 +100,11 @@ export function pluralise(amount: number, singular: string, plural = singular +
return amount === 1 ? `${amount} ${singular}` : `${amount} ${plural}`;
}
+export function interpolateIfDefined(strings: TemplateStringsArray, ...args: any[]) {
+ if (args.some(arg => arg == null)) return "";
+ return String.raw({ raw: strings }, ...args);
+}
+
export function tryOrElse(func: () => T, fallback: T): T {
try {
const res = func();
diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx
index 83b2f0553..d06e58036 100644
--- a/src/utils/modal.tsx
+++ b/src/utils/modal.tsx
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-import { findByPropsLazy, findModuleId, proxyLazyWebpack, wreq } from "@webpack";
+import { filters, findModuleId, mapMangledModuleLazy, proxyLazyWebpack, wreq } from "@webpack";
import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react";
import { LazyComponent } from "./react";
@@ -49,7 +49,7 @@ export interface ModalOptions {
type RenderFunction = (props: ModalProps) => ReactNode | Promise;
-export const Modals = findByPropsLazy("ModalRoot", "ModalCloseButton") as {
+interface Modals {
ModalRoot: ComponentType;
-};
+}
+
+export const Modals: Modals = mapMangledModuleLazy(':"thin")', {
+ ModalRoot: filters.componentByCode('.MODAL,"aria-labelledby":'),
+ ModalHeader: filters.componentByCode(",id:"),
+ ModalContent: filters.componentByCode(".content,"),
+ ModalFooter: filters.componentByCode(".footer,"),
+ ModalCloseButton: filters.componentByCode(".close]:")
+});
+
+export const ModalRoot = LazyComponent(() => Modals.ModalRoot);
+export const ModalHeader = LazyComponent(() => Modals.ModalHeader);
+export const ModalContent = LazyComponent(() => Modals.ModalContent);
+export const ModalFooter = LazyComponent(() => Modals.ModalFooter);
+export const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton);
export type MediaModalItem = {
url: string;
@@ -135,38 +149,33 @@ export const openMediaModal: (props: MediaModalProps) => void = proxyLazyWebpack
return Object.values(openMediaModalModule).find(v => String(v).includes("modalKey:"));
});
-export const ModalRoot = LazyComponent(() => Modals.ModalRoot);
-export const ModalHeader = LazyComponent(() => Modals.ModalHeader);
-export const ModalContent = LazyComponent(() => Modals.ModalContent);
-export const ModalFooter = LazyComponent(() => Modals.ModalFooter);
-export const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton);
+interface ModalAPI {
+ /**
+ * Wait for the render promise to resolve, then open a modal with it.
+ * This is equivalent to render().then(openModal)
+ * You should use the Modal components exported by this file
+ */
+ openModalLazy: (render: () => Promise, options?: ModalOptions & { contextKey?: string; }) => Promise;
+ /**
+ * Open a Modal with the given render function.
+ * You should use the Modal components exported by this file
+ */
+ openModal: (render: RenderFunction, options?: ModalOptions, contextKey?: string) => string;
+ /**
+ * Close a modal by its key
+ */
+ closeModal: (modalKey: string, contextKey?: string) => void;
+ /**
+ * Close all open modals
+ */
+ closeAllModals: () => void;
+}
-export const ModalAPI = findByPropsLazy("openModalLazy");
-
-/**
- * Wait for the render promise to resolve, then open a modal with it.
- * This is equivalent to render().then(openModal)
- * You should use the Modal components exported by this file
- */
-export const openModalLazy: (render: () => Promise, options?: ModalOptions & { contextKey?: string; }) => Promise
- = proxyLazyWebpack(() => ModalAPI.openModalLazy);
-
-/**
- * Open a Modal with the given render function.
- * You should use the Modal components exported by this file
- */
-export const openModal: (render: RenderFunction, options?: ModalOptions, contextKey?: string) => string
- = proxyLazyWebpack(() => ModalAPI.openModal);
-
-/**
- * Close a modal by its key
- */
-export const closeModal: (modalKey: string, contextKey?: string) => void
- = proxyLazyWebpack(() => ModalAPI.closeModal);
-
-/**
- * Close all open modals
- */
-export const closeAllModals: () => void
- = proxyLazyWebpack(() => ModalAPI.closeAllModals);
+export const ModalAPI: ModalAPI = mapMangledModuleLazy(".modalKey?", {
+ openModalLazy: filters.byCode(".modalKey?"),
+ openModal: filters.byCode(",instant:"),
+ closeModal: filters.byCode(".onCloseCallback()"),
+ closeAllModals: filters.byCode(".getState();for")
+});
+export const { openModalLazy, openModal, closeModal, closeAllModals } = ModalAPI;
diff --git a/src/utils/patches.ts b/src/utils/patches.ts
index 097c64560..b212e6241 100644
--- a/src/utils/patches.ts
+++ b/src/utils/patches.ts
@@ -41,16 +41,17 @@ export function canonicalizeMatch(match: T): T {
}
const canonSource = partialCanon.replaceAll("\\i", String.raw`(?:[A-Za-z_$][\w$]*)`);
- return new RegExp(canonSource, match.flags) as T;
+ const canonRegex = new RegExp(canonSource, match.flags);
+ canonRegex.toString = match.toString.bind(match);
+
+ return canonRegex as T;
}
-export function canonicalizeReplace(replace: T, pluginName: string): T {
- const self = `Vencord.Plugins.plugins[${JSON.stringify(pluginName)}]`;
-
+export function canonicalizeReplace(replace: T, pluginPath: string): T {
if (typeof replace !== "function")
- return replace.replaceAll("$self", self) as T;
+ return replace.replaceAll("$self", pluginPath) as T;
- return ((...args) => replace(...args).replaceAll("$self", self)) as T;
+ return ((...args) => replace(...args).replaceAll("$self", pluginPath)) as T;
}
export function canonicalizeDescriptor(descriptor: TypedPropertyDescriptor, canonicalize: (value: T) => T) {
@@ -65,12 +66,12 @@ export function canonicalizeDescriptor(descriptor: TypedPropertyDescriptor
return descriptor;
}
-export function canonicalizeReplacement(replacement: Pick, plugin: string) {
+export function canonicalizeReplacement(replacement: Pick, pluginPath: string) {
const descriptors = Object.getOwnPropertyDescriptors(replacement);
descriptors.match = canonicalizeDescriptor(descriptors.match, canonicalizeMatch);
descriptors.replace = canonicalizeDescriptor(
descriptors.replace,
- replace => canonicalizeReplace(replace, plugin),
+ replace => canonicalizeReplace(replace, pluginPath),
);
Object.defineProperties(replacement, descriptors);
}
diff --git a/src/utils/settingsSync.ts b/src/utils/settingsSync.ts
index f19928ac4..6ec3e527a 100644
--- a/src/utils/settingsSync.ts
+++ b/src/utils/settingsSync.ts
@@ -60,7 +60,7 @@ export async function downloadSettingsBackup() {
}
}
-const toast = (type: number, message: string) =>
+const toast = (type: string, message: string) =>
Toasts.show({
type,
message,
diff --git a/src/utils/types.ts b/src/utils/types.ts
index 02760d964..6a791c86e 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -16,8 +16,15 @@
* along with this program. If not, see .
*/
+import { ProfileBadge } from "@api/Badges";
+import { ChatBarButtonFactory } from "@api/ChatButtons";
import { Command } from "@api/Commands";
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
+import { MemberListDecoratorFactory } from "@api/MemberListDecorators";
+import { MessageAccessoryFactory } from "@api/MessageAccessories";
+import { MessageDecorationFactory } from "@api/MessageDecorations";
+import { MessageClickListener, MessageEditListener, MessageSendListener } from "@api/MessageEvents";
+import { MessagePopoverButtonFactory } from "@api/MessagePopover";
import { FluxEvents } from "@webpack/types";
import { JSX } from "react";
import { Promisable } from "type-fest";
@@ -34,8 +41,17 @@ export interface PatchReplacement {
match: string | RegExp;
/** The replacement string or function which returns the string for the patch replacement */
replace: string | ReplaceFn;
- /** A function which returns whether this patch replacement should be applied */
+ /** Do not warn if this replacement did no changes */
+ noWarn?: boolean;
+ /**
+ * A function which returns whether this patch replacement should be applied.
+ * This is ran before patches are registered, so if this returns false, the patch will never be registered.
+ */
predicate?(): boolean;
+ /** The minimum build number for this patch to be applied */
+ fromBuild?: number;
+ /** The maximum build number for this patch to be applied */
+ toBuild?: number;
}
export interface Patch {
@@ -50,8 +66,15 @@ export interface Patch {
noWarn?: boolean;
/** Only apply this set of replacements if all of them succeed. Use this if your replacements depend on each other */
group?: boolean;
- /** A function which returns whether this patch should be applied */
+ /**
+ * A function which returns whether this patch replacement should be applied.
+ * This is ran before patches are registered, so if this returns false, the patch will never be registered.
+ */
predicate?(): boolean;
+ /** The minimum build number for this patch to be applied */
+ fromBuild?: number;
+ /** The maximum build number for this patch to be applied */
+ toBuild?: number;
}
export interface PluginAuthor {
@@ -142,6 +165,25 @@ export interface PluginDef {
toolboxActions?: Record void>;
tags?: string[];
+
+ /**
+ * Managed style to automatically enable and disable when the plugin is enabled or disabled
+ */
+ managedStyle?: string;
+
+ userProfileBadge?: ProfileBadge;
+
+ onMessageClick?: MessageClickListener;
+ onBeforeMessageSend?: MessageSendListener;
+ onBeforeMessageEdit?: MessageEditListener;
+
+ renderMessagePopoverButton?: MessagePopoverButtonFactory;
+ renderMessageAccessory?: MessageAccessoryFactory;
+ renderMessageDecoration?: MessageDecorationFactory;
+
+ renderMemberListDecorator?: MemberListDecoratorFactory;
+
+ renderChatBarButton?: ChatBarButtonFactory;
}
export const enum StartAt {
@@ -168,6 +210,7 @@ export const enum OptionType {
SELECT,
SLIDER,
COMPONENT,
+ CUSTOM
}
export type SettingsDefinition = Record;
@@ -176,15 +219,16 @@ export type SettingsChecks = {
(IsDisabled> & IsValid, DefinedSettings>);
};
-export type PluginSettingDef = (
- | PluginSettingStringDef
- | PluginSettingNumberDef
- | PluginSettingBooleanDef
- | PluginSettingSelectDef
- | PluginSettingSliderDef
- | PluginSettingComponentDef
- | PluginSettingBigIntDef
-) & PluginSettingCommon;
+export type PluginSettingDef =
+ (PluginSettingCustomDef & Pick) |
+ (PluginSettingComponentDef & Omit) | ((
+ | PluginSettingStringDef
+ | PluginSettingNumberDef
+ | PluginSettingBooleanDef
+ | PluginSettingSelectDef
+ | PluginSettingSliderDef
+ | PluginSettingBigIntDef
+ ) & PluginSettingCommon);
export interface PluginSettingCommon {
description: string;
@@ -204,12 +248,14 @@ export interface PluginSettingCommon {
*/
target?: "WEB" | "DESKTOP" | "BOTH";
}
+
interface IsDisabled