diff --git a/package.json b/package.json index d412bb8..c7b9d8e 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "devDependencies": { "@stylistic/eslint-plugin": "^1.5.1", "@types/better-sqlite3": "^7.6.8", + "@types/content-disposition": "^0.5.8", "@types/mime-types": "^2.1.4", "@types/node": "^20.10.5", "@typescript-eslint/eslint-plugin": "^6.16.0", @@ -33,6 +34,7 @@ "@mtcute/node": "^0.15.1", "@t3-oss/env-core": "^0.7.1", "better-sqlite3": "^9.3.0", + "content-disposition": "^0.5.4", "dotenv": "^16.3.1", "drizzle-orm": "^0.29.3", "mime-types": "^2.1.35", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index abf4506..1814caa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: better-sqlite3: specifier: ^9.3.0 version: 9.3.0 + content-disposition: + specifier: ^0.5.4 + version: 0.5.4 dotenv: specifier: ^16.3.1 version: 16.3.1 @@ -45,6 +48,9 @@ importers: '@types/better-sqlite3': specifier: ^7.6.8 version: 7.6.8 + '@types/content-disposition': + specifier: ^0.5.8 + version: 0.5.8 '@types/mime-types': specifier: ^2.1.4 version: 2.1.4 @@ -471,6 +477,9 @@ packages: '@types/better-sqlite3@7.6.8': resolution: {integrity: sha512-ASndM4rdGrzk7iXXqyNC4fbwt4UEjpK0i3j4q4FyeQrLAthfB6s7EF135ZJE0qQxtKIMFwmyT6x0switET7uIw==} + '@types/content-disposition@0.5.8': + resolution: {integrity: sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==} + '@types/events@3.0.0': resolution: {integrity: sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==} @@ -646,6 +655,10 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + copy-anything@3.0.5: resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} engines: {node: '>=12.13'} @@ -1669,6 +1682,8 @@ snapshots: dependencies: '@types/node': 20.10.5 + '@types/content-disposition@0.5.8': {} + '@types/events@3.0.0': {} '@types/json-schema@7.0.15': {} @@ -1867,6 +1882,10 @@ snapshots: concat-map@0.0.1: {} + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + copy-anything@3.0.5: dependencies: is-what: 4.1.16 diff --git a/src/core/data/cobalt.ts b/src/core/data/cobalt.ts index bcd8e63..443d925 100644 --- a/src/core/data/cobalt.ts +++ b/src/core/data/cobalt.ts @@ -1,6 +1,7 @@ import { z } from "zod" import { Text, literal, translatable } from "#core/utils/text" import { error, ok, Result } from "#core/utils/result" +import { parse as parseContentDisposition } from "content-disposition" const genericErrorSchema = z.object({ status: z.literal("error"), @@ -70,6 +71,14 @@ export const fetchMedia = async ( // Stream +const fileName = (header: string): string | undefined => { + try { + const contentDisposition = parseContentDisposition(header) + return contentDisposition.parameters.filename + } catch { + return undefined + } +} export const fetchStream = async (url: string) => { const data = await fetch(url, { headers: [ @@ -87,8 +96,7 @@ export const fetchStream = async (url: string) => { } const contentDisposition = data.headers.get("Content-Disposition") - const match = contentDisposition && /.*filename="([^"]+)".*/.exec(contentDisposition) - const filename = (match && match[1]) ?? undefined + const filename = contentDisposition ? fileName(contentDisposition) : undefined const buffer = Buffer.from(await data.arrayBuffer()) if (!buffer.length)