// 书籍元信息整理。 // // 番茄的 multi-detail 和 batch_full 里的 novel_data 字段名一致,但很多 // 字段是「JSON 字符串」而不是数组/对象(category_v2、roles),要先解析。 // 这里把 TXT 和 EPUB 都要用的东西集中处理一次。 export interface BookMeta { book_id: string title: string author: string /** 分类名,如「无限流」 */ categories: string[] /** 主角名 */ roles: string[] abstract: string /** 版权声明,已去掉 APP 内的反馈引导语 */ copyright: string cover_url: string word_number: number /** 原始 creation_status */ creation_status: string } /** APP 的版权声明里带一句只在客户端有意义的引导语 */ const FEEDBACK_HINT = /,如有任何疑问,请通过[“"]?我的-意见反馈[”"]?告知我们/ function parseJsonField(raw: unknown, fallback: T): T { if (raw == null) return fallback if (typeof raw !== 'string') return (raw as T) ?? fallback try { const parsed = JSON.parse(raw) return parsed == null ? fallback : (parsed as T) } catch { return fallback } } /** * 高清化封面。 * * 列表接口给的是带缩放参数的地址(…~tplv-xxx:300:0.image),换成 origin * 域名并去掉参数就能拿到原图。novel-pic-r 是已经处理过的,不动。 * 移植自 web.html 的 replaceCover。 */ export function hdCover(url: string): string { if (!url) return '' if (url.includes('novel-pic-r')) return url let u = url if (u.startsWith('https://')) u = u.substring(8) else if (u.startsWith('http://')) u = u.substring(7) const parts = u.split('/') parts[0] = 'https://p6-novel.byteimg.com/origin' return parts .map(part => (part.includes('?') || part.includes('~') ? part.split('~')[0]! : part)) .join('/') } /** 从 novel_data / multi-detail 的原始数据里提取元信息 */ export function toBookMeta(raw: any): BookMeta { const categories = parseJsonField>(raw?.category_v2, []) .map(item => String(item?.Name ?? '')) .filter(Boolean) return { book_id: String(raw?.book_id ?? ''), title: String(raw?.book_name || raw?.original_book_name || '未命名'), author: String(raw?.author ?? '未知作者'), categories: categories.length ? categories : [String(raw?.category ?? '')].filter(Boolean), roles: parseJsonField(raw?.roles, []).map(String).filter(Boolean), abstract: String(raw?.abstract ?? ''), copyright: String(raw?.copyright_info ?? '').replace(FEEDBACK_HINT, ''), cover_url: String(raw?.thumb_url ?? ''), word_number: Number(raw?.word_number) || 0, creation_status: String(raw?.creation_status ?? ''), } } /** 简介段落,TXT 头部与 EPUB description 共用 */ export function describeBook(meta: BookMeta): string { const lines: string[] = [] if (meta.categories.length) lines.push(`分类:${meta.categories.join('、')}`) if (meta.roles.length) lines.push(`主角:${meta.roles.join('、')}`) if (meta.abstract) lines.push(`简介:${meta.abstract}`) if (meta.copyright) lines.push(`${meta.copyright}。`) return lines.join('\n') }