fix(core): automatically add relevant file extension

This commit is contained in:
alina 🌸 2024-04-14 05:47:30 +03:00
parent 3580575b82
commit 32e29715ad
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
3 changed files with 98 additions and 60 deletions

View file

@ -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) {

View file

@ -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)
})
})

View file

@ -128,3 +128,38 @@ export function guessFileMime(chunk: Uint8Array): string | null {
return null
}
export const MIME_TO_EXTENSION: Record<string, string | undefined> = {
'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',
}