Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion packages/bridge/src/dialogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3490,7 +3490,17 @@ export class DialogRpc {
}
const file = media.file
if (file._ !== 'inputFile' && file._ !== 'inputFileBig') throw new RpcError(400, 'FILE_ID_INVALID')
const kind = media._ === 'inputMediaUploadedPhoto' ? 'image' : 'file'
// A GIF or APNG that the client sends as an animation reaches the relay as
// an uploaded document, never as a photo, and Telegram models it as an
// image: the relay itself projects an animated image document back as
// `kind: 'image'` with the image mime. Sending it to the platform as a file
// produces a plain attachment whose name the user never chose. Only a
// document that the client marked as animated counts, so an explicit
// "send as file" and a video never take the image path.
const animatedImage = media._ === 'inputMediaUploadedDocument'
&& isAnimatedImageMime(media.mimeType)
&& media.attributes.some((item) => item._ === 'documentAttributeAnimated')
const kind = media._ === 'inputMediaUploadedPhoto' || animatedImage ? 'image' : 'file'
const attribute = media._ === 'inputMediaUploadedDocument'
? media.attributes.find((item) => item._ === 'documentAttributeFilename')
: undefined
Expand Down
57 changes: 57 additions & 0 deletions packages/bridge/src/media-send.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,63 @@ describe('media send streaming', () => {
} }])
})

it('relays an animated image document as an image instead of a file', async () => {
const { rpc, uploads, inputs, peerId } = await createHarness()
await uploads.savePart(session.platformSessionId, '92', 0, Uint8Array.of(71, 73, 70))
await rpc.sendMedia({
_: 'messages.sendMedia', peer: peer(peerId), randomId: Long.fromNumber(92), message: '',
media: {
_: 'inputMediaUploadedDocument', file: inputFile(92, 1, '8230e1c2.gif'),
mimeType: 'image/gif',
attributes: [
{ _: 'documentAttributeFilename', fileName: '8230e1c2.gif' },
{ _: 'documentAttributeAnimated' },
{ _: 'documentAttributeImageSize', w: 704, h: 1103 },
],
},
})
expect(inputs).toHaveLength(1)
expect(inputs[0]!.parts).toMatchObject([{ type: 'media', media: {
kind: 'image', name: '8230e1c2.gif', mimeType: 'image/gif',
} }])
})

it('keeps a document the client did not mark as animated on the file path', async () => {
const { rpc, uploads, inputs, peerId } = await createHarness()
await uploads.savePart(session.platformSessionId, '93', 0, Uint8Array.of(1, 2, 3))
await rpc.sendMedia({
_: 'messages.sendMedia', peer: peer(peerId), randomId: Long.fromNumber(93), message: '',
media: {
_: 'inputMediaUploadedDocument', file: inputFile(93, 1, 'kept.gif'), mimeType: 'image/gif',
attributes: [{ _: 'documentAttributeFilename', fileName: 'kept.gif' }],
},
})
expect(inputs).toHaveLength(1)
expect(inputs[0]!.parts).toMatchObject([{ type: 'media', media: {
kind: 'file', name: 'kept.gif', mimeType: 'image/gif',
} }])
})

it('keeps an animated video document on the file path', async () => {
const { rpc, uploads, inputs, peerId } = await createHarness()
await uploads.savePart(session.platformSessionId, '94', 0, Uint8Array.of(4, 5, 6))
await rpc.sendMedia({
_: 'messages.sendMedia', peer: peer(peerId), randomId: Long.fromNumber(94), message: '',
media: {
_: 'inputMediaUploadedDocument', file: inputFile(94, 1, 'clip.mp4'), mimeType: 'video/mp4',
attributes: [
{ _: 'documentAttributeFilename', fileName: 'clip.mp4' },
{ _: 'documentAttributeAnimated' },
{ _: 'documentAttributeVideo', w: 320, h: 240, duration: 2, supportsStreaming: true },
],
},
})
expect(inputs).toHaveLength(1)
expect(inputs[0]!.parts).toMatchObject([{ type: 'media', media: {
kind: 'file', name: 'clip.mp4', mimeType: 'video/mp4',
} }])
})

it('sends mixed image and file content through one platform call with independent streams', async () => {
const { rpc, uploads, inputs, consumed, progress, peerId } = await createHarness()
await uploads.savePart(session.platformSessionId, '1', 0, new Uint8Array([1, 2]))
Expand Down
Loading