{
- if ((settings.store as any).unsafeViewAsRole && permission.type === PermissionType.Role)
+ if (permission.type === PermissionType.Role)
ContextMenuApi.openContextMenu(e, () => (
));
+ else if (permission.type === PermissionType.User) {
+ ContextMenuApi.openContextMenu(e, () => (
+
+ ));
+ }
}}
>
{(permission.type === PermissionType.Role || permission.type === PermissionType.Owner) && (
@@ -200,24 +208,53 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str
aria-label="Role Options"
>
{
- const role = GuildStore.getRole(guild.id, roleId);
- if (!role) return;
+ Clipboard.copy(roleId);
+ }}
+ />
- onClose();
+ {(settings.store as any).unsafeViewAsRole && (
+ {
+ const role = GuildStore.getRole(guild.id, roleId);
+ if (!role) return;
- FluxDispatcher.dispatch({
- type: "IMPERSONATE_UPDATE",
- guildId: guild.id,
- data: {
- type: "ROLES",
- roles: {
- [roleId]: role
+ onClose();
+
+ FluxDispatcher.dispatch({
+ type: "IMPERSONATE_UPDATE",
+ guildId: guild.id,
+ data: {
+ type: "ROLES",
+ roles: {
+ [roleId]: role
+ }
}
- }
- });
+ });
+ }
+ }
+ />
+ )}
+
+ );
+}
+
+function UserContextMenu({ userId, onClose }: { userId: string; onClose: () => void; }) {
+ return (
+
+ {
+ Clipboard.copy(userId);
}}
/>
diff --git a/src/plugins/pronoundb/pronoundbUtils.ts b/src/plugins/pronoundb/pronoundbUtils.ts
index 789cd530..f4bd27ac 100644
--- a/src/plugins/pronoundb/pronoundbUtils.ts
+++ b/src/plugins/pronoundb/pronoundbUtils.ts
@@ -78,9 +78,8 @@ export function useFormattedPronouns(id: string, useGlobalProfile: boolean = fal
if (settings.store.pronounSource === PronounSource.PreferDiscord && discordPronouns)
return [discordPronouns, "Discord"];
- if (result && result !== PronounMapping.unspecified) {
+ if (result && result !== PronounMapping.unspecified)
return [result, "PronounDB"];
- }
return [discordPronouns, "Discord"];
}
@@ -142,34 +141,25 @@ async function bulkFetchPronouns(ids: string[]): Promise {
} catch (e) {
// If the request errors, treat it as if no pronouns were found for all ids, and log it
console.error("PronounDB fetching failed: ", e);
- const dummyPronouns = Object.fromEntries(ids.map(id => [id, { sets: { en: ["unspecified"] } }] as const));
+ const dummyPronouns = Object.fromEntries(ids.map(id => [id, { sets: {} }] as const));
Object.assign(cache, dummyPronouns);
// @ts-ignore
return dummyPronouns;
}
}
-export function extractPronouns(pronounSet: { [locale: string]: PronounCode[]; }): string {
- if (!pronounSet || !pronounSet?.en) return PronounMapping.unspecified;
- // for some reason pronounDB returns empty sets sometimes instead of nothing?
+export function extractPronouns(pronounSet?: { [locale: string]: PronounCode[] }): string {
+ if (!pronounSet || !pronounSet.en) return PronounMapping.unspecified;
+ // PronounDB returns an empty set instead of {sets: {en: ["unspecified"]}}.
const pronouns = pronounSet.en;
const { pronounsFormat } = Settings.plugins.PronounDB as { pronounsFormat: PronounsFormat, enabled: boolean; };
- const shouldCapitalise = (pronoun: string) => {
- // if a pronoun is a sentence we keep the capitalisation.
- return pronounsFormat === PronounsFormat.Capitalized || ["any", "ask", "avoid", "other", "unspecified"].includes(pronoun);
- };
if (pronouns.length === 1) {
- if (shouldCapitalise(pronouns[0]))
+ // For capitalized pronouns or special codes (any, ask, avoid), we always return the normal (capitalized) string
+ if (pronounsFormat === PronounsFormat.Capitalized || ["any", "ask", "avoid", "other", "unspecified"].includes(pronouns[0]))
return PronounMapping[pronouns[0]];
- else if (
- pronounsFormat === PronounsFormat.Lowercase
- && ["any", "ask", "avoid", "other", "unspecified"].includes(pronouns[0])
- ) return PronounMapping[pronouns[0]];
else return PronounMapping[pronouns[0]].toLowerCase();
}
- return pronouns.map(pronoun => {
- const mappedPronoun = PronounMapping[pronoun + "S"];
- return shouldCapitalise(mappedPronoun) ? mappedPronoun : mappedPronoun.toLowerCase();
- }).join("/");
+ const pronounString = pronouns.map(p => p[0].toUpperCase() + p.slice(1)).join("/");
+ return pronounsFormat === PronounsFormat.Capitalized ? pronounString : pronounString.toLowerCase();
}
diff --git a/src/plugins/pronoundb/types.ts b/src/plugins/pronoundb/types.ts
index 123a846b..d099a7de 100644
--- a/src/plugins/pronoundb/types.ts
+++ b/src/plugins/pronoundb/types.ts
@@ -27,16 +27,16 @@ export interface UserProfilePronounsProps {
export interface PronounsResponse {
[id: string]: {
- sets: {
+ sets?: {
[locale: string]: PronounCode[];
- };
- };
+ }
+ }
}
export interface CachePronouns {
- sets: {
+ sets?: {
[locale: string]: PronounCode[];
- };
+ }
}
export type PronounCode = keyof typeof PronounMapping;
@@ -51,15 +51,4 @@ export const PronounMapping = {
ask: "Ask me my pronouns",
avoid: "Avoid pronouns, use my name",
unspecified: "No pronouns specified.",
- // neither avoid nor unspecified can occur when there is any other pronoun set, hence there is no short for needed.
-
- // S for short form, used for when there is multiple different pronouns.
- // pronounDB actually allows you to select any/other/ask as 2nd or 3rd set, so these are included here.
- anyS: "Any",
- otherS: "Other",
- askS: "Ask",
- heS: "He",
- itS: "It",
- sheS: "She",
- theyS: "They",
} as const;
diff --git a/src/plugins/readAllNotificationsButton/index.tsx b/src/plugins/readAllNotificationsButton/index.tsx
index bb975afd..c97e765a 100644
--- a/src/plugins/readAllNotificationsButton/index.tsx
+++ b/src/plugins/readAllNotificationsButton/index.tsx
@@ -19,6 +19,7 @@
import "./style.css";
import { addServerListElement, removeServerListElement, ServerListRenderPosition } from "@api/ServerList";
+import ErrorBoundary from "@components/ErrorBoundary";
import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types";
import { Button, FluxDispatcher, GuildChannelStore, GuildStore, React, ReadStateStore } from "@webpack/common";
@@ -64,7 +65,7 @@ export default definePlugin({
authors: [Devs.kemo, EquicordDevs.KrystalSkull],
dependencies: ["ServerListAPI"],
- renderReadAllButton: () => ,
+ renderReadAllButton: ErrorBoundary.wrap(ReadAllButton, { noop: true }),
start() {
addServerListElement(ServerListRenderPosition.Above, this.renderReadAllButton);
diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx
index 76acedcd..7ba245da 100644
--- a/src/plugins/showMeYourName/index.tsx
+++ b/src/plugins/showMeYourName/index.tsx
@@ -7,14 +7,14 @@
import "./styles.css";
import { definePluginSettings } from "@api/Settings";
+import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
-import { GuildMemberStore, RelationshipStore } from "@webpack/common";
import { Message, User } from "discord-types/general";
interface UsernameProps {
author: { nick: string; };
- message?: Message;
+ message: Message;
withMentionPrefix?: boolean;
isRepliedMessage: boolean;
userOverride?: User;
@@ -40,11 +40,6 @@ const settings = definePluginSettings({
default: false,
description: "Also apply functionality to reply previews",
},
- whenTyping: {
- type: OptionType.BOOLEAN,
- default: false,
- description: "Also apply functionality to when someone is typing",
- },
});
export default definePlugin({
@@ -59,27 +54,12 @@ export default definePlugin({
replace: "$self.renderUsername(arguments[0])}"
}
},
- {
- find: "getCooldownTextStyle",
- predicate: () => settings.store.whenTyping,
- replacement: [
- {
- match: /\.map\(\i=>\i\.\i\.getName\(\i,this\.props\.channel\.id,\i\)\)/,
- replace: ""
- },
- {
- match: /(?<=children:\[(\i)\.length>0.{0,200}?"aria-atomic":!0,children:)\i/,
- replace: "$self.renderTypingNames(this.props, $1, $&)"
- }
- ]
- },
],
settings,
- renderUsername: ({ author, message, isRepliedMessage, withMentionPrefix, userOverride }: UsernameProps) => {
+ renderUsername: ErrorBoundary.wrap(({ author, message, isRepliedMessage, withMentionPrefix, userOverride }: UsernameProps) => {
try {
- const user = userOverride ?? message?.author;
- if (!user) return author?.nick;
+ const user = userOverride ?? message.author;
let { username } = user;
if (settings.store.displayNames)
username = (user as any).globalName || username;
@@ -87,42 +67,14 @@ export default definePlugin({
const { nick } = author;
const prefix = withMentionPrefix ? "@" : "";
if (username === nick || isRepliedMessage && !settings.store.inReplies)
- return prefix + nick;
+ return <>{prefix}{nick}>;
if (settings.store.mode === "user-nick")
return <>{prefix}{username} {nick}>;
if (settings.store.mode === "nick-user")
return <>{prefix}{nick} {username}>;
- return prefix + username;
+ return <>{prefix}{username}>;
} catch {
- return author?.nick;
+ return <>{author?.nick}>;
}
- },
-
- renderTypingNames(props: any, users: User[], children: any) {
- if (!Array.isArray(children)) return children;
-
- let index = 0;
-
- return children.map(c => {
- if (c.type === "strong") {
- const user = users[index++];
- if (!user) return c;
-
- const nick = GuildMemberStore.getNick(props.guildId!, user.id)
- || (!props.guildId && RelationshipStore.getNickname(user.id))
- || (user as any).globalName
- || user.username;
- if (!nick) return c;
-
- return <>{this.renderUsername({
- author: { nick },
- message: undefined,
- isRepliedMessage: false,
- withMentionPrefix: false,
- userOverride: user
- })}>;
- }
- return c;
- });
- },
+ }, { noop: true }),
});
diff --git a/src/plugins/silentTyping/index.tsx b/src/plugins/silentTyping/index.tsx
index 8b59c6ac..2a6a6428 100644
--- a/src/plugins/silentTyping/index.tsx
+++ b/src/plugins/silentTyping/index.tsx
@@ -18,10 +18,11 @@
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
+import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
-import { FluxDispatcher, React } from "@webpack/common";
+import { FluxDispatcher, Menu, React } from "@webpack/common";
const settings = definePluginSettings({
showIcon: {
@@ -30,6 +31,11 @@ const settings = definePluginSettings({
description: "Show an icon for toggling the plugin",
restartNeeded: true,
},
+ contextMenu: {
+ type: OptionType.BOOLEAN,
+ description: "Add option to toggle the functionality in the chat input context menu",
+ default: true
+ },
isEnabled: {
type: OptionType.BOOLEAN,
description: "Toggle functionality",
@@ -56,13 +62,37 @@ const SilentTypingToggle: ChatBarButton = ({ isMainChat }) => {
);
};
+
+const ChatBarContextCheckbox: NavContextMenuPatchCallback = children => {
+ const { isEnabled, contextMenu } = settings.use(["isEnabled", "contextMenu"]);
+ if (!contextMenu) return;
+
+ const group = findGroupChildrenByChildId("submit-button", children);
+
+ if (!group) return;
+
+ const idx = group.findIndex(c => c?.props?.id === "submit-button");
+
+ group.splice(idx + 1, 0,
+ settings.store.isEnabled = !settings.store.isEnabled}
+ />
+ );
+};
+
+
export default definePlugin({
name: "SilentTyping",
- authors: [Devs.Ven, Devs.Rini],
+ authors: [Devs.Ven, Devs.Rini, Devs.ImBanana],
description: "Hide that you are typing",
dependencies: ["CommandsAPI", "ChatInputButtonAPI"],
settings,
-
+ contextMenus: {
+ "textarea-context": ChatBarContextCheckbox
+ },
patches: [
{
find: '.dispatch({type:"TYPING_START_LOCAL"',
diff --git a/src/plugins/themeAttributes/README.md b/src/plugins/themeAttributes/README.md
index 110eca57..87cb803c 100644
--- a/src/plugins/themeAttributes/README.md
+++ b/src/plugins/themeAttributes/README.md
@@ -15,6 +15,7 @@ This allows themes to more easily theme those elements or even do things that ot
### Chat Messages
- `data-author-id` contains the id of the author
+- `data-author-username` contains the username of the author
- `data-is-self` is a boolean indicating whether this is the current user's message

diff --git a/src/plugins/themeAttributes/index.ts b/src/plugins/themeAttributes/index.ts
index 8afc2121..b8ceac62 100644
--- a/src/plugins/themeAttributes/index.ts
+++ b/src/plugins/themeAttributes/index.ts
@@ -36,10 +36,12 @@ export default definePlugin({
],
getMessageProps(props: { message: Message; }) {
- const authorId = props.message?.author?.id;
+ const author = props.message?.author;
+ const authorId = author?.id;
return {
"data-author-id": authorId,
- "data-is-self": authorId && authorId === UserStore.getCurrentUser()?.id
+ "data-author-username": author?.username,
+ "data-is-self": authorId && authorId === UserStore.getCurrentUser()?.id,
};
}
});
diff --git a/src/plugins/translate/TranslateIcon.tsx b/src/plugins/translate/TranslateIcon.tsx
index cc0ed5e9..b22c488e 100644
--- a/src/plugins/translate/TranslateIcon.tsx
+++ b/src/plugins/translate/TranslateIcon.tsx
@@ -40,9 +40,9 @@ export function TranslateIcon({ height = 24, width = 24, className }: { height?:
}
export const TranslateChatBarIcon: ChatBarButton = ({ isMainChat }) => {
- const { autoTranslate } = settings.use(["autoTranslate"]);
+ const { autoTranslate, showChatBarButton } = settings.use(["autoTranslate", "showChatBarButton"]);
- if (!isMainChat) return null;
+ if (!isMainChat || !showChatBarButton) return null;
const toggle = () => {
const newState = !autoTranslate;
diff --git a/src/plugins/translate/settings.ts b/src/plugins/translate/settings.ts
index cef003a8..65d84535 100644
--- a/src/plugins/translate/settings.ts
+++ b/src/plugins/translate/settings.ts
@@ -48,6 +48,11 @@ export const settings = definePluginSettings({
type: OptionType.BOOLEAN,
description: "Automatically translate your messages before sending. You can also shift/right click the translate button to toggle this",
default: false
+ },
+ showChatBarButton: {
+ type: OptionType.BOOLEAN,
+ description: "Show translate button in chat bar",
+ default: true
}
}).withPrivateSettings<{
showAutoTranslateAlert: boolean;
diff --git a/src/plugins/xsOverlay.desktop/index.ts b/src/plugins/xsOverlay.desktop/index.ts
index 763f6a78..b666d116 100644
--- a/src/plugins/xsOverlay.desktop/index.ts
+++ b/src/plugins/xsOverlay.desktop/index.ts
@@ -1,6 +1,6 @@
/*
* Vencord, a Discord client mod
- * Copyright (c) 2023 Vendicated and contributors
+ * Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
@@ -13,10 +13,7 @@ import { findByPropsLazy } from "@webpack";
import { ChannelStore, GuildStore, UserStore } from "@webpack/common";
import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general";
-const enum ChannelTypes {
- DM = 1,
- GROUP_DM = 3
-}
+const { ChannelTypes } = findByPropsLazy("ChannelTypes");
interface Message {
guild_id: string,
@@ -72,14 +69,35 @@ interface Call {
}
const MuteStore = findByPropsLazy("isSuppressEveryoneEnabled");
+const Notifs = findByPropsLazy("makeTextChatNotification");
const XSLog = new Logger("XSOverlay");
const settings = definePluginSettings({
- ignoreBots: {
+ botNotifications: {
type: OptionType.BOOLEAN,
- description: "Ignore messages from bots",
+ description: "Allow bot notifications",
default: false
},
+ serverNotifications: {
+ type: OptionType.BOOLEAN,
+ description: "Allow server notifications",
+ default: true
+ },
+ dmNotifications: {
+ type: OptionType.BOOLEAN,
+ description: "Allow Direct Message notifications",
+ default: true
+ },
+ groupDmNotifications: {
+ type: OptionType.BOOLEAN,
+ description: "Allow Group DM notifications",
+ default: true
+ },
+ callNotifications: {
+ type: OptionType.BOOLEAN,
+ description: "Allow call notifications",
+ default: true
+ },
pingColor: {
type: OptionType.STRING,
description: "User mention color",
@@ -100,6 +118,11 @@ const settings = definePluginSettings({
description: "Notif duration (secs)",
default: 1.0,
},
+ timeoutPerCharacter: {
+ type: OptionType.NUMBER,
+ description: "Duration multiplier per character",
+ default: 0.5
+ },
opacity: {
type: OptionType.SLIDER,
description: "Notif opacity",
@@ -124,7 +147,7 @@ export default definePlugin({
settings,
flux: {
CALL_UPDATE({ call }: { call: Call; }) {
- if (call?.ringing?.includes(UserStore.getCurrentUser().id)) {
+ if (call?.ringing?.includes(UserStore.getCurrentUser().id) && settings.store.callNotifications) {
const channel = ChannelStore.getChannel(call.channel_id);
sendOtherNotif("Incoming call", `${channel.name} is calling you...`);
}
@@ -134,7 +157,7 @@ export default definePlugin({
try {
if (optimistic) return;
const channel = ChannelStore.getChannel(message.channel_id);
- if (!shouldNotify(message, channel)) return;
+ if (!shouldNotify(message, message.channel_id)) return;
const pingColor = settings.store.pingColor.replaceAll("#", "").trim();
const channelPingColor = settings.store.channelPingColor.replaceAll("#", "").trim();
@@ -194,6 +217,7 @@ export default definePlugin({
finalMsg = finalMsg.replace(/<@!?(\d{17,20})>/g, (_, id) => `@${UserStore.getUser(id)?.username || "unknown-user"}`);
}
+ // color role mentions (unity styling btw lol)
if (message.mention_roles.length > 0) {
for (const roleId of message.mention_roles) {
const role = GuildStore.getRole(channel.guild_id, roleId);
@@ -213,6 +237,7 @@ export default definePlugin({
}
}
+ // color channel mentions
if (channelMatches) {
for (const cMatch of channelMatches) {
let channelId = cMatch.split("<#")[1];
@@ -221,6 +246,7 @@ export default definePlugin({
}
}
+ if (shouldIgnoreForChannelType(channel)) return;
sendMsgNotif(titleString, finalMsg, message);
} catch (err) {
XSLog.error(`Failed to catch MESSAGE_CREATE: ${err}`);
@@ -229,13 +255,20 @@ export default definePlugin({
}
});
+function shouldIgnoreForChannelType(channel: Channel) {
+ if (channel.type === ChannelTypes.DM && settings.store.dmNotifications) return false;
+ if (channel.type === ChannelTypes.GROUP_DM && settings.store.groupDmNotifications) return false;
+ else return !settings.store.serverNotifications;
+}
+
function sendMsgNotif(titleString: string, content: string, message: Message) {
+ const timeout = Math.max(settings.store.timeout, content.length * settings.store.timeoutPerCharacter);
fetch(`https://cdn.discordapp.com/avatars/${message.author.id}/${message.author.avatar}.png?size=128`).then(response => response.arrayBuffer()).then(result => {
const msgData = {
messageType: 1,
index: 0,
- timeout: settings.store.timeout,
- height: calculateHeight(cleanMessage(content)),
+ timeout,
+ height: calculateHeight(content),
opacity: settings.store.opacity,
volume: settings.store.volume,
audioPath: settings.store.soundPath,
@@ -254,7 +287,7 @@ function sendOtherNotif(content: string, titleString: string) {
messageType: 1,
index: 0,
timeout: settings.store.timeout,
- height: calculateHeight(cleanMessage(content)),
+ height: calculateHeight(content),
opacity: settings.store.opacity,
volume: settings.store.volume,
audioPath: settings.store.soundPath,
@@ -267,13 +300,11 @@ function sendOtherNotif(content: string, titleString: string) {
Native.sendToOverlay(msgData);
}
-function shouldNotify(message: Message, channel: Channel) {
+function shouldNotify(message: Message, channel: string) {
const currentUser = UserStore.getCurrentUser();
if (message.author.id === currentUser.id) return false;
- if (message.author.bot && settings.store.ignoreBots) return false;
- if (MuteStore.allowAllMessages(channel) || message.mention_everyone && !MuteStore.isSuppressEveryoneEnabled(message.guild_id)) return true;
-
- return message.mentions.some(m => m.id === currentUser.id);
+ if (message.author.bot && !settings.store.botNotifications) return false;
+ return Notifs.shouldNotify(message, channel);
}
function calculateHeight(content: string) {
@@ -282,7 +313,3 @@ function calculateHeight(content: string) {
if (content.length <= 300) return 200;
return 250;
}
-
-function cleanMessage(content: string) {
- return content.replace(new RegExp("<[^>]*>", "g"), "");
-}
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 99ae3ef0..55620898 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -420,6 +420,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "Kyuuhachi",
id: 236588665420251137n,
},
+ nin0dev: {
+ name: "nin0dev",
+ id: 886685857560539176n
+ },
Elvyra: {
name: "Elvyra",
id: 708275751816003615n,
@@ -460,9 +464,17 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "Oleh Polisan",
id: 242305263313485825n
},
+ HAHALOSAH: {
+ name: "HAHALOSAH",
+ id: 903418691268513883n
+ },
GabiRP: {
name: "GabiRP",
id: 507955112027750401n
+ },
+ ImBanana: {
+ name: "Im_Banana",
+ id: 635250116688871425n
}
} satisfies Record);
diff --git a/src/utils/mergeDefaults.ts b/src/utils/mergeDefaults.ts
new file mode 100644
index 00000000..58ba136d
--- /dev/null
+++ b/src/utils/mergeDefaults.ts
@@ -0,0 +1,24 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/**
+ * Recursively merges defaults into an object and returns the same object
+ * @param obj Object
+ * @param defaults Defaults
+ * @returns obj
+ */
+export function mergeDefaults(obj: T, defaults: T): T {
+ for (const key in defaults) {
+ const v = defaults[key];
+ if (typeof v === "object" && !Array.isArray(v)) {
+ obj[key] ??= {} as any;
+ mergeDefaults(obj[key], v);
+ } else {
+ obj[key] ??= v;
+ }
+ }
+ return obj;
+}
diff --git a/src/utils/misc.tsx b/src/utils/misc.tsx
index 1c7f63b5..7842f79a 100644
--- a/src/utils/misc.tsx
+++ b/src/utils/misc.tsx
@@ -20,25 +20,6 @@ import { Clipboard, Toasts } from "@webpack/common";
import { DevsById, EquicordDevsById } from "./constants";
-/**
- * Recursively merges defaults into an object and returns the same object
- * @param obj Object
- * @param defaults Defaults
- * @returns obj
- */
-export function mergeDefaults(obj: T, defaults: T): T {
- for (const key in defaults) {
- const v = defaults[key];
- if (typeof v === "object" && !Array.isArray(v)) {
- obj[key] ??= {} as any;
- mergeDefaults(obj[key], v);
- } else {
- obj[key] ??= v;
- }
- }
- return obj;
-}
-
/**
* Calls .join(" ") on the arguments
* classes("one", "two") => "one two"
diff --git a/src/utils/settingsSync.ts b/src/utils/settingsSync.ts
index 52dc4422..15653e50 100644
--- a/src/utils/settingsSync.ts
+++ b/src/utils/settingsSync.ts
@@ -18,7 +18,7 @@
import { showNotification } from "@api/Notifications";
import { PlainSettings, Settings } from "@api/Settings";
-import { Toasts } from "@webpack/common";
+import { moment, Toasts } from "@webpack/common";
import { deflateSync, inflateSync } from "fflate";
import { getCloudAuth, getCloudUrl } from "./cloud";
@@ -49,7 +49,7 @@ export async function exportSettings({ minify }: { minify?: boolean; } = {}) {
}
export async function downloadSettingsBackup() {
- const filename = "vencord-settings-backup.json";
+ const filename = `vencord-settings-backup-${moment().format("YYYY-MM-DD")}.json`;
const backup = await exportSettings();
const data = new TextEncoder().encode(backup);