ChatButtonsPlus Plugin (#371)

* delete meow and woof and make a new plugin called ChatButtonsPlus

* rename css file

* update to only add once if plugins were enabled before
This commit is contained in:
creations 2025-09-01 16:03:58 -04:00 • committed by GitHub
parent 3787622f99
commit 3cd45b4182
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 336 additions and 63 deletions

View file

@ -0,0 +1,284 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./styles.css";
import { ChatBarButton } from "@api/ChatButtons";
import { DataStore } from "@api/index";
import { definePluginSettings, Settings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import { getCurrentChannel, sendMessage } from "@utils/discord";
import { useForceUpdater } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
import { Button, Forms, React, Switch, TextInput } from "@webpack/common";
type ButtonEntry = {
id: string;
label: string;
message: string;
svg: string;
enabled: boolean;
};
let buttonEntries: ButtonEntry[] = [];
const BUTTON_ENTRIES_KEY = "ChatButtonsPlus_buttonEntries";
function generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
async function handleButtonClick(context: string) {
const currentChannel = getCurrentChannel();
if (!currentChannel) return;
sendMessage(currentChannel.id, { content: context });
}
async function addButtonEntry(forceUpdate: () => void) {
try {
buttonEntries.push({
id: generateId(),
label: "New Button",
message: "Hello!",
svg: '<path fill="currentColor" d="M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h4l4 4 4-4h4c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/>',
enabled: true
});
await DataStore.set(BUTTON_ENTRIES_KEY, buttonEntries);
forceUpdate();
} catch (error) {
console.error("ChatButtonsPlus: Failed to add button entry:", error);
}
}
async function removeButtonEntry(id: string, forceUpdate: () => void) {
try {
const index = buttonEntries.findIndex(entry => entry.id === id);
if (index !== -1) {
buttonEntries.splice(index, 1);
await DataStore.set(BUTTON_ENTRIES_KEY, buttonEntries);
forceUpdate();
}
} catch (error) {
console.error("ChatButtonsPlus: Failed to remove button entry:", error);
}
}
function ButtonEntries() {
const update = useForceUpdater();
async function setLabel(id: string, value: string) {
try {
const index = buttonEntries.findIndex(entry => entry.id === id);
if (index !== -1) {
buttonEntries[index].label = value.trim() || "Button";
await DataStore.set(BUTTON_ENTRIES_KEY, buttonEntries);
update();
}
} catch (error) {
console.error("ChatButtonsPlus: Failed to update label:", error);
}
}
async function setMessage(id: string, value: string) {
try {
const index = buttonEntries.findIndex(entry => entry.id === id);
if (index !== -1) {
buttonEntries[index].message = value.trim() || "Hello!";
await DataStore.set(BUTTON_ENTRIES_KEY, buttonEntries);
update();
}
} catch (error) {
console.error("ChatButtonsPlus: Failed to update message:", error);
}
}
async function setSvg(id: string, value: string) {
try {
const index = buttonEntries.findIndex(entry => entry.id === id);
if (index !== -1 && isValidSvg(value.trim())) {
buttonEntries[index].svg = value.trim();
await DataStore.set(BUTTON_ENTRIES_KEY, buttonEntries);
update();
}
} catch (error) {
console.error("ChatButtonsPlus: Failed to update SVG:", error);
}
}
async function setEnabled(id: string, value: boolean) {
try {
const index = buttonEntries.findIndex(entry => entry.id === id);
if (index !== -1) {
buttonEntries[index].enabled = value;
await DataStore.set(BUTTON_ENTRIES_KEY, buttonEntries);
update();
}
} catch (error) {
console.error("ChatButtonsPlus: Failed to update enabled state:", error);
}
}
function isValidSvg(svg: string): boolean {
if (!svg) return true;
const dangerousPatterns = [
/<script/i,
/javascript:/i,
/on\w+=/i,
/<iframe/i,
/<object/i,
/<embed/i,
/<link/i,
/<meta/i
];
if (dangerousPatterns.some(pattern => pattern.test(svg))) {
return false;
}
return svg.includes("<") && (
svg.includes("path") ||
svg.includes("svg") ||
svg.includes("circle") ||
svg.includes("rect") ||
svg.includes("polygon") ||
svg.includes("g")
);
}
const elements = buttonEntries.map((entry, i) => {
return (
<div key={entry.id} className="chatButtonsPlus-card">
<div className="chatButtonsPlus-header">
<Forms.FormTitle tag="h5" className="chatButtonsPlus-title">Button {i + 1}</Forms.FormTitle>
<div className="chatButtonsPlus-controls">
<Switch
value={entry.enabled ?? true}
onChange={value => setEnabled(entry.id, value)}
className="chatButtonsPlus-toggle"
>
Enabled
</Switch>
<Button
onClick={() => removeButtonEntry(entry.id, update)}
look={Button.Looks.OUTLINED}
color={Button.Colors.RED}
size={Button.Sizes.SMALL}
>
Delete
</Button>
</div>
</div>
<div className="chatButtonsPlus-field">
<Forms.FormText className="chatButtonsPlus-label">Button Label (Tooltip)</Forms.FormText>
<TextInput
placeholder="Button label/tooltip"
value={entry.label}
onChange={e => setLabel(entry.id, e)}
/>
</div>
<div className="chatButtonsPlus-field">
<Forms.FormText className="chatButtonsPlus-label">Message to Send</Forms.FormText>
<TextInput
placeholder="Message to send when clicked"
value={entry.message}
onChange={e => setMessage(entry.id, e)}
/>
</div>
<div className="chatButtonsPlus-field">
<Forms.FormText className="chatButtonsPlus-label">Custom SVG Path (24x24 viewBox)</Forms.FormText>
<TextInput
placeholder='<path fill="currentColor" d="..."/>'
value={entry.svg}
onChange={e => setSvg(entry.id, e)}
/>
<Forms.FormText className="chatButtonsPlus-description">
Enter SVG path elements for a 24x24 viewBox. Use "currentColor" for the fill to match Discord's theme.
</Forms.FormText>
</div>
</div>
);
});
return (
<>
{elements}
<Button onClick={() => addButtonEntry(update)}>Add Button</Button>
</>
);
}
const settings = definePluginSettings({
buttons: {
type: OptionType.COMPONENT,
description: "Manage your custom chat buttons",
component: () => <ButtonEntries />
}
});
export default definePlugin({
name: "ChatButtonsPlus",
description: "Add custom chat buttons with personalized + messages and SVG icons",
authors:
[EquicordDevs.creations],
settings,
renderChatBarButton: ({ isMainChat }) => {
if (!isMainChat) return null;
return (
<>
{buttonEntries.filter(entry => entry.enabled !== false).map(entry => (
<ChatBarButton
key={entry.id}
tooltip={entry.label}
onClick={() => handleButtonClick(entry.message)}
>
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 24 24">
<g dangerouslySetInnerHTML={{ __html: entry.svg }} />
</svg>
</ChatBarButton>
))}
</>
);
},
async start() {
const storedEntries = await DataStore.get(BUTTON_ENTRIES_KEY) ?? [];
if (Settings.plugins?.Woof.enabled) {
storedEntries.push({
id: generateId(),
label: "Woof",
message: "Woof",
svg: '<path fill="currentColor" d="M 12.898438 7.9375 L 13.863281 2.160156 C 13.941406 1.683594 14.355469 1.332031 14.835938 1.332031 C 15.148438 1.332031 15.441406 1.480469 15.628906 1.730469 L 16.332031 2.667969 L 18.503906 2.667969 C 19.035156 2.667969 19.542969 2.878906 19.917969 3.253906 L 20.667969 4 L 23 4 C 23.554688 4 24 4.445312 24 5 L 24 6 C 24 7.839844 22.507812 9.332031 20.667969 9.332031 L 17.777344 9.332031 L 17.566406 10.605469 Z M 17.332031 12.003906 L 17.332031 21.332031 C 17.332031 22.070312 16.738281 22.667969 16 22.667969 L 14.667969 22.667969 C 13.929688 22.667969 13.332031 22.070312 13.332031 21.332031 L 13.332031 16.535156 C 12.332031 17.046875 11.199219 17.332031 10 17.332031 C 8.800781 17.332031 7.667969 17.046875 6.667969 16.535156 L 6.667969 21.332031 C 6.667969 22.070312 6.070312 22.667969 5.332031 22.667969 L 4 22.667969 C 3.261719 22.667969 2.667969 22.070312 2.667969 21.332031 L 2.667969 11.742188 C 1.464844 11.289062 0.523438 10.269531 0.199219 8.972656 L 0.0429688 8.324219 C -0.136719 7.613281 0.296875 6.886719 1.011719 6.707031 C 1.730469 6.527344 2.449219 6.960938 2.628906 7.679688 L 2.792969 8.324219 C 2.9375 8.917969 3.472656 9.332031 4.082031 9.332031 L 12.660156 9.332031 Z M 19.332031 4.667969 C 19.332031 4.296875 19.035156 4 18.667969 4 C 18.296875 4 18 4.296875 18 4.667969 C 18 5.035156 18.296875 5.332031 18.667969 5.332031 C 19.035156 5.332031 19.332031 5.035156 19.332031 4.667969 "/>',
enabled: true
});
Settings.plugins.Woof.enabled = false;
}
if (Settings.plugins?.Meow.enabled) {
storedEntries.push({
id: generateId(),
label: "Meow",
message: "Meow",
svg: '<path fill="currentColor" d="M 13.332031 9 L 14.046875 9 C 14.964844 10.796875 16.691406 12 18.667969 12 C 19.125 12 19.574219 11.933594 20 11.8125 L 20 22.5 C 20 23.328125 19.402344 24 18.667969 24 C 17.929688 24 17.332031 23.328125 17.332031 22.5 L 17.332031 15.898438 L 11.667969 21 L 14 21 C 14.738281 21 15.332031 21.671875 15.332031 22.5 C 15.332031 23.328125 14.738281 24 14 24 L 8 24 C 5.792969 24 4 21.984375 4 19.5 L 4 9.023438 C 4 8.269531 3.5 7.625 2.832031 7.53125 L 2.503906 7.484375 C 1.773438 7.382812 1.253906 6.632812 1.347656 5.8125 C 1.4375 4.992188 2.105469 4.40625 2.832031 4.507812 L 3.164062 4.554688 C 5.164062 4.835938 6.667969 6.75 6.667969 9.023438 L 6.667969 13.023438 C 8.101562 10.597656 10.550781 9 13.332031 9 M 20 10.242188 C 19.582031 10.40625 19.132812 10.5 18.667969 10.5 C 17.484375 10.5 16.417969 9.917969 15.683594 9 C 15.527344 8.808594 15.390625 8.601562 15.269531 8.382812 C 14.886719 7.6875 14.667969 6.871094 14.667969 6 L 14.667969 0.5 C 14.667969 0.226562 14.863281 0.00390625 15.109375 0 L 15.117188 0 C 15.253906 0 15.382812 0.0742188 15.464844 0.195312 L 15.464844 0.203125 L 16 1 L 17.132812 2.699219 L 17.332031 3 L 20 3 L 20.199219 2.699219 L 21.332031 1 L 21.867188 0.203125 L 21.867188 0.195312 C 21.949219 0.0742188 22.078125 0 22.214844 0 L 22.226562 0 C 22.472656 0.00390625 22.667969 0.226562 22.667969 0.5 L 22.667969 6 C 22.667969 6.8125 22.476562 7.574219 22.140625 8.230469 C 21.671875 9.160156 20.910156 9.882812 20 10.242188 M 18 6 C 18 5.585938 17.703125 5.25 17.332031 5.25 C 16.964844 5.25 16.667969 5.585938 16.667969 6 C 16.667969 6.414062 16.964844 6.75 17.332031 6.75 C 17.703125 6.75 18 6.414062 18 6 M 20 6.75 C 20.367188 6.75 20.667969 6.414062 20.667969 6 C 20.667969 5.585938 20.367188 5.25 20 5.25 C 19.632812 5.25 19.332031 5.585938 19.332031 6 C 19.332031 6.414062 19.632812 6.75 20 6.75 "/>',
enabled: true
});
Settings.plugins.Meow.enabled = false;
}
await DataStore.set(BUTTON_ENTRIES_KEY, storedEntries);
buttonEntries = storedEntries;
}
});

View file

@ -0,0 +1,52 @@
.chatButtonsPlus-card {
margin-bottom: 15px;
padding: 15px;
border: 1px solid var(--background-modifier-accent);
border-radius: 8px;
}
.chatButtonsPlus-header {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 10px;
}
.chatButtonsPlus-title {
margin: 0;
flex-grow: 1;
}
.chatButtonsPlus-controls {
display: flex;
align-items: center;
gap: 15px;
}
.chatButtonsPlus-toggle div[class*="divider"] {
display: none;
}
.chatButtonsPlus-toggle div[class*="labelRow"] {
display: flex;
gap: 5px;
}
.chatButtonsPlus-toggle {
margin: 0;
}
.chatButtonsPlus-field {
margin-bottom: 10px;
}
.chatButtonsPlus-label {
margin-bottom: 5px;
}
.chatButtonsPlus-description {
font-size: 12px;
color: var(--text-muted);
margin-top: 3px;
}

View file

@ -1,32 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
import { Devs } from "@utils/constants";
import { getCurrentChannel, sendMessage } from "@utils/discord";
import definePlugin from "@utils/types";
async function handleButtonClick() {
// @ts-expect-error typing issue
sendMessage(getCurrentChannel().id, { content: "meow" });
}
const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
if (!isMainChat) return null;
return (
<ChatBarButton tooltip="Meow" onClick={handleButtonClick}>
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 576 512"><path fill="currentColor" d="M320 192h17.1c22.1 38.3 63.5 64 110.9 64c11 0 21.8-1.4 32-4v228c0 17.7-14.3 32-32 32s-32-14.3-32-32V339.2L280 448h56c17.7 0 32 14.3 32 32s-14.3 32-32 32H192c-53 0-96-43-96-96V192.5c0-16.1-12-29.8-28-31.8l-7.9-1c-17.5-2.2-30-18.2-27.8-35.7S50.5 94 68 96.2l7.9 1c48 6 84.1 46.8 84.1 95.3v85.3c34.4-51.7 93.2-85.8 160-85.8m160 26.5c-10 3.5-20.8 5.5-32 5.5c-28.4 0-54-12.4-71.6-32c-3.7-4.1-7-8.5-9.9-13.2C357.3 164 352 146.6 352 128V10.7C352 4.8 356.7.1 362.6 0h.2c3.3 0 6.4 1.6 8.4 4.2v.1l12.8 17l27.2 36.3L416 64h64l4.8-6.4L512 21.3l12.8-17v-.1c2-2.6 5.1-4.2 8.4-4.2h.2c5.9.1 10.6 4.8 10.6 10.7V128c0 17.3-4.6 33.6-12.6 47.6c-11.3 19.8-29.6 35.2-51.4 42.9M432 128a16 16 0 1 0-32 0a16 16 0 1 0 32 0m48 16a16 16 0 1 0 0-32a16 16 0 1 0 0 32" /></svg>
</ChatBarButton>
);
};
export default definePlugin({
name: "Meow",
description: "Adds a chatbar button to meow in chat",
authors:
[Devs.Samwich],
renderChatBarButton: ChatBarIcon,
});

View file

@ -1,31 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
import { Devs } from "@utils/constants";
import { getCurrentChannel, sendMessage } from "@utils/discord";
import definePlugin from "@utils/types";
async function handleButtonClick() {
// @ts-expect-error typing issue
sendMessage(getCurrentChannel().id, { content: "woof" });
}
const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
if (!isMainChat) return null;
return (
<ChatBarButton tooltip="Woof" onClick={handleButtonClick}>
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 576 512"><path fill="currentColor" d="m309.6 158.5l23.1-138.7C334.6 8.4 344.5 0 356.1 0c7.5 0 14.5 3.5 19 9.5L392 32h52.1c12.7 0 24.9 5.1 33.9 14.1L496 64h56c13.3 0 24 10.7 24 24v24c0 44.2-35.8 80-80 80h-69.3l-5.1 30.5zM416 256.1V480c0 17.7-14.3 32-32 32h-32c-17.7 0-32-14.3-32-32V364.8c-24 12.3-51.2 19.2-80 19.2s-56-6.9-80-19.2V480c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32V249.8c-28.8-10.9-51.4-35.3-59.2-66.5L1 167.8c-4.3-17.1 6.1-34.5 23.3-38.8s34.5 6.1 38.8 23.3l3.9 15.5C70.5 182 83.3 192 98 192h205.8zM464 80a16 16 0 1 0-32 0a16 16 0 1 0 32 0" /></svg>
</ChatBarButton>
);
};
export default definePlugin({
name: "Woof",
description: "Adds a chatbar button to woof in chat",
authors: [Devs.Samwich],
renderChatBarButton: ChatBarIcon,
});