From 9b3d39d0555727c77af8eb0df3491948eadcd8eb Mon Sep 17 00:00:00 2001 From: nemmy Date: Sun, 7 Jun 2026 13:13:33 -0400 Subject: [PATCH] update clipupload to support parsing from existing metadata (#1148) * update clipupload to support parsing from existing metadata * fuck you greptile * add self as author --- .../UploadClipFileModal.tsx | 29 +++++++++--- .../clipUpload.desktop/index.tsx | 2 +- .../clipUpload.desktop/native.ts | 45 ++++++++++++++++++- .../clipUpload.desktop/upload.ts | 20 ++++++++- src/utils/constants.ts | 4 ++ 5 files changed, 91 insertions(+), 9 deletions(-) diff --git a/src/equicordplugins/clipUpload.desktop/UploadClipFileModal.tsx b/src/equicordplugins/clipUpload.desktop/UploadClipFileModal.tsx index 961aad2ae..640199871 100644 --- a/src/equicordplugins/clipUpload.desktop/UploadClipFileModal.tsx +++ b/src/equicordplugins/clipUpload.desktop/UploadClipFileModal.tsx @@ -9,7 +9,7 @@ import { Flex } from "@components/Flex"; import { Heading } from "@components/Heading"; import { Paragraph } from "@components/Paragraph"; import type { RenderModalProps } from "@vencord/discord-types"; -import { Modal, openModal, showToast, Toasts, useEffect, useState } from "@webpack/common"; +import { Checkbox, Modal, openModal, showToast, Toasts, useEffect, useState } from "@webpack/common"; import { ApplicationField, BooleanField, DateTimeField, getDateTimeLocalValue, ParticipantField, TextField } from "./fields"; import { abortActiveClipUploads, type ClipMetadata, getClipCreatedAt, getClipTitleFromName, getDefaultClipTitle, getDefaultFileName, getErrorMessage, getParticipantIds, getString, isValidDate, pickClipFile, uploadClipFile } from "./upload"; @@ -37,6 +37,7 @@ function UploadClipFileModal({ modalProps, channelId, clip }: { modalProps: Rend const [message, setMessage] = useState(""); const [applicationId, setApplicationId] = useState(getString(clip?.applicationId) ?? ""); const [uploading, setUploading] = useState(false); + const [parseMetadata, setParseMetadata] = useState(false); const canUpload = Boolean(file && fileName.trim() && title.trim() && isValidDate(createdAt)) && !uploading; const notice = createdAt && !isValidDate(createdAt) @@ -46,18 +47,28 @@ function UploadClipFileModal({ modalProps, channelId, clip }: { modalProps: Rend useEffect(() => abortActiveClipUploads, []); async function chooseClipFile() { - let picked: File | null; + let result; try { - picked = await pickClipFile(); - if (!picked) return; + result = await pickClipFile(parseMetadata); + if (!result) return; } catch (error) { showToast(getErrorMessage(error), Toasts.Type.FAILURE); return; } + const { file: picked, metadata } = result; + setFile(picked); setFileName(name => name === defaultFileName ? picked.name : name); - setTitle(currentTitle => currentTitle || getClipTitleFromName(picked.name)); + + const appName = metadata?.applicationName; + const appUsers = metadata?.users; + const appId = metadata?.applicationId; + + setTitle(currentTitle => appName || currentTitle || getClipTitleFromName(picked.name)); + + if (appUsers?.length) setParticipants(current => current.length ? current : appUsers); + if (appId) setApplicationId(current => current || appId); } async function submit() { @@ -117,6 +128,14 @@ function UploadClipFileModal({ modalProps, channelId, clip }: { modalProps: Rend {file?.name ?? "No file selected"} + setParseMetadata(checked)} + disabled={uploading} + type="row" + > + Parse clip metadata from file + diff --git a/src/equicordplugins/clipUpload.desktop/index.tsx b/src/equicordplugins/clipUpload.desktop/index.tsx index d95a675dd..a8e03ce1c 100644 --- a/src/equicordplugins/clipUpload.desktop/index.tsx +++ b/src/equicordplugins/clipUpload.desktop/index.tsx @@ -38,7 +38,7 @@ const ctxMenuPatch: NavContextMenuPatchCallback = (children, props) => { export default definePlugin({ name: "ClipUpload", description: "Adds a button to upload a local video file as a Discord clip.", - authors: [EquicordDevs.qdnx, EquicordDevs.BachLe2000], + authors: [EquicordDevs.qdnx, EquicordDevs.BachLe2000, EquicordDevs.pandaptable], tags: ["Media", "Utility"], patches: [ diff --git a/src/equicordplugins/clipUpload.desktop/native.ts b/src/equicordplugins/clipUpload.desktop/native.ts index 62fa7aea3..152180922 100644 --- a/src/equicordplugins/clipUpload.desktop/native.ts +++ b/src/equicordplugins/clipUpload.desktop/native.ts @@ -16,6 +16,14 @@ interface TempEntry { tmpPath: string; } +interface RawClipAttachment { + id?: string; + applicationId?: string; + applicationName?: string; + users?: string[]; + version?: number; +} + const pendingTokens = new Map(); const tempEntries = new Map(); const CLIP_UPLOAD_DIR = join(DATA_DIR, "clipUpload"); @@ -25,10 +33,38 @@ const MIME_TYPES: Record = { ".m4v": "video/mp4", }; +const CLIP_FOOTER = Buffer.from([ + 0x75, 0x75, 0x69, 0x64, + 0xA1, 0xC8, 0x52, 0x99, 0x33, 0x46, 0x4D, 0xB8, 0x88, 0xF0, 0x83, 0xF5, 0x7A, 0x75, 0xA5, 0xEF, +]); +const CLIP_FOOTER_SIZE = CLIP_FOOTER.length; + function getMimeType(filePath: string): string { return MIME_TYPES[extname(filePath).toLowerCase()] ?? "application/octet-stream"; } +function stripClipFooter(data: Buffer): Buffer { + const idx = data.indexOf(CLIP_FOOTER); + return idx === -1 ? data : data.subarray(0, idx); +} + +async function parseClipMetadata(filePath: string): Promise { + try { + const buf = await readFile(filePath); + if (buf.length <= CLIP_FOOTER_SIZE) return null; + + const footerIdx = buf.indexOf(CLIP_FOOTER); + if (footerIdx === -1) return null; + + const jsonStr = buf.subarray(footerIdx + CLIP_FOOTER_SIZE).toString("utf-8"); + const parsed = JSON.parse(jsonStr); + if (parsed && typeof parsed === "object") return Array.isArray(parsed) ? parsed : [parsed]; + return null; + } catch { + return null; + } +} + export async function chooseVideoFile(_: IpcMainInvokeEvent): Promise<{ token: string; name: string; type: string; } | null> { try { const { filePaths, canceled } = await dialog.showOpenDialog({ @@ -63,7 +99,7 @@ export async function createTempVideoFile(_: IpcMainInvokeEvent, token: string): if (!ensureSafePath(tmpDir, basename(originalPath))) return null; await mkdir(tmpDir, { recursive: true }); - await writeFile(tmpPath, await readFile(originalPath)); + await writeFile(tmpPath, stripClipFooter(await readFile(originalPath))); const tmpToken = randomUUID(); tempEntries.set(tmpToken, { tmpDir, tmpPath }); @@ -97,3 +133,10 @@ export async function deleteTempVideoFile(_: IpcMainInvokeEvent, token: string): await rm(entry.tmpDir, { force: true, recursive: true }); } catch { } } + +export async function parseClipFileMetadata(_: IpcMainInvokeEvent, token: string): Promise { + const originalPath = pendingTokens.get(token); + if (!originalPath) return null; + + return parseClipMetadata(originalPath); +} diff --git a/src/equicordplugins/clipUpload.desktop/upload.ts b/src/equicordplugins/clipUpload.desktop/upload.ts index a28bd80b6..6124a7c9d 100644 --- a/src/equicordplugins/clipUpload.desktop/upload.ts +++ b/src/equicordplugins/clipUpload.desktop/upload.ts @@ -121,11 +121,27 @@ async function readStampedVideoFile(token: string, name: string, type: string) { } } -export async function pickClipFile() { +export interface ParsedClipMetadata { + id?: string; + applicationId?: string; + applicationName?: string; + users?: string[]; + version?: number; +} + +export async function pickClipFile(parseFileMetadata: boolean) { const picked = await Native.chooseVideoFile(); if (!picked) return null; - return readStampedVideoFile(picked.token, picked.name, picked.type); + let metadata: ParsedClipMetadata | null = null; + if (parseFileMetadata) { + const result = await Native.parseClipFileMetadata(picked.token); + metadata = result?.[0] ?? null; + } + + const file = await readStampedVideoFile(picked.token, picked.name, picked.type); + + return { file, metadata }; } function isCompatibleClipFile(file: File) { diff --git a/src/utils/constants.ts b/src/utils/constants.ts index a4bce2565..6ba611d34 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1341,6 +1341,10 @@ export const EquicordDevs = Object.freeze({ name: "BachLe2000", id: 624091967625625610n }, + pandaptable: { + name: "pandaptable", + id: 97153209843335168n + }, } satisfies Record); // iife so #__PURE__ works correctly