Files
MeBox/web/src/api/danmaku.ts
T
truewhile 065652d758 优化
2026-09-24 22:27:09 +08:00

117 lines
4.0 KiB
TypeScript

import { api } from './client'
// DanmakuEpisode / DanmakuAnime mirror the dandanplay search response. When
// multiple anime match, the backend returns them as candidates and the player
// asks the user which to load (disambiguation).
export interface DanmakuEpisode {
episodeId: number
episodeTitle: string
}
export interface DanmakuAnime {
animeId: number
animeTitle: string
episodes: DanmakuEpisode[]
}
// DanmakuFetchResult mirrors the backend /api/danmaku/:id response: renderer
// knobs (resolved from runtime settings) plus the raw upstream payload. The
// dandanplay protocol delivers Bilibili-format XML or its own JSON shape
// ({comments:[{p,m,t}]}); source_type is sniffed from the body by the backend
// ("auto" when there is nothing to sniff). When several anime matched,
// candidates is non-empty and raw is empty.
export interface DanmakuFetchResult {
enabled: boolean
source_type: 'auto' | 'xml' | 'json'
opacity: string
font_size: string
area: string
raw?: string
/** Number of sources merged into `raw`; absent/0 means not merged. */
merged_sources?: number
candidates?: DanmakuAnime[]
/**
* Same episode, other sources. Unlike `candidates` (which means "pick one
* before anything loads"), `alternatives` arrives together with a loaded
* library: the backend already auto-picked one and offers the rest so the
* user can switch without re-searching. Aggregating sources such as LogVar
* return several libraries for the same episode.
*/
alternatives?: DanmakuAnime[]
anime_title?: string
episode_title?: string
episode_id?: number
match_mode?: 'hash' | 'filename' | 'metadata' | 'search' | 'manual' | string
}
export interface DanmakuLoadedInfo {
animeTitle?: string
episodeTitle?: string
episodeId?: number | string
matchMode?: 'hash' | 'filename' | 'metadata' | 'search' | 'manual' | string
totalCount: number
sourceType?: 'auto' | 'xml' | 'json'
/** Number of sources merged into the loaded comments (0 = not merged). */
mergedSources?: number
}
export type DanmakuFetchOptions = {
/** Overrides the media-derived search keyword (manual search). */
kw?: string
/** Forces a specific danmaku library chosen by the user. */
episodeId?: number | string
}
export interface DanmakuConfig {
enabled: boolean
source?: string
app_id?: string
app_key_configured: boolean
opacity: string
font_size: string
area: string
volume: number
/** 当前用户是否已经看过 VR 全景播放的首次操作说明(按用户存储)。 */
vr360_guide_seen: boolean
/** Per-user preference: merge the same episode's multiple sources. */
merge_sources: boolean
}
export interface DanmakuSettingsPatch {
enabled?: boolean
source?: string
app_id?: string
/** 留空不会修改密钥;清空请使用 clear_app_key。 */
app_key?: string
clear_app_key?: boolean
opacity?: number
font_size?: number
area?: number
merge_sources?: boolean
volume?: number
/** 看过 VR 操作说明后置为 true,之后不再弹出。 */
vr360_guide_seen?: boolean
}
// danmakuAPI fetches danmaku comments for a media item. The backend resolves
// the configured dandanplay source by the video's name (search for episode,
// then fetch its comment library) and returns the raw Bilibili-format XML;
// parsing into comment objects happens client-side in utils/parseDanmaku.
export const danmakuAPI = {
fetch: (mediaId: string, options: DanmakuFetchOptions = {}) =>
api
.get<DanmakuFetchResult>(`/danmaku/${encodeURIComponent(mediaId)}`, {
params: { kw: options.kw || undefined, episodeId: options.episodeId || undefined },
timeout: 20_000,
})
.then((r) => r.data),
// config returns the current user's player volume and danmaku preferences.
config: () =>
api.get<DanmakuConfig>('/danmaku/config').then((r) => r.data),
// updateSettings persists a partial per-user player/danmaku settings patch.
updateSettings: (settings: DanmakuSettingsPatch) =>
api.put<DanmakuConfig>('/danmaku/settings', settings).then((r) => r.data),
}