/** * danmakus.com API * @link https://ukamnads.icu/swagger */ export declare namespace Danmakus { /** * 直播实时排行榜 * @link https://ukamnads.icu/api/v2/living */ export interface RankingProps { /** * 200: success */ code: number message: string data: RankingItem[] } /** * Channel info, shared by `RankingItem`, `ChannelInfoProps` and * `WatchedChannelsItem` (maps to `APIChannelInfo` in the spec). */ export interface ChannelInfo { uId: number uName: string roomId: number faceUrl: string frameUrl: string isLiving: boolean isDeleted: boolean title: string tags: string[] lastLiveDate: number lastLiveDanmakuCount: number totalDanmakuCount: number totalIncome: number totalLiveCount: number totalLiveSecond: number addDate: Date /** can be empty in some rare case due to some program error */ livingInfo?: LiveItem commentCount: number fansCount: number followCount: number lastUpdateStatAt: number lastLiveIncome: number recordingStatus?: ChannelRecordingStatus | null } /** Recording status of a channel (`APIChannelRecordingStatus`) */ export interface ChannelRecordingStatus { publicUsers: ChannelRecordingUser[] privateCount: number } /** Item of `ChannelRecordingStatus.publicUsers` (`APIChannelRecordingUser`) */ export interface ChannelRecordingUser { accountId: number name: string } /** Item of `RankingProps` */ export interface RankingItem extends ChannelInfo {} /** * 频道信息(直播记录列表) * @link https://ukamnads.icu/api/v2/channel?uId=2132180406 */ export interface ChannelProps { /** * 200: success */ code: number message: string data: ChannelData } /** Data of `ChannelProps` */ export interface ChannelData { channel: ChannelInfoProps lives: LiveItem[] extra?: { fansHistory: { archiveView: number follower: number time: number }[] guardHistory: { guardNum: number areaRank: number time: number }[] } } /** Props of `ChannelData` */ export interface ChannelInfoProps extends ChannelInfo {} /** General live stream item (`APILiveInfo`) */ export interface LiveItem { liveId: string isFinish: boolean isFull: boolean isMerged: boolean parentArea: string area: string coverUrl: string danmakusCount: number startDate: number stopDate: number | null title: string totalIncome: number watchCount: number likeCount: number payCount: number interactionCount: number onlineRank: number | null maxOnlineCount: number commentCount: number /** present when requested with `includeExtra=true` */ extra?: ExtraProps versions?: LiveVersionInfo[] } /** Item of `LiveItem.versions` (`APILiveVersionInfo`) */ export interface LiveVersionInfo { live: LiveItem userName: string userId: number isOfficial: boolean isFull: boolean rangeDanmakusCount: number } /** General danmaku event props */ export interface DanmakuProps { uId: number uName: string /** * - 0: 普通消息 * - 1: 礼物 * - 2: 上舰 * - 3: SuperChat * - 4: 进入直播间 * - 5: 标题变动 * - 6: 分区变动 * - 7: 直播中止 * - 8: 直播继续 * - 9: 用户封禁 * - 10, 11, 12: 见 API(未公开说明) */ type: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 sendDate: number message: string price: number | null ct: string count: number | null /** * 如存在,这条弹幕由用户补充上传,非本站直接采集,内容和时间可能存在偏差,本站不保证准确性。 */ uploadAccountId?: number /** 盲盒名称 */ mysteryBoxName?: string /** 盲盒价格 */ mysteryBoxPrice?: number debugSourceFingerprint?: number } /** Extra props for live item */ export interface ExtraProps { wordCloud: { [key: string]: number } onlineRank: { [key: string]: number } blockUsers: number[] } /** * 单场直播信息 * @link https://ukamnads.icu/api/v2/live?liveid=cafc0eb5-a79a-4a0d-b283-3360b6a1a814&includeExtra=true */ export interface LiveProps { /** * 200: success */ code: number message: string data: LiveData } /** Data of `LiveProps` */ export interface LiveData { total: number pageNum: number pageSize: number hasMore: boolean data: { channel: ChannelInfoProps live: LiveItem danmakus: DanmakuProps[] } } /** * 用户看过的主播 * @link https://ukamnads.icu/api/v2/user/watchedChannels?uid=2132180406 */ export interface WatchedChannels { /** * 200: success */ code: number message: string data: WatchedChannelsItem[] } /** * Item of `WatchedChannels`. * * Note: the spec types `/api/v2/user/watchedChannels` `data` loosely as * `any`; this mirrors the channel info actually returned (`APIChannelInfo`). */ export interface WatchedChannelsItem extends ChannelInfo {} /** * 用户观看/事件记录 */ export interface User { code: number message: string data: UserData } /** Data of `User` */ export interface UserData { total: number pageNum: number pageSize: number hasMore: boolean data: { records: UserDataRecordsItem[] } } /** Item of `UserData` */ export interface UserDataRecordsItem { channel: WatchedChannelsItem live: LiveItem danmakus: DanmakuProps[] } // ────────────────────────────────────────────────────────────────────────── // v3 API // // The v3 endpoints replace the flat `DanmakuProps[]` list with a normalized // "frame": danmaku records reference interned `actors` and `roomEmojis` // tables by index, which dramatically shrinks the payload for long lives. // // Two transports exist for the same data: // - JSON (`GET /api/v3/lives/{liveId}/danmakus`) — readable, fields named // - MessagePack (`GET /api/v3/lives/{liveId}/full|delta`) — compact, fields positional // // The MessagePack endpoints are documented in the `Msgpack` block below. // ────────────────────────────────────────────────────────────────────────── /** * Payload shape discriminator (`DanmakuPayloadKindV3`). Selects which * `DanmakuPayloadV3` fields are populated — and, in the MessagePack * transport, the positional layout of `payloadArgs` (see `Msgpack`). * * - 0 `None`: 无负载(控制 / 进场事件) * - 1 `TextMessage`: 纯文本弹幕 → `rawText` * - 2 `RoomEmojiMessage`: 房间表情 → `roomEmojiId` * - 3 `Gift`: 礼物 → `name`, `count`, `price`, `mysteryBoxName`, `mysteryBoxPrice` * - 4 `Guard`: 上舰 → `name`, `count`, `price` * - 5 `SuperChat`: → `text`, `price` * - 6 `TitleChange`: 标题变动 → `previousTitle`, `currentTitle` * - 7 `AreaChange`: 分区变动 → `previous(Parent)Area`, `current(Parent)Area` * - 8 `Warning`: 超管警告 → `text` * - 9 `RecordStart`: 录制开始 → `text` * - 10 `RecordInterrupt`: 录制中断 → `text` */ export type DanmakuPayloadKind = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 /** Interned speaker (`ActorV3`), referenced by `DanmakuRecordV3.actorId` */ export interface ActorV3 { uid: number name: string } /** Interned room emoji (`RoomEmojiV3`), referenced by `payload.roomEmojiId` */ export interface RoomEmojiV3 { name: string resource: string } /** Variant payload of a danmaku record (`DanmakuPayloadV3`); see `DanmakuPayloadKind` */ export interface DanmakuPayloadV3 { /** kind 1: 弹幕正文 */ rawText?: string /** kind 2: 0-based index into `DanmakuFrameV3.roomEmojis` */ roomEmojiId?: number /** kind 3/4: 礼物 / 大航海名称 */ name?: string count?: number /** kind 3/4/5: 金额,单位元(人民币) */ price?: number mysteryBoxName?: string mysteryBoxPrice?: number /** kind 6 */ previousTitle?: string /** kind 6 */ currentTitle?: string /** kind 7 */ previousParentArea?: string previousArea?: string currentParentArea?: string currentArea?: string /** kind 5: SuperChat 正文;kind 8/9/10: 提示文本 */ text?: string } /** * A single danmaku/event record (`DanmakuRecordV3`). * * Note (vs the MessagePack transport): here `ts` is absolute and `actorId` * is a **0-based** index into `DanmakuFrameV3.actors` with `undefined` = 系统; * the msgpack wire is 1-based with `0` = 系统. See {@link Msgpack}. */ export interface DanmakuRecordV3 { /** 绝对时间戳,unix ms */ ts: number type: DanmakuProps['type'] /** 0-based index into `DanmakuFrameV3.actors`; `undefined` = 系统 / 无主体 */ actorId?: number /** 如存在,这条弹幕由用户补充上传,非本站直接采集 */ uploadAccountId?: number payloadKind: DanmakuPayloadKind payload?: DanmakuPayloadV3 | null } /** Normalized danmaku frame (`DanmakuFrameV3`) shared by the v3 endpoints */ export interface DanmakuFrameV3 { actors: ActorV3[] roomEmojis: RoomEmojiV3[] records: DanmakuRecordV3[] } /** * 单场直播弹幕切片(JSON) * @link https://ukamnads.icu/api/v3/lives/{liveId}/danmakus * * Query: `offset` (default 0), `limit` (default 2000, max 1e7), * `userId` (filter by speaker), `type` (repeatable, filter by event type). */ export interface LiveDanmakusPageV3 { code: number message: string data: { total: number offset: number limit: number hasMore: boolean frame: DanmakuFrameV3 } } /** * 单场直播元数据(JSON,无弹幕) * @link https://ukamnads.icu/api/v3/lives/{liveId} * * Query: `include` (`extra` to populate `live.extra`). This is the * authoritative, typed source for live/channel metadata — prefer it over * decoding the positional header of the MessagePack endpoints. */ export interface LiveResponseV3 { code: number message: string data: { channel: ChannelInfoProps live: LiveItem } } /** * 用户历史记录(JSON) * @link https://ukamnads.icu/api/v3/users/{userId}/history * * Query: `page` (1-based, default 1), `pageSize` (default 10), * `channelUserId` (filter by streamer). */ export interface UserHistoryResponseV3 { code: number message: string data: { total: number page: number pageSize: number hasMore: boolean items: { channel: ChannelInfoProps live: LiveItem danmakus: DanmakuFrameV3 }[] } } /** * 用户页聚合数据(JSON) * @link https://ukamnads.icu/api/v3/users/{userId}/bootstrap */ export interface UserBootstrapResponseV3 { code: number message: string data: { history: UserHistoryResponseV3['data'] watchedChannels: { uName: string uId: number faceUrl: string count: number }[] blocks: { uName: string uId: number faceUrl: string date: number liveId: string }[] historyUserNames: string[] } } /** * MessagePack transport for the v3 endpoints. * * The wire layout is **not fully described by swagger** (msgpack bodies show * up as opaque schemas there). The authoritative definitions are the official * web client's models, which this namespace mirrors 1:1: * - `DanmakuVue/src/api/DanmakuMessagePackModels.ts` (the `*Wire` tuples) * - `DanmakuVue/src/api/DanmakuMessagePackDecoder.ts` (decode semantics) * - `DanmakuVue/src/api/IndexBackgroundModels.ts` * * ## Endpoints (all respond with `application/x-msgpack`) * * | Endpoint | Decoded as | Encoding | * | -------------------------------------------- | ---------- | -------- | * | `GET /api/v3/lives/{liveId}/full` | {@link Msgpack.LiveFullEnvelopeWire} | positional | * | `GET /api/v3/lives/{liveId}/delta` | {@link Msgpack.LiveDeltaEnvelopeWire} | positional | * | `GET /api/v3/meta/index-background` | {@link Msgpack.IndexBackgroundDanmakuEnvelopeWire} | positional | * | `GET /api/v3/user-analysis-jobs/{id}/result` | {@link UserAnalyzeResult} | keyed map | * * The live & index-background bodies are **positional arrays** (field order = * the tuple order below); the user-analysis result is an ordinary **keyed * map** that decodes straight to its object shape. * * `/full` query: `include=extra` (populate `live[17]`), `includeEnter=true` * (include 进场 events, default false). * `/delta` query: `afterSeq` (cursor, default 0), `includeEnter`. * * ## Fetching & decoding * * ```ts * import { decode } from '@msgpack/msgpack' * * const res = await fetch( * `https://ukamnads.icu/api/v3/lives/${liveId}/full`, * { headers: { Accept: 'application/x-msgpack' } }, * ) * const [channel, live, frame] = decode( * new Uint8Array(await res.arrayBuffer()), * ) as Danmakus.Msgpack.LiveFullEnvelopeWire * ``` * * Note: the body is a positional **array**, not an object, and `extra[0]` * (`onlineRank`) is an integer-keyed map (keyed by unix-ms) — use a decoder * that allows non-string map keys (`@msgpack/msgpack` with `{ useMap: true }`, * or python `msgpack` with `strict_map_key=False`). * * ## Wire-format rules (differ from the JSON transport!) * * 1. **Records are positional tuples**, see {@link Msgpack.LiveFullRecordWire}. * 2. **Indices are 1-based**; `0`/`<=0` means "none". `actorId`/`roomEmojiId` * resolve as `table[id - 1]`. (The JSON transport is 0-based and uses * `undefined` for "none".) * 3. **Timestamps are delta-encoded.** `frame[0]` (`baseTs`) is a base * unix-ms; each record's `dt` is the gap from the previous record. * Reconstruct with a running sum: `absTs = baseTs + Σ dt` up to and * including that record. * 4. **Prices are integer milli-yuan** (value `/ 1000` = 元) — both * `live.totalIncomeMilli` and the price slots in `payloadArgs`. The JSON * transport already divides (e.g. msgpack `30000` ⇄ JSON `30`). * 5. `stopDate <= 0` means the live is ongoing (decode to `null`). * 6. Each `/delta` response is **self-contained**: its records reference that * response's own `actors`/`roomEmojis` tables, not a global one. * * ## Reconstructing records * * ```ts * const [baseTs, actors, emojis, records] = frame * let ts = baseTs * for (const [dt, type, actorId, , kind, args] of records) { * ts += dt * const actor = actorId > 0 ? actors[actorId - 1] : undefined // [uid, name] * // kind 1 → [text]; kind 2 → [emojiId]; kind 3 → [name, count, priceMilli, ...] * } * ``` * * ## Polling `/delta` for a live stream * * Call with `afterSeq=0` for the full frame so far, then on each poll pass the * previous response's `live.latestSeq` (`live[19]`) as the next `afterSeq`; * each call returns only records newer than that sequence. */ export namespace Msgpack { /** `[uid, name, roomId]` */ export type LiveFullChannelWire = [uid: number, name: string, roomId: number] /** `[uid, name]` — element of `LiveFullFrameWire[1]` */ export type LiveFullActorWire = [uid: number, name: string] /** `[resource]` — element of `LiveFullFrameWire[2]` (`name` dropped vs JSON) */ export type LiveFullRoomEmojiWire = [resource: string] /** * `[onlineRank, wordCloud]` — present only when `/full?include=extra`, * otherwise `null`; either slot may itself be `null`. */ export type LiveFullExtraWire = [ /** unix-ms → 在线人数 */ onlineRank?: Record | null, /** 词 → 次数 */ wordCloud?: Record | null, ] /** * `LiveFullRecordWire[5]` (`payloadArgs`) — variant tuple selected by * `payloadKind`. The source types this loosely as `unknown[] | null`; the * union below is the exact per-kind layout used by the decoder. `price*` * slots are **milli-yuan**; `roomEmojiId` is a 1-based index. */ export type PayloadArgs = | [rawText: string] // kind 1 — 纯文本弹幕 | [roomEmojiId: number] // kind 2 — 房间表情 | [ name: string, count: number, priceMilli: number, mysteryBoxName: string | null, mysteryBoxPriceMilli: number | null, ] // kind 3 — 礼物 | [name: string, count: number, priceMilli: number] // kind 4 — 上舰 | [text: string, priceMilli: number] // kind 5 — SuperChat | [previousTitle: string, currentTitle: string] // kind 6 — 标题变动 | [previousParentArea: string, previousArea: string, currentParentArea: string, currentArea: string] // kind 7 — 分区变动 | [text: string] // kind 8/9/10 — 警告 / 录制开始 / 录制中断 /** `[dt, type, actorId, uploadAccountId, payloadKind, payloadArgs?]` */ export type LiveFullRecordWire = [ /** 距上一条记录的毫秒增量;绝对值 = `LiveFullFrameWire[0]` + 累加和 */ dt: number, type: DanmakuProps['type'], /** 1-based index into `LiveFullFrameWire[1]`; `<=0` = 系统 / 无主体 */ actorId: number, /** 1-based account id of an uploader; `<=0` = 本站采集 */ uploadAccountId: number, payloadKind: DanmakuPayloadKind, payloadArgs?: PayloadArgs | null, ] /** `[baseTs, actors, roomEmojis, records]` */ export type LiveFullFrameWire = [ /** 基准时间戳,unix ms */ baseTs: number, actors: LiveFullActorWire[], roomEmojis: LiveFullRoomEmojiWire[], records: LiveFullRecordWire[], ] /** * A contributed recording, flattened. * `[liveId, startDate, stopDate, danmakusCount, userName, userId, * isOfficial, isFull, rangeDanmakusCount]` */ export type LiveFullVersionWire = [ liveId: string, startDate: number, stopDate: number, danmakusCount: number, userName: string, userId: number, isOfficial: boolean, isFull: boolean, rangeDanmakusCount: number, ] /** * Positional projection of the live's metadata. For the full typed metadata * prefer JSON `GET /api/v3/lives/{liveId}` ({@link LiveResponseV3}); this * drops `likeCount`/`payCount`/`onlineRank` and adds * `totalDanmakusCount`/`latestSeq`. */ export type LiveFullLiveWire = [ liveId: string, title: string, coverUrl: string, startDate: number, stopDate: number, isFinish: boolean, isFull: boolean, isMerged: boolean, /** 本帧弹幕数 */ danmakusCount: number, /** 所有版本合并后的弹幕总数 */ totalDanmakusCount: number, interactionCount: number, watchCount: number, /** 收益,单位 milli-yuan(/1000 = 元) */ totalIncomeMilli: number, maxOnlineCount: number, commentCount: number, area?: string | null, parentArea?: string | null, /** 仅当 `include=extra` 时存在 */ extra?: LiveFullExtraWire | null, versions?: LiveFullVersionWire[] | null, /** 最新序号;轮询 `/delta` 时作为下一次的 `afterSeq` */ latestSeq?: number, ] /** msgpack body of `/api/v3/lives/{liveId}/full`: `[channel, live, frame]` */ export type LiveFullEnvelopeWire = [channel: LiveFullChannelWire, live: LiveFullLiveWire, frame: LiveFullFrameWire] /** msgpack body of `/api/v3/lives/{liveId}/delta` — identical shape to {@link LiveFullEnvelopeWire} */ export type LiveDeltaEnvelopeWire = LiveFullEnvelopeWire /** `[id, liveId, uId, liveTitle, message, sendDate]` */ export type IndexBackgroundDanmakuCardWire = [ id: string, liveId: string, uId: number, liveTitle: string, message: string, sendDate: number, ] /** msgpack body of `/api/v3/meta/index-background`: `[cards]` */ export type IndexBackgroundDanmakuEnvelopeWire = [cards: IndexBackgroundDanmakuCardWire[]] } /** * 创建用户分析任务请求体(JSON) * @link https://ukamnads.icu/api/v3/user-analysis-jobs (POST,需鉴权) */ export interface CreateUserAnalyzeJobRequest { userId: number startTime?: number endTime?: number } /** Response of `POST /api/v3/user-analysis-jobs` */ export interface CreateUserAnalyzeJobResponse { jobId: string } /** 用户分析任务事件类型(SSE event name / `UserAnalyzeEvent.type`) */ export type UserAnalyzeEventType = | 'created' | 'queued' | 'indexing' | 'loading' | 'aggregating' | 'completed' | 'failed' | 'canceled' /** * 进度事件。通过 SSE 推送: * @link https://ukamnads.icu/api/v3/user-analysis-jobs/{jobId}/events * 也可轮询 `GET /api/v3/user-analysis-jobs/{jobId}` 获取最新状态。 */ export interface UserAnalyzeEvent { type: UserAnalyzeEventType message: string progress: number loadedLives: number totalLives: number loadedDanmakus: number } /** `UserAnalyzeSummary` */ export interface UserAnalyzeSummary { totalMessages: number /** 元 */ totalPayment: number blockCount: number activeDays: number } /** Item of `UserAnalyzeChannelResult.lives` */ export interface UserAnalyzeLiveResult { live: LiveItem danmakus: DanmakuProps[] } /** Item of `UserAnalyzeResult.channels` */ export interface UserAnalyzeChannelResult { channel: ChannelInfo lives: UserAnalyzeLiveResult[] } /** * 用户分析结果(msgpack,keyed map — 直接 decode 成对象) * @link https://ukamnads.icu/api/v3/user-analysis-jobs/{jobId}/result * * Fetch with `Accept: application/x-msgpack` and decode; unlike the live * endpoints this body is an ordinary keyed map, not a positional tuple. */ export interface UserAnalyzeResult { userId: number startTime: number endTime: number nameHistory: string[] wordCloud: Record summary: UserAnalyzeSummary channels: UserAnalyzeChannelResult[] } }