Equicord/src/equicordplugins/equicordHelper/index.tsx
2026-03-13 09:51:32 -04:00

325 lines
12 KiB
TypeScript

/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { ApplicationCommandInputType, sendBotMessage } from "@api/Commands";
import { HeaderBarButton } from "@api/HeaderBar";
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { isPluginEnabled } from "@api/PluginManager";
import { definePluginSettings, migratePluginToSettings } from "@api/Settings";
import customRPC from "@plugins/customRPC";
import { Devs, EquicordDevs, GUILD_ID, SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_IDS, VC_SUPPORT_CHANNEL_IDS } from "@utils/constants";
import { isAnyPluginDev } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import { StandingState } from "@vencord/discord-types/enums";
import { findByCodeLazy, findExportedComponentLazy, findStoreLazy } from "@webpack";
import { Alerts, ApplicationCommandIndexStore, NavigationRouter, React, SettingsRouter, UserStore, useStateFromStores } from "@webpack/common";
import { ComponentType } from "react";
import { PluginButtons } from "./pluginButtons";
import { PluginCards } from "./pluginCards";
migratePluginToSettings(true, "EquicordHelper", "NoBulletPoints", "noBulletPoints");
migratePluginToSettings(true, "EquicordHelper", "NoModalAnimation", "noModalAnimation");
let clicked = false;
const SafetyHubStore = findStoreLazy("SafetyHubStore");
const fetchSafetyHub: () => Promise<void> = findByCodeLazy("SAFETY_HUB_FETCH_START");
const WarningIcon = findExportedComponentLazy("WarningIcon");
const ShieldIcon = findExportedComponentLazy("ShieldIcon");
const StandingConfig: Record<number, { label: string; hoverColor: string; Icon: ComponentType<any>; }> = {
[StandingState.ALL_GOOD]: { label: "All good!", hoverColor: "var(--status-positive)", Icon: ShieldIcon },
[StandingState.LIMITED]: { label: "Limited", hoverColor: "var(--status-warning)", Icon: WarningIcon },
[StandingState.VERY_LIMITED]: { label: "Very limited", hoverColor: "var(--orange-345)", Icon: WarningIcon },
[StandingState.AT_RISK]: { label: "At risk", hoverColor: "var(--status-danger)", Icon: WarningIcon },
[StandingState.SUSPENDED]: { label: "Suspended", hoverColor: "var(--interactive-muted)", Icon: WarningIcon },
};
function StandingButton() {
const standing = useStateFromStores([SafetyHubStore], () => SafetyHubStore.getAccountStanding());
const isInitialized = useStateFromStores([SafetyHubStore], () => SafetyHubStore.isInitialized());
const [hovered, setHovered] = React.useState(false);
React.useEffect(() => {
if (!isInitialized) fetchSafetyHub().catch(() => { });
}, [isInitialized]);
const config = StandingConfig[standing?.state] ?? StandingConfig[StandingState.ALL_GOOD];
return (
<div style={{ display: "contents" }} onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)}>
<HeaderBarButton
tooltip={config.label}
position="bottom"
icon={props => <config.Icon {...props} color={hovered ? config.hoverColor : "currentColor"} />}
onClick={() => SettingsRouter.openUserSettings("my_account_panel")}
/>
</div>
);
}
const listener = async (channelId, msg) => {
if (!settings.store.noBulletPoints) return;
msg.content = textProcessing(msg.content);
};
const settings = definePluginSettings({
noMirroredCamera: {
type: OptionType.BOOLEAN,
description: "Prevents the camera from being mirrored on your screen",
restartNeeded: true,
default: false,
},
removeActivitySection: {
type: OptionType.BOOLEAN,
description: "Removes the activity section above member list",
restartNeeded: true,
default: false,
},
showYourOwnActivityButtons: {
type: OptionType.BOOLEAN,
description: "Discord hides your own activity buttons for some reason",
restartNeeded: true,
default: false,
},
refreshSlashCommands: {
type: OptionType.BOOLEAN,
description: "Refreshes Slash Commands to show newly added commands without restarting your client.",
default: false,
},
forceRoleIcon: {
type: OptionType.BOOLEAN,
description: "Forces role icons to display next to messages in compact mode",
restartNeeded: true,
default: false
},
accountStandingButton: {
type: OptionType.BOOLEAN,
description: "Show an account standing button in the header bar",
restartNeeded: true,
default: false,
},
restoreFileDownloadButton: {
type: OptionType.BOOLEAN,
description: "Adds back the Download button at the top right corner of files",
restartNeeded: true,
default: false
},
noBulletPoints: {
type: OptionType.BOOLEAN,
description: "Stops you from typing markdown bullet points (stinky)",
restartNeeded: true,
default: false
},
noModalAnimation: {
type: OptionType.BOOLEAN,
description: "Remove the 300ms long animation when opening or closing modals",
restartNeeded: true,
default: false
},
});
export default definePlugin({
name: "EquicordHelper",
description: "Used to provide support, fix discord caused crashes, and other misc features.",
authors: [
Devs.thororen,
EquicordDevs.nyx,
EquicordDevs.Naibuu,
EquicordDevs.keircn,
EquicordDevs.SerStars,
EquicordDevs.mart,
EquicordDevs.omaw,
Devs.Samwich,
Devs.AutumnVN
],
required: true,
settings,
headerBarButton: {
icon: ShieldIcon,
render: () => (settings.store.accountStandingButton ? <StandingButton /> : null),
},
patches: [
// Fixes Unknown Resolution/FPS Crashing
{
find: "Unknown resolution:",
replacement: [
{
match: /throw Error\(`Unknown resolution: \$\{(\i)\}`\)/,
replace: "return $1;"
},
{
match: /throw Error\(`Unknown frame rate: \$\{(\i)\}`\)/,
replace: "return $1;"
}
]
},
// When focused on voice channel or group chat voice call
{
find: ".STATUS_WARNING_BACKGROUND})})",
predicate: () => settings.store.noMirroredCamera,
replacement: {
match: /mirror:\i/,
replace: "mirror:!1"
},
},
// Popout camera when not focused on voice channel
{
find: "this.handleReady})",
all: true,
predicate: () => settings.store.noMirroredCamera,
replacement: {
match: /(\[\i\.\i\]:)\i/,
replace: "$1!1"
},
},
// Overriding css on Preview Camera/Change Video Background popup
{
find: ".PREVIEW_CAMERA_MODAL,",
replacement: {
match: /className:\i.\i,(?=children:\()/,
replace: "$&style:{transform: \"scalex(1)\"},"
},
predicate: () => settings.store.noMirroredCamera
},
// Remove Activity Section above Member List
{
find: ".MEMBERLIST_CONTENT_FEED_TOGGLED,",
predicate: () => settings.store.removeActivitySection,
replacement: {
match: /null==\i\|\|/,
replace: "true||$&"
},
},
// Show your own activity buttons because discord removes them for who knows why
{
find: ".USER_PROFILE_ACTIVITY_BUTTONS),",
predicate: () => settings.store.showYourOwnActivityButtons && !isPluginEnabled(customRPC.name),
replacement: {
match: /.getId\(\)===\i.id/,
replace: "$& && false"
}
},
// Force Role Icon
{
find: "#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}",
predicate: () => settings.store.forceRoleIcon,
replacement: {
match: /(?<=\}\):null\].{0,150}\?2:)0(?=\})/,
replace: "1"
}
},
// Restore File Download Button
{
find: '"VISUAL_PLACEHOLDER":',
predicate: () => settings.store.restoreFileDownloadButton,
replacement: {
match: /(\.downloadUrl,showDownload:)\i/,
replace: "$1!0"
}
},
// Removes Modal Animation
{
find: "DURATION_IN:",
predicate: () => settings.store.noModalAnimation,
replacement: {
match: /300,/,
replace: "0,",
}
},
// Removes Modal Animation
{
find: 'backdropFilter:"blur(0px)"',
predicate: () => settings.store.noModalAnimation,
replacement: {
match: /\?0:200/,
replace: "?0:0",
}
},
// Removes Modal Animation
{
find: '="ABOVE"',
predicate: () => settings.store.noModalAnimation,
replacement: {
match: /\?\?300/,
replace: "??0",
}
},
// Removes Modal Animation
{
find: "renderLurkerModeUpsellPopout,position:",
predicate: () => settings.store.noModalAnimation,
replacement: {
match: /200:300/g,
replace: "0:0",
},
}
],
renderMessageAccessory(props) {
return (
<>
<PluginButtons message={props.message} />
<PluginCards message={props.message} />
</>
);
},
flux: {
async CHANNEL_SELECT({ channelId }) {
const isSupportChannel = SUPPORT_CHANNEL_IDS.includes(channelId);
if (!isSupportChannel) return;
const selfId = UserStore.getCurrentUser()?.id;
if (!selfId || isAnyPluginDev(selfId)) return;
if (VC_SUPPORT_CHANNEL_IDS.includes(channelId) && !clicked) {
return Alerts.show({
title: "Vencord Support Channel Warning",
body: "Before asking for help. Check updates and if this issue is actually caused by Equicord!",
confirmText: "Equicord Support",
onConfirm() {
NavigationRouter.transitionTo(`/channels/${GUILD_ID}/${SUPPORT_CHANNEL_ID}`);
},
cancelText: "Okay continue",
onCancel() {
clicked = true;
},
});
}
},
},
commands: [
{
name: "refresh-commands",
description: "Refresh Slash Commands",
inputType: ApplicationCommandInputType.BUILT_IN,
predicate: () => settings.store.refreshSlashCommands,
execute: async (opts, ctx) => {
try {
ApplicationCommandIndexStore.indices = {};
sendBotMessage(ctx.channel.id, { content: "Slash Commands refreshed successfully." });
}
catch (e) {
console.error("[refreshSlashCommands] Failed to refresh commands:", e);
sendBotMessage(ctx.channel.id, { content: "Failed to refresh commands. Check console for details." });
}
}
}
],
start() {
if (settings.store.noBulletPoints) {
addMessagePreSendListener(listener);
}
},
stop() {
if (settings.store.noBulletPoints) {
removeMessagePreSendListener(listener);
}
}
});
function textProcessing(text: string): string {
return text.replace(/(^|\n)(\s*)([*+-])\s+/g, "$1$2\\$3 ");
}