Merge branch 'dev'

This commit is contained in:
thororen1234 2025-09-26 16:27:25 -04:00
commit 532ca3cd81
No known key found for this signature in database
30 changed files with 527 additions and 347 deletions

View file

@ -6,15 +6,17 @@ import type { FluxEvents } from "./fluxEvents";
export { FluxEvents };
type FluxEventsAutoComplete = LiteralUnion<FluxEvents, string>;
export interface FluxDispatcher {
_actionHandlers: any;
_interceptors: any;
_subscriptions: any;
addInterceptor(interceptor: any): void;
dispatch(event: { [key: string]: unknown; type: FluxEvents; }): Promise<void>;
dispatch(event: { [key: string]: unknown; type: FluxEventsAutoComplete; }): Promise<void>;
isDispatching(): boolean;
subscribe(event: FluxEvents, callback: (data: any) => void): void;
unsubscribe(event: FluxEvents, callback: (data: any) => void): void;
subscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void;
unsubscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void;
wait(callback: () => void): void;
}

View file

@ -128,6 +128,23 @@ export function InfoIcon(props: IconProps) {
);
}
export function WarningIcon({ height = 32, width = 32, className }: IconProps) {
return (
<Icon
height={height}
width={width}
className={classes(className, "vc-warning-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
fillRule="evenodd"
d="M10 3.1a2.37 2.37 0 0 1 4 0l8.71 14.75c.84 1.41-.26 3.15-2 3.15H3.29c-1.74 0-2.84-1.74-2-3.15L9.99 3.1Zm3.25 14.65a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0ZM13.06 14l.37-5.94a1 1 0 0 0-1-1.06h-.87a1 1 0 0 0-1 1.06l.38 5.94a1.06 1.06 0 0 0 2.12 0Z"
/>
</Icon>
);
}
export function OwnerCrownIcon(props: IconProps) {
return (
<Icon

View file

@ -8,6 +8,7 @@ import "./styles.css";
export { default as ThemesTab } from "../../ThemeSettings/ThemesTab";
export * from "./BaseTab";
export { default as ChangelogTab } from "./changelog";
export { default as PatchHelperTab } from "./patchHelper";
export { default as PluginsTab } from "./plugins";
export { openContributorModal } from "./plugins/ContributorModal";
@ -16,4 +17,3 @@ export { default as BackupAndRestoreTab } from "./sync/BackupAndRestoreTab";
export { default as CloudTab } from "./sync/CloudTab";
export { openUpdaterModal, default as UpdaterTab } from "./updater";
export { default as VencordTab } from "./vencord";
export { default as ChangelogTab } from "./changelog";

View file

@ -28,18 +28,20 @@ const { startDependenciesRecursive, startPlugin, stopPlugin, isPluginEnabled } =
export const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
interface PluginCardProps {
plugin: Plugin;
disabled: boolean;
disabled?: boolean;
onRestartNeeded(name: string, key: string): void;
isNew?: boolean;
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
}
export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
const settings = Settings.plugins[plugin.name];
const pluginMeta = PluginMeta[plugin.name];
const isEquicordPlugin = pluginMeta?.folderName?.startsWith("src/equicordplugins/") ?? false;
const isUserplugin = pluginMeta?.userPlugin ?? false;
const isEquicordPlugin = pluginMeta.folderName.startsWith("src/equicordplugins/") ?? false;
const isUserplugin = pluginMeta.userPlugin ?? false;
const isEnabled = () => isPluginEnabled(plugin.name);

View file

@ -39,7 +39,7 @@ import { PluginMeta } from "~plugins";
import { OptionComponentMap } from "./components";
import { openContributorModal } from "./ContributorModal";
import { GithubButton } from "./LinkIconButton";
import { GithubButton, WebsiteButton } from "./LinkIconButton";
const cl = classNameFactory("vc-plugin-modal-");
@ -146,6 +146,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
}
const pluginMeta = PluginMeta[plugin.name];
const isEquicordPlugin = pluginMeta.folderName.startsWith("src/equicordplugins/") ?? false;
return (
<ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM}>
@ -160,6 +161,10 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
<Forms.FormText className={cl("description")}>{plugin.description}</Forms.FormText>
{!pluginMeta.userPlugin && (
<div className="vc-settings-modal-links">
<WebsiteButton
text="View more info"
href={isEquicordPlugin ? `https://equicord.org/plugins/${plugin.name}` : `https://vencord.dev/plugins/${plugin.name}`}
/>
<GithubButton
text="View source code"
href={`https://github.com/${gitRemote}/tree/main/${pluginMeta.folderName}`}

View file

@ -104,19 +104,19 @@ const enum SearchStatus {
NEW,
}
export const ExcludedReasons: Record<"web" | "discordDesktop" | "vesktop" | "equibop" | "desktop" | "dev", string> = {
desktop: "Discord Desktop app or Vesktop",
discordDesktop: "Discord Desktop app",
vesktop: "Vesktop & Equibop apps",
equibop: "Vesktop & Equibop apps",
web: "Vesktop & Equibop apps as well as the Web version of Discord",
dev: "Developer version of Equicord"
};
function ExcludedPluginsList({ search }: { search: string; }) {
const matchingExcludedPlugins = Object.entries(ExcludedPlugins)
.filter(([name]) => name.toLowerCase().includes(search));
const ExcludedReasons: Record<"web" | "discordDesktop" | "vesktop" | "equibop" | "desktop" | "dev", string> = {
desktop: "Discord Desktop app or Vesktop",
discordDesktop: "Discord Desktop app",
vesktop: "Vesktop & Equibop apps",
equibop: "Vesktop & Equibop apps",
web: "Vesktop & Equibop apps as well as the Web version of Discord",
dev: "Developer version of Equicord"
};
return (
<Text variant="text-md/normal" className={Margins.top16}>
{matchingExcludedPlugins.length
@ -160,19 +160,7 @@ export default function PluginSettings() {
}));
}, []);
const depMap = useMemo(() => {
const o = {} as Record<string, string[]>;
for (const plugin in Plugins) {
const deps = Plugins[plugin].dependencies;
if (deps) {
for (const dep of deps) {
o[dep] ??= [];
o[dep].push(plugin);
}
}
}
return o;
}, []);
const depMap = Vencord.Plugins.calculatePluginDependencyMap();
const sortedPlugins = useMemo(() => Object.values(Plugins)
.sort((a, b) => a.name.localeCompare(b.name)), []);
@ -191,8 +179,8 @@ export default function PluginSettings() {
const { status } = searchValue;
const enabled = Vencord.Plugins.isPluginEnabled(plugin.name);
const pluginMeta = PluginMeta[plugin.name];
const isEquicordPlugin = pluginMeta?.folderName?.startsWith("src/equicordplugins/") ?? false;
const isUserplugin = pluginMeta?.userPlugin ?? false;
const isEquicordPlugin = pluginMeta.folderName.startsWith("src/equicordplugins/") ?? false;
const isUserplugin = pluginMeta.userPlugin ?? false;
if (enabled && status === SearchStatus.DISABLED) return false;
if (!enabled && status === SearchStatus.ENABLED) return false;
@ -241,7 +229,7 @@ export default function PluginSettings() {
if (isRequired) {
const tooltipText = p.required || !depMap[p.name]
? "This plugin is required for Equicord to function."
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));
: <PluginDependencyList deps={depMap[p.name]?.filter(d => settings.plugins[d].enabled)} />;
requiredPlugins.push(
<Tooltip text={tooltipText} key={p.name}>
@ -419,7 +407,7 @@ export default function PluginSettings() {
);
}
function makeDependencyList(deps: string[]) {
export function PluginDependencyList({ deps }: { deps: string[]; }) {
return (
<React.Fragment>
<Forms.FormText>This plugin is required by:</Forms.FormText>

View file

@ -1,18 +1,16 @@
## vencord-animalese
animalese yapping in your discord client
## Animalese
> [!WARNING]
> don't go to Vencord support if you can't install this.
animalese yapping in your discord client
### demo
https://github.com/user-attachments/assets/3f1fa925-bbd2-451d-b63e-ea14dbe4be44
<https://cdn.nest.rip/uploads/8558dfb4-c386-4ffc-a3fb-63cbe180d3d2.mp4>
### settings
##### settings used in the video
![CleanShot 2024-12-26 at 22 11 49@2x](https://github.com/user-attachments/assets/35895fcc-d703-42cb-b0ef-bdef39ab15be)
![settings](https://cdn.nest.rip/uploads/32f09dd7-3cb2-4065-84ed-cbc5b193b2ec.png)
you can change the settings in the settings but otherwise, enjoy the yap

View file

@ -227,8 +227,7 @@ migratePluginSettings("ChatButtonsPlus", "Meow", "Woof");
export default definePlugin({
name: "ChatButtonsPlus",
description: "Add custom chat buttons with personalized + messages and SVG icons",
authors:
[EquicordDevs.creations],
authors: [EquicordDevs.creations],
settings,
renderChatBarButton: ({ isMainChat }) => {

View file

@ -1,3 +1,3 @@
# EquicordHelper
Fixes various issues for crashing and adds other items that might help with support
Fixes various issues for crashing and adds other items such as plugin cards and buttons for managing plugins quickly in support

View file

@ -8,13 +8,10 @@ import "@equicordplugins/_misc/styles.css";
import { definePluginSettings } from "@api/Settings";
import { Devs, EquicordDevs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import { isEquicordGuild, isEquicordSupport } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import { Button, Flex, showToast, Toasts } from "@webpack/common";
import { JSX } from "react";
import { toggleEnabled } from "./utils";
import { PluginButtons } from "./pluginButtons";
import { PluginCards } from "./pluginCards";
const settings = definePluginSettings({
disableCreateDMButton: {
@ -70,59 +67,11 @@ export default definePlugin({
},
],
renderMessageAccessory(props) {
return pluginToggleButtons(props);
}
});
function pluginToggleButtons(props) {
const buttons = [] as JSX.Element[];
const msg = props.message.content?.toLowerCase() ?? "";
const contentWords = (msg.match(/`\w+`/g) ?? []).map(e => e.slice(1, -1));
const matchedPlugins = Object.keys(Vencord.Plugins.plugins).filter(name => contentWords.includes(name.toLowerCase()));
const matchedPlugin = matchedPlugins.sort((a, b) => b.length - a.length)[0];
const pluginData = matchedPlugin ? Vencord.Plugins.plugins[matchedPlugin] : null;
const isEquicord = isEquicordGuild(props.channel.id) && isEquicordSupport(props.message.author.id);
const startsWithEnabled = msg.startsWith("enable");
const startsWithDisabled = msg.startsWith("disable");
const shouldAddPluginButtons = pluginData && isEquicord && (startsWithEnabled || startsWithDisabled);
if (shouldAddPluginButtons) {
if (pluginData.required || pluginData.name.endsWith("API")) return;
const isEnabled = Vencord.Plugins.isPluginEnabled(matchedPlugin);
let label = `${matchedPlugin} is already ${isEnabled ? "enabled" : "disabled"}`;
let disabled = true;
if ((startsWithDisabled && isEnabled) || (startsWithEnabled && !isEnabled)) {
label = `${isEnabled ? "Disable" : "Enable"} ${matchedPlugin}`;
disabled = false;
}
buttons.push(
<Button
key="vc-plugin-toggle"
color={disabled ? Button.Colors.PRIMARY : (isEnabled ? Button.Colors.RED : Button.Colors.GREEN)}
disabled={disabled}
size={Button.Sizes.SMALL}
onClick={async () => {
try {
const success = await toggleEnabled(matchedPlugin);
if (success) showToast(`${label}`, Toasts.Type.SUCCESS);
} catch (e) {
new Logger("EquicordHelper").error("Error while toggling:", e);
showToast(`Failed to ${label.toLowerCase()}`, Toasts.Type.FAILURE);
}
}}
>
{label}
</Button>
return (
<>
<PluginButtons message={props.message} />
<PluginCards message={props.message} />
</>
);
}
return buttons.length
? <Flex>{buttons}</Flex>
: null;
}
});

View file

@ -0,0 +1,71 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { Logger } from "@utils/Logger";
import { isEquicordGuild, isEquicordSupport } from "@utils/misc";
import { Message } from "@vencord/discord-types";
import { Button, showToast, Toasts } from "@webpack/common";
import { JSX } from "react";
import { toggleEnabled } from "./utils";
export const PluginButtons = ErrorBoundary.wrap(function PluginCards({ message }: { message: Message; }) {
const pluginButtons = [] as JSX.Element[];
const msg = message.content?.toLowerCase() ?? "";
const contentWords = (msg.match(/`\w+`/g) ?? []).map(e => e.slice(1, -1));
const matchedPlugins = Object.keys(Vencord.Plugins.plugins).filter(name => contentWords.includes(name.toLowerCase()));
const matchedPlugin = matchedPlugins.sort((a, b) => b.length - a.length)[0];
const pluginData = matchedPlugin ? Vencord.Plugins.plugins[matchedPlugin] : null;
const isEquicord = isEquicordGuild(message.channel_id) && isEquicordSupport(message.author.id);
const startsWithEnabled = msg.startsWith("enable");
const startsWithDisabled = msg.startsWith("disable");
const shouldAddPluginButtons = pluginData && isEquicord && (startsWithEnabled || startsWithDisabled);
if (shouldAddPluginButtons) {
if (pluginData.required || pluginData.name.endsWith("API")) return;
const isEnabled = Vencord.Plugins.isPluginEnabled(matchedPlugin);
let label = `${matchedPlugin} is already ${isEnabled ? "enabled" : "disabled"}`;
let disabled = true;
if ((startsWithDisabled && isEnabled) || (startsWithEnabled && !isEnabled)) {
label = `${isEnabled ? "Disable" : "Enable"} ${matchedPlugin}`;
disabled = false;
}
pluginButtons.push(
<Button
key="vc-plugin-toggle"
color={disabled ? Button.Colors.PRIMARY : (isEnabled ? Button.Colors.RED : Button.Colors.GREEN)}
disabled={disabled}
size={Button.Sizes.SMALL}
onClick={async () => {
try {
const success = await toggleEnabled(matchedPlugin);
if (success) showToast(`${label}`, Toasts.Type.SUCCESS);
} catch (e) {
new Logger("EquicordHelper").error("Error while toggling:", e);
showToast(`Failed to ${label.toLowerCase()}`, Toasts.Type.FAILURE);
}
}}
>
{label}
</Button>
);
}
if (pluginButtons.length === 0) return null;
return (
<div className="vc-plugins-action-buttons" style={{ marginTop: "0px" }}>
{pluginButtons}
</div>
);
}, { noop: true });

View file

@ -0,0 +1,155 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { useSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { WarningIcon } from "@components/Icons";
import { AddonCard } from "@components/settings";
import { ExcludedReasons, PluginDependencyList } from "@components/settings/tabs/plugins";
import { PluginCard } from "@components/settings/tabs/plugins/PluginCard";
import { EQUIBOT_USER_ID } from "@utils/constants";
import { isEquicordGuild, isEquicordSupport } from "@utils/misc";
import { Message } from "@vencord/discord-types";
import { showToast, Tooltip, TooltipContainer } from "@webpack/common";
import { JSX } from "react";
import plugins, { ExcludedPlugins } from "~plugins";
export function ChatPluginCard({ url, description }: { url: string, description: string; }) {
const pluginNameFromUrl = new URL(url).pathname.split("/")[2];
const actualPluginName = Object.keys(plugins).find(name =>
name.toLowerCase() === pluginNameFromUrl?.toLowerCase()
);
const pluginName = actualPluginName || pluginNameFromUrl;
useSettings([`plugins.${pluginName ?? ""}.enabled`]);
if (!pluginName) return null;
const p = plugins[pluginName];
const excludedPlugin = ExcludedPlugins[pluginName];
if (excludedPlugin || !p) {
const toolTipText = !p
? `${pluginName} is only available on the ${ExcludedReasons[ExcludedPlugins[pluginName]]}`
: "This plugin is not on this version of Equicord. Try updating!";
const card = (
<AddonCard
name={pluginName}
description={description || toolTipText}
enabled={false}
setEnabled={() => { }}
disabled={true}
infoButton={<WarningIcon />}
/>
);
return description
? <TooltipContainer text={toolTipText}>{card}</TooltipContainer>
: card;
}
const onRestartNeeded = () => showToast("A restart is required for the change to take effect!");
const required = Vencord.Plugins.isPluginRequired(pluginName);
const dependents = Vencord.Plugins.calculatePluginDependencyMap()[p.name]?.filter(d => Vencord.Plugins.isPluginEnabled(d));
if (required) {
const tooltipText = p.required || !dependents.length
? "This plugin is required for Equicord to function."
: <PluginDependencyList deps={dependents} />;
return (
<Tooltip text={tooltipText} key={p.name}>
{({ onMouseLeave, onMouseEnter }) =>
<PluginCard
key={p.name}
onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter}
onRestartNeeded={onRestartNeeded}
plugin={p}
disabled
/>
}
</Tooltip>
);
}
return (
<PluginCard
key={p.name}
onRestartNeeded={onRestartNeeded}
plugin={p}
/>
);
}
export const PluginCards = ErrorBoundary.wrap(function PluginCards({ message }: { message: Message; }) {
const seenPlugins = new Set<string>();
const pluginCards: JSX.Element[] = [];
// Process embeds
message.embeds?.forEach(embed => {
if (!embed.url?.startsWith("https://equicord.org/plugins/") && !embed.url?.startsWith("https://vencord.dev/plugins/")) return;
const isEquicord = isEquicordGuild(message.channel_id) && isEquicordSupport(message.author.id);
if (!isEquicord) return;
const pluginNameFromUrl = new URL(embed.url).pathname.split("/")[2];
const actualPluginName = Object.keys(plugins).find(name =>
name.toLowerCase() === pluginNameFromUrl?.toLowerCase()
);
const pluginName = actualPluginName || pluginNameFromUrl;
if (!pluginName || seenPlugins.has(pluginName)) return;
seenPlugins.add(pluginName);
pluginCards.push(
<ChatPluginCard
key={embed.url}
url={embed.url}
description={embed.rawDescription}
/>
);
});
// Process components
const components = (message.components?.[0] as any)?.components;
if (message.author.id === EQUIBOT_USER_ID && components?.length >= 4) {
const description = components[1]?.content;
const pluginUrl = components.find((c: any) => c?.components)?.components[0]?.url;
if (pluginUrl?.startsWith("https://equicord.org/plugins/") || pluginUrl?.startsWith("https://vencord.dev/plugins/")) {
const pluginNameFromUrl = new URL(pluginUrl).pathname.split("/")[2];
const actualPluginName = Object.keys(plugins).find(name =>
name.toLowerCase() === pluginNameFromUrl?.toLowerCase()
);
const pluginName = actualPluginName || pluginNameFromUrl;
if (pluginName && !seenPlugins.has(pluginName)) {
seenPlugins.add(pluginName);
pluginCards.push(
<ChatPluginCard
key={pluginUrl}
url={pluginUrl}
description={description}
/>
);
}
}
}
if (pluginCards.length === 0) return null;
return (
<div className="vc-plugins-management-cards vc-plugins-grid" style={{ marginTop: "0px" }}>
{pluginCards}
</div>
);
}, { noop: true });

View file

@ -1191,7 +1191,7 @@ export default definePlugin({
{
// The Quest Accepted button is disabled by default. If the user reloads the client, they need a way
// to resume the automatic completion, so patch in optionally enabling it if the feature is enabled.
match: /(START_QUEST_CTA.{0,400}?disabled:)(!0)/,
match: /(fullWidth:!0}\)}\):.{0,150}?disabled:)(!0.{0,150}?"horizontal")/,
replace: "$1$self.shouldDisableQuestAcceptedButton(arguments[0].quest)??$2"
},
{

View file

@ -1,10 +1,10 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { findGroupChildrenByChildId } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { getCurrentChannel } from "@utils/discord";
@ -20,44 +20,28 @@ enum ImageStyle {
inspirational
}
const messagePatch: NavContextMenuPatchCallback = (children, { message }) => {
recentmessage = message;
if (!message.content) return;
const buttonElement =
<Menu.MenuItem
id="vc-quote"
label="Quote"
icon={QuoteIcon}
action={async () => {
openModal(props => <QuoteModal {...props} />);
}}
/>;
const group = findGroupChildrenByChildId("copy-text", children);
if (!group) {
children.push(buttonElement);
return;
}
group.splice(
group.findIndex(c => c?.props?.id === "copy-text") + 1, 0, buttonElement
);
};
let recentmessage: Message;
let grayscale;
let setStyle: ImageStyle = ImageStyle.inspirational;
let customMessage: string = "";
enum userIDOptions {
displayName,
userName,
userId
}
interface QuoteImageOptions {
avatarUrl: string;
quoteOld: string;
grayScale: boolean;
imageStyle: ImageStyle;
author: {
username: string;
globalName?: string;
id: string;
};
userIdentifier: userIDOptions;
}
const settings = definePluginSettings({
userIdentifier:
{
userIdentifier: {
type: OptionType.SELECT,
description: "What the author's name should be displayed as",
options: [
@ -71,14 +55,28 @@ const settings = definePluginSettings({
export default definePlugin({
name: "Quoter",
description: "Adds the ability to create an inspirational quote image from a message",
authors: [Devs.Samwich],
authors: [Devs.Samwich, Devs.thororen],
settings,
contextMenus: {
"message": messagePatch
},
settings
"message": (children, { message }) => {
if (!message.content) return;
const buttonElement = (
<Menu.MenuItem
id="vc-quote"
label="Quote"
icon={QuoteIcon}
action={() => openModal(props => <QuoteModal message={message} {...props} />)}
/>
);
const group = findGroupChildrenByChildId("copy-text", children);
if (!group) children.push(buttonElement);
else group.splice(group.findIndex(c => c?.props?.id === "copy-text") + 1, 0, buttonElement);
}
}
});
function sizeUpgrade(url) {
function sizeUpgrade(url: string) {
const u = new URL(url);
u.searchParams.set("size", "512");
return u.toString();
@ -87,68 +85,49 @@ function sizeUpgrade(url) {
const preparingSentence: string[] = [];
const lines: string[] = [];
async function createQuoteImage(avatarUrl: string, quoteOld: string, grayScale: boolean): Promise<Blob> {
async function createQuoteImage(options: QuoteImageOptions): Promise<Blob> {
const { avatarUrl, quoteOld, grayScale, imageStyle, author, userIdentifier } = options;
const quote = FixUpQuote(quoteOld);
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
if (!ctx) throw new Error("Cant get 2d rendering context :(");
if (!ctx) {
throw new Error("Cant get 2d rendering context :(");
}
let name: string = "";
switch (settings.store.userIdentifier) {
let name = "";
switch (userIdentifier) {
case userIDOptions.displayName:
// @ts-ignore
const meow = recentmessage.author.globalName;
if (meow) {
name = meow;
}
else {
name = recentmessage.author.username;
}
name = author.globalName || author.username;
break;
case userIDOptions.userName:
name = recentmessage.author.username;
name = author.username;
break;
case userIDOptions.userId:
name = recentmessage.author.id;
name = author.id;
break;
default:
name = "MAN WTF HAPPENED";
break;
name = "Unknown";
}
switch (setStyle) {
switch (imageStyle) {
case ImageStyle.inspirational:
const cardWidth = 1200;
const cardHeight = 600;
canvas.width = cardWidth;
canvas.height = cardHeight;
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(0, 0, cardWidth, cardHeight);
const avatarBlob = await fetchImageAsBlob(avatarUrl);
const fadeBlob = await fetchImageAsBlob("https://raw.githubusercontent.com/Equicord/Equibored/refs/heads/main/icons/quoter/quoter.png");
const fadeBlob = await fetchImageAsBlob("https://raw.githubusercontent.com/Equicord/Equibored/main/icons/quoter/quoter.png");
const avatar = new Image();
const fade = new Image();
const avatarPromise = new Promise<void>(resolve => {
avatar.onload = () => resolve();
avatar.src = URL.createObjectURL(avatarBlob);
});
const fadePromise = new Promise<void>(resolve => {
fade.onload = () => resolve();
fade.src = URL.createObjectURL(fadeBlob);
});
await Promise.all([avatarPromise, fadePromise]);
await Promise.all([
new Promise<void>(resolve => { avatar.onload = () => resolve(); avatar.src = URL.createObjectURL(avatarBlob); }),
new Promise<void>(resolve => { fade.onload = () => resolve(); fade.src = URL.createObjectURL(fadeBlob); })
]);
ctx.drawImage(avatar, 0, 0, cardHeight, cardHeight);
@ -164,42 +143,69 @@ async function createQuoteImage(avatarUrl: string, quoteOld: string, grayScale:
ctx.fillStyle = "#fff";
ctx.font = "italic 20px Georgia";
const quoteWidth = cardWidth / 2 - 50;
const quoteX = ((cardWidth - cardHeight));
const quoteX = cardWidth - cardHeight;
const quoteY = cardHeight / 2 - 10;
wrapText(ctx, `"${quote}"`, quoteX, quoteY, quoteWidth, 20, preparingSentence, lines);
const wrappedTextHeight = lines.length * 25;
ctx.font = "bold 16px Georgia";
const authorNameX = (cardHeight * 1.5) - (ctx.measureText(`- ${name}`).width / 2) - 30;
const authorNameX = cardHeight * 1.5 - ctx.measureText(`- ${name}`).width / 2 - 30;
const authorNameY = quoteY + wrappedTextHeight + 30;
ctx.fillText(`- ${name}`, authorNameX, authorNameY);
preparingSentence.length = 0;
lines.length = 0;
return await canvasToBlob(canvas);
}
}
function registerStyleChange(style) {
setStyle = style;
GeneratePreview();
function generateFileNamePreview(message: string) {
const words = message.split(" ");
return words.length >= 6 ? words.slice(0, 6).join(" ") : words.join(" ");
}
function QuoteModal(props: ModalProps) {
function QuoteModal({ message, ...props }: ModalProps & { message: Message; }) {
const [gray, setGray] = useState(true);
useEffect(() => {
grayscale = gray;
GeneratePreview();
}, [gray]);
const [style, setStyle] = useState(ImageStyle.inspirational);
const [quoteImage, setQuoteImage] = useState<Blob | null>(null);
const { userIdentifier } = settings.store;
const safeContent = message.content ? message.content : "";
const safeContent = recentmessage && recentmessage.content ? recentmessage.content : "";
const generateImage = async () => {
const image = await createQuoteImage({
avatarUrl: sizeUpgrade(message.author.getAvatarURL()),
quoteOld: safeContent,
grayScale: gray,
imageStyle: style,
author: message.author,
userIdentifier
});
setQuoteImage(image);
document.getElementById("quoterPreview")?.setAttribute("src", URL.createObjectURL(image));
};
const [custom, setCustom] = useState(safeContent);
useEffect(() => {
customMessage = custom;
GeneratePreview();
}, [custom]);
useEffect(() => { generateImage(); }, [gray, style, safeContent]);
const Export = () => {
if (!quoteImage) return;
const link = document.createElement("a");
const preview = generateFileNamePreview(safeContent);
link.href = URL.createObjectURL(quoteImage);
link.download = `${preview} - ${message.author.username}.png`;
link.click();
link.remove();
};
const SendInChat = () => {
if (!quoteImage) return;
const preview = generateFileNamePreview(safeContent);
const file = new File([quoteImage], `${preview} - ${message.author.username}.png`, { type: "image/png" });
// @ts-expect-error typing issue
UploadHandler.promptToUpload([file], getCurrentChannel(), 0);
props.onClose?.();
};
return (
<ModalRoot {...props} size={ModalSize.MEDIUM}>
@ -210,59 +216,27 @@ function QuoteModal(props: ModalProps) {
<ModalCloseButton onClick={props.onClose} />
</ModalHeader>
<ModalContent scrollbarType="none">
<img alt="" src="" id={"quoterPreview"} style={{ borderRadius: "20px", width: "100%" }}></img>
<br></br><br></br>
<img alt="" src="" id="quoterPreview" style={{ borderRadius: "20px", width: "100%" }} />
<br /><br />
<br /><br />
<Switch value={gray} onChange={setGray}>Grayscale</Switch>
<Select look={1}
options={Object.keys(ImageStyle).filter(key => isNaN(parseInt(key, 10))).map(key => ({
label: key.charAt(0).toUpperCase() + key.slice(1),
value: ImageStyle[key as keyof typeof ImageStyle]
}))}
select={v => registerStyleChange(v)} isSelected={v => v === setStyle}
serialize={v => v}></Select>
<Select
look={1}
options={Object.keys(ImageStyle)
.filter(key => isNaN(parseInt(key, 10)))
.map(key => ({
label: key.charAt(0).toUpperCase() + key.slice(1),
value: ImageStyle[key as keyof typeof ImageStyle]
}))}
select={v => setStyle(v)}
isSelected={v => v === style}
serialize={v => v}
/>
<br />
<Button color={Button.Colors.BRAND_NEW} size={Button.Sizes.SMALL} onClick={() => Export()} style={{ display: "inline-block", marginRight: "5px" }}>Export</Button>
<Button color={Button.Colors.BRAND_NEW} size={Button.Sizes.SMALL} onClick={() => SendInChat(props.onClose)} style={{ display: "inline-block" }}>Send</Button>
<Button color={Button.Colors.BRAND_NEW} size={Button.Sizes.SMALL} onClick={async () => await Export()} style={{ display: "inline-block", marginRight: "5px" }}>Export</Button>
<Button color={Button.Colors.BRAND_NEW} size={Button.Sizes.SMALL} onClick={async () => await SendInChat()} style={{ display: "inline-block" }}>Send</Button>
</ModalContent>
<br></br>
</ModalRoot>
);
}
async function SendInChat(onClose) {
const image = await createQuoteImage(sizeUpgrade(recentmessage.author.getAvatarURL()), recentmessage.content, grayscale);
const preview = generateFileNamePreview(recentmessage.content);
const imageName = `${preview} - ${recentmessage.author.username}`;
const file = new File([image], `${imageName}.png`, { type: "image/png" });
// @ts-expect-error typing issue
UploadHandler.promptToUpload([file], getCurrentChannel(), 0);
onClose();
}
async function Export() {
const image = await createQuoteImage(sizeUpgrade(recentmessage.author.getAvatarURL()), recentmessage.content, grayscale);
const link = document.createElement("a");
link.href = URL.createObjectURL(image);
const preview = generateFileNamePreview(recentmessage.content);
const imageName = `${preview} - ${recentmessage.author.username}`;
link.download = `${imageName}.png`;
link.click();
link.remove();
}
async function GeneratePreview() {
const image = await createQuoteImage(sizeUpgrade(recentmessage.author.getAvatarURL()), recentmessage.content, grayscale);
document.getElementById("quoterPreview")?.setAttribute("src", URL.createObjectURL(image));
}
function generateFileNamePreview(message) {
const words = message.split(" ");
let preview;
if (words.length >= 6) {
preview = words.slice(0, 6).join(" ");
} else {
preview = words.join(" ");
}
return preview;
}

View file

@ -7,61 +7,39 @@
import { UserStore } from "@webpack/common";
export function canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob> {
return new Promise<Blob>(resolve => {
return new Promise<Blob>((resolve, reject) => {
canvas.toBlob(blob => {
if (blob) {
resolve(blob);
} else {
throw new Error("Failed to create Blob");
}
blob ? resolve(blob) : reject(new Error("Failed to create Blob"));
}, "image/png");
});
}
export function wrapText(context: CanvasRenderingContext2D, text: string, x: number, y: number, maxWidth: number, lineHeight: number, preparingSentence: string[], lines: string[]) {
const words = text.split(" ");
for (let i = 0; i < words.length; i++) {
const workSentence = preparingSentence.join(" ") + " " + words[i];
if (context.measureText(workSentence).width > maxWidth) {
export function wrapText(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, maxWidth: number, lineHeight: number, preparingSentence: string[], lines: string[]) {
text.split(" ").forEach(word => {
const workSentence = preparingSentence.join(" ") + " " + word;
if (ctx.measureText(workSentence).width > maxWidth) {
lines.push(preparingSentence.join(" "));
preparingSentence = [words[i]];
preparingSentence = [word];
} else {
preparingSentence.push(words[i]);
preparingSentence.push(word);
}
}
});
lines.push(preparingSentence.join(" "));
lines.forEach(element => {
const lineWidth = context.measureText(element).width;
const xOffset = (maxWidth - lineWidth) / 2;
lines.forEach(line => {
const xOffset = (maxWidth - ctx.measureText(line).width) / 2;
y += lineHeight;
context.fillText(element, x + xOffset, y);
ctx.fillText(line, x + xOffset, y);
});
}
export async function fetchImageAsBlob(url: string): Promise<Blob> {
if (!url) throw new Error("Invalid URL: URL is empty or undefined");
try {
if (!url) throw new Error("Invalid URL: URL is empty or undefined");
const response = await fetch(url, {
mode: "cors",
cache: "no-cache",
credentials: "same-origin",
headers: {
"Accept": "image/*"
}
});
if (!response.ok) {
throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
}
const blob = await response.blob();
return blob;
const response = await fetch(`https://corsproxy.io/?url=${encodeURIComponent(url)}`);
if (!response.ok) throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
return await response.blob();
} catch (error) {
console.error("Error fetching image:", error);
return new Blob([
@ -76,31 +54,18 @@ export async function fetchImageAsBlob(url: string): Promise<Blob> {
}
}
export function FixUpQuote(quote) {
const emojiRegex = /<a?:(\w+):(\d+)>/g;
quote = quote.replace(emojiRegex, "");
const mentionRegex = /<@(.*)>/;
let result = quote;
export function FixUpQuote(quote: string) {
try {
const matches = mentionRegex.exec(quote);
if (matches) {
matches.forEach(match => {
quote = quote.replace(/<a?:(\w+):(\d+)>/g, "");
const mentionMatch = /<@(.*?)>/.exec(quote);
if (mentionMatch) {
mentionMatch.forEach(match => {
try {
const userId = match.replace("<@", "").replace(">", "");
const user = UserStore.getUser(userId);
if (user && user.username) {
result = result.replace(match, `@${user.username}`);
}
} catch (err) {
console.error("Error processing mention:", err);
}
const user = UserStore.getUser(match.replace(/[<@>]/g, ""));
if (user?.username) quote = quote.replace(match, `@${user.username}`);
} catch { }
});
}
} catch (err) {
console.error("Error in FixUpQuote:", err);
}
return result;
} catch { }
return quote;
}

View file

@ -11,7 +11,7 @@ import definePlugin, { OptionType } from "@utils/types";
import { PresenceStore, UserStore } from "@webpack/common";
let savedStatus = "";
const statusSettings = getUserSettingLazy("status", "status");
const StatusSettings = getUserSettingLazy("status", "status");
const settings = definePluginSettings({
statusToSet: {
type: OptionType.SELECT,
@ -44,17 +44,18 @@ export default definePlugin({
authors: [Devs.thororen],
settings,
flux: {
RUNNING_GAMES_CHANGE(event) {
const status = PresenceStore.getStatus(UserStore.getCurrentUser().id);
if (event.games.length > 0) {
RUNNING_GAMES_CHANGE({ games }) {
const userId = UserStore.getCurrentUser().id;
const status = PresenceStore.getStatus(userId);
if (games.length > 0) {
if (status !== settings.store.statusToSet) {
savedStatus = status;
statusSettings?.updateSetting(settings.store.statusToSet);
StatusSettings?.updateSetting(settings.store.statusToSet);
}
} else {
if (savedStatus !== "" && savedStatus !== settings.store.statusToSet)
statusSettings?.updateSetting(savedStatus);
} else if (savedStatus && savedStatus !== settings.store.statusToSet) {
StatusSettings?.updateSetting(savedStatus);
}
},
}
}
});

View file

@ -71,6 +71,15 @@ export default definePlugin({
}
],
// The TRACK event takes an optional `resolve` property that is called when the tracking event was submitted to the server.
// A few spots in Discord await this callback before continuing (most notably the Voice Debug Logging toggle).
// Since we NOOP the AnalyticsActionHandlers module, there is no handler for the TRACK event, so we have to handle it ourselves
flux: {
TRACK(event) {
event?.resolve?.();
}
},
startAt: StartAt.Init,
start() {
// Sentry is initialized in its own WebpackInstance.

View file

@ -77,11 +77,13 @@ export default definePlugin({
},
]
},
// Fix the settings cog context menu to work properly
{
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
replacement: {
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/,
replace: "$2.open($1);return;",
// Skip the check discord performs to make sure the section is valid
match: /(?<=function\((\i),(\i),\i\)\{)(?=let \i=Object.values\(\i\.\i\).*?(\(0,\i\.openUserSettings\))\()/,
replace: (_, target, section, func) => `${func}(${target},{section:${section}});return;`
},
},
],

View file

@ -17,6 +17,7 @@
*/
import { popNotice, showNotice } from "@api/Notices";
import { migratePluginSettings, Settings } from "@api/Settings";
import { Link } from "@components/Link";
import { Devs } from "@utils/constants";
import definePlugin, { ReporterTestable } from "@utils/types";
@ -37,16 +38,18 @@ async function lookupApp(applicationId: string): Promise<string> {
}
let hideSetting = false;
let ws: WebSocket;
if (IS_VESKTOP || IS_EQUIBOP || "legcord" in window) {
hideSetting = true;
Settings.plugins.WebRichPresence.enabled = false;
} else if ("goofcord" in window) {
hideSetting = false;
}
let ws: WebSocket;
migratePluginSettings("WebRichPresence", "WebRichPresence (arRPC)");
export default definePlugin({
name: "WebRichPresence (arRPC)",
name: "WebRichPresence",
description: "Client plugin for arRPC to enable RPC on Discord Web (experimental)",
authors: [Devs.Ducko],
reporterTestable: ReporterTestable.None,

View file

@ -21,24 +21,39 @@ import { findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Animations, useStateFromStores } from "@webpack/common";
import type { CSSProperties } from "react";
import { ExpandedGuildFolderStore, settings } from ".";
import { ExpandedGuildFolderStore, settings, SortedGuildStore } from ".";
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
const GuildsBar = findComponentByCodeLazy('("guildsnav")');
function getExpandedFolderIds() {
const expandedFolders = ExpandedGuildFolderStore.getExpandedFolders();
const folders = SortedGuildStore.getGuildFolders();
const expandedFolderIds = new Set<string>();
for (const folder of folders) {
if (expandedFolders.has(folder.folderId) && folder.guildIds?.length) {
expandedFolderIds.add(folder.folderId);
}
}
return expandedFolderIds;
}
export default ErrorBoundary.wrap(guildsBarProps => {
const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders());
const expandedFolderIds = useStateFromStores([ExpandedGuildFolderStore, SortedGuildStore], () => getExpandedFolderIds());
const isFullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext());
const Sidebar = (
<GuildsBar
{...guildsBarProps}
isBetterFolders={true}
betterFoldersExpandedIds={expandedFolders}
betterFoldersExpandedIds={expandedFolderIds}
/>
);
const visible = !!expandedFolders.size;
const visible = !!expandedFolderIds.size;
const guilds = document.querySelector(guildsBarProps.className.split(" ").map(c => `.${c}`).join(""));
// We need to display none if we are in fullscreen. Yes this seems horrible doing with css, but it's literally how Discord does it.

View file

@ -34,7 +34,7 @@ enum FolderIconDisplay {
}
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
const SortedGuildStore = findStoreLazy("SortedGuildStore");
export const SortedGuildStore = findStoreLazy("SortedGuildStore");
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
let lastGuildId = null as string | null;

View file

@ -60,7 +60,7 @@ function makeIcon(showCurrentGame?: boolean) {
};
}
function GameActivityToggleButton() {
function GameActivityToggleButton(props: { nameplate?: any; }) {
const showCurrentGame = ShowCurrentGame.useSetting();
return (
@ -71,6 +71,7 @@ function GameActivityToggleButton() {
role="switch"
aria-checked={!showCurrentGame}
redGlow={!showCurrentGame}
plated={props?.nameplate != null}
onClick={() => ShowCurrentGame.updateSetting(old => !old)}
/>
);
@ -98,7 +99,7 @@ export default definePlugin({
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
replacement: {
match: /className:\i\.buttons,.{0,50}children:\[/,
replace: "$&$self.GameActivityToggleButton(),"
replace: "$&$self.GameActivityToggleButton(arguments[0]),"
}
}
],

View file

@ -48,7 +48,7 @@ export default definePlugin({
},
// Sections header
{
find: "#{intl::FRIENDS_SECTION_ONLINE}",
find: "#{intl::FRIENDS_SECTION_ONLINE}),className",
replacement: {
match: /,{id:(\i\.\i)\.PENDING,show:.+?className:(\i\.item)/,
replace: (rest, relationShipTypes, className) => `,{id:${relationShipTypes}.IMPLICIT,show:true,className:${className},content:"Implicit"}${rest}`

View file

@ -28,6 +28,7 @@ import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/Messag
import { addNicknameIcon, removeNicknameIcon } from "@api/NicknameIcons";
import { Settings, SettingsStore } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { makeLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger";
import { canonicalizeFind, canonicalizeReplacement } from "@utils/patches";
import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types";
@ -52,10 +53,36 @@ const subscribedFluxEventsPlugins = new Set<string>();
const pluginsValues = Object.values(Plugins);
const settings = Settings.plugins;
/**
* Whether a plugin is required (or dependency of another enabled plugin)
*/
export function isPluginRequired(name: string) {
const p = Plugins[name];
if (!p) return false;
return p.required || p.isDependency;
}
/**
* A map of plugin names to the plugins that depend on them
*/
export const calculatePluginDependencyMap = makeLazy(() => {
const dependencies: Record<string, string[]> = {};
for (const plugin in Plugins) {
const deps = Plugins[plugin].dependencies;
if (deps) {
for (const dep of deps) {
dependencies[dep] ??= [];
dependencies[dep].push(plugin);
}
}
}
return dependencies;
});
export function isPluginEnabled(p: string) {
return (
Plugins[p]?.required ||
Plugins[p]?.isDependency ||
isPluginRequired(p) ||
settings[p]?.enabled
) ?? false;
}
@ -233,7 +260,7 @@ export function subscribePluginFluxEvents(p: Plugin, fluxDispatcher: typeof Flux
const wrappedHandler = p.flux[event] = function () {
if (p.name === "Encryptcord" && event === "MESSAGE_CREATE") return;
try {
const res = handler.apply(p, arguments as any);
const res = handler!.apply(p, arguments as any);
return res instanceof Promise
? res.catch(e => logger.error(`${p.name}: Error while handling ${event}\n`, e))
: res;
@ -253,7 +280,7 @@ export function unsubscribePluginFluxEvents(p: Plugin, fluxDispatcher: typeof Fl
logger.debug("Unsubscribing from flux events of plugin", p.name);
for (const [event, handler] of Object.entries(p.flux)) {
fluxDispatcher.unsubscribe(event as FluxEvents, handler);
fluxDispatcher.unsubscribe(event as FluxEvents, handler!);
}
}
}

View file

@ -14,7 +14,7 @@ import { updateMessage } from "@api/MessageUpdater";
import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs, VC_SUPPORT_CATEGORY_ID, VENBOT_USER_ID } from "@utils/constants";
import { Devs, EQUIBOT_USER_ID, SUPPORT_CHANNEL_ID, VC_SUPPORT_CATEGORY_ID, VENBOT_USER_ID } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { Logger } from "@utils/Logger";
import { classes } from "@utils/misc";
@ -409,7 +409,8 @@ export default definePlugin({
(isEdit ? !logEdits : !logDeletes) ||
ignoreGuilds.includes(ChannelStore.getChannel(message.channel_id)?.guild_id) ||
// Ignore Venbot in the support channels (love you venbot!!!)
(message.author?.id === VENBOT_USER_ID && ChannelStore.getChannel(message.channel_id)?.parent_id === VC_SUPPORT_CATEGORY_ID));
(message.author?.id === VENBOT_USER_ID && ChannelStore.getChannel(message.channel_id)?.parent_id === VC_SUPPORT_CATEGORY_ID) ||
(message.author?.id === EQUIBOT_USER_ID && ChannelStore.getChannel(message.channel_id)?.id === SUPPORT_CHANNEL_ID));
},
EditMarker({ message, className, children, ...props }: any) {

View file

@ -27,7 +27,7 @@ import { classes } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import { User } from "@vencord/discord-types";
import { filters, findStoreLazy, mapMangledModuleLazy } from "@webpack";
import { PresenceStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
import { AuthenticationStore, PresenceStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
export interface Session {
sessionId: string;
@ -113,7 +113,7 @@ const PlatformIcon = ({ platform, status, small }: PlatformIconProps) => {
};
function useEnsureOwnStatus(user: User) {
if (user.id !== UserStore.getCurrentUser()?.id) {
if (user.id !== AuthenticationStore.getId()) {
return;
}

View file

@ -186,10 +186,10 @@ const SilentTypingChatToggle: ChatBarButtonFactory = ({ channel, type }) => {
}
}}>
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style={{ scale: "1.2" }}>
<path fill="currentColor" mask="url(#silent-typing-msg-mask)" d="M18.333 15.556H1.667a1.667 1.667 0 0 1 -1.667 -1.667v-10a1.667 1.667 0 0 1 1.667 -1.667h16.667a1.667 1.667 0 0 1 1.667 1.667v10a1.667 1.667 0 0 1 -1.667 1.667M4.444 6.25V4.861a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333V8.194a0.417 0.417 0 0 0 -0.417 -0.417H4.306a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417H7.639a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m10 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h8.056a0.417 0.417 0 0 0 0.417 -0.417m3.333 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417" transform="translate(2, 3)" />
<path fill="currentColor" mask={`url(#silent-typing-msg-mask-${channel.id})`} d="M18.333 15.556H1.667a1.667 1.667 0 0 1 -1.667 -1.667v-10a1.667 1.667 0 0 1 1.667 -1.667h16.667a1.667 1.667 0 0 1 1.667 1.667v10a1.667 1.667 0 0 1 -1.667 1.667M4.444 6.25V4.861a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333V8.194a0.417 0.417 0 0 0 -0.417 -0.417H4.306a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417H7.639a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m10 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h8.056a0.417 0.417 0 0 0 0.417 -0.417m3.333 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417" transform="translate(2, 3)" />
{(enabledLocally) && (
<>
<mask id="silent-typing-msg-mask">
<mask id={`silent-typing-msg-mask-${channel.id}`}>
<path fill="#fff" d="M0 0h24v24H0Z"></path>
<path stroke="#000" strokeWidth="5.99068" d="M0 24 24 0" transform="translate(-2, -3)"></path>
</mask>

View file

@ -27,6 +27,7 @@ export const EQUICORD_TEAM = "1173520023239786538";
export const EQUICORD_HELPERS = "1326406112144265257";
export const EQUIBOP_CONTRIB_ROLE_ID = "1287079931645263968";
export const VENCORD_CONTRIB_ROLE_ID = "1173343399470964856";
export const EQUIBOT_USER_ID = "1243063117852835941";
// Vencord
export const VC_SUPPORT_CHANNEL_ID = "1026515880080842772";

View file

@ -27,6 +27,7 @@ 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";
// exists to export default definePlugin({...})
export default function definePlugin<P extends PluginDef>(p: P & Record<PropertyKey, any>) {
@ -151,9 +152,9 @@ export interface PluginDef {
/**
* Allows you to subscribe to Flux events
*/
flux?: {
[E in FluxEvents]?: (event: any) => void | Promise<void>;
};
flux?: Partial<{
[E in LiteralUnion<FluxEvents, string>]: (event: any) => void | Promise<void>;
}>;
/**
* Allows you to manipulate context menus
*/

View file

@ -87,13 +87,7 @@ export const ListScrollerNone = LazyComponent(() => createListScroller(listScrol
export const ListScrollerThin = LazyComponent(() => createListScroller(listScrollerClasses.thin, listScrollerClasses.fade, "", ResizeObserver));
export const ListScrollerAuto = LazyComponent(() => createListScroller(listScrollerClasses.auto, listScrollerClasses.fade, "", ResizeObserver));
const { FocusLock_ } = mapMangledModuleLazy('document.getElementById("app-mount"))', {
FocusLock_: filters.componentByCode(".containerRef")
}) as {
FocusLock_: t.FocusLock;
};
export const FocusLock = LazyComponent(() => FocusLock_);
export const FocusLock = waitForComponent<t.FocusLock>("FocusLock", filters.componentByCode("containerRef,{keyboardModeEnabled"));
export let useToken: t.useToken;
waitFor(m => {