mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
fix(api): accept any image or video attachment as embed media (#2901)
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import type {AttachmentID, ChannelID} from '@app/api/BrandedTypes';
|
||||
import type {AttachmentRequestData} from '@app/api/channel/AttachmentDTOs';
|
||||
import type {RichEmbedMediaWithMetadata} from '@app/api/channel/EmbedTypes';
|
||||
import {makeAttachmentCdnUrl} from '@app/api/channel/services/message/MessageHelpers';
|
||||
import {getContentType, makeAttachmentCdnUrl} from '@app/api/channel/services/message/MessageHelpers';
|
||||
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
|
||||
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
|
||||
import type {RichEmbedRequest} from '@fluxer/schema/src/domains/message/MessageRequestSchemas';
|
||||
@@ -26,7 +26,10 @@ interface RichEmbedRequestWithMetadata extends Omit<RichEmbedRequest, 'image' |
|
||||
thumbnail?: RichEmbedMediaWithMetadata | null;
|
||||
}
|
||||
|
||||
const SUPPORTED_IMAGE_EXTENSIONS = new Set(['png', 'jpg', 'jpeg', 'webp', 'gif']);
|
||||
function isEmbeddableMediaType(contentType: string): boolean {
|
||||
const normalized = contentType.toLowerCase();
|
||||
return normalized.startsWith('image/') || normalized.startsWith('video/');
|
||||
}
|
||||
|
||||
export class MessageEmbedAttachmentResolver {
|
||||
validateAttachmentReferences(params: {
|
||||
@@ -69,8 +72,7 @@ export class MessageEmbedAttachmentResolver {
|
||||
{filename},
|
||||
);
|
||||
}
|
||||
const extension = filename.split('.').pop()?.toLowerCase();
|
||||
if (!extension || !SUPPORTED_IMAGE_EXTENSIONS.has(extension)) {
|
||||
if (!isEmbeddableMediaType(getContentType(filename))) {
|
||||
throw InputValidationError.fromCode(
|
||||
`embeds[${embedIndex}].${field}`,
|
||||
ValidationErrorCodes.ATTACHMENT_MUST_BE_IMAGE,
|
||||
@@ -137,8 +139,7 @@ export class MessageEmbedAttachmentResolver {
|
||||
if (!attachmentData) {
|
||||
throw InputValidationError.fromCode(field, ValidationErrorCodes.REFERENCED_ATTACHMENT_NOT_FOUND, {filename});
|
||||
}
|
||||
const extension = filename.split('.').pop()?.toLowerCase();
|
||||
if (!extension || !SUPPORTED_IMAGE_EXTENSIONS.has(extension)) {
|
||||
if (!isEmbeddableMediaType(attachmentData.metadata.content_type)) {
|
||||
throw InputValidationError.fromCode(field, ValidationErrorCodes.ATTACHMENT_MUST_BE_IMAGE, {filename});
|
||||
}
|
||||
return attachmentData;
|
||||
|
||||
@@ -512,6 +512,34 @@ describe('Embed Attachment URL Resolution', () => {
|
||||
expect(json.embeds).toHaveLength(1);
|
||||
expect(json.embeds![0].image?.url).not.toContain('attachment://');
|
||||
});
|
||||
it('should accept image and video attachments beyond the legacy image extensions', async () => {
|
||||
const account = await createTestAccount(harness);
|
||||
const guild = await createGuild(harness, account.token, 'Media Type Guild');
|
||||
const channel = await createChannel(harness, account.token, guild.id, 'test-channel');
|
||||
const channelId = guild.system_channel_id ?? channel.id;
|
||||
const payload = {
|
||||
content: 'Test with jxl and mp4 embed media',
|
||||
attachments: [
|
||||
{id: 0, filename: 'photo.jxl'},
|
||||
{id: 1, filename: 'clip.mp4'},
|
||||
],
|
||||
embeds: [
|
||||
{
|
||||
title: 'Media Embed',
|
||||
image: {url: 'attachment://clip.mp4'},
|
||||
thumbnail: {url: 'attachment://photo.jxl'},
|
||||
},
|
||||
],
|
||||
};
|
||||
const {response, json} = await sendMessageWithAttachments(harness, account.token, channelId, payload, [
|
||||
{index: 0, filename: 'photo.jxl', data: Buffer.from('jxl bytes')},
|
||||
{index: 1, filename: 'clip.mp4', data: Buffer.from('mp4 bytes')},
|
||||
]);
|
||||
expect(response.status).toBe(200);
|
||||
expect(json.embeds).toHaveLength(1);
|
||||
expect(json.embeds![0].image?.url).not.toContain('attachment://');
|
||||
expect(json.embeds![0].thumbnail?.url).not.toContain('attachment://');
|
||||
});
|
||||
});
|
||||
describe('Multiple Embeds and Files', () => {
|
||||
it('should handle multiple embeds with different URL types', async () => {
|
||||
|
||||
Reference in New Issue
Block a user