From c38aac23fd1e1f4acf171479fe4beb17d8fe4ae9 Mon Sep 17 00:00:00 2001 From: V Date: Tue, 9 Sep 2025 01:48:53 +0200 Subject: [PATCH] improve various types (#3663) Co-authored-by: V Co-authored-by: John Davis <70701251+gobuster@users.noreply.github.com> Co-authored-by: sadan4 <117494111+sadan4@users.noreply.github.com> --- packages/discord-types/enums/index.ts | 3 +- packages/discord-types/enums/misc.ts | 4 + packages/discord-types/src/index.d.ts | 1 + .../src/modules/CloudUpload.d.ts | 74 +++++++++++++++++++ packages/discord-types/src/modules/index.d.ts | 1 + src/api/MessageEvents.ts | 28 +------ src/api/{Notices.ts => Notices.tsx} | 11 ++- src/plugins/anonymiseFileNames/index.tsx | 6 +- src/plugins/previewMessage/index.tsx | 6 +- src/plugins/voiceMessages/index.tsx | 8 +- src/utils/discord.tsx | 16 +++- src/utils/react.tsx | 10 ++- 12 files changed, 128 insertions(+), 40 deletions(-) create mode 100644 packages/discord-types/enums/misc.ts create mode 100644 packages/discord-types/src/modules/CloudUpload.d.ts create mode 100644 packages/discord-types/src/modules/index.d.ts rename src/api/{Notices.ts => Notices.tsx} (73%) diff --git a/packages/discord-types/enums/index.ts b/packages/discord-types/enums/index.ts index 0ab49887..3d80895c 100644 --- a/packages/discord-types/enums/index.ts +++ b/packages/discord-types/enums/index.ts @@ -1,3 +1,4 @@ +export * from "./channel"; export * from "./commands"; export * from "./messages"; -export * from "./channel"; \ No newline at end of file +export * from "./misc"; diff --git a/packages/discord-types/enums/misc.ts b/packages/discord-types/enums/misc.ts new file mode 100644 index 00000000..d1419ba3 --- /dev/null +++ b/packages/discord-types/enums/misc.ts @@ -0,0 +1,4 @@ +export const enum CloudUploadPlatform { + REACT_NATIVE = 0, + WEB = 1, +} diff --git a/packages/discord-types/src/index.d.ts b/packages/discord-types/src/index.d.ts index 6d9356b4..fdbd65f1 100644 --- a/packages/discord-types/src/index.d.ts +++ b/packages/discord-types/src/index.d.ts @@ -4,6 +4,7 @@ export * from "./components"; export * from "./flux"; export * from "./fluxEvents"; export * from "./menu"; +export * from "./modules"; export * from "./stores"; export * from "./utils"; export * as Webpack from "../webpack"; diff --git a/packages/discord-types/src/modules/CloudUpload.d.ts b/packages/discord-types/src/modules/CloudUpload.d.ts new file mode 100644 index 00000000..02192ed5 --- /dev/null +++ b/packages/discord-types/src/modules/CloudUpload.d.ts @@ -0,0 +1,74 @@ +import EventEmitter from "events"; +import { CloudUploadPlatform } from "../../enums"; + +interface BaseUploadItem { + platform: CloudUploadPlatform; + id?: string; + origin?: string; + isThumbnail?: boolean; + clip?: unknown; +} + +export interface ReactNativeUploadItem extends BaseUploadItem { + platform: CloudUploadPlatform.REACT_NATIVE; + uri: string; + filename?: string; + mimeType?: string; + durationSecs?: number; + waveform?: string; + isRemix?: boolean; +} + +export interface WebUploadItem extends BaseUploadItem { + platform: CloudUploadPlatform.WEB; + file: File; +} + +export type CloudUploadItem = ReactNativeUploadItem | WebUploadItem; + +export class CloudUpload extends EventEmitter { + constructor(item: CloudUploadItem, channelId: string, showLargeMessageDialog?: boolean, reactNativeFileIndex?: number); + + channelId: string; + classification: string; + clip: unknown; + contentHash: unknown; + currentSize: number; + description: string | null; + durationSecs: number | undefined; + etag: string | undefined; + error: unknown; + filename: string; + id: string; + isImage: boolean; + isRemix: boolean | undefined; + isThumbnail: boolean; + isVideo: boolean; + item: { + file: File; + platform: CloudUploadPlatform; + origin: string; + }; + loaded: number; + mimeType: string; + origin: string; + postCompressionSize: number | undefined; + preCompressionSize: number; + responseUrl: string; + sensitive: boolean; + showLargeMessageDialog: boolean; + spoiler: boolean; + startTime: number; + status: "NOT_STARTED" | "STARTED" | "UPLOADING" | "ERROR" | "COMPLETED" | "CANCELLED" | "REMOVED_FROM_MSG_DRAFT"; + uniqueId: string; + uploadedFilename: string; + waveform: string | undefined; + + // there are many more methods than just these but I didn't find them particularly useful + upload(): Promise; + cancel(): void; + delete(): Promise; + getSize(): number; + maybeConvertToWebP(): Promise; + removeFromMsgDraft(): void; +} diff --git a/packages/discord-types/src/modules/index.d.ts b/packages/discord-types/src/modules/index.d.ts new file mode 100644 index 00000000..fe0c1e24 --- /dev/null +++ b/packages/discord-types/src/modules/index.d.ts @@ -0,0 +1 @@ +export * from "./CloudUpload"; diff --git a/src/api/MessageEvents.ts b/src/api/MessageEvents.ts index 6e752c90..54d40000 100644 --- a/src/api/MessageEvents.ts +++ b/src/api/MessageEvents.ts @@ -17,7 +17,7 @@ */ import { Logger } from "@utils/Logger"; -import type { Channel, CustomEmoji, Message } from "@vencord/discord-types"; +import type { Channel, CloudUpload, CustomEmoji, Message } from "@vencord/discord-types"; import { MessageStore } from "@webpack/common"; import type { Promisable } from "type-fest"; @@ -30,30 +30,6 @@ export interface MessageObject { tts: boolean; } -export interface Upload { - classification: string; - currentSize: number; - description: string | null; - filename: string; - id: string; - isImage: boolean; - isVideo: boolean; - item: { - file: File; - platform: number; - }; - loaded: number; - mimeType: string; - preCompressionSize: number; - responseUrl: string; - sensitive: boolean; - showLargeMessageDialog: boolean; - spoiler: boolean; - status: "NOT_STARTED" | "STARTED" | "UPLOADING" | "ERROR" | "COMPLETED" | "CANCELLED"; - uniqueId: string; - uploadedFilename: string; -} - export interface MessageReplyOptions { messageReference: Message["messageReference"]; allowedMentions?: { @@ -64,7 +40,7 @@ export interface MessageReplyOptions { export interface MessageOptions { stickers?: string[]; - uploads?: Upload[]; + uploads?: CloudUpload[]; replyOptions: MessageReplyOptions; content: string; channel: Channel; diff --git a/src/api/Notices.ts b/src/api/Notices.tsx similarity index 73% rename from src/api/Notices.ts rename to src/api/Notices.tsx index 6d20087a..b4c44b88 100644 --- a/src/api/Notices.ts +++ b/src/api/Notices.tsx @@ -16,7 +16,10 @@ * along with this program. If not, see . */ +import ErrorBoundary from "@components/ErrorBoundary"; +import { isPrimitiveReactNode } from "@utils/react"; import { waitFor } from "@webpack"; +import { ReactNode } from "react"; let NoticesModule: any; waitFor(m => m.show && m.dismiss && !m.suppressAll, m => NoticesModule = m); @@ -36,7 +39,11 @@ export function nextNotice() { } } -export function showNotice(message: string, buttonText: string, onOkClick: () => void) { - noticesQueue.push(["GENERIC", message, buttonText, onOkClick]); +export function showNotice(message: ReactNode, buttonText: string, onOkClick: () => void) { + const notice = isPrimitiveReactNode(message) + ? message + : "Error Showing Notice"}>{message}; + + noticesQueue.push(["GENERIC", notice, buttonText, onOkClick]); if (!currentNotice) nextNotice(); } diff --git a/src/plugins/anonymiseFileNames/index.tsx b/src/plugins/anonymiseFileNames/index.tsx index 8237be8b..62cd7796 100644 --- a/src/plugins/anonymiseFileNames/index.tsx +++ b/src/plugins/anonymiseFileNames/index.tsx @@ -16,11 +16,11 @@ * along with this program. If not, see . */ -import { Upload } from "@api/MessageEvents"; import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { CloudUpload } from "@vencord/discord-types"; import { findByCodeLazy } from "@webpack"; import { useState } from "@webpack/common"; @@ -89,7 +89,7 @@ export default definePlugin({ }, ], - AnonymiseUploadButton: ErrorBoundary.wrap(({ upload }: { upload: Upload; }) => { + AnonymiseUploadButton: ErrorBoundary.wrap(({ upload }: { upload: CloudUpload; }) => { const [anonymise, setAnonymise] = useState(upload[ANONYMISE_UPLOAD_SYMBOL] ?? settings.store.anonymiseByDefault); function onToggleAnonymise() { @@ -110,7 +110,7 @@ export default definePlugin({ ); }, { noop: true }), - anonymise(upload: Upload) { + anonymise(upload: CloudUpload) { if ((upload[ANONYMISE_UPLOAD_SYMBOL] ?? settings.store.anonymiseByDefault) === false) { return; } diff --git a/src/plugins/previewMessage/index.tsx b/src/plugins/previewMessage/index.tsx index 1f64c714..6bc9d25f 100644 --- a/src/plugins/previewMessage/index.tsx +++ b/src/plugins/previewMessage/index.tsx @@ -20,7 +20,7 @@ import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons"; import { generateId, sendBotMessage } from "@api/Commands"; import { Devs } from "@utils/constants"; import definePlugin, { StartAt } from "@utils/types"; -import { MessageAttachment } from "@vencord/discord-types"; +import { CloudUpload, MessageAttachment } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { DraftStore, DraftType, SelectedChannelStore, UserStore, useStateFromStores } from "@webpack/common"; @@ -45,7 +45,7 @@ const getImageBox = (url: string): Promise<{ width: number, height: number; } | const getAttachments = async (channelId: string) => await Promise.all( UploadStore.getUploads(channelId, DraftType.ChannelMessage) - .map(async (upload: any) => { + .map(async (upload: CloudUpload) => { const { isImage, filename, spoiler, item: { file } } = upload; const url = URL.createObjectURL(file); const attachment: MessageAttachment = { @@ -53,7 +53,7 @@ const getAttachments = async (channelId: string) => filename: spoiler ? "SPOILER_" + filename : filename, // weird eh? if i give it the normal content type the preview doenst work content_type: undefined, - size: await upload.getSize(), + size: upload.getSize(), spoiler, // discord adds query params to the url, so we need to add a hash to prevent that url: url + "#", diff --git a/src/plugins/voiceMessages/index.tsx b/src/plugins/voiceMessages/index.tsx index 74f39ea3..ddb395e4 100644 --- a/src/plugins/voiceMessages/index.tsx +++ b/src/plugins/voiceMessages/index.tsx @@ -27,6 +27,8 @@ import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModa import { useAwaiter } from "@utils/react"; import definePlugin from "@utils/types"; import { chooseFile } from "@utils/web"; +import { CloudUpload as TCloudUpload } from "@vencord/discord-types"; +import { CloudUploadPlatform } from "@vencord/discord-types/enums"; import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; import { Button, Card, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common"; import { ComponentType } from "react"; @@ -37,7 +39,7 @@ import { cl } from "./utils"; import { VoicePreview } from "./VoicePreview"; import { VoiceRecorderWeb } from "./WebRecorder"; -const CloudUpload = findLazy(m => m.prototype?.trackUploadFinished); +const CloudUpload: typeof TCloudUpload = findLazy(m => m.prototype?.trackUploadFinished); const PendingReplyStore = findStoreLazy("PendingReplyStore"); const OptionClasses = findByPropsLazy("optionName", "optionIcon", "optionLabel"); @@ -92,8 +94,8 @@ function sendAudio(blob: Blob, meta: AudioMetadata) { const upload = new CloudUpload({ file: new File([blob], "voice-message.ogg", { type: "audio/ogg; codecs=opus" }), isThumbnail: false, - platform: 1, - }, channelId, false, 0); + platform: CloudUploadPlatform.WEB, + }, channelId); upload.on("complete", () => { RestAPI.post({ diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index f236943b..83713625 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -17,7 +17,7 @@ */ import { MessageObject } from "@api/MessageEvents"; -import { Channel, Guild, GuildFeatures, Message, User } from "@vencord/discord-types"; +import { Channel, CloudUpload, Guild, GuildFeatures, Message, User } from "@vencord/discord-types"; import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, IconUtils, InviteActions, MessageActions, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; import { Except } from "type-fest"; @@ -117,6 +117,20 @@ interface MessageOptions { replied_user: boolean; }; stickerIds: string[]; + attachmentsToUpload: CloudUpload[]; + poll: { + allow_multiselect: boolean; + answers: Array<{ + poll_media: { + text: string; + attachment_ids?: unknown; + emoji?: { name: string; id?: string; }; + }; + }>; + duration: number; + layout_type: number; + question: { text: string; }; + }; } export function sendMessage( diff --git a/src/utils/react.tsx b/src/utils/react.tsx index 146725c5..8963bdf5 100644 --- a/src/utils/react.tsx +++ b/src/utils/react.tsx @@ -17,7 +17,7 @@ */ import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common"; -import { ActionDispatch } from "react"; +import { ActionDispatch, ReactNode } from "react"; import { checkIntersecting } from "./misc"; @@ -25,6 +25,14 @@ export * from "./lazyReact"; export const NoopComponent = () => null; +/** + * Check if a React node is a primitive (string, number, bigint, boolean, undefined) + */ +export function isPrimitiveReactNode(node: ReactNode): boolean { + const t = typeof node; + return t === "string" || t === "number" || t === "bigint" || t === "boolean" || t === "undefined"; +} + /** * Check if an element is on screen * @param intersectOnly If `true`, will only update the state when the element comes into view