Plugin

PreviewMessage

Lets you preview your message before sending it.

Chat Utility
index.tsx
Download

Source

src/plugins/previewMessage/index.tsx
1/*
2 * FiveCord — a Discord client mod
3 * Copyright (c) 2025 FiveCord
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
7import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
8import { generateId, sendBotMessage } from "@api/Commands";
9import { Devs } from "@utils/constants";
10import definePlugin, { IconComponent, StartAt } from "@utils/types";
11import { CloudUpload, MessageAttachment } from "@vencord/discord-types";
12import { DraftStore, DraftType, UploadAttachmentStore, UserStore, useStateFromStores } from "@webpack/common";
13
14const getDraft = (channelId: string) => DraftStore.getDraft(channelId, DraftType.ChannelMessage);
15
16
17const getImageBox = (url: string): Promise<{ width: number, height: number; } | null> =>
18 new Promise(res => {
19 const img = new Image();
20 img.onload = () =>
21 res({ width: img.width, height: img.height });
22
23 img.onerror = () =>
24 res(null);
25
26 img.src = url;
27 });
28
29
30const getAttachments = async (channelId: string) =>
31 await Promise.all(
32 UploadAttachmentStore.getUploads(channelId, DraftType.ChannelMessage)
33 .map(async (upload: CloudUpload) => {
34 const { isImage, filename, spoiler, item: { file } } = upload;
35 const url = URL.createObjectURL(file);
36 const attachment: MessageAttachment = {
37 id: generateId(),
38 filename: spoiler ? "SPOILER_" + filename : filename,
39 // weird eh? if i give it the normal content type the preview doenst work
40 content_type: undefined,
41 size: upload.getSize(),
42 spoiler,
43 // discord adds query params to the url, so we need to add a hash to prevent that
44 url: url + "#",
45 proxy_url: url + "#",
46 };
47
48 if (isImage) {
49 const box = await getImageBox(url);
50 if (!box) return attachment;
51
52 attachment.width = box.width;
53 attachment.height = box.height;
54 }
55
56 return attachment;
57 })
58 );
59
60
61const PreviewIcon: IconComponent = ({ height = 20, width = 20, className }) => {
62 return (
63 <svg
64 fill="currentColor"
65 fillRule="evenodd"
66 width={width}
67 height={height}
68 className={className}
69 viewBox="0 0 24 24"
70 style={{ scale: "1.096", translate: "0 -1px" }}
71 >
72 <path d="M22.89 11.7c.07.2.07.4 0 .6C22.27 13.9 19.1 21 12 21c-7.11 0-10.27-7.11-10.89-8.7a.83.83 0 0 1 0-.6C1.73 10.1 4.9 3 12 3c7.11 0 10.27 7.11 10.89 8.7Zm-4.5-3.62A15.11 15.11 0 0 1 20.85 12c-.38.88-1.18 2.47-2.46 3.92C16.87 17.62 14.8 19 12 19c-2.8 0-4.87-1.38-6.39-3.08A15.11 15.11 0 0 1 3.15 12c.38-.88 1.18-2.47 2.46-3.92C7.13 6.38 9.2 5 12 5c2.8 0 4.87 1.38 6.39 3.08ZM15.56 11.77c.2-.1.44.02.44.23a4 4 0 1 1-4-4c.21 0 .33.25.23.44a2.5 2.5 0 0 0 3.32 3.32Z" />
73 </svg>
74 );
75};
76
77const PreviewButton: ChatBarButtonFactory = ({ isAnyChat, isEmpty, type: { attachments }, channel: { id: channelId } }) => {
78 const draft = useStateFromStores([DraftStore], () => getDraft(channelId));
79
80 if (!isAnyChat) return null;
81
82 const hasAttachments = attachments && UploadAttachmentStore.getUploads(channelId, DraftType.ChannelMessage).length > 0;
83 const hasContent = !isEmpty && draft?.length > 0;
84
85 if (!hasContent && !hasAttachments) return null;
86
87 return (
88 <ChatBarButton
89 tooltip="Preview Message"
90 onClick={async () =>
91 sendBotMessage(
92 channelId,
93 {
94 content: getDraft(channelId),
95 author: UserStore.getCurrentUser(),
96 attachments: hasAttachments ? await getAttachments(channelId) : undefined,
97 }
98 )}
99 buttonProps={{
100 style: {
101 translate: "0 2px"
102 }
103 }}
104 >
105 <PreviewIcon />
106 </ChatBarButton>
107 );
108
109};
110
111export default definePlugin({
112 name: "PreviewMessage",
113 description: "Lets you preview your message before sending it.",
114 tags: ["Chat", "Utility"],
115 authors: [Devs.Aria],
116 // start early to ensure we're the first plugin to add our button
117 // This makes the popping in less awkward
118 startAt: StartAt.Init,
119
120 chatBarButton: {
121 icon: PreviewIcon,
122 render: PreviewButton
123 }
124});
125