MoreStickers: PromptToUpload Setting

This commit is contained in:
thororen1234 2026-02-11 17:38:02 -05:00
parent 6ce8a8654b
commit e6396f8553
No known key found for this signature in database
8 changed files with 84 additions and 109 deletions

View file

@ -55,8 +55,8 @@ const extensionToMime: Record<string, string> = {
"mkv": "video/x-matroska"
};
export function getMimeFromExtension(ext: string): string {
return extensionToMime[ext.toLowerCase()] || "application/octet-stream";
export function getMimeFromExtension(ext?: string): string {
return extensionToMime[ext?.toLowerCase() ?? ""] || "application/octet-stream";
}
export async function getExtensionFromBytes(blob: Blob): Promise<string | undefined> {
@ -113,7 +113,7 @@ export function isSupported(url: string): boolean {
return ext ? supportedExtensions.includes(ext) : false;
}
export function getMediaUrl(props: { src?: string; href?: string; itemSrc?: string; itemHref?: string; target?: any }): string | null {
export function getMediaUrl(props: { src?: string; href?: string; itemSrc?: string; itemHref?: string; target?: any; }): string | null {
const url = props.src || props.href || props.itemSrc || props.itemHref;
if (url && isSupported(url)) return url;

View file

@ -18,7 +18,12 @@ import { getStickerPack, getStickerPackMetas } from "./stickers";
import { StickerPack, StickerPackMeta } from "./types";
import { cl, FFmpegStateContext, loadFFmpeg } from "./utils";
const settings = definePluginSettings({
export const settings = definePluginSettings({
promptToUpload: {
type: OptionType.BOOLEAN,
description: "Inserts the sticker into your chatbar instead of sending immediately",
default: false
},
packs: {
type: OptionType.COMPONENT,
description: "Packs",
@ -72,13 +77,6 @@ export default definePlugin({
}
]
},
{
find: '==="remove_text"',
replacement: {
match: /,\i\.insertText=\i=>{[\w ;]*?1===\i\.length&&.+?==="remove_text"/,
replace: ",$self.textEditor=arguments[0]$&"
}
}
],
stickerButton({
innerClassName,

View file

@ -4,17 +4,23 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { getMimeFromExtension } from "@equicordplugins/fileUpload/utils/getMediaUrl";
import { FFmpeg } from "@ffmpeg/ffmpeg";
import { fetchFile } from "@ffmpeg/util";
import { findByPropsLazy } from "@webpack";
import { ChannelStore, UploadHandler } from "@webpack/common";
import { insertTextIntoChatInputBox, MessageOptions } from "@utils/discord";
import { CloudUploadPlatform } from "@vencord/discord-types/enums";
import { ChannelStore, CloudUploader, Constants, DraftStore, FluxDispatcher, MessageActions, PendingReplyStore, RestAPI, showToast, SnowflakeUtils, Toasts, UploadHandler } from "@webpack/common";
import moreStickers from ".";
import { settings } from ".";
import { FFmpegState, Sticker } from "./types";
const PendingReplyStore = findByPropsLazy("getPendingReply");
const MessageUtils = findByPropsLazy("sendMessage");
const DraftStore = findByPropsLazy("getDraft", "getState");
type SendStickerOptions = {
channelId: string;
sticker: Sticker;
ctrlKey: boolean;
shiftKey: boolean;
ffmpegState?: FFmpegState;
};
export const ffmpeg = new FFmpeg();
@ -97,88 +103,64 @@ async function toGIF(url: string, ffmpeg: FFmpeg): Promise<File> {
});
}
export async function sendSticker({
channelId,
sticker,
sendAsLink,
ctrlKey,
shiftKey,
ffmpegState
}: { channelId: string; sticker: Sticker; sendAsLink?: boolean; ctrlKey: boolean; shiftKey: boolean; ffmpegState?: FFmpegState; }) {
export async function sendSticker({ channelId, sticker, ctrlKey, shiftKey, ffmpegState }: SendStickerOptions) {
const reply = PendingReplyStore.getPendingReply(channelId);
let content = DraftStore.getDraft(channelId, 0);
let options: Partial<MessageOptions> = {};
let file: File;
let messageContent = "";
const { textEditor } = moreStickers;
if (DraftStore) {
messageContent = DraftStore.getDraft(channelId, 0);
if (reply) {
FluxDispatcher.dispatch({ type: "DELETE_PENDING_REPLY", channelId });
options = MessageActions.getSendMessageOptionsForReply(reply);
}
let messageOptions = {};
if (PendingReplyStore) {
const pendingReply = PendingReplyStore.getPendingReply(channelId);
if (pendingReply) {
messageOptions = MessageUtils.getSendMessageOptionsForReply(pendingReply);
}
if (shiftKey) {
if (!content.endsWith(" ") && !content.endsWith("\n")) content = " ";
content += sticker.image;
return ctrlKey
? insertTextIntoChatInputBox(content)
: MessageActions._sendMessage(channelId, { content: sticker.image }, options);
}
if ((ctrlKey || !sendAsLink) && !shiftKey) {
let file: File | null = null;
if (sticker?.isAnimated) {
if (!ffmpegState) {
throw new Error("FFmpeg state is not provided");
}
if (!ffmpegState?.ffmpeg) {
throw new Error("FFmpeg is not provided");
}
if (!ffmpegState?.isLoaded) {
throw new Error("FFmpeg is not loaded");
}
file = await toGIF(sticker.image, ffmpegState.ffmpeg);
}
else {
const url = new URL(sticker.image);
url.searchParams.set("t", Date.now().toString()); // To prevent caching, in order to avoid CORS bug in Chrome
const response = await fetch(sticker.image);
const orgImageUrl = URL.createObjectURL(await response.blob());
const processedImage = await resizeImage(orgImageUrl);
const filename = sticker.filename ?? (new URL(sticker.image)).pathname.split("/").pop();
let mimeType = "image/png";
switch (filename?.split(".").pop()?.toLowerCase()) {
case "jpg":
case "jpeg":
mimeType = "image/jpeg";
break;
case "gif":
mimeType = "image/gif";
break;
case "webp":
mimeType = "image/webp";
break;
case "svg":
mimeType = "image/svg+xml";
break;
}
file = new File([processedImage], filename!, { type: mimeType });
}
UploadHandler.promptToUpload([file], ChannelStore.getChannel(channelId), 0);
return;
} else if (shiftKey) {
if (!messageContent.endsWith(" ") || !messageContent.endsWith("\n")) messageContent += " ";
messageContent += sticker.image;
if (ctrlKey && textEditor && textEditor.insertText && typeof textEditor.insertText === "function") {
textEditor.insertText(messageContent);
} else {
MessageUtils._sendMessage(channelId, {
content: sticker.image
}, messageOptions || {});
}
if (sticker?.isAnimated) {
if (!ffmpegState?.ffmpeg || !ffmpegState.isLoaded) throw new Error("FFmpeg not ready");
file = await toGIF(sticker.image, ffmpegState.ffmpeg);
} else {
MessageUtils._sendMessage(channelId, {
content: `${messageContent} ${sticker.image}`.trim()
}, messageOptions || {});
const res = await fetch(sticker.image);
const blobUrl = URL.createObjectURL(await res.blob());
const processed = await resizeImage(blobUrl);
const filename = sticker.filename ?? new URL(sticker.image).pathname.split("/").pop()!;
const mimeType = getMimeFromExtension(filename.split(".").pop());
file = new File([processed], filename, { type: mimeType });
}
if (settings.store.promptToUpload || content) return UploadHandler.promptToUpload([file], ChannelStore.getChannel(channelId), 0);
const upload = new CloudUploader({ file, platform: CloudUploadPlatform.WEB }, channelId);
upload.on("complete", () => {
RestAPI.post({
url: Constants.Endpoints.MESSAGES(channelId),
body: {
flags: 0,
channel_id: channelId,
content: "",
nonce: SnowflakeUtils.fromTimestamp(Date.now()),
sticker_ids: [],
type: 0,
attachments: [{
id: "0",
filename: upload.filename,
uploaded_filename: upload.uploadedFilename,
}],
message_reference: reply ? options?.messageReference : null,
}
});
});
upload.on("error", () => showToast("Failed to upload sticker", Toasts.Type.FAILURE));
upload.upload();
}

View file

@ -6,18 +6,14 @@
import * as DataStore from "@api/DataStore";
import { Logger } from "@utils/Logger";
import { CloudUpload, Message } from "@vencord/discord-types";
import { Message } from "@vencord/discord-types";
import { CloudUploadPlatform } from "@vencord/discord-types/enums";
import { findLazy } from "@webpack";
import { ChannelStore, Constants, FluxDispatcher, GuildStore, IconUtils, MessageActions, MessageStore, RestAPI, showToast, SnowflakeUtils, Toasts, UserStore } from "@webpack/common";
import { ChannelStore, CloudUploader, Constants, FluxDispatcher, GuildStore, IconUtils, MessageActions, MessageStore, RestAPI, showToast, SnowflakeUtils, Toasts, UserStore } from "@webpack/common";
import { settings } from ".";
import { PhantomMessageData, ScheduledAttachment, ScheduledMessage, ScheduledReaction } from "./types";
const logger = new Logger("ScheduledMessages");
const CloudUploader = findLazy(m => m.prototype?.trackUploadFinished) as typeof CloudUpload;
const STORAGE_KEY = "ScheduledMessages_queue";
let scheduledMessages: ScheduledMessage[] = [];
@ -297,7 +293,7 @@ async function uploadAttachment(channelId: string, att: ScheduledAttachment): Pr
const file = new File([bytes], att.filename, { type: att.type });
const upload = new CloudUploader({ file, platform: CloudUploadPlatform.WEB }, channelId);
upload.on("complete", () => resolve({ id: "", filename: upload.filename, uploaded_filename: upload.uploadedFilename }));
upload.on("complete", () => resolve({ id: "0", filename: upload.filename, uploaded_filename: upload.uploadedFilename }));
upload.on("error", () => resolve(null));
upload.upload();
});

View file

@ -33,10 +33,8 @@ import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModa
import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
import { chooseFile } from "@utils/web";
import { CloudUpload } from "@vencord/discord-types";
import { CloudUploadPlatform } from "@vencord/discord-types/enums";
import { findLazy, findStoreLazy } from "@webpack";
import { Button, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common";
import { Button, CloudUploader, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, PendingReplyStore, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common";
import { VoiceRecorderDesktop } from "./components/DesktopRecorder";
import { VoicePreview } from "./components/VoicePreview";
@ -56,9 +54,6 @@ const EMPTY_META: AudioMetadata = {
duration: DEFAULT_DURATION,
};
const CloudUploadConstructor = findLazy(m => m.prototype?.trackUploadFinished) as typeof CloudUpload;
const PendingReplyStore = findStoreLazy("PendingReplyStore");
export const cl = classNameFactory("vc-vmsg-");
export type VoiceRecorder = React.ComponentType<{
@ -108,7 +103,7 @@ function sendAudio(blob: Blob, meta: AudioMetadata) {
const reply = PendingReplyStore.getPendingReply(channelId);
if (reply) FluxDispatcher.dispatch({ type: "DELETE_PENDING_REPLY", channelId });
const upload = new CloudUploadConstructor({
const upload = new CloudUploader({
file: new File([blob], "voice-message.ogg", { type: "audio/ogg; codecs=opus" }),
isThumbnail: false,
platform: CloudUploadPlatform.WEB,

View file

@ -124,7 +124,7 @@ export async function copyWithToast(text: string, toastMessage = "Copied to clip
});
}
interface MessageOptions {
export interface MessageOptions {
messageReference: Message["messageReference"];
allowedMentions: {
parse: string[];

View file

@ -82,6 +82,7 @@ export let PopoutWindowStore: t.PopoutWindowStore;
export let ApplicationCommandIndexStore: t.ApplicationCommandIndexStore;
export let EditMessageStore: t.EditMessageStore;
export let QuestStore: t.QuestStore;
export let PendingReplyStore: GenericStore;
/**
* @see jsdoc of {@link t.useStateFromStores}
@ -135,6 +136,7 @@ waitForStore("SoundboardStore", m => SoundboardStore = m);
waitForStore("PopoutWindowStore", m => PopoutWindowStore = m);
waitForStore("ApplicationCommandIndexStore", m => ApplicationCommandIndexStore = m);
waitForStore("EditMessageStore", m => EditMessageStore = m);
waitForStore("PendingReplyStore", m => PendingReplyStore = m);
waitForStore("QuestStore", m => QuestStore = m);
waitForStore("ThemeStore", m => {
ThemeStore = m;

View file

@ -226,3 +226,5 @@ export const DateUtils: t.DateUtils = mapMangledModuleLazy("millisecondsInUnit:"
export const MessageTypeSets: t.MessageTypeSets = findByPropsLazy("REPLYABLE", "FORWARDABLE");
export const fetchApplicationsRPC = findByCodeLazy('"Invalid Origin"', ".application");
export const CloudUploader = findLazy(m => m.prototype?.trackUploadFinished) as typeof t.CloudUpload;