FavouriteAnything: Message components V2 support (#1175)

* FavouriteAnything: Add support for file message components

* FavouriteAnything: Further components v2 support

* FavouriteAnything: Improve CV2 handling
This commit is contained in:
Davri 2026-06-14 05:56:41 +02:00 • committed by GitHub
parent 4bb2a24977
commit a75c31773e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 53 additions and 9 deletions

View file

@ -279,7 +279,8 @@ export function AttachmentAccessory() {
const props: FavoriteButtonProps | null = useMemo(() => {
if (!attachment?.downloadUrl) return null;
const { originalItem, type, downloadUrl, width = 600, height = 400, srcIsAnimated } = attachment;
const { originalItem, type, downloadUrl, srcIsAnimated } = attachment;
const width = attachment.width || 600, height = attachment.height || 400;
// Do not render the custom accessory if the original attachment component already has a gif accessory
const isAnimated = ImageUtils.isAnimated({

View file

@ -7,7 +7,7 @@
import { Devs, EquicordDevs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import definePlugin from "@utils/types";
import { Embed } from "@vencord/discord-types";
import { Embed, MessageAttachment } from "@vencord/discord-types";
import { proxyLazyWebpack } from "@webpack";
import { React } from "@webpack/common";
import { ComponentType, ReactNode } from "react";
@ -15,8 +15,8 @@ import { ComponentType, ReactNode } from "react";
import { AttachmentAccessory, EmbedAccessory, FilePicker } from "./components";
import { SignedUrlsStore } from "./stores";
import managedStyle from "./style.css?managed";
import { AttachmentItem, EmbedComponent, ExpressionPickerTabProps, ExpressionPickerView, FavouriteItem, FavouriteItemFormat } from "./types";
import { getThumbnailUrl } from "./utils";
import { AttachmentItem, CV2Attachment, EmbedComponent, ExpressionPickerTabProps, ExpressionPickerView, FavouriteItem, FavouriteItemFormat } from "./types";
import { getThumbnailUrl, transformAttachment } from "./utils";
export const EmbedContext = proxyLazyWebpack(() => React.createContext<null | Embed>(null));
export const EmbedMosaicContext = proxyLazyWebpack(() => React.createContext<null | number>(null));
@ -70,6 +70,14 @@ export default definePlugin({
}
]
},
// COMPONENTS V2
{
find: "#{intl::ATTACHMENT_FILENAME_UNKNOWN}",
replacement: {
match: /(?<=case \i\.\i\.FILE:)return(\(0,\i\.jsx\)\(\i,\{\.\.\.(\i)\},(\i)\))/,
replace: "return $self.renderCV2File($1,$3,$2)"
}
},
// EXPRESSION PICKER
{
find: "#{intl::EXPRESSION_PICKER_CATEGORIES_A11Y_LABEL}",
@ -133,8 +141,30 @@ export default definePlugin({
renderFilePicker(activeView: ExpressionPickerView, onSelectGIF: (item: { url: string; }) => void) {
return activeView === ExpressionPickerView.FILES ? <FilePicker onSelectItem={onSelectGIF} /> : null;
},
renderAttachment(children: ReactNode, props: { item: AttachmentItem; }) {
return <AttachmentContext.Provider value={props.item}>{children}</AttachmentContext.Provider>;
renderAttachment(children: ReactNode, props: { item: AttachmentItem<MessageAttachment | { media: CV2Attachment; }>; }) {
const { item: { originalItem, ...rest } } = props;
// Regular media attachments and cv2 media attachments are structured differently
const raw: MessageAttachment =
"media" in originalItem
? {
...originalItem.media,
id: rest.uniqueId,
size: 0,
spoiler: rest.spoiler,
filename: (rest.spoiler ? "SPOILER_" : "") + rest.uniqueId,
content_type: originalItem.media.contentType,
proxy_url: originalItem.media.proxyUrl,
}
: originalItem;
return <AttachmentContext.Provider value={{ originalItem: raw, ...rest }}>{children}</AttachmentContext.Provider>;
},
renderCV2File(children: ReactNode, key: React.Key, props: { id: string; size: number; name: string; spoiler: boolean; file: CV2Attachment; }) {
const { id, size, name, spoiler, file } = props;
const raw = { ...file, size, filename: name, id, spoiler, content_type: file.contentType, proxy_url: file.proxyUrl };
return <AttachmentContext.Provider value={transformAttachment(raw)} key={key}>{children}</AttachmentContext.Provider>;
},
renderEmbed(this: EmbedComponent) {
return <EmbedContext.Provider value={this.props.embed}>{this.__render()}</EmbedContext.Provider>;

View file

@ -76,7 +76,7 @@ export interface EmbedComponent extends Component<{ embed: Embed; }> {
__render: () => ReactNode;
}
export interface AttachmentItem {
export interface AttachmentItem<TOriginal = MessageAttachment> {
contentType: string;
type: "IMAGE" | "VIDEO" | "CLIP" | "AUDIO" | "VISUAL_PLACEHOLDER" | "PLAINTEXT_PREVIEW" | "OTHER" | "INVALID";
width?: number;
@ -85,7 +85,17 @@ export interface AttachmentItem {
spoiler: boolean;
srcIsAnimated: boolean;
uniqueId: string;
originalItem: MessageAttachment;
originalItem: TOriginal;
}
export interface CV2Attachment {
url: string;
proxyUrl: string;
width: number;
height: number;
placeholder?: string;
contentType: string;
flags: number;
}
export enum FavouriteItemFormat {
@ -138,3 +148,5 @@ export type ResizeObserverHook = (
export interface ImageUtils {
isAnimated(image: { src: string; original?: string; animated: boolean; srcIsAnimated?: boolean; }): boolean;
}
export type AttachmentTransformer = (attachment: MessageAttachment, inlineAttachmentMedia?: boolean) => AttachmentItem;

View file

@ -18,7 +18,7 @@ import { Key } from "react";
import { JsonValue } from "type-fest";
import { base64ToUint8Array, uint8ArrayToBase64 } from "./polyfills";
import { CustomItemDef, CustomItemFormat, FavouriteItem, FavouriteItemFormat, ImageUtils as ImageUtils_, ItemsDef, ResizeObserverHook, UnfurledEmbedsResponse } from "./types";
import { AttachmentTransformer, CustomItemDef, CustomItemFormat, FavouriteItem, FavouriteItemFormat, ImageUtils as ImageUtils_, ItemsDef, ResizeObserverHook, UnfurledEmbedsResponse } from "./types";
const Native = VencordNative.pluginHelpers.FavouriteAnything as PluginNative<typeof import("./native")>;
@ -26,6 +26,7 @@ export const cl = classNameFactory("vc-favouriteAnything-");
export const useResizeObserver: ResizeObserverHook = findByCodeLazy("borderBoxSize", "blockSize", "inlineSize");
export const ImageUtils: ImageUtils_ = findByPropsLazy("isAnimated", "getFormatQuality");
export const transformAttachment: AttachmentTransformer = findByCodeLazy("return{uniqueId", ".IS_ANIMATED");
const defineItem = <const A, const B extends JsonValue>(item: CustomItemDef<A, B>) => item;
function defineItems<T extends Record<CustomItemFormat, CustomItemDef>>(def: ItemsDef<T>) {