From e0f4b0d7b51b6fa50ea1ab37bc24a05b40a8ebee Mon Sep 17 00:00:00 2001 From: Alina Sireneva Date: Thu, 2 Nov 2023 19:40:14 +0300 Subject: [PATCH] build(deps): removed @types/node and file-type --- package.json | 1 - packages/client/package.json | 8 +- .../src/methods/files/download-file.web.ts | 5 + .../client/src/methods/files/upload-file.ts | 12 +- packages/client/src/utils/file-type.ts | 138 ++++++++++++++++++ packages/core/package.json | 1 - pnpm-lock.yaml | 56 +------ 7 files changed, 153 insertions(+), 68 deletions(-) create mode 100644 packages/client/src/methods/files/download-file.web.ts create mode 100644 packages/client/src/utils/file-type.ts diff --git a/package.json b/package.json index 55ccd02d..fefca0b1 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "@types/chai": "4.3.5", "@types/chai-spies": "^1.0.4", "@types/mocha": "10.0.1", - "@types/node": "18.16.0", "@types/node-forge": "1.3.2", "@types/ws": "8.5.4", "@typescript-eslint/eslint-plugin": "6.4.0", diff --git a/packages/client/package.json b/packages/client/package.json index 19c73bf9..5da86d74 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -32,12 +32,12 @@ }, "browser": { "./cjs/methods/files/_platform.js": "./cjs/methods/files/_platform.web.js", - "./esm/methods/files/_platform.js": "./esm/methods/files/_platform.web.js" + "./esm/methods/files/_platform.js": "./esm/methods/files/_platform.web.js", + "./cjs/methods/files/download-file.js": "./cjs/methods/files/download-file.web.js", + "./esm/methods/files/download-file.js": "./esm/methods/files/download-file.web.js" }, "dependencies": { - "@types/node": "18.16.0", "@mtcute/core": "workspace:^", - "@mtcute/file-id": "workspace:^", - "file-type": "16.5.4" + "@mtcute/file-id": "workspace:^" } } diff --git a/packages/client/src/methods/files/download-file.web.ts b/packages/client/src/methods/files/download-file.web.ts new file mode 100644 index 00000000..0855eca7 --- /dev/null +++ b/packages/client/src/methods/files/download-file.web.ts @@ -0,0 +1,5 @@ +import { MtUnsupportedError } from '@mtcute/core' + +export function downloadToFile() { + throw new MtUnsupportedError('Downloading to file is only supported in NodeJS') +} diff --git a/packages/client/src/methods/files/upload-file.ts b/packages/client/src/methods/files/upload-file.ts index 09724eee..19ed541f 100644 --- a/packages/client/src/methods/files/upload-file.ts +++ b/packages/client/src/methods/files/upload-file.ts @@ -1,15 +1,12 @@ -import fileType from 'file-type' - import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core' import { randomLong } from '@mtcute/core/utils.js' import { UploadedFile, UploadFileLike } from '../../types/index.js' +import { guessFileMime } from '../../utils/file-type.js' import { determinePartSize, isProbablyPlainText } from '../../utils/file-utils.js' import { bufferToStream, createChunkedReader, streamToBuffer } from '../../utils/stream-utils.js' import { _createFileStream, _extractFileStreamMeta, _handleNodeStream, _isFileStream } from './_platform.js' -const { fromBuffer: fileTypeFromBuffer } = fileType - const OVERRIDE_MIME: Record = { // tg doesn't interpret `audio/opus` files as voice messages for some reason 'audio/opus': 'audio/ogg', @@ -252,10 +249,11 @@ export async function uploadFile( } if (thisIdx === 0 && fileMime === undefined) { - const fileType = await fileTypeFromBuffer(part) - fileMime = fileType?.mime + const mime = guessFileMime(part) - if (!fileMime) { + if (mime) { + fileMime = mime + } else { // either plain text or random binary gibberish // make an assumption based on the first 8 bytes // if all 8 bytes are printable ASCII characters, diff --git a/packages/client/src/utils/file-type.ts b/packages/client/src/utils/file-type.ts new file mode 100644 index 00000000..6ea46a35 --- /dev/null +++ b/packages/client/src/utils/file-type.ts @@ -0,0 +1,138 @@ +export function guessFileMime(chunk: Uint8Array): string | null { + if (chunk.length < 12) return null + + // 2-byte magic numbers + const b0 = chunk[0] + const b1 = chunk[1] + if (b0 === 0x42 && b1 === 0x4d) return 'image/bmp' + if (b0 === 0x4d && b1 === 0x5a) return 'application/x-msdownload' + if (b0 === 0x1f && (b1 === 0x9d || b1 === 0xa0)) return 'application/x-compress' + if (b0 === 0x1f && b1 === 0x8b) return 'application/gzip' + + // 3-byte magic numbers + const b2 = chunk[2] + if (b0 === 0x42 && b1 === 0x5a && b2 === 0x68) return 'application/x-bzip2' + if (b0 === 0x49 && b1 === 0x44 && b2 === 0x33) return 'audio/mpeg' // ID3v2 + if (b0 === 0xff && (b1 === 0xfb || b1 === 0xf3 || b1 === 0xf2)) return 'audio/mpeg' + + // 4-byte magic numbers + const b3 = chunk[3] + if (b0 === 0x50 && b1 === 0x4b && b2 === 0x03 && b3 === 0x04) return 'application/zip' + if (b0 === 0x38 && b1 === 0x42 && b2 === 0x50 && b3 === 0x53) return 'image/vnd.adobe.photoshop' + if (b0 === 0x7f && b1 === 0x45 && b2 === 0x4c && b3 === 0x46) return 'application/x-elf' + if (b0 === 0xfe && b1 === 0xed && b2 === 0xfa && b3 === 0xcf) return 'application/x-mach-binary' + if (b0 === 0x28 && b1 === 0xb5 && b2 === 0x2f && b3 === 0xfd) return 'application/zstd' + if (b0 === 0x66 && b1 === 0x4c && b2 === 0x61 && b3 === 0x43) return 'audio/x-flac' + + if (b0 === 0xff && b1 === 0xd8 && b2 === 0xff && (b3 === 0xdb || b3 === 0xe0 || b3 === 0xee || b3 === 0xe1)) { + return 'image/jpeg' + } + + // OggS + if (b0 === 0x4f && b1 === 0x67 && b2 === 0x67 && b3 === 0x53) { + // 28-36 bytes: type + if (chunk.length > 36) { + const type = String.fromCharCode(...chunk.subarray(28, 36)) + if (type === 'OpusHead') return 'audio/ogg' // not audio/opus because Telegram is dumb + if (type.startsWith('\x80theora')) return 'video/ogg' + if (type.startsWith('\x01video')) return 'video/ogg' + if (type.startsWith('\x7fFLAC')) return 'audio/ogg' + if (type.startsWith('Speex ')) return 'audio/ogg' + if (type.startsWith('\x01vorbis')) return 'audio/ogg' + } + + return 'application/ogg' + } + + // 5-byte magic numbers + const b4 = chunk[4] + if (b0 === 0x25 && b1 === 0x50 && b2 === 0x44 && b3 === 0x46 && b4 === 0x2d) return 'application/pdf' + + // 6-byte magic numbers + const b5 = chunk[5] + + if (b0 === 0x47 && b1 === 0x49 && b2 === 0x46 && b3 === 0x38) { + if ((b4 === 0x37 || b4 === 0x39) && b5 === 0x61) return 'image/gif' + } + if (b0 === 0x37 && b1 === 0x7a && b2 === 0xbc && b3 === 0xaf && b4 === 0x27 && b5 === 0x1c) { + return 'application/x-7z-compressed' + } + + // 8-byte magic numbers + const b6 = chunk[6] + const b7 = chunk[7] + + if ( + b0 === 0x89 && + b1 === 0x50 && + b2 === 0x4e && + b3 === 0x47 && + b4 === 0x0d && + b5 === 0x0a && + b6 === 0x1a && + b7 === 0x0a + ) { + return 'image/png' + } + + if (b0 === 0x52 && b1 === 0x61 && b2 === 0x72 && b3 === 0x21 && b4 === 0x1a && b5 === 0x07) { + if (chunk[6] === 0x00 || chunk[6] === 0x01) return 'application/x-rar-compressed' + } + + // ftyp - iso container + if (b4 === 0x66 && b5 === 0x74 && b6 === 0x79 && b7 === 0x70 && chunk[8] & 0x60) { + const brandMajor = String.fromCharCode(...chunk.subarray(8, 12)) + .replace('\0', ' ') + .trim() + + switch (brandMajor) { + case 'avif': + case 'avis': + return 'image/avif' + case 'mif1': + return 'image/heif' + case 'msf1': + return 'image/heif-sequence' + case 'heic': + case 'heix': + return 'image/heic' + case 'hevc': + case 'hevx': + return 'image/heic-sequence' + case 'qt': + return 'video/quicktime' + case 'M4V': + case 'M4VH': + case 'M4VP': + return 'video/x-m4v' + case 'M4P': + return 'video/mp4' + case 'M4B': + return 'audio/mp4' + case 'M4A': + return 'audio/x-m4a' + case 'F4V': + return 'video/mp4' + case 'F4P': + return 'video/mp4' + case 'F4A': + return 'audio/mp4' + case 'F4B': + return 'audio/mp4' + case 'crx': + return 'image/x-canon-cr3' + default: + if (brandMajor.startsWith('3g')) { + if (brandMajor.startsWith('3g2')) { + return 'video/3gpp2' + } + + return 'video/3gpp' + } + + return 'video/mp4' + } + } + + return null +} diff --git a/packages/core/package.json b/packages/core/package.json index cd8712ff..e11ecdf9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -47,7 +47,6 @@ "@mtcute/tl": "workspace:^", "@mtcute/tl-runtime": "workspace:^", "@types/events": "3.0.0", - "@types/node": "18.16.0", "big-integer": "1.6.51", "events": "3.2.0", "long": "5.2.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 72e0dc11..56488ef2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,9 +27,6 @@ importers: '@types/mocha': specifier: 10.0.1 version: 10.0.1 - '@types/node': - specifier: 18.16.0 - version: 18.16.0 '@types/node-forge': specifier: 1.3.2 version: 1.3.2 @@ -120,12 +117,6 @@ importers: '@mtcute/file-id': specifier: workspace:^ version: link:../file-id - '@types/node': - specifier: 18.16.0 - version: 18.16.0 - file-type: - specifier: 16.5.4 - version: 16.5.4 packages/core: dependencies: @@ -138,9 +129,6 @@ importers: '@types/events': specifier: 3.0.0 version: 3.0.0 - '@types/node': - specifier: 18.16.0 - version: 18.16.0 big-integer: specifier: 1.6.51 version: 1.6.51 @@ -186,8 +174,6 @@ importers: specifier: ^9.0.6 version: 9.0.6 - packages/crypto: {} - packages/crypto-node: dependencies: '@mtcute/core': @@ -900,10 +886,6 @@ packages: requiresBuild: true optional: true - /@tokenizer/token@0.3.0: - resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} - dev: false - /@tootallnate/once@2.0.0: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} @@ -980,6 +962,7 @@ packages: /@types/node@18.16.0: resolution: {integrity: sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==} + dev: true /@types/normalize-package-data@2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -2599,15 +2582,6 @@ packages: flat-cache: 3.0.4 dev: true - /file-type@16.5.4: - resolution: {integrity: sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==} - engines: {node: '>=10'} - dependencies: - readable-web-to-node-stream: 3.0.2 - strtok3: 6.3.0 - token-types: 4.2.1 - dev: false - /file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} dev: false @@ -4408,11 +4382,6 @@ packages: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} dev: true - /peek-readable@4.1.0: - resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==} - engines: {node: '>=8'} - dev: false - /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true @@ -4581,13 +4550,6 @@ packages: string_decoder: 1.3.0 util-deprecate: 1.0.2 - /readable-web-to-node-stream@3.0.2: - resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==} - engines: {node: '>=8'} - dependencies: - readable-stream: 3.6.0 - dev: false - /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -5077,14 +5039,6 @@ packages: engines: {node: '>=8'} dev: true - /strtok3@6.3.0: - resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} - engines: {node: '>=10'} - dependencies: - '@tokenizer/token': 0.3.0 - peek-readable: 4.1.0 - dev: false - /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -5198,14 +5152,6 @@ packages: is-number: 7.0.0 dev: true - /token-types@4.2.1: - resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==} - engines: {node: '>=10'} - dependencies: - '@tokenizer/token': 0.3.0 - ieee754: 1.2.1 - dev: false - /trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'}