diff --git a/src/equicordplugins/repeatMessages/index.tsx b/src/equicordplugins/repeatMessages/index.tsx new file mode 100644 index 000000000..7f89ff3bb --- /dev/null +++ b/src/equicordplugins/repeatMessages/index.tsx @@ -0,0 +1,157 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { migratePluginSettings } from "@api/Settings"; +import { classNameFactory } from "@api/Styles"; +import { Devs, EquicordDevs } from "@utils/constants"; +import { sendMessage } from "@utils/discord"; +import { useForceUpdater } from "@utils/react"; +import definePlugin from "@utils/types"; +import { Message } from "@vencord/discord-types"; +import { ChannelStore, Menu, useEffect } from "@webpack/common"; + +interface AttachmentInfo { + filename: string; + url: string; +} + +const cl = classNameFactory("vc-repeat-"); + +function RepeatMessageIcon({ className }: { className?: string; }) { + return ( + + + + ); +} + +let shift = false; + +function repeatMessage(msg: Message) { + if (!msg) return; + let content = ""; + content += msg.content; + + const allAttachments: AttachmentInfo[] = []; + + if (msg.attachments?.length) { + allAttachments.push( + ...msg.attachments.map(a => ({ + filename: a.filename, + url: a.url, + })) + ); + } + + if (msg.messageSnapshots) { + msg.messageSnapshots.forEach(snapshot => { + if (snapshot.message.attachments?.length) { + allAttachments.push( + ...snapshot.message.attachments.map(a => ({ + filename: a.filename, + url: a.url, + })) + ); + } + }); + } + + if (allAttachments.length) { + content += "\nAttachments:"; + allAttachments.forEach(a => { + content += `\n - ${a.filename} (${a.url})`; + }); + } + + sendMessage(msg.channel_id, + { content }, + true, + { + allowedMentions: { + parse: [], + replied_user: false + }, + messageReference: shift ? { + channel_id: msg.channel_id, + message_id: msg.id + } : undefined, + stickerIds: msg.stickerItems.map(s => s.id) + }); +} + +const messageCtxPatch: NavContextMenuPatchCallback = (children, { msg }: { msg: Message; }) => { + if (!msg) return null; + + const group = findGroupChildrenByChildId("copy-text", children); + if (!group) return; + + const forceUpdate = useForceUpdater(); + + useEffect(() => { + const handler = () => { + forceUpdate(); + }; + + window.addEventListener("keydown", handler); + window.addEventListener("keyup", handler); + + return () => { + window.removeEventListener("keydown", handler); + window.removeEventListener("keyup", handler); + }; + }, []); + + group.splice(group.findIndex(c => c?.props?.id === "reply") + 1, 0, ( + repeatMessage(msg)} + /> + )); +}; + +const keyupListener = (event: KeyboardEvent) => { + if (event.key === "Shift") shift = false; +}; + +const keydownListener = (event: KeyboardEvent) => { + if (event.key === "Shift") shift = true; +}; + +migratePluginSettings("RepeatMessages", "RepeatMessage"); +export default definePlugin({ + name: "RepeatMessages", + description: "Allows you to repeat messages quickly. If you hold shift while clicking the Repeat option, it will reply to the message.", + authors: [EquicordDevs.Tolgchu, Devs.thororen], + contextMenus: { + "message": messageCtxPatch + }, + renderMessagePopoverButton(msg) { + if (!msg) return null; + return { + label: "Repeat (Click) / Repeat and Reply (Shift + Click)", + icon: RepeatMessageIcon, + message: msg, + channel: ChannelStore.getChannel(msg.channel_id), + onClick: async () => repeatMessage(msg) + }; + }, + start() { + document.addEventListener("keyup", keyupListener); + document.addEventListener("keydown", keydownListener); + }, + stop() { + document.removeEventListener("keyup", keyupListener); + document.removeEventListener("keydown", keydownListener); + }, +});