update: state, event, condition, data.event.types

This commit is contained in:
Vick Scarlet
2026-07-30 19:56:12 +08:00
parent 97988a95ba
commit 329a4e3a81
18 changed files with 1216 additions and 793 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "@remake/condition",
"name": "@remake/thirdparty-bili-toy",
"type": "module",
"version": "3.0.0",
"main": "./index.ts",
+484 -175
View File
@@ -1,194 +1,503 @@
declare namespace toy {
/**
* Toy JS SDK 类型声明(面向创作者)
*
* 用法:把本文件放进你的 Toy 项目(如 `types/toy.d.ts`),TypeScript 会自动加载,
* 无需 import 即可获得 `window.toy` 的类型检查与补全。
*
* 前置:页面 `<head>` 中引入
* <script src="//s1.hdslb.com/bfs/seed/toy/app/sdk/toy-sdk.js"></script>
* 加载后 SDK 把实例挂到全局 `window.toy`,所有方法返回 Promise。
*
* 通用约定:
* - 所有方法抛出的 Errormessage 统一带 `[ToySDK]` 前缀。
* - 数据类能力(用户 / 作者 / 视频互动)不抛错时通过 `status` 字段表达结果,
* 需要先判断 `status === 'ok'` 再读 `data` / `items`。
* - 云存储与排行榜失败时 Promise reject,需要 try/catch。
*/
declare namespace ToySDK {
// ---------------------------------------------------------------------------
// 页面跳转
// ---------------------------------------------------------------------------
/** 站内跳转的目标页面类型。 */
type NavigateType = 'video' | 'space' | 'search' | 'opus' | 'tribee' | 'toy'
interface NavigateReq {
/** 目标页面类型。 */
type: NavigateType
/**
* B站 Toy 平台开放能力名称联合类型
* 资源标识,随 `type` 变化:
* - video: BV 号,如 `BV1Hh411S7Ys`
* - space: 用户 mid
* - search: 搜索关键词(SDK 内部会做 URL 编码)
* - opus: 图文 / 动态 id
* - tribee: 小站 id
* - toy: toy id
*/
type Ability =
| 'navigate'
| 'saveImageToAlbum'
| 'closeBrowser'
| 'getUserProfile'
| 'reportAction'
| 'getCloudStorage'
| 'setCloudStorage'
| 'removeCloudStorage'
| 'getAuthorProfile'
| 'getAuthorVideos'
| 'getAuthorRelation'
| 'getVideoUserActions'
| 'submitScore'
| 'getRankList'
| 'getMyRank'
id: string
/** 额外查询参数,拼接到目标 URL 上透传给目标页面。 */
extra?: Record<string, string>
}
// ---------------------------------------------------------------------------
// 保存图片
// ---------------------------------------------------------------------------
/** `url` 与 `base64Data` 二选一,两者都不传则由客户端返回错误。 */
interface SaveImageReq {
/** 网络图片地址。 */
url?: string
/** base64 图片数据。 */
base64Data?: string
/** 申请相册权限时展示给用户的提示文案。 */
hintMsg?: string
}
interface SaveImageResp {
/** 保存后的本地文件路径,由客户端返回。 */
localPath: string
}
interface ReportActionReq {
/** UP 主自定义的动作标识。 */
userEventId: string
}
// ---------------------------------------------------------------------------
// 用户信息
// ---------------------------------------------------------------------------
interface UserProfileResp {
/** 头像地址,SDK 已统一归一化为 https 与 p0 CDN 域名。 */
avatar: string
/** 昵称。 */
nickname: string
}
// ---------------------------------------------------------------------------
// 数据类能力的状态枚举
// ---------------------------------------------------------------------------
/**
* 数据类能力的整体状态。
* - `ok`: 成功
* - `partial`: 批量请求部分成功,逐项状态见各 item 的 `status`
* - `unauthorized`: 未登录
* - `denied`: 用户拒绝了数据使用确认
* - `unsupported`: 当前环境不支持(如外部手机浏览器,SDK 会引导打开 B站 App)
* - `toy_context_unavailable`: 拿不到当前 toy 上下文
* - `author_mismatch`: 请求的资源不属于当前 Toy 作者
* - `video_not_found`: 视频不存在
* - `video_invisible`: 视频对当前用户不可见
* - `unavailable`: 依赖服务不可用
* - `invalid_argument`: 参数非法
*/
type ToyDataStatus =
| 'ok'
| 'partial'
| 'unauthorized'
| 'denied'
| 'unsupported'
| 'toy_context_unavailable'
| 'author_mismatch'
| 'video_not_found'
| 'video_invisible'
| 'unavailable'
| 'invalid_argument'
/** 批量结果中单项的状态:与 `ToyDataStatus` 相同,但不会是 `partial`。 */
type ToyItemStatus = Exclude<ToyDataStatus, 'partial'>
// ---------------------------------------------------------------------------
// 作者资料
// ---------------------------------------------------------------------------
/** 作者认证信息。`role` / `type` 为后端数字枚举,SDK 未定义其取值含义。 */
interface AuthorCertification {
role: number
title: string
description: string
type: number
}
/** 充电聚合信息。 */
interface ChargingSummary {
/** 充电人数。 */
count: number
display?: {
show: boolean
text?: string
}
}
/** 粉丝勋章配置。 */
interface FanMedalConfig {
/** 勋章名称。 */
name: string
/** 是否已开启粉丝勋章。 */
enabled: boolean
/** 各等级的亲密度区间;最高等级无上限时 `maxIntimacy` 缺省。 */
levels: Array<{
level: number
minIntimacy: number
maxIntimacy?: number
}>
}
interface AuthorProfile {
nickname: string
avatar: string
/** 个性签名。 */
sign: string
/** 未认证时缺省。 */
certification?: AuthorCertification
/** 关注数。 */
following: number
/** 粉丝数。 */
follower: number
/** 稿件数。 */
archiveCount: number
/** 未开通充电时缺省。 */
charging?: ChargingSummary
/** 未配置粉丝勋章时缺省。 */
fanMedal?: FanMedalConfig
/** 生日,时间戳;未公开时缺省。 */
birthday?: number
}
interface AuthorProfileResp {
status: ToyDataStatus
/** `status` 非 `ok` 时缺省。 */
data?: AuthorProfile
}
// ---------------------------------------------------------------------------
// 作者视频
// ---------------------------------------------------------------------------
/** 视频引用:每项只能传 `aid` 或 `bvid` 之一,同时传或都不传会本地抛错。 */
type AuthorVideoRef =
| { aid: number; bvid?: never }
| { bvid: string; aid?: never }
interface AuthorVideosReq {
/** 150 项;SDK 会按 aid/bvid 去重并保留首次出现顺序。 */
videos: AuthorVideoRef[]
}
interface AuthorVideo {
aid: number
bvid: string
title: string
/** 封面地址,SDK 已归一化 CDN 域名。 */
cover: string
description: string
/** 发布时间,时间戳。 */
publishTime: number
/** 总时长,单位由后端定义。 */
duration: number
/** 分区名称。 */
partition?: string
/** 分 P 列表。 */
pages: Array<{
page: number
title: string
duration: number
}>
/** 稿件统计数据。 */
stat: {
view: number
like: number
coin: number
favorite: number
share: number
comment: number
danmaku: number
}
/** 付费相关标记。 */
pay: {
/** 是否充电专属。 */
chargingPay: boolean
/** 是否付费稿件。 */
paid: boolean
}
/** 所属合集 id;不属于任何合集时缺省。 */
seasonId?: number
/** 排行信息;无排行数据时缺省。 */
rank?: {
now: number
highest: number
}
}
interface AuthorVideoItem {
/** 回显本次请求传入的引用,用于与请求项对应。 */
ref: AuthorVideoRef
status: ToyItemStatus
/** `status` 非 `ok` 时缺省(如非当前作者、视频不可见)。 */
data?: AuthorVideo
}
interface AuthorVideosResp {
/** 整体状态;部分项失败时为 `partial`。 */
status: ToyDataStatus
items: AuthorVideoItem[]
}
// ---------------------------------------------------------------------------
// 作者互动关系
// ---------------------------------------------------------------------------
interface AuthorRelation {
/** 当前访问用户是否已关注该作者。 */
isFollowing: boolean
/** 当前访问用户是否就是该 Toy 作者本人。 */
isAuthor: boolean
/** 是否为老粉。 */
isOldFan: boolean
/** 是否持有该作者的粉丝勋章。 */
hasFanMedal: boolean
/** 勋章名称;无勋章时缺省。 */
fanMedalName?: string
/** 勋章等级;无勋章时缺省。 */
fanMedalLevel?: number
/** 勋章是否处于点亮状态;无勋章时缺省。 */
isFanMedalActive?: boolean
/** 当前是否正在对该作者进行包月充电。 */
isCharging: boolean
/** 关注时间,时间戳;未关注时缺省。 */
followTime?: number
}
interface AuthorRelationResp {
status: ToyDataStatus
/** `status` 非 `ok` 时缺省。 */
data?: AuthorRelation
}
// ---------------------------------------------------------------------------
// 视频互动数据
// ---------------------------------------------------------------------------
interface VideoUserActionsReq {
/** 150 个正整数 aid;SDK 会去重并保留首次出现顺序。 */
aids: number[]
}
interface VideoUserActionItem {
aid: number
status: ToyItemStatus
/** 是否已点赞;`status` 非 `ok` 时缺省。 */
liked?: boolean
/** 已投币数;`status` 非 `ok` 时缺省。 */
coinCount?: number
/** 是否已收藏;`status` 非 `ok` 时缺省。 */
favorited?: boolean
}
interface VideoUserActionsResp {
/** 整体状态;部分项失败时为 `partial`。 */
status: ToyDataStatus
items: VideoUserActionItem[]
}
// ---------------------------------------------------------------------------
// 排行榜
// ---------------------------------------------------------------------------
/** 榜单周期:总榜(永久)/ 月 / 周 / 日。 */
type RankPeriod = 'all' | 'month' | 'week' | 'day'
interface SubmitScoreReq {
/** 榜位,固定 1 / 2 / 3(含义由 toy 自定义),不传默认 1;非法值本地抛错。 */
board?: number
/**
* 本次成绩的绝对分数,不是增量。整数,取值范围 -16777216 ~ 16777215
* 允许 0 与负数;超出范围本地抛错。服务端只保留该榜位的历史最高分。
*/
score: number
}
interface SubmitScoreResp {
/** 我的总榜(all)历史最高分,已合并本次提交。 */
score: number
}
interface RankListReq {
/** 榜位,固定 1 / 2 / 3,不传默认 1。 */
board?: number
/** 周期,不传按总榜 `all`。 */
period?: RankPeriod
/** 返回名次数量;不传或超上限按后端默认(≤100)。 */
limit?: number
}
/** 榜单单行:名次 + 历史最高分 + 展示用昵称/头像,不含 uid。 */
interface RankItem {
rank: number
score: number
nickname: string
/** 头像地址,SDK 已归一化 CDN 域名。 */
avatar: string
}
interface MyRankReq {
/** 榜位,固定 1 / 2 / 3,不传默认 1。 */
board?: number
/** 周期,不传按总榜 `all`。 */
period?: RankPeriod
}
interface MyRankResp {
/** 是否已上榜。分数允许 0 / 负,判断是否上榜必须用本字段,不能用 `score`。 */
ranked: boolean
/** 我的名次,从 1 起,唯一不并列(同分先达成者靠前);未上榜为 0。 */
rank: number
/** 我的历史最高分;未上榜为 0。 */
score: number
}
// ---------------------------------------------------------------------------
// 媒体能力
// ---------------------------------------------------------------------------
interface MediaRelayOptions {
/** 摄像头朝向,不传默认使用前置摄像头。 */
facingMode?: 'user' | 'environment'
}
// ---------------------------------------------------------------------------
// window.toy 的公开 API 面
// ---------------------------------------------------------------------------
interface Toy {
/**
* 判断当前环境是否支持指定能力。传能力名(如 `'saveImageToAlbum'`)即可,
* 带不带 `toy.` 前缀都能匹配。
*
* 端外 Web 不支持 `saveImageToAlbum` / `closeBrowser`,其余能力两端一致。
*/
isSupport(ability: string): Promise<boolean>
/**
* 判断当前 B站 App 宿主环境是否支持指定开放能力
* 跳转到指定页面。
*
* 必须在用户手势事件(如 click)中调用:SDK 会检查 `navigator.userActivation`
* 无有效用户激活时抛错。端内走 JSB 原生跳转,端外用 `window.open` 新开标签页。
*/
function isSupport(ability: Ability): Promise<boolean>
/** 跨页面/App组件跳转配置参数 */
type NavigateRequest = {
/** 目标页面类型:video(视频), space(空间), search(搜索), opus(图文动态), tribee(社区), toy(其它小游戏) */
type: 'video' | 'space' | 'search' | 'opus' | 'tribee' | 'toy'
/** 目标资源唯一标识 ID,如视频 BV 号、用户 mid、动态 id、游戏 id */
id: string
/** 额外附加参数,会作为 Query 或者是透传参数传递给目标承载页面 */
extra?: Record<string, string>
}
/**
* 跳转到指定 B站 App 原生或 H5 页面(注意:必须由用户手势或点击事件同步触发)
*/
function navigate(req: NavigateRequest): Promise<void>
/** 保存图片到相册的参数约束 */
type SaveImageToAlbumRequest = (
| {
/** 网络图片的绝对 URL 地址 */
url: string
base64?: never
}
| {
/** 带有 Data URI 前缀或纯 base64 的图片字符数据,体积最大硬限制 2M */
base64: string
url?: never
}
) & {
/** 客户端唤起操作系统申请相册写入权限时的引导提示文案 */
hintMsg?: string
}
/**
* 保存图片到系统相册(此 API 仅在 B站 App 宿主环境内环境生效)
*/
function saveImageToAlbum(
req: SaveImageToAlbumRequest,
): Promise<{ localPath: string }>
navigate(req: NavigateReq): Promise<void>
/**
* 关闭当前的 H5/小游戏浏览器容器,返回到上一级 App 原生界面
* 保存图片到系统相册。**仅 B站 App 内可用**,Web 端调用直接抛错。
*
* Web 端请改用标准浏览器下载能力(`<a download>` 或 canvas blob URL,需用户点击触发)。
*/
function closeBrowser(): Promise<void>
saveImageToAlbum(req: SaveImageReq): Promise<SaveImageResp>
/** 用户基本个人资料返回结果 */
type UserProfile = {
/** 用户 mid */
mid: string
/** 昵称 */
nickname: string
/** 头像绝对 URL 地址 */
avatar: string
/** 性别: 0-保密, 1-男, 2-女 */
gender: 0 | 1 | 2
}
/**
* 唤起 B站 原生授权弹窗,获取当前登录用户的公开个人资料
*/
function getUserProfile(): Promise<UserProfile>
/** 行为汇报参数配置 */
type ReportActionRequest = {
/** 行为埋点事件名/动作名 */
action: string
/** 汇报携带的属性字典 */
label?: Record<string, string | number>
}
/**
* 向 B站 开放平台数仓上报当前玩家的游戏行为埋点数据
*/
function reportAction(req: ReportActionRequest): Promise<void>
/** 获取云存储数据(托管数据)*/
function getCloudStorage<K extends string[]>(
keys?: [...K],
): Promise<{ [P in K[number]]?: string }>
/** 设置/写入云存储数据 */
function setCloudStorage(items: Record<string, string>): Promise<void>
/** 移除指定的云存储键值对 */
function removeCloudStorage(req: string[]): Promise<void>
/** UP主/作者档案资料 */
type AuthorProfile = {
mid: string
name: string
face: string
fans: number
}
/** 获取关联当前活动/游戏的UP主详细档案 */
function getAuthorProfile(): Promise<AuthorProfile>
/** 视频资产简单模型 */
type VideoItem = {
bvid: string
title: string
pic: string
play: number
}
/** 获取当前活动/游戏的关联视频列表 */
function getAuthorVideos(req: {
page: number
pageSize: number
}): Promise<{ list: VideoItem[]; total: number }>
/** 玩家与该关联UP主的关系状态 */
type AuthorRelation = {
/** 是否已关注该UP主 */
isFollowing: boolean
/** 是否是该UP主的特粉/大航海成员 */
isVipRelation: boolean
}
/** 获取当前登录玩家与目标UP主之间的社交关注链状态 */
function getAuthorRelation(): Promise<AuthorRelation>
/** 用户针对某条视频的互动行为状态 */
type VideoUserActions = {
like: boolean // 是否点赞
coin: boolean // 是否投币
fav: boolean // 是否收藏
share: boolean // 是否分享
}
/** 获取用户针对指定关联视频的点赞、投币、收藏、分享(三连)等原生行为状态 */
function getVideoUserActions(req: {
bvid: string
}): Promise<VideoUserActions>
/** 排行榜单项得分模型 */
type RankItem = {
rank: number
mid: string
nickname: string
avatar: string
score: number
}
/** 提交积分排行榜得分参数 */
type SubmitScoreRequest = {
/** 榜单唯一标识 ID */
leaderboardId: string
/** 当前局内取得的纯数字分数 */
score: number
/** 额外附加的自定义上下文字符串(如关卡详情等) */
extra?: string
}
/**
* 提交当前玩家的得分到 B站 开放平台官方托管的活动排行榜
*/
function submitScore(
req: SubmitScoreRequest,
): Promise<{ isNewRecord: boolean }>
/** 关闭当前 WebView 容器。**仅 B站 App 内可用**,Web 端调用直接抛错。 */
closeBrowser(): Promise<void>
/**
* 获取指定官方托管排行榜的分数列表
* 获取当前登录用户的头像与昵称。
*
* 首次调用需由用户手势触发,并由平台展示固定的用户数据确认弹窗(Toy 不能自定义弹窗内容);
* 用户拒绝、未登录或在外部手机浏览器中调用时 Promise reject(外部浏览器会先引导打开 B站 App)。
*/
function getRankList(req: {
leaderboardId: string
page: number
pageSize: number
}): Promise<{ list: RankItem[]; total: number }>
getUserProfile(): Promise<UserProfileResp>
/** 上报 UP 主自定义的用户动作。 */
reportAction(req: ReportActionReq): Promise<void>
/**
* 获取当前登录玩家自己在指定排行榜中的实时名次、分数等信息
* 获取当前 Toy 作者的公开资料、账号统计、稿件数、充电聚合与粉丝勋章配置。
* 不能指定作者,固定取当前 Toy 的作者。
*/
function getMyRank(req: { leaderboardId: string }): Promise<RankItem>
getAuthorProfile(): Promise<AuthorProfileResp>
/**
* 批量获取当前 Toy 作者的视频公开信息。
* 非当前作者或对当前用户不可见的视频,对应 item 只返回 `status`,不含 `data`。
*/
getAuthorVideos(req: AuthorVideosReq): Promise<AuthorVideosResp>
/**
* 获取当前访问用户与当前 Toy 作者的关注、老粉、粉丝勋章状态,
* 以及当前是否正在对该作者进行包月充电。
*
* 只校验登录态,不触发用户数据确认弹窗。外部手机浏览器返回 `status: 'unsupported'`
* 并引导打开 B站 App。
*/
getAuthorRelation(): Promise<AuthorRelationResp>
/**
* 获取当前访问用户对当前作者视频的点赞、投币、收藏状态。
*
* 只校验登录态,不触发用户数据确认弹窗。外部手机浏览器返回
* `status: 'unsupported'` 且 `items` 为空数组。
*/
getVideoUserActions(req: VideoUserActionsReq): Promise<VideoUserActionsResp>
/**
* 读取云存储。不传或传空数组读取当前用户在该 Toy 下的全部数据;
* 未命中的 key 不出现在结果中。
*
* 需用户已登录,按「登录用户 + Toy」双维度隔离,不触发用户数据确认。
* key 不满足 `[a-zA-Z0-9_-]{1,128}` 时本地抛错。
*/
getCloudStorage(keys?: string[]): Promise<Record<string, string>>
/**
* 批量写入云存储(upsert),同 key 覆盖旧值。
*
* `items` 必须是普通对象(传数组 / 字符串 / null 会本地抛错)。
* key 只能含字母、数字、下划线、短横线且 ≤128 字节;value 为字符串,
* 字节上限由服务端校验(存对象请自行 `JSON.stringify`)。
* 单个 Toy 的 key 数量上限由服务端拦截。
*/
setCloudStorage(items: Record<string, string>): Promise<void>
/** 批量删除云存储中指定的 key。key 格式非法时本地抛错。 */
removeCloudStorage(keys: string[]): Promise<void>
/**
* 上报分数到排行榜。需用户已登录,首次提交前由平台完成用户数据确认。
* 按「toy + 榜位 + 周期」隔离,同榜位只保留历史最高分,本次更低不覆盖。
*/
submitScore(req: SubmitScoreReq): Promise<SubmitScoreResp>
/**
* 读取榜单,游客可读。返回前 `limit` 名,固定从高到低;
* 同分时先达成者靠前,名次唯一、不并列。
*/
getRankList(req?: RankListReq): Promise<RankItem[]>
/** 查询我在指定榜单的排名。需用户已登录;是否上榜必须用 `ranked` 判断。 */
getMyRank(req?: MyRankReq): Promise<MyRankResp>
/**
* 申请摄像头业务授权和系统权限,并返回中继的媒体流。
* 必须在用户手势事件中调用。
*/
requestCamera(options?: MediaRelayOptions): Promise<MediaStream>
/**
* 申请麦克风业务授权和系统权限,并返回中继的媒体流。
* 不接受参数,且必须在用户手势事件中调用。
*/
requestMicrophone(): Promise<MediaStream>
/** 停止媒体中继并释放摄像头或麦克风设备。 */
stopMedia(stream: MediaStream): Promise<void>
}
}
// 全局声明。本文件是 ambient 声明文件(没有顶层 import / export),
// 放进项目后 TypeScript 自动加载,`window.toy` 与 `ToySDK.*` 均可直接使用。
interface Window {
/** 哔哩哔哩 Toy 开放平台(H5游戏容器/小程序)专属官方高级运行时 JavaScript-SDK 挂载点 */
readonly toy: typeof toy
/** Toy JS SDK 实例,由 toy-sdk.js 加载后挂载到全局。 */
toy: ToySDK.Toy
}
/** Toy JS SDK 实例(等价于 `window.toy`)。 */
declare const toy: ToySDK.Toy