Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
thororen 2026-10-03 11:10:38 -04:00
commit cfec48401b
No known key found for this signature in database
37 changed files with 239 additions and 264 deletions

View file

@ -53,3 +53,8 @@ export const enum PremiumType {
TIER_2 = 2, TIER_2 = 2,
TIER_0 = 3, TIER_0 = 3,
} }
export const enum ToastPosition {
TOP = 0,
BOTTOM = 1,
}

View file

@ -7,5 +7,6 @@ export * from "./fluxEvents";
export * from "./menu"; export * from "./menu";
export * from "./modules"; export * from "./modules";
export * from "./stores"; export * from "./stores";
export * from "./toasts";
export * from "./utils"; export * from "./utils";
export * as Webpack from "../webpack"; export * as Webpack from "../webpack";

35
packages/discord-types/src/toasts.d.ts vendored Normal file
View file

@ -0,0 +1,35 @@
import type { ComponentType } from "react";
import { ToastPosition } from "../enums";
export type ToastType = "message" | "success" | "failure" | "custom" | "clip" | "link" | "forward" | "bookmark" | "clock";
export type NewToastVariant = "default" | "success" | "critical";
export type NewToastPosition = "top" | "bottom";
export interface ToastData {
message: string;
type?: ToastType;
options?: {
position?: ToastPosition;
duration?: number;
};
}
export interface NewToastData {
text: string;
variant: NewToastVariant;
position?: NewToastPosition;
duration?: number;
icon?: ComponentType;
iconColor?: any;
}
export type showToast = (data: NewToastData) => void;
export type popToast = (context?: string) => void;
export type createToast = (data: ToastData) => NewToastData;
export interface Toasts {
show: showToast;
pop: popToast;
}

View file

@ -417,13 +417,13 @@ export interface EmojiUtils {
applyPlatformToThemedEmojiColorPalette(colors: any): any; applyPlatformToThemedEmojiColorPalette(colors: any): any;
} }
export interface OpenUserSettingsOpts { export interface OpenUserSettingsOptions {
/** /**
* a *sub*section of settings to navigate to * a *sub*section of settings to navigate to
* *
* to navigate to a top-level section, use the navigationTarget parameter of {@link SettingsRouter.openUserSettings} instead * to navigate to a top-level section, use the navigationTarget parameter of {@link SettingsRouter.openUserSettings} instead
* *
* discord has an enum with all the possible values (go dig in the openUserSettings source) * discord has an enum with all the possible values (go dig in the {@link SettingsRouter.openUserSettings} source)
*/ */
path?: string; path?: string;
/** /**
@ -434,10 +434,10 @@ export interface OpenUserSettingsOpts {
} }
export interface SettingsRouter { export interface SettingsRouter {
openUserSettings(navigationTarget?: string, opts?: OpenUserSettingsOpts, onOpen?: () => void): Promise<void>; openUserSettings(navigationTarget?: string, opts?: OpenUserSettingsOptions, onOpen?: () => void): Promise<void>;
} }
export interface OpenUserProfileModalOpts { export interface OpenUserProfileModalOptions {
userId: string; userId: string;
guildId?: string | null; guildId?: string | null;
originGuildId?: string | null; originGuildId?: string | null;
@ -458,6 +458,4 @@ export interface OpenUserProfileModalOpts {
onModalOpen?: unknown; onModalOpen?: unknown;
} }
export interface UserProfileModalActionCreators { export type OpenUserProfileModal = (opts: OpenUserProfileModalOptions) => Promise<void>;
openUserProfileModal(opts: OpenUserProfileModalOpts): Promise<void>;
}

View file

@ -20,6 +20,8 @@
import "~plugins"; import "~plugins";
import "./fixWeirdAppRegionBug.css"; import "./fixWeirdAppRegionBug.css";
import nativeTitleBarStyles from "./nativeTitleBar.css?managed";
export * as Api from "./api"; export * as Api from "./api";
export * as Plugins from "./api/PluginManager"; export * as Plugins from "./api/PluginManager";
export * as Components from "./components"; export * as Components from "./components";
@ -29,11 +31,9 @@ export * as Webpack from "./webpack";
export * as WebpackPatcher from "./webpack/patchWebpack"; export * as WebpackPatcher from "./webpack/patchWebpack";
export { PlainSettings, Settings }; export { PlainSettings, Settings };
import { coreStyleRootNode, initStyles } from "@api/Styles"; import { enableStyle, initStyles } from "@api/Styles";
import { openSettingsTabModal, UpdaterTab } from "@components/settings"; import { openSettingsTabModal, UpdaterTab } from "@components/settings";
import { debounce } from "@shared/debounce"; import { debounce } from "@shared/debounce";
import { IS_WINDOWS } from "@utils/constants";
import { createAndAppendStyle } from "@utils/css";
import { StartAt } from "@utils/types"; import { StartAt } from "@utils/types";
import { SettingsRouter } from "@webpack/common"; import { SettingsRouter } from "@webpack/common";
@ -237,8 +237,7 @@ init();
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
startAllPlugins(StartAt.DOMContentLoaded); startAllPlugins(StartAt.DOMContentLoaded);
// FIXME if (IS_DISCORD_DESKTOP && Settings.nativeTitleBar) {
if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && IS_WINDOWS) { enableStyle(nativeTitleBarStyles);
createAndAppendStyle("vencord-native-titlebar-style", coreStyleRootNode).textContent = "[class*=titleBar]{display: none!important}";
} }
}, { once: true }); }, { once: true });

View file

@ -72,7 +72,7 @@ export interface Settings {
| undefined; | undefined;
windowsMaterial: "none" | "mica" | "tabbed" | "acrylic"; windowsMaterial: "none" | "mica" | "tabbed" | "acrylic";
disableMinSize: boolean; disableMinSize: boolean;
winNativeTitleBar: boolean; nativeTitleBar: boolean;
plugins: { plugins: {
[plugin: string]: { [plugin: string]: {
enabled: boolean; enabled: boolean;
@ -124,7 +124,7 @@ const DefaultSettings: Settings = {
macosVibrancyStyle: undefined, macosVibrancyStyle: undefined,
windowsMaterial: "none", windowsMaterial: "none",
disableMinSize: false, disableMinSize: false,
winNativeTitleBar: false, nativeTitleBar: false,
plugins: {}, plugins: {},
uiElements: { uiElements: {

View file

@ -7,24 +7,17 @@
import { PlainSettings } from "@api/Settings"; import { PlainSettings } from "@api/Settings";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { chooseFile, saveFile } from "@utils/web"; import { chooseFile, saveFile } from "@utils/web";
import { moment, Toasts } from "@webpack/common"; import { moment, showToast } from "@webpack/common";
import { DataStore } from ".."; import { DataStore } from "..";
type BackupType = "all" | "plugins" | "css" | "datastore"; type BackupType = "all" | "plugins" | "css" | "datastore";
const toast = (type: string, message: string) =>
Toasts.show({
type,
message,
id: Toasts.genId()
});
const toastSuccess = () => const toastSuccess = () =>
toast(Toasts.Type.SUCCESS, "Settings successfully imported. Restart to apply changes!"); showToast("Settings successfully imported. Restart to apply changes!", "success");
const toastFailure = (err: any) => const toastFailure = (err: any) =>
toast(Toasts.Type.FAILURE, `Failed to import settings: ${String(err)}`); showToast(`Failed to import settings: ${String(err)}`, "failure");
const logger = new Logger("SettingsSync:Offline", "#39b7e0"); const logger = new Logger("SettingsSync:Offline", "#39b7e0");
@ -118,7 +111,7 @@ export async function exportSettings({ syncDataStore = true, type = "all", minif
if (type === "all") { if (type === "all") {
logger.warn("Skipping DataStore in backup due to size. Export DataStore separately if needed."); logger.warn("Skipping DataStore in backup due to size. Export DataStore separately if needed.");
toast(Toasts.Type.MESSAGE, "DataStore too large - exported without it. Use 'Export DataStore' separately if needed."); showToast("DataStore too large - exported without it. Use 'Export DataStore' separately if needed.", "message");
dataStore = undefined; dataStore = undefined;
} else if (type === "datastore") { } else if (type === "datastore") {
throw new Error("DataStore is too large to export. Please clear some plugin data and try again."); throw new Error("DataStore is too large to export. Please clear some plugin data and try again.");
@ -156,7 +149,7 @@ export async function downloadSettingsBackup(type: BackupType = "all", { minify
} }
} catch (err) { } catch (err) {
logger.error("Failed to export settings:", err); logger.error("Failed to export settings:", err);
toast(Toasts.Type.FAILURE, "Failed to export settings, check console"); showToast("Failed to export settings, check console", "failure");
throw err; throw err;
} }
} }

View file

@ -12,7 +12,8 @@ import { AddonCard } from "@components/settings/AddonCard";
import { classNameFactory } from "@utils/css"; import { classNameFactory } from "@utils/css";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Plugin } from "@utils/types"; import { Plugin } from "@utils/types";
import { React, showToast, Toasts } from "@webpack/common"; import { ToastPosition } from "@vencord/discord-types/enums";
import { React, showToast } from "@webpack/common";
import { PluginMeta } from "~plugins"; import { PluginMeta } from "~plugins";
@ -79,9 +80,7 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
settings.enabled = false; settings.enabled = false;
const msg = `Error while ${wasEnabled ? "stopping" : "starting"} plugin ${plugin.name}`; const msg = `Error while ${wasEnabled ? "stopping" : "starting"} plugin ${plugin.name}`;
showToast(msg, Toasts.Type.FAILURE, { showToast(msg, "failure", { position: ToastPosition.BOTTOM });
position: Toasts.Position.BOTTOM,
});
return; return;
} }

View file

@ -14,7 +14,8 @@ import { Span } from "@components/Span";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { relaunch } from "@utils/native"; import { relaunch } from "@utils/native";
import { changes, checkForUpdates, update, updateError } from "@utils/updater"; import { changes, checkForUpdates, update, updateError } from "@utils/updater";
import { ConfirmModal, openModal, React, Toasts, useState } from "@webpack/common"; import { ToastPosition } from "@vencord/discord-types/enums";
import { ConfirmModal, openModal, React, showToast, Toasts, useState } from "@webpack/common";
import { runWithDispatch } from "./runWithDispatch"; import { runWithDispatch } from "./runWithDispatch";
@ -135,6 +136,24 @@ export function Updatable(props: CommonProps) {
Update Now Update Now
</Button> </Button>
)} )}
<Button
disabled={isUpdating || isChecking}
onClick={runWithDispatch(setIsChecking, async () => {
const outdated = await checkForUpdates();
if (outdated) {
setUpdates(changes);
} else {
setUpdates([]);
showToast("No updates found!", "message", {
position: ToastPosition.BOTTOM
});
}
})}
>
Check for Updates
</Button>
</Flex> </Flex>
{!updates && updateError ? ( {!updates && updateError ? (
<> <>

View file

@ -49,7 +49,7 @@ type KeysOfType<Object, Type> = {
}[keyof Object]; }[keyof Object];
function Switches() { function Switches() {
const settings = useSettings(["useQuickCss", "enableReactDevtools", "mainWindowFrameless", "frameless", "winNativeTitleBar", "transparent", "winCtrlQ", "disableMinSize"]); const settings = useSettings(["useQuickCss", "enableReactDevtools", "mainWindowFrameless", "frameless", "nativeTitleBar", "transparent", "winCtrlQ", "disableMinSize"]);
const Switches = [ const Switches = [
{ {
@ -71,7 +71,7 @@ function Switches() {
restartRequired: true, restartRequired: true,
} }
: { : {
key: "winNativeTitleBar", key: "nativeTitleBar",
title: "Use Windows' native title bar instead of Discord's custom one", title: "Use Windows' native title bar instead of Discord's custom one",
description: "Replace Discord's custom title bar with the standard Windows title bar. This may improve compatibility with some window management tools.", description: "Replace Discord's custom title bar with the standard Windows title bar. This may improve compatibility with some window management tools.",
restartRequired: true, restartRequired: true,

View file

@ -87,7 +87,7 @@ if (!IS_VANILLA) {
return; return;
} }
const { frameless, mainWindowFrameless, winNativeTitleBar, disableMinSize, transparent, macosVibrancyStyle, windowsMaterial } = settings; const { frameless, mainWindowFrameless, nativeTitleBar, disableMinSize, transparent, macosVibrancyStyle, windowsMaterial } = settings;
const original = options.webPreferences.preload; const original = options.webPreferences.preload;
const isMainWindow = options.title === "Discord"; const isMainWindow = options.title === "Discord";
@ -98,7 +98,7 @@ if (!IS_VANILLA) {
options.frame = false; options.frame = false;
} else if (frameless) { } else if (frameless) {
options.frame = false; options.frame = false;
} else if (process.platform === "win32" && winNativeTitleBar) { } else if (process.platform === "win32" && nativeTitleBar) {
delete options.frame; delete options.frame;
} }

7
src/nativeTitleBar.css Normal file
View file

@ -0,0 +1,7 @@
[class*="bar_"] {
margin-inline-end: 12px !important;
}
[class*="winButtons_"] {
display: none !important;
}

View file

@ -27,7 +27,7 @@ import { copyWithToast } from "@utils/discord";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { shouldShowContributorBadge, shouldShowEquicordContributorBadge } from "@utils/misc"; import { shouldShowContributorBadge, shouldShowEquicordContributorBadge } from "@utils/misc";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { ContextMenuApi, Menu, Toasts, UserStore } from "@webpack/common"; import { ContextMenuApi, Menu, showToast, UserStore } from "@webpack/common";
import Plugins, { PluginMeta } from "~plugins"; import Plugins, { PluginMeta } from "~plugins";
@ -181,11 +181,7 @@ export default definePlugin({
toolboxActions: { toolboxActions: {
async "Refetch Badges"() { async "Refetch Badges"() {
await loadAllBadges(true); await loadAllBadges(true);
Toasts.show({ showToast("Successfully refetched badges!", "success");
id: Toasts.genId(),
message: "Successfully refetched badges!",
type: Toasts.Type.SUCCESS
});
} }
}, },

View file

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { sendBotMessage } from "@api/Commands";
import { isPluginEnabled } from "@api/PluginManager"; import { isPluginEnabled } from "@api/PluginManager";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { getUserSettingLazy } from "@api/UserSettings"; import { getUserSettingLazy } from "@api/UserSettings";
@ -34,7 +33,7 @@ import { CONTRIB_ROLE_ID, Devs, DONOR_ROLE_ID, EQUICORD_TEAM, GUILD_ID, SUPPORT_
import { sendMessage } from "@utils/discord"; import { sendMessage } from "@utils/discord";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { isAnyPluginDev, isEquicordGuild, isEquicordSupport, isKnownIssuesCategory, isSupportChannel, tryOrElse } from "@utils/misc"; import { isAnyPluginDev, isEquicordGuild, isEquicordSupport, isSupportChannel, tryOrElse } from "@utils/misc";
import { relaunch } from "@utils/native"; import { relaunch } from "@utils/native";
import { onlyOnce } from "@utils/onlyOnce"; import { onlyOnce } from "@utils/onlyOnce";
import { makeCodeblock } from "@utils/text"; import { makeCodeblock } from "@utils/text";
@ -49,8 +48,6 @@ import plugins, { PluginMeta } from "~plugins";
import SettingsPlugin from "./settings"; import SettingsPlugin from "./settings";
const CodeBlockRe = /```snippet\n(.+?)```/s;
const TrustedRolesIds = [ const TrustedRolesIds = [
VC_CONTRIB_ROLE_ID, // Vencord Contributor VC_CONTRIB_ROLE_ID, // Vencord Contributor
VC_REGULAR_ROLE_ID, // Vencord Regular VC_REGULAR_ROLE_ID, // Vencord Regular
@ -61,8 +58,6 @@ const TrustedRolesIds = [
VENCORD_CONTRIB_ROLE_ID, // Vencord Contributor VENCORD_CONTRIB_ROLE_ID, // Vencord Contributor
]; ];
const AsyncFunction = async function () { }.constructor;
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!; const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
const ShowEmbeds = getUserSettingLazy<boolean>("textAndImages", "renderEmbeds")!; const ShowEmbeds = getUserSettingLazy<boolean>("textAndImages", "renderEmbeds")!;
@ -451,12 +446,12 @@ export default definePlugin({
onClick={async () => { onClick={async () => {
try { try {
if (await forceUpdate()) if (await forceUpdate())
showToast("Success! Restarting...", Toasts.Type.SUCCESS); showToast("Success! Restarting...", "success");
else else
showToast("Already up to date!", Toasts.Type.MESSAGE); showToast("Already up to date!");
} catch (e) { } catch (e) {
new Logger(this.name).error("Error while updating:", e); new Logger(this.name).error("Error while updating:", e);
showToast("Failed to update :(", Toasts.Type.FAILURE); showToast("Failed to update :(", "failure");
} }
}} }}
> >
@ -498,34 +493,6 @@ export default definePlugin({
); );
} }
} }
if (equicordSupport || isSupportChannel(props?.channel?.id, true) || isKnownIssuesCategory(props?.channel?.parent_id, true)) {
const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || "");
if (match) {
buttons.push(
<Button
key="vc-run-snippet"
onClick={async () => {
try {
const result = await AsyncFunction(match[1])();
const stringed = String(result);
if (stringed) {
await sendBotMessage(SelectedChannelStore.getChannelId(), {
content: stringed
});
}
showToast("Success!", Toasts.Type.SUCCESS);
} catch (e) {
new Logger(this.name).error("Error while running snippet:", e);
showToast("Failed to run snippet :(", Toasts.Type.FAILURE);
}
}}
>
Run Snippet
</Button>
);
}
}
return buttons.length return buttons.length
? <Flex>{buttons}</Flex> ? <Flex>{buttons}</Flex>

View file

@ -7,12 +7,12 @@
import { ChatBarButton } from "@api/ChatButtons"; import { ChatBarButton } from "@api/ChatButtons";
import { UploadIcon } from "@components/Icons"; import { UploadIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { pluralize } from "@utils/misc"; import { pluralise } from "@utils/misc";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { Message } from "@vencord/discord-types"; import { Message } from "@vencord/discord-types";
import { MessageFlags, MessageType } from "@vencord/discord-types/enums"; import { MessageFlags, MessageType } from "@vencord/discord-types/enums";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { AuthenticationStore, ChannelStore, EditMessageStore, MessageStore, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, Toasts } from "@webpack/common"; import { AuthenticationStore, ChannelStore, EditMessageStore, MessageStore, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast } from "@webpack/common";
const { uniqueId } = findByPropsLazy("uniqueId"); const { uniqueId } = findByPropsLazy("uniqueId");
@ -39,15 +39,15 @@ async function addAttachments(channelId: string, messageId: string, existingAtta
if (files.length + existingAttachmentCount > 10) { if (files.length + existingAttachmentCount > 10) {
const remaining = 10 - existingAttachmentCount; const remaining = 10 - existingAttachmentCount;
if (remaining <= 0) { if (remaining <= 0) {
showToast("You cannot add more attachments to this message.", Toasts.Type.FAILURE); showToast("You cannot add more attachments to this message.", "failure");
return; return;
} }
showToast(`You can only add ${pluralize(remaining, "more attachment")} to this message.`, Toasts.Type.FAILURE); showToast(`You can only add ${pluralise(remaining, "more attachment")} to this message.`, "failure");
return; return;
} }
showToast("Uploading, this can take a while...", Toasts.Type.CLOCK); showToast("Uploading, this can take a while...", "clock");
const { body: { attachments } } = await RestAPI.post({ const { body: { attachments } } = await RestAPI.post({
url: `/channels/${channelId}/attachments`, url: `/channels/${channelId}/attachments`,
@ -86,7 +86,7 @@ async function addAttachments(channelId: string, messageId: string, existingAtta
} }
}); });
showToast(`${files.length === 1 ? "Attachment" : "Attachments"} added successfully!`, Toasts.Type.SUCCESS); showToast(`${files.length === 1 ? "Attachment" : "Attachments"} added successfully!`, "success");
} }
function canAddAttachments(msg: Message) { function canAddAttachments(msg: Message) {

View file

@ -23,7 +23,7 @@ import { Link } from "@components/Link";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { ReporterTestable } from "@utils/types"; import definePlugin, { ReporterTestable } from "@utils/types";
import { ApplicationAssetUtils, fetchApplicationsRPC, FluxDispatcher, Toasts } from "@webpack/common"; import { ApplicationAssetUtils, fetchApplicationsRPC, FluxDispatcher, showToast } from "@webpack/common";
async function lookupAsset(applicationId: string, key: string): Promise<string> { async function lookupAsset(applicationId: string, key: string): Promise<string> {
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0]; return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
@ -92,16 +92,7 @@ export default definePlugin({
return; return;
} }
Toasts.show({ showToast("Connected to arRPC", "success");
// show toast on success
message: "Connected to arRPC",
type: Toasts.Type.SUCCESS,
id: Toasts.genId(),
options: {
duration: 1000,
position: Toasts.Position.BOTTOM
}
});
}, },
stop() { stop() {

View file

@ -218,12 +218,10 @@ export default definePlugin({
settings, settings,
openRoleContextMenu, openRoleContextMenu,
patches: [ patches: [
// This patch is completely overwritten by RoleColorEverywhere, but RoleColorEverywhere handles it itself
// member list role headers (in threads)
{ {
find: 'tutorialId:"whos-online', find: 'tutorialId:"whos-online',
replacement: { replacement: {
match: /(?<=#{intl::CHANNEL_MEMBERS_A11Y_LABEL}.{0,200}?"aria-hidden":!0,)children:.{0,200}?(?:—|\\u2014) ",\i\]\}\)\]/, match: /(?<=#{intl::CHANNEL_MEMBERS_A11Y_LABEL}.{0,400}?)children:(?=.{0,20}?(?:—|\\u2014) ",\i\])/,
replace: "onContextMenu:e=>$self.openRoleContextMenu(e,arguments[0]),$&" replace: "onContextMenu:e=>$self.openRoleContextMenu(e,arguments[0]),$&"
} }
}, },

View file

@ -18,22 +18,21 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { getUserSettingLazy } from "@api/UserSettings"; import { getUserSettingLazy } from "@api/UserSettings";
import { BaseText } from "@components/BaseText";
import { Button } from "@components/Button";
import { Card } from "@components/Card";
import { Divider } from "@components/Divider"; import { Divider } from "@components/Divider";
import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Heading } from "@components/Heading";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { isTruthy } from "@utils/guards"; import { isTruthy } from "@utils/guards";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { Activity } from "@vencord/discord-types"; import { Activity } from "@vencord/discord-types";
import { ActivityType } from "@vencord/discord-types/enums"; import { ActivityType } from "@vencord/discord-types/enums";
import { findByCodeLazy, findComponentByCodeLazy } from "@webpack"; import { findByCodeLazy, findComponentByCodeLazy } from "@webpack";
import { ApplicationAssetUtils, Button, FluxDispatcher, React, UserStore } from "@webpack/common"; import { ApplicationAssetUtils, FluxDispatcher, Menu, React, UserStore } from "@webpack/common";
import { RPCSettings } from "./RpcSettings"; import { RPCSettings } from "./RpcSettings";
@ -80,6 +79,12 @@ export interface RpcConfig {
} }
export const settings = definePluginSettings({ export const settings = definePluginSettings({
enablePresence: {
type: OptionType.BOOLEAN,
description: "Whether to show the presence",
default: true,
onChange: v => setRpc(!v)
},
config: { config: {
type: OptionType.COMPONENT, type: OptionType.COMPONENT,
component: RPCSettings component: RPCSettings
@ -211,11 +216,12 @@ async function createActivity(): Promise<Activity | undefined> {
} }
export async function setRpc(disable?: boolean) { export async function setRpc(disable?: boolean) {
const shouldDisable = disable ?? !settings.store.enablePresence;
const activity: Activity | undefined = await createActivity(); const activity: Activity | undefined = await createActivity();
FluxDispatcher.dispatch({ FluxDispatcher.dispatch({
type: "LOCAL_ACTIVITY_UPDATE", type: "LOCAL_ACTIVITY_UPDATE",
activity: !disable ? activity : null, activity: !shouldDisable ? activity : null,
socketId: "CustomRPC", socketId: "CustomRPC",
}); });
} }
@ -257,12 +263,28 @@ export default definePlugin({
name: "CustomRPC", name: "CustomRPC",
description: "Add a fully customisable Rich Presence (Game status) to your Discord profile", description: "Add a fully customisable Rich Presence (Game status) to your Discord profile",
tags: ["Activity", "Customisation"], tags: ["Activity", "Customisation"],
authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev], authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev, Devs.c0nnorgg],
dependencies: ["UserSettingsAPI"], dependencies: ["UserSettingsAPI"],
// This plugin's patch is not important for functionality, so don't require a restart // This plugin's patch is not important for functionality, so don't require a restart
requiresRestart: false, requiresRestart: false,
settings, settings,
toolboxActions() {
const { enablePresence } = settings.use(["enablePresence"]);
return (
<Menu.MenuCheckboxItem
id="custom-rpc-toggle-toolbox"
label="Show Custom RPC"
checked={enablePresence}
action={() => {
settings.store.enablePresence = !enablePresence;
setRpc();
}}
/>
);
},
start() { start() {
startTimestampLoop(); startTimestampLoop();
setRpc(); setRpc();
@ -284,31 +306,38 @@ export default definePlugin({
], ],
settingsAboutComponent: () => { settingsAboutComponent: () => {
const { enablePresence } = settings.use(["enablePresence"]);
const [activity] = useAwaiter(createActivity, { fallbackValue: undefined, deps: Object.values(settings.store) }); const [activity] = useAwaiter(createActivity, { fallbackValue: undefined, deps: Object.values(settings.store) });
const gameActivityEnabled = ShowCurrentGame.useSetting(); const gameActivityEnabled = ShowCurrentGame.useSetting();
const { profileThemeStyle } = useProfileThemeStyle({}); const { profileThemeStyle } = useProfileThemeStyle({});
return ( return (
<> <Flex flexDirection="column" gap=".5em">
{!enablePresence && (
<Card variant="warning">
<Flex flexDirection="column" gap=".5em" alignItems="flex-start">
<BaseText size="md" weight="bold">Custom RPC disabled</BaseText>
<Paragraph>The "Enable Presence" setting is disabled, so your presence won't show. If this was unintentional, enable it below.</Paragraph>
</Flex>
</Card>
)}
{!gameActivityEnabled && ( {!gameActivityEnabled && (
<ErrorCard <Card variant="danger">
className={classes(Margins.top16, Margins.bottom16)} <Flex flexDirection="column" gap=".5em" alignItems="flex-start">
style={{ padding: "1em" }} <BaseText size="md" weight="bold">Activity Sharing disabled</BaseText>
> <Paragraph>Activity Sharing isn't enabled, so people won't be able to see your custom rich presence!</Paragraph>
<Heading>Notice</Heading>
<Paragraph>Activity Sharing isn't enabled, people won't be able to see your custom rich presence!</Paragraph>
<Button <Button
color={Button.Colors.TRANSPARENT} variant="overlayPrimary"
className={Margins.top8}
onClick={() => ShowCurrentGame.updateSetting(true)} onClick={() => ShowCurrentGame.updateSetting(true)}
> >
Enable Enable Activity Sharing
</Button> </Button>
</ErrorCard> </Flex>
</Card>
)} )}
<Flex flexDirection="column" gap=".5em" className={Margins.top16}> <Flex flexDirection="column" gap=".5em">
<Paragraph> <Paragraph>
Go to the <Link href="https://discord.com/developers/applications">Discord Developer Portal</Link> to create an application and Go to the <Link href="https://discord.com/developers/applications">Discord Developer Portal</Link> to create an application and
get the application ID. get the application ID.
@ -327,16 +356,16 @@ export default definePlugin({
</Paragraph> </Paragraph>
</Flex> </Flex>
<Divider className={Margins.top8} /> <Divider />
<div style={{ width: "284px", ...profileThemeStyle, marginTop: 8, borderRadius: 8, background: "var(--background-mod-muted)" }}> <div style={{ width: "284px", ...profileThemeStyle, borderRadius: 8, background: "var(--background-mod-muted)" }}>
{activity && <ActivityView {activity && <ActivityView
activity={activity} activity={activity}
user={UserStore.getCurrentUser()} user={UserStore.getCurrentUser()}
currentUser={UserStore.getCurrentUser()} currentUser={UserStore.getCurrentUser()}
/>} />}
</div> </div>
</> </Flex>
); );
} }
}); });

View file

@ -8,7 +8,7 @@ import * as DataStore from "@api/DataStore";
import { AUTHORIZE_URL, CLIENT_ID } from "@plugins/decor/lib/constants"; import { AUTHORIZE_URL, CLIENT_ID } from "@plugins/decor/lib/constants";
import { proxyLazy } from "@utils/lazy"; import { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { OAuth2AuthorizeModal, openModal, showToast, Toasts, UserStore, zustandCreate, zustandPersist } from "@webpack/common"; import { OAuth2AuthorizeModal, openModal, showToast, UserStore, zustandCreate, zustandPersist } from "@webpack/common";
interface AuthorizationState { interface AuthorizationState {
token: string | null; token: string | null;
@ -74,7 +74,7 @@ export const useAuthorizationStore = proxyLazy(() => zustandCreate(
resolve(void 0); resolve(void 0);
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {
showToast(`Failed to authorize: ${e.message}`, Toasts.Type.FAILURE); showToast(`Failed to authorize: ${e.message}`, "failure");
new Logger("Decor").error("Failed to authorize", e); new Logger("Decor").error("Failed to authorize", e);
reject(e); reject(e);
} }

View file

@ -31,7 +31,7 @@ import definePlugin from "@utils/types";
import { Guild, GuildSticker, Message } from "@vencord/discord-types"; import { Guild, GuildSticker, Message } from "@vencord/discord-types";
import { StickerFormatType } from "@vencord/discord-types/enums"; import { StickerFormatType } from "@vencord/discord-types/enums";
import { findByCodeLazy } from "@webpack"; import { findByCodeLazy } from "@webpack";
import { Constants, EmojiStore, FluxDispatcher, GuildStore, IconUtils, Menu, Modal, openModalLazy, PermissionsBits, PermissionStore, React, RestAPI, StickersStore, Toasts, Tooltip, UserStore } from "@webpack/common"; import { Constants, EmojiStore, FluxDispatcher, GuildStore, IconUtils, Menu, Modal, openModalLazy, PermissionsBits, PermissionStore, React, RestAPI, showToast, StickersStore, Tooltip, UserStore } from "@webpack/common";
import { Promisable } from "type-fest"; import { Promisable } from "type-fest";
const uploadEmoji = findByCodeLazy(".GUILD_EMOJIS(", "EMOJI_UPLOAD_START"); const uploadEmoji = findByCodeLazy(".GUILD_EMOJIS(", "EMOJI_UPLOAD_START");
@ -198,11 +198,7 @@ async function doClone(guildId: string, data: Sticker | Emoji) {
else else
await cloneEmoji(guildId, data); await cloneEmoji(guildId, data);
Toasts.show({ showToast(`Successfully cloned ${data.name} to ${GuildStore.getGuild(guildId)?.name ?? "your server"}!`, "success");
message: `Successfully cloned ${data.name} to ${GuildStore.getGuild(guildId)?.name ?? "your server"}!`,
type: Toasts.Type.SUCCESS,
id: Toasts.genId()
});
} catch (e: any) { } catch (e: any) {
let message = "Something went wrong (check console!)"; let message = "Something went wrong (check console!)";
try { try {
@ -210,11 +206,7 @@ async function doClone(guildId: string, data: Sticker | Emoji) {
} catch { } } catch { }
new Logger("ExpressionCloner").error("Failed to clone", data.name, "to", guildId, e); new Logger("ExpressionCloner").error("Failed to clone", data.name, "to", guildId, e);
Toasts.show({ showToast("Failed to clone: " + message, "failure");
message: "Failed to clone: " + message,
type: Toasts.Type.FAILURE,
id: Toasts.genId()
});
} }
} }

View file

@ -75,6 +75,7 @@ const fakeNitroEmojiRegex = /\/emojis\/(\d+?)\.(png|webp|gif)/;
const fakeNitroStickerRegex = /\/stickers\/(\d+?)\./; const fakeNitroStickerRegex = /\/stickers\/(\d+?)\./;
const fakeNitroGifStickerRegex = /\/attachments\/\d+?\/\d+?\/(\d+?)\.gif/; const fakeNitroGifStickerRegex = /\/attachments\/\d+?\/\d+?\/(\d+?)\.gif/;
const hyperLinkRegex = /\[.+?\]\((https?:\/\/.+?)\)/; const hyperLinkRegex = /\[.+?\]\((https?:\/\/.+?)\)/;
const hyperLinkOnlyRegex = new RegExp(`^${hyperLinkRegex.source}$`);
const mediaSizes = [16, 32, 48, 56, 64, 96, 128, 160, 256, 512, 1024]; const mediaSizes = [16, 32, 48, 56, 64, 96, 128, 160, 256, 512, 1024];
const DEFAULT_EMOJI_SIZE = 48; const DEFAULT_EMOJI_SIZE = 48;
@ -578,6 +579,7 @@ export default definePlugin({
const contentItems = message.content.split(/\s/); const contentItems = message.content.split(/\s/);
if (settings.store.transformCompoundSentence) itemsToMaybePush.push(...contentItems); if (settings.store.transformCompoundSentence) itemsToMaybePush.push(...contentItems);
else if (contentItems.length === 1) itemsToMaybePush.push(contentItems[0]); else if (contentItems.length === 1) itemsToMaybePush.push(contentItems[0]);
else if (hyperLinkOnlyRegex.test(message.content)) itemsToMaybePush.push(message.content);
itemsToMaybePush.push(...message.attachments.filter(attachment => attachment.content_type === "image/gif").map(attachment => attachment.url)); itemsToMaybePush.push(...message.attachments.filter(attachment => attachment.content_type === "image/gif").map(attachment => attachment.url));
@ -622,7 +624,7 @@ export default definePlugin({
shouldIgnoreEmbed(embed: Message["embeds"][number], message: Message) { shouldIgnoreEmbed(embed: Message["embeds"][number], message: Message) {
try { try {
const contentItems = message.content.split(/\s/); const contentItems = message.content.split(/\s/);
if (contentItems.length > 1 && !settings.store.transformCompoundSentence) return false; if (contentItems.length > 1 && !settings.store.transformCompoundSentence && !hyperLinkOnlyRegex.test(message.content)) return false;
switch (embed.type) { switch (embed.type) {
case "image": { case "image": {
@ -631,6 +633,7 @@ export default definePlugin({
if ( if (
!settings.store.transformCompoundSentence !settings.store.transformCompoundSentence
&& !contentItems.some(item => item === url || item.match(hyperLinkRegex)?.[1] === url) && !contentItems.some(item => item === url || item.match(hyperLinkRegex)?.[1] === url)
&& !hyperLinkOnlyRegex.test(message.content)
) return false; ) return false;
if (settings.store.transformEmojis) { if (settings.store.transformEmojis) {

View file

@ -16,7 +16,7 @@ import CustomRpcPlugin from "@plugins/customRPC";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { FluxDispatcher, Menu, RunningGameStore, showToast, TextArea, Toasts, Tooltip, useEffect, useState } from "@webpack/common"; import { FluxDispatcher, Menu, RunningGameStore, showToast, TextArea, Tooltip, useEffect, useState } from "@webpack/common";
const enum ActivitiesTypes { const enum ActivitiesTypes {
Game, Game,
@ -91,12 +91,12 @@ function ImportCustomRPCComponent() {
onClick={() => { onClick={() => {
const id = CustomRpcPlugin.settings.store.appID; const id = CustomRpcPlugin.settings.store.appID;
if (!id) { if (!id) {
return showToast("CustomRPC application ID is not set.", Toasts.Type.FAILURE); return showToast("CustomRPC application ID is not set.", "failure");
} }
const isAlreadyAdded = idsListPushID?.(id); const isAlreadyAdded = idsListPushID?.(id);
if (isAlreadyAdded) { if (isAlreadyAdded) {
showToast("CustomRPC application ID is already added.", Toasts.Type.FAILURE); showToast("CustomRPC application ID is already added.", "failure");
} }
}} }}
> >
@ -296,8 +296,8 @@ export default definePlugin({
find: '"LocalActivityStore"', find: '"LocalActivityStore"',
replacement: [ replacement: [
{ {
match: /\.LISTENING.+?(?=!?\i\(\)\(\i,\i\))(?<=(\i)\.push.+?)/, match: /\.LISTENING.+?([,:])(?=!?\i\(\)\(\i,\i\))(?<=(\i)\.push.+?)/,
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);` replace: (m, commaOrSemiColon, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored)${commaOrSemiColon}`
} }
] ]
}, },

View file

@ -163,7 +163,7 @@ export default definePlugin({
FluxDispatcher.dispatch({ FluxDispatcher.dispatch({
type: "GUILD_MEMBERS_REQUEST", type: "GUILD_MEMBERS_REQUEST",
guildIds: allGuildIds, guildIds: allGuildIds,
userIds: toRequest.slice(i, i + 100), userIds: toRequest.slice(i, i + 100).map(e => e.otherUserId),
presences: true, presences: true,
nonce: sentNonce, nonce: sentNonce,
}); });

View file

@ -19,7 +19,7 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType, PluginNative, SettingsDefinition } from "@utils/types"; import definePlugin, { OptionType, PluginNative, SettingsDefinition } from "@utils/types";
import { showToast, Toasts } from "@webpack/common"; import { showToast } from "@webpack/common";
import type { MouseEvent } from "react"; import type { MouseEvent } from "react";
interface URLReplacementRule { interface URLReplacementRule {
@ -145,7 +145,7 @@ export default definePlugin({
} }
if (rule.match.test(url)) { if (rule.match.test(url)) {
showToast("Opened link in native app", Toasts.Type.SUCCESS); showToast("Opened link in native app", "success");
const newUrl = url.replace(rule.match, rule.replace); const newUrl = url.replace(rule.match, rule.replace);
VencordNative.native.openExternal(newUrl); VencordNative.native.openExternal(newUrl);

View file

@ -10,7 +10,7 @@ import { categoryLen, createCategory, getCategory } from "@plugins/pinDms/data";
import { classNameFactory } from "@utils/css"; import { classNameFactory } from "@utils/css";
import { RenderModalProps } from "@vencord/discord-types"; import { RenderModalProps } from "@vencord/discord-types";
import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack"; import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack";
import { ColorPicker, Modal, openModalLazy, TextInput, Toasts, useMemo, useState } from "@webpack/common"; import { ColorPicker, Modal, openModalLazy, TextInput, useMemo, useState } from "@webpack/common";
interface ColorPickerWithSwatchesProps { interface ColorPickerWithSwatchesProps {
className?: string; className?: string;
@ -41,7 +41,7 @@ function useCategory(categoryId: string | null, initalChannelId: string | null)
return getCategory(categoryId); return getCategory(categoryId);
} else if (initalChannelId) { } else if (initalChannelId) {
return { return {
id: Toasts.genId(), id: crypto.randomUUID(),
name: `Pin Category ${categoryLen() + 1}`, name: `Pin Category ${categoryLen() + 1}`,
color: DEFAULT_COLOR, color: DEFAULT_COLOR,
collapsed: false, collapsed: false,

View file

@ -6,7 +6,7 @@
import * as DataStore from "@api/DataStore"; import * as DataStore from "@api/DataStore";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { OAuth2AuthorizeModal, openModal, showToast, Toasts, UserStore } from "@webpack/common"; import { OAuth2AuthorizeModal, openModal, showToast, UserStore } from "@webpack/common";
import { ReviewDBAuth } from "./entities"; import { ReviewDBAuth } from "./entities";
@ -60,13 +60,13 @@ export function authorize(callback?: () => void) {
if (!res.ok) { if (!res.ok) {
const { message } = await res.json(); const { message } = await res.json();
showToast(message ?? "An error occured while authorizing", Toasts.Type.FAILURE); showToast(message ?? "An error occured while authorizing", "failure");
return; return;
} }
const { token } = await res.json(); const { token } = await res.json();
updateAuth({ token }); updateAuth({ token });
showToast("Successfully logged in!", Toasts.Type.SUCCESS); showToast("Successfully logged in!", "success");
callback?.(); callback?.();
} catch (e) { } catch (e) {
new Logger("ReviewDB").error("Failed to authorize", e); new Logger("ReviewDB").error("Failed to authorize", e);

View file

@ -4,8 +4,6 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { Toasts } from "@webpack/common";
import { Auth, authorize, getToken, updateAuth } from "./auth"; import { Auth, authorize, getToken, updateAuth } from "./auth";
import { Review, ReviewDBCurrentUser, ReviewDBUser, ReviewType } from "./entities"; import { Review, ReviewDBCurrentUser, ReviewDBUser, ReviewType } from "./entities";
import { settings } from "./settings"; import { settings } from "./settings";
@ -51,7 +49,7 @@ async function rdbRequest<T = unknown>(path: string, options: RequestInit = {}):
...options, ...options,
headers, headers,
}).catch(err => { }).catch(err => {
showToast("Network error: Failed to connect to ReviewDB.", Toasts.Type.FAILURE); showToast("Network error: Failed to connect to ReviewDB.", "failure");
return null; return null;
}); });
@ -61,7 +59,7 @@ async function rdbRequest<T = unknown>(path: string, options: RequestInit = {}):
if (!res.ok) { if (!res.ok) {
const message = data?.message ?? `ReviewDB: Request failed with status ${res.status}`; const message = data?.message ?? `ReviewDB: Request failed with status ${res.status}`;
showToast(message, Toasts.Type.FAILURE); showToast(message, "failure");
return null; return null;
} }
@ -92,7 +90,7 @@ export async function getReviews(id: string, { limit, offset = 0, fetchVotes = f
}; };
if (!req.ok) { if (!req.ok) {
showToast(res.message, Toasts.Type.FAILURE); showToast(res.message, "failure");
return { return {
...res, ...res,
reviews: [ reviews: [
@ -140,7 +138,7 @@ export async function getReviewVotes(id: string): Promise<ReviewVote[]> {
return res?.votes ?? []; return res?.votes ?? [];
} }
export async function addReview(review): Promise<UserReviewsData | null> { export async function addReview(review: { userid: string; comment: string; }): Promise<UserReviewsData | null> {
const token = await getToken(); const token = await getToken();
if (!token) { if (!token) {
@ -224,7 +222,7 @@ async function patchBlock(action: "block" | "unblock", userId: string) {
if (!data) return; if (!data) return;
showToast(`Successfully ${action}ed user`, Toasts.Type.SUCCESS); showToast(`Successfully ${action}ed user`, "success");
if (Auth?.user?.blockedUsers) { if (Auth?.user?.blockedUsers) {
const newBlockedUsers = action === "block" const newBlockedUsers = action === "block"

View file

@ -17,7 +17,9 @@
*/ */
import { classNameFactory } from "@utils/css"; import { classNameFactory } from "@utils/css";
import { Toasts, UserStore } from "@webpack/common"; import { ToastType } from "@vencord/discord-types";
import { ToastPosition } from "@vencord/discord-types/enums";
import { showToast as originalShowToast, UserStore } from "@webpack/common";
import { Auth } from "./auth"; import { Auth } from "./auth";
import { Review, UserType } from "./entities"; import { Review, UserType } from "./entities";
@ -42,13 +44,8 @@ export function canReportReview(review: Review) {
return review.sender.discordID !== UserStore.getCurrentUser().id; return review.sender.discordID !== UserStore.getCurrentUser().id;
} }
export function showToast(message: string, type = Toasts.Type.MESSAGE) { export function showToast(message: string, type: ToastType = "message") {
Toasts.show({ originalShowToast(message, type, {
id: Toasts.genId(), position: ToastPosition.BOTTOM
message,
type,
options: {
position: Toasts.Position.BOTTOM, // NOBODY LIKES TOASTS AT THE TOP
},
}); });
} }

View file

@ -17,10 +17,8 @@
*/ */
import { isPluginEnabled } from "@api/PluginManager"; import { isPluginEnabled } from "@api/PluginManager";
import { definePluginSettings, Settings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import customUserColors, { getCustomColorString } from "@equicordplugins/customUserColors";
import { getCustomColorString } from "@equicordplugins/customUserColors";
import BetterRoleContext from "@plugins/betterRoleContext";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { makeRange, OptionType } from "@utils/types"; import definePlugin, { makeRange, OptionType } from "@utils/types";
@ -110,8 +108,8 @@ export default definePlugin({
find: 'tutorialId:"whos-online', find: 'tutorialId:"whos-online',
replacement: [ replacement: [
{ {
match: /(#{intl::CHANNEL_MEMBERS_A11Y_LABEL}.{0,200}?}\):null,).{0,200}?(?:—|\\u2014) ",\i\]\}\)\]/, match: /(?<=#{intl::CHANNEL_MEMBERS_A11Y_LABEL}.{0,400}?)children:(?=.{0,20}?(?:—|\\u2014) ",\i\])/,
replace: "$1$self.RoleGroupColor(arguments[0])]" replace: "style:{color:$self.getRoleColor(arguments[0])},$&"
}, },
], ],
predicate: () => settings.store.memberList predicate: () => settings.store.memberList
@ -121,8 +119,8 @@ export default definePlugin({
find: "?null:new Intl.NumberFormat", find: "?null:new Intl.NumberFormat",
replacement: [ replacement: [
{ {
match: /\(0,\i\.jsx\)\("span",\{[^}]+\}\),null==\i\?null:\(0,\i\.jsxs\)\("span",\{children:\["\\xa0\\u2014 ",\i\]\}\)\]/, match: /(?<=#{intl::CHANNEL_MEMBERS_A11Y_LABEL},\{title:\i,count:\i\}\)\}\),\(0,\i\.jsxs\)\("div",\{)/,
replace: "$self.RoleGroupColor(arguments[0])]" replace: "style:{color:$self.getRoleColor(arguments[0])},"
}, },
], ],
predicate: () => settings.store.memberList predicate: () => settings.store.memberList
@ -169,7 +167,7 @@ export default definePlugin({
getColorString(userId: string, channelOrGuildId: string) { getColorString(userId: string, channelOrGuildId: string) {
try { try {
if (Settings.plugins.CustomUserColors.enabled) { if (isPluginEnabled(customUserColors.name)) {
const customColor = getCustomColorString(userId, true); const customColor = getCustomColorString(userId, true);
if (customColor) return customColor; if (customColor) return customColor;
} }
@ -222,22 +220,9 @@ export default definePlugin({
return null; return null;
}, },
RoleGroupColor: ErrorBoundary.wrap(({ id, count, title, guildId, label }: { id: string; count: number; title: string; guildId: string; label: string; }) => { getRoleColor(props: any) {
const role = GuildRoleStore.getRole(guildId, id); try {
// we overwrite the context menu event added by BetterRoleContext in our patch return GuildRoleStore.getRole(props?.guildId, props?.id)?.colorString;
const wantsRoleContext = isPluginEnabled(BetterRoleContext.name); } catch (e) { }
}
return (
<span
style={{
color: role?.colorString,
fontWeight: "unset",
letterSpacing: ".05em"
}}
onContextMenu={wantsRoleContext ? e => BetterRoleContext.openRoleContextMenu(e, { guildId, id }) : undefined}
>
{title ?? label} &mdash; {count}
</span>
);
}, { noop: true })
}); });

View file

@ -17,7 +17,7 @@ export default definePlugin({
{ {
// This function is a util func from another module // This function is a util func from another module
// but it only has once use here so it's inlined // but it only has once use here so it's inlined
find: ".CREATE_FORUM_POST||function(", find: '.STICKER_PICKER:return""',
replacement: { replacement: {
match: /(?<=\){)if\(\i\.\i\.getUploadCount/, match: /(?<=\){)if\(\i\.\i\.getUploadCount/,
replace: "return true;$&", replace: "return true;$&",

View file

@ -19,7 +19,7 @@
import { classNameFactory } from "@utils/css"; import { classNameFactory } from "@utils/css";
import { onlyOnce } from "@utils/onlyOnce"; import { onlyOnce } from "@utils/onlyOnce";
import { PluginNative } from "@utils/types"; import { PluginNative } from "@utils/types";
import { showToast, Toasts } from "@webpack/common"; import { showToast } from "@webpack/common";
import { DeeplLanguages, deeplLanguageToGoogleLanguage, GoogleLanguages, KagiLanguages } from "./languages"; import { DeeplLanguages, deeplLanguageToGoogleLanguage, GoogleLanguages, KagiLanguages } from "./languages";
import { resetLanguageDefaults, settings } from "./settings"; import { resetLanguageDefaults, settings } from "./settings";
@ -89,7 +89,7 @@ export async function translate(kind: "received" | "sent", text: string): Promis
? e ? e
: "Something went wrong. If this issue persists, please check the console or ask for help in the support server."; : "Something went wrong. If this issue persists, please check the console or ask for help in the support server.";
showToast(userMessage, Toasts.Type.FAILURE); showToast(userMessage, "failure");
throw e instanceof Error throw e instanceof Error
? e ? e
@ -131,12 +131,12 @@ function fallbackToGoogle(text: string, sourceLang: string, targetLang: string):
} }
const showDeeplApiQuotaToast = onlyOnce( const showDeeplApiQuotaToast = onlyOnce(
() => showToast("Deepl API quota exceeded. Falling back to Google Translate", Toasts.Type.FAILURE) () => showToast("Deepl API quota exceeded. Falling back to Google Translate", "failure")
); );
async function deeplTranslate(text: string, sourceLang: string, targetLang: string): Promise<TranslationValue> { async function deeplTranslate(text: string, sourceLang: string, targetLang: string): Promise<TranslationValue> {
if (!settings.store.deeplApiKey) { if (!settings.store.deeplApiKey) {
showToast("DeepL API key is not set. Resetting to Google", Toasts.Type.FAILURE); showToast("DeepL API key is not set. Resetting to Google", "failure");
settings.store.service = "google"; settings.store.service = "google";
resetLanguageDefaults(); resetLanguageDefaults();

View file

@ -12,7 +12,7 @@ import { classNameFactory } from "@utils/css";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { Channel } from "@vencord/discord-types"; import { Channel } from "@vencord/discord-types";
import { findByPropsLazy, findCssClassesLazy } from "@webpack"; import { findByPropsLazy, findCssClassesLazy } from "@webpack";
import { ChannelRouter, ChannelStore, Parser, PermissionsBits, PermissionStore, React, showToast, Toasts, Tooltip, useMemo, UserStore, UserSummaryItem, useStateFromStores, VoiceStateStore } from "@webpack/common"; import { ChannelRouter, ChannelStore, Parser, PermissionsBits, PermissionStore, React, showToast, Tooltip, useMemo, UserStore, UserSummaryItem, useStateFromStores, VoiceStateStore } from "@webpack/common";
import { PropsWithChildren } from "react"; import { PropsWithChildren } from "react";
const cl = classNameFactory("vc-uvs-"); const cl = classNameFactory("vc-uvs-");
@ -155,7 +155,7 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isProfile, is
if (e.detail > 1) { if (e.detail > 1) {
if (!isDM && !PermissionStore.can(PermissionsBits.CONNECT, channel)) { if (!isDM && !PermissionStore.can(PermissionsBits.CONNECT, channel)) {
showToast("You cannot join the user's Voice Channel", Toasts.Type.FAILURE); showToast("You cannot join the user's Voice Channel", "failure");
return; return;
} }

View file

@ -17,7 +17,7 @@
*/ */
import { PluginNative } from "@utils/types"; import { PluginNative } from "@utils/types";
import { Button, MediaEngineStore, showToast, Toasts, useState } from "@webpack/common"; import { Button, MediaEngineStore, showToast, useState } from "@webpack/common";
import { settings, type VoiceRecorder } from ".."; import { settings, type VoiceRecorder } from "..";
@ -46,7 +46,7 @@ export const VoiceRecorderDesktop: VoiceRecorder = ({ setAudioBlob, onRecordingC
if (success) if (success)
changeRecording(true); changeRecording(true);
else else
showToast("Failed to start recording", Toasts.Type.FAILURE); showToast("Failed to start recording", "failure");
} }
); );
} else { } else {
@ -56,7 +56,7 @@ export const VoiceRecorderDesktop: VoiceRecorder = ({ setAudioBlob, onRecordingC
if (buf) if (buf)
setAudioBlob(new Blob([new Uint8Array(buf)], { type: "audio/ogg; codecs=opus" })); setAudioBlob(new Blob([new Uint8Array(buf)], { type: "audio/ogg; codecs=opus" }));
else else
showToast("Failed to finish recording", Toasts.Type.FAILURE); showToast("Failed to finish recording", "failure");
} }
changeRecording(false); changeRecording(false);
}); });

View file

@ -33,7 +33,7 @@ import definePlugin, { OptionType } from "@utils/types";
import { chooseFile } from "@utils/web"; import { chooseFile } from "@utils/web";
import { RenderModalProps } from "@vencord/discord-types"; import { RenderModalProps } from "@vencord/discord-types";
import { CloudUploadPlatform } from "@vencord/discord-types/enums"; import { CloudUploadPlatform } from "@vencord/discord-types/enums";
import { Button, CloudUploader, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, Modal, openModal, PendingReplyStore, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common"; import { Button, CloudUploader, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, Modal, openModal, PendingReplyStore, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, useEffect, useState } from "@webpack/common";
import { ComponentType } from "react"; import { ComponentType } from "react";
import { VoiceRecorderDesktop } from "./components/DesktopRecorder"; import { VoiceRecorderDesktop } from "./components/DesktopRecorder";
@ -190,7 +190,7 @@ function sendAudio(blob: Blob, meta: AudioMetadata) {
} }
}); });
}); });
upload.on("error", () => showToast("Failed to upload voice message", Toasts.Type.FAILURE)); upload.on("error", () => showToast("Failed to upload voice message", "failure"));
upload.upload(); upload.upload();
} }
@ -244,7 +244,7 @@ function VoiceMessageModal({ modalProps }: { modalProps: RenderModalProps; }) {
onClick: () => { onClick: () => {
sendAudio(blob!, meta ?? EMPTY_META); sendAudio(blob!, meta ?? EMPTY_META);
modalProps.onClose(); modalProps.onClose();
showToast("Now sending voice message... Please be patient", Toasts.Type.MESSAGE); showToast("Now sending voice message... Please be patient", "clock");
}, },
disabled: !blob disabled: !blob
}]} }]}

View file

@ -698,6 +698,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
Kaede: { Kaede: {
name: "Kaede", name: "Kaede",
id: 1492642701320126504n id: 1492642701320126504n
},
c0nnorgg: {
name: "c0nnorgg",
id: 1088882977985003620n,
} }
} satisfies Record<string, Dev>); } satisfies Record<string, Dev>);

View file

@ -18,7 +18,7 @@
import type { MessageObject } from "@api/MessageEvents"; import type { MessageObject } from "@api/MessageEvents";
import type { BasicGuild, Channel, CloudUpload, Guild, GuildFeatures, GuildProfile, MediaModalItem, MediaModalProps, Message, User } from "@vencord/discord-types"; import type { BasicGuild, Channel, CloudUpload, Guild, GuildFeatures, GuildProfile, MediaModalItem, MediaModalProps, Message, User } from "@vencord/discord-types";
import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, InviteActions, MessageActions, openMediaModal, RestAPI, SelectedChannelStore, SelectedGuildStore, Toasts, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, InviteActions, MessageActions, openMediaModal, openUserProfileModal, RestAPI, SelectedChannelStore, SelectedGuildStore, showToast, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common";
import { Except } from "type-fest"; import { Except } from "type-fest";
import { copyToClipboard } from "./clipboard"; import { copyToClipboard } from "./clipboard";
@ -116,11 +116,7 @@ export function insertTextIntoChatInputBox(text: string) {
export async function copyWithToast(text: string, toastMessage = "Copied to clipboard!") { export async function copyWithToast(text: string, toastMessage = "Copied to clipboard!") {
await copyToClipboard(text); await copyToClipboard(text);
Toasts.show({ showToast(toastMessage, "success");
message: toastMessage,
id: Toasts.genId(),
type: Toasts.Type.SUCCESS
});
} }
export interface MessageOptions { export interface MessageOptions {
@ -182,10 +178,14 @@ export async function openUserProfile(id: string) {
if (!user) throw new Error("No such user: " + id); if (!user) throw new Error("No such user: " + id);
const guildId = SelectedGuildStore.getGuildId(); const guildId = SelectedGuildStore.getGuildId();
UserProfileActions.openUserProfileModal({ openUserProfileModal({
userId: id, userId: id,
guildId, guildId,
channelId: SelectedChannelStore.getChannelId(), channelId: SelectedChannelStore.getChannelId(),
sourceAnalyticsLocations: [
"username",
"user profile popout",
]
}); });
} }

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import type * as t from "@vencord/discord-types"; import * as t from "@vencord/discord-types";
import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "@webpack"; import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "@webpack";
import type * as TSPattern from "ts-pattern"; import type * as TSPattern from "ts-pattern";
@ -70,23 +70,6 @@ waitFor("parseTopic", m => Parser = m);
export let Alerts: t.Alerts; export let Alerts: t.Alerts;
waitFor(["show", "close"], m => Alerts = m); waitFor(["show", "close"], m => Alerts = m);
const ToastType = {
MESSAGE: "message",
SUCCESS: "success",
FAILURE: "failure",
CUSTOM: "custom",
CLIP: "clip",
LINK: "link",
FORWARD: "forward",
BOOKMARK: "bookmark",
CLOCK: "clock"
};
const ToastPosition = {
TOP: 0,
BOTTOM: 1
};
export interface ToastData { export interface ToastData {
message: string, message: string,
id: string, id: string,
@ -106,40 +89,18 @@ export interface ToastOptions {
duration?: number; duration?: number;
} }
interface ToastsExports { export const Toasts: t.Toasts = mapMangledModuleLazy(".currentToastMap.has(", {
showToast: (data: ToastData) => void; show: filters.byCode(".currentToastMap.has("),
popToast(): void; pop: filters.byCode(".delete(")
}
const ToastsExports = mapMangledModuleLazy(".currentToastMap.has(", {
showToast: filters.byCode(".currentToastMap.has("),
popToast: filters.byCode(".delete(")
}); });
export function createToast(message: string, type: string, options?: ToastOptions): ToastData { export const createToast: t.createToast = findByCodeLazy('variant:"default",icon:', ".duration");
return {
message,
id: Toasts.genId(),
type,
options
};
}
export const Toasts = {
Type: ToastType,
Position: ToastPosition,
genId: () => (Math.random() || Math.random()).toString(36).slice(2),
show: ToastsExports.showToast,
pop: ToastsExports.popToast,
create: createToast,
};
/** /**
* Show a simple toast. If you need more options, use Toasts.show manually * Show a simple toast. If you need more options, use Toasts.show manually
*/ */
export function showToast(message: string, type = ToastType.MESSAGE, options?: ToastOptions) { export function showToast(message: string, type: t.ToastType = "message", options?: ToastOptions) {
Toasts.show(Toasts.create(message, type, options)); Toasts.show(createToast({ message, type, options }));
} }
export const UserUtils = { export const UserUtils = {
@ -185,9 +146,7 @@ export const { zustandPersist } = mapMangledModuleLazy(".onRehydrateStorage)?",
export const MessageActions = findByPropsLazy("editMessage", "sendMessage"); export const MessageActions = findByPropsLazy("editMessage", "sendMessage");
export const MessageCache = findByPropsLazy("clearCache", "_channelMessages"); export const MessageCache = findByPropsLazy("clearCache", "_channelMessages");
export const UserProfileActions: t.UserProfileModalActionCreators = mapMangledModuleLazy(".log(`Failed to fetch profile for $", { export const openUserProfileModal: t.OpenUserProfileModal = findByCodeLazy('type:"USER_PROFILE_MODAL_OPEN"');
openUserProfileModal: filters.byCode('type:"USER_PROFILE_MODAL_OPEN"')
});
export const InviteActions = findByPropsLazy("resolveInvite"); export const InviteActions = findByPropsLazy("resolveInvite");
export const ChannelActionCreators = findByPropsLazy("openPrivateChannel"); export const ChannelActionCreators = findByPropsLazy("openPrivateChannel");