From 32e29715ade4dd5ccadd95c60711e7100830ad57 Mon Sep 17 00:00:00 2001 From: alina sireneva Date: Sun, 14 Apr 2024 05:47:30 +0300 Subject: [PATCH] fix(core): automatically add relevant file extension --- .../highlevel/methods/files/upload-file.ts | 16 +-- .../src/highlevel/utils/file-type.test.ts | 107 +++++++++--------- .../core/src/highlevel/utils/file-type.ts | 35 ++++++ 3 files changed, 98 insertions(+), 60 deletions(-) diff --git a/packages/core/src/highlevel/methods/files/upload-file.ts b/packages/core/src/highlevel/methods/files/upload-file.ts index 958076c8..eb74837d 100644 --- a/packages/core/src/highlevel/methods/files/upload-file.ts +++ b/packages/core/src/highlevel/methods/files/upload-file.ts @@ -5,7 +5,7 @@ import { MtArgumentError } from '../../../types/errors.js' import { randomLong } from '../../../utils/long-utils.js' import { ITelegramClient } from '../../client.types.js' import { UploadedFile, UploadFileLike } from '../../types/index.js' -import { guessFileMime } from '../../utils/file-type.js' +import { guessFileMime, MIME_TO_EXTENSION } from '../../utils/file-type.js' import { determinePartSize, isProbablyPlainText } from '../../utils/file-utils.js' import { bufferToStream, createChunkedReader, streamToBuffer } from '../../utils/stream-utils.js' @@ -105,7 +105,7 @@ export async function uploadFile( // normalize params let file = params.file let fileSize = -1 // unknown - let fileName = DEFAULT_FILE_NAME + let fileName = params.fileName let fileMime = params.fileMime const platform = getPlatform() @@ -178,9 +178,6 @@ export async function uploadFile( throw new MtArgumentError('Could not convert input `file` to stream!') } - // override file name and mime (if any) - if (params.fileName) fileName = params.fileName - // set file size if not automatically inferred if (fileSize === -1 && params.fileSize) fileSize = params.fileSize @@ -229,8 +226,6 @@ export async function uploadFile( connectionPoolSize, ) - // why is the file id generated by the client? - // isn't the server supposed to generate it and handle collisions? const fileId = randomLong() const stream = file @@ -309,6 +304,13 @@ export async function uploadFile( await Promise.all(Array.from({ length: poolSize }, uploadNextPart)) + if (fileName === undefined) { + // infer file extension from mime type. for some media types, + // telegram requires us to specify the file extension + const ext = MIME_TO_EXTENSION[fileMime!] + fileName = ext ? `${DEFAULT_FILE_NAME}.${ext}` : DEFAULT_FILE_NAME + } + let inputFile: tl.TypeInputFile if (isBig) { diff --git a/packages/core/src/highlevel/utils/file-type.test.ts b/packages/core/src/highlevel/utils/file-type.test.ts index e3b3703d..a30e4dfc 100644 --- a/packages/core/src/highlevel/utils/file-type.test.ts +++ b/packages/core/src/highlevel/utils/file-type.test.ts @@ -1,66 +1,67 @@ import { describe, expect, it } from 'vitest' import { getPlatform } from '../../platform.js' -import { guessFileMime } from './file-type.js' +import { guessFileMime, MIME_TO_EXTENSION } from './file-type.js' const p = getPlatform() describe('guessFileMime', () => { it.each([ - ['424d', 'image/bmp'], - ['4d5a', 'application/x-msdownload'], - ['1f9d', 'application/x-compress'], - ['1fa0', 'application/x-compress'], - ['1f8b', 'application/gzip'], - ['425a68', 'application/x-bzip2'], - ['494433', 'audio/mpeg'], - ['fffb', 'audio/mpeg'], - ['fff3', 'audio/mpeg'], - ['fff2', 'audio/mpeg'], - ['504b0304', 'application/zip'], - ['38425053', 'image/vnd.adobe.photoshop'], - ['7f454c46', 'application/x-elf'], - ['feedfacf', 'application/x-mach-binary'], - ['28b52ffd', 'application/zstd'], - ['664c6143', 'audio/x-flac'], - ['ffd8ffdb', 'image/jpeg'], - ['ffd8ffe0', 'image/jpeg'], - ['ffd8ffee', 'image/jpeg'], - ['ffd8ffe1', 'image/jpeg'], - ['4f676753', 'application/ogg'], - ['4f6767530000000000000000000000000000000000000000000000004f70757348656164', 'audio/ogg'], - ['4f67675300000000000000000000000000000000000000000000000001766964656f', 'video/ogg'], - ['4f6767530000000000000000000000000000000000000000000000007f464c4143', 'audio/ogg'], - ['4f67675300000000000000000000000000000000000000000000000001766f72626973', 'audio/ogg'], - ['255044462d', 'application/pdf'], - ['474946383761', 'image/gif'], - ['474946383961', 'image/gif'], - ['377abcaf271c', 'application/x-7z-compressed'], - ['89504e470d0a1a0a', 'image/png'], - ['526172211a0700', 'application/x-rar-compressed'], - ['526172211a0701', 'application/x-rar-compressed'], - ['000000006674797061766966', 'image/avif'], - ['000000006674797061766973', 'image/avif'], - ['00000000667479706d696631', 'image/heif'], - ['000000006674797068656963', 'image/heic'], - ['000000006674797068656978', 'image/heic'], - ['000000006674797068657663', 'image/heic-sequence'], - ['000000006674797068657678', 'image/heic-sequence'], - ['000000006674797071740000', 'video/quicktime'], - ['00000000667479704d345600', 'video/x-m4v'], - ['00000000667479704d345648', 'video/x-m4v'], - ['00000000667479704d345650', 'video/x-m4v'], - ['00000000667479704d345000', 'video/mp4'], - ['00000000667479704d344100', 'audio/x-m4a'], - ['00000000667479704d344200', 'audio/mp4'], - ['000000006674797046344100', 'audio/mp4'], - ['000000006674797046344200', 'audio/mp4'], - ['000000006674797063727800', 'image/x-canon-cr3'], - ['000000006674797033673200', 'video/3gpp2'], - ['000000006674797033670000', 'video/3gpp'], - ])('should detect %s as %s', (header, mime) => { + ['424d', 'image/bmp', 'bmp'], + ['4d5a', 'application/x-msdownload', 'exe'], + ['1f9d', 'application/x-compress', 'z'], + ['1fa0', 'application/x-compress', 'z'], + ['1f8b', 'application/gzip', 'gz'], + ['425a68', 'application/x-bzip2', 'bz2'], + ['494433', 'audio/mpeg', 'mp3'], + ['fffb', 'audio/mpeg', 'mp3'], + ['fff3', 'audio/mpeg', 'mp3'], + ['fff2', 'audio/mpeg', 'mp3'], + ['504b0304', 'application/zip', 'zip'], + ['38425053', 'image/vnd.adobe.photoshop', 'psd'], + ['7f454c46', 'application/x-elf', undefined], + ['feedfacf', 'application/x-mach-binary', undefined], + ['28b52ffd', 'application/zstd', 'zst'], + ['664c6143', 'audio/x-flac', 'flac'], + ['ffd8ffdb', 'image/jpeg', 'jpg'], + ['ffd8ffe0', 'image/jpeg', 'jpg'], + ['ffd8ffee', 'image/jpeg', 'jpg'], + ['ffd8ffe1', 'image/jpeg', 'jpg'], + ['4f676753', 'application/ogg', 'ogg'], + ['4f6767530000000000000000000000000000000000000000000000004f70757348656164', 'audio/ogg', 'ogg'], + ['4f67675300000000000000000000000000000000000000000000000001766964656f', 'video/ogg', 'ogv'], + ['4f6767530000000000000000000000000000000000000000000000007f464c4143', 'audio/ogg', 'ogg'], + ['4f67675300000000000000000000000000000000000000000000000001766f72626973', 'audio/ogg', 'ogg'], + ['255044462d', 'application/pdf', 'pdf'], + ['474946383761', 'image/gif', 'gif'], + ['474946383961', 'image/gif', 'gif'], + ['377abcaf271c', 'application/x-7z-compressed', '7z'], + ['89504e470d0a1a0a', 'image/png', 'png'], + ['526172211a0700', 'application/x-rar-compressed', 'rar'], + ['526172211a0701', 'application/x-rar-compressed', 'rar'], + ['000000006674797061766966', 'image/avif', 'avif'], + ['000000006674797061766973', 'image/avif', 'avif'], + ['00000000667479706d696631', 'image/heif', 'heif'], + ['000000006674797068656963', 'image/heic', 'heic'], + ['000000006674797068656978', 'image/heic', 'heic'], + ['000000006674797068657663', 'image/heic-sequence', 'heic'], + ['000000006674797068657678', 'image/heic-sequence', 'heic'], + ['000000006674797071740000', 'video/quicktime', 'mov'], + ['00000000667479704d345600', 'video/x-m4v', 'm4v'], + ['00000000667479704d345648', 'video/x-m4v', 'm4v'], + ['00000000667479704d345650', 'video/x-m4v', 'm4v'], + ['00000000667479704d345000', 'video/mp4', 'mp4'], + ['00000000667479704d344100', 'audio/x-m4a', 'm4a'], + ['00000000667479704d344200', 'audio/mp4', 'm4a'], + ['000000006674797046344100', 'audio/mp4', 'm4a'], + ['000000006674797046344200', 'audio/mp4', 'm4a'], + ['000000006674797063727800', 'image/x-canon-cr3', 'cr3'], + ['000000006674797033673200', 'video/3gpp2', '3g2'], + ['000000006674797033670000', 'video/3gpp', '3gp'], + ])('should detect %s as %s with %s extension', (header, mime, ext) => { header += '00'.repeat(16) expect(guessFileMime(p.hexDecode(header))).toEqual(mime) + expect(MIME_TO_EXTENSION[mime]).toEqual(ext) }) }) diff --git a/packages/core/src/highlevel/utils/file-type.ts b/packages/core/src/highlevel/utils/file-type.ts index 384c0e6e..551871aa 100644 --- a/packages/core/src/highlevel/utils/file-type.ts +++ b/packages/core/src/highlevel/utils/file-type.ts @@ -128,3 +128,38 @@ export function guessFileMime(chunk: Uint8Array): string | null { return null } + +export const MIME_TO_EXTENSION: Record = { + 'image/bmp': 'bmp', + 'application/x-msdownload': 'exe', + 'application/x-compress': 'z', + 'application/gzip': 'gz', + 'application/x-bzip2': 'bz2', + 'audio/mpeg': 'mp3', + 'application/zip': 'zip', + 'image/vnd.adobe.photoshop': 'psd', + 'application/zstd': 'zst', + 'audio/x-flac': 'flac', + 'image/jpeg': 'jpg', + 'audio/ogg': 'ogg', + 'video/ogg': 'ogv', + 'application/ogg': 'ogg', + 'application/pdf': 'pdf', + 'image/gif': 'gif', + 'application/x-7z-compressed': '7z', + 'image/png': 'png', + 'application/x-rar-compressed': 'rar', + 'image/avif': 'avif', + 'image/heif': 'heif', + 'image/heif-sequence': 'heif', + 'image/heic': 'heic', + 'image/heic-sequence': 'heic', + 'video/quicktime': 'mov', + 'video/x-m4v': 'm4v', + 'audio/x-m4a': 'm4a', + 'audio/mp4': 'm4a', + 'image/x-canon-cr3': 'cr3', + 'video/3gpp2': '3g2', + 'video/3gpp': '3gp', + 'video/mp4': 'mp4', +}