Files
MeBox/web/src/api/danmaku.ts
T
truewhile b7f1cd85f8 支持按用户保存播放器与弹幕偏好
在 user 记录上新增播放器音量、弹幕开关、透明度、字号、显示区域、合并偏好、自定义弹幕源以及 dandanplay 应用凭据字段,并提供 PUT /danmaku/settings 局部更新接口,带取值校验。
返回脱敏后的完整渲染配置,绝不回传应用密钥,仅在响应中标记密钥是否已配置。
结果缓存键加入用户 ID 与凭据指纹,避免不同用户或凭据串用缓存;旧版实例级设置仅作为未登录与迁移期的回退。
2026-09-13 23:01:43 +08:00

113 lines
3.8 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
/** 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
}
// 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),
}