{
- if (IS_DISCORD_DESKTOP) {
- const [file] = await DiscordNative.fileManager.openFiles({
- filters: [
- { name: "Equicord Settings Backup", extensions: ["json"] },
- { name: "all", extensions: ["*"] }
- ]
- });
-
- if (file) {
- try {
- await importSettings(new TextDecoder().decode(file.data));
- if (showToast) toastSuccess();
- } catch (err) {
- new Logger("SettingsSync").error(err);
- if (showToast) toastFailure(err);
- }
- }
- } else {
- const file = await chooseFile("application/json");
- if (!file) return;
-
- const reader = new FileReader();
- reader.onload = async () => {
- try {
- await importSettings(reader.result as string);
- if (showToast) toastSuccess();
- } catch (err) {
- new Logger("SettingsSync").error(err);
- if (showToast) toastFailure(err);
- }
- };
- reader.readAsText(file);
- }
-}
-
-// Cloud settings
-const cloudSettingsLogger = new Logger("Cloud:Settings", "#39b7e0");
-
-export async function putCloudSettings(manual?: boolean) {
- const settings = await exportSettings({ minify: true });
-
- try {
- const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
- method: "PUT",
- headers: {
- Authorization: await getCloudAuth(),
- "Content-Type": "application/octet-stream"
- },
- body: deflateSync(new TextEncoder().encode(settings))
- });
-
- if (!res.ok) {
- cloudSettingsLogger.error(`Failed to sync up, API returned ${res.status}`);
- showNotification({
- title: "Cloud Settings",
- body: `Could not synchronize settings to cloud (API returned ${res.status}).`,
- color: "var(--red-360)"
- });
- return;
- }
-
- const { written } = await res.json();
- PlainSettings.cloud.settingsSyncVersion = written;
- VencordNative.settings.set(PlainSettings);
-
- cloudSettingsLogger.info("Settings uploaded to cloud successfully");
-
- if (manual) {
- showNotification({
- title: "Cloud Settings",
- body: "Synchronized settings to the cloud!",
- noPersist: true,
- });
- }
- } catch (e: any) {
- cloudSettingsLogger.error("Failed to sync up", e);
- showNotification({
- title: "Cloud Settings",
- body: `Could not synchronize settings to the cloud (${e.toString()}).`,
- color: "var(--red-360)"
- });
- }
-}
-
-export async function getCloudSettings(shouldNotify = true, force = false) {
- try {
- const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
- method: "GET",
- headers: {
- Authorization: await getCloudAuth(),
- Accept: "application/octet-stream",
- "If-None-Match": Settings.cloud.settingsSyncVersion.toString()
- },
- });
-
- if (res.status === 401) {
- // User switched to an account that isn't connected to cloud
- showNotification({
- title: "Cloud Settings",
- body: "Cloud sync was disabled because this account isn't connected to the cloud App. You can enable it again by connecting this account in Cloud Settings. (note: it will store your preferences separately)",
- color: "var(--yellow-360)",
- onClick: () => SettingsRouter.open("VencordCloud")
- });
- // Disable cloud sync globally
- Settings.cloud.authenticated = false;
- return false;
- }
-
- if (res.status === 404) {
- cloudSettingsLogger.info("No settings on the cloud");
- if (shouldNotify)
- showNotification({
- title: "Cloud Settings",
- body: "There are no settings in the cloud.",
- noPersist: true
- });
- return false;
- }
-
- if (res.status === 304) {
- cloudSettingsLogger.info("Settings up to date");
- if (shouldNotify)
- showNotification({
- title: "Cloud Settings",
- body: "Your settings are up to date.",
- noPersist: true
- });
- return false;
- }
-
- if (!res.ok) {
- cloudSettingsLogger.error(`Failed to sync down, API returned ${res.status}`);
- showNotification({
- title: "Cloud Settings",
- body: `Could not synchronize settings from the cloud (API returned ${res.status}).`,
- color: "var(--red-360)"
- });
- return false;
- }
-
- const written = Number(res.headers.get("etag")!);
- const localWritten = Settings.cloud.settingsSyncVersion;
-
- // don't need to check for written > localWritten because the server will return 304 due to if-none-match
- if (!force && written < localWritten) {
- if (shouldNotify)
- showNotification({
- title: "Cloud Settings",
- body: "Your local settings are newer than the cloud ones.",
- noPersist: true,
- });
- return;
- }
-
- const data = await res.arrayBuffer();
-
- const settings = new TextDecoder().decode(inflateSync(new Uint8Array(data)));
- await importSettings(settings);
-
- // sync with server timestamp instead of local one
- PlainSettings.cloud.settingsSyncVersion = written;
- VencordNative.settings.set(PlainSettings);
-
- cloudSettingsLogger.info("Settings loaded from cloud successfully");
- if (shouldNotify)
- showNotification({
- title: "Cloud Settings",
- body: "Your settings have been updated! Click here to restart to fully apply changes!",
- color: "var(--green-360)",
- onClick: IS_WEB ? () => location.reload() : relaunch,
- noPersist: true
- });
-
- return true;
- } catch (e: any) {
- cloudSettingsLogger.error("Failed to sync down", e);
- showNotification({
- title: "Cloud Settings",
- body: `Could not synchronize settings from the cloud (${e.toString()}).`,
- color: "var(--red-360)"
- });
-
- return false;
- }
-}
-
-export async function deleteCloudSettings() {
- try {
- const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
- method: "DELETE",
- headers: { Authorization: await getCloudAuth() },
- });
-
- if (!res.ok) {
- cloudSettingsLogger.error(`Failed to delete, API returned ${res.status}`);
- showNotification({
- title: "Cloud Settings",
- body: `Could not delete settings (API returned ${res.status}).`,
- color: "var(--red-360)"
- });
- return;
- }
-
- cloudSettingsLogger.info("Settings deleted from cloud successfully");
- showNotification({
- title: "Cloud Settings",
- body: "Settings deleted from cloud!",
- color: "var(--green-360)"
- });
- } catch (e: any) {
- cloudSettingsLogger.error("Failed to delete", e);
- showNotification({
- title: "Cloud Settings",
- body: `Could not delete settings (${e.toString()}).`,
- color: "var(--red-360)"
- });
- }
-}
diff --git a/src/utils/text.ts b/src/utils/text.ts
index 0203a85dc..2d84fecd0 100644
--- a/src/utils/text.ts
+++ b/src/utils/text.ts
@@ -16,8 +16,6 @@
* along with this program. If not, see .
*/
-import { moment } from "@webpack/common";
-
// Utils for readable text transformations eg: `toTitle(fromKebab())`
// Case style to words
@@ -54,6 +52,7 @@ function getUnitStr(unit: Units, isOne: boolean, short: boolean) {
* @param short Whether to use short units like "d" instead of "days"
*/
export function formatDuration(time: number, unit: Units, short: boolean = false) {
+ const { moment } = require("@webpack/common") as typeof import("@webpack/common");
const dur = moment.duration(time, unit);
let unitsAmounts = units.map(unit => ({ amount: dur[unit](), unit }));
diff --git a/src/utils/types.ts b/src/utils/types.ts
index ed2b51cb6..84ad8ae79 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -16,18 +16,18 @@
* along with this program. If not, see .
*/
-import { ProfileBadge } from "@api/Badges";
-import { ChatBarButtonFactory } from "@api/ChatButtons";
-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 { NicknameIconFactory } from "@api/NicknameIcons";
-import { Command, FluxEvents } from "@vencord/discord-types";
-import { ReactNode } from "react";
-import { LiteralUnion } from "type-fest";
+import type { ProfileBadge } from "@api/Badges";
+import type { ChatBarButtonFactory } from "@api/ChatButtons";
+import type { NavContextMenuPatchCallback } from "@api/ContextMenu";
+import type { MemberListDecoratorFactory } from "@api/MemberListDecorators";
+import type { MessageAccessoryFactory } from "@api/MessageAccessories";
+import type { MessageDecorationFactory } from "@api/MessageDecorations";
+import type { MessageClickListener, MessageEditListener, MessageSendListener } from "@api/MessageEvents";
+import type { MessagePopoverButtonFactory } from "@api/MessagePopover";
+import type { NicknameIconFactory } from "@api/NicknameIcons";
+import type { Command, FluxEvents } from "@vencord/discord-types";
+import type { ReactNode } from "react";
+import type { LiteralUnion } from "type-fest";
// exists to export default definePlugin({...})
export default function definePlugin(p: P & Record) {
diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts
index cc282fd91..659cd8dfd 100644
--- a/src/webpack/common/components.ts
+++ b/src/webpack/common/components.ts
@@ -30,7 +30,9 @@ import { waitForComponent } from "./internal";
export const Forms = {
// TODO: Stop using this and use Heading/Paragraph directly
+ /** @deprecated use Heading from Vencord */
FormTitle: Heading,
+ /** @deprecated use Paragraph from Vencord */
FormText: Paragraph,
/** @deprecated don't use this */
FormSection: "section" as never, // Backwards compat since Vesktop uses this
@@ -39,12 +41,15 @@ export const Forms = {
};
// TODO: Stop using this and use Paragraph/Span directly
+/** @deprecated use Paragraph, Span, or BaseText from Vencord */
export const Text = TextCompat;
+/** @deprecated use Button from Vencord */
export const Button = ButtonCompat;
/** @deprecated Use FormSwitch from Vencord */
export const Switch = FormSwitchCompat as never;
-export const Card = waitForComponent("Card", filters.componentByCode(".editable),", ".outline:"));
+/** @deprecated Use Card from Vencord */
+export const Card = waitForComponent("Card", filters.componentByCode(".editable),", ".outline:"));
export const Checkbox = waitForComponent("Checkbox", filters.componentByCode(".checkboxWrapperDisabled:"));
const Tooltips = mapMangledModuleLazy(".tooltipTop,bottom:", {
@@ -55,6 +60,7 @@ const Tooltips = mapMangledModuleLazy(".tooltipTop,bottom:", {
TooltipContainer: t.TooltipContainer;
};
+// TODO: if these finds break, they should just return their children
export const Tooltip = LazyComponent(() => Tooltips.Tooltip);
export const TooltipContainer = LazyComponent(() => Tooltips.TooltipContainer);
@@ -67,6 +73,7 @@ export const Popout = waitForComponent("Popout", filters.componentByCo
export const Dialog = waitForComponent("Dialog", filters.componentByCode('role:"dialog",tabIndex:-1'));
export const TabBar = waitForComponent("TabBar", filters.componentByCode("ref:this.tabBarRef,className:"));
export const Paginator = waitForComponent("Paginator", filters.componentByCode('rel:"prev",children:'));
+// TODO: remake this component
export const Clickable = waitForComponent("Clickable", filters.componentByCode("this.context?this.renderNonInteractive():"));
export const Avatar = waitForComponent("Avatar", filters.componentByCode(".size-1.375*"));
@@ -106,7 +113,6 @@ waitFor(m => {
export const MaskedLink = waitForComponent("MaskedLink", filters.componentByCode("MASKED_LINK)"));
export const Timestamp = waitForComponent("Timestamp", filters.componentByCode("#{intl::MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL}"));
-export const Flex = waitForComponent("Flex", ["Justify", "Align", "Wrap"]);
export const OAuth2AuthorizeModal = waitForComponent("OAuth2AuthorizeModal", filters.componentByCode(".authorize,children:", ".contentBackground"));
export const Animations = mapMangledModuleLazy(".assign({colorNames:", {
diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts
index 1a0bd6f3a..dd07ed89d 100644
--- a/src/webpack/common/stores.ts
+++ b/src/webpack/common/stores.ts
@@ -34,7 +34,7 @@ export let MessageStore: Omit & GenericStore & {
export let PermissionStore: GenericStore;
export let GuildChannelStore: GenericStore;
export let ReadStateStore: GenericStore;
-export let PresenceStore: GenericStore;
+export let PresenceStore: t.PresenceStore;
export let GuildStore: t.GuildStore;
export let GuildRoleStore: t.GuildRoleStore;
@@ -92,6 +92,7 @@ waitForStore("StreamerModeStore", m => StreamerModeStore = m);
waitForStore("OverridePremiumTypeStore", m => OverridePremiumTypeStore = m);
waitForStore("ThemeStore", m => {
ThemeStore = m;
- // Importing this directly can easily cause circular imports. For this reason, use a non import access here.
- Vencord.QuickCss.initQuickCssThemeStore();
+ // Importing this directly causes all webpack commons to be imported, which can easily cause circular dependencies.
+ // For this reason, use a non import access here.
+ Vencord.Api.Themes.initQuickCssThemeStore(m);
});
diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts
index 63778f5cc..011f2e410 100644
--- a/src/webpack/common/utils.ts
+++ b/src/webpack/common/utils.ts
@@ -22,8 +22,9 @@ import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapM
export let FluxDispatcher: t.FluxDispatcher;
waitFor(["dispatch", "subscribe"], m => {
FluxDispatcher = m;
- // Non import access to avoid circular dependency
- Vencord.Plugins.subscribeAllPluginsFluxEvents(m);
+ // Importing this directly causes all webpack commons to be imported, which can easily cause circular dependencies.
+ // For this reason, use a non import access here.
+ Vencord.Api.PluginManager.subscribeAllPluginsFluxEvents(m);
const cb = () => {
m.unsubscribe("CONNECTION_OPEN", cb);
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 64cfdd7c9..e261b07a2 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -538,7 +538,7 @@ function patchFactory(moduleId: PropertyKey, originalFactory: AnyModuleFactory):
const previousFactory = originalFactory;
let markedAsPatched = false;
- // We change all patch.replacement to array in plugins/index
+ // We change all patch.replacement to array in PluginManager
for (const replacement of patch.replacement as PatchReplacement[]) {
if (
shouldCheckBuildNumber &&
diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts
index d8f6d95c3..2d8813ab4 100644
--- a/src/webpack/webpack.ts
+++ b/src/webpack/webpack.ts
@@ -20,12 +20,11 @@ import { makeLazy, proxyLazy } from "@utils/lazy";
import { LazyComponent } from "@utils/lazyReact";
import { Logger } from "@utils/Logger";
import { canonicalizeMatch } from "@utils/patches";
-import { FluxStore } from "@vencord/discord-types";
-import { ModuleExports, WebpackRequire } from "@vencord/discord-types/webpack";
+import type { FluxStore } from "@vencord/discord-types";
+import type { ModuleExports, WebpackRequire } from "@vencord/discord-types/webpack";
import { traceFunction } from "../debug/Tracer";
-import { Flux } from "./common";
-import { AnyModuleFactory, AnyWebpackRequire } from "./types";
+import type { AnyModuleFactory, AnyWebpackRequire } from "./types";
const logger = new Logger("Webpack");
@@ -39,7 +38,7 @@ export const onceReady = new Promise(r => _resolveReady = r);
export let wreq: WebpackRequire;
export let cache: WebpackRequire["c"];
-export const fluxStores: Record = {};
+export const fluxStores = new Map();
export type FilterFn = (mod: any) => boolean;
@@ -75,7 +74,7 @@ export const filters = {
componentByCode: (...code: CodeFilter): FilterFn => {
const byCodeFilter = filters.byCode(...code);
- const filter = m => {
+ const filter = (m: any) => {
let inner = m;
while (inner != null) {
@@ -457,55 +456,50 @@ export function findByCodeLazy(...code: CodeFilter) {
return proxyLazy(() => findByCode(...code));
}
+function populateFluxStoreMap() {
+ const { Flux } = require("./common") as typeof import("./common");
+
+ Flux.Store.getAll?.().forEach(store =>
+ fluxStores.set(store.getName(), store)
+ );
+
+ try {
+ const getLibdiscore = findByCode("libdiscoreWasm is not initialized");
+ const libdiscoreExports = getLibdiscore();
+
+ for (const libdiscoreExportName in libdiscoreExports) {
+ if (!libdiscoreExportName.endsWith("Store")) {
+ continue;
+ }
+
+ const storeName = libdiscoreExportName;
+ const store = libdiscoreExports[storeName];
+
+ fluxStores.set(storeName, store);
+ }
+ } catch { }
+}
+
/**
* Find a store by its displayName
*/
export function findStore(name: StoreNameFilter) {
- let res = fluxStores[name] as any;
- if (res == null) {
- for (const store of Flux.Store.getAll?.() ?? []) {
- const storeName = store.getName();
-
- if (storeName === name) {
- res = store;
- }
-
- if (fluxStores[storeName] == null) {
- fluxStores[storeName] = store;
- }
- }
-
- try {
- const getLibdiscore = findByCode("libdiscoreWasm is not initialized");
- const libdiscoreExports = getLibdiscore();
-
- for (const libdiscoreExportName in libdiscoreExports) {
- if (!libdiscoreExportName.endsWith("Store")) {
- continue;
- }
-
- const storeName = libdiscoreExportName;
- const store = libdiscoreExports[storeName];
-
- if (storeName === name) {
- res = store;
- }
-
- if (fluxStores[storeName] == null) {
- fluxStores[storeName] = store;
- }
- }
-
- } catch { }
-
- if (res == null) {
- res = find(filters.byStoreName(name), { isIndirect: true });
- }
+ if (!fluxStores.has(name)) {
+ populateFluxStoreMap();
}
- if (!res)
- handleModuleNotFound("findStore", name);
- return res;
+ if (fluxStores.has(name)) {
+ return fluxStores.get(name);
+ }
+
+ const res = find(filters.byStoreName(name), { isIndirect: true });
+ if (res) {
+ fluxStores.set(name, res);
+ return res;
+ }
+
+ handleModuleNotFound("findStore", name);
+ return null;
}
/**
diff --git a/tsconfig.json b/tsconfig.json
index ff6756143..4a9a031cc 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -14,7 +14,7 @@
"esnext.symbol"
],
"module": "esnext",
- "moduleResolution": "node",
+ "moduleResolution": "bundler",
"strict": true,
"noImplicitAny": false,
"target": "ESNEXT",
@@ -25,8 +25,10 @@
"@api/*": ["./api/*"],
"@components/*": ["./components/*"],
"@utils/*": ["./utils/*"],
+ "@plugins/*": ["./plugins/*"],
"@shared/*": ["./shared/*"],
"@webpack/common": ["./webpack/common"],
+ "@webpack/common/*": ["./webpack/common/*"],
"@webpack": ["./webpack/webpack"],
"@webpack/patcher": ["./webpack/patchWebpack"],
"@webpack/wreq.d": ["./webpack/wreq.d"],