feat(HeaderBarAPI): Add API to register header bar buttons (#530)

* feat(HeaderBarAPI): Add API to register header bar buttons

* bleh

* t

* t

* a

* t

---------

Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com>
This commit is contained in:
prism 2025-11-27 13:47:59 -08:00 • committed by GitHub
parent 4850a6401f
commit 0c0498e531
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 204 additions and 400 deletions

62
src/api/HeaderBar.tsx Normal file
View file

@ -0,0 +1,62 @@
/*
* 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 { useEffect, useState } from "@webpack/common";
import type { JSX } from "react";
export type HeaderBarButtonFactory = () => JSX.Element | null;
interface HeaderBarButton {
render: HeaderBarButtonFactory;
priority: number;
}
export const buttons = new Map<string, HeaderBarButton>();
const listeners = new Set<() => void>();
/**
* Add a button to the header bar.
* @param id Unique identifier for the button.
* @param render Function that renders the button component.
* @param priority Higher priority = more to the right. Default is 0.
*/
export function addHeaderBarButton(id: string, render: HeaderBarButtonFactory, priority = 0) {
buttons.set(id, { render, priority });
listeners.forEach(l => l());
}
/**
* Remove a button from the header bar.
* @param id The identifier of the button to remove.
*/
export function removeHeaderBarButton(id: string) {
buttons.delete(id);
listeners.forEach(l => l());
}
function HeaderBarButtons() {
const [, forceUpdate] = useState(0);
useEffect(() => {
const listener = () => forceUpdate(n => n + 1);
listeners.add(listener);
return () => { listeners.delete(listener); };
}, []);
const sorted = Array.from(buttons).sort(([, a], [, b]) => a.priority - b.priority);
return sorted.map(([id, { render: Button }]) => (
<ErrorBoundary noop key={id}>
<Button />
</ErrorBoundary>
));
}
export function _addButtons() {
return [<HeaderBarButtons key="vc-header-bar-buttons" />];
}

View file

@ -41,6 +41,7 @@ export { Plugins as plugins };
import { traceFunction } from "../debug/Tracer";
import { addAudioProcessor, removeAudioProcessor } from "./AudioPlayer";
import { addHeaderBarButton, removeHeaderBarButton } from "./HeaderBar";
const logger = new Logger("PluginManager", "#a6d189");
@ -190,7 +191,7 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton,
// Custom
renderNicknameIcon, audioProcessor
renderNicknameIcon, renderHeaderBarButton, onAudioProcessor
} = p;
if (p.start) {
@ -252,7 +253,8 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
// Custom
if (renderNicknameIcon) addNicknameIcon(name, renderNicknameIcon);
if (audioProcessor) addAudioProcessor(name, audioProcessor);
if (renderHeaderBarButton) addHeaderBarButton(name, renderHeaderBarButton);
if (onAudioProcessor) addAudioProcessor(name, onAudioProcessor);
return true;
}, p => `startPlugin ${p.name}`);
@ -263,7 +265,7 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton,
// Custom
renderNicknameIcon, audioProcessor
renderNicknameIcon, renderHeaderBarButton, onAudioProcessor
} = p;
if (p.stop) {
@ -319,7 +321,8 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
// Custom
if (renderNicknameIcon) removeNicknameIcon(name);
if (audioProcessor) removeAudioProcessor(name);
if (renderHeaderBarButton) removeHeaderBarButton(name);
if (onAudioProcessor) removeAudioProcessor(name);
return true;
}, p => `stopPlugin ${p.name}`);
@ -330,7 +333,9 @@ export const initPluginManager = onlyOnce(function init() {
const pluginKeysToBind: Array<keyof PluginDef & `${"on" | "render"}${string}`> = [
"onBeforeMessageEdit", "onBeforeMessageSend", "onMessageClick",
"renderChatBarButton", "renderMemberListDecorator", "renderMessageAccessory", "renderMessageDecoration", "renderMessagePopoverButton", "renderNicknameIcon"
"renderChatBarButton", "renderMemberListDecorator", "renderMessageAccessory", "renderMessageDecoration", "renderMessagePopoverButton",
// Custom
"renderNicknameIcon", "renderHeaderBarButton", "onAudioProcessor"
];
const neededApiPlugins = new Set<string>();
@ -369,7 +374,8 @@ export const initPluginManager = onlyOnce(function init() {
// Custom
if (p.renderNicknameIcon) neededApiPlugins.add("NicknameIconsAPI");
if (p.audioProcessor) neededApiPlugins.add("AudioPlayerAPI");
if (p.renderHeaderBarButton) neededApiPlugins.add("HeaderBarAPI");
if (p.onAudioProcessor) neededApiPlugins.add("AudioPlayerAPI");
for (const key of pluginKeysToBind) {
p[key] &&= p[key].bind(p) as any;

View file

@ -22,6 +22,7 @@ import * as $ChatButtons from "./ChatButtons";
import * as $Commands from "./Commands";
import * as $ContextMenu from "./ContextMenu";
import * as $DataStore from "./DataStore";
import * as $HeaderBar from "./HeaderBar";
import * as $MemberListDecorators from "./MemberListDecorators";
import * as $MessageAccessories from "./MessageAccessories";
import * as $MessageDecorations from "./MessageDecorations";
@ -129,6 +130,11 @@ export const ContextMenu = $ContextMenu;
*/
export const ChatButtons = $ChatButtons;
/**
* An API allowing you to add buttons to the header bar.
*/
export const HeaderBar = $HeaderBar;
/**
* An API allowing you to update and re-render messages.
*/

View file

@ -110,7 +110,6 @@ export default definePlugin({
name: "DecodeBase64",
description: "Decode base64 content of any message and copy the decoded content.",
authors: [EquicordDevs.ThePirateStoner],
dependencies: ["MessagePopoverAPI"],
settings,
messagePopoverButton: {
icon: DecodeIcon,

View file

@ -394,12 +394,9 @@ export default definePlugin({
name: "CustomSounds",
description: "Customize Discord's sounds.",
authors: [Devs.ScattrdBlade, Devs.TheKodeToad],
dependencies: ["AudioPlayerAPI"],
settings,
getCustomSoundURL,
startAt: StartAt.Init,
audioProcessor: getCustomSoundURL,
onAudioProcessor: getCustomSoundURL,
async start() {
console.log("[CustomSounds] Plugin starting...");

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { Paragraph } from "@components/Paragraph";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
@ -35,36 +34,27 @@ function warningEmbedNotice(trigger) {
});
});
}
const handleMessage = async (channelId, messageObj) => {
const channel = ChannelStore.getChannel(channelId);
if (channel.isDM()) return { cancel: false };
if (PermissionStore.can(PermissionsBits.ADMINISTRATOR, channel) || PermissionStore.can(PermissionsBits.MANAGE_GUILD, channel)) return { cancel: false };
const escapedStrings = filterList.map(escapeRegex);
const regexString = escapedStrings.join("|");
const regex = new RegExp(`(${regexString})`, "i");
const matches = regex.exec(messageObj.content);
console.log(matches);
if (matches) {
if (!await warningEmbedNotice(matches[0])) {
return { cancel: true };
}
}
return { cancel: false };
};
export default definePlugin({
name: "DontFilterMe",
description: "Warns you if your message contains a term in the automod preset list",
authors: [Devs.Samwich],
dependencies: ["MessageEventsAPI"],
start() {
addMessagePreSendListener(handleMessage);
},
stop() {
removeMessagePreSendListener(handleMessage);
async onBeforeMessageSend(channelId, messageObj) {
const channel = ChannelStore.getChannel(channelId);
if (channel.isDM()) return { cancel: false };
if (PermissionStore.can(PermissionsBits.ADMINISTRATOR, channel) || PermissionStore.can(PermissionsBits.MANAGE_GUILD, channel)) return { cancel: false };
const escapedStrings = filterList.map(escapeRegex);
const regexString = escapedStrings.join("|");
const regex = new RegExp(`(${regexString})`, "i");
const matches = regex.exec(messageObj.content);
console.log(matches);
if (matches) {
if (!await warningEmbedNotice(matches[0])) {
return { cancel: true };
}
}
return { cancel: false };
}
});

View file

@ -18,13 +18,12 @@
import "./styles.css";
import { addHeaderBarButton, removeHeaderBarButton } from "@api/HeaderBar";
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findComponentByCodeLazy } from "@webpack";
import { Popout, useRef, useState } from "@webpack/common";
import type { PropsWithChildren } from "react";
import { renderPopout } from "./menu";
@ -48,7 +47,7 @@ function Icon() {
);
}
function VencordPopoutButton({ buttonClass }: { buttonClass: string; }) {
function VencordPopoutButton() {
const buttonRef = useRef(null);
const [show, setShow] = useState(false);
@ -66,10 +65,10 @@ function VencordPopoutButton({ buttonClass }: { buttonClass: string; }) {
{(_, { isShown }) => (
<HeaderBarIcon
ref={buttonRef}
className={`vc-toolbox-btn ${buttonClass}`}
className="vc-toolbox-btn"
onClick={() => setShow(v => !v)}
tooltip={isShown ? null : "Equicord Toolbox"}
icon={() => <Icon />}
icon={Icon}
selected={isShown}
/>
)}
@ -82,27 +81,15 @@ export default definePlugin({
name: "EquicordToolbox",
description: "Adds a button next to the inbox button in the channel header that houses Equicord quick actions",
authors: [Devs.Ven, Devs.AutumnVN],
dependencies: ["HeaderBarAPI"],
settings,
patches: [
{
find: '?"BACK_FORWARD_NAVIGATION":',
replacement: {
match: /(?<=trailing:.{0,50})\i\.Fragment,\{(?=.+?className:(\i))/,
replace: "$self.TrailingWrapper,{className:$1,"
}
}
],
TrailingWrapper({ children, className }: PropsWithChildren<{ className: string; }>) {
return (
<>
{children}
<ErrorBoundary noop>
<VencordPopoutButton buttonClass={className} />
</ErrorBoundary>
</>
);
start() {
addHeaderBarButton("EquicordToolbox", VencordPopoutButton, 1000);
},
stop() {
removeHeaderBarButton("EquicordToolbox");
}
});

View file

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { removeMessagePopoverButton } from "@api/MessagePopover";
import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { Devs } from "@utils/constants";
@ -154,7 +153,6 @@ export default definePlugin({
enableStyle(styles);
},
stop() {
removeMessagePopoverButton("vc-findreply");
root && root.unmount();
element?.remove();
disableStyle(styles);

View file

@ -20,8 +20,6 @@ import "./style.css";
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { DataStore } from "@api/index";
import { removeMessagePopoverButton } from "@api/MessagePopover";
import ErrorBoundary from "@components/ErrorBoundary";
import { EquicordDevs } from "@utils/constants";
import { classes } from "@utils/misc";
import { openModal } from "@utils/modal";
@ -58,15 +56,13 @@ function ToolBarHeader() {
const iconClasses = findByProps("iconWrapper", "clickable");
return (
<ErrorBoundary noop={true}>
<HeaderBarIcon
tooltip="Holy Notes"
position="bottom"
className={classes("vc-note-button", iconClasses.iconWrapper, iconClasses.clickable)}
icon={e => Popover(e)}
onClick={() => openModal(props => <NoteModal {...props} />)}
/>
</ErrorBoundary>
<HeaderBarIcon
tooltip="Holy Notes"
position="bottom"
className={classes("vc-note-button", iconClasses.iconWrapper, iconClasses.clickable)}
icon={Popover}
onClick={() => openModal(props => <NoteModal {...props} />)}
/>
);
}
@ -75,17 +71,6 @@ export default definePlugin({
name: "HolyNotes",
description: "Holy Notes allows you to save messages",
authors: [EquicordDevs.Wolfie],
dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI"],
patches: [
{
find: ".controlButtonWrapper,",
replacement: {
match: /(function \i\(\i\){)(.{1,200}toolbar.{1,100}mobileToolbar)/,
replace: "$1$self.toolbarAction(arguments[0]);$2"
}
}
],
toolboxActions: {
async "Open Notes"() {
@ -97,21 +82,8 @@ export default definePlugin({
"message": messageContextMenuPatch
},
toolbarAction(e) {
if (Array.isArray(e.toolbar))
return e.toolbar.unshift(
<ErrorBoundary noop={true}>
<ToolBarHeader />
</ErrorBoundary>
);
renderHeaderBarButton: ToolBarHeader,
e.toolbar = [
<ErrorBoundary noop={true} key={"HolyNotes"}>
<ToolBarHeader />
</ErrorBoundary>,
e.toolbar,
];
},
messagePopoverButton: {
icon: NoteButtonPopover,
render(message) {
@ -129,8 +101,4 @@ export default definePlugin({
if (await DataStore.keys(HolyNoteStore).then(keys => !keys.includes("Main"))) return noteHandler.newNoteBook("Main");
if (!noteHandlerCache.has("Main")) await DataStoreToCache();
},
async stop() {
removeMessagePopoverButton("HolyNotes");
}
});

View file

@ -102,7 +102,6 @@ export default definePlugin({
name: "Husk",
description: "Adds Husk button (check settings to change used emoji)",
authors: [Devs.nin0dev],
dependencies: ["MessagePopoverAPI"],
settings,
messagePopoverButton: {
icon: Husk,

View file

@ -21,28 +21,7 @@ import { findComponentByCodeLazy } from "@webpack";
import { openLogModal } from "./LogsModal";
const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"');
export function OpenLogsIcon() {
return (
<svg
stroke="currentColor"
fill="none"
strokeWidth="0"
viewBox="0 0 15 15"
height={24}
width={24}
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
fill="currentColor"
d="M3 2.5C3 2.22386 3.22386 2 3.5 2H9.08579C9.21839 2 9.34557 2.05268 9.43934 2.14645L11.8536 4.56066C11.9473 4.65443 12 4.78161 12 4.91421V12.5C12 12.7761 11.7761 13 11.5 13H3.5C3.22386 13 3 12.7761 3 12.5V2.5ZM3.5 1C2.67157 1 2 1.67157 2 2.5V12.5C2 13.3284 2.67157 14 3.5 14H11.5C12.3284 14 13 13.3284 13 12.5V4.91421C13 4.51639 12.842 4.13486 12.5607 3.85355L10.1464 1.43934C9.86514 1.15804 9.48361 1 9.08579 1H3.5ZM4.5 4C4.22386 4 4 4.22386 4 4.5C4 4.77614 4.22386 5 4.5 5H7.5C7.77614 5 8 4.77614 8 4.5C8 4.22386 7.77614 4 7.5 4H4.5ZM4.5 7C4.22386 7 4 7.22386 4 7.5C4 7.77614 4.22386 8 4.5 8H10.5C10.7761 8 11 7.77614 11 7.5C11 7.22386 10.7761 7 10.5 7H4.5ZM4.5 10C4.22386 10 4 10.2239 4 10.5C4 10.7761 4.22386 11 4.5 11H10.5C10.7761 11 11 10.7761 11 10.5C11 10.2239 10.7761 10 10.5 10H4.5Z"
>
</path>
</svg>
);
}
const Icon = findComponentByCodeLazy("0-1.27-.97l-2.5.7a3");
export function OpenLogsButton() {
return (
@ -50,7 +29,7 @@ export function OpenLogsButton() {
className="vc-log-toolbox-btn"
onClick={() => openLogModal()}
tooltip={"Open Logs"}
icon={OpenLogsIcon}
icon={Icon}
/>
);
}

View file

@ -8,12 +8,11 @@ export const Native = getNative();
import "./styles.css";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { FluxDispatcher, MessageStore, React, UserStore } from "@webpack/common";
import { FluxDispatcher, MessageStore, UserStore } from "@webpack/common";
import { OpenLogsButton } from "./components/LogsButton";
import { openLogModal } from "./components/LogsModal";
@ -256,15 +255,6 @@ export default definePlugin({
replace: "deleted:$self.getDeleted(...arguments), editHistory:$self.getEdited(...arguments),"
}
},
{
find: ".controlButtonWrapper,",
predicate: () => settings.store.ShowLogsButton,
replacement: {
match: /(function \i\(\i\){)(.{1,200}toolbar.{1,100}mobileToolbar)/,
replace: "$1$self.addIconToToolBar(arguments[0]);$2"
}
},
{
find: "childrenMessageContent:null",
replacement: {
@ -310,21 +300,9 @@ export default definePlugin({
openLogModal();
}
},
addIconToToolBar(e: { toolbar: React.ReactNode[] | React.ReactNode; }) {
if (Array.isArray(e.toolbar))
return e.toolbar.unshift(
<ErrorBoundary noop={true}>
<OpenLogsButton />
</ErrorBoundary>
);
e.toolbar = [
<ErrorBoundary noop={true} key={"MessageLoggerEnhanced"} >
<OpenLogsButton />
</ErrorBoundary>,
e.toolbar,
];
renderHeaderBarButton() {
if (!settings.store.ShowLogsButton) return null;
return OpenLogsButton();
},
processMessageFetch,

View file

@ -35,7 +35,6 @@ export default definePlugin({
name: "MoreUserTags",
description: "Adds tags for webhooks and moderative roles (owner, admin, etc.)",
authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias, Devs.AutumnVN, EquicordDevs.Hen],
dependencies: ["MemberListDecoratorsAPI", "NicknameIconsAPI", "MessageDecorationsAPI"],
settings,
patches: [
// Make discord actually use our tags

View file

@ -4,23 +4,14 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "NoBulletPoints",
description: "Stops you from typing markdown bullet points (stinky)",
authors: [Devs.Samwich],
dependencies: ["MessageEventsAPI"],
start() {
this.preSend = addMessagePreSendListener((channelId, msg) => {
msg.content = textProcessing(msg.content);
});
},
stop() {
this.preSend = removeMessagePreSendListener((channelId, msg) => {
msg.content = textProcessing(msg.content);
});
onBeforeMessageSend(channelId, msg) {
msg.content = textProcessing(msg.content);
},
});

View file

@ -5,9 +5,7 @@
*/
import {
addMessagePreSendListener,
MessageSendListener,
removeMessagePreSendListener,
} from "@api/MessageEvents";
import {
definePluginSettings,
@ -67,9 +65,7 @@ export default definePlugin({
name: "PolishWording",
description: "Tweaks your messages to make them look nicer and have better grammar. See settings",
authors: [Devs.Samwich, EquicordDevs.WKoA],
dependencies: ["MessageEventsAPI"],
start: () => addMessagePreSendListener(presendObject),
stop: () => removeMessagePreSendListener(presendObject),
onBeforeMessageSend: presendObject,
settings,
});

View file

@ -20,12 +20,11 @@ import "./style.css";
import { showNotification } from "@api/Notifications";
import { definePluginSettings } from "@api/Settings";
import { ErrorBoundary } from "@components/index";
import { Devs } from "@utils/constants";
import { getTheme, Theme } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Button, ChannelStore, FluxDispatcher, GuildChannelStore, NavigationRouter, RestAPI, Tooltip, UserStore } from "@webpack/common";
import { ChannelStore, FluxDispatcher, GuildChannelStore, NavigationRouter, RestAPI, UserStore } from "@webpack/common";
const QuestIcon = findComponentByCodeLazy("10.47a.76.76");
const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"');
@ -37,16 +36,13 @@ let questIdCheck = 0;
function ToolBarHeader() {
return (
<ErrorBoundary noop={true}>
<HeaderBarIcon
tooltip="Complete Quest"
position="bottom"
className="vc-quest-completer"
icon={QuestIcon}
onClick={openCompleteQuestUI}
>
</HeaderBarIcon>
</ErrorBoundary>
<HeaderBarIcon
tooltip="Complete Quest"
position="bottom"
className="vc-quest-completer"
icon={QuestIcon}
onClick={openCompleteQuestUI}
/>
);
}
@ -212,11 +208,6 @@ async function openCompleteQuestUI() {
}
const settings = definePluginSettings({
useNavBar: {
description: "Move quest button down to the server nav bar",
type: OptionType.BOOLEAN,
default: false,
},
disableNotifications: {
description: "Disable notifications when no quests are available or when a quest is completed",
type: OptionType.BOOLEAN,
@ -237,22 +228,6 @@ export default definePlugin({
replace: "onClick:()=>$self.mobileQuestPatch($1)"
},
},
{
find: '?"BACK_FORWARD_NAVIGATION":',
replacement: {
match: /"HELP".{0,100}className:\i\}\)(?=\])/,
replace: "$&,$self.renderTitleBar()"
},
predicate: () => !settings.store.useNavBar
},
{
find: ".controlButtonWrapper,",
replacement: {
match: /(function \i\(\i\){)(.{1,200}toolbar.{1,200}mobileToolbar)/,
replace: "$1$self.renderNavBar(arguments[0]);$2"
},
predicate: () => settings.store.useNavBar
}
],
mobileQuestPatch(questId) {
if (questId === questIdCheck) return;
@ -264,37 +239,5 @@ export default definePlugin({
}
});
},
renderTitleBar() {
return (
<ErrorBoundary noop>
<Tooltip text="Complete Quest">
{tooltipProps => (
<Button style={{ backgroundColor: "transparent", border: "none" }}
{...tooltipProps}
size={Button.Sizes.SMALL}
className={"vc-quest-completer-icon"}
onClick={openCompleteQuestUI}
>
<QuestIcon width={20} height={20} />
</Button>
)}
</Tooltip>
</ErrorBoundary>
);
},
renderNavBar(e) {
if (Array.isArray(e.toolbar))
return e.toolbar.unshift(
<ErrorBoundary noop={true}>
<ToolBarHeader />
</ErrorBoundary>
);
e.toolbar = [
<ErrorBoundary noop={true} key={"QuestCompleter"}>
<ToolBarHeader />
</ErrorBoundary>,
e.toolbar,
];
}
renderHeaderBarButton: ToolBarHeader
});

View file

@ -34,7 +34,6 @@ export default definePlugin({
name: "SekaiStickers",
description: "Sekai Stickers built in discord originally from github.com/TheOriginalAyaka",
authors: [Devs.MaiKokain],
dependencies: ["ChatInputButtonAPI"],
settings,
chatBarButton: {
icon: kanadeSvg,

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
import { Devs, EquicordDevs } from "@utils/constants";
import { isEquicordPluginDev, isPluginDev } from "@utils/misc";
import definePlugin from "@utils/types";
@ -142,12 +141,8 @@ export default definePlugin({
name: "ShowBadgesInChat",
authors: [Devs.Inbestigator, EquicordDevs.KrystalSkull],
description: "Shows the message author's badges beside their name in chat.",
dependencies: ["MessageDecorationsAPI"],
settings,
start: () => {
addMessageDecoration("vc-show-badges-in-chat", props => props.message?.author ? <ChatBadges author={props.message.author} /> : null);
},
stop: () => {
removeMessageDecoration("vc-show-badges-in-chat");
renderMessageDecoration(props) {
return props.message?.author ? <ChatBadges author={props.message.author} /> : null;
}
});

View file

@ -7,7 +7,6 @@
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { definePluginSettings } from "@api/Settings";
import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
@ -78,14 +77,6 @@ function SignatureIcon() {
);
}
const handleMessage = ((channelId, msg) => {
if (!settings.store.isEnabled) {
msg.content = msg.content;
} else {
msg.content = textProcessing(msg.content);
}
});
const ChatBarContextCheckbox: NavContextMenuPatchCallback = children => {
const { isEnabled, contextMenu } = settings.use(["isEnabled", "contextMenu"]);
if (!contextMenu) return;
@ -110,16 +101,18 @@ export default definePlugin({
name: "Signature",
description: "Automated fingerprint/end text",
authors: [Devs.Ven, Devs.Rini, Devs.ImBanana, EquicordDevs.KrystalSkull],
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"],
onBeforeMessageSend(channelId, msg) {
if (!settings.store.isEnabled) {
msg.content = msg.content;
} else {
msg.content = textProcessing(msg.content);
}
},
start: () => {
if (settings.store.isEnabled) true;
addMessagePreSendListener(handleMessage);
},
stop: () => {
if (settings.store.isEnabled) false;
removeMessagePreSendListener(handleMessage);
},
chatBarButton: {

View file

@ -6,7 +6,6 @@
import { AudioPlayerInterface, createAudioPlayer } from "@api/AudioPlayer";
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { EquicordDevs } from "@utils/constants";
import { ModalProps, ModalRoot, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
@ -143,15 +142,7 @@ export default definePlugin({
authors: [EquicordDevs.sliwka],
settings,
dependencies: ["AudioPlayerAPI"],
patches: [
{
find: ".controlButtonWrapper,",
replacement: {
match: /(function \i\(\i\){)(.{1,200}toolbar.{1,450}mobileToolbar)/,
replace: "$1$self.addIconToToolBar(arguments[0]);$2"
}
}
],
renderHeaderBarButton: SoggyButton,
start() {
assignBoop(settings.store.boopLink, settings.store.boopVolume * 100);
@ -162,21 +153,4 @@ export default definePlugin({
boopSound?.delete();
song?.delete();
},
// taken from message logger lol
addIconToToolBar(e: { toolbar: React.ReactNode[] | React.ReactNode; }) {
if (Array.isArray(e.toolbar))
return e.toolbar.unshift(
<ErrorBoundary noop={true}>
<SoggyButton />
</ErrorBoundary>
);
e.toolbar = [
<ErrorBoundary noop={true} key={"MessageLoggerEnhanced"} >
<SoggyButton />
</ErrorBoundary>,
e.toolbar,
];
},
});

View file

@ -4,14 +4,11 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types";
import { FluxDispatcher } from "@webpack/common";
import { CharBarButton, ChatBarIcon } from "./components/Icons";
import { OpenSBLogsButton } from "./components/SoundBoardLog";
import settings from "./settings";
import { updateLoggedSounds } from "./store";
@ -22,33 +19,9 @@ export default definePlugin({
name: "SoundBoardLogger",
authors: [Devs.Moxxie, EquicordDevs.Fres, Devs.amy, Devs.thororen],
description: "Logs all soundboards that are played in a voice chat and allows you to download them",
dependencies: ["AudioPlayerAPI", "ChatInputButtonAPI"],
patches: [
{
predicate: () => settings.store.IconLocation === "toolbar",
find: ".controlButtonWrapper,",
replacement: {
match: /(function \i\(\i\){)(.{1,500}toolbar.{1,500}mobileToolbar)/,
replace: "$1$self.addSBIconToToolBar(arguments[0]);$2"
}
}
],
dependencies: ["AudioPlayerAPI"],
settings,
addSBIconToToolBar(e: { toolbar: React.ReactNode[] | React.ReactNode; }) {
if (Array.isArray(e.toolbar))
return e.toolbar.unshift(
<ErrorBoundary noop={true}>
<OpenSBLogsButton />
</ErrorBoundary>
);
e.toolbar = [
<ErrorBoundary noop={true} key={"SoundBoardLogger"} >
<OpenSBLogsButton />
</ErrorBoundary>,
e.toolbar,
];
},
renderHeaderBarButton: OpenSBLogsButton,
start() {
enableStyle(styles);
FluxDispatcher.subscribe("VOICE_CHANNEL_EFFECT_SEND", async sound => {
@ -56,10 +29,8 @@ export default definePlugin({
await updateLoggedSounds(sound);
getListeners().forEach(cb => cb());
});
if (settings.store.IconLocation === "chat") addChatBarButton("vc-soundlog-button", CharBarButton, ChatBarIcon);
},
stop() {
disableStyle(styles);
if (settings.store.IconLocation === "chat") removeChatBarButton("vc-soundlog-button");
}
});

View file

@ -85,15 +85,6 @@ const settings = definePluginSettings({
default: 0.5,
markers: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
},
IconLocation: {
description: "choose where to show the SoundBoard Log icon (requires restart)",
type: OptionType.SELECT,
options: [
{ label: "Toolbar", value: "toolbar", default: true },
{ label: "Chat input", value: "chat" }
],
restartNeeded: true
},
});
export default settings;

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { MessageSendListener } from "@api/MessageEvents";
import { definePluginSettings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import { getCurrentChannel, sendMessage } from "@utils/discord";
@ -117,16 +117,11 @@ export default definePlugin({
name: "SplitLargeMessages",
description: "Splits large messages into multiple to fit Discord's message limit.",
authors: [EquicordDevs.Reycko],
dependencies: ["MessageEventsAPI"],
settings,
onBeforeMessageSend: listener,
start() {
if (settings.store.maxLength === 0) maxLength = autoMaxLength();
addMessagePreSendListener(listener);
},
stop() {
removeMessagePreSendListener(listener);
},
patches: [

View file

@ -66,7 +66,7 @@ export default definePlugin({
name: "TalkInReverse",
authors: [EquicordDevs.Tolgchu],
description: "Reverses the message content before sending it.",
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"],
dependencies: ["MessageEventsAPI"],
chatBarButton: {
icon: ReverseMessageIcon,
render: ReverseMessageToggle

View file

@ -46,7 +46,6 @@ const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) =>
export default definePlugin({
name: "Translate+",
description: "Vencord's translate plugin but with support for artistic languages!",
dependencies: ["MessageAccessoriesAPI"],
authors: [Devs.Ven, EquicordDevs.Prince527],
settings,
contextMenus: {

View file

@ -0,0 +1,24 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "HeaderBarAPI",
description: "API to add buttons to the header bar.",
authors: [EquicordDevs.Prism],
patches: [
{
find: '?"BACK_FORWARD_NAVIGATION":',
replacement: {
match: /(?<=\i\.Fragment,\{children:\[.{1,500}className:\i\}\))(?=\])/,
replace: ",...Vencord.Api.HeaderBar._addButtons()"
}
}
]
});

View file

@ -18,9 +18,6 @@
import "./style.css";
import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
import { addNicknameIcon, removeNicknameIcon } from "@api/NicknameIcons";
import { definePluginSettings, migratePluginSetting, Settings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { classes } from "@utils/misc";
@ -192,49 +189,22 @@ const PlatformIndicator = ({ user, isProfile, isMessage, isMemberList }: Platfor
);
};
function toggleMemberListDecorators(enabled: boolean) {
if (enabled) {
addMemberListDecorator("PlatformIndicators", props => <PlatformIndicator user={props.user} isMemberList />);
} else {
removeMemberListDecorator("PlatformIndicators");
}
}
function toggleNicknameIcons(enabled: boolean) {
if (enabled) {
addNicknameIcon("PlatformIndicators", props => <PlatformIndicator user={UserStore.getUser(props.userId)} isProfile />, 1);
} else {
removeNicknameIcon("PlatformIndicators");
}
}
function toggleMessageDecorators(enabled: boolean) {
if (enabled) {
addMessageDecoration("PlatformIndicators", props => <PlatformIndicator user={props.message?.author} isMessage />);
} else {
removeMessageDecoration("PlatformIndicators");
}
}
migratePluginSetting("PlatformIndicators", "profiles", "badges");
const settings = definePluginSettings({
list: {
type: OptionType.BOOLEAN,
description: "Show indicators in the member list",
default: true,
onChange: toggleMemberListDecorators
},
profiles: {
type: OptionType.BOOLEAN,
description: "Show indicators in user profiles",
default: true,
onChange: toggleNicknameIcons
},
messages: {
type: OptionType.BOOLEAN,
description: "Show indicators inside messages",
default: true,
onChange: toggleMessageDecorators
},
colorMobileIndicator: {
type: OptionType.BOOLEAN,
@ -278,19 +248,21 @@ export default definePlugin({
name: "PlatformIndicators",
description: "Adds platform indicators (Desktop, Mobile, Web...) to users",
authors: [Devs.kemo, Devs.TheSun, Devs.Nuckyz, Devs.Ven],
dependencies: ["MemberListDecoratorsAPI", "NicknameIconsAPI", "MessageDecorationsAPI"],
settings,
start() {
if (settings.store.list) toggleMemberListDecorators(true);
if (settings.store.profiles) toggleNicknameIcons(true);
if (settings.store.messages) toggleMessageDecorators(true);
renderNicknameIcon(props) {
if (!settings.store.profiles) return null;
return (
<PlatformIndicator user={UserStore.getUser(props.userId)} isProfile />
);
},
renderMemberListDecorator(props) {
if (!settings.store.list) return null;
return <PlatformIndicator user={props.user} isMemberList />;
stop() {
if (settings.store.list) toggleMemberListDecorators(false);
if (settings.store.profiles) toggleNicknameIcons;
if (settings.store.messages) toggleMessageDecorators(false);
},
renderMessageDecoration(props) {
if (!settings.store.messages) return null;
return <PlatformIndicator user={props.message?.author} isMessage />;
},
patches: [

View file

@ -296,7 +296,6 @@ export default definePlugin({
name: "SilentTyping",
authors: [Devs.Ven, Devs.Rini, Devs.ImBanana, EquicordDevs.Etorix],
description: "Hide your typing indicator from chat.",
dependencies: ["ChatInputButtonAPI"],
isModified: true,
settings,

View file

@ -18,9 +18,6 @@
import "./style.css";
import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
import { addNicknameIcon, removeNicknameIcon } from "@api/NicknameIcons";
import { definePluginSettings } from "@api/Settings";
import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
@ -52,9 +49,22 @@ export default definePlugin({
name: "UserVoiceShow",
description: "Shows an indicator when a user is in a Voice Channel",
authors: [Devs.Nuckyz, Devs.LordElias, EquicordDevs.omaw],
dependencies: ["NicknameIconsAPI", "MemberListDecoratorsAPI", "MessageDecorationsAPI"],
settings,
renderNicknameIcon({ userId }) {
if (!settings.store.showInUserProfileModal) return null;
return (
<VoiceChannelIndicator userId={userId} isProfile />
);
},
renderMemberListDecorator({ user }) {
if (!settings.store.showInMemberList) return null;
return user == null ? null : <VoiceChannelIndicator userId={user.id} />;
},
renderMessageDecoration({ message }) {
if (!settings.store.showInMessages) return null;
return message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} isMessageIndicator />;
},
patches: [
// Friends List
{
@ -67,23 +77,5 @@ export default definePlugin({
}
],
start() {
if (settings.store.showInUserProfileModal) {
addNicknameIcon("UserVoiceShow", ({ userId }) => <VoiceChannelIndicator userId={userId} isProfile />);
}
if (settings.store.showInMemberList) {
addMemberListDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />);
}
if (settings.store.showInMessages) {
addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} isMessageIndicator />);
}
},
stop() {
removeNicknameIcon("UserVoiceShow");
removeMemberListDecorator("UserVoiceShow");
removeMessageDecoration("UserVoiceShow");
},
VoiceChannelIndicator
});

View file

@ -20,6 +20,7 @@ import { AudioProcessor } from "@api/AudioPlayer";
import type { ProfileBadge } from "@api/Badges";
import type { ChatBarButtonData, ChatBarButtonFactory } from "@api/ChatButtons";
import type { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { HeaderBarButtonFactory } from "@api/HeaderBar";
import type { MemberListDecoratorFactory } from "@api/MemberListDecorators";
import type { MessageAccessoryFactory } from "@api/MessageAccessories";
import type { MessageDecorationFactory } from "@api/MessageDecorations";
@ -199,7 +200,8 @@ export interface PluginDef {
// Custom
renderNicknameIcon?: NicknameIconFactory;
audioProcessor?: AudioProcessor;
renderHeaderBarButton?: HeaderBarButtonFactory;
onAudioProcessor?: AudioProcessor;
// TODO: Remove eventually
/**