mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-02 12:26:36 +08:00
优化阅读功能
This commit is contained in:
+85
-2
@@ -65,6 +65,13 @@ export interface ReaderBook {
|
||||
dur_chapter_title: string
|
||||
dur_chapter_time: number
|
||||
order: number
|
||||
/** 本地导入书籍(TXT / EPUB / 有声书目录),正文在服务端,不走书源。 */
|
||||
is_local: boolean
|
||||
/** 原地引用服务器上已有的文件/目录:移出书架只解除引用,不删除源文件。 */
|
||||
local_external: boolean
|
||||
/** 听书跳过片头/片尾秒数(0 表示不跳过,对应 legado Book.openCredits/closeCredits)。 */
|
||||
open_credits: number
|
||||
close_credits: number
|
||||
}
|
||||
|
||||
export interface ReaderBookInfo {
|
||||
@@ -100,6 +107,8 @@ export interface ReaderChapterContent {
|
||||
tracks?: string[]
|
||||
images?: string[]
|
||||
image_style?: string
|
||||
/** 该音轨走了服务端转码(源格式浏览器解不了),首次播放需要等转码完成。 */
|
||||
transcoding?: boolean
|
||||
}
|
||||
|
||||
export interface ReaderReplaceRule {
|
||||
@@ -148,11 +157,26 @@ export interface ReaderBrowserRequest {
|
||||
title: string
|
||||
}
|
||||
|
||||
/** 书源 JS 交给宿主浏览器承载的一个页面(java.startBrowser / startBrowserAwait)。 */
|
||||
export interface ReaderBrowserPage {
|
||||
id: string
|
||||
title: string
|
||||
/** wait = 需要回传 DOM(点 √);open = 仅展示。 */
|
||||
mode: 'wait' | 'open'
|
||||
/** iframe 承载地址(同源,带签名)。 */
|
||||
page_url: string
|
||||
refetch: boolean
|
||||
source_id: string
|
||||
target_url?: string
|
||||
}
|
||||
|
||||
export interface ReaderLoginResult {
|
||||
ok: boolean
|
||||
error?: string
|
||||
toasts?: string[]
|
||||
browsers?: ReaderBrowserRequest[]
|
||||
/** 书源要求重新渲染登录表单(java.reLoginView / refreshExplore / upLoginData)。 */
|
||||
ui_refresh?: boolean
|
||||
values: Record<string, string>
|
||||
cookies: Record<string, string>
|
||||
logged_in: boolean
|
||||
@@ -160,6 +184,10 @@ export interface ReaderLoginResult {
|
||||
|
||||
const longOpts = { timeout: LONG_REQUEST_TIMEOUT } as const
|
||||
|
||||
// 登录动作可能阻塞等待用户在页面里操作(java.startBrowserAwait),
|
||||
// 服务端上限 10 分钟,这里留出余量,避免 axios 先超时把请求掐掉。
|
||||
const browserWaitOpts = { timeout: 11 * 60_000 } as const
|
||||
|
||||
export const readerAPI = {
|
||||
// ── 书源 ──
|
||||
listSources: () => api.get<{ sources: ReaderSource[] }>('/reader/sources').then((r) => r.data.sources),
|
||||
@@ -175,7 +203,30 @@ export const readerAPI = {
|
||||
sourceLogin: (id: string) => api.get<ReaderSourceLogin>(`/reader/sources/${id}/login`).then((r) => r.data),
|
||||
// 执行登录动作:action 为空表示执行 loginUrl 的 login()(确认登录)
|
||||
runSourceLogin: (id: string, body: { action?: string; fields?: Record<string, string> }) =>
|
||||
api.post<ReaderLoginResult>(`/reader/sources/${id}/login`, body, longOpts).then((r) => r.data),
|
||||
api.post<ReaderLoginResult>(`/reader/sources/${id}/login`, body, browserWaitOpts).then((r) => r.data),
|
||||
// 书源 JS 的宿主浏览器:轮询待用户完成的页面(startBrowserAwait 会阻塞在服务端)
|
||||
browserPending: (sourceId: string) =>
|
||||
api
|
||||
.get<{ pages: ReaderBrowserPage[] }>('/reader/browser/pending', { params: { source_id: sourceId } })
|
||||
.then((r) => r.data.pages ?? []),
|
||||
// 回传用户操作后的 DOM(或取消),解除服务端阻塞
|
||||
submitBrowserResult: (body: { id: string; body?: string; url?: string; cancelled?: boolean }) =>
|
||||
api.post('/reader/browser/result', body).then((r) => r.data),
|
||||
// 承载页面内的接口请求转交服务端代发(iframe 是不透明源,带不上书源 Cookie)
|
||||
browserXHR: (body: {
|
||||
id: string
|
||||
url: string
|
||||
method?: string
|
||||
headers?: Record<string, string>
|
||||
body?: string
|
||||
}) =>
|
||||
api
|
||||
.post<{ status: number; content_type: string; body: string; base64: boolean }>(
|
||||
'/reader/browser/xhr',
|
||||
body,
|
||||
longOpts,
|
||||
)
|
||||
.then((r) => r.data),
|
||||
// 仅保存表单值,不触发登录
|
||||
saveSourceLoginInfo: (id: string, fields: Record<string, string>) =>
|
||||
api.put(`/reader/sources/${id}/login-info`, { fields }),
|
||||
@@ -185,7 +236,11 @@ export const readerAPI = {
|
||||
// ── 搜索 / 详情 / 目录 / 正文 ──
|
||||
search: (key: string) =>
|
||||
api
|
||||
.post<{ books: ReaderSearchBook[]; skipped: ReaderSearchSkipped[] }>('/reader/search', { key }, longOpts)
|
||||
.post<{ books: ReaderSearchBook[] | null; skipped: ReaderSearchSkipped[] | null }>(
|
||||
'/reader/search',
|
||||
{ key },
|
||||
longOpts,
|
||||
)
|
||||
.then((r) => r.data),
|
||||
bookInfo: (params: { source_id?: string; source_url?: string; book_url: string }) =>
|
||||
api.get<ReaderBookInfo>('/reader/book-info', { params, timeout: LONG_REQUEST_TIMEOUT }).then((r) => r.data),
|
||||
@@ -202,9 +257,37 @@ export const readerAPI = {
|
||||
listBooks: () => api.get<{ books: ReaderBook[] }>('/reader/books').then((r) => r.data.books),
|
||||
addBook: (body: { origin: ReaderSearchOrigin; name: string; author: string; cover_url: string }) =>
|
||||
api.post<ReaderBook>('/reader/books', body).then((r) => r.data),
|
||||
/** 上传本地书籍(TXT / EPUB),服务端解析目录后加入书架。 */
|
||||
uploadLocalBook: (file: File, onProgress?: (percent: number) => void) =>
|
||||
api
|
||||
.post<ReaderBook>('/reader/local/books', (() => {
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
return form
|
||||
})(), {
|
||||
timeout: 0,
|
||||
onUploadProgress: (e) => {
|
||||
if (!onProgress || !e.total) return
|
||||
onProgress(Math.round((e.loaded / e.total) * 100))
|
||||
},
|
||||
})
|
||||
.then((r) => r.data),
|
||||
/** 从服务器已有文件导入书籍(TXT / EPUB),原地引用不复制。仅管理员。 */
|
||||
importLocalBookFromPath: (path: string) =>
|
||||
api
|
||||
.post<ReaderBook>('/reader/local/books/from-path', { path }, { timeout: LONG_REQUEST_TIMEOUT })
|
||||
.then((r) => r.data),
|
||||
/** 把服务器上的一本目录导入为有声书(音频文件 + .strm 播放指针)。仅管理员。 */
|
||||
importLocalAudioDir: (path: string) =>
|
||||
api
|
||||
.post<ReaderBook>('/reader/local/audiobooks', { path }, { timeout: LONG_REQUEST_TIMEOUT })
|
||||
.then((r) => r.data),
|
||||
removeBook: (id: string) => api.delete(`/reader/books/${id}`),
|
||||
saveProgress: (id: string, body: { chapter_index: number; pos: number; chapter_title: string }) =>
|
||||
api.put(`/reader/books/${id}/progress`, body),
|
||||
// 听书跳过片头/片尾(秒,0 不跳过)
|
||||
saveAudioConfig: (id: string, body: { open_credits: number; close_credits: number }) =>
|
||||
api.put(`/reader/books/${id}/audio-config`, body),
|
||||
listChapters: (id: string) =>
|
||||
api.get<{ chapters: ReaderChapter[] }>(`/reader/books/${id}/chapters`).then((r) => r.data.chapters),
|
||||
saveChapters: (id: string, chapters: ReaderChapter[]) => api.post(`/reader/books/${id}/chapters`, { chapters }),
|
||||
|
||||
Reference in New Issue
Block a user