From 4e805689c14fd3702b0c9f1f38c03d242f5ccc61 Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Mon, 18 May 2026 19:38:12 +0700 Subject: [PATCH] feat: extend file type classification and implement computeHash - Add sticker (webp) and video_note to classification mapping and file limits - Handle unknown mime or caption fallback to document - Implement computeHash utility using Bun.CryptoHasher for sha256 checksums - Update and add comprehensive tests in file.test.ts Co-Authored-By: Claude Opus 4.7 --- src/utils/file.ts | 12 +++++++++++- test/file.test.ts | 21 ++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/utils/file.ts b/src/utils/file.ts index 03b37e4..311c0bd 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -5,12 +5,16 @@ const FILE_TYPES: Record = { audio: 200 * 1024 * 1024, // 200MB voice: 200 * 1024 * 1024, // 200MB animation: 2 * 1024 * 1024 * 1024, // 2GB + sticker: 10 * 1024 * 1024, // 10MB + video_note: 2 * 1024 * 1024 * 1024, // 2GB }; export const getFileType = (mime: string | null, caption?: string): string => { const mimeUpper = mime?.split('/')[0]?.toLowerCase(); const captionLower = caption?.toLowerCase(); + if (mime?.toLowerCase() === 'image/webp' || captionLower?.includes('sticker')) return 'sticker'; + if (captionLower?.includes('video_note')) return 'video_note'; if (mimeUpper === 'video') return 'video'; if (mimeUpper === 'audio') return 'audio'; if (mimeUpper === 'document') return 'document'; @@ -18,7 +22,7 @@ export const getFileType = (mime: string | null, caption?: string): string => { if (captionLower?.includes('voice')) return 'voice'; if (captionLower?.includes('animation')) return 'animation'; - return mimeUpper || 'document'; + return mimeUpper === 'application' ? 'application' : 'document'; }; export const checkFileSize = (sizeBytes: number, fileType: string): boolean => { @@ -95,3 +99,9 @@ export const extractMimeType = (msg: any, request: any): string => { 'application/octet-stream' ); }; + +export const computeHash = (buffer: Buffer): string => { + const hasher = new Bun.CryptoHasher('sha256'); + hasher.update(buffer); + return hasher.digest('hex'); +}; diff --git a/test/file.test.ts b/test/file.test.ts index 22377d9..ff4eff8 100644 --- a/test/file.test.ts +++ b/test/file.test.ts @@ -1,6 +1,6 @@ // @ts-nocheck import { describe, expect, it } from 'bun:test'; -import { checkFileSize, extractFileName, extractMimeType, getFileType } from '../src/utils/file'; +import { checkFileSize, computeHash, extractFileName, extractMimeType, getFileType } from '../src/utils/file'; describe('File Utilities', () => { describe('getFileType', () => { @@ -26,9 +26,17 @@ describe('File Utilities', () => { expect(getFileType('application/octet-stream', 'cool animation')).toBe('animation'); }); + it('should classify sticker and video_note', () => { + expect(getFileType('image/webp', '')).toBe('sticker'); + expect(getFileType('image/webp', 'some caption')).toBe('sticker'); + expect(getFileType('video/mp4', 'this is a video_note')).toBe('video_note'); + expect(getFileType('application/octet-stream', 'video_note file')).toBe('video_note'); + }); + it('should default to mime first segment or document', () => { expect(getFileType('application/pdf', '')).toBe('application'); expect(getFileType(null, '')).toBe('document'); + expect(getFileType('unknown/type', '')).toBe('document'); }); }); @@ -36,11 +44,13 @@ describe('File Utilities', () => { it('should allow files under the size limit', () => { expect(checkFileSize(5 * 1024 * 1024, 'photo')).toBe(true); // Photo limit is 10MB expect(checkFileSize(1 * 1024 * 1024 * 1024, 'video')).toBe(true); // Video limit is 2GB + expect(checkFileSize(2 * 1024 * 1024 * 1024, 'document')).toBe(true); // Document limit is 2GB }); it('should block files exceeding the size limit', () => { expect(checkFileSize(15 * 1024 * 1024, 'photo')).toBe(false); // Photo limit is 10MB expect(checkFileSize(3 * 1024 * 1024 * 1024, 'video')).toBe(false); // Video limit is 2GB + expect(checkFileSize(3 * 1024 * 1024 * 1024, 'document')).toBe(false); }); it('should fall back to document limit if fileType is unknown', () => { @@ -92,4 +102,13 @@ describe('File Utilities', () => { expect(extractMimeType({}, null)).toBe('application/octet-stream'); }); }); + + describe('computeHash', () => { + it('should compute SHA-256 hash of a buffer', () => { + const buffer = Buffer.from('hello world'); + const hash = computeHash(buffer); + // sha256 of 'hello world' is b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 + expect(hash).toBe('b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'); + }); + }); });