From 3abef6ea75891ae572af8d776695a069e181b0fa Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Tue, 23 Dec 2025 15:53:50 +0800 Subject: [PATCH] fix(opds): correctly parse file extension for CBZ files from OPDS servers, closes #2765 (#2771) --- apps/readest-app/src/app/opds/page.tsx | 13 ++++++++++--- apps/readest-app/src/app/opds/utils/opdsUtils.ts | 11 +++++++++++ apps/readest-app/src/libs/document.ts | 6 ++++-- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/apps/readest-app/src/app/opds/page.tsx b/apps/readest-app/src/app/opds/page.tsx index 5ed53836..80a62fac 100644 --- a/apps/readest-app/src/app/opds/page.tsx +++ b/apps/readest-app/src/app/opds/page.tsx @@ -19,7 +19,13 @@ import { useLibrary } from '@/hooks/useLibrary'; import { eventDispatcher } from '@/utils/event'; import { getFileExtFromMimeType } from '@/libs/document'; import { OPDSFeed, OPDSPublication, OPDSSearch } from '@/types/opds'; -import { isSearchLink, MIME, parseMediaType, resolveURL } from './utils/opdsUtils'; +import { + getFileExtFromPath, + isSearchLink, + MIME, + parseMediaType, + resolveURL, +} from './utils/opdsUtils'; import { getProxiedURL, fetchWithAuth, probeAuth, needsProxy } from './utils/opdsReq'; import { READEST_OPDS_USER_AGENT } from '@/services/constants'; import { FeedView } from './components/FeedView'; @@ -403,8 +409,9 @@ export default function BrowserPage() { } return; } else { - const ext = parsed?.mediaType ? getFileExtFromMimeType(parsed.mediaType) : ''; - const basename = new URL(url).pathname.replaceAll('/', '_'); + const pathname = new URL(url).pathname; + const ext = getFileExtFromMimeType(parsed?.mediaType) || getFileExtFromPath(pathname); + const basename = pathname.replaceAll('/', '_'); const filename = ext ? `${basename}.${ext}` : basename; const dstFilePath = await appService?.resolveFilePath(filename, 'Cache'); if (dstFilePath) { diff --git a/apps/readest-app/src/app/opds/utils/opdsUtils.ts b/apps/readest-app/src/app/opds/utils/opdsUtils.ts index 17a828fa..9508de4a 100644 --- a/apps/readest-app/src/app/opds/utils/opdsUtils.ts +++ b/apps/readest-app/src/app/opds/utils/opdsUtils.ts @@ -1,5 +1,6 @@ import { isOPDSCatalog } from 'foliate-js/opds.js'; import { OPDSLink } from '@/types/opds'; +import { EXTS } from '@/libs/document'; import { fetchWithAuth } from './opdsReq'; export const groupByArray = (arr: T[] | undefined, f: (el: T) => K | K[]): Map => { @@ -215,3 +216,13 @@ export const validateOPDSURL = async ( }; } }; + +export const getFileExtFromPath = (pathname: string, delimiter = '/'): string => { + const parts = pathname.split(delimiter); + for (const ext of Object.values(EXTS)) { + if (parts.includes(ext)) { + return ext; + } + } + return ''; +}; diff --git a/apps/readest-app/src/libs/document.ts b/apps/readest-app/src/libs/document.ts index 10c7a9b8..4fc06776 100644 --- a/apps/readest-app/src/libs/document.ts +++ b/apps/readest-app/src/libs/document.ts @@ -87,7 +87,7 @@ export const MIMETYPES: Record = { MOBI: ['application/x-mobipocket-ebook'], AZW: ['application/vnd.amazon.ebook'], AZW3: ['application/vnd.amazon.mobi8-ebook', 'application/x-mobi8-ebook'], - CBZ: ['application/vnd.comicbook+zip', 'application/zip'], + CBZ: ['application/vnd.comicbook+zip', 'application/zip', 'application/x-cbz'], FB2: ['application/x-fictionbook+xml', 'text/xml', 'application/xml'], FBZ: ['application/x-zip-compressed-fb2', 'application/zip'], }; @@ -243,7 +243,9 @@ export const getDirection = (doc: Document) => { return { vertical, rtl }; }; -export const getFileExtFromMimeType = (mimeType: string): string => { +export const getFileExtFromMimeType = (mimeType?: string): string => { + if (!mimeType) return ''; + for (const format in MIMETYPES) { const list = MIMETYPES[format as BookFormat]; if (list.includes(mimeType)) {