diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx
index 0b06d035..d252682f 100644
--- a/src/plugins/betterFolders/index.tsx
+++ b/src/plugins/betterFolders/index.tsx
@@ -279,7 +279,7 @@ export default definePlugin({
makeGuildsBarTreeFilter(isBetterFolders: boolean) {
return child => {
if (isBetterFolders) {
- return "onScroll" in child.props;
+ return child?.props?.onScroll != null;
}
return true;
};
diff --git a/src/plugins/betterNotes/index.tsx b/src/plugins/betterNotes/index.tsx
index 2183d98e..cacdba5f 100644
--- a/src/plugins/betterNotes/index.tsx
+++ b/src/plugins/betterNotes/index.tsx
@@ -17,6 +17,7 @@
*/
import { Settings } from "@api/Settings";
+import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { canonicalizeMatch } from "@utils/patches";
import definePlugin, { OptionType } from "@utils/types";
@@ -60,7 +61,7 @@ export default definePlugin({
find: ".popularApplicationCommandIds,",
replacement: {
match: /lastSection:(!?\i)}\),/,
- replace: "$&$self.patchPadding($1),"
+ replace: "$&$self.patchPadding({lastSection:$1}),"
}
}
],
@@ -80,10 +81,10 @@ export default definePlugin({
}
},
- patchPadding(lastSection: any) {
- if (!lastSection) return;
+ patchPadding: ErrorBoundary.wrap(({ lastSection }) => {
+ if (!lastSection) return null;
return (
-
+
);
- }
+ })
});
diff --git a/src/plugins/betterSessions/index.tsx b/src/plugins/betterSessions/index.tsx
index 539508f8..9c93289c 100644
--- a/src/plugins/betterSessions/index.tsx
+++ b/src/plugins/betterSessions/index.tsx
@@ -22,7 +22,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findExportedComponentLazy, findStoreLazy } from "@webpack";
-import { React, RestAPI, Tooltip } from "@webpack/common";
+import { Constants, React, RestAPI, Tooltip } from "@webpack/common";
import { RenameButton } from "./components/RenameButton";
import { Session, SessionInfo } from "./types";
@@ -168,7 +168,7 @@ export default definePlugin({
async checkNewSessions() {
const data = await RestAPI.get({
- url: "/auth/sessions"
+ url: Constants.Endpoints.AUTH_SESSIONS
});
for (const session of data.body.user_sessions) {
diff --git a/src/plugins/betterSettings/index.tsx b/src/plugins/betterSettings/index.tsx
index 7d81c6f5..5064bd53 100644
--- a/src/plugins/betterSettings/index.tsx
+++ b/src/plugins/betterSettings/index.tsx
@@ -6,17 +6,18 @@
import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
-import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
+import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
-import { findByPropsLazy } from "@webpack";
+import { waitFor } from "@webpack";
import { ComponentDispatch, FocusLock, i18n, Menu, useEffect, useRef } from "@webpack/common";
import type { HTMLAttributes, ReactElement } from "react";
type SettingsEntry = { section: string, label: string; };
const cl = classNameFactory("");
-const Classes = findByPropsLazy("animating", "baseLayer", "bg", "layer", "layers");
+let Classes: Record
;
+waitFor(["animating", "baseLayer", "bg", "layer", "layers"], m => Classes = m);
const settings = definePluginSettings({
disableFade: {
@@ -124,12 +125,19 @@ export default definePlugin({
}
],
+ // This is the very outer layer of the entire ui, so we can't wrap this in an ErrorBoundary
+ // without possibly also catching unrelated errors of children.
+ //
+ // Thus, we sanity check webpack modules & do this really hacky try catch to hopefully prevent hard crashes if something goes wrong.
+ // try catch will only catch errors in the Layer function (hence why it's called as a plain function rather than a component), but
+ // not in children
Layer(props: LayerProps) {
- return (
- props.children as any}>
-
-
- );
+ if (!FocusLock || !ComponentDispatch || !Classes) {
+ new Logger("BetterSettings").error("Failed to find some components");
+ return props.children;
+ }
+
+ return ;
},
wrapMenu(list: SettingsEntry[]) {
diff --git a/src/plugins/crashHandler/index.ts b/src/plugins/crashHandler/index.ts
index f8c76d7f..10053021 100644
--- a/src/plugins/crashHandler/index.ts
+++ b/src/plugins/crashHandler/index.ts
@@ -104,7 +104,7 @@ export default definePlugin({
shouldAttemptRecover = false;
// This is enough to avoid a crash loop
- setTimeout(() => shouldAttemptRecover = true, 500);
+ setTimeout(() => shouldAttemptRecover = true, 1000);
} catch { }
try {
diff --git a/src/plugins/customRPC/index.tsx b/src/plugins/customRPC/index.tsx
index 334372e3..f1b2fbf5 100644
--- a/src/plugins/customRPC/index.tsx
+++ b/src/plugins/customRPC/index.tsx
@@ -17,13 +17,16 @@
*/
import { definePluginSettings, Settings } from "@api/Settings";
+import { ErrorCard } from "@components/ErrorCard";
import { Link } from "@components/Link";
import { Devs } from "@utils/constants";
import { isTruthy } from "@utils/guards";
+import { Margins } from "@utils/margins";
+import { classes } from "@utils/misc";
import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack";
-import { ApplicationAssetUtils, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
+import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, StatusSettingsStores, UserStore } from "@webpack/common";
const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gradient-primary-color");
const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile");
@@ -386,17 +389,36 @@ async function setRpc(disable?: boolean) {
export default definePlugin({
name: "CustomRPC",
description: "Allows you to set a custom rich presence.",
- authors: [Devs.captain, Devs.AutumnVN],
+ authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev],
start: setRpc,
stop: () => setRpc(true),
settings,
settingsAboutComponent: () => {
const activity = useAwaiter(createActivity);
+ const gameActivityEnabled = StatusSettingsStores.ShowCurrentGame.useSetting();
const { profileThemeStyle } = useProfileThemeStyle({});
return (
<>
+ {!gameActivityEnabled && (
+
+ Notice
+ Game activity isn't enabled, people won't be able to see your custom rich presence!
+
+
+
+ )}
+
Go to Discord Developer Portal to create an application and
get the application ID.
@@ -407,7 +429,9 @@ export default definePlugin({
If you want to use image link, download your image and reupload the image to Imgur and get the image link by right-clicking the image and select "Copy image address".
-
+
+
+
{activity[0] &&
) {
try {
const { embed } = this.props;
+ const { replaceElements } = settings.store;
+
if (!embed || embed.dearrow || embed.provider?.name !== "YouTube" || !embed.video?.url) return;
const videoId = embedUrlRe.exec(embed.video.url)?.[1];
@@ -58,12 +67,12 @@ async function embedDidMount(this: Component) {
enabled: true
};
- if (hasTitle) {
+ if (hasTitle && replaceElements !== ReplaceElements.ReplaceThumbnailsOnly) {
embed.dearrow.oldTitle = embed.rawTitle;
embed.rawTitle = titles[0].title.replace(/ >(\S)/g, " $1");
}
- if (hasThumb) {
+ if (hasThumb && replaceElements !== ReplaceElements.ReplaceTitlesOnly) {
embed.dearrow.oldThumb = embed.thumbnail.proxyURL;
embed.thumbnail.proxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`;
}
@@ -128,10 +137,30 @@ function DearrowButton({ component }: { component: Component; }) {
);
}
+const settings = definePluginSettings({
+ hideButton: {
+ description: "Hides the Dearrow button from YouTube embeds",
+ type: OptionType.BOOLEAN,
+ default: false,
+ restartNeeded: true
+ },
+ replaceElements: {
+ description: "Choose which elements of the embed will be replaced",
+ type: OptionType.SELECT,
+ restartNeeded: true,
+ options: [
+ { label: "Everything (Titles & Thumbnails)", value: ReplaceElements.ReplaceAllElements, default: true },
+ { label: "Titles", value: ReplaceElements.ReplaceTitlesOnly },
+ { label: "Thumbnails", value: ReplaceElements.ReplaceThumbnailsOnly },
+ ],
+ }
+});
+
export default definePlugin({
name: "Dearrow",
description: "Makes YouTube embed titles and thumbnails less sensationalist, powered by Dearrow",
authors: [Devs.Ven],
+ settings,
embedDidMount,
renderButton(component: Component) {
@@ -154,7 +183,8 @@ export default definePlugin({
// add dearrow button
{
match: /children:\[(?=null!=\i\?\i\.renderSuppressButton)/,
- replace: "children:[$self.renderButton(this),"
+ replace: "children:[$self.renderButton(this),",
+ predicate: () => !settings.store.hideButton
}
]
}],
diff --git a/src/plugins/emoteCloner/index.tsx b/src/plugins/emoteCloner/index.tsx
index cd9890a8..b456c351 100644
--- a/src/plugins/emoteCloner/index.tsx
+++ b/src/plugins/emoteCloner/index.tsx
@@ -24,7 +24,7 @@ import { Margins } from "@utils/margins";
import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal";
import definePlugin from "@utils/types";
import { findByPropsLazy, findStoreLazy } from "@webpack";
-import { EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
+import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
import { Promisable } from "type-fest";
const StickersStore = findStoreLazy("StickersStore");
@@ -64,7 +64,7 @@ async function fetchSticker(id: string) {
if (cached) return cached;
const { body } = await RestAPI.get({
- url: `/stickers/${id}`
+ url: Constants.Endpoints.STICKER(id)
});
FluxDispatcher.dispatch({
@@ -83,7 +83,7 @@ async function cloneSticker(guildId: string, sticker: Sticker) {
data.append("file", await fetchBlob(getUrl(sticker)));
const { body } = await RestAPI.post({
- url: `/guilds/${guildId}/stickers`,
+ url: Constants.Endpoints.GUILD_STICKER_PACKS(guildId),
body: data,
});
@@ -322,8 +322,9 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) =
switch (favoriteableType) {
case "emoji":
const match = props.message.content.match(RegExp(`|https://cdn\\.discordapp\\.com/emojis/${favoriteableId}\\.`));
- if (!match) return;
- const name = match[1] ?? "FakeNitroEmoji";
+ const reaction = props.message.reactions.find(reaction => reaction.emoji.id === favoriteableId);
+ if (!match && !reaction) return;
+ const name = (match && match[1]) ?? reaction?.emoji.name ?? "FakeNitroEmoji";
return buildMenuItem("Emoji", () => ({
id: favoriteableId,
diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx
index 03feda0a..29318366 100644
--- a/src/plugins/fakeNitro/index.tsx
+++ b/src/plugins/fakeNitro/index.tsx
@@ -39,6 +39,7 @@ const StickerStore = findStoreLazy("StickersStore") as {
const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore");
const ProtoUtils = findByPropsLazy("BINARY_READ_OPTIONS");
+const RoleSubscriptionEmojiUtils = findByPropsLazy("isUnusableRoleSubscriptionEmoji");
function searchProtoClassField(localName: string, protoClass: any) {
const field = protoClass?.fields?.find((field: any) => field.localName === localName);
@@ -111,7 +112,7 @@ const hyperLinkRegex = /\[.+?\]\((https?:\/\/.+?)\)/;
const settings = definePluginSettings({
enableEmojiBypass: {
- description: "Allow sending fake emojis",
+ description: "Allows sending fake emojis (also bypasses missing permission to use custom emojis)",
type: OptionType.BOOLEAN,
default: true,
restartNeeded: true
@@ -129,7 +130,7 @@ const settings = definePluginSettings({
restartNeeded: true
},
enableStickerBypass: {
- description: "Allow sending fake stickers",
+ description: "Allows sending fake stickers (also bypasses missing permission to use stickers)",
type: OptionType.BOOLEAN,
default: true,
restartNeeded: true
@@ -190,7 +191,7 @@ const hasAttachmentPerms = (channelId: string) => hasPermission(channelId, Permi
export default definePlugin({
name: "FakeNitro",
authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.fawn, Devs.captain, Devs.Nuckyz, Devs.AutumnVN],
- description: "Allows you to stream in nitro quality, send fake emojis/stickers and use client themes.",
+ description: "Allows you to stream in nitro quality, send fake emojis/stickers, use client themes and custom Discord notifications.",
dependencies: ["MessageEventsAPI"],
settings,
@@ -408,6 +409,15 @@ export default definePlugin({
match: /canUseCustomNotificationSounds:function\(\i\){/,
replace: "$&return true;"
}
+ },
+ // Allows the usage of subscription-locked emojis
+ {
+ find: "isUnusableRoleSubscriptionEmoji:function",
+ replacement: {
+ match: /isUnusableRoleSubscriptionEmoji:function/,
+ // replace the original export with a func that always returns false and alias the original
+ replace: "isUnusableRoleSubscriptionEmoji:()=>()=>false,isUnusableRoleSubscriptionEmojiOriginal:function"
+ }
}
],
@@ -804,6 +814,9 @@ export default definePlugin({
if (e.require_colons === false) return true;
if (e.available === false) return false;
+ const isUnusableRoleSubEmoji = RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmojiOriginal ?? RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmoji;
+ if (isUnusableRoleSubEmoji(e, this.guildId)) return false;
+
if (this.canUseEmotes)
return e.guildId === this.guildId || hasExternalEmojiPerms(channelId);
else
diff --git a/src/plugins/friendInvites/index.ts b/src/plugins/friendInvites/index.ts
index e5ff447e..47e312c3 100644
--- a/src/plugins/friendInvites/index.ts
+++ b/src/plugins/friendInvites/index.ts
@@ -20,7 +20,7 @@ import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption,
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack";
-import { RestAPI, UserStore } from "@webpack/common";
+import { Constants, RestAPI, UserStore } from "@webpack/common";
const FriendInvites = findByPropsLazy("createFriendInvite");
const { uuid4 } = findByPropsLazy("uuid4");
@@ -58,7 +58,7 @@ export default definePlugin({
if (uses === 1) {
const random = uuid4();
const { body: { invite_suggestions } } = await RestAPI.post({
- url: "/friend-finder/find-friends",
+ url: Constants.Endpoints.FRIEND_FINDER,
body: {
modified_contacts: {
[random]: [1, "", ""]
diff --git a/src/plugins/imageZoom/components/Magnifier.tsx b/src/plugins/imageZoom/components/Magnifier.tsx
index 81671735..aadd0903 100644
--- a/src/plugins/imageZoom/components/Magnifier.tsx
+++ b/src/plugins/imageZoom/components/Magnifier.tsx
@@ -17,6 +17,7 @@
*/
import { classNameFactory } from "@api/Styles";
+import ErrorBoundary from "@components/ErrorBoundary";
import { FluxDispatcher, React, useRef, useState } from "@webpack/common";
import { ELEMENT_ID } from "../constants";
@@ -36,7 +37,7 @@ export interface MagnifierProps {
const cl = classNameFactory("vc-imgzoom-");
-export const Magnifier: React.FC = ({ instance, size: initialSize, zoom: initalZoom }) => {
+export const Magnifier = ErrorBoundary.wrap(({ instance, size: initialSize, zoom: initalZoom }) => {
const [ready, setReady] = useState(false);
const [lensPosition, setLensPosition] = useState({ x: 0, y: 0 });
@@ -199,4 +200,4 @@ export const Magnifier: React.FC = ({ instance, size: initialSiz
)}
);
-};
+}, { noop: true });
diff --git a/src/plugins/invisibleChat.desktop/index.tsx b/src/plugins/invisibleChat.desktop/index.tsx
index fcb0af71..3dfe51e7 100644
--- a/src/plugins/invisibleChat.desktop/index.tsx
+++ b/src/plugins/invisibleChat.desktop/index.tsx
@@ -23,7 +23,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { getStegCloak } from "@utils/dependencies";
import definePlugin, { OptionType } from "@utils/types";
-import { ChannelStore, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common";
+import { ChannelStore, Constants, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common";
import { Message } from "discord-types/general";
import { buildDecModal } from "./components/DecryptionModal";
@@ -153,7 +153,7 @@ export default definePlugin({
// Gets the Embed of a Link
async getEmbed(url: URL): Promise