Merge branch 'dev'
This commit is contained in:
commit
532ca3cd81
30 changed files with 527 additions and 347 deletions
8
packages/discord-types/src/utils.d.ts
vendored
8
packages/discord-types/src/utils.d.ts
vendored
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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}`}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||

|
||||
|
||||

|
||||
|
||||
you can change the settings in the settings but otherwise, enjoy the yap
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
71
src/equicordplugins/equicordHelper/pluginButtons.tsx
Normal file
71
src/equicordplugins/equicordHelper/pluginButtons.tsx
Normal 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 });
|
||||
155
src/equicordplugins/equicordHelper/pluginCards.tsx
Normal file
155
src/equicordplugins/equicordHelper/pluginCards.tsx
Normal 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 });
|
||||
|
|
@ -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"
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;`
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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]),"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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}`
|
||||
|
|
|
|||
|
|
@ -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!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue