improve various types (#3663)

Co-authored-by: V <vendicated@riseup.net>
Co-authored-by: John Davis <70701251+gobuster@users.noreply.github.com>
Co-authored-by: sadan4 <117494111+sadan4@users.noreply.github.com>
This commit is contained in:
V 2025-09-09 01:48:53 +02:00 committed by GitHub
parent 84957b0e88
commit c38aac23fd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 128 additions and 40 deletions

View file

@ -1,3 +1,4 @@
export * from "./channel";
export * from "./commands"; export * from "./commands";
export * from "./messages"; export * from "./messages";
export * from "./channel"; export * from "./misc";

View file

@ -0,0 +1,4 @@
export const enum CloudUploadPlatform {
REACT_NATIVE = 0,
WEB = 1,
}

View file

@ -4,6 +4,7 @@ export * from "./components";
export * from "./flux"; export * from "./flux";
export * from "./fluxEvents"; export * from "./fluxEvents";
export * from "./menu"; export * from "./menu";
export * from "./modules";
export * from "./stores"; export * from "./stores";
export * from "./utils"; export * from "./utils";
export * as Webpack from "../webpack"; export * as Webpack from "../webpack";

View file

@ -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<void>;
cancel(): void;
delete(): Promise<void>;
getSize(): number;
maybeConvertToWebP(): Promise<void>;
removeFromMsgDraft(): void;
}

View file

@ -0,0 +1 @@
export * from "./CloudUpload";

View file

@ -17,7 +17,7 @@
*/ */
import { Logger } from "@utils/Logger"; 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 { MessageStore } from "@webpack/common";
import type { Promisable } from "type-fest"; import type { Promisable } from "type-fest";
@ -30,30 +30,6 @@ export interface MessageObject {
tts: boolean; 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 { export interface MessageReplyOptions {
messageReference: Message["messageReference"]; messageReference: Message["messageReference"];
allowedMentions?: { allowedMentions?: {
@ -64,7 +40,7 @@ export interface MessageReplyOptions {
export interface MessageOptions { export interface MessageOptions {
stickers?: string[]; stickers?: string[];
uploads?: Upload[]; uploads?: CloudUpload[];
replyOptions: MessageReplyOptions; replyOptions: MessageReplyOptions;
content: string; content: string;
channel: Channel; channel: Channel;

View file

@ -16,7 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import ErrorBoundary from "@components/ErrorBoundary";
import { isPrimitiveReactNode } from "@utils/react";
import { waitFor } from "@webpack"; import { waitFor } from "@webpack";
import { ReactNode } from "react";
let NoticesModule: any; let NoticesModule: any;
waitFor(m => m.show && m.dismiss && !m.suppressAll, m => NoticesModule = m); 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) { export function showNotice(message: ReactNode, buttonText: string, onOkClick: () => void) {
noticesQueue.push(["GENERIC", message, buttonText, onOkClick]); const notice = isPrimitiveReactNode(message)
? message
: <ErrorBoundary fallback={() => "Error Showing Notice"}>{message}</ErrorBoundary>;
noticesQueue.push(["GENERIC", notice, buttonText, onOkClick]);
if (!currentNotice) nextNotice(); if (!currentNotice) nextNotice();
} }

View file

@ -16,11 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Upload } from "@api/MessageEvents";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { CloudUpload } from "@vencord/discord-types";
import { findByCodeLazy } from "@webpack"; import { findByCodeLazy } from "@webpack";
import { useState } from "@webpack/common"; 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); const [anonymise, setAnonymise] = useState(upload[ANONYMISE_UPLOAD_SYMBOL] ?? settings.store.anonymiseByDefault);
function onToggleAnonymise() { function onToggleAnonymise() {
@ -110,7 +110,7 @@ export default definePlugin({
); );
}, { noop: true }), }, { noop: true }),
anonymise(upload: Upload) { anonymise(upload: CloudUpload) {
if ((upload[ANONYMISE_UPLOAD_SYMBOL] ?? settings.store.anonymiseByDefault) === false) { if ((upload[ANONYMISE_UPLOAD_SYMBOL] ?? settings.store.anonymiseByDefault) === false) {
return; return;
} }

View file

@ -20,7 +20,7 @@ import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
import { generateId, sendBotMessage } from "@api/Commands"; import { generateId, sendBotMessage } from "@api/Commands";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { StartAt } from "@utils/types"; import definePlugin, { StartAt } from "@utils/types";
import { MessageAttachment } from "@vencord/discord-types"; import { CloudUpload, MessageAttachment } from "@vencord/discord-types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { DraftStore, DraftType, SelectedChannelStore, UserStore, useStateFromStores } from "@webpack/common"; 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) => const getAttachments = async (channelId: string) =>
await Promise.all( await Promise.all(
UploadStore.getUploads(channelId, DraftType.ChannelMessage) UploadStore.getUploads(channelId, DraftType.ChannelMessage)
.map(async (upload: any) => { .map(async (upload: CloudUpload) => {
const { isImage, filename, spoiler, item: { file } } = upload; const { isImage, filename, spoiler, item: { file } } = upload;
const url = URL.createObjectURL(file); const url = URL.createObjectURL(file);
const attachment: MessageAttachment = { const attachment: MessageAttachment = {
@ -53,7 +53,7 @@ const getAttachments = async (channelId: string) =>
filename: spoiler ? "SPOILER_" + filename : filename, filename: spoiler ? "SPOILER_" + filename : filename,
// weird eh? if i give it the normal content type the preview doenst work // weird eh? if i give it the normal content type the preview doenst work
content_type: undefined, content_type: undefined,
size: await upload.getSize(), size: upload.getSize(),
spoiler, spoiler,
// discord adds query params to the url, so we need to add a hash to prevent that // discord adds query params to the url, so we need to add a hash to prevent that
url: url + "#", url: url + "#",

View file

@ -27,6 +27,8 @@ import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModa
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { chooseFile } from "@utils/web"; 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 { 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 { Button, Card, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common";
import { ComponentType } from "react"; import { ComponentType } from "react";
@ -37,7 +39,7 @@ import { cl } from "./utils";
import { VoicePreview } from "./VoicePreview"; import { VoicePreview } from "./VoicePreview";
import { VoiceRecorderWeb } from "./WebRecorder"; 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 PendingReplyStore = findStoreLazy("PendingReplyStore");
const OptionClasses = findByPropsLazy("optionName", "optionIcon", "optionLabel"); const OptionClasses = findByPropsLazy("optionName", "optionIcon", "optionLabel");
@ -92,8 +94,8 @@ function sendAudio(blob: Blob, meta: AudioMetadata) {
const upload = new CloudUpload({ const upload = new CloudUpload({
file: new File([blob], "voice-message.ogg", { type: "audio/ogg; codecs=opus" }), file: new File([blob], "voice-message.ogg", { type: "audio/ogg; codecs=opus" }),
isThumbnail: false, isThumbnail: false,
platform: 1, platform: CloudUploadPlatform.WEB,
}, channelId, false, 0); }, channelId);
upload.on("complete", () => { upload.on("complete", () => {
RestAPI.post({ RestAPI.post({

View file

@ -17,7 +17,7 @@
*/ */
import { MessageObject } from "@api/MessageEvents"; 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 { 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"; import { Except } from "type-fest";
@ -117,6 +117,20 @@ interface MessageOptions {
replied_user: boolean; replied_user: boolean;
}; };
stickerIds: string[]; 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( export function sendMessage(

View file

@ -17,7 +17,7 @@
*/ */
import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common"; import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common";
import { ActionDispatch } from "react"; import { ActionDispatch, ReactNode } from "react";
import { checkIntersecting } from "./misc"; import { checkIntersecting } from "./misc";
@ -25,6 +25,14 @@ export * from "./lazyReact";
export const NoopComponent = () => null; 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 * Check if an element is on screen
* @param intersectOnly If `true`, will only update the state when the element comes into view * @param intersectOnly If `true`, will only update the state when the element comes into view