mirror of
https://github.com/czp3009/bilibili-api.git
synced 2026-08-13 22:43:29 +08:00
refactor
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,5 @@
|
||||
target
|
||||
out
|
||||
build
|
||||
src/test/resources/config.json
|
||||
.gradle
|
||||
gradlew.bat
|
||||
|
||||
510
README.md
510
README.md
@@ -1,490 +1,70 @@
|
||||
# Bilibili API JVM 调用库
|
||||
该项目提供 Bilibili API 的 JVM 调用, 协议来自 Bilibili Android APP 的逆向工程以及截包分析.
|
||||
# Bilibili API library for Kotlin
|
||||
本项目封装一些 Bilibili API 以方便在 Kotlin 中使用(也可用于其他 JVM 语言).
|
||||
|
||||
协议来自对 Bilibili Android APP 的逆向工程以及截包分析.
|
||||
|
||||
使用一台虚拟的 `Pixel 2` 设备来截取数据包, 一些固定参数可能与真实设备不一致.
|
||||
|
||||
# 使用
|
||||
# 引入依赖
|
||||
RestFul API
|
||||
```groovy
|
||||
compile group: 'com.hiczp', name: 'bilibili-api', version: '0.1.0'
|
||||
compile group: 'com.hiczp', name: 'bilibili-api-rest', version: '0.2.0'
|
||||
```
|
||||
|
||||
# 技术说明
|
||||
`BilibiliClient` 类表示一个模拟的客户端, 实例化此类即表示打开了 Bilibili APP.
|
||||
Websocket(用于连接直播间弹幕推送服务器以获取实时弹幕)
|
||||
```groovy
|
||||
compile group: 'com.hiczp', name: 'bilibili-api-websocket', version: '0.2.0'
|
||||
```
|
||||
|
||||
所有调用从这个类开始, 包括登陆以及访问其他各种 API.
|
||||
|
||||
使用协程来实现异步, 由于 [kotlin coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) 为编译器实现, 因此并非所有 JVM 语言都能正确调用 `suspend` 方法.
|
||||
|
||||
本项目尽可能的兼容其他 JVM 语言和 Android, 不要问, 问就没测试过.
|
||||
|
||||
`BilibiliClient` 实例化时会记录一些信息, 例如初始化的事件, 用于更逼真的模拟真实客户端发送的请求. 因此请不要每次都实例化一个新的 `BilibiliClient` 实例, 而应该保存其引用.
|
||||
|
||||
一个客户端下各种不同类型的 API (代理类)都是惰性初始化的, 并且只初始化一次, 因此不需要保存 API 的引用, 例如以下代码是被推荐的:
|
||||
全部引入
|
||||
```groovy
|
||||
compile group: 'com.hiczp', name: 'bilibili-api', version: '0.2.0'
|
||||
```
|
||||
|
||||
# 获取直播间实时弹幕
|
||||
举个例子
|
||||
```kotlin
|
||||
runBlocking {
|
||||
val bilibiliClient = BilibiliClient().apply {
|
||||
login(username, password)
|
||||
}
|
||||
val myInfo = bilibiliClient.appAPI.myInfo().await()
|
||||
val reply = bilibiliClient.mainAPI.reply(oid = 44154463).await()
|
||||
}
|
||||
```
|
||||
|
||||
如果一个请求的返回内容中的 `code`(code 是 BODY 的内容, 并非 HttpStatus) 不为 0, 将抛出异常 `BilibiliApiException`, 通过以下代码来获取服务器原始返回的 `code`:
|
||||
|
||||
```kotlin
|
||||
val code = bilibiliApiException.commonResponse.code
|
||||
```
|
||||
|
||||
一个错误返回的原始 `JSON` 如下所示:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": -629,
|
||||
"message": "用户名与密码不匹配",
|
||||
"ts": 1550730464
|
||||
}
|
||||
```
|
||||
|
||||
每种不同的 API 在错误时返回的 `code` 丰富多彩(确信), 可能是正数也可能是负数, 可能上万也可能是个位数, 不要问, 问就是你菜.
|
||||
|
||||
# 登录和登出
|
||||
(Bilibili oauth2 v3)
|
||||
|
||||
登陆和登出均为异步方法, 需要在协程上下文中执行(接下去不会特地强调这一点).
|
||||
|
||||
```kotlin
|
||||
runBlocking {
|
||||
BilibiliClient().run {
|
||||
login(username, password)
|
||||
logout()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`login` 方法返回一个 `LoginResponse` 实例, 下次可以直接赋值到没有登陆的 `BilibiliClient` 实例中来恢复登陆状态.
|
||||
|
||||
```kotlin
|
||||
BilibiliClient().apply {
|
||||
this.loginResponse = loginResponse
|
||||
}
|
||||
```
|
||||
|
||||
`LoginResponse` 继承 `Serializable`, 可被序列化(JVM 序列化).
|
||||
|
||||
可能的错误返回有两种:
|
||||
|
||||
-629 用户名与密码不匹配
|
||||
-105 验证码错误
|
||||
|
||||
如果仅使用用户名与密码进行登陆并且得到了 `-105` 的结果, 那么说明需要验证码(通常是由于多次错误的登陆尝试导致的).
|
||||
|
||||
原始返回如下所示
|
||||
|
||||
{"ts":1550569982,"code":-105,"data":{"url":"https://passport.bilibili.com/register/verification.html?success=1>=b6e5b7fad7ecd37f465838689732e788&challenge=9a67afa4d42ede71a93aeaaa54a4b6fe&ct=1&hash=105af2e7cc6ea829c4a95205f2371dc5"},"message":"验证码错误!"}
|
||||
|
||||
自行访问 `commonResponse.data.obj.url.string` 打开一个极验弹窗, 完成滑动验证码后再次调用登陆接口:
|
||||
|
||||
```kotlin
|
||||
login(username, password, challenge, secCode, validate)
|
||||
```
|
||||
|
||||
`challenge` 为本次极验的唯一标识(在一开始给出的 url 中)
|
||||
|
||||
`validate` 为极验返回值
|
||||
|
||||
`secCode` 为 `"$validate|jordan"`
|
||||
|
||||
(注意, 极验会根据滑动的轨迹来识别人机, 所以要为最终用户打开一个 WebView 来进行真人操作而不能自动完成. 极验最终返回的是一个 jsonp, 里面包含以上三个参数, 详见极验接入文档).
|
||||
|
||||
注意, `BilibiliClient` 不能严格保证线程安全, 如果在登出的同时进行登录操作可能引发错误(想要这么做的人一定脑子瓦特了).
|
||||
|
||||
登陆后, 可以访问全部 API(注意, 有一些明显不需要登录的 API 也有可能需要登录).
|
||||
|
||||
由于各种需要登陆的 API 在未登录时返回的 `code` 并不统一, 因此没有办法做自动 `token` 刷新, 自己看着办.
|
||||
|
||||
在真实的客户端上, 每次一打开 APP 就会访问[个人信息 API](#获取个人信息)来确定 `token` 是否仍然可用, 这就是 B站 自己的解决方案.
|
||||
|
||||
# 访问 API
|
||||
不要问文档, 用自动补全(心)来感受. 以下给出几个示例
|
||||
|
||||
## 获取个人信息
|
||||
(首先要登陆)
|
||||
|
||||
```kotlin
|
||||
val myInfo = bilibiliClient.appAPI.myInfo().await()
|
||||
```
|
||||
|
||||
返回用户 ID, vip 信息等.
|
||||
|
||||
## 搜索
|
||||
当我们想看某些内容时, 我们会首先使用搜索功能, 例如
|
||||
|
||||
```kotlin
|
||||
val searchResult = bilibiliClient.appAPI.search(keyword = "刀剑神域").await()
|
||||
```
|
||||
|
||||
实际上这对应客户端上的 搜索 -> 综合.
|
||||
|
||||
如果要搜索番剧则使用 `bilibiliClient.appAPI.searchBangumi`.
|
||||
|
||||
同理, 搜索直播, 用户, 影视, 专栏分别使用 `searchLive`, `searchUser`, `searchMovie`, `searchArticle`.
|
||||
|
||||
所有的搜索都使用 `pageNumber` 参数来控制翻页(从 1 开始).
|
||||
|
||||
## 获取视频播放地址
|
||||
获取视频实际播放地址的 API 比较特殊, 被单独分了出来, 示例如下
|
||||
|
||||
```kotlin
|
||||
val videoPlayUrl = bilibiliClient.playerAPI.videoPlayUrl(aid = 41517911, cid = 72913641).await()
|
||||
```
|
||||
|
||||
`aid` 即 av 号, 只能表示视频播放的那个页面, 如果一个视频有多个 `p`, 那么每个 `p` 都有单独的 `cid`.
|
||||
|
||||
在 Web 端, URL 通常是这样的
|
||||
|
||||
https://www.bilibili.com/video/av44541340/?p=2
|
||||
|
||||
实际上就是选择了该 `aid` 下的第二个 `cid`(注意, 参数里使用的 `cid` 不是这个 p 的序号, 它也是一个很长的数字).
|
||||
|
||||
简单的来说, `aid` 和 `cid` 加在一起才能表示一个视频流(为什么 `cid` 不能直接表示一个视频我也不知道).
|
||||
|
||||
因此无论是获取视频播放地址, 还是获取弹幕列表, 都要同时传入 `aid` 与 `cid`.
|
||||
|
||||
而 `cid` 在哪里获得呢, 如下所示
|
||||
|
||||
```kotlin
|
||||
val view = bilibiliClient.appAPI.view(aid = 41517911).await()
|
||||
```
|
||||
|
||||
该接口返回对一个视频页面的描述信息(甚至包含广告和推荐), 客户端根据这些信息生成视频页面.
|
||||
|
||||
其中 `data.cid` 为默认 `p` 的 `cid`. `data.pages[n].cid` 为每个 `p` 的 `cid`. 如果只有一个 `p` 那么说明视频没有分 `p`.
|
||||
|
||||
请求视频地址将访问如下结构的内容
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"accept_description": [
|
||||
"高清 1080P+",
|
||||
"高清 1080P",
|
||||
"高清 720P",
|
||||
"清晰 480P",
|
||||
"流畅 360P"
|
||||
],
|
||||
"accept_format": "hdflv2,flv,flv720,flv480,flv360",
|
||||
"accept_quality": [
|
||||
112,
|
||||
80,
|
||||
64,
|
||||
32,
|
||||
16
|
||||
],
|
||||
"dash": {
|
||||
"audio": [
|
||||
{
|
||||
"bandwidth": 319173,
|
||||
"base_url": "http://upos-hz-mirrorks3u.acgvideo.com/upgcxcode/18/58/77995818/77995818-1-30280.m4s?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEuENvNC8aNEVEtEvE9IMvXBvE2ENvNCImNEVEIj0Y2J_aug859r1qXg8xNEVE5XREto8GuFGv2U7SuxI72X6fTr859IB_&deadline=1551113319&gen=playurl&nbs=1&oi=3670888782&os=ks3u&platform=android&trid=925269b941bf4883ac9ec92c6ab5af4e&uipk=5&upsig=33273eaf403739d9f51304509f55589e",
|
||||
"codecid": 0,
|
||||
"id": 30280
|
||||
},
|
||||
{
|
||||
"bandwidth": 67326,
|
||||
"base_url": "http://upos-hz-mirrorkodou.acgvideo.com/upgcxcode/18/58/77995818/77995818-1-30216.m4s?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEuENvNC8aNEVEtEvE9IMvXBvE2ENvNCImNEVEIj0Y2J_aug859r1qXg8xNEVE5XREto8GuFGv2U7SuxI72X6fTr859IB_&deadline=1551113319&gen=playurl&nbs=1&oi=3670888782&os=kodou&platform=android&trid=925269b941bf4883ac9ec92c6ab5af4e&uipk=5&upsig=3d1f9b836430bb8033b2f318faf42f9b",
|
||||
"codecid": 0,
|
||||
"id": 30216
|
||||
}
|
||||
],
|
||||
"video": [
|
||||
{
|
||||
"bandwidth": 376693,
|
||||
"base_url": "http://upos-hz-mirrorks3u.acgvideo.com/upgcxcode/18/58/77995818/77995818-1-30015.m4s?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEuENvNC8aNEVEtEvE9IMvXBvE2ENvNCImNEVEIj0Y2J_aug859r1qXg8xNEVE5XREto8GuFGv2U7SuxI72X6fTr859IB_&deadline=1551113319&gen=playurl&nbs=1&oi=3670888782&os=ks3u&platform=android&trid=925269b941bf4883ac9ec92c6ab5af4e&uipk=5&upsig=82bc845bce9f22b731b062bf83fa000f",
|
||||
"codecid": 7,
|
||||
"id": 16
|
||||
},
|
||||
...
|
||||
{
|
||||
"bandwidth": 2615324,
|
||||
"base_url": "http://upos-hz-mirrorcosu.acgvideo.com/upgcxcode/18/58/77995818/77995818-1-30080.m4s?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEuENvNC8aNEVEtEvE9IMvXBvE2ENvNCImNEVEIj0Y2J_aug859r1qXg8xNEVE5XREto8GuFGv2U7SuxI72X6fTr859IB_&deadline=1551113319&dynamic=1&gen=playurl&oi=3670888782&os=cosu&platform=android&rate=0&trid=925269b941bf4883ac9ec92c6ab5af4e&uipk=5&uipv=5&um_deadline=1551113319&um_sign=22fef3c0efa0d23388429f6926fad298&upsig=c4768c036beb667ba4648369770f8de8",
|
||||
"codecid": 7,
|
||||
"id": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"fnval": 16,
|
||||
"fnver": 0,
|
||||
"format": "flv480",
|
||||
"from": "local",
|
||||
"quality": 32,
|
||||
"result": "suee",
|
||||
"seek_param": "start",
|
||||
"seek_type": "offset",
|
||||
"timelength": 175332,
|
||||
"video_codecid": 7,
|
||||
"video_project": true
|
||||
},
|
||||
"message": "0",
|
||||
"ttl": 1
|
||||
}
|
||||
```
|
||||
|
||||
(由于内容太长, 去除了一部分内容)
|
||||
|
||||
注意, 视频下载地址有好几个(以上返回内容中被折叠成了两个), 但是实际上他们都是一样的内容, 只是清晰度不同. `data.dash.video.id` 实际上代表 `data.accept_quality`.
|
||||
|
||||
视频和音频是分开的, 视频和音频都返回 `m4s` 文件, 将其合并即可得到完整的 `mp4` 文件.
|
||||
|
||||
`data.quality` 指默认选择的清晰度, 通常情况下移动网络会自动选择 `32`, 即 "清晰 480P"(在 `data.accept_description` 中对应).
|
||||
|
||||
对于番剧来说, 也使用 `aid` 与 `cid` 来获得播放地址
|
||||
|
||||
```kotlin
|
||||
val bangumiPlayUrl = bilibiliClient.playerAPI.bangumiPlayUrl(aid = 42714241, cid = 74921228).await()
|
||||
```
|
||||
|
||||
返回内容差不多是一个原理, 这里就不赘述了.
|
||||
|
||||
如何获得番剧的 `aid` 与 `cid` 呢. 我们都知道, 实际上番剧那个页面的唯一标识是 "季", 同一个番的不同 "季" 其实是不同的东西.
|
||||
|
||||
我们在番剧搜索页面可以得到番剧的 `season`, 这代表了一个番剧的某一季的页面.
|
||||
|
||||
然后我们用 `season` 来打开番剧页面.
|
||||
|
||||
```kotlin
|
||||
val season = bilibiliClient.mainAPI.season(seasonId = 25617).await()
|
||||
```
|
||||
|
||||
返回值中的 `result.seasons[n].season_id` 为该番所有季的 id(包含用来作为查询条件的 `seasonId`).
|
||||
|
||||
该 API 还可以用 `episodeId` 作为查询条件, 即以集为条件打开一个番剧页面(会跳转到对应的季).
|
||||
|
||||
返回值中的 `result.episodes` 包含了当前所选择的季的全部集的 `aid` 与 `cid`.
|
||||
|
||||
## 查看视频下面的评论
|
||||
看完了视频当然要看一下傻吊网友都在说些什么. 使用以下 API 获取一个视频的评论.
|
||||
|
||||
```kotlin
|
||||
val reply = bilibiliClient.mainAPI.reply(oid = 44154463).await()
|
||||
```
|
||||
|
||||
这里的 `oid` 指 `aid`(其他一些 API 中 `oid` 也可能指 `cid` 详见方法上面的注释).
|
||||
|
||||
评论是不分 `p` 的, 所有评论都是在一起的.
|
||||
|
||||
可以额外使用一个 `next` 参数来指定返回的起始楼层(即翻页).
|
||||
|
||||
楼层是越翻越小的, 所以 `next` 也要越来越小.
|
||||
|
||||
看到了傻吊网友们的评论是不够的, 我们还想看到杠精与其隔着屏幕对喷的场景, 因此我们要获取评论的子评论, 即评论的评论
|
||||
|
||||
```kotlin
|
||||
val childReply = bilibiliClient.mainAPI.childReply(oid = 16622855, root = 1405602348).await()
|
||||
```
|
||||
|
||||
其中的 `root` 表示根评论的 id.
|
||||
|
||||
每个评论都有自己的 `replyId`, `parentId` 以及 `rootId`.
|
||||
|
||||
假如一个人在一个评论的子评论里发布了一个评论并且 at 了其他人发的评论, 那么其 `parentId` 是他所 at 的评论, 其 `rootId` 为所在的根评论.
|
||||
|
||||
如果不满足对应的层级逻辑关系(例如本身为根评论), `parentId` 或 `rootId` 可能为 0.
|
||||
|
||||
用额外的 `minId` 参数来指定返回的起始子楼层.
|
||||
|
||||
注意, 子楼层是越翻越大的.
|
||||
|
||||
如果一个根评论下面有很多个喷子在互喷, 会导致看不清, 客户端上有一个按钮 "查看对话" 就是解决这个问题的.
|
||||
|
||||
```kotlin
|
||||
val chatList = bilibiliClient.mainAPI.chatList(oid = 34175504, root = 1136310360, dialog = 1136351035).await()
|
||||
```
|
||||
|
||||
`root` 为根评论 ID, `dialog` 为父评论 ID.
|
||||
|
||||
用 `minFloor` 控制分页, 原理同上.
|
||||
|
||||
番剧下面的评论用一样的方式获取.
|
||||
|
||||
## 获得一个视频的弹幕
|
||||
看评论自然不够刺激, 我们想看到弹幕!
|
||||
|
||||
获取弹幕非常简单
|
||||
|
||||
```kotlin
|
||||
val danmakuFile = bilibiliClient.danmakuAPI.list(aid = 810872, oid = 1176840).await()
|
||||
```
|
||||
|
||||
弹幕是一个文件, 可能非常大, 里面是二进制内容.
|
||||
|
||||
为了解析弹幕, 我们要用到另一个类
|
||||
|
||||
```kotlin
|
||||
val (flagMap, danmakuList) = DanmakuParser.parser(danmakuFile.byteStream())
|
||||
```
|
||||
|
||||
`flagMap` 类型为 `Map<Long, Int>` 键和值分别表示 弹幕ID 与 弹幕等级.
|
||||
|
||||
弹幕等级在区间 \[1, 10\] 内, 低于客户端设置的 "弹幕云屏蔽等级" 的弹幕将不会显示出来.
|
||||
|
||||
`danmakuList` 类型为 `List<Danmaku>`, 内含所有解析得到的弹幕.
|
||||
|
||||
使用以下代码来输出全部弹幕的内容
|
||||
|
||||
```kotlin
|
||||
danmakuList.forEach {
|
||||
println(it.content)
|
||||
}
|
||||
```
|
||||
|
||||
注意, 弹幕的解析是惰性的, `danmakuList` 是一个 `Sequence`. 如果同时持有很多未用完的 `danmakuList` 的引用可能会造成大量内存浪费.
|
||||
|
||||
客户端的弹幕屏蔽设置是对弹幕中的 `user` 属性做的. 而实际上 `danmaku.user` 是一个字符串.
|
||||
|
||||
这个字符串是 用户ID 的 `CRC32` 的校验和.
|
||||
|
||||
众所周知, 一切 hash 算法都有冲突的问题. 这也就意味着, 屏蔽一个用户的同时可能屏蔽掉了多个与该用户 hash 值相同的用户.
|
||||
|
||||
在另一方面, 通过这个 `CRC32` 校验和进行用户 ID 反查, 将查询到多个可能的用户, 因此无法完全确定一条弹幕到底是哪个用户发送的.
|
||||
|
||||
如果想获得发送这条弹幕的所有可能的用户的 ID, 可以通过以下方法:
|
||||
|
||||
```kotlin
|
||||
val possibleUserIds = danmaku.calculatePossibleUserIds()
|
||||
```
|
||||
|
||||
返回一个 `List<Int>`, 内容为所有可能的用户 ID(至少有一个).
|
||||
|
||||
注意, 第一次使用 `CRC反查` 功能将花费大约 `300ms` 来生成彩虹表, 如果想手动初始化请使用以下代码
|
||||
|
||||
```kotlin
|
||||
Crc32Cracker
|
||||
```
|
||||
|
||||
(`Crc32Cracker` 是一个惰性初始化的单例)
|
||||
|
||||
通常情况下, 一次 `CRC反查` 耗时大约 `1ms`.
|
||||
|
||||
由于这是一个比较耗时的操作, 请不要每条弹幕都如此操作(相比较 6000 条弹幕的解析只需要 `150ms`).
|
||||
|
||||
番剧的弹幕同理.
|
||||
|
||||
## 发送视频弹幕
|
||||
光看不发憋着慌, 我们来发送一条视频弹幕:
|
||||
|
||||
```kotlin
|
||||
bilibiliClient.mainAPI.sendDanmaku(aid = 40675923, cid = 71438168, progress = 2297, message = "2333").await()
|
||||
```
|
||||
|
||||
其中 `progress` 是播放器时间, 其他观众将看到你的弹幕在视频的此处出现, 单位为毫秒.
|
||||
|
||||
`message` 应该是有长度限制的, 但是没有测过.
|
||||
|
||||
如果不确定视频的长度, 需要从[视频播放地址的 API](#获取视频播放地址) 中的 `data.timelength` 来获得, 单位也是毫秒.
|
||||
|
||||
## 获取直播弹幕
|
||||
刚进入直播间时, 立即看到的十条弹幕实际上是最近的历史弹幕, 通过以下方式来获取
|
||||
|
||||
```kotlin
|
||||
bilibiliClient.liveAPI.roomMessage(roomId).await()
|
||||
```
|
||||
|
||||
接下来的弹幕都是实时弹幕, 直播间实时弹幕通过 `Websocket` 来推送.
|
||||
|
||||
```kotlin
|
||||
val job = bilibiliClient.liveClient(roomId = 3) {
|
||||
onConnect = {
|
||||
println("Connected")
|
||||
}
|
||||
|
||||
onPopularityPacket = { _, popularity ->
|
||||
println("Current popularity: $popularity")
|
||||
}
|
||||
|
||||
onCommandPacket = { _, jsonObject ->
|
||||
println(jsonObject)
|
||||
}
|
||||
|
||||
onClose = { _, closeReason ->
|
||||
println(closeReason)
|
||||
}
|
||||
}.launch()
|
||||
```
|
||||
|
||||
服务器推送的 `Message` 有两种, 一种是 `人气值` 数据, 另一种是 `Command` 数据.
|
||||
|
||||
`Command` 数据包用于控制客户端渲染何种内容. 弹幕, 送礼, 系统公告等全部都是由 `Command` 数据包控制的, 其本体为一个 `JsonObject`.
|
||||
|
||||
例如一个弹幕数据是这样的(`cmd` 字段的值为 `DANMU_MSG`):
|
||||
|
||||
```json
|
||||
{"cmd":"DANMU_MSG","info":[[0,1,25,16777215,1553417856,1553414245,0,"9e539d78",0,0,0],"记得存档!",[3432444,"喵的叫一声",0,0,0,10000,1,""],[6,"日常","奶粉の日常",35399,5805790,""],[22,0,5805790,">50000"],["",""],0,0,null,{"ts":1553417856,"ct":"87255D9C"}]}
|
||||
```
|
||||
|
||||
`Welcome` 的数据是这样的
|
||||
```json
|
||||
{"cmd":"WELCOME","data":{"uid":110208099,"uname":"霸刀宋壹i","is_admin":false,"svip":1}}
|
||||
```
|
||||
|
||||
各种 `Command` 数据包的结构经常改变, 因此不提供实体类.
|
||||
|
||||
由于 `DANMU_MSG` 的数据结构太过意识流, 因此提供了额外的辅助工具来方便地解析它.
|
||||
|
||||
`DanmakuMessage` 是一个 `inline class` 请不要对其进行太过复杂的操作.
|
||||
|
||||
```kotlin
|
||||
onCommandPacket = { _, jsonObject ->
|
||||
val cmd by jsonObject.byString
|
||||
println(
|
||||
if (cmd == "DANMU_MSG") {
|
||||
with(DanmakuMessage(jsonObject)) {
|
||||
"${if (fansMedalInfo.isNotEmpty()) "[$fansMedalName $fansMedalLevel] " else ""}[UL$userLevel] $nickname: $message"
|
||||
val liveClient = LiveClient(roomId = 23058) {
|
||||
resolvedPackets.consumeEach {
|
||||
when (it) {
|
||||
is CommandPacket -> {
|
||||
val command = it.content
|
||||
println("[${command.cmd}] $command")
|
||||
}
|
||||
is PopularityPacket -> {
|
||||
println("Popularity: ${it.content}")
|
||||
}
|
||||
} else {
|
||||
jsonObject.toString()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
runBlocking {
|
||||
liveClient.connect()
|
||||
}
|
||||
```
|
||||
|
||||
输出:
|
||||
`resolvedPackets` 是一个输送解析后的数据包的 `Channel`, 解析后的数据包有 `CommandPacket` 以及 `PopularityPacket` 两种类型.
|
||||
|
||||
```
|
||||
[甜甜天 7] [UL25] czp3009: 233
|
||||
```
|
||||
`CommandPacket` 的本体是一段 JSON, 由于其内容经常发生改变, 所以不提供 POJO.
|
||||
|
||||
更多 `Command` 数据包的数据结构详见本项目的 [/record/直播弹幕](record/直播弹幕) 文件夹.
|
||||
`Command.cmd` 是一个快捷方式, 可以快速取得其中的 `cmd` 字段从而判断其类别.
|
||||
|
||||
注意, `onPopularityPacket`, `onCommandPacket` 这些回调不能进行耗时操作.
|
||||
作为样本的 JSON 数据在本项目 `/record` 文件夹下.
|
||||
|
||||
关闭连接
|
||||
大部分 JSON 都有光怪陆离的数据结构. 其中 `DANMU_MSG` 尤为恶劣, 通篇都是数组. 为了方便对其解析, 故提供内联类 `DanmakuMessage`.
|
||||
|
||||
使用方法如下
|
||||
|
||||
```kotlin
|
||||
job.cancel()
|
||||
val command = commandPacket.content
|
||||
val danmakuMessage = command.asDanmakuMessage()
|
||||
with(danmakuMessage) {
|
||||
println("$nickname: $message")
|
||||
}
|
||||
```
|
||||
|
||||
## 发送直播弹幕
|
||||
在直播间里发送弹幕也非常简单(必须先登陆)
|
||||
`PopularityPacket` 的本体是一个 `Int` 数字, 表示当前房间的人气值. 该数据包每 30秒 收到一次.
|
||||
|
||||
```kotlin
|
||||
liveClient.sendMessage("我上我也行").await()
|
||||
```
|
||||
如果要为读取操作设定超时, 可以设定为 40秒.
|
||||
|
||||
注意, 除了弹幕超长(普通用户为 20 个 Unicode 字符, 老爷, 会员可以额外加长)会导致抛出异常, 其他情况都会正常返回(`code` 为 0).
|
||||
|
||||
完全正常返回时(弹幕正确的被发送了), 返回内容中的 `message` 为一个空字符串.
|
||||
|
||||
如果不为空字符串, 则表示不完全正常
|
||||
|
||||
例如返回内容的 `message` 为 "msg repeat" 则表示短时间重复发送相同的弹幕而被服务器拒绝, 但是返回的 `code` 确实是 0.
|
||||
|
||||
其他情况诸如包含特殊字符, 包含不文明词语等均会导致不完全正常的返回.
|
||||
|
||||
正常返回时, 就算不完全正常, 客户端也会将这条弹幕显示到屏幕上, 如果不是完全正常的, 那么这条弹幕就只有自己能看见(刷新后也会消失).
|
||||
|
||||
需要额外判断返回的 `message` 是否为空字符串来确认这条弹幕有没有被正确发送.
|
||||
注意: 如果使用短房间号来连接弹幕推送服务器, 可能会得不到正确的人气值信息(一直为 0 或者一直为 1). 因此在连接弹幕推送服务器前应当首先获取直播间基本信息. 同时, 弹幕服务器不是唯一的, 在构造 `LiveClient` 时可以传入从房间基本信息中获取到的其他服务器地址.
|
||||
|
||||
# License
|
||||
GPL V3
|
||||
|
||||
230
build.gradle
230
build.gradle
@@ -1,9 +1,9 @@
|
||||
buildscript {
|
||||
ext {
|
||||
kotlin_version = '1.3.21'
|
||||
kotlin_coroutines_version = '1.1.1'
|
||||
ktor_version = '1.1.3'
|
||||
kotlin_version = '1.3.31'
|
||||
kotlin_coroutines_version = '1.2.1'
|
||||
jvm_target = JavaVersion.VERSION_1_8
|
||||
ktor_version = '1.1.4'
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -15,136 +15,132 @@ buildscript {
|
||||
}
|
||||
}
|
||||
|
||||
group = 'com.hiczp'
|
||||
version = '0.1.0'
|
||||
description = 'Bilibili Android client API library for Kotlin'
|
||||
allprojects {
|
||||
group = 'com.hiczp'
|
||||
version = '0.2.0'
|
||||
description = 'Bilibili API library for Kotlin'
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
//kotlin
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-stdlib-jdk8
|
||||
compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8'
|
||||
// https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core
|
||||
compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: kotlin_coroutines_version
|
||||
}
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = jvm_target
|
||||
freeCompilerArgs = ["-Xjvm-default=enable", "-Xuse-experimental=kotlin.Experimental", "-XXLanguage:+InlineClasses"]
|
||||
}
|
||||
}
|
||||
compileTestKotlin {
|
||||
kotlinOptions.jvmTarget = jvm_target
|
||||
}
|
||||
|
||||
//logging
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/io.github.microutils/kotlin-logging
|
||||
compile group: 'io.github.microutils', name: 'kotlin-logging', version: '1.6.25'
|
||||
// https://mvnrepository.com/artifact/org.slf4j/slf4j-simple
|
||||
testCompile group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.26'
|
||||
}
|
||||
|
||||
//http
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/com.squareup.retrofit2/retrofit
|
||||
compile group: 'com.squareup.retrofit2', name: 'retrofit', version: '2.5.0'
|
||||
// https://mvnrepository.com/artifact/com.squareup.retrofit2/converter-gson
|
||||
compile group: 'com.squareup.retrofit2', name: 'converter-gson', version: '2.5.0'
|
||||
// https://mvnrepository.com/artifact/com.github.salomonbrys.kotson/kotson
|
||||
compile group: 'com.github.salomonbrys.kotson', name: 'kotson', version: '2.5.0'
|
||||
// https://mvnrepository.com/artifact/com.jakewharton.retrofit/retrofit2-kotlin-coroutines-adapter
|
||||
compile group: 'com.jakewharton.retrofit', name: 'retrofit2-kotlin-coroutines-adapter', version: '0.9.2'
|
||||
// https://mvnrepository.com/artifact/com.squareup.okhttp3/logging-interceptor
|
||||
compile group: 'com.squareup.okhttp3', name: 'logging-interceptor', version: '3.14.0'
|
||||
}
|
||||
|
||||
//ktor
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/io.ktor/ktor-client-websocket
|
||||
compile group: 'io.ktor', name: 'ktor-client-websocket', version: ktor_version
|
||||
// https://mvnrepository.com/artifact/io.ktor/ktor-client-cio
|
||||
compile group: 'io.ktor', name: 'ktor-client-cio', version: ktor_version
|
||||
}
|
||||
|
||||
//checksum
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/com.hiczp/crc32-crack
|
||||
compile group: 'com.hiczp', name: 'crc32-crack', version: '1.0'
|
||||
}
|
||||
|
||||
//unit test
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter
|
||||
testCompile group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.4.1'
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
from sourceSets.main.allSource
|
||||
archiveClassifier = 'sources'
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar) {
|
||||
from javadoc
|
||||
archiveClassifier = 'javadoc'
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
|
||||
credentials {
|
||||
username = project.properties.ossUsername
|
||||
password = project.properties.ossPassword
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
force "io.ktor:ktor-client-cio:$ktor_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
//kotlin
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-stdlib-jdk8
|
||||
api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8'
|
||||
// https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core
|
||||
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: kotlin_coroutines_version
|
||||
}
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = jvm_target
|
||||
freeCompilerArgs = ["-Xjvm-default=enable", "-Xuse-experimental=kotlin.Experimental", "-XXLanguage:+InlineClasses"]
|
||||
}
|
||||
}
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = jvm_target
|
||||
freeCompilerArgs = ["-Xjvm-default=enable", "-Xuse-experimental=kotlin.Experimental", "-XXLanguage:+InlineClasses"]
|
||||
}
|
||||
}
|
||||
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
artifact sourcesJar
|
||||
artifact javadocJar
|
||||
//logging
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/io.github.microutils/kotlin-logging
|
||||
api group: 'io.github.microutils', name: 'kotlin-logging', version: '1.6.26'
|
||||
// https://mvnrepository.com/artifact/org.slf4j/slf4j-simple
|
||||
testImplementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.26'
|
||||
}
|
||||
|
||||
pom {
|
||||
name = project.name
|
||||
description = project.description
|
||||
url = 'https://github.com/czp3009/bilibili-api'
|
||||
//unit test
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter
|
||||
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.4.2'
|
||||
}
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name = 'GNU GENERAL PUBLIC LICENSE Version 3'
|
||||
url = 'https://www.gnu.org/licenses/gpl-3.0.txt'
|
||||
}
|
||||
task sourcesJar(type: Jar) {
|
||||
from sourceSets.main.allSource
|
||||
archiveClassifier = 'sources'
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar) {
|
||||
from javadoc
|
||||
archiveClassifier = 'javadoc'
|
||||
}
|
||||
|
||||
publishing {
|
||||
def moduleName = "${rootProject.name}-${project.name}"
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
|
||||
credentials {
|
||||
username = project.properties.ossUsername
|
||||
password = project.properties.ossPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
developers {
|
||||
developer {
|
||||
id = 'czp3009'
|
||||
name = 'czp3009'
|
||||
email = 'czp3009@gmail.com'
|
||||
url = 'https://www.hiczp.com'
|
||||
}
|
||||
}
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
artifact sourcesJar
|
||||
artifact javadocJar
|
||||
artifactId moduleName
|
||||
|
||||
scm {
|
||||
connection = 'scm:git:git://github.com/czp3009/bilibili-api.git'
|
||||
developerConnection = 'scm:git:ssh://github.com/czp3009/bilibili-api.git'
|
||||
pom {
|
||||
name = moduleName
|
||||
description = project.description
|
||||
url = 'https://github.com/czp3009/bilibili-api'
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name = 'GNU GENERAL PUBLIC LICENSE Version 3'
|
||||
url = 'https://www.gnu.org/licenses/gpl-3.0.txt'
|
||||
}
|
||||
}
|
||||
|
||||
developers {
|
||||
developer {
|
||||
id = 'czp3009'
|
||||
name = 'czp3009'
|
||||
email = 'czp3009@gmail.com'
|
||||
url = 'https://www.hiczp.com'
|
||||
}
|
||||
}
|
||||
|
||||
scm {
|
||||
connection = 'scm:git:git://github.com/czp3009/bilibili-api.git'
|
||||
developerConnection = 'scm:git:ssh://github.com/czp3009/bilibili-api.git'
|
||||
url = 'https://github.com/czp3009/bilibili-api'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
sign publishing.publications.mavenJava
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
sign publishing.publications.mavenJava
|
||||
dependencies {
|
||||
api project(':rest')
|
||||
api project(':websocket')
|
||||
}
|
||||
|
||||
jar {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.3-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"cmd": "ROOM_REAL_TIME_MESSAGE_UPDATE",
|
||||
"data": {
|
||||
"roomid": 23058,
|
||||
"fans": 300958
|
||||
"fans": 297141,
|
||||
"red_notice": -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
rest/.gitignore
vendored
Normal file
1
rest/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
src/test/resources/config.json
|
||||
19
rest/build.gradle
Normal file
19
rest/build.gradle
Normal file
@@ -0,0 +1,19 @@
|
||||
//http
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/com.hiczp/caeruleum
|
||||
api group: 'com.hiczp', name: 'caeruleum', version: '1.1.0'
|
||||
}
|
||||
|
||||
//json
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/com.google.code.gson/gson
|
||||
api group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
|
||||
// https://mvnrepository.com/artifact/com.github.salomonbrys.kotson/kotson
|
||||
api group: 'com.github.salomonbrys.kotson', name: 'kotson', version: '2.5.0'
|
||||
}
|
||||
|
||||
//checksum
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/com.hiczp/crc32-crack
|
||||
api group: 'com.hiczp', name: 'crc32-crack', version: '1.0'
|
||||
}
|
||||
2
rest/src/main/kotlin/com/hiczp/bilibili/rest/Empty.kt
Normal file
2
rest/src/main/kotlin/com/hiczp/bilibili/rest/Empty.kt
Normal file
@@ -0,0 +1,2 @@
|
||||
package com.hiczp.bilibili.rest
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
rootProject.name='bilibili-api'
|
||||
rootProject.name = 'bilibili-api'
|
||||
|
||||
include 'rest'
|
||||
include 'websocket'
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package com.hiczp.bilibili.api
|
||||
|
||||
/**
|
||||
* 各个站点的域名
|
||||
*/
|
||||
object BaseUrl {
|
||||
/**
|
||||
* 用户鉴权
|
||||
*/
|
||||
const val passport = "https://passport.bilibili.com"
|
||||
|
||||
/**
|
||||
* 消息
|
||||
*/
|
||||
const val message = "https://message.bilibili.com"
|
||||
|
||||
/**
|
||||
* 主站
|
||||
*/
|
||||
const val app = "https://app.bilibili.com"
|
||||
|
||||
/**
|
||||
* 这也是主站
|
||||
*/
|
||||
const val main = "https://api.bilibili.com"
|
||||
|
||||
/**
|
||||
* 小视频
|
||||
*/
|
||||
const val vc = "https://api.vc.bilibili.com"
|
||||
|
||||
/**
|
||||
* 创作中心
|
||||
*/
|
||||
const val member = "https://member.bilibili.com"
|
||||
|
||||
/**
|
||||
* 直播
|
||||
*/
|
||||
const val live = "https://api.live.bilibili.com"
|
||||
}
|
||||
@@ -1,325 +0,0 @@
|
||||
package com.hiczp.bilibili.api
|
||||
|
||||
import com.hiczp.bilibili.api.app.AppAPI
|
||||
import com.hiczp.bilibili.api.danmaku.DanmakuAPI
|
||||
import com.hiczp.bilibili.api.live.LiveAPI
|
||||
import com.hiczp.bilibili.api.main.MainAPI
|
||||
import com.hiczp.bilibili.api.member.MemberAPI
|
||||
import com.hiczp.bilibili.api.message.MessageAPI
|
||||
import com.hiczp.bilibili.api.passport.PassportAPI
|
||||
import com.hiczp.bilibili.api.passport.model.LoginResponse
|
||||
import com.hiczp.bilibili.api.player.PlayerAPI
|
||||
import com.hiczp.bilibili.api.player.PlayerInterceptor
|
||||
import com.hiczp.bilibili.api.retrofit.Header
|
||||
import com.hiczp.bilibili.api.retrofit.Param
|
||||
import com.hiczp.bilibili.api.retrofit.exception.BilibiliApiException
|
||||
import com.hiczp.bilibili.api.retrofit.interceptor.CommonHeaderInterceptor
|
||||
import com.hiczp.bilibili.api.retrofit.interceptor.CommonParamInterceptor
|
||||
import com.hiczp.bilibili.api.retrofit.interceptor.FailureResponseInterceptor
|
||||
import com.hiczp.bilibili.api.retrofit.interceptor.SortAndSignInterceptor
|
||||
import com.hiczp.bilibili.api.vc.VcAPI
|
||||
import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
|
||||
import okhttp3.ConnectionPool
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.security.KeyFactory
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import javax.crypto.Cipher
|
||||
|
||||
/**
|
||||
* 此类表示一个模拟的 Bilibili 客户端(Android), 所有调用由此开始.
|
||||
* 多个 BilibiliClient 实例之间不共享登陆状态.
|
||||
* 不能严格保证线程安全.
|
||||
*
|
||||
* @param billingClientProperties 客户端的固有属性, 是一种常量
|
||||
* @param logLevel 日志打印等级
|
||||
*/
|
||||
@Suppress("unused")
|
||||
class BilibiliClient(
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
val billingClientProperties: BilibiliClientProperties = BilibiliClientProperties(),
|
||||
private val logLevel: HttpLoggingInterceptor.Level = HttpLoggingInterceptor.Level.NONE
|
||||
) {
|
||||
/**
|
||||
* 客户端被打开的时间(BilibiliClient 被实例化的时间)
|
||||
*/
|
||||
private val initTime = Instant.now().epochSecond
|
||||
|
||||
/**
|
||||
* 登陆操作得到的 Response
|
||||
*/
|
||||
var loginResponse: LoginResponse? = null
|
||||
|
||||
/**
|
||||
* 是否已登录
|
||||
*/
|
||||
val isLogin
|
||||
get() = loginResponse != null
|
||||
|
||||
//快捷方式
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
val userId
|
||||
get() = loginResponse?.userId
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
val token
|
||||
get() = loginResponse?.token
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
private val defaultCommonHeaderInterceptor = CommonHeaderInterceptor(
|
||||
Header.DISPLAY_ID to { "${billingClientProperties.buildVersionId}-$initTime" },
|
||||
Header.BUILD_VERSION_ID to { billingClientProperties.buildVersionId },
|
||||
Header.USER_AGENT to { billingClientProperties.defaultUserAgent },
|
||||
Header.DEVICE_ID to { billingClientProperties.hardwareId }
|
||||
)
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
private val defaultCommonParamArray = arrayOf(
|
||||
Param.ACCESS_KEY to { token },
|
||||
Param.APP_KEY to { billingClientProperties.appKey },
|
||||
Param.BUILD to { billingClientProperties.build },
|
||||
Param.CHANNEL to { billingClientProperties.channel },
|
||||
Param.MOBILE_APP to { billingClientProperties.platform },
|
||||
Param.PLATFORM to { billingClientProperties.platform },
|
||||
Param.TIMESTAMP to { Instant.now().epochSecond.toString() }
|
||||
)
|
||||
|
||||
private val defaultCommonParamInterceptor = CommonParamInterceptor(*defaultCommonParamArray)
|
||||
|
||||
/**
|
||||
* 用户鉴权相关的接口
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
val passportAPI by lazy {
|
||||
createAPI<PassportAPI>(BaseUrl.passport,
|
||||
defaultCommonHeaderInterceptor,
|
||||
CommonParamInterceptor(
|
||||
Param.APP_KEY to { billingClientProperties.appKey },
|
||||
Param.BUILD to { billingClientProperties.build },
|
||||
Param.CHANNEL to { billingClientProperties.channel },
|
||||
Param.MOBILE_APP to { billingClientProperties.platform },
|
||||
Param.PLATFORM to { billingClientProperties.platform },
|
||||
Param.TIMESTAMP to { Instant.now().epochSecond.toString() }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息通知有关的接口
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
val messageAPI by lazy {
|
||||
createAPI<MessageAPI>(BaseUrl.message,
|
||||
defaultCommonHeaderInterceptor,
|
||||
CommonParamInterceptor(*defaultCommonParamArray,
|
||||
Param.ACTION_KEY to { Param.APP_KEY },
|
||||
"has_up" to { "1" }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 总站 API
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
val appAPI by lazy {
|
||||
createAPI<AppAPI>(BaseUrl.app,
|
||||
defaultCommonHeaderInterceptor,
|
||||
defaultCommonParamInterceptor
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 这也是总站 API
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
val mainAPI by lazy {
|
||||
createAPI<MainAPI>(BaseUrl.main,
|
||||
CommonHeaderInterceptor(
|
||||
//如果未登陆则没有 Display-ID
|
||||
Header.DISPLAY_ID to { userId?.let { "$it-$initTime" } },
|
||||
Header.BUILD_VERSION_ID to { billingClientProperties.buildVersionId },
|
||||
Header.USER_AGENT to { billingClientProperties.defaultUserAgent },
|
||||
Header.DEVICE_ID to { billingClientProperties.hardwareId }
|
||||
),
|
||||
defaultCommonParamInterceptor
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 小视频相关接口
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
val vcAPI by lazy {
|
||||
createAPI<VcAPI>(BaseUrl.vc,
|
||||
defaultCommonHeaderInterceptor,
|
||||
CommonParamInterceptor(*defaultCommonParamArray,
|
||||
Param._DEVICE to { billingClientProperties.platform },
|
||||
Param._HARDWARE_ID to { billingClientProperties.hardwareId },
|
||||
Param.SOURCE to { billingClientProperties.channel },
|
||||
Param.TRACE_ID to { generateTraceId() },
|
||||
Param.USER_ID to { userId?.toString() },
|
||||
Param.VERSION to { billingClientProperties.version }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创作中心
|
||||
*/
|
||||
val memberAPI by lazy {
|
||||
createAPI<MemberAPI>(BaseUrl.member,
|
||||
defaultCommonHeaderInterceptor,
|
||||
defaultCommonParamInterceptor
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放器所需的 API, 用于获取视频播放地址
|
||||
*/
|
||||
val playerAPI: PlayerAPI by lazy {
|
||||
Retrofit.Builder()
|
||||
.baseUrl("https://bilibili.com") //这里的 baseUrl 是没用的
|
||||
.addConverterFactory(gsonConverterFactory)
|
||||
.addCallAdapterFactory(coroutineCallAdapterFactory)
|
||||
.client(OkHttpClient.Builder().apply {
|
||||
addInterceptor(PlayerInterceptor(billingClientProperties) { loginResponse })
|
||||
addInterceptor(FailureResponseInterceptor)
|
||||
addNetworkInterceptor(httpLoggingInterceptor)
|
||||
connectionPool(connectionPool)
|
||||
}.build())
|
||||
.build()
|
||||
.create(PlayerAPI::class.java)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取弹幕所用的 API
|
||||
*/
|
||||
val danmakuAPI: DanmakuAPI by lazy {
|
||||
Retrofit.Builder()
|
||||
.baseUrl(BaseUrl.main)
|
||||
.addCallAdapterFactory(coroutineCallAdapterFactory)
|
||||
.client(OkHttpClient.Builder().apply {
|
||||
addInterceptor(CommonHeaderInterceptor(
|
||||
Header.ACCEPT to { "application/xhtml+xml,application/xml" },
|
||||
Header.ACCEPT_ENCODING to { "gzip, deflate" },
|
||||
Header.USER_AGENT to { billingClientProperties.defaultUserAgent }
|
||||
))
|
||||
addInterceptor(defaultCommonParamInterceptor)
|
||||
addInterceptor(sortAndSignInterceptor)
|
||||
addNetworkInterceptor(httpLoggingInterceptor)
|
||||
connectionPool(connectionPool)
|
||||
}.build())
|
||||
.build()
|
||||
.create(DanmakuAPI::class.java)
|
||||
}
|
||||
|
||||
/**
|
||||
* 直播站
|
||||
*/
|
||||
val liveAPI by lazy {
|
||||
createAPI<LiveAPI>(BaseUrl.live,
|
||||
CommonHeaderInterceptor(
|
||||
//如果未登陆则没有 Display-ID
|
||||
Header.DISPLAY_ID to { userId?.let { "$it-$initTime" } },
|
||||
Header.BUILD_VERSION_ID to { billingClientProperties.buildVersionId },
|
||||
Header.USER_AGENT to { billingClientProperties.defaultUserAgent },
|
||||
Header.DEVICE_ID to { billingClientProperties.hardwareId }
|
||||
),
|
||||
CommonParamInterceptor(*defaultCommonParamArray,
|
||||
Param.ACTION_KEY to { Param.APP_KEY },
|
||||
Param.DEVICE to { billingClientProperties.platform }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 登陆
|
||||
* v3 登陆接口会同时返回 cookies 和 token
|
||||
* 如果要求验证码, 访问 data 中提供的 url 将打开一个弹窗, 里面会加载 js 并显示极验
|
||||
* 极验会调用 https://api.geetest.com/ajax.php 上传滑动轨迹, 然后获得 validate 的值
|
||||
* secCode 的值为 "$validate|jordan"
|
||||
*
|
||||
* @throws BilibiliApiException 用户名与密码不匹配(-629)或者需要验证码(极验)(-105)
|
||||
*/
|
||||
@Throws(BilibiliApiException::class)
|
||||
suspend fun login(
|
||||
username: String, password: String,
|
||||
//如果登陆请求返回了 "验证码错误!"(-105) 的结果, 那么下一次发送登陆请求就需要带上验证码
|
||||
challenge: String? = null,
|
||||
secCode: String? = null,
|
||||
validate: String? = null
|
||||
): LoginResponse {
|
||||
//取得 hash 和 RSA 公钥
|
||||
val (hash, key) = passportAPI.getKey().await().data.let { data ->
|
||||
data.hash to data.key.split('\n').filterNot { it.startsWith('-') }.joinToString(separator = "")
|
||||
}
|
||||
|
||||
//解析 RSA 公钥
|
||||
val publicKey = X509EncodedKeySpec(Base64.getDecoder().decode(key)).let {
|
||||
KeyFactory.getInstance("RSA").generatePublic(it)
|
||||
}
|
||||
//加密密码
|
||||
//兼容 Android
|
||||
val cipheredPassword = Cipher.getInstance("RSA/ECB/PKCS1Padding").apply {
|
||||
init(Cipher.ENCRYPT_MODE, publicKey)
|
||||
}.doFinal((hash + password).toByteArray()).let {
|
||||
Base64.getEncoder().encode(it)
|
||||
}.let {
|
||||
String(it)
|
||||
}
|
||||
|
||||
return passportAPI.login(username, cipheredPassword, challenge, secCode, validate).await().also {
|
||||
this.loginResponse = it
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出
|
||||
* 这个方法不一定是线程安全的, 登出的同时如果进行登陆操作可能引发错误
|
||||
*/
|
||||
suspend fun logout() {
|
||||
val response = loginResponse ?: return
|
||||
val cookieMap = response.data.cookieInfo.cookies
|
||||
.associate {
|
||||
it.name to it.value
|
||||
}
|
||||
passportAPI.revoke(cookieMap, response.token).await()
|
||||
loginResponse = null
|
||||
}
|
||||
|
||||
private val sortAndSignInterceptor = SortAndSignInterceptor(billingClientProperties.appSecret)
|
||||
private val httpLoggingInterceptor = HttpLoggingInterceptor().setLevel(logLevel)
|
||||
private inline fun <reified T : Any> createAPI(
|
||||
baseUrl: String,
|
||||
vararg interceptors: Interceptor
|
||||
) = Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
.addConverterFactory(gsonConverterFactory)
|
||||
.addCallAdapterFactory(coroutineCallAdapterFactory)
|
||||
.client(OkHttpClient.Builder().apply {
|
||||
interceptors.forEach {
|
||||
addInterceptor(it)
|
||||
}
|
||||
addInterceptor(sortAndSignInterceptor)
|
||||
addInterceptor(FailureResponseInterceptor)
|
||||
addNetworkInterceptor(httpLoggingInterceptor)
|
||||
connectionPool(connectionPool)
|
||||
}.build())
|
||||
.build()
|
||||
.create(T::class.java)
|
||||
|
||||
companion object {
|
||||
@Suppress("SpellCheckingInspection")
|
||||
private val gsonConverterFactory = GsonConverterFactory.create()
|
||||
private val coroutineCallAdapterFactory = CoroutineCallAdapterFactory()
|
||||
private val connectionPool = ConnectionPool()
|
||||
private val traceIdFormat = SimpleDateFormat("yyyyMMddHHmm000ss")
|
||||
private fun generateTraceId() = traceIdFormat.format(Date())
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.hiczp.bilibili.api
|
||||
|
||||
/**
|
||||
* 客户端固有属性. 包括版本号, 密钥以及硬件编码.
|
||||
* 默认值对应 5.37.0(release-b220051) 版本.
|
||||
*/
|
||||
class BilibiliClientProperties {
|
||||
/**
|
||||
* 默认 UA, 用于大多数访问
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
var defaultUserAgent = "Mozilla/5.0 BiliDroid/5.37.0 (bbcallen@gmail.com)"
|
||||
|
||||
/**
|
||||
* Android 平台的 appKey(该默认值为普通版客户端, 非概念版)
|
||||
*/
|
||||
var appKey = "1d8b6e7d45233436"
|
||||
|
||||
/**
|
||||
* 由反编译 so 文件得到的 appSecret, 与 appKey 必须匹配
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
var appSecret = "560c52ccd288fed045859ed18bffd973"
|
||||
|
||||
/**
|
||||
* 获取视频播放地址使用的 appKey, 与访问其他 RestFulAPI 所用的 appKey 是不一样的
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
var videoAppKey = "iVGUTjsxvpLeuDCf"
|
||||
|
||||
/**
|
||||
* 获取视频播放地址所用的 appSecret
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
var videoAppSecret = "aHRmhWMLkdeMuILqORnYZocwMBpMEOdt"
|
||||
|
||||
/**
|
||||
* 客户端平台
|
||||
*/
|
||||
var platform = "android"
|
||||
|
||||
/**
|
||||
* 客户端类型
|
||||
* 此属性在旧版客户端不存在
|
||||
*/
|
||||
var channel = "html5_app_bili"
|
||||
|
||||
/**
|
||||
* 硬件 ID, 尚不明确生成算法
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
var hardwareId = "aBRoDWAVeRhsA3FDewMzS3lLMwM"
|
||||
|
||||
/**
|
||||
* 屏幕尺寸, 大屏手机(已经没有小屏手机了)统一为 xxhdpi
|
||||
* 此参数在新版客户端已经较少使用
|
||||
*/
|
||||
var scale = "xxhdpi"
|
||||
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
var version = "5.37.0.5370000"
|
||||
|
||||
/**
|
||||
* 构建版本号
|
||||
*/
|
||||
var build = "5370000"
|
||||
|
||||
/**
|
||||
* 构建版本 ID, 可能是某种 Hash
|
||||
*/
|
||||
var buildVersionId = "XXD9E43D7A1EBB6669597650E3EE417D9E7F5"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.hiczp.bilibili.api
|
||||
|
||||
import java.security.MessageDigest
|
||||
|
||||
//MD5
|
||||
private val md5Instance = MessageDigest.getInstance("MD5")
|
||||
|
||||
fun String.md5() =
|
||||
StringBuilder(32).apply {
|
||||
//优化过的 md5 字符串生成算法
|
||||
md5Instance.digest(toByteArray()).forEach {
|
||||
val value = it.toInt() and 0xFF
|
||||
val high = value / 16
|
||||
val low = value % 16
|
||||
append(if (high <= 9) '0' + high else 'a' - 10 + high)
|
||||
append(if (low <= 9) '0' + low else 'a' - 10 + low)
|
||||
}
|
||||
}.toString()
|
||||
|
||||
/**
|
||||
* 签名算法为 "$排序后的参数字符串$appSecret".md5()
|
||||
*/
|
||||
internal fun calculateSign(sortedQuery: String, appSecret: String) = (sortedQuery + appSecret).md5()
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.hiczp.bilibili.api
|
||||
|
||||
import kotlin.experimental.ExperimentalTypeInference
|
||||
|
||||
@UseExperimental(ExperimentalTypeInference::class)
|
||||
internal inline fun <T> list(@BuilderInference block: MutableList<T>.() -> Unit): List<T> {
|
||||
val list = ArrayList<T>()
|
||||
block(list)
|
||||
return list
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.hiczp.bilibili.api
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonParser
|
||||
|
||||
internal val gson = Gson()
|
||||
|
||||
internal val jsonParser = JsonParser()
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.hiczp.bilibili.api
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun JsonArray.isEmpty() = size() == 0
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun JsonArray.isNotEmpty() = size() != 0
|
||||
@@ -1,56 +0,0 @@
|
||||
package com.hiczp.bilibili.api
|
||||
|
||||
import com.hiczp.bilibili.api.thirdpart.commons.BoundedInputStream
|
||||
import io.ktor.util.InternalAPI
|
||||
import kotlinx.io.errors.EOFException
|
||||
import java.io.InputStream
|
||||
|
||||
//减少包引入
|
||||
//https://github.com/apache/commons-io/blob/master/src/main/java/org/apache/commons/io/IOUtils.java
|
||||
fun InputStream.readFully(length: Int): ByteArray {
|
||||
if (length < 0) {
|
||||
throw IllegalArgumentException("Length must not be negative: $length")
|
||||
}
|
||||
|
||||
val byteArray = ByteArray(length)
|
||||
var remaining = length
|
||||
|
||||
while (remaining > 0) {
|
||||
val count = read(byteArray, length - remaining, remaining)
|
||||
if (count == -1) break
|
||||
remaining -= count
|
||||
}
|
||||
|
||||
val actual = length - remaining
|
||||
if (actual != length) {
|
||||
throw EOFException("Length to read: $length actual: $actual")
|
||||
}
|
||||
|
||||
return byteArray
|
||||
}
|
||||
|
||||
/**
|
||||
* 以大端模式从流中读取一个 int
|
||||
*/
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
fun InputStream.readInt(): Int {
|
||||
val byteArray = readFully(4)
|
||||
return (byteArray[0].toUByte().toInt() shl 24) or
|
||||
(byteArray[1].toUByte().toInt() shl 16) or
|
||||
(byteArray[2].toUByte().toInt() shl 8) or
|
||||
(byteArray[3].toUByte().toInt())
|
||||
}
|
||||
|
||||
/**
|
||||
* 以大端模式从流中读取一个 unsigned int
|
||||
*/
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
fun InputStream.readUInt() = readInt().toUInt()
|
||||
|
||||
fun InputStream.bounded(size: Long) = BoundedInputStream(this, size)
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
fun InputStream.bounded(size: UInt) = bounded(size.toLong())
|
||||
|
||||
@UseExperimental(InternalAPI::class)
|
||||
internal fun ByteArray.toPrettyPrintString() = joinToString(prefix = "[", postfix = "]") { "0x%02x".format(it) }
|
||||
@@ -1,383 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import com.hiczp.bilibili.api.app.model.*
|
||||
import com.hiczp.bilibili.api.retrofit.CommonResponse
|
||||
import kotlinx.coroutines.Deferred
|
||||
import retrofit2.http.*
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* 总站 API
|
||||
*/
|
||||
@Suppress("DeferredIsResult")
|
||||
interface AppAPI {
|
||||
/**
|
||||
* 打开 APP 时将访问此接口来获得 UI 排布顺序
|
||||
* 包括下方 tab(首页, 频道, 动态, 会员购), 首页的上方 tab(直播, 推荐, 热门, 追番) 以及右上角的 游戏中心, 离线下载, 消息
|
||||
*/
|
||||
@GET("/x/resource/show/tab")
|
||||
fun tab(): Deferred<Tab>
|
||||
|
||||
/**
|
||||
* 登陆完成后将请求一次此接口以获得个人资料
|
||||
* 如果未登录将返回 {"code":-101,"message":"账号未登录","ttl":1}
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/x/v2/account/myinfo")
|
||||
fun myInfo(): Deferred<MyInfo>
|
||||
|
||||
/**
|
||||
* 登陆后也会访问此接口, 返回内容大致与 myInfo() 相同
|
||||
*/
|
||||
@GET("/x/v2/account/mine")
|
||||
fun mine(): Deferred<Mine>
|
||||
|
||||
/**
|
||||
* 侧边栏中动态增加的按钮, 返回信息包含 URI 地址(到对应的 activity)
|
||||
* 侧拉抽屉
|
||||
*/
|
||||
@GET("/x/resource/sidebar")
|
||||
fun sidebar(): Deferred<Sidebar>
|
||||
|
||||
/**
|
||||
* 首页内容(客户端通过解析返回的内容来生成页面内容, 下同)
|
||||
* 该 API 没有翻页参数, 同样的参数每次请求都会返回不一样的内容. 刷新和下拉只是简单的重新访问此接口.
|
||||
* 首页 -> 推荐
|
||||
*
|
||||
* @param pull 如果是通过滑动到最顶端来刷新页面的, 那么将是 true, 将页面滑动到最底端来获取更多内容将是 false
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/x/v2/feed/index")
|
||||
fun homePage(
|
||||
@Query("ad_extra") adExtra: String? = null,
|
||||
@Query("autoplay_card") autoplayCard: Int = 0,
|
||||
@Query("banner_hash") bannerHash: String? = null,
|
||||
@Query("column") column: Int = 2,
|
||||
@Query("device_type") deviceType: Int = 0,
|
||||
@Query("flush") flush: Int = 0,
|
||||
@Query("fnval") fnVal: Int = 16,
|
||||
@Query("fnver") fnVer: Int = 0,
|
||||
@Query("force_host") forceHost: Int = 0,
|
||||
@Query("idx") index: Long = Instant.now().epochSecond,
|
||||
@Query("login_event") loginEvent: Int = 0,
|
||||
@Query("network") network: String = "mobile",
|
||||
@Query("open_event") openEvent: String? = null,
|
||||
@Query("pull") pull: Boolean = true,
|
||||
@Query("qn") qn: Int = 32,
|
||||
@Query("recsys_mode") recsysMode: Int = 0
|
||||
): Deferred<HomePage>
|
||||
|
||||
/**
|
||||
* 热门页面
|
||||
* 首页 -> 热门
|
||||
*
|
||||
* @param index 翻页参数, 一开始为 0, 然后每次滑动到底端就会加 10
|
||||
* @param ver 第一次请求时没有这个参数, 第二次开始这个参数为上一次请求此接口时的返回值中的 `ver`
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/x/v2/show/popular/index")
|
||||
fun popularPage(
|
||||
@Query("fnval") fnVal: Int = 16,
|
||||
@Query("fnver") fnVer: Int = 0,
|
||||
@Query("force_host") forceHost: Int = 0,
|
||||
@Query("idx") index: Long = 0,
|
||||
@Query("last_param") lastParam: String? = null,
|
||||
@Query("login_event") loginEvent: Int = 0,
|
||||
@Query("qn") qn: Int = 32,
|
||||
@Query("ver") ver: Long? = null
|
||||
): Deferred<PopularPage>
|
||||
|
||||
/**
|
||||
* 视频页面(普通视频, 非番剧)
|
||||
* 包含视频基本信息, 推荐和广告
|
||||
* 从这个接口得到视频的 cid
|
||||
* 如果返回内容里的 pages 有多个表明有多个 p, 每个 p 有自己的 cid(外层的 cid 为默认的那个 p 的 cid)
|
||||
*
|
||||
* @param aid av 号
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/x/v2/view")
|
||||
fun view(
|
||||
@Query("ad_extra") adExtra: String? = null,
|
||||
@Query("aid") aid: Long,
|
||||
@Query("autoplay") autoplay: Int = 0,
|
||||
@Query("fnval") fnVal: Int = 16,
|
||||
@Query("fnver") fnVer: Int = 0,
|
||||
@Query("force_host") forceHost: Int = 0,
|
||||
@Query("from") from: Int? = null,
|
||||
@Query("plat") plat: Int = 0,
|
||||
@Query("qn") qn: Int = 32,
|
||||
@Query("trackid") trackId: String? = null //all_10.shylf-ai-recsys-120.1550674524909.237
|
||||
): Deferred<View>
|
||||
|
||||
/**
|
||||
* 点赞(视频)
|
||||
*
|
||||
* @param aid 视频的唯一标识
|
||||
* @param like 为 0 时表示点赞, 为 1 时表示取消点赞
|
||||
* @param dislike 正常为 0, 为 1 时(like 为 0)表示 取消不喜欢的同时为该视频点赞(等于做了两个操作, 下同)
|
||||
*/
|
||||
@POST("/x/v2/view/like")
|
||||
@FormUrlEncoded
|
||||
fun like(
|
||||
@Field("aid") aid: Long,
|
||||
@Field("like") like: Int = 0,
|
||||
@Field("dislike") dislike: Int = 0,
|
||||
@Field("from") from: Int? = null
|
||||
): Deferred<LikeResponse>
|
||||
|
||||
/**
|
||||
* 不喜欢(视频)
|
||||
*
|
||||
* @param aid 视频的唯一标识
|
||||
* @param dislike 为 0 时表示不喜欢, 为 1 时表示取消不喜欢
|
||||
* @param like 正常为 0, 为 1 时(dislike 为 0)表示 取消点赞的同时不喜欢该视频
|
||||
*/
|
||||
@POST("/x/v2/view/dislike")
|
||||
@FormUrlEncoded
|
||||
fun dislike(
|
||||
@Field("aid") aid: Long,
|
||||
@Field("like") like: Int = 0,
|
||||
@Field("dislike") dislike: Int = 0,
|
||||
@Field("from") from: Int? = null
|
||||
): Deferred<CommonResponse>
|
||||
|
||||
/**
|
||||
* 投币
|
||||
* 自制视频能投两个, 转载视频只能投一个. 是转载还是自制在获取视频页面的 API 的 copyright.
|
||||
*
|
||||
* @param multiply 投币数量
|
||||
* @param selectLike 为 1 表示投币的同时为视频点赞, 对番剧投币时, 该值总为 0
|
||||
* @param upId 该值似乎总为 0
|
||||
*
|
||||
* @see view
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@POST("/x/v2/view/coin/add")
|
||||
@FormUrlEncoded
|
||||
fun addCoin(
|
||||
@Field("aid") aid: Long,
|
||||
@Field("avtype") avType: Int = 1,
|
||||
@Field("from") from: Int? = null,
|
||||
@Field("multiply") multiply: Int,
|
||||
@Field("select_like") selectLike: Int = 0,
|
||||
@Field("upid") upId: Long? = 0
|
||||
): Deferred<AddCoinResponse>
|
||||
|
||||
/**
|
||||
* 查看某个用户的主页(也可以查看自己)
|
||||
*
|
||||
* @param vmId 欲查看的用户的 id
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/x/v2/space")
|
||||
fun space(
|
||||
@Query("from") from: Int? = 0,
|
||||
@Query("ps") pageSize: Int = 10,
|
||||
@Query("vmid") vmId: Long
|
||||
): Deferred<Space>
|
||||
|
||||
/**
|
||||
* 收藏页面
|
||||
* 侧拉抽屉 -> 收藏
|
||||
*
|
||||
* @param vmId 所查看的用户的 id(看自己的收藏也要有该参数)
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/x/v2/favorite")
|
||||
fun favoritePage(
|
||||
@Query("aid") aid: Long = 0,
|
||||
@Query("pn") pageNumber: Int = 1,
|
||||
@Query("ps") pageSize: Int = 20,
|
||||
@Query("vmid") vmId: Long
|
||||
): Deferred<FavoritePage>
|
||||
|
||||
/**
|
||||
* 收藏的视频
|
||||
* 侧拉抽屉 -> 收藏 -> 视频 -> (打开一个收藏夹)
|
||||
*
|
||||
* @param fid 收藏夹的 id, 在拉取收藏页面时获得
|
||||
* @param tid 不明确
|
||||
* @param vmId 用户 id
|
||||
*
|
||||
* @see favoritePage
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/x/v2/favorite/video")
|
||||
fun favoriteVideo(
|
||||
@Query("fid") fid: Long,
|
||||
@Query("order") order: String = "ftime",
|
||||
@Query("pn") pageNumber: Int = 1,
|
||||
@Query("ps") pageSize: Int = 20,
|
||||
@Query("tid") tid: Long = 0,
|
||||
@Query("vmid") vmId: Long
|
||||
): Deferred<FavoriteVideo>
|
||||
|
||||
/**
|
||||
* 收藏的文章
|
||||
* 这个 API 的返回内容里没有总页数, 真实的客户端会直接访问下一页来确认当前页是不是最后一页
|
||||
* 侧拉抽屉 -> 收藏 -> 专栏
|
||||
*/
|
||||
@GET("/x/v2/favorite/article")
|
||||
fun favoriteArticle(
|
||||
@Query("pn") pageNumber: Int = 1,
|
||||
@Query("ps") pageSize: Int = 20
|
||||
): Deferred<FavoriteArticle>
|
||||
|
||||
/**
|
||||
* 大家都在搜(热搜关键字列表)
|
||||
*
|
||||
* 上方搜索栏 -> 搜索提示
|
||||
*
|
||||
* @param limit 分页大小
|
||||
*/
|
||||
@GET("/x/v2/search/hot")
|
||||
fun searchHot(@Query("limit") limit: Int = 50): Deferred<SearchHot>
|
||||
|
||||
/**
|
||||
* 默认搜索词, 当点击搜索框但是没输入内容时就会显示该词条.
|
||||
*
|
||||
* 上方搜索框 -> placeholder
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/x/v2/search/defaultwords")
|
||||
fun searchDefaultWords(): Deferred<SearchDefaultWords>
|
||||
|
||||
/**
|
||||
* 搜索联想
|
||||
*
|
||||
* 上方搜索框 -> 搜索时的显示的候选项
|
||||
*/
|
||||
@GET("/x/v2/search/suggest3")
|
||||
fun searchSuggest(
|
||||
@Query("highlight") highlight: Int = 1,
|
||||
@Query("keyword") keyword: String
|
||||
): Deferred<SearchSuggest>
|
||||
|
||||
/**
|
||||
* 搜索(综合)
|
||||
*
|
||||
* 上方搜索栏
|
||||
*
|
||||
* @param order 排序. null/default 默认排序, view 播放多, pubdate 新发布, danmaku 弹幕多
|
||||
* @param duration 视频时长. 0 全部时长, 1 0-10分钟, 2 10-30分钟, 3 30-60分钟, 4 60+分钟
|
||||
* @param rid 按某种分区搜索, 编号为数字. null 全部分区.
|
||||
* @param keyword 搜索的关键字, 下同
|
||||
* @param from_source 来源, 如果是直接搜索的则为 app_search, 在历史记录里点击的则为 apphistory_search, 从搜索时的候选项里点的为 appsuggest_search
|
||||
* @param pageNumber 分页, 从 1 开始
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/x/v2/search")
|
||||
fun search(
|
||||
@Query("duration") duration: Int = 0,
|
||||
@Query("from_source") from_source: String = "app_search",
|
||||
@Query("highlight") highlight: Int = 1,
|
||||
@Query("keyword") keyword: String,
|
||||
@Query("order") order: String? = null,
|
||||
@Query("pn") pageNumber: Int = 1,
|
||||
@Query("ps") pageSize: Int = 20,
|
||||
@Query("recommend") recommend: Int = 1,
|
||||
@Query("rid") rid: Int? = null
|
||||
): Deferred<SearchResult>
|
||||
|
||||
/**
|
||||
* 搜索直播间
|
||||
*
|
||||
* 上方搜索栏 -> 直播
|
||||
*
|
||||
* @param type 搜索的内容的类型, 每种搜索的 type 都是固定的, 下同
|
||||
*/
|
||||
@GET("/x/v2/search/live")
|
||||
fun searchLive(
|
||||
@Query("keyword") keyword: String,
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@Query("order") order: String = "totalrank",
|
||||
@Query("pn") pageNumber: Int = 1,
|
||||
@Query("ps") pageSize: Int = 20,
|
||||
@Query("type") type: Int = 4
|
||||
): Deferred<SearchLiveResult>
|
||||
|
||||
/**
|
||||
* 根据某个类型来进行搜索(自定义)
|
||||
*/
|
||||
@GET("/x/v2/search/type")
|
||||
fun searchType(
|
||||
@Query("keyword") keyword: String,
|
||||
@Query("pn") pageNumber: Int = 1,
|
||||
@Query("ps") pageSize: Int = 20,
|
||||
@Query("type") type: Int,
|
||||
@QueryMap additionParam: Map<String, String>
|
||||
): Deferred<JsonObject>
|
||||
|
||||
/**
|
||||
* 搜索番剧
|
||||
*
|
||||
* 上方搜索栏 -> 番剧
|
||||
*/
|
||||
@GET("/x/v2/search/type")
|
||||
fun searchBangumi(
|
||||
@Query("keyword") keyword: String,
|
||||
@Query("pn") pageNumber: Int = 1,
|
||||
@Query("ps") pageSize: Int = 20,
|
||||
@Query("type") type: Int = 7
|
||||
): Deferred<SearchBangumiResult>
|
||||
|
||||
/**
|
||||
* 搜索用户
|
||||
*
|
||||
* 上方搜索栏 -> 用户
|
||||
*
|
||||
* @param order 排序维度. totalrank 默认排序,fans 粉丝, level 等级.
|
||||
* @param orderSort 排序顺序. 0 从高到低, 1 从低到高.
|
||||
* @param userType 用户类型. 0 全部用户, 1 up主, 2 普通用户, 3 认证用户.
|
||||
*
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/x/v2/search/type")
|
||||
fun searchUser(
|
||||
@Query("highlight") highlight: Int = 1,
|
||||
@Query("keyword") keyword: String,
|
||||
@Query("order") order: String = "totalrank",
|
||||
@Query("order_sort") orderSort: Int? = null,
|
||||
@Query("pn") pageNumber: Int = 1,
|
||||
@Query("ps") pageSize: Int = 20,
|
||||
@Query("type") type: Int = 2,
|
||||
@Query("user_type") userType: Int = 0
|
||||
): Deferred<SearchUserResult>
|
||||
|
||||
/**
|
||||
* 搜索影视(包括动漫的剧场版和纪录片)
|
||||
*
|
||||
* 上方搜索栏 -> 影视
|
||||
*/
|
||||
@GET("/x/v2/search/type")
|
||||
fun searchMovie(
|
||||
@Query("keyword") keyword: String,
|
||||
@Query("pn") pageNumber: Int = 1,
|
||||
@Query("ps") pageSize: Int = 20,
|
||||
@Query("type") type: Int = 8
|
||||
): Deferred<SearchMovieResult>
|
||||
|
||||
/**
|
||||
* 搜索文章
|
||||
*
|
||||
* 上方搜索栏 -> 专栏
|
||||
*
|
||||
* @param order 排序. null 默认排序, pubdate 发布时间, click 按阅读数, scores 按评论数, attention 按点赞数.
|
||||
* @param categoryId 分类, 编号为数字. 0 全部分类.
|
||||
*
|
||||
* @see com.hiczp.bilibili.api.main.MainAPI.articleCategories
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/x/v2/search/type")
|
||||
fun searchArticle(
|
||||
@Query("category_id") categoryId: Int = 0,
|
||||
@Query("highlight") highlight: Int = 1,
|
||||
@Query("keyword") keyword: String,
|
||||
@Query("order") order: String? = null,
|
||||
@Query("pn") pageNumber: Int = 1,
|
||||
@Query("ps") pageSize: Int = 20,
|
||||
@Query("type") type: Int = 6
|
||||
): Deferred<SearchArticleResult>
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class AddCoinResponse(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("like")
|
||||
var like: Boolean, // false
|
||||
@SerializedName("prompt")
|
||||
var prompt: Boolean? // true
|
||||
)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class FavoriteArticle(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 1
|
||||
@SerializedName("items")
|
||||
var items: List<Item>
|
||||
) {
|
||||
data class Item(
|
||||
@SerializedName("banner_url")
|
||||
var bannerUrl: String, // https://i0.hdslb.com/bfs/article/97cddc6d048297aedcc5cc498cbfb090358567eb.jpg
|
||||
@SerializedName("favorite_time")
|
||||
var favoriteTime: Int, // 1551348963
|
||||
@SerializedName("goto")
|
||||
var goto: String, // article
|
||||
@SerializedName("id")
|
||||
var id: Long, // 2165049
|
||||
@SerializedName("image_urls")
|
||||
var imageUrls: List<String>,
|
||||
@SerializedName("name")
|
||||
var name: String, // 京八贱
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 2165049
|
||||
@SerializedName("summary")
|
||||
var summary: String, // 相信不少玩家在完破了《荒野大镖客:救赎2》的单人剧情模式后,就开始了线上模式。近期官方推出的更新给大家带来了不少新内容,比如新的对决模式、竞速、衣物和表情动作等等,还强化了游戏内的通缉和小地图等系统,尝试给予玩家更加多样的体验。不过更新上线后所带来的改变反而激起了玩家的怒火,首当其冲的便是打猎相关的更动,在游戏中玩家可把肢解完毕的猎物尸体卖给肉贩,而经历了这次的更新后,多人线上模式这项打猎物品的价格直接被砍半,玩家也发现不少猎物的掉落物数量有减少的迹象,让不少喜爱悠闲打猎生活的玩家大感不满。猎人
|
||||
@SerializedName("template_id")
|
||||
var templateId: Int, // 4
|
||||
@SerializedName("title")
|
||||
var title: String, // 《荒野大镖客2》线上模式的近期更新引起玩家强烈不满,引发争议
|
||||
@SerializedName("uri")
|
||||
var uri: String // bilibili://article/2165049
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class FavoritePage(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("favorite")
|
||||
var favorite: Favorite,
|
||||
@SerializedName("tab")
|
||||
var tab: Tab
|
||||
) {
|
||||
data class Favorite(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 1
|
||||
@SerializedName("items")
|
||||
var items: List<Item>
|
||||
) {
|
||||
data class Item(
|
||||
/**
|
||||
* 如果该收藏夹最后一个视频被删除了, 那么将没有封面
|
||||
*/
|
||||
@SerializedName("cover")
|
||||
var cover: List<Cover>?, // null
|
||||
@SerializedName("cur_count")
|
||||
var curCount: Int, // 1
|
||||
@SerializedName("fid")
|
||||
var fid: Long, // 795158
|
||||
@SerializedName("media_id")
|
||||
var mediaId: Long, // 79515830
|
||||
@SerializedName("mid")
|
||||
var mid: Long, // 20293030
|
||||
@SerializedName("name")
|
||||
var name: String, // 默认收藏夹
|
||||
@SerializedName("state")
|
||||
var state: Int // 0
|
||||
) {
|
||||
data class Cover(
|
||||
@SerializedName("aid")
|
||||
var aid: Long, // 9498716
|
||||
@SerializedName("pic")
|
||||
var pic: String, // http://i2.hdslb.com/bfs/archive/3536b8de71da4dd7bf01200db1e6c710b5f4aa0e.png
|
||||
@SerializedName("type")
|
||||
var type: Int // 2
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class Tab(
|
||||
@SerializedName("albums")
|
||||
var albums: Boolean, // false
|
||||
@SerializedName("article")
|
||||
var article: Boolean, // true
|
||||
@SerializedName("audios")
|
||||
var audios: Boolean, // false
|
||||
@SerializedName("cinema")
|
||||
var cinema: Boolean, // true
|
||||
@SerializedName("clips")
|
||||
var clips: Boolean, // false
|
||||
@SerializedName("favorite")
|
||||
var favorite: Boolean, // true
|
||||
@SerializedName("menu")
|
||||
var menu: Boolean, // false
|
||||
@SerializedName("pgc_menu")
|
||||
var pgcMenu: Boolean, // false
|
||||
@SerializedName("product")
|
||||
var product: Boolean, // false
|
||||
@SerializedName("specil")
|
||||
var specil: Boolean, // false
|
||||
@SerializedName("ticket")
|
||||
var ticket: Boolean, // false
|
||||
@SerializedName("topic")
|
||||
var topic: Boolean // false
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class FavoriteVideo(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 1
|
||||
@SerializedName("items")
|
||||
var items: List<Item>
|
||||
) {
|
||||
data class Item(
|
||||
@SerializedName("aid")
|
||||
var aid: Int, // 30702
|
||||
@SerializedName("danmaku")
|
||||
var danmaku: Int, // 19363
|
||||
@SerializedName("goto")
|
||||
var goto: String, // av
|
||||
@SerializedName("name")
|
||||
var name: String, // ⑨搬运君
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 30702
|
||||
@SerializedName("pic")
|
||||
var pic: String, // http://i1.hdslb.com/bfs/archive/76a045020b6aaa830121132d4a6536d6b82660f4.jpg
|
||||
@SerializedName("play_num")
|
||||
var playNum: Long, // 371718
|
||||
@SerializedName("title")
|
||||
var title: String, // 【整理发布】妄想学生会
|
||||
@SerializedName("ugc_pay")
|
||||
var ugcPay: Long, // 0
|
||||
@SerializedName("uri")
|
||||
var uri: String // bilibili://video/30702
|
||||
)
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,22 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class LikeResponse(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
/**
|
||||
* 取消点赞时 toast 为 ""
|
||||
*/
|
||||
@SerializedName("toast")
|
||||
var toast: String // 点赞收到!视频可能推荐哦
|
||||
)
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Mine(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("audio_type")
|
||||
var audioType: Int, // 0
|
||||
@SerializedName("bcoin")
|
||||
var bcoin: Int, // 5
|
||||
@SerializedName("coin")
|
||||
var coin: Double, // 892.7
|
||||
@SerializedName("dynamic")
|
||||
var `dynamic`: Int, // 8
|
||||
@SerializedName("face")
|
||||
var face: String, // http://i0.hdslb.com/bfs/face/0434dccc0ec4de223e8ca374dea06a6e1e8eb471.jpg
|
||||
@SerializedName("follower")
|
||||
var follower: Int, // 512
|
||||
@SerializedName("following")
|
||||
var following: Int, // 106
|
||||
@SerializedName("level")
|
||||
var level: Int, // 5
|
||||
@SerializedName("mid")
|
||||
var mid: Long, // 2866663
|
||||
@SerializedName("name")
|
||||
var name: String, // hyx5020
|
||||
@SerializedName("new_followers")
|
||||
var newFollowers: Int, // 0
|
||||
@SerializedName("official_verify")
|
||||
var officialVerify: OfficialVerify,
|
||||
@SerializedName("rank")
|
||||
var rank: Int, // 10000
|
||||
@SerializedName("sex")
|
||||
var sex: Int, // 0
|
||||
@SerializedName("show_creative")
|
||||
var showCreative: Int, // 1
|
||||
@SerializedName("show_videoup")
|
||||
var showVideoup: Int, // 1
|
||||
@SerializedName("silence")
|
||||
var silence: Int, // 0
|
||||
@SerializedName("vip_type")
|
||||
var vipType: Int // 2
|
||||
) {
|
||||
data class OfficialVerify(
|
||||
@SerializedName("desc")
|
||||
var desc: String,
|
||||
@SerializedName("type")
|
||||
var type: Int // -1
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class MyInfo(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("birthday")
|
||||
var birthday: String, // 1995-11-18
|
||||
@SerializedName("coins")
|
||||
var coins: Int, // 1025
|
||||
@SerializedName("email_status")
|
||||
var emailStatus: Int, // 0
|
||||
@SerializedName("face")
|
||||
var face: String, // http://i1.hdslb.com/bfs/face/4f65e79399ad5a1bf3f877851b2f819d5870b494.jpg
|
||||
@SerializedName("identification")
|
||||
var identification: Int, // 1
|
||||
@SerializedName("level")
|
||||
var level: Int, // 4
|
||||
@SerializedName("mid")
|
||||
var mid: Long, // 20293030
|
||||
@SerializedName("name")
|
||||
var name: String, // czp3009
|
||||
@SerializedName("official")
|
||||
var official: Official,
|
||||
@SerializedName("rank")
|
||||
var rank: Int, // 10000
|
||||
@SerializedName("sex")
|
||||
var sex: Int, // 0
|
||||
@SerializedName("sign")
|
||||
var sign: String,
|
||||
@SerializedName("silence")
|
||||
var silence: Int, // 0
|
||||
@SerializedName("tel_status")
|
||||
var telStatus: Int, // 1
|
||||
@SerializedName("vip")
|
||||
var vip: Vip
|
||||
) {
|
||||
data class Official(
|
||||
@SerializedName("desc")
|
||||
var desc: String,
|
||||
@SerializedName("role")
|
||||
var role: Int, // 0
|
||||
@SerializedName("title")
|
||||
var title: String
|
||||
)
|
||||
|
||||
data class Vip(
|
||||
@SerializedName("due_date")
|
||||
var dueDate: Long, // 0
|
||||
@SerializedName("status")
|
||||
var status: Int, // 0
|
||||
@SerializedName("type")
|
||||
var type: Int, // 0
|
||||
@SerializedName("vip_pay_type")
|
||||
var vipPayType: Int // 0
|
||||
)
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,58 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class SearchArticleResult(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("items")
|
||||
var items: List<Item>,
|
||||
@SerializedName("pages")
|
||||
var pages: Int, // 50
|
||||
@SerializedName("total")
|
||||
var total: Int, // 0
|
||||
@SerializedName("trackid")
|
||||
var trackid: String // 2251647613743955310
|
||||
) {
|
||||
data class Item(
|
||||
@SerializedName("badge")
|
||||
var badge: String, // 专栏
|
||||
@SerializedName("desc")
|
||||
var desc: String, // 今天来和大家说下《刀剑神域3》第19集的先行图剧情,与其说是第19集,确切的讲应该是18.5集,根据先行图来看,这一集是前面的合集,主要讲的是桐人和尤吉欧的经历。在第18集结尾,桐人不小心说出了赛鲁卡的名字,导致爱丽丝记忆发生了错乱,隐约想起了自己的妹妹,而接下来就是桐人会和爱丽丝说关于她的事了,而所说的方式根据先行图来看事回忆杀方式。下面就简单和大家说下官方给出的先行图。先行图01:a姐植入记忆水晶桐人既然要告诉爱丽丝真相,也会给她说这一切的元凶就是最高祭师a姐,从上图先行图可以看出,这是a姐
|
||||
@SerializedName("goto")
|
||||
var goto: String, // article
|
||||
@SerializedName("id")
|
||||
var id: Long, // 2052980
|
||||
@SerializedName("image_urls")
|
||||
var imageUrls: List<String>,
|
||||
@SerializedName("like")
|
||||
var like: Int, // 116
|
||||
@SerializedName("mid")
|
||||
var mid: Int, // 12043763
|
||||
@SerializedName("name")
|
||||
var name: String, // 老白与动漫
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 2052980
|
||||
@SerializedName("play")
|
||||
var play: Int, // 19101
|
||||
@SerializedName("reply")
|
||||
var reply: Int, // 82
|
||||
@SerializedName("template_id")
|
||||
var templateId: Int, // 3
|
||||
@SerializedName("title")
|
||||
var title: String, // <em class="keyword">刀剑神域</em>3第19集先行:桐人告诉爱丽丝真相,又是回忆杀剧情!
|
||||
@SerializedName("uri")
|
||||
var uri: String, // bilibili://article/2052980
|
||||
@SerializedName("view")
|
||||
var view: Int // 19101
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class SearchBangumiResult(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("items")
|
||||
var items: List<Item>,
|
||||
@SerializedName("pages")
|
||||
var pages: Int, // 1
|
||||
@SerializedName("total")
|
||||
var total: Int, // 1
|
||||
@SerializedName("trackid")
|
||||
var trackid: String // 1357843021891149439
|
||||
) {
|
||||
data class Item(
|
||||
@SerializedName("area")
|
||||
var area: String, // 日本
|
||||
@SerializedName("badge")
|
||||
var badge: String, // 番剧
|
||||
@SerializedName("badges")
|
||||
var badges: List<Badge>,
|
||||
@SerializedName("cover")
|
||||
var cover: String, // https://i0.hdslb.com/bfs/bangumi/4d9f43eb3dba572797f8915f8f28efce9e58d756.jpg
|
||||
@SerializedName("cv")
|
||||
var cv: String, // 桐人(桐谷和人):松冈祯丞亚丝娜(结城明日奈):户松遥爱丽丝:茅野爱衣尤吉欧:岛崎信长赛鲁卡:前田佳织里罗妮耶·亚拉贝尔:近藤玲奈蒂洁·修特利尼:石原夏织神代凛子:小林沙苗菊冈诚二郎:森川智之莱欧斯·安提诺斯:岩濑周平温贝尔·吉泽克:木岛隆一索尔狄丽娜·塞路尔特:潘惠美沃罗·利凡玎:村田太志诗乃(朝田诗乃):泽城美雪 强尼·布莱克(金本敦):逢坂良太 西莉卡(绫野珪子):日高里菜 莉兹贝特(筱崎里香):高垣彩阳
|
||||
@SerializedName("episodes")
|
||||
var episodes: List<Episode>,
|
||||
@SerializedName("goto")
|
||||
var goto: String, // bangumi
|
||||
@SerializedName("is_atten")
|
||||
var isAtten: Int, // 1
|
||||
@SerializedName("is_selection")
|
||||
var isSelection: Int, // 1
|
||||
@SerializedName("label")
|
||||
var label: String, // 小说改/热血/奇幻/战斗/励志
|
||||
@SerializedName("media_type")
|
||||
var mediaType: Int, // 1
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 130412
|
||||
@SerializedName("ptime")
|
||||
var ptime: Int, // 1538841600
|
||||
@SerializedName("rating")
|
||||
var rating: Double, // 9.2
|
||||
@SerializedName("season_id")
|
||||
var seasonId: Int, // 25510
|
||||
@SerializedName("season_type")
|
||||
var seasonType: Int, // 1
|
||||
@SerializedName("season_type_name")
|
||||
var seasonTypeName: String, // 番剧
|
||||
@SerializedName("staff")
|
||||
var staff: String, // 原作:川原砾原作插画 / 角色设计草案:abec导演:小野学助理导演:佐久间贵史角色设计:足立慎吾、铃木豪、西口智也总作画监督:铃木豪、西口智也动作作画监督:菅野芳弘、竹内哲也美术导演:小川友佳子、渡边佳人美术设定:森冈贤一、谷内优穗色彩设计:中野尚美CG导演:云藤隆太音响导演:岩浪美和效果:小山恭正音响制作:ソニルード音乐:梶浦由记制片:EGG FIRM、Straight Edge制作:A-1 Pictures
|
||||
@SerializedName("style")
|
||||
var style: String, // 小说改/热血/奇幻/战斗/励志
|
||||
@SerializedName("title")
|
||||
var title: String, // 刀剑神域 Alicization
|
||||
@SerializedName("uri")
|
||||
var uri: String, // https://www.bilibili.com/bangumi/play/ss25510/
|
||||
@SerializedName("vote")
|
||||
var vote: Int // 48497
|
||||
) {
|
||||
data class Badge(
|
||||
@SerializedName("bg_color")
|
||||
var bgColor: String, // #FB7299
|
||||
@SerializedName("bg_color_night")
|
||||
var bgColorNight: String, // #BB5B76
|
||||
@SerializedName("bg_style")
|
||||
var bgStyle: Int, // 1
|
||||
@SerializedName("border_color")
|
||||
var borderColor: String, // #FB7299
|
||||
@SerializedName("border_color_night")
|
||||
var borderColorNight: String, // #BB5B76
|
||||
@SerializedName("text")
|
||||
var text: String, // 会员抢先
|
||||
@SerializedName("text_color")
|
||||
var textColor: String, // #FFFFFF
|
||||
@SerializedName("text_color_night")
|
||||
var textColorNight: String // #E5E5E5
|
||||
)
|
||||
|
||||
data class Episode(
|
||||
@SerializedName("index")
|
||||
var index: String, // 21
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 250557
|
||||
@SerializedName("uri")
|
||||
var uri: String // https://www.bilibili.com/bangumi/play/ep250557
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class SearchDefaultWords(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 5157193909505109430
|
||||
@SerializedName("show")
|
||||
var show: String, // 如果绝地求生也能滑铲
|
||||
@SerializedName("trackid")
|
||||
var trackid: String, // 14016083035920227490
|
||||
@SerializedName("word")
|
||||
var word: String // av46169875
|
||||
)
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class SearchHot(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("list")
|
||||
var list: List<Hot>,
|
||||
@SerializedName("trackid")
|
||||
var trackid: String // 3482080616107297898
|
||||
) {
|
||||
data class Hot(
|
||||
@SerializedName("keyword")
|
||||
var keyword: String, // 凹凸世界
|
||||
@SerializedName("name_type")
|
||||
var nameType: String,
|
||||
@SerializedName("status")
|
||||
var status: String
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class SearchLiveResult(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("live_master")
|
||||
var liveMaster: LiveMaster,
|
||||
@SerializedName("live_room")
|
||||
var liveRoom: LiveRoom,
|
||||
@SerializedName("pages")
|
||||
var pages: Int, // 3
|
||||
@SerializedName("total")
|
||||
var total: Int, // 0
|
||||
@SerializedName("trackid")
|
||||
var trackid: String // 14587616663833842975
|
||||
) {
|
||||
data class LiveRoom(
|
||||
@SerializedName("items")
|
||||
var items: List<Item>,
|
||||
@SerializedName("pages")
|
||||
var pages: Int, // 3
|
||||
@SerializedName("total")
|
||||
var total: Int, // 57
|
||||
@SerializedName("trackid")
|
||||
var trackid: String
|
||||
) {
|
||||
data class Item(
|
||||
@SerializedName("area_v2_name")
|
||||
var areaV2Name: String, // 300英雄
|
||||
@SerializedName("attentions")
|
||||
var attentions: Int, // 2
|
||||
@SerializedName("badge")
|
||||
var badge: String, // 直播
|
||||
@SerializedName("cover")
|
||||
var cover: String, // https://i0.hdslb.com/bfs/live/16181996f2260024f861db58d0d3dea2dd390930.jpg
|
||||
@SerializedName("goto")
|
||||
var goto: String, // live
|
||||
@SerializedName("live_status")
|
||||
var liveStatus: Int, // 2
|
||||
@SerializedName("mid")
|
||||
var mid: Long, // 82542745
|
||||
@SerializedName("name")
|
||||
var name: String, // SAO刀剑神域丶
|
||||
@SerializedName("online")
|
||||
var online: Int, // 7
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 3387258
|
||||
@SerializedName("region")
|
||||
var region: Int, // 4
|
||||
@SerializedName("roomid")
|
||||
var roomid: Long, // 3387258
|
||||
@SerializedName("tags")
|
||||
var tags: String, // 点点关注
|
||||
@SerializedName("title")
|
||||
var title: String, // SAO刀剑神域丶的直播间
|
||||
@SerializedName("type")
|
||||
var type: String, // live_room
|
||||
@SerializedName("uri")
|
||||
var uri: String // bilibili://live/3387258?broadcast_type=0
|
||||
)
|
||||
}
|
||||
|
||||
data class LiveMaster(
|
||||
@SerializedName("pages")
|
||||
var pages: Int, // 0
|
||||
@SerializedName("total")
|
||||
var total: Int, // 0
|
||||
@SerializedName("trackid")
|
||||
var trackid: String
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class SearchMovieResult(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("items")
|
||||
var items: List<Item>,
|
||||
@SerializedName("pages")
|
||||
var pages: Int, // 1
|
||||
@SerializedName("total")
|
||||
var total: Int, // 2
|
||||
@SerializedName("trackid")
|
||||
var trackid: String // 11415000413319714311
|
||||
) {
|
||||
data class Item(
|
||||
@SerializedName("area")
|
||||
var area: String, // 日本
|
||||
@SerializedName("badge")
|
||||
var badge: String, // 电影
|
||||
/**
|
||||
* badge 的特殊样式
|
||||
*/
|
||||
@SerializedName("badges")
|
||||
var badges: List<Map<String, String>>,
|
||||
@SerializedName("cover")
|
||||
var cover: String, // https://i0.hdslb.com/bfs/bangumi/aef914fac9edfa518c4df9a82d5d3d0cba08a451.jpg
|
||||
/**
|
||||
* 类型为 纪录片 时没有 cv, 其他一些字段同理
|
||||
*/
|
||||
@SerializedName("cv")
|
||||
var cv: String?, // 曹旭鹏、阎萌萌、碧涓、惠霖、梦娇
|
||||
/**
|
||||
* 有些纪录片有分集, 有些没有
|
||||
*/
|
||||
@SerializedName("episodes")
|
||||
var episodes: List<Episode>?,
|
||||
@SerializedName("goto")
|
||||
var goto: String, // movie
|
||||
/**
|
||||
* 一些纪录片没有 label, 其他一些字段同理
|
||||
*/
|
||||
@SerializedName("label")
|
||||
var label: String?, // 演员:曹旭鹏、阎萌萌、碧涓、惠霖、梦娇
|
||||
/**
|
||||
* 2 为剧场版动画, 3 为纪录片
|
||||
*/
|
||||
@SerializedName("media_type")
|
||||
var mediaType: Int, // 2
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 115472
|
||||
@SerializedName("ptime")
|
||||
var ptime: Long, // 1505404800
|
||||
@SerializedName("rating")
|
||||
var rating: Double, // 5.8
|
||||
@SerializedName("season_id")
|
||||
var seasonId: Int, // 12767
|
||||
@SerializedName("season_type")
|
||||
var seasonType: Int, // 2
|
||||
@SerializedName("season_type_name")
|
||||
var seasonTypeName: String, // 电影
|
||||
@SerializedName("staff")
|
||||
var staff: String?, // 导演:伊藤智彦编剧:川原砾
|
||||
@SerializedName("style")
|
||||
var style: String, // 科幻/动画
|
||||
@SerializedName("title")
|
||||
var title: String, // 刀剑神域:序列之争(中文)
|
||||
@SerializedName("uri")
|
||||
var uri: String, // https://www.bilibili.com/bangumi/play/ss12767/
|
||||
@SerializedName("vote")
|
||||
var vote: Int? // 581
|
||||
) {
|
||||
data class Episode(
|
||||
@SerializedName("index")
|
||||
var index: String, // 4
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 250006
|
||||
@SerializedName("uri")
|
||||
var uri: String // https://www.bilibili.com/bangumi/play/ep250006
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class SearchResult(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("array")
|
||||
var array: Int, // 1
|
||||
@SerializedName("attribute")
|
||||
var attribute: Int, // 1
|
||||
@SerializedName("item")
|
||||
var item: List<Item>,
|
||||
@SerializedName("items")
|
||||
var items: JsonElement,
|
||||
@SerializedName("nav")
|
||||
var nav: List<Nav>,
|
||||
@SerializedName("page")
|
||||
var page: Int, // 1
|
||||
@SerializedName("trackid")
|
||||
var trackid: String // 9256129479667154639
|
||||
) {
|
||||
data class Nav(
|
||||
@SerializedName("name")
|
||||
var name: String, // 专栏
|
||||
@SerializedName("pages")
|
||||
var pages: Int, // 50
|
||||
@SerializedName("total")
|
||||
var total: Int, // 1000
|
||||
@SerializedName("type")
|
||||
var type: Int // 6
|
||||
)
|
||||
|
||||
data class Item(
|
||||
@SerializedName("area")
|
||||
var area: String, // 日本
|
||||
@SerializedName("author")
|
||||
var author: String, // 那位滑稽
|
||||
@SerializedName("badge")
|
||||
var badge: String, // 专栏
|
||||
@SerializedName("badges")
|
||||
var badges: List<Badge>,
|
||||
@SerializedName("cover")
|
||||
var cover: String, // https://i0.hdslb.com/bfs/archive/a4a58b6772d0de16df6e9a7d3e208fd52a552710.jpg
|
||||
@SerializedName("cv")
|
||||
var cv: String, // 曹旭鹏、阎萌萌、碧涓、惠霖、梦娇
|
||||
@SerializedName("danmaku")
|
||||
var danmaku: Int, // 196
|
||||
@SerializedName("desc")
|
||||
var desc: String, // 刀剑神域第一季作品以 2022 年为舞台,大厂牌电子机械制造商“ARGUS”开发出-“NERvGear”-能连结虚拟世界的机器。完全的虚拟实境终于能够实现。主角桐人使用 NERvGear 游玩 VR MMORPG《Sword Art Online》的玩家,幸运地参与过封测并买下正式版的桐人,和正式营运就马上“完全潜行”享受着正式版的 SAO 世界。就在游戏四小时多后,桐人发现到“登出”指令竟然消失。认为只是系统暂时出错的桐人和开始陷入混乱的所有玩家们一起被传送到开始地点广场,并传来游戏设计者的死亡游戏
|
||||
@SerializedName("duration")
|
||||
var duration: String, // 591:52
|
||||
@SerializedName("episodes")
|
||||
var episodes: List<Episode>,
|
||||
@SerializedName("face")
|
||||
var face: String, // http://i0.hdslb.com/bfs/face/d34c34fca6471f07e60db3a7007cc5c2eb6bd785.jpg
|
||||
@SerializedName("goto")
|
||||
var goto: String, // recommend_word
|
||||
@SerializedName("id")
|
||||
var id: Int, // 2227576
|
||||
@SerializedName("image_urls")
|
||||
var imageUrls: List<String>,
|
||||
@SerializedName("is_atten")
|
||||
var isAtten: Int, // 1
|
||||
@SerializedName("is_selection")
|
||||
var isSelection: Int, // 1
|
||||
@SerializedName("label")
|
||||
var label: String, // 演员:曹旭鹏、阎萌萌、碧涓、惠霖、梦娇
|
||||
@SerializedName("like")
|
||||
var like: Int, // 13
|
||||
@SerializedName("linktype")
|
||||
var linktype: String, // query_rec
|
||||
@SerializedName("list")
|
||||
var list: List<X>,
|
||||
@SerializedName("media_type")
|
||||
var mediaType: Int, // 2
|
||||
@SerializedName("mid")
|
||||
var mid: Long, // 382820503
|
||||
@SerializedName("new_rec_tags")
|
||||
var newRecTags: List<NewRecTag>,
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 33673993
|
||||
@SerializedName("play")
|
||||
var play: Int, // 324938
|
||||
@SerializedName("position")
|
||||
var position: Int, // 21
|
||||
@SerializedName("ptime")
|
||||
var ptime: Long, // 1505404800
|
||||
@SerializedName("rating")
|
||||
var rating: Double, // 5.8
|
||||
@SerializedName("rec_tags")
|
||||
var recTags: List<String>,
|
||||
@SerializedName("reply")
|
||||
var reply: Int, // 4
|
||||
@SerializedName("season_id")
|
||||
var seasonId: Int, // 12767
|
||||
@SerializedName("season_type")
|
||||
var seasonType: Int, // 2
|
||||
@SerializedName("season_type_name")
|
||||
var seasonTypeName: String, // 电影
|
||||
@SerializedName("staff")
|
||||
var staff: String, // 导演:伊藤智彦编剧:川原砾
|
||||
@SerializedName("style")
|
||||
var style: String, // 科幻/动画
|
||||
@SerializedName("template_id")
|
||||
var templateId: Int, // 4
|
||||
@SerializedName("title")
|
||||
var title: String, // 相关推荐
|
||||
@SerializedName("trackid")
|
||||
var trackid: String, // 9256129479667154639
|
||||
@SerializedName("uri")
|
||||
var uri: String, // bilibili://video/33673993?player_width=352&player_height=288&player_rotate=0
|
||||
@SerializedName("view")
|
||||
var view: Int, // 2310
|
||||
@SerializedName("vote")
|
||||
var vote: Int // 581
|
||||
) {
|
||||
data class NewRecTag(
|
||||
@SerializedName("bg_color")
|
||||
var bgColor: String, // #FAAB4B
|
||||
@SerializedName("bg_color_night")
|
||||
var bgColorNight: String, // #BA833F
|
||||
@SerializedName("bg_style")
|
||||
var bgStyle: Int, // 1
|
||||
@SerializedName("border_color")
|
||||
var borderColor: String, // #FAAB4B
|
||||
@SerializedName("border_color_night")
|
||||
var borderColorNight: String, // #BA833F
|
||||
@SerializedName("text")
|
||||
var text: String, // MINECRAFT
|
||||
@SerializedName("text_color")
|
||||
var textColor: String, // #FFFFFFFF
|
||||
@SerializedName("text_color_night")
|
||||
var textColorNight: String // #E5E5E5
|
||||
)
|
||||
|
||||
data class Badge(
|
||||
@SerializedName("bg_color")
|
||||
var bgColor: String, // #FB7299
|
||||
@SerializedName("bg_color_night")
|
||||
var bgColorNight: String, // #BB5B76
|
||||
@SerializedName("bg_style")
|
||||
var bgStyle: Int, // 1
|
||||
@SerializedName("border_color")
|
||||
var borderColor: String, // #FB7299
|
||||
@SerializedName("border_color_night")
|
||||
var borderColorNight: String, // #BB5B76
|
||||
@SerializedName("text")
|
||||
var text: String, // 会员抢先
|
||||
@SerializedName("text_color")
|
||||
var textColor: String, // #FFFFFF
|
||||
@SerializedName("text_color_night")
|
||||
var textColorNight: String // #E5E5E5
|
||||
)
|
||||
|
||||
data class X(
|
||||
@SerializedName("from_source")
|
||||
var fromSource: String, // query_rec_search
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 7852399559249609627
|
||||
@SerializedName("title")
|
||||
var title: String, // FATE 刀剑神域
|
||||
@SerializedName("type")
|
||||
var type: String // query_rec
|
||||
)
|
||||
|
||||
data class Episode(
|
||||
@SerializedName("index")
|
||||
var index: String, // 21
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 250557
|
||||
@SerializedName("uri")
|
||||
var uri: String // https://www.bilibili.com/bangumi/play/ep250557
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class SearchSuggest(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("list")
|
||||
var list: List<Suggest>,
|
||||
@SerializedName("trackid")
|
||||
var trackid: String // 8544564822819789247
|
||||
) {
|
||||
data class Suggest(
|
||||
@SerializedName("from")
|
||||
var from: String, // search
|
||||
@SerializedName("keyword")
|
||||
var keyword: String, // 刀剑神域 ALICIZATION
|
||||
@SerializedName("position")
|
||||
var position: Int, // 10
|
||||
@SerializedName("sug_type")
|
||||
var sugType: String?, // 番剧
|
||||
@SerializedName("term_type")
|
||||
var termType: Int, // 8
|
||||
@SerializedName("title")
|
||||
var title: String // 刀剑神域 ALICIZATION
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class SearchUserResult(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("items")
|
||||
var items: List<Item>,
|
||||
@SerializedName("pages")
|
||||
var pages: Int, // 2
|
||||
@SerializedName("total")
|
||||
var total: Int, // 0
|
||||
@SerializedName("trackid")
|
||||
var trackid: String // 15623048138266462990
|
||||
) {
|
||||
data class Item(
|
||||
@SerializedName("archives")
|
||||
var archives: Int, // 1
|
||||
@SerializedName("av_items")
|
||||
var avItems: List<AvItem>,
|
||||
@SerializedName("cover")
|
||||
var cover: String, // https://i0.hdslb.com/bfs/face/c3200c52ae76004fbbab44010990431d0604aee5.jpg
|
||||
@SerializedName("fans")
|
||||
var fans: Int, // 3
|
||||
@SerializedName("goto")
|
||||
var goto: String, // author
|
||||
@SerializedName("is_up")
|
||||
var isUp: Boolean, // true
|
||||
@SerializedName("level")
|
||||
var level: Int, // 3
|
||||
@SerializedName("live_status")
|
||||
var liveStatus: Int, // 1
|
||||
@SerializedName("live_uri")
|
||||
var liveUri: String, // bilibili://live/3234638?broadcast_type=0
|
||||
@SerializedName("mid")
|
||||
var mid: Long, // 32557668
|
||||
@SerializedName("official_verify")
|
||||
var officialVerify: OfficialVerify,
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 32557668
|
||||
@SerializedName("roomid")
|
||||
var roomid: Long, // 3234638
|
||||
@SerializedName("sign")
|
||||
var sign: String, // 担心额刚好阿西
|
||||
@SerializedName("title")
|
||||
var title: String, // 刀剑神域小漠
|
||||
@SerializedName("uri")
|
||||
var uri: String // bilibili://author/32557668
|
||||
) {
|
||||
data class OfficialVerify(
|
||||
@SerializedName("type")
|
||||
var type: Int // 127
|
||||
)
|
||||
|
||||
data class AvItem(
|
||||
@SerializedName("cover")
|
||||
var cover: String, // https://i0.hdslb.com/bfs/archive/95be7c1a940dda2bbf4c33213df94eb650e44d10.jpg
|
||||
@SerializedName("ctime")
|
||||
var ctime: Int, // 1535755416
|
||||
@SerializedName("danmaku")
|
||||
var danmaku: Int, // 1
|
||||
@SerializedName("duration")
|
||||
var duration: String, // 3:1
|
||||
@SerializedName("goto")
|
||||
var goto: String, // av
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 30843572
|
||||
@SerializedName("play")
|
||||
var play: Int, // 15
|
||||
@SerializedName("title")
|
||||
var title: String, // 官方认证:非洲正品大酋长
|
||||
@SerializedName("uri")
|
||||
var uri: String // bilibili://video/30843572?player_width=1920&player_height=1080&player_rotate=0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Sidebar(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: List<SidebarElement>,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class SidebarElement(
|
||||
@SerializedName("id")
|
||||
var id: Int, // 13
|
||||
@SerializedName("logo")
|
||||
var logo: String, // http://i0.hdslb.com/bfs/archive/91f7ba40e54502f7479c8d355e4298989bb8ebce.png
|
||||
@SerializedName("module")
|
||||
var module: Int, // 1
|
||||
@SerializedName("name")
|
||||
var name: String, // 会员购中心
|
||||
@SerializedName("online_time")
|
||||
var onlineTime: Int, // 0
|
||||
@SerializedName("param")
|
||||
var `param`: String, // bilibili://mall/mine?msource=mine
|
||||
@SerializedName("rank")
|
||||
var rank: Int, // 300
|
||||
@SerializedName("tip")
|
||||
var tip: Int // 1
|
||||
)
|
||||
}
|
||||
@@ -1,500 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Space(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("album")
|
||||
var album: Album,
|
||||
/**
|
||||
* 投稿
|
||||
*/
|
||||
@SerializedName("archive")
|
||||
var archive: Archive,
|
||||
@SerializedName("article")
|
||||
var article: Article,
|
||||
@SerializedName("audios")
|
||||
var audios: Audios,
|
||||
@SerializedName("card")
|
||||
var card: Card,
|
||||
@SerializedName("clip")
|
||||
var clip: Clip,
|
||||
/**
|
||||
* 最近投币
|
||||
*/
|
||||
@SerializedName("coin_archive")
|
||||
var coinArchive: CoinArchive,
|
||||
@SerializedName("elec")
|
||||
var elec: Elec,
|
||||
@SerializedName("favourite")
|
||||
var favourite: Favourite,
|
||||
@SerializedName("images")
|
||||
var images: Images,
|
||||
/**
|
||||
* Ta推荐的视频
|
||||
*/
|
||||
@SerializedName("like_archive")
|
||||
var likeArchive: LikeArchive,
|
||||
@SerializedName("live")
|
||||
var live: Live,
|
||||
@SerializedName("medal")
|
||||
var medal: Int, // 1
|
||||
@SerializedName("relation")
|
||||
var relation: Int, // 1
|
||||
@SerializedName("season")
|
||||
var season: Season,
|
||||
@SerializedName("setting")
|
||||
var setting: Setting,
|
||||
@SerializedName("tab")
|
||||
var tab: Tab
|
||||
) {
|
||||
data class Archive(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 8
|
||||
@SerializedName("item")
|
||||
var item: List<Item>
|
||||
) {
|
||||
data class Item(
|
||||
@SerializedName("author")
|
||||
var author: String, // hyx5020
|
||||
@SerializedName("cover")
|
||||
var cover: String, // http://i0.hdslb.com/bfs/archive/603e9b0d67400ce05199e0c8ff14cc4204c7e4e5.jpg
|
||||
@SerializedName("ctime")
|
||||
var ctime: Int, // 1475686898
|
||||
@SerializedName("danmaku")
|
||||
var danmaku: Int, // 2
|
||||
@SerializedName("duration")
|
||||
var duration: Int, // 3720
|
||||
@SerializedName("goto")
|
||||
var goto: String, // av
|
||||
@SerializedName("length")
|
||||
var length: String,
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 6557595
|
||||
@SerializedName("play")
|
||||
var play: Int, // 246
|
||||
@SerializedName("title")
|
||||
var title: String, // 蓝色起源 New Shepard In-flight Escape Test
|
||||
@SerializedName("tname")
|
||||
var tname: String, // 趣味科普人文
|
||||
@SerializedName("ugc_pay")
|
||||
var ugcPay: Int, // 0
|
||||
@SerializedName("uri")
|
||||
var uri: String // bilibili://video/6557595
|
||||
)
|
||||
}
|
||||
|
||||
data class Favourite(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 0
|
||||
@SerializedName("item")
|
||||
var item: List<Item>
|
||||
) {
|
||||
data class Item(
|
||||
@SerializedName("atten_count")
|
||||
var attenCount: Int, // 0
|
||||
@SerializedName("ctime")
|
||||
var ctime: Long, // 1451133174
|
||||
@SerializedName("cur_count")
|
||||
var curCount: Int, // 1
|
||||
@SerializedName("fid")
|
||||
var fid: Long, // 795158
|
||||
@SerializedName("max_count")
|
||||
var maxCount: Int, // 50000
|
||||
@SerializedName("media_id")
|
||||
var mediaId: Long, // 79515830
|
||||
@SerializedName("mid")
|
||||
var mid: Long, // 20293030
|
||||
@SerializedName("mtime")
|
||||
var mtime: Int, // 1544629663
|
||||
@SerializedName("name")
|
||||
var name: String, // 默认收藏夹
|
||||
@SerializedName("state")
|
||||
var state: Int // 0
|
||||
)
|
||||
}
|
||||
|
||||
data class Images(
|
||||
@SerializedName("imgUrl")
|
||||
var imgUrl: String
|
||||
)
|
||||
|
||||
data class Season(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 0
|
||||
@SerializedName("item")
|
||||
var item: List<Item>
|
||||
) {
|
||||
data class Item(
|
||||
@SerializedName("attention")
|
||||
var attention: String, // 0
|
||||
@SerializedName("cover")
|
||||
var cover: String, // http://i0.hdslb.com/bfs/bangumi/de944b7c9306932d8dd3dcaeaf2eeec8670deec5.png
|
||||
@SerializedName("finish")
|
||||
var finish: Int, // 0
|
||||
@SerializedName("goto")
|
||||
var goto: String, // bangumi
|
||||
@SerializedName("index")
|
||||
var index: String,
|
||||
@SerializedName("is_finish")
|
||||
var isFinish: String,
|
||||
@SerializedName("is_started")
|
||||
var isStarted: Int, // 1
|
||||
@SerializedName("mtime")
|
||||
var mtime: Int, // 0
|
||||
@SerializedName("newest_ep_id")
|
||||
var newestEpId: String,
|
||||
@SerializedName("newest_ep_index")
|
||||
var newestEpIndex: String, // 8
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 26284
|
||||
@SerializedName("title")
|
||||
var title: String, // 盾之勇者成名录
|
||||
@SerializedName("total_count")
|
||||
var totalCount: String, // 25
|
||||
@SerializedName("uri")
|
||||
var uri: String // http://bangumi.bilibili.com/anime/26284
|
||||
)
|
||||
}
|
||||
|
||||
data class Article(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 0
|
||||
@SerializedName("item")
|
||||
var item: List<JsonElement>,
|
||||
@SerializedName("lists")
|
||||
var lists: List<JsonElement>,
|
||||
@SerializedName("lists_count")
|
||||
var listsCount: Int // 0
|
||||
)
|
||||
|
||||
data class Album(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 0
|
||||
@SerializedName("has_more")
|
||||
var hasMore: Int, // 0
|
||||
@SerializedName("item")
|
||||
var item: List<JsonElement>,
|
||||
@SerializedName("next_offset")
|
||||
var nextOffset: Int // 0
|
||||
)
|
||||
|
||||
data class CoinArchive(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 1
|
||||
@SerializedName("item")
|
||||
var item: List<Item>
|
||||
) {
|
||||
data class Item(
|
||||
@SerializedName("cover")
|
||||
var cover: String, // http://i0.hdslb.com/bfs/archive/0b49549eeefd58441ad88613fe460630182d1afe.jpg
|
||||
@SerializedName("ctime")
|
||||
var ctime: Int, // 1548875105
|
||||
@SerializedName("danmaku")
|
||||
var danmaku: Int, // 2
|
||||
@SerializedName("duration")
|
||||
var duration: Int, // 169
|
||||
@SerializedName("goto")
|
||||
var goto: String, // av
|
||||
@SerializedName("length")
|
||||
var length: String,
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 42179433
|
||||
@SerializedName("play")
|
||||
var play: Int, // 3373
|
||||
@SerializedName("title")
|
||||
var title: String, // 《吹响吧!上低音号!》Dream Solister 上低音号四重奏
|
||||
@SerializedName("tname")
|
||||
var tname: String,
|
||||
@SerializedName("ugc_pay")
|
||||
var ugcPay: Int, // 0
|
||||
@SerializedName("uri")
|
||||
var uri: String // bilibili://video/42179433
|
||||
)
|
||||
}
|
||||
|
||||
data class Elec(
|
||||
@SerializedName("elec_num")
|
||||
var elecNum: Int, // 0
|
||||
@SerializedName("elec_set")
|
||||
var elecSet: ElecSet,
|
||||
@SerializedName("list")
|
||||
var list: List<JsonElement>,
|
||||
@SerializedName("show")
|
||||
var show: Boolean // true
|
||||
) {
|
||||
data class ElecSet(
|
||||
@SerializedName("elec_list")
|
||||
var elecList: List<Elec>,
|
||||
@SerializedName("elec_theme")
|
||||
var elecTheme: Int, // 0
|
||||
@SerializedName("integrity_rate")
|
||||
var integrityRate: Double, // 10.0
|
||||
@SerializedName("rmb_rate")
|
||||
var rmbRate: Double, // 10.0
|
||||
@SerializedName("round_mode")
|
||||
var roundMode: Int // 0
|
||||
) {
|
||||
data class Elec(
|
||||
@SerializedName("elec_num")
|
||||
var elecNum: Int, // 0
|
||||
@SerializedName("is_customize")
|
||||
var isCustomize: Int, // 1
|
||||
@SerializedName("max_elec")
|
||||
var maxElec: Int, // 99999
|
||||
@SerializedName("min_elec")
|
||||
var minElec: Int, // 20
|
||||
@SerializedName("title")
|
||||
var title: String // 自定义
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class Setting(
|
||||
@SerializedName("bangumi")
|
||||
var bangumi: Int, // 0
|
||||
@SerializedName("channel")
|
||||
var channel: Int, // 1
|
||||
@SerializedName("coins_video")
|
||||
var coinsVideo: Int, // 1
|
||||
@SerializedName("fav_video")
|
||||
var favVideo: Int, // 0
|
||||
@SerializedName("groups")
|
||||
var groups: Int, // 0
|
||||
@SerializedName("likes_video")
|
||||
var likesVideo: Int, // 1
|
||||
@SerializedName("played_game")
|
||||
var playedGame: Int // 0
|
||||
)
|
||||
|
||||
data class Card(
|
||||
@SerializedName("DisplayRank")
|
||||
var displayRank: String,
|
||||
@SerializedName("approve")
|
||||
var approve: Boolean, // false
|
||||
@SerializedName("article")
|
||||
var article: Int, // 0
|
||||
@SerializedName("attention")
|
||||
var attention: Int, // 113
|
||||
@SerializedName("attentions")
|
||||
var attentions: JsonElement?, // null
|
||||
@SerializedName("birthday")
|
||||
var birthday: String,
|
||||
@SerializedName("description")
|
||||
var description: String,
|
||||
@SerializedName("end_time")
|
||||
var endTime: Int, // 0
|
||||
@SerializedName("face")
|
||||
var face: String, // http://i0.hdslb.com/bfs/face/0434dccc0ec4de223e8ca374dea06a6e1e8eb471.jpg
|
||||
@SerializedName("fans")
|
||||
var fans: Int, // 539
|
||||
@SerializedName("friend")
|
||||
var friend: Int, // 0
|
||||
@SerializedName("level_info")
|
||||
var levelInfo: LevelInfo,
|
||||
@SerializedName("mid")
|
||||
var mid: String, // 2866663
|
||||
@SerializedName("name")
|
||||
var name: String, // hyx5020
|
||||
@SerializedName("nameplate")
|
||||
var nameplate: Nameplate,
|
||||
@SerializedName("official_verify")
|
||||
var officialVerify: OfficialVerify,
|
||||
@SerializedName("pendant")
|
||||
var pendant: Pendant,
|
||||
@SerializedName("place")
|
||||
var place: String,
|
||||
@SerializedName("rank")
|
||||
var rank: String,
|
||||
@SerializedName("regtime")
|
||||
var regtime: Int, // 0
|
||||
@SerializedName("sex")
|
||||
var sex: String, // 保密
|
||||
@SerializedName("sign")
|
||||
var sign: String, // 简介?不存在的
|
||||
@SerializedName("silence")
|
||||
var silence: Int, // 0
|
||||
@SerializedName("silence_url")
|
||||
var silenceUrl: String,
|
||||
@SerializedName("spacesta")
|
||||
var spacesta: Int, // 0
|
||||
@SerializedName("vip")
|
||||
var vip: Vip
|
||||
) {
|
||||
data class Vip(
|
||||
@SerializedName("accessStatus")
|
||||
var accessStatus: Int, // 0
|
||||
@SerializedName("dueRemark")
|
||||
var dueRemark: String,
|
||||
@SerializedName("vipDueDate")
|
||||
var vipDueDate: Long, // 1623081600000
|
||||
@SerializedName("vipStatus")
|
||||
var vipStatus: Int, // 1
|
||||
@SerializedName("vipStatusWarn")
|
||||
var vipStatusWarn: String,
|
||||
@SerializedName("vipType")
|
||||
var vipType: Int // 2
|
||||
)
|
||||
|
||||
data class OfficialVerify(
|
||||
@SerializedName("desc")
|
||||
var desc: String,
|
||||
@SerializedName("role")
|
||||
var role: Int, // 0
|
||||
@SerializedName("title")
|
||||
var title: String,
|
||||
@SerializedName("type")
|
||||
var type: Int // -1
|
||||
)
|
||||
|
||||
data class Pendant(
|
||||
@SerializedName("expire")
|
||||
var expire: Int, // 0
|
||||
@SerializedName("image")
|
||||
var image: String,
|
||||
@SerializedName("name")
|
||||
var name: String,
|
||||
@SerializedName("pid")
|
||||
var pid: Int // 0
|
||||
)
|
||||
|
||||
data class Nameplate(
|
||||
@SerializedName("condition")
|
||||
var condition: String,
|
||||
@SerializedName("image")
|
||||
var image: String,
|
||||
@SerializedName("image_small")
|
||||
var imageSmall: String,
|
||||
@SerializedName("level")
|
||||
var level: String,
|
||||
@SerializedName("name")
|
||||
var name: String,
|
||||
@SerializedName("nid")
|
||||
var nid: Int // 0
|
||||
)
|
||||
|
||||
data class LevelInfo(
|
||||
@SerializedName("current_exp")
|
||||
var currentExp: Int, // 11224
|
||||
@SerializedName("current_level")
|
||||
var currentLevel: Int, // 5
|
||||
@SerializedName("current_min")
|
||||
var currentMin: Int, // 10800
|
||||
@SerializedName("next_exp")
|
||||
var nextExp: Int // 28800
|
||||
)
|
||||
}
|
||||
|
||||
data class Tab(
|
||||
@SerializedName("album")
|
||||
var album: Boolean, // false
|
||||
@SerializedName("archive")
|
||||
var archive: Boolean, // true
|
||||
@SerializedName("article")
|
||||
var article: Boolean, // false
|
||||
@SerializedName("audios")
|
||||
var audios: Boolean, // false
|
||||
@SerializedName("bangumi")
|
||||
var bangumi: Boolean, // false
|
||||
@SerializedName("clip")
|
||||
var clip: Boolean, // false
|
||||
@SerializedName("coin")
|
||||
var coin: Boolean, // true
|
||||
@SerializedName("community")
|
||||
var community: Boolean, // false
|
||||
@SerializedName("dynamic")
|
||||
var `dynamic`: Boolean, // true
|
||||
@SerializedName("favorite")
|
||||
var favorite: Boolean, // false
|
||||
@SerializedName("like")
|
||||
var like: Boolean, // true
|
||||
@SerializedName("mall")
|
||||
var mall: Boolean, // false
|
||||
@SerializedName("shop")
|
||||
var shop: Boolean // false
|
||||
)
|
||||
|
||||
data class Audios(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 0
|
||||
@SerializedName("item")
|
||||
var item: List<JsonElement>
|
||||
)
|
||||
|
||||
data class LikeArchive(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 7
|
||||
@SerializedName("item")
|
||||
var item: List<Item>
|
||||
) {
|
||||
data class Item(
|
||||
@SerializedName("cover")
|
||||
var cover: String, // http://i1.hdslb.com/bfs/archive/6e246c830b26591924984f0f9275eede61621d80.jpg
|
||||
@SerializedName("ctime")
|
||||
var ctime: Int, // 1527760833
|
||||
@SerializedName("danmaku")
|
||||
var danmaku: Int, // 11309
|
||||
@SerializedName("duration")
|
||||
var duration: Int, // 215
|
||||
@SerializedName("goto")
|
||||
var goto: String, // av
|
||||
@SerializedName("length")
|
||||
var length: String,
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 24180113
|
||||
@SerializedName("play")
|
||||
var play: Int, // 1325557
|
||||
@SerializedName("title")
|
||||
var title: String, // 【洛天依/言和原创曲】反派死于话多 (真实童话 Act.3)【PV付】
|
||||
@SerializedName("tname")
|
||||
var tname: String,
|
||||
@SerializedName("ugc_pay")
|
||||
var ugcPay: Int, // 0
|
||||
@SerializedName("uri")
|
||||
var uri: String // bilibili://video/24180113
|
||||
)
|
||||
}
|
||||
|
||||
data class Live(
|
||||
@SerializedName("broadcast_type")
|
||||
var broadcastType: Int, // 0
|
||||
@SerializedName("cover")
|
||||
var cover: String, // http://i0.hdslb.com/bfs/live/af5786fa6d011c143fde5275c7af011f2c54a619.jpg
|
||||
@SerializedName("liveStatus")
|
||||
var liveStatus: Int, // 0
|
||||
@SerializedName("online")
|
||||
var online: Int, // 103
|
||||
@SerializedName("roomStatus")
|
||||
var roomStatus: Int, // 1
|
||||
@SerializedName("roomid")
|
||||
var roomid: Long, // 29434
|
||||
@SerializedName("roundStatus")
|
||||
var roundStatus: Int, // 0
|
||||
@SerializedName("title")
|
||||
var title: String, // 直播
|
||||
@SerializedName("url")
|
||||
var url: String // http://live.bilibili.com/29434
|
||||
)
|
||||
|
||||
data class Clip(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 0
|
||||
@SerializedName("has_more")
|
||||
var hasMore: Int, // 0
|
||||
@SerializedName("item")
|
||||
var item: List<JsonElement>,
|
||||
@SerializedName("next_offset")
|
||||
var nextOffset: Int // 0
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Tab(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Map<String, List<UIElement>>,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ver")
|
||||
var ver: String // 5720051238481856755
|
||||
) {
|
||||
data class UIElement(
|
||||
@SerializedName("default_selected")
|
||||
var defaultSelected: Int, // 1
|
||||
@SerializedName("id")
|
||||
var id: Int, // 30
|
||||
@SerializedName("name")
|
||||
var name: String, // 追番
|
||||
@SerializedName("pos")
|
||||
var pos: Int, // 4
|
||||
@SerializedName("tab_id")
|
||||
var tabId: String, // 追番Tab
|
||||
@SerializedName("uri")
|
||||
var uri: String // bilibili://pgc/home
|
||||
)
|
||||
}
|
||||
@@ -1,449 +0,0 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class View(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("aid")
|
||||
var aid: Int, // 44172743
|
||||
@SerializedName("attribute")
|
||||
var attribute: Int, // 16512
|
||||
@SerializedName("cid")
|
||||
var cid: Int, // 77356986
|
||||
@SerializedName("cm_config")
|
||||
var cmConfig: CmConfig,
|
||||
@SerializedName("cms")
|
||||
var cms: List<Cm>,
|
||||
/**
|
||||
* copyright 为 1 时表示自制, 2 表示转载
|
||||
*/
|
||||
@SerializedName("copyright")
|
||||
var copyright: Int, // 1
|
||||
@SerializedName("ctime")
|
||||
var ctime: Int, // 1550654012
|
||||
@SerializedName("desc")
|
||||
var desc: String,
|
||||
@SerializedName("dimension")
|
||||
var dimension: Dimension,
|
||||
@SerializedName("dislike_reasons")
|
||||
var dislikeReasons: List<DislikeReason>,
|
||||
@SerializedName("dm_seg")
|
||||
var dmSeg: Int, // 1
|
||||
@SerializedName("duration")
|
||||
var duration: Int, // 444
|
||||
@SerializedName("dynamic")
|
||||
var `dynamic`: String, // #流浪地球##木星##太阳#
|
||||
@SerializedName("elec")
|
||||
var elec: Elec,
|
||||
@SerializedName("owner")
|
||||
var owner: Owner,
|
||||
@SerializedName("owner_ext")
|
||||
var ownerExt: OwnerExt,
|
||||
@SerializedName("pages")
|
||||
var pages: List<Page>,
|
||||
@SerializedName("pic")
|
||||
var pic: String, // http://i0.hdslb.com/bfs/archive/783445f04541299ee84de21a2479cce88d8268ff.jpg
|
||||
@SerializedName("pubdate")
|
||||
var pubdate: Int, // 1550654012
|
||||
@SerializedName("relates")
|
||||
var relates: List<Relate>,
|
||||
@SerializedName("req_user")
|
||||
var reqUser: ReqUser,
|
||||
@SerializedName("rights")
|
||||
var rights: Rights,
|
||||
@SerializedName("staff")
|
||||
var staff: List<Staff>,
|
||||
@SerializedName("stat")
|
||||
var stat: Stat,
|
||||
@SerializedName("state")
|
||||
var state: Int, // 0
|
||||
@SerializedName("tag")
|
||||
var tag: List<Tag>,
|
||||
@SerializedName("tid")
|
||||
var tid: Int, // 96
|
||||
@SerializedName("title")
|
||||
var title: String, // 模拟流浪地球进入木星的洛希极限,太阳要膨胀吞没地球这事是真的吗?
|
||||
@SerializedName("tname")
|
||||
var tname: String, // 星海
|
||||
@SerializedName("videos")
|
||||
var videos: Int // 1
|
||||
) {
|
||||
data class Cm(
|
||||
@SerializedName("ad_info")
|
||||
var adInfo: JsonElement, // {}
|
||||
@SerializedName("client_ip")
|
||||
var clientIp: String, // 218.205.81.101
|
||||
@SerializedName("index")
|
||||
var index: Int, // 1
|
||||
@SerializedName("is_ad_loc")
|
||||
var isAdLoc: Boolean, // true
|
||||
@SerializedName("request_id")
|
||||
var requestId: String, // 1550675871470q172a22a56a79q738
|
||||
@SerializedName("rsc_id")
|
||||
var rscId: Int, // 2337
|
||||
@SerializedName("src_id")
|
||||
var srcId: Int // 2338
|
||||
)
|
||||
|
||||
data class Owner(
|
||||
@SerializedName("face")
|
||||
var face: String, // http://i1.hdslb.com/bfs/face/9a586d1ef659b322af150c925976a134ad046a74.jpg
|
||||
@SerializedName("mid")
|
||||
var mid: Long, // 393484294
|
||||
@SerializedName("name")
|
||||
var name: String // 娱乐酱鸭
|
||||
)
|
||||
|
||||
data class Elec(
|
||||
@SerializedName("elec_set")
|
||||
var elecSet: ElecSet,
|
||||
@SerializedName("list")
|
||||
var list: List<JsonElement>,
|
||||
@SerializedName("show")
|
||||
var show: Boolean // true
|
||||
) {
|
||||
data class ElecSet(
|
||||
@SerializedName("elec_list")
|
||||
var elecList: List<Elec>,
|
||||
@SerializedName("elec_theme")
|
||||
var elecTheme: Int, // 0
|
||||
@SerializedName("integrity_rate")
|
||||
var integrityRate: Double, // 10.0
|
||||
@SerializedName("rmb_rate")
|
||||
var rmbRate: Double, // 10.0
|
||||
@SerializedName("round_mode")
|
||||
var roundMode: Int // 0
|
||||
) {
|
||||
data class Elec(
|
||||
@SerializedName("elec_num")
|
||||
var elecNum: Int, // 0
|
||||
@SerializedName("is_customize")
|
||||
var isCustomize: Int, // 1
|
||||
@SerializedName("max_elec")
|
||||
var maxElec: Int, // 99999
|
||||
@SerializedName("min_elec")
|
||||
var minElec: Int, // 20
|
||||
@SerializedName("title")
|
||||
var title: String // 自定义
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class ReqUser(
|
||||
@SerializedName("attention")
|
||||
var attention: Int, // -999
|
||||
@SerializedName("coin")
|
||||
var coin: Int, // 0
|
||||
@SerializedName("dislike")
|
||||
var dislike: Int, // 0
|
||||
@SerializedName("favorite")
|
||||
var favorite: Int, // 0
|
||||
@SerializedName("like")
|
||||
var like: Int // 0
|
||||
)
|
||||
|
||||
data class Stat(
|
||||
@SerializedName("aid")
|
||||
var aid: Int, // 44172743
|
||||
@SerializedName("coin")
|
||||
var coin: Int, // 23
|
||||
@SerializedName("danmaku")
|
||||
var danmaku: Int, // 19
|
||||
@SerializedName("dislike")
|
||||
var dislike: Int, // 0
|
||||
@SerializedName("favorite")
|
||||
var favorite: Int, // 10
|
||||
@SerializedName("his_rank")
|
||||
var hisRank: Int, // 0
|
||||
@SerializedName("like")
|
||||
var like: Int, // 35
|
||||
@SerializedName("now_rank")
|
||||
var nowRank: Int, // 0
|
||||
@SerializedName("reply")
|
||||
var reply: Int, // 11
|
||||
@SerializedName("share")
|
||||
var share: Int, // 0
|
||||
@SerializedName("view")
|
||||
var view: Int // 1995
|
||||
)
|
||||
|
||||
data class OwnerExt(
|
||||
@SerializedName("assists")
|
||||
var assists: JsonElement?, // null
|
||||
@SerializedName("fans")
|
||||
var fans: Int, // 275
|
||||
@SerializedName("official_verify")
|
||||
var officialVerify: OfficialVerify,
|
||||
@SerializedName("vip")
|
||||
var vip: Vip
|
||||
) {
|
||||
data class OfficialVerify(
|
||||
@SerializedName("desc")
|
||||
var desc: String,
|
||||
@SerializedName("type")
|
||||
var type: Int // -1
|
||||
)
|
||||
|
||||
data class Vip(
|
||||
@SerializedName("accessStatus")
|
||||
var accessStatus: Int, // 0
|
||||
@SerializedName("dueRemark")
|
||||
var dueRemark: String,
|
||||
@SerializedName("vipDueDate")
|
||||
var vipDueDate: Long, // 0
|
||||
@SerializedName("vipStatus")
|
||||
var vipStatus: Int, // 0
|
||||
@SerializedName("vipStatusWarn")
|
||||
var vipStatusWarn: String,
|
||||
@SerializedName("vipType")
|
||||
var vipType: Int // 0
|
||||
)
|
||||
}
|
||||
|
||||
data class Tag(
|
||||
@SerializedName("attribute")
|
||||
var attribute: Int, // 0
|
||||
@SerializedName("cover")
|
||||
var cover: String,
|
||||
@SerializedName("hated")
|
||||
var hated: Int, // 0
|
||||
@SerializedName("hates")
|
||||
var hates: Int, // 0
|
||||
@SerializedName("is_activity")
|
||||
var isActivity: Int, // 0
|
||||
@SerializedName("liked")
|
||||
var liked: Int, // 0
|
||||
@SerializedName("likes")
|
||||
var likes: Int, // 0
|
||||
@SerializedName("tag_id")
|
||||
var tagId: Int, // 7534
|
||||
@SerializedName("tag_name")
|
||||
var tagName: String // 未来
|
||||
)
|
||||
|
||||
data class Staff(
|
||||
@SerializedName("attention")
|
||||
var attention: Int, // 0
|
||||
@SerializedName("face")
|
||||
var face: String, // http://i1.hdslb.com/bfs/face/9a586d1ef659b322af150c925976a134ad046a74.jpg
|
||||
@SerializedName("mid")
|
||||
var mid: Long, // 393484294
|
||||
@SerializedName("name")
|
||||
var name: String, // 娱乐酱鸭
|
||||
@SerializedName("official_verify")
|
||||
var officialVerify: OfficialVerify,
|
||||
@SerializedName("title")
|
||||
var title: String, // UP主
|
||||
@SerializedName("vip")
|
||||
var vip: Vip
|
||||
) {
|
||||
data class OfficialVerify(
|
||||
@SerializedName("desc")
|
||||
var desc: String,
|
||||
@SerializedName("type")
|
||||
var type: Int // -1
|
||||
)
|
||||
|
||||
data class Vip(
|
||||
@SerializedName("accessStatus")
|
||||
var accessStatus: Int, // 0
|
||||
@SerializedName("dueRemark")
|
||||
var dueRemark: String,
|
||||
@SerializedName("vipDueDate")
|
||||
var vipDueDate: Long, // 0
|
||||
@SerializedName("vipStatus")
|
||||
var vipStatus: Int, // 0
|
||||
@SerializedName("vipStatusWarn")
|
||||
var vipStatusWarn: String,
|
||||
@SerializedName("vipType")
|
||||
var vipType: Int // 0
|
||||
)
|
||||
}
|
||||
|
||||
data class DislikeReason(
|
||||
@SerializedName("reason_id")
|
||||
var reasonId: Int, // 8
|
||||
@SerializedName("reason_name")
|
||||
var reasonName: String // 营销广告
|
||||
)
|
||||
|
||||
data class Relate(
|
||||
@SerializedName("ad_index")
|
||||
var adIndex: Int, // 2
|
||||
@SerializedName("aid")
|
||||
var aid: Int, // 38496110
|
||||
@SerializedName("card_index")
|
||||
var cardIndex: Int, // 3
|
||||
@SerializedName("cid")
|
||||
var cid: Int, // 67669037
|
||||
@SerializedName("client_ip")
|
||||
var clientIp: String, // 218.205.81.101
|
||||
@SerializedName("duration")
|
||||
var duration: Int, // 189
|
||||
@SerializedName("goto")
|
||||
var goto: String, // av
|
||||
@SerializedName("is_ad_loc")
|
||||
var isAdLoc: Boolean, // true
|
||||
@SerializedName("owner")
|
||||
var owner: Owner,
|
||||
@SerializedName("param")
|
||||
var `param`: String, // 38496110
|
||||
@SerializedName("pic")
|
||||
var pic: String, // http://i2.hdslb.com/bfs/archive/ca80fb7c554e083716feb910370b77caa5e124b3.jpg
|
||||
@SerializedName("request_id")
|
||||
var requestId: String, // 1550675871470q172a22a56a79q738
|
||||
@SerializedName("src_id")
|
||||
var srcId: Int, // 2334
|
||||
@SerializedName("stat")
|
||||
var stat: Stat,
|
||||
@SerializedName("title")
|
||||
var title: String, // 《流浪地球》发布创想特辑,从无到有呈现刘慈欣科幻想象
|
||||
@SerializedName("trackid")
|
||||
var trackid: String, // related_0.shylf-ai-recsys-87.1550675871470.909
|
||||
@SerializedName("uri")
|
||||
var uri: String // bilibili://video/38496110?player_width=1920&player_height=1080&player_rotate=0&trackid=related_0.shylf-ai-recsys-87.1550675871470.909
|
||||
) {
|
||||
data class Owner(
|
||||
@SerializedName("face")
|
||||
var face: String, // http://static.hdslb.com/images/member/noface.gif
|
||||
@SerializedName("mid")
|
||||
var mid: Long, // 334512441
|
||||
@SerializedName("name")
|
||||
var name: String // 达岸电影2018
|
||||
)
|
||||
|
||||
data class Stat(
|
||||
@SerializedName("aid")
|
||||
var aid: Int, // 38496110
|
||||
@SerializedName("coin")
|
||||
var coin: Int, // 130
|
||||
@SerializedName("danmaku")
|
||||
var danmaku: Int, // 152
|
||||
@SerializedName("dislike")
|
||||
var dislike: Int, // 0
|
||||
@SerializedName("favorite")
|
||||
var favorite: Int, // 194
|
||||
@SerializedName("his_rank")
|
||||
var hisRank: Int, // 0
|
||||
@SerializedName("like")
|
||||
var like: Int, // 341
|
||||
@SerializedName("now_rank")
|
||||
var nowRank: Int, // 0
|
||||
@SerializedName("reply")
|
||||
var reply: Int, // 264
|
||||
@SerializedName("share")
|
||||
var share: Int, // 278
|
||||
@SerializedName("view")
|
||||
var view: Int // 19397
|
||||
)
|
||||
}
|
||||
|
||||
data class Page(
|
||||
@SerializedName("cid")
|
||||
var cid: Int, // 77356986
|
||||
@SerializedName("dimension")
|
||||
var dimension: Dimension,
|
||||
@SerializedName("dm")
|
||||
var dm: Dm,
|
||||
@SerializedName("dmlink")
|
||||
var dmlink: String, // http://comment.bilibili.com/77356986.xml
|
||||
@SerializedName("duration")
|
||||
var duration: Int, // 444
|
||||
@SerializedName("from")
|
||||
var from: String, // vupload
|
||||
@SerializedName("metas")
|
||||
var metas: List<Meta>,
|
||||
@SerializedName("page")
|
||||
var page: Int, // 1
|
||||
@SerializedName("part")
|
||||
var part: String, // 2.20.2
|
||||
@SerializedName("vid")
|
||||
var vid: String,
|
||||
@SerializedName("weblink")
|
||||
var weblink: String
|
||||
) {
|
||||
data class Dm(
|
||||
@SerializedName("closed")
|
||||
var closed: Boolean, // false
|
||||
@SerializedName("count")
|
||||
var count: Int, // 19
|
||||
@SerializedName("mask")
|
||||
var mask: JsonElement, // {}
|
||||
@SerializedName("real_name")
|
||||
var realName: Boolean, // false
|
||||
@SerializedName("subtitles")
|
||||
var subtitles: JsonElement? // null
|
||||
)
|
||||
|
||||
data class Dimension(
|
||||
@SerializedName("height")
|
||||
var height: Int, // 720
|
||||
@SerializedName("rotate")
|
||||
var rotate: Int, // 0
|
||||
@SerializedName("width")
|
||||
var width: Int // 1280
|
||||
)
|
||||
|
||||
data class Meta(
|
||||
@SerializedName("format")
|
||||
var format: String,
|
||||
@SerializedName("quality")
|
||||
var quality: Int, // 48
|
||||
@SerializedName("size")
|
||||
var size: Int // 81074
|
||||
)
|
||||
}
|
||||
|
||||
data class Rights(
|
||||
@SerializedName("autoplay")
|
||||
var autoplay: Int, // 1
|
||||
@SerializedName("bp")
|
||||
var bp: Int, // 0
|
||||
@SerializedName("download")
|
||||
var download: Int, // 1
|
||||
@SerializedName("elec")
|
||||
var elec: Int, // 1
|
||||
@SerializedName("hd5")
|
||||
var hd5: Int, // 0
|
||||
@SerializedName("is_cooperation")
|
||||
var isCooperation: Int, // 0
|
||||
@SerializedName("movie")
|
||||
var movie: Int, // 0
|
||||
@SerializedName("no_reprint")
|
||||
var noReprint: Int, // 1
|
||||
@SerializedName("pay")
|
||||
var pay: Int, // 0
|
||||
@SerializedName("ugc_pay")
|
||||
var ugcPay: Int // 0
|
||||
)
|
||||
|
||||
data class Dimension(
|
||||
@SerializedName("height")
|
||||
var height: Int, // 720
|
||||
@SerializedName("rotate")
|
||||
var rotate: Int, // 0
|
||||
@SerializedName("width")
|
||||
var width: Int // 1280
|
||||
)
|
||||
|
||||
data class CmConfig(
|
||||
@SerializedName("ads_control")
|
||||
var adsControl: AdsControl
|
||||
) {
|
||||
data class AdsControl(
|
||||
@SerializedName("has_danmu")
|
||||
var hasDanmu: Int // 0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package com.hiczp.bilibili.api.danmaku
|
||||
|
||||
import com.hiczp.crc32crack.Crc32Cracker
|
||||
|
||||
data class Danmaku(
|
||||
/**
|
||||
* 弹幕 id
|
||||
*/
|
||||
val id: Long,
|
||||
|
||||
/**
|
||||
* TODO 下标 1, 不明属性
|
||||
*/
|
||||
val unknownAttribute1: String,
|
||||
|
||||
/**
|
||||
* 弹幕出现时间(播放器时间)(ms)
|
||||
*/
|
||||
val time: Long,
|
||||
|
||||
/**
|
||||
* 弹幕模式
|
||||
* (1从右至左滚动弹幕|6从左至右滚动弹幕|5顶端固定弹幕|4底端固定弹幕|7高级弹幕|8脚本弹幕)
|
||||
*/
|
||||
val mode: Int,
|
||||
|
||||
/**
|
||||
* 字号
|
||||
*/
|
||||
val fontSize: Int,
|
||||
|
||||
/**
|
||||
* 颜色
|
||||
*/
|
||||
val color: Int,
|
||||
|
||||
/**
|
||||
* 弹幕的发送时间(时间戳)(s)
|
||||
*/
|
||||
val timestamp: Long,
|
||||
|
||||
/**
|
||||
* TODO 下标 7, 不明属性
|
||||
*/
|
||||
val unknownAttribute7: String,
|
||||
|
||||
/**
|
||||
* 弹幕发送者的 hash(用户 id 的 CRC32 校验和)
|
||||
*/
|
||||
val user: String,
|
||||
|
||||
/**
|
||||
* 弹幕的内容
|
||||
* 注意, 不一定是一个自然语言字符串, 可能是以 [ 开头的具有语义的文本, 如下所示
|
||||
* [0,0,"1-1",4.5,"天下第一电击公主,贯穿天地的惊艳落雷!我炮傲娇永世长存!",0,0,0,0.99,500,0,1,"SimHei",true]
|
||||
* 这可能表示某种特殊的输出格式
|
||||
*/
|
||||
val content: String
|
||||
) {
|
||||
/**
|
||||
* 计算弹幕发送者 ID(可能有多个)
|
||||
* 第一次调用 Crc32Cracker 将花费大约 300ms 来生成反查表
|
||||
* hash 反查通常不超过 1ms
|
||||
*/
|
||||
fun calculatePossibleUserIds() = Crc32Cracker.crack(user)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.hiczp.bilibili.api.danmaku
|
||||
|
||||
import kotlinx.coroutines.Deferred
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
@Suppress("DeferredIsResult")
|
||||
interface DanmakuAPI {
|
||||
/**
|
||||
* 获取弹幕(视频或者番剧)
|
||||
*
|
||||
* @param aid 视频的唯一标识
|
||||
* @param oid 注意, 此处的 oid 是 cid
|
||||
*
|
||||
* @return 返回的内容是二进制数据, 由于数据量可能很大, 此处不做解析
|
||||
*/
|
||||
@GET("/x/v2/dm/list.so")
|
||||
fun list(
|
||||
@Query("aid") aid: Long,
|
||||
@Query("oid") oid: Long,
|
||||
@Query("plat") plat: Int? = 2,
|
||||
@Query("ps") pageSize: Int = 0,
|
||||
@Query("type") type: Int = 1
|
||||
): Deferred<ResponseBody>
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package com.hiczp.bilibili.api.danmaku
|
||||
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.hiczp.bilibili.api.bounded
|
||||
import com.hiczp.bilibili.api.readUInt
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import java.util.zip.GZIPInputStream
|
||||
import javax.xml.namespace.QName
|
||||
import javax.xml.stream.XMLInputFactory
|
||||
import javax.xml.stream.XMLStreamConstants
|
||||
|
||||
/**
|
||||
* 弹幕文件解析器.
|
||||
* 弹幕文件(list.so)有三个部分
|
||||
* 第一个部分为一个 Int, 表示第二部分的长度
|
||||
* 第二部分为一个 Json, 标识各个弹幕的等级(用于屏蔽设置)
|
||||
* 第三部分为一个 gzip 压缩过的 xml
|
||||
*
|
||||
* Web 端的弹幕是一个明文 xml, 与 APP 的接口是不一样的.
|
||||
*
|
||||
* json 部分形如 {"dmflags":[{"dmid":12551893546958848,"flag":10}],"rec_flag":1,"rec_text":"开启后,全站视频将按等级等优化弹幕","rec_switch":1}
|
||||
* xml 部分形如 <d p="12509048833835076,0,117373,5,25,16777215,1551001292,0,d2c5fc5">硬核劈柴</d>
|
||||
*
|
||||
* @see com.hiczp.bilibili.api.danmaku.DanmakuAPI.list
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
object DanmakuParser {
|
||||
/**
|
||||
* 解析弹幕文件
|
||||
*
|
||||
* @param inputStream 输入流, 可以指向任何位置
|
||||
*
|
||||
* @return 返回 flags map 与 弹幕序列. 注意, 原始的弹幕顺序是按发送时间来排的, 而非播放器时间.
|
||||
*/
|
||||
fun parse(inputStream: InputStream): Pair<Map<Long, Int>, Sequence<Danmaku>> {
|
||||
//Json 的长度
|
||||
val jsonLength = inputStream.readUInt()
|
||||
|
||||
//弹幕ID-Flag
|
||||
val danmakuFlags = HashMap<Long, Int>()
|
||||
//gson 会从 reader 中自行缓冲 1024 个字符, 这会导致额外的字符被消费. 因此要限制其读取数量
|
||||
//流式解析 Json
|
||||
with(JsonReader(inputStream.bounded(jsonLength).reader())) {
|
||||
beginObject()
|
||||
while (hasNext()) {
|
||||
when (nextName()) {
|
||||
"dmflags" -> {
|
||||
beginArray()
|
||||
while (hasNext()) {
|
||||
var danmakuId = 0L
|
||||
var flag = 0
|
||||
beginObject()
|
||||
while (hasNext()) {
|
||||
when (nextName()) {
|
||||
"dmid" -> danmakuId = nextLong()
|
||||
"flag" -> flag = nextInt()
|
||||
else -> skipValue()
|
||||
}
|
||||
}
|
||||
endObject()
|
||||
danmakuFlags[danmakuId] = flag
|
||||
}
|
||||
endArray()
|
||||
}
|
||||
else -> skipValue()
|
||||
}
|
||||
}
|
||||
endObject()
|
||||
}
|
||||
|
||||
//json 解析完毕后, 剩下的内容是一个 gzip 压缩过的 xml
|
||||
val reader = GZIPInputStream(inputStream).reader()
|
||||
//流式解析 xml
|
||||
val xmlEventReader = XMLInputFactory.newInstance().createXMLEventReader(reader)
|
||||
//lazy sequence
|
||||
val danmakus = sequence {
|
||||
var startD = false //之前解析到的 element 是否是 d
|
||||
var p: String? = null //之前解析到的 p 的值
|
||||
while (xmlEventReader.hasNext()) {
|
||||
val event = xmlEventReader.nextEvent()
|
||||
when (event.eventType) {
|
||||
XMLStreamConstants.START_ELEMENT -> {
|
||||
with(event.asStartElement()) {
|
||||
startD = name.localPart == "d"
|
||||
if (startD) {
|
||||
p = getAttributeByName(P).value
|
||||
}
|
||||
}
|
||||
}
|
||||
XMLStreamConstants.CHARACTERS -> {
|
||||
//如果前一个解析到的是 d 标签, 那么此处得到的一定是 d 标签的 body
|
||||
if (startD) {
|
||||
val danmaku = with(StringTokenizer(p, ",")) {
|
||||
Danmaku(
|
||||
nextToken().toLong(),
|
||||
nextToken(),
|
||||
nextToken().toLong(),
|
||||
nextToken().toInt(),
|
||||
nextToken().toInt(),
|
||||
nextToken().toInt(),
|
||||
nextToken().toLong(),
|
||||
nextToken(),
|
||||
nextToken(),
|
||||
event.asCharacters().data
|
||||
)
|
||||
}
|
||||
yield(danmaku)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return danmakuFlags to danmakus
|
||||
}
|
||||
|
||||
//常量, 用于加快速度
|
||||
private val P = QName("p")
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live
|
||||
|
||||
import com.hiczp.bilibili.api.live.model.*
|
||||
import com.hiczp.bilibili.api.retrofit.CommonResponse
|
||||
import com.hiczp.bilibili.api.retrofit.Header
|
||||
import kotlinx.coroutines.Deferred
|
||||
import retrofit2.http.*
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* 直播站 API
|
||||
*/
|
||||
@Suppress("DeferredIsResult")
|
||||
interface LiveAPI {
|
||||
/**
|
||||
* 获取一个房间的基本信息
|
||||
*
|
||||
* @param id 房间号或房间短号
|
||||
*/
|
||||
@GET("/room/v1/Room/mobileRoomInit")
|
||||
fun mobileRoomInit(@Query("id") id: Long): Deferred<MobileRoom>
|
||||
|
||||
/**
|
||||
* 进入房间时客户端将访问该接口
|
||||
* 访问该接口将在自己的账户中产生一条观看直播的历史记录
|
||||
*
|
||||
* @param roomId 房间号(没试过能不能用短号, 下同)
|
||||
*/
|
||||
@POST("/room/v1/Room/room_entry_action")
|
||||
@FormUrlEncoded
|
||||
fun roomEntryAction(
|
||||
@Field("room_id") roomId: Long,
|
||||
@Field("jumpFrom") jumpFrom: Int? = 0
|
||||
): Deferred<CommonResponse>
|
||||
|
||||
/**
|
||||
* 获取一个房间的详细信息
|
||||
*
|
||||
* @param id 房间号
|
||||
*/
|
||||
@GET("/room/v1/Room/get_info")
|
||||
fun getInfo(@Query("id") id: Long): Deferred<RoomInfo>
|
||||
|
||||
/**
|
||||
* 获取弹幕服务器
|
||||
*
|
||||
* @param roomId 房间号
|
||||
*/
|
||||
@GET("/room/v1/Danmu/getConf")
|
||||
fun getDanmakuConfig(
|
||||
@Query("room_id") roomId: Long
|
||||
): Deferred<DanmakuConfig>
|
||||
|
||||
/**
|
||||
* 获取该房间的主播的头像和等级一类的信息
|
||||
*
|
||||
* @param roomId 房间号
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/live_user/v1/UserInfo/get_anchor_in_room")
|
||||
fun getAnchorInRoom(@Query("roomid") roomId: Long): Deferred<AnchorInRoom>
|
||||
|
||||
/**
|
||||
* 获取自己在直播站的基本信息, 包括自己的直播间号, 银瓜子, 金瓜子数量等
|
||||
*/
|
||||
@GET("/mobile/getUser")
|
||||
fun getUser(): Deferred<User>
|
||||
|
||||
/**
|
||||
* 获取自己在当前直播间的信息, 包括自己的权限以及是否是管理员等
|
||||
*
|
||||
* @param roomId 房间号
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/live_user/v1/UserInfo/get_info_in_room")
|
||||
fun getUserInfoInRoom(@Query("roomid") roomId: Long): Deferred<UserInfoInRoom>
|
||||
|
||||
/**
|
||||
* 获取所有头衔
|
||||
*
|
||||
* @param scale 屏幕尺寸
|
||||
*/
|
||||
@GET("/appUser/getTitle")
|
||||
fun getTitle(@Query("scale") scale: String = "xxhdpi"): Deferred<Title>
|
||||
|
||||
/**
|
||||
* 查询是否关注了当前主播
|
||||
*
|
||||
* @param follow 所查询的主播的用户 ID
|
||||
*/
|
||||
@POST("/relation/v1/Feed/isFollowed")
|
||||
@FormUrlEncoded
|
||||
fun isFollowed(@Field("follow") follow: Long): Deferred<Follow>
|
||||
|
||||
/**
|
||||
* 进入直播间的时候, 客户端会访问该接口来动态获取上方的 Tab. 包括 互动, 主播, 贡献榜 等
|
||||
*
|
||||
* @param roomId 房间号
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/room/v2/Room/mobileTab")
|
||||
fun mobileTab(@Query("roomid") roomId: Long): Deferred<MobileTab>
|
||||
|
||||
/**
|
||||
* 获取房间的历史弹幕(10条)
|
||||
*
|
||||
* @param roomId 房间号
|
||||
*/
|
||||
@GET("/AppRoom/msg")
|
||||
fun roomMessage(@Query("room_id") roomId: Long): Deferred<RoomMessage>
|
||||
|
||||
/**
|
||||
* 获取进房后右下角显示的那些东西, 通常是一些活动, 它们导向 H5 页面
|
||||
*
|
||||
* @param roomId 房间号
|
||||
* @param roomUserId 主播的用户 ID
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/activity/v1/Common/mobileRoomBanner")
|
||||
fun mobileRoomBanner(
|
||||
@Query("area_v2_id") areaV2Id: Int,
|
||||
@Query("area_v2_parent_id") areaV2ParentId: Int,
|
||||
@Query("roomid") roomId: Long,
|
||||
@Query("ruid") roomUserId: Long
|
||||
): Deferred<MobileRoomBanner>
|
||||
|
||||
/**
|
||||
* 获取各种礼物的基本信息, 包括贴图地址, 描述, 价格等
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/gift/v3/live/gift_config")
|
||||
fun getGiftConfig(
|
||||
@Query("area_v2_id") areaV2Id: Int,
|
||||
@Query("area_v2_parent_id") areaV2ParentId: Int,
|
||||
@Query("roomid") roomId: Long
|
||||
): Deferred<GiftConfig>
|
||||
|
||||
/**
|
||||
* 获取访问 小时总榜 的地址(H5)
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/rankdb/v1/Common/roomRank")
|
||||
fun roomRank(
|
||||
@Query("area_v2_id") areaV2Id: Int,
|
||||
@Query("area_v2_parent_id") areaV2ParentId: Int,
|
||||
@Query("roomid") roomId: Long,
|
||||
@Query("ruid") roomUserId: Long
|
||||
): Deferred<RoomRank>
|
||||
|
||||
/**
|
||||
* 直播站首页
|
||||
* 首页 -> 直播
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/xlive/app-interface/v2/index/getAllList")
|
||||
fun homePage(
|
||||
@Query("quality") quality: Int = 0,
|
||||
@Query("rec_page") recPage: Int = 2,
|
||||
@Query("relation_page") relationPage: Int = 2,
|
||||
@Query("scale") scale: String = "xxhdpi"
|
||||
): Deferred<HomePage>
|
||||
|
||||
/**
|
||||
* 获取某个直播分类下的全部子分类
|
||||
*/
|
||||
@GET("/room/v1/Area/getList")
|
||||
fun getAreaList(@Query("parent_id") parentId: Int): Deferred<AreaList>
|
||||
|
||||
/**
|
||||
* 根据某种维度来获取房间列表
|
||||
* area, parent, category 为 0 表示不筛选这些维度
|
||||
* sortType 为 null 表示不排序
|
||||
*
|
||||
* 首页 -> 直播 -> 查看更多/全部直播
|
||||
*
|
||||
* @param page 分页, 从 1 开始
|
||||
* @param sortType 排序维度, 已知的有 online(最热直播), live_time(最新开播)
|
||||
*/
|
||||
@GET("/room/v3/Area/getRoomList")
|
||||
fun getRoomList(
|
||||
@Query("area_id") areaId: Int = 0,
|
||||
@Query("parent_area_id") parentAreaId: Int = 0,
|
||||
@Query("cate_id") categoryId: Int = 0,
|
||||
@Query("page") page: Int = 1,
|
||||
@Query("page_size") pageSize: Int = 30,
|
||||
@Query("sort_type") sortType: String? = null
|
||||
): Deferred<RoomList>
|
||||
|
||||
/**
|
||||
* 发送弹幕(直播)
|
||||
*
|
||||
* @param bubble 气泡, 不明确含义
|
||||
* @param cid 房间号
|
||||
* @param mid 发送者的用户 ID
|
||||
* @param message 弹幕内容
|
||||
* @param random 随机数, 不包括符号位有 9 位 或者 10 位
|
||||
* @param mode 弹幕模式, 可能与视频弹幕的模式含义相同, 可能需要特殊身份才能使用额外模式, 下同
|
||||
* @param pool 弹幕池
|
||||
* @param type 固定为 "json"
|
||||
* @param color 弹幕颜色
|
||||
* @param fontSize 弹幕字号
|
||||
* @param playTime 不明确
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@POST("/api/sendmsg")
|
||||
@FormUrlEncoded
|
||||
@Headers(Header.FORCE_QUERY)
|
||||
fun sendMessage(
|
||||
@Field("bubble") bubble: Int = 0,
|
||||
@Field("cid") cid: Long,
|
||||
@Field("mid") mid: Long,
|
||||
@Field("msg") message: String,
|
||||
@Field("rnd") random: Int = (if (Random.nextBoolean()) 1 else -1) * Random.nextInt(100000000, Int.MAX_VALUE),
|
||||
@Field("mode") mode: Int = 1,
|
||||
@Field("pool") pool: Int = 0,
|
||||
@Field("type") type: String = "json",
|
||||
@Field("color") color: Int = 16777215,
|
||||
@Field("fontsize") fontSize: Int = 25,
|
||||
@Field("playTime") playTime: Float = 0.0f
|
||||
): Deferred<CommonResponse>
|
||||
|
||||
/**
|
||||
* 用于确认客户端在看直播的心跳包(与弹幕推送无关)
|
||||
* 每五分钟发送一次
|
||||
*/
|
||||
@POST("/mobile/userOnlineHeart")
|
||||
@FormUrlEncoded
|
||||
@Headers(Header.FORCE_QUERY)
|
||||
fun userOnlineHeart(
|
||||
@Field("room_id") roomId: Long,
|
||||
@Field("scale") scale: String = "xxhdpi"
|
||||
): Deferred<CommonResponse>
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class AnchorInRoom(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // success
|
||||
@SerializedName("msg")
|
||||
var msg: String // success
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("info")
|
||||
var info: Info,
|
||||
@SerializedName("level")
|
||||
var level: Level,
|
||||
@SerializedName("san")
|
||||
var san: Int // 12
|
||||
) {
|
||||
data class Info(
|
||||
@SerializedName("face")
|
||||
var face: String, // https://i0.hdslb.com/bfs/face/0434dccc0ec4de223e8ca374dea06a6e1e8eb471.jpg
|
||||
@SerializedName("gender")
|
||||
var gender: Int, // 0
|
||||
@SerializedName("identification")
|
||||
var identification: Int?, // 1
|
||||
@SerializedName("mobile_verify")
|
||||
var mobileVerify: Int, // 0
|
||||
@SerializedName("official_verify")
|
||||
var officialVerify: OfficialVerify,
|
||||
@SerializedName("platform_user_level")
|
||||
var platformUserLevel: Int, // 5
|
||||
@SerializedName("rank")
|
||||
var rank: String, // 10000
|
||||
@SerializedName("uid")
|
||||
var uid: Long, // 2866663
|
||||
@SerializedName("uname")
|
||||
var uname: String, // hyx5020
|
||||
@SerializedName("vip_type")
|
||||
var vipType: Int // 2
|
||||
) {
|
||||
data class OfficialVerify(
|
||||
@SerializedName("desc")
|
||||
var desc: String,
|
||||
@SerializedName("role")
|
||||
var role: Int, // 0
|
||||
@SerializedName("type")
|
||||
var type: Int // -1
|
||||
)
|
||||
}
|
||||
|
||||
data class Level(
|
||||
@SerializedName("anchor_score")
|
||||
var anchorScore: Int, // 36685
|
||||
@SerializedName("color")
|
||||
var color: Int, // 5805790
|
||||
@SerializedName("cost")
|
||||
var cost: Int, // 29431298
|
||||
@SerializedName("master_level")
|
||||
var masterLevel: MasterLevel,
|
||||
@SerializedName("rcost")
|
||||
var rcost: Long, // 3668592
|
||||
@SerializedName("svip")
|
||||
var svip: Int, // 0
|
||||
@SerializedName("svip_time")
|
||||
var svipTime: String, // 2019-02-09 11:03:54
|
||||
@SerializedName("uid")
|
||||
var uid: Int, // 2866663
|
||||
@SerializedName("update_time")
|
||||
var updateTime: String, // 2019-03-12 23:00:42
|
||||
@SerializedName("user_level")
|
||||
var userLevel: Int, // 22
|
||||
@SerializedName("user_score")
|
||||
var userScore: String, // 0
|
||||
@SerializedName("vip")
|
||||
var vip: Int, // 0
|
||||
@SerializedName("vip_time")
|
||||
var vipTime: String // 2019-02-09 11:03:54
|
||||
) {
|
||||
data class MasterLevel(
|
||||
@SerializedName("anchor_score")
|
||||
var anchorScore: Int, // 36685
|
||||
@SerializedName("color")
|
||||
var color: Int, // 5805790
|
||||
@SerializedName("current")
|
||||
var current: List<Int>,
|
||||
@SerializedName("level")
|
||||
var level: Int, // 11
|
||||
@SerializedName("master_level_color")
|
||||
var masterLevelColor: Int, // 5805790
|
||||
@SerializedName("next")
|
||||
var next: List<Int>,
|
||||
@SerializedName("sort")
|
||||
var sort: String, // >10000
|
||||
@SerializedName("upgrade_score")
|
||||
var upgradeScore: Int // 2925
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class AreaList(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: List<Data>,
|
||||
@SerializedName("message")
|
||||
var message: String, // success
|
||||
@SerializedName("msg")
|
||||
var msg: String // success
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("act_id")
|
||||
var actId: String, // 0
|
||||
@SerializedName("area_type")
|
||||
var areaType: Int, // 0
|
||||
@SerializedName("hot_status")
|
||||
var hotStatus: Int, // 0
|
||||
@SerializedName("id")
|
||||
var id: String, // 34
|
||||
@SerializedName("lock_status")
|
||||
var lockStatus: String, // 0
|
||||
@SerializedName("name")
|
||||
var name: String, // 音乐台
|
||||
@SerializedName("old_area_id")
|
||||
var oldAreaId: String, // 7
|
||||
@SerializedName("parent_id")
|
||||
var parentId: String, // 1
|
||||
@SerializedName("parent_name")
|
||||
var parentName: String, // 娱乐
|
||||
@SerializedName("pic")
|
||||
var pic: String, // https://i0.hdslb.com/bfs/vc/8537694f4fe68ab0798dd5d493d3ca5deb908088.png
|
||||
@SerializedName("pk_status")
|
||||
var pkStatus: String // 0
|
||||
)
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class DanmakuConfig(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // ok
|
||||
@SerializedName("msg")
|
||||
var msg: String // ok
|
||||
) {
|
||||
data class Data(
|
||||
/**
|
||||
* 推荐服务器
|
||||
*/
|
||||
@SerializedName("host")
|
||||
var host: String, // broadcastlv.chat.bilibili.com
|
||||
/**
|
||||
* 服务器列表
|
||||
*/
|
||||
@SerializedName("host_server_list")
|
||||
var hostServerList: List<HostServer>,
|
||||
@SerializedName("max_delay")
|
||||
var maxDelay: Int, // 5000
|
||||
/**
|
||||
* 这里的端口是非 TSL 的 websocket 端口, 需要到 host_server_list 中寻找推荐服务器对应的 websocket TSL 端口(通常是 443)
|
||||
*/
|
||||
@SerializedName("port")
|
||||
var port: Int, // 2243
|
||||
@SerializedName("refresh_rate")
|
||||
var refreshRate: Int, // 100
|
||||
@SerializedName("refresh_row_factor")
|
||||
var refreshRowFactor: Double, // 0.125
|
||||
/**
|
||||
* 如果 DNS 失效可以使用该列表中的 IP
|
||||
*/
|
||||
@SerializedName("server_list")
|
||||
var serverList: List<Server>
|
||||
) {
|
||||
data class Server(
|
||||
@SerializedName("host")
|
||||
var host: String, // broadcastlv.chat.bilibili.com
|
||||
@SerializedName("port")
|
||||
var port: Int // 80
|
||||
)
|
||||
|
||||
data class HostServer(
|
||||
@SerializedName("host")
|
||||
var host: String, // broadcastlv.chat.bilibili.com
|
||||
@SerializedName("port")
|
||||
var port: Int, // 2243
|
||||
/**
|
||||
* websocket 端口
|
||||
*/
|
||||
@SerializedName("ws_port")
|
||||
var wsPort: Int, // 2244
|
||||
/**
|
||||
* websocket TSL 端口
|
||||
*/
|
||||
@SerializedName("wss_port")
|
||||
var wssPort: Int // 443
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Follow(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // success
|
||||
@SerializedName("msg")
|
||||
var msg: String // success
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("follow")
|
||||
var follow: Int // 0
|
||||
)
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class GiftConfig(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: List<Data>,
|
||||
@SerializedName("message")
|
||||
var message: String, // success
|
||||
@SerializedName("msg")
|
||||
var msg: String // success
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("animation_frame_num")
|
||||
var animationFrameNum: Int, // 12
|
||||
@SerializedName("bag_gift")
|
||||
var bagGift: Int, // 0
|
||||
@SerializedName("broadcast")
|
||||
var broadcast: Int, // 0
|
||||
@SerializedName("bullet_head")
|
||||
var bulletHead: String,
|
||||
@SerializedName("bullet_tail")
|
||||
var bulletTail: String,
|
||||
@SerializedName("coin_type")
|
||||
var coinType: String, // gold
|
||||
@SerializedName("corner_background")
|
||||
var cornerBackground: String,
|
||||
@SerializedName("corner_mark")
|
||||
var cornerMark: String, // 祈愿
|
||||
@SerializedName("count_map")
|
||||
var countMap: List<CountMap>,
|
||||
@SerializedName("desc")
|
||||
var desc: String, // 祥瑞御免,家宅平安。
|
||||
@SerializedName("draw")
|
||||
var draw: Int, // 0
|
||||
@SerializedName("effect")
|
||||
var effect: Int, // 0
|
||||
@SerializedName("frame_animation")
|
||||
var frameAnimation: String, // https://i0.hdslb.com/bfs/live/4e19f947d0bd346d38fe4838d7ab431d003f9d7f.png
|
||||
@SerializedName("full_sc_horizontal")
|
||||
var fullScHorizontal: String,
|
||||
@SerializedName("full_sc_horizontal_svga")
|
||||
var fullScHorizontalSvga: String,
|
||||
@SerializedName("full_sc_vertical")
|
||||
var fullScVertical: String,
|
||||
@SerializedName("full_sc_vertical_svga")
|
||||
var fullScVerticalSvga: String,
|
||||
@SerializedName("full_sc_web")
|
||||
var fullScWeb: String,
|
||||
@SerializedName("gif")
|
||||
var gif: String, // https://i0.hdslb.com/bfs/live/a90ff57209661b309f121116682bdee1f3937a98.gif
|
||||
@SerializedName("gift_type")
|
||||
var giftType: Int, // 1
|
||||
@SerializedName("id")
|
||||
var id: Int, // 30136
|
||||
@SerializedName("img_basic")
|
||||
var imgBasic: String, // https://s1.hdslb.com/bfs/live/a7c750335ed42ae4dfeb70570804326d3ecaf61c.png
|
||||
@SerializedName("img_dynamic")
|
||||
var imgDynamic: String, // https://i0.hdslb.com/bfs/live/f6f314227e7ed8065c4f7266ada913289971f806.png
|
||||
@SerializedName("limit_interval")
|
||||
var limitInterval: Int, // 0
|
||||
@SerializedName("name")
|
||||
var name: String, // 御守
|
||||
@SerializedName("price")
|
||||
var price: Int, // 1000
|
||||
@SerializedName("privilege_required")
|
||||
var privilegeRequired: Int, // 0
|
||||
@SerializedName("rights")
|
||||
var rights: String, // 当前主播亲密度+10 经验值+1000
|
||||
@SerializedName("rule")
|
||||
var rule: String, // 赠送御守即可参与“敬祈照准”活动。
|
||||
@SerializedName("stay_time")
|
||||
var stayTime: Int, // 3
|
||||
@SerializedName("type")
|
||||
var type: Int, // 0
|
||||
@SerializedName("webp")
|
||||
var webp: String // https://i0.hdslb.com/bfs/live/6d8a7907cf89556d074b8ce220e7dd56ccaf5160.webp
|
||||
) {
|
||||
data class CountMap(
|
||||
@SerializedName("num")
|
||||
var num: Int, // 2333
|
||||
@SerializedName("text")
|
||||
var text: String
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,393 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.model
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class HomePage(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("activity_card")
|
||||
var activityCard: List<JsonElement>,
|
||||
@SerializedName("area_entrance")
|
||||
var areaEntrance: List<AreaEntrance>,
|
||||
@SerializedName("area_entrance_v2")
|
||||
var areaEntranceV2: List<JsonElement>,
|
||||
@SerializedName("banner")
|
||||
var banner: List<Banner>,
|
||||
@SerializedName("hour_rank")
|
||||
var hourRank: List<HourRank>,
|
||||
@SerializedName("interval")
|
||||
var interval: Int, // 10
|
||||
@SerializedName("is_sky_horse_gray")
|
||||
var isSkyHorseGray: Int, // 0
|
||||
@SerializedName("my_idol")
|
||||
var myIdol: List<MyIdol>,
|
||||
@SerializedName("my_tag")
|
||||
var myTag: List<MyTag>,
|
||||
@SerializedName("room_list")
|
||||
var roomList: List<Room>,
|
||||
@SerializedName("sea_patrol")
|
||||
var seaPatrol: List<JsonElement>
|
||||
) {
|
||||
data class MyTag(
|
||||
@SerializedName("extra_info")
|
||||
var extraInfo: ExtraInfo,
|
||||
@SerializedName("list")
|
||||
var list: List<X>,
|
||||
@SerializedName("module_info")
|
||||
var moduleInfo: ModuleInfo
|
||||
) {
|
||||
data class X(
|
||||
@SerializedName("area_v2_id")
|
||||
var areaV2Id: Int, // 0
|
||||
@SerializedName("area_v2_name")
|
||||
var areaV2Name: String, // 全部标签
|
||||
@SerializedName("area_v2_parent_id")
|
||||
var areaV2ParentId: Int, // 0
|
||||
@SerializedName("area_v2_parent_name")
|
||||
var areaV2ParentName: String,
|
||||
@SerializedName("is_advice")
|
||||
var isAdvice: Int, // 1
|
||||
@SerializedName("link")
|
||||
var link: String, // http://live.bilibili.com/app/mytag/
|
||||
@SerializedName("pic")
|
||||
var pic: String // http://i0.hdslb.com/bfs/vc/ff03528785fc8c91491d79e440398484811d6d87.png
|
||||
)
|
||||
|
||||
data class ModuleInfo(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 0
|
||||
@SerializedName("id")
|
||||
var id: Int, // 28
|
||||
@SerializedName("link")
|
||||
var link: String,
|
||||
@SerializedName("pic")
|
||||
var pic: String,
|
||||
@SerializedName("sort")
|
||||
var sort: Int, // 1
|
||||
@SerializedName("title")
|
||||
var title: String, // 常用标签
|
||||
@SerializedName("type")
|
||||
var type: Int // 12
|
||||
)
|
||||
|
||||
data class ExtraInfo(
|
||||
@SerializedName("is_gray")
|
||||
var isGray: Int, // 0
|
||||
@SerializedName("offline")
|
||||
var offline: List<JsonElement>
|
||||
)
|
||||
}
|
||||
|
||||
data class Room(
|
||||
@SerializedName("list")
|
||||
var list: List<X>,
|
||||
@SerializedName("module_info")
|
||||
var moduleInfo: ModuleInfo
|
||||
) {
|
||||
data class X(
|
||||
@SerializedName("accept_quality")
|
||||
var acceptQuality: List<Int>,
|
||||
@SerializedName("area_v2_id")
|
||||
var areaV2Id: Int, // 96
|
||||
@SerializedName("area_v2_name")
|
||||
var areaV2Name: String, // 其他绘画
|
||||
@SerializedName("area_v2_parent_id")
|
||||
var areaV2ParentId: Int, // 4
|
||||
@SerializedName("area_v2_parent_name")
|
||||
var areaV2ParentName: String, // 绘画
|
||||
@SerializedName("broadcast_type")
|
||||
var broadcastType: Int, // 0
|
||||
@SerializedName("click_callback")
|
||||
var clickCallback: String,
|
||||
@SerializedName("cover")
|
||||
var cover: String, // http://i0.hdslb.com/bfs/live/room_cover/6ef5b522bdf4de9fe2535b6031ae9b32c405cea0.jpg
|
||||
@SerializedName("current_quality")
|
||||
var currentQuality: Int, // 4
|
||||
@SerializedName("face")
|
||||
var face: String, // http://i0.hdslb.com/bfs/face/c165088330c1bd7f671b427b610379603aa002ae.jpg
|
||||
@SerializedName("group_id")
|
||||
var groupId: Int, // 0
|
||||
@SerializedName("link")
|
||||
var link: String,
|
||||
@SerializedName("online")
|
||||
var online: Int, // 5584
|
||||
@SerializedName("pendent_ru")
|
||||
var pendentRu: String,
|
||||
@SerializedName("pendent_ru_color")
|
||||
var pendentRuColor: String,
|
||||
@SerializedName("pendent_ru_pic")
|
||||
var pendentRuPic: String,
|
||||
@SerializedName("pk_id")
|
||||
var pkId: Int, // 0
|
||||
@SerializedName("play_url")
|
||||
var playUrl: String, // http://ws.live-play.acgvideo.com/live-ws/403834/live_397298321_43558493.flv?wsSecret=9dc8725a77c8ef5c68545e436f53b917&wsTime=1552470101&trid=9179fec58e79438aab34a9bbe5087e33&sig=no
|
||||
@SerializedName("play_url_h265")
|
||||
var playUrlH265: String,
|
||||
@SerializedName("rec_type")
|
||||
var recType: Int, // 0
|
||||
@SerializedName("roomid")
|
||||
var roomid: Long, // 21218600
|
||||
@SerializedName("session_id")
|
||||
var sessionId: String, // 456BB3BF-16D6-4BD3-9B4E-4570C274CEE5
|
||||
@SerializedName("show_callback")
|
||||
var showCallback: String,
|
||||
@SerializedName("title")
|
||||
var title: String, // 学员答疑13:00~21:00
|
||||
@SerializedName("uname")
|
||||
var uname: String // 轻微课魔鬼绘画特训班
|
||||
)
|
||||
|
||||
data class ModuleInfo(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 0
|
||||
@SerializedName("id")
|
||||
var id: Int, // 8
|
||||
@SerializedName("link")
|
||||
var link: String, // http://live.bilibili.com/app/area?parent_area_id=4&parent_area_name=绘画&area_id=&area_name=
|
||||
@SerializedName("pic")
|
||||
var pic: String, // http://i0.hdslb.com/bfs/live/7c54d7cc64e022845fccd63221de069b71eb6f67.png
|
||||
@SerializedName("sort")
|
||||
var sort: Int, // 25
|
||||
@SerializedName("title")
|
||||
var title: String, // 绘画
|
||||
@SerializedName("type")
|
||||
var type: Int // 9
|
||||
)
|
||||
}
|
||||
|
||||
data class AreaEntrance(
|
||||
@SerializedName("list")
|
||||
var list: List<X>,
|
||||
@SerializedName("module_info")
|
||||
var moduleInfo: ModuleInfo
|
||||
) {
|
||||
data class ModuleInfo(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 0
|
||||
@SerializedName("id")
|
||||
var id: Int, // 2
|
||||
@SerializedName("link")
|
||||
var link: String,
|
||||
@SerializedName("pic")
|
||||
var pic: String,
|
||||
@SerializedName("sort")
|
||||
var sort: Int, // 2
|
||||
@SerializedName("title")
|
||||
var title: String, // 分区入口
|
||||
@SerializedName("type")
|
||||
var type: Int // 2
|
||||
)
|
||||
|
||||
data class X(
|
||||
@SerializedName("content")
|
||||
var content: String,
|
||||
@SerializedName("id")
|
||||
var id: Int, // 45
|
||||
@SerializedName("link")
|
||||
var link: String, // http://live.bilibili.com/app/area?parent_area_id=1&parent_area_name=娱乐&area_id=199&area_name=虚拟主播
|
||||
@SerializedName("pic")
|
||||
var pic: String, // http://i0.hdslb.com/bfs/vc/7725a45469b776ee91f2d42afca1e5711f84ac51.png
|
||||
@SerializedName("title")
|
||||
var title: String // 虚拟主播
|
||||
)
|
||||
}
|
||||
|
||||
data class Banner(
|
||||
@SerializedName("list")
|
||||
var list: List<X>,
|
||||
@SerializedName("module_info")
|
||||
var moduleInfo: ModuleInfo
|
||||
) {
|
||||
data class X(
|
||||
@SerializedName("content")
|
||||
var content: String,
|
||||
@SerializedName("id")
|
||||
var id: Int, // 1117
|
||||
@SerializedName("link")
|
||||
var link: String, // https://www.bilibili.com/blackboard/live/activity-flower-girl2-h5.html
|
||||
@SerializedName("pic")
|
||||
var pic: String, // http://i0.hdslb.com/bfs/vc/523a719b51a647eeb969956865a20781d7e6d994.jpg
|
||||
@SerializedName("title")
|
||||
var title: String // 花之初少女
|
||||
)
|
||||
|
||||
data class ModuleInfo(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 0
|
||||
@SerializedName("id")
|
||||
var id: Int, // 1
|
||||
@SerializedName("link")
|
||||
var link: String,
|
||||
@SerializedName("pic")
|
||||
var pic: String,
|
||||
@SerializedName("sort")
|
||||
var sort: Int, // 0
|
||||
@SerializedName("title")
|
||||
var title: String, // banner位
|
||||
@SerializedName("type")
|
||||
var type: Int // 1
|
||||
)
|
||||
}
|
||||
|
||||
data class MyIdol(
|
||||
@SerializedName("extra_info")
|
||||
var extraInfo: ExtraInfo,
|
||||
@SerializedName("list")
|
||||
var list: List<X>,
|
||||
@SerializedName("module_info")
|
||||
var moduleInfo: ModuleInfo
|
||||
) {
|
||||
data class ExtraInfo(
|
||||
@SerializedName("card_type")
|
||||
var cardType: Int, // 1
|
||||
@SerializedName("relation_page")
|
||||
var relationPage: Int, // 1
|
||||
@SerializedName("tags_desc")
|
||||
var tagsDesc: String,
|
||||
@SerializedName("time_desc")
|
||||
var timeDesc: String,
|
||||
@SerializedName("total_count")
|
||||
var totalCount: Int, // 1
|
||||
@SerializedName("uname_desc")
|
||||
var unameDesc: String
|
||||
)
|
||||
|
||||
data class ModuleInfo(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 0
|
||||
@SerializedName("id")
|
||||
var id: Int, // 13
|
||||
@SerializedName("link")
|
||||
var link: String, // http://live.bilibili.com/app/myfollow/
|
||||
@SerializedName("pic")
|
||||
var pic: String, // http://i0.hdslb.com/bfs/live/484abcd8940ee43ec8b4409cbfe0c1e52f09a338.png
|
||||
@SerializedName("sort")
|
||||
var sort: Int, // 4
|
||||
@SerializedName("title")
|
||||
var title: String, // 我的关注
|
||||
@SerializedName("type")
|
||||
var type: Int // 8
|
||||
)
|
||||
|
||||
data class X(
|
||||
@SerializedName("accept_quality")
|
||||
var acceptQuality: List<Int>,
|
||||
@SerializedName("area")
|
||||
var area: Int, // 7
|
||||
@SerializedName("area_name")
|
||||
var areaName: String, // 放映厅
|
||||
@SerializedName("area_v2_id")
|
||||
var areaV2Id: Int, // 34
|
||||
@SerializedName("area_v2_name")
|
||||
var areaV2Name: String, // 音乐台
|
||||
@SerializedName("area_v2_parent_id")
|
||||
var areaV2ParentId: Int, // 1
|
||||
@SerializedName("area_v2_parent_name")
|
||||
var areaV2ParentName: String, // 娱乐
|
||||
@SerializedName("broadcast_type")
|
||||
var broadcastType: Int, // 0
|
||||
@SerializedName("cover")
|
||||
var cover: String, // http://i0.hdslb.com/bfs/live/6029764557e3cbe91475faae26e6e244de8c1d3c.jpg
|
||||
@SerializedName("current_quality")
|
||||
var currentQuality: Int, // 4
|
||||
@SerializedName("face")
|
||||
var face: String, // http://i0.hdslb.com/bfs/face/5d35da6e93fbfb1a77ad6d1f1004b08413913f9a.jpg
|
||||
@SerializedName("link")
|
||||
var link: String, // http://live.bilibili.com/23058?broadcast_type=0
|
||||
@SerializedName("live_tag_name")
|
||||
var liveTagName: String, // 音乐台
|
||||
@SerializedName("live_time")
|
||||
var liveTime: Int, // 1552406400
|
||||
@SerializedName("official_verify")
|
||||
var officialVerify: Int, // 1
|
||||
@SerializedName("online")
|
||||
var online: Int, // 9961
|
||||
@SerializedName("pendent_ru")
|
||||
var pendentRu: String,
|
||||
@SerializedName("pendent_ru_color")
|
||||
var pendentRuColor: String,
|
||||
@SerializedName("pendent_ru_pic")
|
||||
var pendentRuPic: String,
|
||||
@SerializedName("pk_id")
|
||||
var pkId: Int, // 0
|
||||
@SerializedName("play_url")
|
||||
var playUrl: String, // http://ws.live-play.acgvideo.com/live-ws/637609/live_11153765_9369560.flv?wsSecret=49e118106b827b5008e10b0c74fa1a5a&wsTime=1552470101&trid=fe3b3a0b017a439c86d792ab5dd6fcd5&sig=no
|
||||
@SerializedName("play_url_h265")
|
||||
var playUrlH265: String,
|
||||
@SerializedName("roomid")
|
||||
var roomid: Long, // 23058
|
||||
@SerializedName("special_attention")
|
||||
var specialAttention: Int, // 0
|
||||
@SerializedName("title")
|
||||
var title: String, // 哔哩哔哩音悦台
|
||||
@SerializedName("uid")
|
||||
var uid: Long, // 11153765
|
||||
@SerializedName("uname")
|
||||
var uname: String // 3号直播间
|
||||
)
|
||||
}
|
||||
|
||||
data class HourRank(
|
||||
@SerializedName("extra_info")
|
||||
var extraInfo: ExtraInfo,
|
||||
@SerializedName("list")
|
||||
var list: List<X>,
|
||||
@SerializedName("module_info")
|
||||
var moduleInfo: ModuleInfo
|
||||
) {
|
||||
data class ModuleInfo(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 0
|
||||
@SerializedName("id")
|
||||
var id: Int, // 4
|
||||
@SerializedName("link")
|
||||
var link: String, // https://live.bilibili.com/p/html/live-app-rank/index.html?is_live_webview=1&nav=hour
|
||||
@SerializedName("pic")
|
||||
var pic: String, // http://i0.hdslb.com/bfs/live/39cd413f6bc72fb9da8c10ff2686b537477294ab.png
|
||||
@SerializedName("sort")
|
||||
var sort: Int, // 11
|
||||
@SerializedName("title")
|
||||
var title: String, // 小时榜
|
||||
@SerializedName("type")
|
||||
var type: Int // 5
|
||||
)
|
||||
|
||||
data class X(
|
||||
@SerializedName("area_v2_id")
|
||||
var areaV2Id: Int, // 145
|
||||
@SerializedName("area_v2_name")
|
||||
var areaV2Name: String, // 视频聊天
|
||||
@SerializedName("area_v2_parent_id")
|
||||
var areaV2ParentId: Int, // 1
|
||||
@SerializedName("area_v2_parent_name")
|
||||
var areaV2ParentName: String, // 娱乐
|
||||
@SerializedName("face")
|
||||
var face: String, // http://i2.hdslb.com/bfs/face/cdc9866d09ed82e6fae610f5ba4b8706db509802.jpg
|
||||
@SerializedName("live_status")
|
||||
var liveStatus: Int, // 1
|
||||
@SerializedName("rank")
|
||||
var rank: Int, // 3
|
||||
@SerializedName("roomid")
|
||||
var roomid: Long, // 274926
|
||||
@SerializedName("uid")
|
||||
var uid: Long, // 24601383
|
||||
@SerializedName("uname")
|
||||
var uname: String // 蛋黄姬GAT-X105
|
||||
)
|
||||
|
||||
data class ExtraInfo(
|
||||
@SerializedName("sub_title")
|
||||
var subTitle: String // 15:00-16:00 总榜排名
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class MobileRoom(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // ok
|
||||
@SerializedName("msg")
|
||||
var msg: String // ok
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("encrypted")
|
||||
var encrypted: Boolean, // false
|
||||
@SerializedName("hidden_till")
|
||||
var hiddenTill: Int, // 0
|
||||
@SerializedName("is_hidden")
|
||||
var isHidden: Boolean, // false
|
||||
@SerializedName("is_locked")
|
||||
var isLocked: Boolean, // false
|
||||
@SerializedName("is_portrait")
|
||||
var isPortrait: Boolean, // false
|
||||
@SerializedName("is_sp")
|
||||
var isSp: Int, // 0
|
||||
@SerializedName("live_status")
|
||||
var liveStatus: Int, // 0
|
||||
@SerializedName("live_time")
|
||||
var liveTime: Long, // -62170012800
|
||||
@SerializedName("lock_till")
|
||||
var lockTill: Int, // 0
|
||||
@SerializedName("need_p2p")
|
||||
var needP2p: Int, // 0
|
||||
@SerializedName("pwd_verified")
|
||||
var pwdVerified: Boolean, // true
|
||||
/**
|
||||
* 实际房间号
|
||||
*/
|
||||
@SerializedName("room_id")
|
||||
var roomId: Long, // 1110317
|
||||
/**
|
||||
* 如果房间有短号则 roomShield 为 1
|
||||
*/
|
||||
@SerializedName("room_shield")
|
||||
var roomShield: Int, // 0
|
||||
/**
|
||||
* 短房间号
|
||||
*/
|
||||
@SerializedName("short_id")
|
||||
var shortId: Int, // 0
|
||||
@SerializedName("special_type")
|
||||
var specialType: Int, // 0
|
||||
@SerializedName("uid")
|
||||
var uid: Long // 20293030
|
||||
)
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.model
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class MobileRoomBanner(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // OK
|
||||
@SerializedName("msg")
|
||||
var msg: String // OK
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("bottom")
|
||||
var bottom: List<JsonElement>,
|
||||
@SerializedName("gift")
|
||||
var gift: JsonElement?, // null
|
||||
@SerializedName("gift_banner")
|
||||
var giftBanner: JsonElement?, // null
|
||||
@SerializedName("inputBanner")
|
||||
var inputBanner: List<JsonElement>,
|
||||
@SerializedName("lol_activity")
|
||||
var lolActivity: LolActivity,
|
||||
@SerializedName("superBanner")
|
||||
var superBanner: JsonElement?, // null
|
||||
@SerializedName("top")
|
||||
var top: List<Top>
|
||||
) {
|
||||
data class Top(
|
||||
@SerializedName("activity_title")
|
||||
var activityTitle: String, // 周星
|
||||
@SerializedName("color")
|
||||
var color: String,
|
||||
@SerializedName("cover")
|
||||
var cover: String, // https://i0.hdslb.com/bfs/vc/5cfb2a7dc2a25db580f130a55f475f74e2bd3202.png
|
||||
@SerializedName("expire_hour")
|
||||
var expireHour: Int, // 24
|
||||
@SerializedName("gift_img")
|
||||
var giftImg: String, // https://s1.hdslb.com/bfs/vc/39aee4bf13b170f22f19ef1c278cebf3a6e40332.png
|
||||
@SerializedName("id")
|
||||
var id: Int, // 199
|
||||
@SerializedName("is_close")
|
||||
var isClose: Int, // 0
|
||||
@SerializedName("jump_url")
|
||||
var jumpUrl: String, // https://live.bilibili.com/p/html/live-app-weekstar/index.html?is_live_half_webview=1&hybrid_biz=live-app-weekStar&hybrid_rotate_d=1&hybrid_half_ui=1,3,100p,70p,300e51,0,30,100;2,2,375,100p,300e51,0,30,100;3,3,100p,70p,300e51,0,30,100;4,2,375,100p,300e51,0,30,100;5,3,100p,70p,300e51,0,30,100;6,3,100p,70p,300e51,0,30,100;7,3,100p,70p,300e51,0,30,100&room_id=29434
|
||||
@SerializedName("rank")
|
||||
var rank: String, // 999+
|
||||
@SerializedName("rank_name")
|
||||
var rankName: String, // 打榜
|
||||
@SerializedName("title")
|
||||
var title: String, // 排名
|
||||
@SerializedName("type")
|
||||
var type: Int, // 1
|
||||
@SerializedName("week_gift_color")
|
||||
var weekGiftColor: String, // #ffffff
|
||||
@SerializedName("week_rank_color")
|
||||
var weekRankColor: String, // #ffffff
|
||||
@SerializedName("week_text_color")
|
||||
var weekTextColor: String // #ffffff
|
||||
)
|
||||
|
||||
data class LolActivity(
|
||||
@SerializedName("guess_cover")
|
||||
var guessCover: String, // https://i0.hdslb.com/bfs/live/61d1c4bcce470080a5408d6c03b7b48e0a0fa8d7.png
|
||||
@SerializedName("status")
|
||||
var status: Int, // 0
|
||||
@SerializedName("vote_cover")
|
||||
var voteCover: String // https://i0.hdslb.com/bfs/live/6030cb2847f4d197caacb12fbe12f2656b999bcf.png
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class MobileTab(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: List<Tab>,
|
||||
@SerializedName("message")
|
||||
var message: String,
|
||||
@SerializedName("msg")
|
||||
var msg: String
|
||||
) {
|
||||
data class Tab(
|
||||
/**
|
||||
* 如果是非顶层 Tab 则 default 为 null
|
||||
*/
|
||||
@SerializedName("default")
|
||||
var default: Int?, // 0
|
||||
@SerializedName("default_sub_tab")
|
||||
var defaultSubTab: String,
|
||||
@SerializedName("desc")
|
||||
var desc: String, // 友爱社
|
||||
@SerializedName("order")
|
||||
var order: Int, // 600
|
||||
@SerializedName("status")
|
||||
var status: Int, // 0
|
||||
@SerializedName("sub_tab")
|
||||
var subTab: List<Tab>,
|
||||
@SerializedName("type")
|
||||
var type: String, // love-club
|
||||
@SerializedName("url")
|
||||
var url: String
|
||||
)
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.model
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class RoomInfo(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // ok
|
||||
@SerializedName("msg")
|
||||
var msg: String // ok
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("allow_change_area_time")
|
||||
var allowChangeAreaTime: Int, // 0
|
||||
@SerializedName("allow_upload_cover_time")
|
||||
var allowUploadCoverTime: Int, // 0
|
||||
/**
|
||||
* 没有 old 前缀的表示 v2 版本, 例如 area_v2_id. 下同
|
||||
*/
|
||||
@SerializedName("area_id")
|
||||
var areaId: Int, // 107
|
||||
@SerializedName("area_name")
|
||||
var areaName: String, // 其他游戏
|
||||
@SerializedName("area_pendants")
|
||||
var areaPendants: String,
|
||||
/**
|
||||
* 粉丝数
|
||||
*/
|
||||
@SerializedName("attention")
|
||||
var attention: Int, // 62
|
||||
@SerializedName("background")
|
||||
var background: String,
|
||||
@SerializedName("description")
|
||||
var description: String, // <p>立即安装Jetbrains, 享受精彩人生!</p>
|
||||
@SerializedName("hot_words")
|
||||
var hotWords: List<String>,
|
||||
@SerializedName("hot_words_status")
|
||||
var hotWordsStatus: Int, // 0
|
||||
@SerializedName("is_anchor")
|
||||
var isAnchor: Int, // 1
|
||||
@SerializedName("is_portrait")
|
||||
var isPortrait: Boolean, // false
|
||||
@SerializedName("is_strict_room")
|
||||
var isStrictRoom: Boolean, // false
|
||||
@SerializedName("keyframe")
|
||||
var keyframe: String, // https://i0.hdslb.com/bfs/live/1110317.jpg?03092037
|
||||
@SerializedName("live_status")
|
||||
var liveStatus: Int, // 0
|
||||
@SerializedName("live_time")
|
||||
var liveTime: String, // 0000-00-00 00:00:00
|
||||
@SerializedName("new_pendants")
|
||||
var newPendants: NewPendants,
|
||||
@SerializedName("old_area_id")
|
||||
var oldAreaId: Int, // 1
|
||||
@SerializedName("online")
|
||||
var online: Int, // 18
|
||||
@SerializedName("parent_area_id")
|
||||
var parentAreaId: Int, // 2
|
||||
@SerializedName("parent_area_name")
|
||||
var parentAreaName: String, // 网游
|
||||
@SerializedName("pendants")
|
||||
var pendants: String,
|
||||
@SerializedName("pk_id")
|
||||
var pkId: Int, // 0
|
||||
@SerializedName("pk_status")
|
||||
var pkStatus: Int, // 0
|
||||
@SerializedName("room_id")
|
||||
var roomId: Long, // 1110317
|
||||
@SerializedName("room_silent_level")
|
||||
var roomSilentLevel: Int, // 0
|
||||
@SerializedName("room_silent_second")
|
||||
var roomSilentSecond: Int, // 0
|
||||
@SerializedName("room_silent_type")
|
||||
var roomSilentType: String,
|
||||
@SerializedName("short_id")
|
||||
var shortId: Int, // 0
|
||||
@SerializedName("tags")
|
||||
var tags: String, // 编程
|
||||
@SerializedName("title")
|
||||
var title: String, // 太空程序员
|
||||
@SerializedName("uid")
|
||||
var uid: Long, // 20293030
|
||||
@SerializedName("up_session")
|
||||
var upSession: String,
|
||||
@SerializedName("user_cover")
|
||||
var userCover: String, // https://i0.hdslb.com/bfs/live/b4d4dbf35f7a30fb6b0a2ea4077514235262797e.jpg
|
||||
@SerializedName("verify")
|
||||
var verify: String
|
||||
) {
|
||||
data class NewPendants(
|
||||
@SerializedName("badge")
|
||||
var badge: JsonElement?, // null
|
||||
@SerializedName("frame")
|
||||
var frame: JsonElement?, // null
|
||||
@SerializedName("mobile_badge")
|
||||
var mobileBadge: JsonElement?, // null
|
||||
@SerializedName("mobile_frame")
|
||||
var mobileFrame: JsonElement? // null
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.model
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class RoomList(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // success
|
||||
@SerializedName("msg")
|
||||
var msg: String // success
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("banner")
|
||||
var banner: List<Banner>,
|
||||
@SerializedName("count")
|
||||
var count: Int, // 5116
|
||||
@SerializedName("list")
|
||||
var list: List<X>,
|
||||
@SerializedName("tags")
|
||||
var tags: List<Tag>
|
||||
) {
|
||||
data class Banner(
|
||||
@SerializedName("id")
|
||||
var id: String, // 1117
|
||||
@SerializedName("link")
|
||||
var link: String, // https://www.bilibili.com/blackboard/live/activity-flower-girl2-h5.html
|
||||
@SerializedName("pic")
|
||||
var pic: String, // https://i0.hdslb.com/bfs/vc/523a719b51a647eeb969956865a20781d7e6d994.jpg
|
||||
@SerializedName("position")
|
||||
var position: String, // 5
|
||||
@SerializedName("sort_num")
|
||||
var sortNum: String, // 1
|
||||
@SerializedName("title")
|
||||
var title: String // 花之初少女
|
||||
)
|
||||
|
||||
data class X(
|
||||
@SerializedName("accept_quality")
|
||||
var acceptQuality: String, // 4
|
||||
@SerializedName("accept_quality_v2")
|
||||
var acceptQualityV2: List<JsonElement>,
|
||||
@SerializedName("area_id")
|
||||
var areaId: Int, // 107
|
||||
@SerializedName("area_name")
|
||||
var areaName: String, // 其他游戏
|
||||
@SerializedName("area_v2_id")
|
||||
var areaV2Id: Int, // 107
|
||||
@SerializedName("area_v2_name")
|
||||
var areaV2Name: String, // 其他游戏
|
||||
@SerializedName("area_v2_parent_id")
|
||||
var areaV2ParentId: Int, // 2
|
||||
@SerializedName("area_v2_parent_name")
|
||||
var areaV2ParentName: String, // 网游
|
||||
@SerializedName("broadcast_type")
|
||||
var broadcastType: Int, // 0
|
||||
@SerializedName("corner")
|
||||
var corner: String,
|
||||
@SerializedName("cover_size")
|
||||
var coverSize: CoverSize,
|
||||
@SerializedName("current_quality")
|
||||
var currentQuality: Int, // 4
|
||||
@SerializedName("face")
|
||||
var face: String, // https://i0.hdslb.com/bfs/face/9c9ad7d21784e70dfa57cdae40cfdca1b58424c4.jpg
|
||||
@SerializedName("game_live_num")
|
||||
var gameLiveNum: Int, // 30
|
||||
@SerializedName("group_id")
|
||||
var groupId: Int, // 0
|
||||
@SerializedName("is_tv")
|
||||
var isTv: Int, // 0
|
||||
@SerializedName("link")
|
||||
var link: String, // /21142258
|
||||
@SerializedName("online")
|
||||
var online: Int, // 0
|
||||
@SerializedName("parent_id")
|
||||
var parentId: Int, // 2
|
||||
@SerializedName("parent_name")
|
||||
var parentName: String, // 网游
|
||||
@SerializedName("pendent")
|
||||
var pendent: String,
|
||||
@SerializedName("pendent_ld")
|
||||
var pendentLd: String,
|
||||
@SerializedName("pendent_ld_color")
|
||||
var pendentLdColor: String,
|
||||
@SerializedName("pendent_ru")
|
||||
var pendentRu: String,
|
||||
@SerializedName("pendent_ru_color")
|
||||
var pendentRuColor: String,
|
||||
@SerializedName("pendent_ru_pic")
|
||||
var pendentRuPic: String,
|
||||
@SerializedName("pk_id")
|
||||
var pkId: Int, // 0
|
||||
@SerializedName("play_url")
|
||||
var playUrl: String,
|
||||
@SerializedName("roomid")
|
||||
var roomid: Long, // 21142258
|
||||
@SerializedName("session_id")
|
||||
var sessionId: String, // E9168524-EA1A-9F86-7A88-E33B58B9C8A8
|
||||
@SerializedName("show_cover")
|
||||
var showCover: String,
|
||||
@SerializedName("system_cover")
|
||||
var systemCover: String, // https://i0.hdslb.com/bfs/live/21142258.jpg?03131614
|
||||
@SerializedName("title")
|
||||
var title: String, // DNF除了肝啥都没
|
||||
@SerializedName("uid")
|
||||
var uid: Long, // 278287794
|
||||
@SerializedName("uname")
|
||||
var uname: String, // wz85699909
|
||||
@SerializedName("user_cover")
|
||||
var userCover: String, // https://i0.hdslb.com/bfs/live/room_cover/dbfd1bbb5f936620a7bccac4b5e51b54342284d1.jpg
|
||||
@SerializedName("user_cover_flag")
|
||||
var userCoverFlag: Int, // 1
|
||||
@SerializedName("web_pendent")
|
||||
var webPendent: String
|
||||
) {
|
||||
data class CoverSize(
|
||||
@SerializedName("height")
|
||||
var height: Int, // 180
|
||||
@SerializedName("width")
|
||||
var width: Int // 320
|
||||
)
|
||||
}
|
||||
|
||||
data class Tag(
|
||||
@SerializedName("id")
|
||||
var id: Int, // -2
|
||||
@SerializedName("name")
|
||||
var name: String, // 最新
|
||||
@SerializedName("sort_type")
|
||||
var sortType: String // live_time
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class RoomMessage(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String,
|
||||
@SerializedName("msg")
|
||||
var msg: String
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("admin")
|
||||
var admin: List<Admin>,
|
||||
@SerializedName("room")
|
||||
var room: List<Message>
|
||||
) {
|
||||
data class Message(
|
||||
@SerializedName("bubble")
|
||||
var bubble: Int, // 0
|
||||
@SerializedName("check_info")
|
||||
var checkInfo: CheckInfo,
|
||||
@SerializedName("guard_level")
|
||||
var guardLevel: Int, // 0
|
||||
@SerializedName("isadmin")
|
||||
var isadmin: Int, // 0
|
||||
@SerializedName("medal")
|
||||
var medal: List<String>, //[ 17, "毛菇","猫菇椰汁",923614,16752445,""]
|
||||
@SerializedName("nickname")
|
||||
var nickname: String, // 柠檬味狗凉゜
|
||||
@SerializedName("rank")
|
||||
var rank: Int, // 10000
|
||||
@SerializedName("rnd")
|
||||
var rnd: String, // 1552452731
|
||||
@SerializedName("svip")
|
||||
var svip: Int, // 1
|
||||
@SerializedName("teamid")
|
||||
var teamid: Int, // 0
|
||||
@SerializedName("text")
|
||||
var text: String, // 当前有效总督房:14979272;146
|
||||
@SerializedName("timeline")
|
||||
var timeline: String, // 2019-03-13 12:52:09
|
||||
@SerializedName("title")
|
||||
var title: List<String>,
|
||||
@SerializedName("uid")
|
||||
var uid: Int, // 1615204
|
||||
@SerializedName("uname_color")
|
||||
var unameColor: String,
|
||||
/**
|
||||
* 有可能是 >50000 这样的东西, 下同
|
||||
*/
|
||||
@SerializedName("user_level")
|
||||
var userLevel: List<String>,
|
||||
@SerializedName("user_title")
|
||||
var userTitle: String,
|
||||
@SerializedName("vip")
|
||||
var vip: Int // 1
|
||||
) {
|
||||
data class CheckInfo(
|
||||
@SerializedName("ct")
|
||||
var ct: String, // 4B9A2E41
|
||||
@SerializedName("ts")
|
||||
var ts: Int // 1552452729
|
||||
)
|
||||
}
|
||||
|
||||
data class Admin(
|
||||
@SerializedName("bubble")
|
||||
var bubble: Int, // 2
|
||||
@SerializedName("check_info")
|
||||
var checkInfo: CheckInfo,
|
||||
@SerializedName("guard_level")
|
||||
var guardLevel: Int, // 2
|
||||
@SerializedName("isadmin")
|
||||
var isadmin: Int, // 1
|
||||
@SerializedName("medal")
|
||||
var medal: List<String>,
|
||||
@SerializedName("nickname")
|
||||
var nickname: String, // 沧澜ベ
|
||||
@SerializedName("rank")
|
||||
var rank: Int, // 10000
|
||||
@SerializedName("rnd")
|
||||
var rnd: String, // 350761541
|
||||
@SerializedName("svip")
|
||||
var svip: Int, // 1
|
||||
@SerializedName("teamid")
|
||||
var teamid: Int, // 0
|
||||
@SerializedName("text")
|
||||
var text: String, // 下个号见
|
||||
@SerializedName("timeline")
|
||||
var timeline: String, // 2019-03-13 12:54:42
|
||||
@SerializedName("title")
|
||||
var title: List<String>,
|
||||
@SerializedName("uid")
|
||||
var uid: Long, // 11626554
|
||||
@SerializedName("uname_color")
|
||||
var unameColor: String, // #e91e63
|
||||
@SerializedName("user_level")
|
||||
var userLevel: List<String>,
|
||||
@SerializedName("user_title")
|
||||
var userTitle: String, // title-181-1
|
||||
@SerializedName("vip")
|
||||
var vip: Int // 1
|
||||
) {
|
||||
data class CheckInfo(
|
||||
@SerializedName("ct")
|
||||
var ct: String, // D1012BA7
|
||||
@SerializedName("ts")
|
||||
var ts: Long // 1552452882
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class RoomRank(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // OK
|
||||
@SerializedName("msg")
|
||||
var msg: String // OK
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("color")
|
||||
var color: String, // #FB7299
|
||||
@SerializedName("h5_url")
|
||||
var h5Url: String, // https://live.bilibili.com/p/html/live-app-rankcurrent/index.html?is_live_half_webview=1&hybrid_half_ui=1,5,85p,70p,FFE293,0,30,100,10;2,2,320,100p,FFE293,0,30,100,0;4,2,320,100p,FFE293,0,30,100,0;6,5,65p,60p,FFE293,0,30,100,10;5,5,55p,60p,FFE293,0,30,100,10;3,5,85p,70p,FFE293,0,30,100,10;7,5,65p,60p,FFE293,0,30,100,10;&anchor_uid=2866663&rank_type=master_realtime_hour_room&area_hour=1&area_v2_id=145&area_v2_parent_id=1
|
||||
@SerializedName("rank_desc")
|
||||
var rankDesc: String, // 小时总榜
|
||||
@SerializedName("roomid")
|
||||
var roomid: Long, // 29434
|
||||
@SerializedName("timestamp")
|
||||
var timestamp: Long, // 1552451099
|
||||
@SerializedName("web_url")
|
||||
var webUrl: String // https://live.bilibili.com/blackboard/room-current-rank.html?rank_type=master_realtime_hour_room&area_hour=1&area_v2_id=145&area_v2_parent_id=1
|
||||
)
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Title(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: List<Title>,
|
||||
@SerializedName("message")
|
||||
var message: String, // success
|
||||
@SerializedName("msg")
|
||||
var msg: String // success
|
||||
) {
|
||||
data class Title(
|
||||
@SerializedName("colorful")
|
||||
var colorful: Int, // 0
|
||||
@SerializedName("height")
|
||||
var height: Int, // 20
|
||||
@SerializedName("id")
|
||||
var id: String, // cake-flour
|
||||
@SerializedName("img")
|
||||
var img: String, // https://s1.hdslb.com/bfs/static/blive/live-assets/mobile/titles/title/3/cake-flour.png?20180726173300
|
||||
@SerializedName("is_lihui")
|
||||
var isLihui: Int, // 0
|
||||
@SerializedName("lihui_height")
|
||||
var lihuiHeight: Int, // 0
|
||||
@SerializedName("lihui_img")
|
||||
var lihuiImg: String,
|
||||
@SerializedName("lihui_width")
|
||||
var lihuiWidth: Int, // 0
|
||||
@SerializedName("title")
|
||||
var title: String, // 2016 新春活动
|
||||
@SerializedName("width")
|
||||
var width: Int // 68
|
||||
)
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.model
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class User(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // OK
|
||||
@SerializedName("msg")
|
||||
var msg: String // OK
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("gold")
|
||||
var gold: Int, // 0
|
||||
@SerializedName("isSign")
|
||||
var isSign: Int, // 0
|
||||
@SerializedName("medal")
|
||||
var medal: JsonElement?, // null
|
||||
@SerializedName("new")
|
||||
var new: Int, // 1
|
||||
@SerializedName("room_id")
|
||||
var roomId: Long, // 1110317
|
||||
@SerializedName("silver")
|
||||
var silver: Int, // 140258
|
||||
@SerializedName("svip")
|
||||
var svip: Int, // 0
|
||||
@SerializedName("svip_time")
|
||||
var svipTime: String, // 0000-00-00 00:00:00
|
||||
@SerializedName("use_count")
|
||||
var useCount: Int, // 0
|
||||
@SerializedName("user_level")
|
||||
var userLevel: Int, // 25
|
||||
@SerializedName("user_level_color")
|
||||
var userLevelColor: Int, // 5805790
|
||||
@SerializedName("vip")
|
||||
var vip: Int, // 0
|
||||
@SerializedName("vip_time")
|
||||
var vipTime: String, // 2018-05-15 12:00:50
|
||||
@SerializedName("vip_view_status")
|
||||
var vipViewStatus: Int, // 1
|
||||
@SerializedName("wearTitle")
|
||||
var wearTitle: WearTitle
|
||||
) {
|
||||
data class WearTitle(
|
||||
@SerializedName("activity")
|
||||
var activity: String, // 0
|
||||
@SerializedName("title")
|
||||
var title: String // 0
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.model
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class UserInfoInRoom(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // success
|
||||
@SerializedName("msg")
|
||||
var msg: String // success
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("entry_effect")
|
||||
var entryEffect: EntryEffect,
|
||||
@SerializedName("gift")
|
||||
var gift: Gift,
|
||||
@SerializedName("info")
|
||||
var info: Info,
|
||||
@SerializedName("level")
|
||||
var level: Level,
|
||||
@SerializedName("new")
|
||||
var new: Int, // 1
|
||||
@SerializedName("privilege")
|
||||
var privilege: Privilege,
|
||||
@SerializedName("role")
|
||||
var role: Role,
|
||||
@SerializedName("room_admin")
|
||||
var roomAdmin: RoomAdmin,
|
||||
@SerializedName("wallet")
|
||||
var wallet: Wallet
|
||||
) {
|
||||
data class Wallet(
|
||||
@SerializedName("gold")
|
||||
var gold: String, // 0
|
||||
@SerializedName("silver")
|
||||
var silver: String // 140258
|
||||
)
|
||||
|
||||
data class Info(
|
||||
@SerializedName("bili_vip")
|
||||
var biliVip: Int, // 0
|
||||
@SerializedName("face")
|
||||
var face: String, // https://i1.hdslb.com/bfs/face/4f65e79399ad5a1bf3f877851b2f819d5870b494.jpg
|
||||
@SerializedName("gender")
|
||||
var gender: Int, // 0
|
||||
@SerializedName("identification")
|
||||
var identification: Int, // 1
|
||||
@SerializedName("mobile_verify")
|
||||
var mobileVerify: Int, // 1
|
||||
@SerializedName("mobile_virtual")
|
||||
var mobileVirtual: Int, // 0
|
||||
@SerializedName("official_verify")
|
||||
var officialVerify: OfficialVerify,
|
||||
@SerializedName("platform_user_level")
|
||||
var platformUserLevel: Int, // 4
|
||||
@SerializedName("rank")
|
||||
var rank: String, // 10000
|
||||
@SerializedName("uid")
|
||||
var uid: Long, // 20293030
|
||||
@SerializedName("uname")
|
||||
var uname: String, // czp3009
|
||||
@SerializedName("vip_type")
|
||||
var vipType: Int // 0
|
||||
) {
|
||||
data class OfficialVerify(
|
||||
@SerializedName("desc")
|
||||
var desc: String,
|
||||
@SerializedName("role")
|
||||
var role: Int, // 0
|
||||
@SerializedName("type")
|
||||
var type: Int // -1
|
||||
)
|
||||
}
|
||||
|
||||
data class Level(
|
||||
@SerializedName("color")
|
||||
var color: Int, // 5805790
|
||||
@SerializedName("cost")
|
||||
var cost: Int, // 52187800
|
||||
@SerializedName("is_show_vip_broadcast")
|
||||
var isShowVipBroadcast: Int, // 0
|
||||
@SerializedName("master_level")
|
||||
var masterLevel: MasterLevel,
|
||||
@SerializedName("rcost")
|
||||
var rcost: Long, // 2737665
|
||||
@SerializedName("svip")
|
||||
var svip: Int, // 0
|
||||
@SerializedName("svip_time")
|
||||
var svipTime: String, // 0000-00-00 00:00:00
|
||||
@SerializedName("uid")
|
||||
var uid: Int, // 20293030
|
||||
@SerializedName("update_time")
|
||||
var updateTime: String, // 0000-00-00 00:00:00
|
||||
@SerializedName("user_level")
|
||||
var userLevel: Int, // 25
|
||||
@SerializedName("user_level_rank")
|
||||
var userLevelRank: String, // >50000
|
||||
@SerializedName("user_score")
|
||||
var userScore: String, // 0
|
||||
@SerializedName("vip")
|
||||
var vip: Int, // 0
|
||||
@SerializedName("vip_time")
|
||||
var vipTime: String // 2018-05-15 12:00:50
|
||||
) {
|
||||
data class MasterLevel(
|
||||
@SerializedName("color")
|
||||
var color: Int, // 5805790
|
||||
@SerializedName("current")
|
||||
var current: List<Int>,
|
||||
@SerializedName("level")
|
||||
var level: Int, // 11
|
||||
@SerializedName("next")
|
||||
var next: List<Int>
|
||||
)
|
||||
}
|
||||
|
||||
data class RoomAdmin(
|
||||
@SerializedName("is_admin")
|
||||
var isAdmin: Int // 0
|
||||
)
|
||||
|
||||
data class Role(
|
||||
@SerializedName("info")
|
||||
var info: Info,
|
||||
@SerializedName("role_id")
|
||||
var roleId: Int, // 2
|
||||
@SerializedName("role_name")
|
||||
var roleName: String // 播主
|
||||
) {
|
||||
data class Info(
|
||||
@SerializedName("roomid")
|
||||
var roomid: Long // 1110317
|
||||
)
|
||||
}
|
||||
|
||||
data class EntryEffect(
|
||||
@SerializedName("basemap_url")
|
||||
var basemapUrl: String,
|
||||
@SerializedName("copy_writing")
|
||||
var copyWriting: String,
|
||||
@SerializedName("effective_time")
|
||||
var effectiveTime: Int, // 0
|
||||
@SerializedName("face")
|
||||
var face: String, // https://i1.hdslb.com/bfs/face/4f65e79399ad5a1bf3f877851b2f819d5870b494.jpg
|
||||
@SerializedName("highlight_color")
|
||||
var highlightColor: String,
|
||||
@SerializedName("id")
|
||||
var id: Int, // 0
|
||||
@SerializedName("mock_effect")
|
||||
var mockEffect: Int, // 0
|
||||
@SerializedName("priority")
|
||||
var priority: Int, // 0
|
||||
@SerializedName("privilege_type")
|
||||
var privilegeType: Int, // 0
|
||||
@SerializedName("show_avatar")
|
||||
var showAvatar: Int, // 0
|
||||
@SerializedName("target_id")
|
||||
var targetId: Long, // 2866663
|
||||
@SerializedName("uid")
|
||||
var uid: Long // 20293030
|
||||
)
|
||||
|
||||
data class Privilege(
|
||||
@SerializedName("broadcast")
|
||||
var broadcast: JsonElement?, // null
|
||||
@SerializedName("notice_status")
|
||||
var noticeStatus: Int // 1
|
||||
)
|
||||
|
||||
data class Gift(
|
||||
@SerializedName("is_show")
|
||||
var isShow: String, // 1
|
||||
@SerializedName("uid")
|
||||
var uid: Long // 20293030
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.websocket
|
||||
|
||||
import com.github.salomonbrys.kotson.obj
|
||||
import com.google.gson.JsonObject
|
||||
import com.hiczp.bilibili.api.BilibiliClient
|
||||
import com.hiczp.bilibili.api.jsonParser
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.cio.CIO
|
||||
import io.ktor.client.features.websocket.WebSockets
|
||||
import io.ktor.client.features.websocket.wss
|
||||
import io.ktor.http.cio.websocket.CloseReason
|
||||
import io.ktor.http.cio.websocket.close
|
||||
import io.ktor.util.InternalAPI
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import io.ktor.util.decodeString
|
||||
import io.ktor.util.error
|
||||
import kotlinx.coroutines.*
|
||||
import mu.KotlinLogging
|
||||
|
||||
private val logger = KotlinLogging.logger { }
|
||||
|
||||
/**
|
||||
* 直播客户端
|
||||
* 注意该类是有状态的
|
||||
*
|
||||
* @param maybeShortRoomId 可能为短房间号的房间号
|
||||
* @param fetchRoomId 是否在连接前先获取房间号(长号)
|
||||
* @param fetchDanmakuConfig 是否在连接前先获取弹幕推送服务器地址
|
||||
* @param doEntryRoomAction 是否产生直播间观看历史记录
|
||||
* @param sendUserOnlineHeart 是否发送 rest 心跳包, 这会增加观看直播的时长, 用于服务端统计(与弹幕推送无关)
|
||||
* @param callback 回调
|
||||
*/
|
||||
@Suppress("CanBeParameter")
|
||||
class LiveClient(
|
||||
private val bilibiliClient: BilibiliClient,
|
||||
maybeShortRoomId: Long,
|
||||
private val fetchRoomId: Boolean = true,
|
||||
private val fetchDanmakuConfig: Boolean = true,
|
||||
private val doEntryRoomAction: Boolean = false,
|
||||
private val sendUserOnlineHeart: Boolean = false,
|
||||
callback: LiveClientCallbackDSL.() -> Unit
|
||||
) {
|
||||
private val callback = LiveClientCallbackDSL().apply { callback() }
|
||||
private val liveAPI = bilibiliClient.liveAPI
|
||||
|
||||
var roomId = maybeShortRoomId
|
||||
private set
|
||||
|
||||
/**
|
||||
* 开启连接
|
||||
*/
|
||||
@UseExperimental(KtorExperimentalAPI::class, ObsoleteCoroutinesApi::class, InternalAPI::class)
|
||||
fun launch() = GlobalScope.launch(CoroutineExceptionHandler { _, throwable ->
|
||||
callback.onError?.invoke(this, throwable) ?: logger.error(throwable)
|
||||
}) {
|
||||
//得到原始房间号和主播的用户ID
|
||||
var anchorUserId = 0L
|
||||
if (fetchRoomId) {
|
||||
liveAPI.mobileRoomInit(roomId).await().data.also {
|
||||
roomId = it.roomId
|
||||
anchorUserId = it.uid
|
||||
}
|
||||
}
|
||||
|
||||
//获得 wss 地址和端口(推荐服务器)
|
||||
@Suppress("SpellCheckingInspection")
|
||||
var host = "broadcastlv.chat.bilibili.com"
|
||||
var port = 443
|
||||
if (fetchDanmakuConfig) {
|
||||
liveAPI.getDanmakuConfig(roomId).await().data.also { data ->
|
||||
host = data.host
|
||||
data.hostServerList.find { it.host == host }?.wssPort?.also {
|
||||
port = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//产生历史记录
|
||||
@Suppress("DeferredResultUnused")
|
||||
if (doEntryRoomAction && bilibiliClient.isLogin) liveAPI.roomEntryAction(roomId)
|
||||
|
||||
//开启 websocket
|
||||
HttpClient(CIO).config { install(WebSockets) }.wss(host = host, port = port, path = "/sub") {
|
||||
//发送进房数据包
|
||||
send(PresetPacket.enterRoomPacket(anchorUserId, roomId))
|
||||
val enterRoomResponsePacket = incoming.receive().toPackets()[0]
|
||||
if (enterRoomResponsePacket.packetType == PacketType.ENTER_ROOM_RESPONSE) {
|
||||
try {
|
||||
callback.onConnect?.invoke(this@LiveClient)
|
||||
} catch (e: Exception) {
|
||||
logger.error(e)
|
||||
}
|
||||
} else {
|
||||
//impossible
|
||||
logger.error { "Receive unreadable server response: $enterRoomResponsePacket" }
|
||||
close(CloseReason(CloseReason.Codes.NOT_CONSISTENT, ""))
|
||||
return@wss
|
||||
}
|
||||
|
||||
//发送 rest 心跳包
|
||||
//五分钟一次
|
||||
val restHeartBeatJob = if (sendUserOnlineHeart && bilibiliClient.isLogin) {
|
||||
launch {
|
||||
val scale = bilibiliClient.billingClientProperties.scale
|
||||
while (true) {
|
||||
liveAPI.userOnlineHeart(roomId, scale).invokeOnCompletion {
|
||||
if (it != null) logger.error(it)
|
||||
}
|
||||
delay(300_000)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
//发送 websocket 心跳包
|
||||
//30 秒一次
|
||||
val websocketHeartBeatJob = launch {
|
||||
try {
|
||||
while (true) {
|
||||
send(PresetPacket.heartbeatPacket())
|
||||
delay(30_000)
|
||||
}
|
||||
} catch (ignore: CancellationException) {
|
||||
//ignore
|
||||
} catch (e: Exception) {
|
||||
logger.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
withTimeout(40_000) {
|
||||
incoming.receive()
|
||||
}.toPackets().forEach {
|
||||
try {
|
||||
@Suppress("NON_EXHAUSTIVE_WHEN")
|
||||
when (it.packetType) {
|
||||
PacketType.POPULARITY -> callback.onPopularityPacket?.invoke(
|
||||
this@LiveClient,
|
||||
it.content.int
|
||||
)
|
||||
PacketType.COMMAND -> callback.onCommandPacket?.invoke(
|
||||
this@LiveClient,
|
||||
jsonParser.parse(it.content.decodeString()).obj
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: TimeoutCancellationException) {
|
||||
throw e
|
||||
} catch (e: CancellationException) {
|
||||
close()
|
||||
} finally {
|
||||
restHeartBeatJob?.cancel()
|
||||
websocketHeartBeatJob.cancel()
|
||||
launch {
|
||||
val closeReason = closeReason.await()
|
||||
try {
|
||||
callback.onClose?.invoke(this@LiveClient, closeReason)
|
||||
} catch (e: Exception) {
|
||||
logger.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送弹幕
|
||||
*/
|
||||
fun sendMessage(message: String) =
|
||||
liveAPI.sendMessage(cid = roomId, mid = bilibiliClient.userId ?: 0, message = message)
|
||||
}
|
||||
|
||||
class LiveClientCallbackDSL {
|
||||
/**
|
||||
* 成功进入房间时触发
|
||||
*/
|
||||
var onConnect: ((LiveClient) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* 抛出异常时触发
|
||||
*/
|
||||
var onError: ((LiveClient, Throwable) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* 收到人气值数据包
|
||||
*/
|
||||
var onPopularityPacket: ((LiveClient, Int) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* 收到 command 数据包
|
||||
*/
|
||||
var onCommandPacket: ((LiveClient, JsonObject) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* 连接关闭时触发
|
||||
*/
|
||||
var onClose: ((LiveClient, CloseReason?) -> Unit)? = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开一个直播客户端
|
||||
*/
|
||||
fun BilibiliClient.liveClient(
|
||||
roomId: Long,
|
||||
fetchRoomId: Boolean = true,
|
||||
fetchDanmakuConfig: Boolean = true,
|
||||
doEntryRoomAction: Boolean = false,
|
||||
sendUserOnlineHeart: Boolean = false,
|
||||
callback: LiveClientCallbackDSL.() -> Unit
|
||||
) = LiveClient(
|
||||
this, roomId, fetchRoomId, fetchDanmakuConfig, doEntryRoomAction, sendUserOnlineHeart,
|
||||
callback
|
||||
)
|
||||
@@ -1,66 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.websocket
|
||||
|
||||
import io.ktor.http.cio.websocket.Frame
|
||||
import io.ktor.http.cio.websocket.WebSocketSession
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/**
|
||||
* 数据包模型
|
||||
* 由于 Android APP 并未全线换成 wss, 以下用的是移动版网页的协议
|
||||
* 数据包头部结构 00 00 00 65 00 10 00 01 00 00 00 07 00 00 00 01
|
||||
* |数据包总长度| |头长| |tag| |数据包类型 | | tag |
|
||||
*
|
||||
* @param shortTag 一种 tag, 如果是非 command 数据包则为 1, 否则为 0, short 类型
|
||||
* @param packetType 数据包类型
|
||||
* @param tag 同 tagShort, 但是为 int 类型
|
||||
* @param content 正文内容
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
data class Packet(
|
||||
val shortTag: Short = 1,
|
||||
val packetType: PacketType,
|
||||
val tag: Int = 1,
|
||||
val content: ByteBuffer
|
||||
) {
|
||||
val totalLength
|
||||
get() = headerLength + content.limit()
|
||||
|
||||
val headerLength: Short = 0x10
|
||||
|
||||
fun toFrame() = Frame.Binary(
|
||||
true,
|
||||
ByteBuffer.allocate(totalLength)
|
||||
.putInt(totalLength)
|
||||
.putShort(headerLength)
|
||||
.putShort(shortTag)
|
||||
.putInt(packetType.value)
|
||||
.putInt(tag)
|
||||
.put(content).apply {
|
||||
flip()
|
||||
}!!
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个 Message 中可能包含多个数据包
|
||||
*/
|
||||
internal fun Frame.toPackets(): List<Packet> {
|
||||
val bufferLength = buffer.limit()
|
||||
val list = ArrayList<Packet>()
|
||||
while (buffer.hasRemaining()) {
|
||||
val startPosition = buffer.position()
|
||||
val totalLength = buffer.int
|
||||
buffer.position(buffer.position() + 2) //skip headerLength
|
||||
val shortTag = buffer.short
|
||||
val packetType = PacketType.getByValue(buffer.int)
|
||||
val tag = buffer.int
|
||||
buffer.limit(startPosition + totalLength)
|
||||
val content = buffer.slice()
|
||||
buffer.position(buffer.limit())
|
||||
buffer.limit(bufferLength)
|
||||
list.add(Packet(shortTag, packetType, tag, content))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
internal suspend inline fun WebSocketSession.send(packet: Packet) = send(packet.toFrame())
|
||||
@@ -1,373 +0,0 @@
|
||||
package com.hiczp.bilibili.api.main
|
||||
|
||||
import com.hiczp.bilibili.api.main.model.*
|
||||
import com.hiczp.bilibili.api.retrofit.CommonResponse
|
||||
import com.hiczp.bilibili.api.retrofit.Header
|
||||
import kotlinx.coroutines.Deferred
|
||||
import retrofit2.http.*
|
||||
|
||||
/**
|
||||
* 这也是总站 API
|
||||
*/
|
||||
@Suppress("DeferredIsResult")
|
||||
interface MainAPI {
|
||||
/**
|
||||
* 获取一个视频下的评论
|
||||
* 注意, 评论是倒序排序的, 即楼层大的楼排在前面, 所以返回值中的 next 会比 prev 小
|
||||
* 返回值中的 rpid 为评论 id. parent 为父评论的 id, parent 为 0 的是顶级评论
|
||||
*
|
||||
* @param oid 就是 aid, 视频的唯一标识
|
||||
* @param pageSize 分页大小, 最大值 50
|
||||
* @param next 下一页的起始楼层(这一层不包含在返回值内), 注意, 翻页是越翻楼层越小的. 如果为 null 则从最后一楼(最新的评论)开始
|
||||
*/
|
||||
@GET("/x/v2/reply/main")
|
||||
fun reply(
|
||||
@Query("mode") mode: Int = 1,
|
||||
@Query("next") next: Long? = null,
|
||||
@Query("oid") oid: Long,
|
||||
@Query("plat") plat: Int? = 2,
|
||||
@Query("ps") pageSize: Int = 20,
|
||||
@Query("type") type: Int = 1
|
||||
): Deferred<Reply>
|
||||
|
||||
/**
|
||||
* 获取一个视频下的评论的子评论
|
||||
*
|
||||
* @param minId 想要请求的子评论(复数)的第一个子评论的 id(子评论默认升序排序), 为 null 时从 0 楼开始
|
||||
* @param oid aid
|
||||
* @param root 根评论的 id
|
||||
* @param size 分页大小
|
||||
*/
|
||||
@GET("/x/v2/reply/reply/cursor")
|
||||
fun childReply(
|
||||
@Query("min_id") minId: Long? = null,
|
||||
@Query("oid") oid: Long,
|
||||
@Query("plat") plat: Int? = 2,
|
||||
@Query("root") root: Long,
|
||||
@Query("size") size: Int = 20,
|
||||
@Query("sort") sort: Int = 0,
|
||||
@Query("type") type: Int = 1
|
||||
): Deferred<ChildReply>
|
||||
|
||||
/**
|
||||
* 查看 "对话列表"
|
||||
* 当一个子评论中有多组人在互相 at 时, 旁边就会有一个按钮 "查看对话", 将启动一个 dialog 展示内容
|
||||
* parentId 与 rootId 在请求子评论列表时取得
|
||||
*
|
||||
* @param dialog "查看对话" 按钮所在的评论所 at 的那条评论的 id, 即 parentId
|
||||
* @param minFloor 最小楼层, 翻页参数
|
||||
* @param oid aid
|
||||
* @param root "查看对话" 按钮所在的根评论 id, 即 rootId
|
||||
* @param size 分页大小
|
||||
*
|
||||
* @see childReply
|
||||
*/
|
||||
@GET("/x/v2/reply/dialog/cursor")
|
||||
fun chatList(
|
||||
@Query("dialog") dialog: Long,
|
||||
@Query("min_floor") minFloor: Long? = null,
|
||||
@Query("oid") oid: Long,
|
||||
@Query("plat") plat: Int? = 2,
|
||||
@Query("root") root: Long,
|
||||
@Query("size") size: Int = 20,
|
||||
@Query("type") type: Int = 1
|
||||
): Deferred<ChatList>
|
||||
|
||||
/**
|
||||
* 获得一个番剧的分季信息(生成番剧页面所需的信息), 包含当前选择的季的分集信息
|
||||
* seasonId 或 episodeId 必须有一个, 如果用 episodeId 将跳转到对应的 season 的页面
|
||||
* 返回值中, 每个 episode 都有 aid 和 cid
|
||||
*
|
||||
* @param seasonId 季的唯一标识
|
||||
* @param episodeId 集的唯一标识
|
||||
*/
|
||||
@GET("/pgc/view/app/season")
|
||||
fun season(
|
||||
@Query("season_id") seasonId: Long? = null,
|
||||
@Query("ep_id") episodeId: Long? = null,
|
||||
@Query("track_path") trackPath: Int? = null
|
||||
): Deferred<Season>
|
||||
|
||||
/**
|
||||
* 番剧页面下方的推荐(对当前季进行推荐)
|
||||
* 返回值中的 relates 是 "相关推荐"(广告), season 是 "更多推荐"(其他番, 目标为季)
|
||||
*
|
||||
* @param seasonId 季的唯一标识
|
||||
*/
|
||||
@GET("/pgc/season/app/related/recommend")
|
||||
fun recommend(@Query("season_id") seasonId: Long): Deferred<Recommend>
|
||||
|
||||
/**
|
||||
* 我的追番动态(追番页面上方的那一条 "我的追番")
|
||||
* 首页 -> 追番 -> 我的追番
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/pgc/app/page/bangumi/mine")
|
||||
fun myBangumiNews(
|
||||
@Query("fnval") fnval: Int = 16,
|
||||
@Query("fnver") fnver: Int = 0
|
||||
): Deferred<MyBangumiNews>
|
||||
|
||||
/**
|
||||
* 追番页面(客户端用这里面的数据来生成追番页面)
|
||||
* 每个模块(module)的数据(item)全部超过三个.
|
||||
* 每个板块下面的 换一换 按钮并不重新请求数据, 而是从每个模块的数据里选出另一批
|
||||
* 首页 -> 追番
|
||||
*
|
||||
* @param pgcHomeTimelineABTest 与 A/B Test 有关, 不明确其值含义, 有可能使得返回内容不一样
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/pgc/app/page/bangumi")
|
||||
fun bangumiPage(
|
||||
@Query("fnval") fnval: Int = 16,
|
||||
@Query("fnver") fnver: Int = 0,
|
||||
@Query("pgc_home_timeline_abtest") pgcHomeTimelineABTest: Int? = 13
|
||||
): Deferred<BangumiPage>
|
||||
|
||||
/**
|
||||
* 获得更多 "编辑推荐"
|
||||
* 首页 -> 追番 -> (下拉)
|
||||
*
|
||||
* @param cursor 表示时间(ms), 但是可能是科学计数法. 每次请求所用的 cursor 在上一次的返回值里的最后一个 item 里. 第一次请求所用的 cursor 在追番页面的返回值的最后.
|
||||
* @param size 分页大小
|
||||
* @param wid 不明确, 有可能是一些 padding, margin, 用于计算位置
|
||||
*
|
||||
* @see bangumiPage
|
||||
*/
|
||||
fun bangumiMore(
|
||||
@Query("cursor") cursor: String,
|
||||
@Query("size") size: Int = 10,
|
||||
@Query("wid") wid: String? = "78,79,80,81,59"
|
||||
): Deferred<BangumiMore>
|
||||
|
||||
/**
|
||||
* 发送评论
|
||||
* 如果发送根评论则 root 和 parent 为 null
|
||||
* 如果发送子评论则 root 和 parent 均为根评论的 id
|
||||
* 如果在子评论中 at 别人(即对子评论进行评论), 那么 root 为所属根评论的 id, parent 为所 at 的那个评论的 id
|
||||
* at 别人时, 评论的内容必须符合以下格式 "回复 @$username :$message"
|
||||
*
|
||||
* @param message 发送的内容
|
||||
* @param oid aid
|
||||
* @param parent 父评论 id
|
||||
* @param root 根评论 id
|
||||
*/
|
||||
@POST("/x/v2/reply/add")
|
||||
@FormUrlEncoded
|
||||
fun sendReply(
|
||||
@Field("from") from: Int? = null,
|
||||
@Field("message") message: String,
|
||||
@Field("oid") oid: Long,
|
||||
@Field("parent") parent: Long? = null,
|
||||
@Field("plat") plat: Int = 2,
|
||||
@Field("root") root: Long? = null,
|
||||
@Field("type") type: Int = 1
|
||||
): Deferred<SendReplyResponse>
|
||||
|
||||
/**
|
||||
* 点赞(评论)
|
||||
*
|
||||
* @param action 为 1 时表示点赞, 0 表示取消点赞
|
||||
* @param oid aid
|
||||
* @param replyId 评论的 ID
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@POST("/x/v2/reply/action")
|
||||
@FormUrlEncoded
|
||||
fun likeReply(
|
||||
@Field("action") action: Int,
|
||||
@Field("oid") oid: Long,
|
||||
@Field("rpid") replyId: Long,
|
||||
@Field("type") type: Int = 1
|
||||
): Deferred<CommonResponse>
|
||||
|
||||
/**
|
||||
* 不喜欢(评论)
|
||||
*
|
||||
* @param action 为 1 时表示不喜欢, 为 0 时表示取消不喜欢
|
||||
* @param oid aid
|
||||
* @param replyId 评论的 ID
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@POST("/x/v2/reply/hate")
|
||||
@FormUrlEncoded
|
||||
fun dislikeReply(
|
||||
@Field("action") action: Int,
|
||||
@Field("oid") oid: Long,
|
||||
@Field("rpid") replyId: Long,
|
||||
@Field("type") type: Int = 1
|
||||
): Deferred<CommonResponse>
|
||||
|
||||
/**
|
||||
* 查看视频的删除日志
|
||||
* 这个 API 看起来有翻页, 其实没有页
|
||||
* 视频 -> 评论 -> 右上角三个点 ->查看删除日志
|
||||
*
|
||||
* @return 有$replyCount条评论因$reportCount次举报已被管理员移除
|
||||
*/
|
||||
@GET("/x/v2/reply/log")
|
||||
fun deleteLog(
|
||||
@Query("oid") oid: Long,
|
||||
@Query("pn") pageNumber: Int = 1,
|
||||
@Query("ps") pageSize: Int = 20,
|
||||
@Query("type") type: Int = 1
|
||||
): Deferred<DeleteLog>
|
||||
|
||||
/**
|
||||
* 改变与某用户的关系(关注, 取消关注)
|
||||
*
|
||||
* @param action 动作类型(1: 关注;2: 取消关注)
|
||||
* @param followId 操作对象的 ID(例如欲关注的那个用户的 ID)
|
||||
* @param reSrc 不明确
|
||||
*/
|
||||
@POST("/x/relation/modify")
|
||||
@FormUrlEncoded
|
||||
fun modifyRelation(
|
||||
@Field("act") action: Int,
|
||||
@Field("fid") followId: Long,
|
||||
@Field("re_src") reSrc: Int? = 32
|
||||
): Deferred<CommonResponse>
|
||||
|
||||
/**
|
||||
* 查看关注分组
|
||||
* 默认分组永远是 0
|
||||
*/
|
||||
@GET("/x/relation/tag/m/tags")
|
||||
fun relationTags(): Deferred<RelationTags>
|
||||
|
||||
/**
|
||||
* 创建关注分组
|
||||
*
|
||||
* @param tag 不能包含绝大部分符号
|
||||
*/
|
||||
@POST("/x/relation/tag/create")
|
||||
@FormUrlEncoded
|
||||
fun createRelationTag(@Field("tag") tag: String): Deferred<CreateRelationTagResponse>
|
||||
|
||||
/**
|
||||
* 设置分组(对某个用户的关注的分组)
|
||||
* 用户 -> 关注 -> 设置分组
|
||||
*
|
||||
* @param followIds 关注的人(不明确能不能有多个)
|
||||
* @param tagIds 分组 id. 可以有多个, 用逗号隔开, 例如 "-10,110641"
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@POST("/x/relation/tags/addUsers")
|
||||
@FormUrlEncoded
|
||||
fun relationAddUsers(
|
||||
@Field("fids") followIds: String,
|
||||
@Field("tagids") tagIds: String
|
||||
): Deferred<CommonResponse>
|
||||
|
||||
/**
|
||||
* 收藏文章
|
||||
*
|
||||
* @param id 文章的 id
|
||||
*/
|
||||
@POST("/x/article/favorites/add")
|
||||
@FormUrlEncoded
|
||||
fun addFavoriteArticle(@Field("id") id: Long): Deferred<CommonResponse>
|
||||
|
||||
/**
|
||||
* 点赞(文章)
|
||||
*
|
||||
* @param id 文章的 id
|
||||
* @param type 操作类型, 1 为点赞, 2 为取消点赞
|
||||
*/
|
||||
@POST("/x/article/like")
|
||||
@FormUrlEncoded
|
||||
fun articleLike(@Field("id") id: Long, @Field("type") type: Int): Deferred<CommonResponse>
|
||||
|
||||
/**
|
||||
* 查看收藏夹分组
|
||||
*
|
||||
* @param aid 视频的唯一标识, 用于判断是否已经将当前视频加入收藏夹
|
||||
* @param vmId 用户 id
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/x/v2/fav/folder")
|
||||
fun favoriteFolder(
|
||||
@Query("aid") aid: Long,
|
||||
@Query("vmid") vmId: Long
|
||||
): Deferred<FavoriteFolder>
|
||||
|
||||
/**
|
||||
* 创建收藏夹
|
||||
*
|
||||
* @param name 收藏夹名
|
||||
* @param public 是否公开, 0 为公开
|
||||
*/
|
||||
@POST("/x/v2/fav/folder/add")
|
||||
@FormUrlEncoded
|
||||
fun createFavoriteFolder(
|
||||
@Field("name") name: String,
|
||||
@Field("public") public: Int = 0
|
||||
): Deferred<CreateFavoriteFolderResponse>
|
||||
|
||||
/**
|
||||
* 收藏视频
|
||||
*
|
||||
* @param fid 收藏夹的 id, 可以有多个, 用逗号隔开. 例如 795158,3326376
|
||||
*/
|
||||
@POST("/x/v2/fav/video/add")
|
||||
@FormUrlEncoded
|
||||
fun addFavoriteVideo(
|
||||
@Field("aid") aid: Long,
|
||||
@Field("fid") fid: String,
|
||||
@Field("from") from: Int? = null
|
||||
): Deferred<CommonResponse>
|
||||
|
||||
/**
|
||||
* 取消收藏视频
|
||||
*
|
||||
* @param fid 收藏夹的 id, 可以有多个, 同上
|
||||
*
|
||||
* @see addFavoriteVideo
|
||||
*/
|
||||
@POST("/x/v2/fav/video/del")
|
||||
@FormUrlEncoded
|
||||
fun deleteFavoriteVideo(
|
||||
@Field("aid") aid: Long,
|
||||
@Field("fid") fid: String
|
||||
): Deferred<CommonResponse>
|
||||
|
||||
/**
|
||||
* 发送弹幕(视频, 番剧)
|
||||
*
|
||||
* @param oid cid
|
||||
* @param random 9 位的随机数字
|
||||
* @param progress 播放器时间(ms)
|
||||
*/
|
||||
@POST("/x/v2/dm/post")
|
||||
@FormUrlEncoded
|
||||
@Headers(Header.FORCE_QUERY)
|
||||
fun sendDanmaku(
|
||||
@Query("aid") aid: Long,
|
||||
@Query("oid") oid: Long,
|
||||
@Field("pool") pool: Int = 0,
|
||||
@Field("rnd") random: Int = (100000000..999999999).random(),
|
||||
@Field("oid") oidInBody: Long,
|
||||
@Field("fontsize") fontSize: Int = 25,
|
||||
@Field("msg") message: String,
|
||||
@Field("mode") mode: Int = 1,
|
||||
@Field("progress") progress: Long,
|
||||
@Field("color") color: Int = 16777215,
|
||||
@Field("plat") plat: Int = 2,
|
||||
@Field("screen_state") screenState: Int = 0,
|
||||
@Field("from") from: Int? = null,
|
||||
@Field("type") type: Int = 1
|
||||
): Deferred<SendDanmakuResponse>
|
||||
|
||||
/**
|
||||
* 发送弹幕的快捷方式
|
||||
*/
|
||||
@JvmDefault
|
||||
fun sendDanmaku(aid: Long, cid: Long, progress: Long, message: String) =
|
||||
sendDanmaku(aid = aid, oid = cid, oidInBody = cid, progress = progress, message = message)
|
||||
|
||||
/**
|
||||
* 获取文章分类列表
|
||||
*/
|
||||
@GET("/x/article/categories")
|
||||
fun articleCategories(): Deferred<ArticleCategories>
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.hiczp.bilibili.api.main.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ArticleCategories(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: List<Category>,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Category(
|
||||
@SerializedName("children")
|
||||
var children: List<Category>,
|
||||
@SerializedName("id")
|
||||
var id: Int, // 17
|
||||
@SerializedName("name")
|
||||
var name: String, // 科技
|
||||
@SerializedName("parent_id")
|
||||
var parentId: Int // 0
|
||||
)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.hiczp.bilibili.api.main.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class BangumiMore(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("message")
|
||||
var message: String, // success
|
||||
@SerializedName("result")
|
||||
var result: List<Result>
|
||||
) {
|
||||
data class Result(
|
||||
@SerializedName("cover")
|
||||
var cover: String, // http://i0.hdslb.com/bfs/bangumi/5bac9515a50c880e55a772c194241ff9943e0004.png
|
||||
/**
|
||||
* cursor 的值有可能是科学计数法, 例如 1.550210400638E12
|
||||
* 如果不是最后一个 item 将没有这个字段
|
||||
*/
|
||||
@SerializedName("cursor")
|
||||
var cursor: String?, // 1548172800112.0
|
||||
@SerializedName("desc")
|
||||
var desc: String, // 正在就读白凰女学院3年级的加藤茉莉香,是个拥有“私掠船免状”的合法宇宙海贼。她不仅是学生、宇宙艇部的部长、咖啡馆的服务员,还是宇宙海贼船·弁天丸的船长,每天都过着繁忙而充实的生活。某天,正在豪华客船上开展工作的茉莉香,在乘客名单中发现了拥有银河通行证的少年·无限彼方的名字……。少年与海贼的亚空冒险就此展开!
|
||||
@SerializedName("id")
|
||||
var id: Int, // 33409
|
||||
@SerializedName("is_new")
|
||||
var isNew: Int, // 0
|
||||
@SerializedName("link")
|
||||
var link: String, // https://www.bilibili.com/read/cv1831506
|
||||
@SerializedName("link_type")
|
||||
var linkType: Int, // 4
|
||||
@SerializedName("link_value")
|
||||
var linkValue: Int, // 0
|
||||
@SerializedName("pub_time")
|
||||
var pubTime: String, // 2019-01-23 00:00:00
|
||||
@SerializedName("simg")
|
||||
var simg: String,
|
||||
@SerializedName("title")
|
||||
var title: String, // 化身为刃,除魔四方——《多罗罗》
|
||||
@SerializedName("wid")
|
||||
var wid: Int // 81
|
||||
)
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package com.hiczp.bilibili.api.main.model
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class BangumiPage(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("message")
|
||||
var message: String, // success
|
||||
@SerializedName("result")
|
||||
var result: Result
|
||||
) {
|
||||
data class Result(
|
||||
@SerializedName("modules")
|
||||
var modules: List<Module>,
|
||||
@SerializedName("regions")
|
||||
var regions: List<Region>
|
||||
) {
|
||||
data class Module(
|
||||
@SerializedName("attr")
|
||||
var attr: Attr,
|
||||
@SerializedName("headers")
|
||||
var headers: List<JsonElement>,
|
||||
@SerializedName("items")
|
||||
var items: List<Item>,
|
||||
@SerializedName("module_id")
|
||||
var moduleId: Int, // 6
|
||||
@SerializedName("size")
|
||||
var size: Int, // 10
|
||||
@SerializedName("style")
|
||||
var style: String, // fall
|
||||
@SerializedName("title")
|
||||
var title: String, // 编辑推荐
|
||||
@SerializedName("wid")
|
||||
var wid: List<Int>
|
||||
) {
|
||||
data class Item(
|
||||
@SerializedName("badge")
|
||||
var badge: String, // NEW
|
||||
@SerializedName("badge_type")
|
||||
var badgeType: Int, // 0
|
||||
@SerializedName("cover")
|
||||
var cover: String, // http://i0.hdslb.com/bfs/bangumi/57e00f9995459ab0cf40800358ee7f3b392b38a4.jpg
|
||||
@SerializedName("cursor")
|
||||
var cursor: String, // 1.55021040036E12
|
||||
@SerializedName("desc")
|
||||
var desc: String, // 明明是最差劲的相遇,但雏却不知何时开始无法停止心动。雏被初中时的学长·恋雪所吸引,决定和他进入同所高中而拼命学习。并且,和青梅竹马的虎太朗一同进入了樱丘高中。曾经单调乏味的恋雪,为了自己单相思的对象,而在假日结束后改换了形象,变得受欢迎起来。在这种状况下,雏决定要「告白」,但是——!?
|
||||
@SerializedName("is_new")
|
||||
var isNew: Int, // 0
|
||||
@SerializedName("item_id")
|
||||
var itemid: Long, // 34265
|
||||
@SerializedName("link")
|
||||
var link: String, // https://www.bilibili.com/blackboard/topic/activity-dm4qK4-BI.html
|
||||
@SerializedName("title")
|
||||
var title: String, // 【资讯档】2019年第7周
|
||||
@SerializedName("wid")
|
||||
var wid: Int // 78
|
||||
)
|
||||
|
||||
data class Attr(
|
||||
@SerializedName("follow")
|
||||
var follow: Int, // 0
|
||||
@SerializedName("header")
|
||||
var header: Int, // 1
|
||||
@SerializedName("random")
|
||||
var random: Int // 0
|
||||
)
|
||||
}
|
||||
|
||||
data class Region(
|
||||
@SerializedName("icon")
|
||||
var icon: String, // http://i0.hdslb.com/bfs/bangumi/3b66adc7339e62d469ea5b89a45c74e14e3ae831.png
|
||||
@SerializedName("title")
|
||||
var title: String, // 点评
|
||||
@SerializedName("url")
|
||||
var url: String // bilibili://pgc/review/index
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
package com.hiczp.bilibili.api.main.model
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ChatList(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("config")
|
||||
var config: Config,
|
||||
@SerializedName("cursor")
|
||||
var cursor: Cursor,
|
||||
@SerializedName("dialog")
|
||||
var dialog: Dialog,
|
||||
@SerializedName("replies")
|
||||
var replies: List<Reply>
|
||||
) {
|
||||
data class Reply(
|
||||
@SerializedName("action")
|
||||
var action: Int, // 0
|
||||
@SerializedName("assist")
|
||||
var assist: Int, // 0
|
||||
@SerializedName("attr")
|
||||
var attr: Int, // 8
|
||||
@SerializedName("content")
|
||||
var content: Content,
|
||||
@SerializedName("count")
|
||||
var count: Int, // 0
|
||||
@SerializedName("ctime")
|
||||
var ctime: Int, // 1541824116
|
||||
@SerializedName("dialog")
|
||||
var dialog: Long, // 1136351035
|
||||
@SerializedName("dialog_str")
|
||||
var dialogStr: String,
|
||||
@SerializedName("fansgrade")
|
||||
var fansgrade: Int, // 0
|
||||
@SerializedName("floor")
|
||||
var floor: Int, // 172
|
||||
@SerializedName("folder")
|
||||
var folder: Folder,
|
||||
@SerializedName("like")
|
||||
var like: Int, // 0
|
||||
@SerializedName("member")
|
||||
var member: Member,
|
||||
@SerializedName("mid")
|
||||
var mid: Long, // 161745277
|
||||
@SerializedName("oid")
|
||||
var oid: Long, // 34175504
|
||||
@SerializedName("parent")
|
||||
var parent: Int, // 1136656601
|
||||
@SerializedName("parent_str")
|
||||
var parentStr: String, // 1136656601
|
||||
@SerializedName("rcount")
|
||||
var rcount: Int, // 0
|
||||
@SerializedName("replies")
|
||||
var replies: JsonElement?, // null
|
||||
@SerializedName("root")
|
||||
var root: Long, // 1136310360
|
||||
@SerializedName("root_str")
|
||||
var rootStr: String, // 1136310360
|
||||
@SerializedName("rpid")
|
||||
var rpid: Long, // 1175989845
|
||||
@SerializedName("rpid_str")
|
||||
var rpidStr: String, // 1175989845
|
||||
@SerializedName("state")
|
||||
var state: Int, // 2
|
||||
@SerializedName("type")
|
||||
var type: Int, // 1
|
||||
@SerializedName("up_action")
|
||||
var upAction: UpAction
|
||||
) {
|
||||
data class UpAction(
|
||||
@SerializedName("like")
|
||||
var like: Boolean, // false
|
||||
@SerializedName("reply")
|
||||
var reply: Boolean // false
|
||||
)
|
||||
|
||||
data class Member(
|
||||
@SerializedName("DisplayRank")
|
||||
var displayRank: String, // 0
|
||||
@SerializedName("avatar")
|
||||
var avatar: String, // http://static.hdslb.com/images/member/noface.gif
|
||||
@SerializedName("fans_detail")
|
||||
var fansDetail: JsonElement?, // null
|
||||
@SerializedName("following")
|
||||
var following: Int, // 0
|
||||
@SerializedName("level_info")
|
||||
var levelInfo: LevelInfo,
|
||||
@SerializedName("mid")
|
||||
var mid: String, // 161745277
|
||||
@SerializedName("nameplate")
|
||||
var nameplate: Nameplate,
|
||||
@SerializedName("official_verify")
|
||||
var officialVerify: OfficialVerify,
|
||||
@SerializedName("pendant")
|
||||
var pendant: Pendant,
|
||||
@SerializedName("rank")
|
||||
var rank: String, // 10000
|
||||
@SerializedName("sex")
|
||||
var sex: String, // 保密
|
||||
@SerializedName("sign")
|
||||
var sign: String,
|
||||
@SerializedName("uname")
|
||||
var uname: String, // vo6869
|
||||
@SerializedName("vip")
|
||||
var vip: Vip
|
||||
) {
|
||||
data class Pendant(
|
||||
@SerializedName("expire")
|
||||
var expire: Long, // 0
|
||||
@SerializedName("image")
|
||||
var image: String,
|
||||
@SerializedName("name")
|
||||
var name: String,
|
||||
@SerializedName("pid")
|
||||
var pid: Int // 0
|
||||
)
|
||||
|
||||
data class Nameplate(
|
||||
@SerializedName("condition")
|
||||
var condition: String,
|
||||
@SerializedName("image")
|
||||
var image: String,
|
||||
@SerializedName("image_small")
|
||||
var imageSmall: String,
|
||||
@SerializedName("level")
|
||||
var level: String,
|
||||
@SerializedName("name")
|
||||
var name: String,
|
||||
@SerializedName("nid")
|
||||
var nid: Int // 0
|
||||
)
|
||||
|
||||
data class OfficialVerify(
|
||||
@SerializedName("desc")
|
||||
var desc: String,
|
||||
@SerializedName("type")
|
||||
var type: Int // -1
|
||||
)
|
||||
|
||||
data class Vip(
|
||||
@SerializedName("accessStatus")
|
||||
var accessStatus: Int, // 0
|
||||
@SerializedName("dueRemark")
|
||||
var dueRemark: String,
|
||||
@SerializedName("vipDueDate")
|
||||
var vipDueDate: Long, // 1544371200000
|
||||
@SerializedName("vipStatus")
|
||||
var vipStatus: Int, // 0
|
||||
@SerializedName("vipStatusWarn")
|
||||
var vipStatusWarn: String,
|
||||
@SerializedName("vipType")
|
||||
var vipType: Int // 1
|
||||
)
|
||||
|
||||
data class LevelInfo(
|
||||
@SerializedName("current_exp")
|
||||
var currentExp: Int, // 0
|
||||
@SerializedName("current_level")
|
||||
var currentLevel: Int, // 2
|
||||
@SerializedName("current_min")
|
||||
var currentMin: Int, // 0
|
||||
@SerializedName("next_exp")
|
||||
var nextExp: Int // 0
|
||||
)
|
||||
}
|
||||
|
||||
data class Content(
|
||||
@SerializedName("device")
|
||||
var device: String, // phone
|
||||
@SerializedName("members")
|
||||
var members: List<JsonElement>,
|
||||
@SerializedName("message")
|
||||
var message: String, // 回复 @***全副武装 :耶酥是佛教徒
|
||||
@SerializedName("plat")
|
||||
var plat: Int // 3
|
||||
)
|
||||
|
||||
data class Folder(
|
||||
@SerializedName("has_folded")
|
||||
var hasFolded: Boolean, // false
|
||||
@SerializedName("is_folded")
|
||||
var isFolded: Boolean, // false
|
||||
@SerializedName("rule")
|
||||
var rule: String
|
||||
)
|
||||
}
|
||||
|
||||
data class Cursor(
|
||||
@SerializedName("max_floor")
|
||||
var maxFloor: Int, // 172
|
||||
@SerializedName("min_floor")
|
||||
var minFloor: Int, // 8
|
||||
@SerializedName("size")
|
||||
var size: Int // 11
|
||||
)
|
||||
|
||||
data class Config(
|
||||
@SerializedName("show_up_flag")
|
||||
var showUpFlag: Boolean, // true
|
||||
@SerializedName("showadmin")
|
||||
var showadmin: Int, // 0
|
||||
@SerializedName("showentry")
|
||||
var showentry: Int, // 0
|
||||
@SerializedName("showfloor")
|
||||
var showfloor: Int, // 1
|
||||
@SerializedName("showtopic")
|
||||
var showtopic: Int // 1
|
||||
)
|
||||
|
||||
data class Dialog(
|
||||
@SerializedName("max_floor")
|
||||
var maxFloor: Int, // 172
|
||||
@SerializedName("min_floor")
|
||||
var minFloor: Int // 8
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,403 +0,0 @@
|
||||
package com.hiczp.bilibili.api.main.model
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ChildReply(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("assist")
|
||||
var assist: Int, // 0
|
||||
@SerializedName("blacklist")
|
||||
var blacklist: Int, // 0
|
||||
@SerializedName("config")
|
||||
var config: Config,
|
||||
@SerializedName("cursor")
|
||||
var cursor: Cursor,
|
||||
@SerializedName("root")
|
||||
var root: Root,
|
||||
@SerializedName("upper")
|
||||
var upper: Upper
|
||||
) {
|
||||
data class Config(
|
||||
@SerializedName("show_up_flag")
|
||||
var showUpFlag: Boolean, // true
|
||||
@SerializedName("showadmin")
|
||||
var showadmin: Int, // 0
|
||||
@SerializedName("showentry")
|
||||
var showentry: Int, // 0
|
||||
@SerializedName("showfloor")
|
||||
var showfloor: Int, // 1
|
||||
@SerializedName("showtopic")
|
||||
var showtopic: Int // 1
|
||||
)
|
||||
|
||||
data class Upper(
|
||||
@SerializedName("mid")
|
||||
var mid: Long // 7584632
|
||||
)
|
||||
|
||||
data class Cursor(
|
||||
@SerializedName("all_count")
|
||||
var allCount: Int, // 2
|
||||
@SerializedName("max_id")
|
||||
var maxId: Int, // 2
|
||||
@SerializedName("min_id")
|
||||
var minId: Int, // 1
|
||||
@SerializedName("size")
|
||||
var size: Int // 2
|
||||
)
|
||||
|
||||
data class Root(
|
||||
@SerializedName("action")
|
||||
var action: Int, // 0
|
||||
@SerializedName("assist")
|
||||
var assist: Int, // 0
|
||||
@SerializedName("attr")
|
||||
var attr: Int, // 0
|
||||
@SerializedName("content")
|
||||
var content: Content,
|
||||
@SerializedName("count")
|
||||
var count: Int, // 2
|
||||
@SerializedName("ctime")
|
||||
var ctime: Int, // 1550681500
|
||||
@SerializedName("dialog")
|
||||
var dialog: Long, // 0
|
||||
@SerializedName("dialog_str")
|
||||
var dialogStr: String,
|
||||
@SerializedName("fansgrade")
|
||||
var fansgrade: Int, // 0
|
||||
@SerializedName("floor")
|
||||
var floor: Int, // 1348
|
||||
@SerializedName("folder")
|
||||
var folder: Folder,
|
||||
@SerializedName("like")
|
||||
var like: Int, // 1
|
||||
@SerializedName("member")
|
||||
var member: Member,
|
||||
@SerializedName("mid")
|
||||
var mid: Long, // 14363383
|
||||
@SerializedName("oid")
|
||||
var oid: Long, // 16622855
|
||||
@SerializedName("parent")
|
||||
var parent: Long, // 0
|
||||
@SerializedName("parent_str")
|
||||
var parentStr: String, // 0
|
||||
@SerializedName("rcount")
|
||||
var rcount: Int, // 2
|
||||
@SerializedName("replies")
|
||||
var replies: List<Reply>,
|
||||
@SerializedName("root")
|
||||
var root: Long, // 0
|
||||
@SerializedName("root_str")
|
||||
var rootStr: String, // 0
|
||||
@SerializedName("rpid")
|
||||
var rpid: Long, // 1405602348
|
||||
@SerializedName("rpid_str")
|
||||
var rpidStr: String, // 1405602348
|
||||
@SerializedName("state")
|
||||
var state: Int, // 0
|
||||
@SerializedName("type")
|
||||
var type: Int, // 1
|
||||
@SerializedName("up_action")
|
||||
var upAction: UpAction
|
||||
) {
|
||||
data class Folder(
|
||||
@SerializedName("has_folded")
|
||||
var hasFolded: Boolean, // false
|
||||
@SerializedName("is_folded")
|
||||
var isFolded: Boolean, // false
|
||||
@SerializedName("rule")
|
||||
var rule: String // https://www.bilibili.com/blackboard/foldingreply.html
|
||||
)
|
||||
|
||||
data class Reply(
|
||||
@SerializedName("action")
|
||||
var action: Int, // 0
|
||||
@SerializedName("assist")
|
||||
var assist: Int, // 0
|
||||
@SerializedName("attr")
|
||||
var attr: Int, // 0
|
||||
@SerializedName("content")
|
||||
var content: Content,
|
||||
@SerializedName("count")
|
||||
var count: Int, // 0
|
||||
@SerializedName("ctime")
|
||||
var ctime: Int, // 1550682402
|
||||
@SerializedName("dialog")
|
||||
var dialog: Long, // 1405625526
|
||||
@SerializedName("dialog_str")
|
||||
var dialogStr: String,
|
||||
@SerializedName("fansgrade")
|
||||
var fansgrade: Int, // 0
|
||||
@SerializedName("floor")
|
||||
var floor: Int, // 2
|
||||
@SerializedName("folder")
|
||||
var folder: Folder,
|
||||
@SerializedName("like")
|
||||
var like: Int, // 1
|
||||
@SerializedName("member")
|
||||
var member: Member,
|
||||
@SerializedName("mid")
|
||||
var mid: Long, // 14363383
|
||||
@SerializedName("oid")
|
||||
var oid: Long, // 16622855
|
||||
@SerializedName("parent")
|
||||
var parent: Long, // 1405602348
|
||||
@SerializedName("parent_str")
|
||||
var parentStr: String, // 1405602348
|
||||
@SerializedName("rcount")
|
||||
var rcount: Int, // 0
|
||||
@SerializedName("replies")
|
||||
var replies: List<JsonElement>, // []
|
||||
@SerializedName("root")
|
||||
var root: Long, // 1405602348
|
||||
@SerializedName("root_str")
|
||||
var rootStr: String, // 1405602348
|
||||
@SerializedName("rpid")
|
||||
var rpid: Long, // 1405625526
|
||||
@SerializedName("rpid_str")
|
||||
var rpidStr: String, // 1405625526
|
||||
@SerializedName("state")
|
||||
var state: Int, // 0
|
||||
@SerializedName("type")
|
||||
var type: Int, // 1
|
||||
@SerializedName("up_action")
|
||||
var upAction: UpAction
|
||||
) {
|
||||
data class Content(
|
||||
@SerializedName("device")
|
||||
var device: String,
|
||||
@SerializedName("members")
|
||||
var members: List<JsonElement>,
|
||||
@SerializedName("message")
|
||||
var message: String, // 导演:你认为是否有人了解你?像你自己一样了解你?老佛爷:这个问题我很难回答,别人对我的想法已根深蒂固,所以我认为几乎是不可能,我想是如此,即使是我深爱的人。我不想在别人生活中显得真实,我想成为幽灵,现身,然后消失,我也不想面对任何人的真实,因为我不想面对真实的自己,那是我的秘密。别跟我说那些关于孤独的陈词滥调,之于我这种人,孤独是一种胜利,这是场人生战役。像我一样从事创意工作的人,必须独处,让自己重新充电,整日生活在聚光灯前是无法创作的。我还要做许多事,例如阅读,身边有人就无法去做。平时几乎已没时间,但我随时都会想阅读,所以我赞成每人都要该独立生活。将别人当成依靠,对于我这样的人来说很危险,我必须时时刻刻如履薄冰,并在它破裂之前跨出下一步。
|
||||
@SerializedName("plat")
|
||||
var plat: Int // 2
|
||||
)
|
||||
|
||||
data class UpAction(
|
||||
@SerializedName("like")
|
||||
var like: Boolean, // false
|
||||
@SerializedName("reply")
|
||||
var reply: Boolean // false
|
||||
)
|
||||
|
||||
data class Member(
|
||||
@SerializedName("DisplayRank")
|
||||
var displayRank: String, // 0
|
||||
@SerializedName("avatar")
|
||||
var avatar: String, // http://i2.hdslb.com/bfs/face/63f5da7bda813e470cefd465767035efccff747d.jpg
|
||||
@SerializedName("fans_detail")
|
||||
var fansDetail: JsonElement?, // null
|
||||
@SerializedName("following")
|
||||
var following: Int, // 0
|
||||
@SerializedName("level_info")
|
||||
var levelInfo: LevelInfo,
|
||||
@SerializedName("mid")
|
||||
var mid: String, // 14363383
|
||||
@SerializedName("nameplate")
|
||||
var nameplate: Nameplate,
|
||||
@SerializedName("official_verify")
|
||||
var officialVerify: OfficialVerify,
|
||||
@SerializedName("pendant")
|
||||
var pendant: Pendant,
|
||||
@SerializedName("rank")
|
||||
var rank: String, // 10000
|
||||
@SerializedName("sex")
|
||||
var sex: String, // 保密
|
||||
@SerializedName("sign")
|
||||
var sign: String, // - 故事何必听的真切,自在之人掀雨踏天阙。
|
||||
@SerializedName("uname")
|
||||
var uname: String, // 浮生不思量
|
||||
@SerializedName("vip")
|
||||
var vip: Vip
|
||||
) {
|
||||
data class Pendant(
|
||||
@SerializedName("expire")
|
||||
var expire: Long, // 0
|
||||
@SerializedName("image")
|
||||
var image: String,
|
||||
@SerializedName("name")
|
||||
var name: String,
|
||||
@SerializedName("pid")
|
||||
var pid: Int // 0
|
||||
)
|
||||
|
||||
data class Nameplate(
|
||||
@SerializedName("condition")
|
||||
var condition: String,
|
||||
@SerializedName("image")
|
||||
var image: String,
|
||||
@SerializedName("image_small")
|
||||
var imageSmall: String,
|
||||
@SerializedName("level")
|
||||
var level: String,
|
||||
@SerializedName("name")
|
||||
var name: String,
|
||||
@SerializedName("nid")
|
||||
var nid: Int // 0
|
||||
)
|
||||
|
||||
data class OfficialVerify(
|
||||
@SerializedName("desc")
|
||||
var desc: String,
|
||||
@SerializedName("type")
|
||||
var type: Int // -1
|
||||
)
|
||||
|
||||
data class Vip(
|
||||
@SerializedName("accessStatus")
|
||||
var accessStatus: Int, // 0
|
||||
@SerializedName("dueRemark")
|
||||
var dueRemark: String,
|
||||
@SerializedName("vipDueDate")
|
||||
var vipDueDate: Long, // 1515686400000
|
||||
@SerializedName("vipStatus")
|
||||
var vipStatus: Int, // 0
|
||||
@SerializedName("vipStatusWarn")
|
||||
var vipStatusWarn: String,
|
||||
@SerializedName("vipType")
|
||||
var vipType: Int // 1
|
||||
)
|
||||
|
||||
data class LevelInfo(
|
||||
@SerializedName("current_exp")
|
||||
var currentExp: Int, // 0
|
||||
@SerializedName("current_level")
|
||||
var currentLevel: Int, // 5
|
||||
@SerializedName("current_min")
|
||||
var currentMin: Int, // 0
|
||||
@SerializedName("next_exp")
|
||||
var nextExp: Int // 0
|
||||
)
|
||||
}
|
||||
|
||||
data class Folder(
|
||||
@SerializedName("has_folded")
|
||||
var hasFolded: Boolean, // false
|
||||
@SerializedName("is_folded")
|
||||
var isFolded: Boolean, // false
|
||||
@SerializedName("rule")
|
||||
var rule: String
|
||||
)
|
||||
}
|
||||
|
||||
data class Content(
|
||||
@SerializedName("device")
|
||||
var device: String,
|
||||
@SerializedName("members")
|
||||
var members: List<JsonElement>,
|
||||
@SerializedName("message")
|
||||
var message: String, // 唉有点不敢相信…R.I.P……走好走好
|
||||
@SerializedName("plat")
|
||||
var plat: Int // 2
|
||||
)
|
||||
|
||||
data class UpAction(
|
||||
@SerializedName("like")
|
||||
var like: Boolean, // false
|
||||
@SerializedName("reply")
|
||||
var reply: Boolean // false
|
||||
)
|
||||
|
||||
data class Member(
|
||||
@SerializedName("DisplayRank")
|
||||
var displayRank: String, // 0
|
||||
@SerializedName("avatar")
|
||||
var avatar: String, // http://i2.hdslb.com/bfs/face/63f5da7bda813e470cefd465767035efccff747d.jpg
|
||||
@SerializedName("fans_detail")
|
||||
var fansDetail: JsonElement?, // null
|
||||
@SerializedName("following")
|
||||
var following: Int, // 0
|
||||
@SerializedName("level_info")
|
||||
var levelInfo: LevelInfo,
|
||||
@SerializedName("mid")
|
||||
var mid: String, // 14363383
|
||||
@SerializedName("nameplate")
|
||||
var nameplate: Nameplate,
|
||||
@SerializedName("official_verify")
|
||||
var officialVerify: OfficialVerify,
|
||||
@SerializedName("pendant")
|
||||
var pendant: Pendant,
|
||||
@SerializedName("rank")
|
||||
var rank: String, // 10000
|
||||
@SerializedName("sex")
|
||||
var sex: String, // 保密
|
||||
@SerializedName("sign")
|
||||
var sign: String, // - 故事何必听的真切,自在之人掀雨踏天阙。
|
||||
@SerializedName("uname")
|
||||
var uname: String, // 浮生不思量
|
||||
@SerializedName("vip")
|
||||
var vip: Vip
|
||||
) {
|
||||
data class Pendant(
|
||||
@SerializedName("expire")
|
||||
var expire: Long, // 0
|
||||
@SerializedName("image")
|
||||
var image: String,
|
||||
@SerializedName("name")
|
||||
var name: String,
|
||||
@SerializedName("pid")
|
||||
var pid: Int // 0
|
||||
)
|
||||
|
||||
data class Nameplate(
|
||||
@SerializedName("condition")
|
||||
var condition: String,
|
||||
@SerializedName("image")
|
||||
var image: String,
|
||||
@SerializedName("image_small")
|
||||
var imageSmall: String,
|
||||
@SerializedName("level")
|
||||
var level: String,
|
||||
@SerializedName("name")
|
||||
var name: String,
|
||||
@SerializedName("nid")
|
||||
var nid: Int // 0
|
||||
)
|
||||
|
||||
data class OfficialVerify(
|
||||
@SerializedName("desc")
|
||||
var desc: String,
|
||||
@SerializedName("type")
|
||||
var type: Int // -1
|
||||
)
|
||||
|
||||
data class Vip(
|
||||
@SerializedName("accessStatus")
|
||||
var accessStatus: Int, // 0
|
||||
@SerializedName("dueRemark")
|
||||
var dueRemark: String,
|
||||
@SerializedName("vipDueDate")
|
||||
var vipDueDate: Long, // 1515686400000
|
||||
@SerializedName("vipStatus")
|
||||
var vipStatus: Int, // 0
|
||||
@SerializedName("vipStatusWarn")
|
||||
var vipStatusWarn: String,
|
||||
@SerializedName("vipType")
|
||||
var vipType: Int // 1
|
||||
)
|
||||
|
||||
data class LevelInfo(
|
||||
@SerializedName("current_exp")
|
||||
var currentExp: Int, // 0
|
||||
@SerializedName("current_level")
|
||||
var currentLevel: Int, // 5
|
||||
@SerializedName("current_min")
|
||||
var currentMin: Int, // 0
|
||||
@SerializedName("next_exp")
|
||||
var nextExp: Int // 0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.hiczp.bilibili.api.main.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class CreateFavoriteFolderResponse(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("fid")
|
||||
var fid: Long // 3326376
|
||||
)
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.hiczp.bilibili.api.main.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class CreateRelationTagResponse(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
@Suppress("SpellCheckingInspection")
|
||||
data class Data(
|
||||
@SerializedName("tagid")
|
||||
var tagid: Int // 110641
|
||||
)
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.hiczp.bilibili.api.main.model
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class DeleteLog(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("logs")
|
||||
var logs: JsonElement?, // null
|
||||
@SerializedName("page")
|
||||
var page: Page,
|
||||
@SerializedName("reply_count")
|
||||
var replyCount: Int, // 11
|
||||
@SerializedName("report_count")
|
||||
var reportCount: Int // 28
|
||||
) {
|
||||
data class Page(
|
||||
@SerializedName("num")
|
||||
var num: Int, // 1
|
||||
@SerializedName("pages")
|
||||
var pages: Int, // 0
|
||||
@SerializedName("size")
|
||||
var size: Int, // 20
|
||||
@SerializedName("total")
|
||||
var total: Int // 0
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.hiczp.bilibili.api.main.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class FavoriteFolder(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: List<Data>,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("atten_count")
|
||||
var attenCount: Int, // 0
|
||||
@SerializedName("cover")
|
||||
var cover: List<Cover>,
|
||||
@SerializedName("ctime")
|
||||
var ctime: Long, // 1451133174
|
||||
@SerializedName("cur_count")
|
||||
var curCount: Int, // 1
|
||||
@SerializedName("favoured")
|
||||
var favoured: Int, // 0
|
||||
@SerializedName("fid")
|
||||
var fid: Long, // 795158
|
||||
@SerializedName("max_count")
|
||||
var maxCount: Int, // 50000
|
||||
@SerializedName("media_id")
|
||||
var mediaId: Long, // 79515830
|
||||
@SerializedName("mid")
|
||||
var mid: Long, // 20293030
|
||||
@SerializedName("mtime")
|
||||
var mtime: Long, // 1544629663
|
||||
@SerializedName("name")
|
||||
var name: String, // 默认收藏夹
|
||||
@SerializedName("state")
|
||||
var state: Int // 0
|
||||
) {
|
||||
data class Cover(
|
||||
@SerializedName("aid")
|
||||
var aid: Long, // 9498716
|
||||
@SerializedName("pic")
|
||||
var pic: String, // http://i2.hdslb.com/bfs/archive/3536b8de71da4dd7bf01200db1e6c710b5f4aa0e.png
|
||||
@SerializedName("type")
|
||||
var type: Int // 2
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.hiczp.bilibili.api.main.model
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class MyBangumiNews(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("message")
|
||||
var message: String, // success
|
||||
@SerializedName("result")
|
||||
var result: Result
|
||||
) {
|
||||
data class Result(
|
||||
@SerializedName("delay")
|
||||
var delay: List<JsonElement>,
|
||||
@SerializedName("follow")
|
||||
var follow: Int, // 34
|
||||
@SerializedName("follows")
|
||||
var follows: List<Follow>,
|
||||
@SerializedName("follows_type")
|
||||
var followsType: Int, // 1
|
||||
@SerializedName("update")
|
||||
var update: Int // 1
|
||||
) {
|
||||
data class Follow(
|
||||
@SerializedName("badge")
|
||||
var badge: String, // 会员抢先
|
||||
@SerializedName("badge_type")
|
||||
var badgeType: Int, // 0
|
||||
@SerializedName("cover")
|
||||
var cover: String, // http://i0.hdslb.com/bfs/bangumi/f34ff3975c39913af936c133ae60a5891babba08.png
|
||||
@SerializedName("is_finish")
|
||||
var isFinish: Int, // 0
|
||||
@SerializedName("is_started")
|
||||
var isStarted: Int, // 1
|
||||
@SerializedName("new_ep")
|
||||
var newEp: NewEp,
|
||||
/**
|
||||
* 如果 progress 为 null 说明尚未观看
|
||||
*/
|
||||
@SerializedName("progress")
|
||||
var progress: Progress?,
|
||||
@SerializedName("season_id")
|
||||
var seasonId: Int, // 25681
|
||||
@SerializedName("title")
|
||||
var title: String, // JOJO的奇妙冒险 黄金之风
|
||||
@SerializedName("total_count")
|
||||
var totalCount: Int, // 39
|
||||
@SerializedName("url")
|
||||
var url: String // https://www.bilibili.com/bangumi/play/ss25681
|
||||
) {
|
||||
data class NewEp(
|
||||
@SerializedName("cover")
|
||||
var cover: String, // http://i0.hdslb.com/bfs/archive/c3af18bf85040dfacb081db46e033f056318a8f0.jpg
|
||||
@SerializedName("id")
|
||||
var id: Int, // 250631
|
||||
@SerializedName("index_show")
|
||||
var indexShow: String // 更新至第20话
|
||||
)
|
||||
|
||||
data class Progress(
|
||||
@SerializedName("last_ep_desc")
|
||||
var lastEpDesc: String, // 看到第2话
|
||||
@SerializedName("last_ep_id")
|
||||
var lastEpId: Int, // 250837
|
||||
@SerializedName("last_ep_index")
|
||||
var lastEpIndex: String, // 2
|
||||
@SerializedName("last_time")
|
||||
var lastTime: Int // 1377
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package com.hiczp.bilibili.api.main.model
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Recommend(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("message")
|
||||
var message: String, // success
|
||||
@SerializedName("result")
|
||||
var result: Result
|
||||
) {
|
||||
data class Result(
|
||||
@SerializedName("card")
|
||||
var card: JsonElement, // []
|
||||
@SerializedName("relates")
|
||||
var relates: List<Relate>,
|
||||
@SerializedName("season")
|
||||
var season: List<Season>
|
||||
) {
|
||||
data class Season(
|
||||
@SerializedName("badge")
|
||||
var badge: String, // 会员抢先
|
||||
@SerializedName("badge_type")
|
||||
var badgeType: Int, // 0
|
||||
@SerializedName("cover")
|
||||
var cover: String, // http://i0.hdslb.com/bfs/bangumi/3fc16a667502cbff226e585eb660a96a20c7458c.png
|
||||
@SerializedName("from")
|
||||
var from: Int, // 0
|
||||
@SerializedName("new_ep")
|
||||
var newEp: NewEp,
|
||||
@SerializedName("rating")
|
||||
var rating: Rating,
|
||||
@SerializedName("season_id")
|
||||
var seasonId: Int, // 26146
|
||||
@SerializedName("season_type")
|
||||
var seasonType: Int, // 1
|
||||
@SerializedName("stat")
|
||||
var stat: Stat,
|
||||
@SerializedName("title")
|
||||
var title: String, // 多罗罗
|
||||
@SerializedName("url")
|
||||
var url: String // http://www.bilibili.com/bangumi/play/ss26146
|
||||
) {
|
||||
data class NewEp(
|
||||
@SerializedName("cover")
|
||||
var cover: String, // http://i0.hdslb.com/bfs/archive/7dec9d820b82ee57ebde0ba5c186b63e1e728abd.jpg
|
||||
@SerializedName("index_show")
|
||||
var indexShow: String // 更新至第7话
|
||||
)
|
||||
|
||||
data class Rating(
|
||||
@SerializedName("count")
|
||||
var count: Long, // 22916
|
||||
@SerializedName("score")
|
||||
var score: Double // 9.8
|
||||
)
|
||||
|
||||
data class Stat(
|
||||
@SerializedName("danmaku")
|
||||
var danmaku: Int, // 435439
|
||||
@SerializedName("follow")
|
||||
var follow: Int, // 2073877
|
||||
@SerializedName("view")
|
||||
var view: Int // 24884016
|
||||
)
|
||||
}
|
||||
|
||||
data class Relate(
|
||||
@SerializedName("desc1")
|
||||
var desc1: String, // 【萌羽Moeyu】魔法禁书目录御坂美琴易拉罐保温杯
|
||||
@SerializedName("desc2")
|
||||
var desc2: String, // 295
|
||||
@SerializedName("item_id")
|
||||
var itemid: Long, // 10005816
|
||||
@SerializedName("pic")
|
||||
var pic: String, // https://i0.hdslb.com/bfs/mall/mall/c3/f0/c3f029d8221c6ecc96bd1ab321034bc2.jpg
|
||||
@SerializedName("title")
|
||||
var title: String, // 【现货即发】魔法禁书目录正版授权,Moeyu出品。
|
||||
@SerializedName("type")
|
||||
var type: Int, // 1
|
||||
@SerializedName("type_name")
|
||||
var typeName: String, // 商品
|
||||
@SerializedName("url")
|
||||
var url: String // bilibili://mall/web?url=https%3A%2F%2Fmall.bilibili.com%2Fdetail.html%3FitemsId%3D10005816%26msource%3Dfanju_25617_10005816%26noTitleBar%3D1%26loadingShow%3D1
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.hiczp.bilibili.api.main.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class RelationTags(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
/**
|
||||
* all 不是指全部, 而是指 公开关注
|
||||
*/
|
||||
@SerializedName("all")
|
||||
var all: List<Tag>,
|
||||
@SerializedName("default")
|
||||
var default: List<Tag>,
|
||||
@SerializedName("list")
|
||||
var list: List<Tag>,
|
||||
@SerializedName("special")
|
||||
var special: List<Tag>
|
||||
) {
|
||||
@Suppress("SpellCheckingInspection")
|
||||
data class Tag(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 28
|
||||
@SerializedName("name")
|
||||
var name: String, // 公开关注
|
||||
@SerializedName("tagid")
|
||||
var tagid: Int // -1
|
||||
)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,283 +0,0 @@
|
||||
package com.hiczp.bilibili.api.main.model
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Season(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("message")
|
||||
var message: String, // success
|
||||
@SerializedName("result")
|
||||
var result: Result
|
||||
) {
|
||||
data class Result(
|
||||
@SerializedName("cover")
|
||||
var cover: String, // http://i0.hdslb.com/bfs/bangumi/a92892921f3209f7784a954c37467c9869a1d4c1.png
|
||||
@SerializedName("episodes")
|
||||
var episodes: List<Episode>,
|
||||
@SerializedName("evaluate")
|
||||
var evaluate: String, // 位于东京西部的巨大“学园都市”,实施着超能力开发的特殊课程。学生们的能力被给予从“无能力Level 0”到“超能力Level 5”的六阶段评价。高中生上条当麻,由于寄宿在右手中的力量——只要是异能之力...
|
||||
@SerializedName("link")
|
||||
var link: String, // http://www.bilibili.com/bangumi/media/md134912/
|
||||
@SerializedName("media_id")
|
||||
var mimediaId: Long, // 134912
|
||||
@SerializedName("mode")
|
||||
var mode: Int, // 2
|
||||
@SerializedName("new_ep")
|
||||
var newEp: NewEp,
|
||||
@SerializedName("paster")
|
||||
var paster: Paster,
|
||||
@SerializedName("payment")
|
||||
var payment: Payment,
|
||||
@SerializedName("publish")
|
||||
var publish: Publish,
|
||||
@SerializedName("rating")
|
||||
var rating: Rating,
|
||||
@SerializedName("record")
|
||||
var record: String,
|
||||
@SerializedName("rights")
|
||||
var rights: Rights,
|
||||
@SerializedName("season_id")
|
||||
var seasonId: Long, // 25617
|
||||
@SerializedName("season_title")
|
||||
var seasonTitle: String, // 魔法禁书目录 第三季
|
||||
@SerializedName("seasons")
|
||||
var seasons: List<Season>,
|
||||
@SerializedName("section")
|
||||
var section: List<JsonElement>,
|
||||
@SerializedName("series")
|
||||
var series: Series,
|
||||
@SerializedName("share_url")
|
||||
var shareUrl: String, // http://m.bilibili.com/bangumi/play/ss25617
|
||||
@SerializedName("square_cover")
|
||||
var squareCover: String, // http://i0.hdslb.com/bfs/bangumi/91b29251445f9b808e9c30f34019d3ba4f128d6d.jpg
|
||||
@SerializedName("stat")
|
||||
var stat: Stat,
|
||||
@SerializedName("status")
|
||||
var status: Int, // 13
|
||||
@SerializedName("title")
|
||||
var title: String, // 魔法禁书目录 第三季
|
||||
@SerializedName("total")
|
||||
var total: Int, // 0
|
||||
@SerializedName("type")
|
||||
var type: Int, // 1
|
||||
@SerializedName("user_status")
|
||||
var userStatus: UserStatus
|
||||
) {
|
||||
data class Series(
|
||||
@SerializedName("series_id")
|
||||
var seriesId: Int, // 621
|
||||
@SerializedName("series_title")
|
||||
var seriesTitle: String // 魔法禁书目录
|
||||
)
|
||||
|
||||
data class UserStatus(
|
||||
@SerializedName("follow")
|
||||
var follow: Int, // 1
|
||||
@SerializedName("pay")
|
||||
var pay: Int, // 0
|
||||
@SerializedName("progress")
|
||||
var progress: Progress,
|
||||
@SerializedName("review")
|
||||
var review: Review,
|
||||
@SerializedName("sponsor")
|
||||
var sponsor: Int, // 0
|
||||
@SerializedName("vip")
|
||||
var vip: Int, // 0
|
||||
@SerializedName("vip_frozen")
|
||||
var vipFrozen: Int // 0
|
||||
) {
|
||||
data class Progress(
|
||||
@SerializedName("last_ep_id")
|
||||
var lastEpId: Int, // 250436
|
||||
@SerializedName("last_ep_index")
|
||||
var lastEpIndex: String, // 3
|
||||
@SerializedName("last_time")
|
||||
var lastTime: Int // 1405
|
||||
)
|
||||
|
||||
data class Review(
|
||||
@SerializedName("is_open")
|
||||
var isOpen: Int // 0
|
||||
)
|
||||
}
|
||||
|
||||
data class Season(
|
||||
@SerializedName("is_new")
|
||||
var isNew: Int, // 1
|
||||
@SerializedName("season_id")
|
||||
var seasonId: Long, // 25617
|
||||
@SerializedName("season_title")
|
||||
var seasonTitle: String // 第三季
|
||||
)
|
||||
|
||||
data class Episode(
|
||||
@SerializedName("aid")
|
||||
var aid: Int, // 44389470
|
||||
@SerializedName("badge")
|
||||
var badge: String, // 会员
|
||||
@SerializedName("badge_type")
|
||||
var badgeType: Int, // 0
|
||||
@SerializedName("cid")
|
||||
var cid: Int, // 77725026
|
||||
@SerializedName("cover")
|
||||
var cover: String, // http://i0.hdslb.com/bfs/archive/c83ef2a961d8b53d6a30f03e8fb631ea4248fede.jpg
|
||||
@SerializedName("dimension")
|
||||
var dimension: Dimension,
|
||||
@SerializedName("from")
|
||||
var from: String, // bangumi
|
||||
@SerializedName("id")
|
||||
var id: Int, // 250453
|
||||
@SerializedName("long_title")
|
||||
var longTitle: String, // 守护的理由
|
||||
@SerializedName("share_url")
|
||||
var shareUrl: String, // https://m.bilibili.com/bangumi/play/ep250453
|
||||
@SerializedName("status")
|
||||
var status: Int, // 13
|
||||
@SerializedName("title")
|
||||
var title: String, // 20
|
||||
@SerializedName("vid")
|
||||
var vid: String
|
||||
) {
|
||||
data class Dimension(
|
||||
@SerializedName("height")
|
||||
var height: Int, // 1080
|
||||
@SerializedName("rotate")
|
||||
var rotate: Int, // 0
|
||||
@SerializedName("width")
|
||||
var width: Int // 1920
|
||||
)
|
||||
}
|
||||
|
||||
data class Rating(
|
||||
@SerializedName("count")
|
||||
var count: Int, // 32318
|
||||
@SerializedName("score")
|
||||
var score: Double // 7.8
|
||||
)
|
||||
|
||||
data class Rights(
|
||||
@SerializedName("allow_bp")
|
||||
var allowBp: Int, // 0
|
||||
@SerializedName("allow_download")
|
||||
var allowDownload: Int, // 0
|
||||
@SerializedName("allow_review")
|
||||
var allowReview: Int, // 1
|
||||
@SerializedName("area_limit")
|
||||
var areaLimit: Int, // 0
|
||||
@SerializedName("ban_area_show")
|
||||
var banAreaShow: Int, // 1
|
||||
@SerializedName("copyright")
|
||||
var copyright: String, // bilibili
|
||||
@SerializedName("is_preview")
|
||||
var isPreview: Int, // 1
|
||||
@SerializedName("watch_platform")
|
||||
var watchPlatform: Int // 0
|
||||
)
|
||||
|
||||
data class NewEp(
|
||||
@SerializedName("desc")
|
||||
var desc: String, // 连载中, 每周五22:30更新
|
||||
@SerializedName("id")
|
||||
var id: Int, // 250453
|
||||
@SerializedName("is_new")
|
||||
var isNew: Int, // 1
|
||||
@SerializedName("title")
|
||||
var title: String // 20
|
||||
)
|
||||
|
||||
data class Payment(
|
||||
@SerializedName("dialog")
|
||||
var dialog: Dialog,
|
||||
@SerializedName("pay_tip")
|
||||
var payTip: PayTip,
|
||||
@SerializedName("pay_type")
|
||||
var payType: PayType,
|
||||
@SerializedName("price")
|
||||
var price: String, // 0.0
|
||||
@SerializedName("vip_promotion")
|
||||
var vipPromotion: String
|
||||
) {
|
||||
data class Dialog(
|
||||
@SerializedName("btn_right")
|
||||
var btnRight: BtnRight,
|
||||
@SerializedName("desc")
|
||||
var desc: String,
|
||||
@SerializedName("title")
|
||||
var title: String // 开通大会员抢先看
|
||||
) {
|
||||
data class BtnRight(
|
||||
@SerializedName("title")
|
||||
var title: String, // 成为大会员
|
||||
@SerializedName("type")
|
||||
var type: String // vip
|
||||
)
|
||||
}
|
||||
|
||||
data class PayType(
|
||||
@SerializedName("allow_ticket")
|
||||
var allowTicket: Int // 0
|
||||
)
|
||||
|
||||
data class PayTip(
|
||||
@SerializedName("primary")
|
||||
var primary: Primary
|
||||
) {
|
||||
data class Primary(
|
||||
@SerializedName("sub_title")
|
||||
var subTitle: String,
|
||||
@SerializedName("title")
|
||||
var title: String, // 开通大会员抢先看
|
||||
@SerializedName("type")
|
||||
var type: Int, // 1
|
||||
@SerializedName("url")
|
||||
var url: String
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class Stat(
|
||||
@SerializedName("coins")
|
||||
var coins: Long, // 333230
|
||||
@SerializedName("danmakus")
|
||||
var danmakus: Int, // 729836
|
||||
@SerializedName("favorites")
|
||||
var favorites: Int, // 2300833
|
||||
@SerializedName("reply")
|
||||
var reply: Int, // 324057
|
||||
@SerializedName("share")
|
||||
var share: Int, // 18362
|
||||
@SerializedName("views")
|
||||
var views: Int // 41392306
|
||||
)
|
||||
|
||||
data class Publish(
|
||||
@SerializedName("is_finish")
|
||||
var isFinish: Int, // 0
|
||||
@SerializedName("is_started")
|
||||
var isStarted: Int, // 1
|
||||
@SerializedName("pub_time")
|
||||
var pubTime: String, // 2018-10-05 22:30:00
|
||||
@SerializedName("pub_time_show")
|
||||
var pubTimeShow: String, // 10月05日22:30
|
||||
@SerializedName("weekday")
|
||||
var weekday: Int // 0
|
||||
)
|
||||
|
||||
data class Paster(
|
||||
@SerializedName("aid")
|
||||
var aid: Long, // 0
|
||||
@SerializedName("allow_jump")
|
||||
var allowJump: Int, // 0
|
||||
@SerializedName("cid")
|
||||
var cid: Long, // 0
|
||||
@SerializedName("duration")
|
||||
var duration: Int, // 0
|
||||
@SerializedName("type")
|
||||
var type: Int, // 0
|
||||
@SerializedName("url")
|
||||
var url: String
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.hiczp.bilibili.api.main.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class SendDanmakuResponse(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
@Suppress("SpellCheckingInspection")
|
||||
data class Data(
|
||||
@SerializedName("dmid")
|
||||
var dmid: Long // 12699467350278148
|
||||
)
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.hiczp.bilibili.api.main.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class SendReplyResponse(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("dialog")
|
||||
var dialog: Long, // 0
|
||||
@SerializedName("dialog_str")
|
||||
var dialogStr: String, // 0
|
||||
@SerializedName("parent")
|
||||
var parent: Long, // 0
|
||||
@SerializedName("parent_str")
|
||||
var parentStr: String, // 0
|
||||
@SerializedName("root")
|
||||
var root: Long, // 0
|
||||
@SerializedName("root_str")
|
||||
var rootStr: String, // 0
|
||||
@SerializedName("rpid")
|
||||
var rpid: Long, // 1422858564
|
||||
@SerializedName("rpid_str")
|
||||
var rpidStr: String // 1422858564
|
||||
)
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.hiczp.bilibili.api.member
|
||||
|
||||
import com.hiczp.bilibili.api.member.model.Pre
|
||||
import kotlinx.coroutines.Deferred
|
||||
import retrofit2.http.GET
|
||||
|
||||
/**
|
||||
* 创作中心
|
||||
*/
|
||||
@Suppress("DeferredIsResult")
|
||||
interface MemberAPI {
|
||||
/**
|
||||
* 刚登陆时会访问该 API, 使用返回的 url 来创建一个指向一系列 H5 页面的 ListView
|
||||
* 侧拉抽屉 -> 创作中心 -> 更多功能
|
||||
*/
|
||||
@GET("/x/app/pre")
|
||||
fun pre(): Deferred<Pre>
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package com.hiczp.bilibili.api.member.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Pre(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("academy")
|
||||
var academy: Academy,
|
||||
@SerializedName("act")
|
||||
var act: Act,
|
||||
@SerializedName("creative")
|
||||
var creative: Creative,
|
||||
@SerializedName("entrance")
|
||||
var entrance: Entrance
|
||||
) {
|
||||
data class Entrance(
|
||||
@SerializedName("guidance")
|
||||
var guidance: String, // 投稿
|
||||
@SerializedName("show")
|
||||
var show: Int // 1
|
||||
)
|
||||
|
||||
data class Creative(
|
||||
@SerializedName("portal_list")
|
||||
var portalList: List<Portal>,
|
||||
@SerializedName("show")
|
||||
var show: Int // 1
|
||||
) {
|
||||
data class Portal(
|
||||
@SerializedName("icon")
|
||||
var icon: String, // http://i0.hdslb.com/bfs/archive/31f485451e415bbbc59407f1fddce8f317db6287.png
|
||||
@SerializedName("id")
|
||||
var id: Int, // 0
|
||||
@SerializedName("more")
|
||||
var more: Int, // 0
|
||||
@SerializedName("mtime")
|
||||
var mtime: Int, // 1547466050
|
||||
@SerializedName("new")
|
||||
var new: Int, // 1
|
||||
@SerializedName("position")
|
||||
var position: Int, // 8
|
||||
@SerializedName("subtitle")
|
||||
var subtitle: String,
|
||||
@SerializedName("title")
|
||||
var title: String, // 更多功能
|
||||
@SerializedName("url")
|
||||
var url: String // activity://uper/user_center/more_portal
|
||||
)
|
||||
}
|
||||
|
||||
data class Act(
|
||||
@SerializedName("show")
|
||||
var show: Int, // 1
|
||||
@SerializedName("title")
|
||||
var title: String, // 热门活动
|
||||
@SerializedName("url")
|
||||
var url: String // https://www.bilibili.com/blackboard/x/activity-tougao-h5/all
|
||||
)
|
||||
|
||||
data class Academy(
|
||||
@SerializedName("show")
|
||||
var show: Int, // 0
|
||||
@SerializedName("title")
|
||||
var title: String,
|
||||
@SerializedName("url")
|
||||
var url: String
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.hiczp.bilibili.api.message
|
||||
|
||||
import com.hiczp.bilibili.api.message.model.NotifyCount
|
||||
import com.hiczp.bilibili.api.message.model.UplmList
|
||||
import kotlinx.coroutines.Deferred
|
||||
import retrofit2.http.GET
|
||||
|
||||
/**
|
||||
* 消息推送有关的接口
|
||||
*/
|
||||
@Suppress("DeferredIsResult")
|
||||
interface MessageAPI {
|
||||
/**
|
||||
* 获取消息数量
|
||||
* 首页 -> 右上角 talk 图标
|
||||
*/
|
||||
@GET("/api/notify/query.notify.count.do")
|
||||
fun queryNotifyCount(): Deferred<NotifyCount>
|
||||
|
||||
/**
|
||||
* 荣誉周报
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/api/notify/get.uplm.list.do")
|
||||
fun getUplmList(): Deferred<UplmList>
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.hiczp.bilibili.api.message.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class NotifyCount(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String,
|
||||
@SerializedName("msg")
|
||||
var msg: String
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("_gt_")
|
||||
var gt: Int, // 0
|
||||
/**
|
||||
* \@我
|
||||
*/
|
||||
@SerializedName("at_me")
|
||||
var atMe: Int, // 0
|
||||
@SerializedName("notify_me")
|
||||
var notifyMe: Int, // 9
|
||||
/**
|
||||
* 收到的赞
|
||||
*/
|
||||
@SerializedName("praise_me")
|
||||
var praiseMe: Int, // 0
|
||||
/**
|
||||
* 回复我的
|
||||
*/
|
||||
@SerializedName("reply_me")
|
||||
var replyMe: Int, // 0
|
||||
@SerializedName("up")
|
||||
var up: Int // 0
|
||||
)
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.hiczp.bilibili.api.message.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class UplmList(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String,
|
||||
@SerializedName("msg")
|
||||
var msg: String
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("_gt_")
|
||||
var gt: Int, // 0
|
||||
@SerializedName("id")
|
||||
var id: Int, // 2429173
|
||||
@SerializedName("time")
|
||||
var time: String, // 2018-11-25 19:29:22
|
||||
@SerializedName("title")
|
||||
var title: String, // 叮!你有一份荣誉周报待查收!
|
||||
@SerializedName("unread")
|
||||
var unread: Int // 0
|
||||
)
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package com.hiczp.bilibili.api.passport
|
||||
|
||||
import com.hiczp.bilibili.api.passport.model.GetKeyResponse
|
||||
import com.hiczp.bilibili.api.passport.model.LoginResponse
|
||||
import com.hiczp.bilibili.api.passport.model.OAuth2Info
|
||||
import com.hiczp.bilibili.api.retrofit.CommonResponse
|
||||
import kotlinx.coroutines.Deferred
|
||||
import retrofit2.http.*
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 用户鉴权相关的接口
|
||||
*/
|
||||
@Suppress("DeferredIsResult")
|
||||
interface PassportAPI {
|
||||
@POST("/api/oauth2/getKey")
|
||||
fun getKey(): Deferred<GetKeyResponse>
|
||||
|
||||
/**
|
||||
* 多次错误的登陆尝试后, 服务器将返回 {"ts":1550569982,"code":-105,"data":{"url":"https://passport.bilibili.com/register/verification.html?success=1>=b6e5b7fad7ecd37f465838689732e788&challenge=9a67afa4d42ede71a93aeaaa54a4b6fe&ct=1&hash=105af2e7cc6ea829c4a95205f2371dc5"},"message":"验证码错误!"}
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@POST("/api/v3/oauth2/login")
|
||||
@FormUrlEncoded
|
||||
fun login(
|
||||
@Field("username") username: String, @Field("password") password: String,
|
||||
//以下为极验所需字段
|
||||
@Field("challenge") challenge: String? = null,
|
||||
@Field("seccode") secCode: String? = null,
|
||||
@Field("validate") validate: String? = null
|
||||
): Deferred<LoginResponse>
|
||||
|
||||
/**
|
||||
* 除了 accessToken, 其他全部都是 cookie 的值
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@POST("/api/v2/oauth2/revoke")
|
||||
@FormUrlEncoded
|
||||
fun revoke(
|
||||
@Field("DedeUserID") dedeUserId: String? = null,
|
||||
@Field("DedeUserID__ckMd5") ckMd5: String? = null,
|
||||
@Field("SESSDATA") sessData: String? = null,
|
||||
@Field("access_token") accessToken: String,
|
||||
@Field("bili_jct") biliJct: String? = null,
|
||||
@Field("sid") sid: String? = null
|
||||
): Deferred<CommonResponse>
|
||||
|
||||
/**
|
||||
* 将所有 cookie 以 Map 形式传入
|
||||
*/
|
||||
@POST("/api/v2/oauth2/revoke")
|
||||
@FormUrlEncoded
|
||||
fun revoke(
|
||||
@FieldMap cookieMap: Map<String, String> = Collections.emptyMap(),
|
||||
@Field("access_token") accessToken: String
|
||||
): Deferred<CommonResponse>
|
||||
|
||||
/**
|
||||
* 获取 OAuth2 信息
|
||||
* 如果未登录会返回 {"message":"user not login","ts":1552319204,"code":-101}
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/api/v2/oauth2/info")
|
||||
fun info(
|
||||
@Query("DedeUserID") dedeUserId: String? = null,
|
||||
@Query("DedeUserID__ckMd5") ckMd5: String? = null,
|
||||
@Query("SESSDATA") sessData: String? = null,
|
||||
@Query("access_token") accessToken: String,
|
||||
@Query("bili_jct") biliJct: String? = null,
|
||||
@Query("sid") sid: String? = null
|
||||
): Deferred<OAuth2Info>
|
||||
|
||||
@GET("/api/v2/oauth2/info")
|
||||
fun info(
|
||||
@QueryMap cookieMap: Map<String, String> = Collections.emptyMap(),
|
||||
@Query("access_token") accessToken: String
|
||||
): Deferred<OAuth2Info>
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.hiczp.bilibili.api.passport.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class GetKeyResponse(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("message")
|
||||
var message: String?,
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("ts")
|
||||
var ts: Long // 1550219688
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("hash")
|
||||
var hash: String, // 93ac6f60b4789952
|
||||
@SerializedName("key")
|
||||
var key: String // -----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCdScM09sZJqFPX7bvmB2y6i08JbHsa0v4THafPbJN9NoaZ9Djz1LmeLkVlmWx1DwgHVW+K7LVWT5FV3johacVRuV9837+RNntEK6SE82MPcl7fA++dmW2cLlAjsIIkrX+aIvvSGCuUfcWpWFy3YVDqhuHrNDjdNcaefJIQHMW+sQIDAQAB-----END PUBLIC KEY-----
|
||||
)
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.hiczp.bilibili.api.passport.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import java.io.Serializable
|
||||
|
||||
data class LoginResponse(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("message")
|
||||
var message: String?,
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("ts")
|
||||
var ts: Long // 1550219689
|
||||
) : Serializable {
|
||||
data class Data(
|
||||
@SerializedName("cookie_info")
|
||||
var cookieInfo: CookieInfo,
|
||||
@SerializedName("sso")
|
||||
var sso: List<String>,
|
||||
@SerializedName("status")
|
||||
var status: Int, // 0
|
||||
@SerializedName("token_info")
|
||||
var tokenInfo: TokenInfo
|
||||
) : Serializable {
|
||||
data class CookieInfo(
|
||||
@SerializedName("cookies")
|
||||
var cookies: List<Cookie>,
|
||||
@SerializedName("domains")
|
||||
var domains: List<String>
|
||||
) : Serializable {
|
||||
data class Cookie(
|
||||
@SerializedName("expires")
|
||||
var expires: Long, // 1552811689
|
||||
@SerializedName("http_only")
|
||||
var httpOnly: Int, // 1
|
||||
@SerializedName("name")
|
||||
var name: String, // SESSDATA
|
||||
@SerializedName("value")
|
||||
var value: String // 5ff9ba24%2C1552811689%2C04ae9421
|
||||
) : Serializable
|
||||
}
|
||||
|
||||
data class TokenInfo(
|
||||
@SerializedName("access_token")
|
||||
var accessToken: String, // fd0303ff75a6ec6b452c28f4d8621021
|
||||
@SerializedName("expires_in")
|
||||
var expiresIn: Long, // 2592000
|
||||
@SerializedName("mid")
|
||||
var mid: Long, // 20293030
|
||||
@SerializedName("refresh_token")
|
||||
var refreshToken: String // 6a333ebded3c3dbdde65d136b3190d21
|
||||
) : Serializable
|
||||
}
|
||||
|
||||
//快捷方式
|
||||
val userId get() = data.tokenInfo.mid
|
||||
val token get() = data.tokenInfo.accessToken
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.hiczp.bilibili.api.passport.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class OAuth2Info(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("ts")
|
||||
var ts: Long // 1551865482
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("access_token")
|
||||
var accessToken: String, // 813b20ea7f229795eba7bd31608e3621
|
||||
@SerializedName("expires_in")
|
||||
var expiresIn: Long, // 1797151
|
||||
@SerializedName("mid")
|
||||
var mid: Long // 20293030
|
||||
)
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package com.hiczp.bilibili.api.player
|
||||
|
||||
import com.hiczp.bilibili.api.md5
|
||||
import com.hiczp.bilibili.api.player.model.BangumiPlayUrl
|
||||
import com.hiczp.bilibili.api.player.model.VideoPlayUrl
|
||||
import kotlinx.coroutines.Deferred
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
import java.lang.management.ManagementFactory
|
||||
|
||||
/**
|
||||
* 这里是播放器会访问的 API
|
||||
* 返回内容中会有多个视频下载地址, 他们代表不同的视频质量(音频同理)
|
||||
* 音频和视频是分开的
|
||||
* 下载视频得到的是一个 m4s 文件, 但是实际上是完整的视频(例如整个番剧而非片段)
|
||||
* 下载音频得到的也是一个 m4s 文件, 也是完整的
|
||||
* 将视频和音频合在一起, 就可以播放了
|
||||
*/
|
||||
@Suppress("DeferredIsResult", "SpellCheckingInspection")
|
||||
interface PlayerAPI {
|
||||
/**
|
||||
* 获得视频的播放地址
|
||||
* 这个 API 需要使用特别的 appKey
|
||||
*
|
||||
* @param cid 在获取视频详情页面的接口的返回值里
|
||||
* @param aid 视频的唯一标识
|
||||
*
|
||||
* @see com.hiczp.bilibili.api.app.AppAPI.view
|
||||
*/
|
||||
@GET(videoPlayUrl)
|
||||
fun videoPlayUrl(
|
||||
@Query("force_host") forceHost: Int = 0,
|
||||
@Query("fnval") fnVal: Int = 16,
|
||||
@Query("qn") qn: Int = 32,
|
||||
@Query("npcybs") npcybs: Int = 0,
|
||||
@Query("cid") cid: Long,
|
||||
@Query("fnver") fnVer: Int = 0,
|
||||
@Query("aid") aid: Long
|
||||
): Deferred<VideoPlayUrl>
|
||||
|
||||
/**
|
||||
* 获得番剧的播放地址
|
||||
*
|
||||
* @param aid 番剧的唯一标识
|
||||
* @param cid 在番剧详情页的返回值里
|
||||
* @param seasonType 分季类型, 不明确, 似乎总为 1
|
||||
* @param session 其值为 系统已运行时间(ms)的MD5值, 此处的默认值为 JVM 已启动时间, 在 Android 上请使用 SystemClock
|
||||
* @param trackPath 不明确
|
||||
*
|
||||
* @see com.hiczp.bilibili.api.main.MainAPI.season
|
||||
*/
|
||||
@GET("https://api.bilibili.com/pgc/player/api/playurl")
|
||||
fun bangumiPlayUrl(
|
||||
@Query("aid") aid: Long,
|
||||
@Query("cid") cid: Long,
|
||||
@Query("fnval") fnVal: Int = 16,
|
||||
@Query("fnver") fnVer: Int = 0,
|
||||
@Query("module") module: String = "bangumi",
|
||||
@Query("npcybs") npcybs: Int = 0,
|
||||
@Query("qn") qn: Int = 32,
|
||||
@Query("season_type") seasonType: Int = 1,
|
||||
@Query("session") session: String = (System.currentTimeMillis() - ManagementFactory.getRuntimeMXBean().startTime).toString().md5(),
|
||||
@Query("track_path") trackPath: Int? = null
|
||||
): Deferred<BangumiPlayUrl>
|
||||
|
||||
companion object {
|
||||
const val videoPlayUrl = "https://app.bilibili.com/x/playurl"
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package com.hiczp.bilibili.api.player
|
||||
|
||||
import com.hiczp.bilibili.api.BilibiliClientProperties
|
||||
import com.hiczp.bilibili.api.calculateSign
|
||||
import com.hiczp.bilibili.api.passport.model.LoginResponse
|
||||
import com.hiczp.bilibili.api.retrofit.Charsets.UTF_8
|
||||
import com.hiczp.bilibili.api.retrofit.Header
|
||||
import com.hiczp.bilibili.api.retrofit.Param
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import java.net.URLEncoder
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* PlayerAPI 专用的拦截器
|
||||
*
|
||||
* @see PlayerAPI
|
||||
*/
|
||||
class PlayerInterceptor(
|
||||
private val bilibiliClientProperties: BilibiliClientProperties,
|
||||
private val loginResponseExpression: () -> LoginResponse?
|
||||
) : Interceptor {
|
||||
@Suppress("SpellCheckingInspection")
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
|
||||
//添加 header
|
||||
val header = request.headers().newBuilder().apply {
|
||||
add(Header.ACCEPT, "*/*")
|
||||
add(Header.USER_AGENT, "Bilibili Freedoooooom/MarkII")
|
||||
add(Header.ACCEPT_LANGUAGE, "zh-CN,zh;q=0.8")
|
||||
}.build()
|
||||
|
||||
//添加 Query Params
|
||||
val oldUrl = request.url()
|
||||
//如果是视频播放地址这个 API, 要用特殊的 appKey
|
||||
val isVideo = oldUrl.toString().startsWith(PlayerAPI.videoPlayUrl)
|
||||
val url = StringBuilder(oldUrl.encodedQuery() ?: "").apply {
|
||||
//appKey
|
||||
addParamEncode(Param.APP_KEY, if (isVideo) bilibiliClientProperties.videoAppKey else bilibiliClientProperties.appKey)
|
||||
//凭证有关
|
||||
val loginRespons = loginResponseExpression()
|
||||
if (loginRespons != null) {
|
||||
//expire 的值为 token过期时间+2s
|
||||
addParamEncode(Param.EXPIRE, (loginRespons.ts + loginRespons.data.tokenInfo.expiresIn + 2).toString())
|
||||
addParamEncode(Param.ACCESS_KEY, loginRespons.token)
|
||||
addParamEncode(Param.MID, loginRespons.userId.toString())
|
||||
} else {
|
||||
addParamEncode(Param.EXPIRE, "0")
|
||||
addParamEncode(Param.MID, "0")
|
||||
}
|
||||
//公共参数
|
||||
addParamEncode(Param.DEVICE, bilibiliClientProperties.platform)
|
||||
addParamEncode(Param.MOBILE_APP, bilibiliClientProperties.platform)
|
||||
addParamEncode(Param.PLATFORM, bilibiliClientProperties.platform)
|
||||
addParamEncode("otype", "json")
|
||||
addParamEncode(Param.TIMESTAMP, Instant.now().epochSecond.toString())
|
||||
addParamEncode(Param.BUILD, bilibiliClientProperties.build)
|
||||
addParamEncode(Param.BUILD_VERSION_ID, bilibiliClientProperties.buildVersionId)
|
||||
}.toString().let {
|
||||
//排序
|
||||
val sortedEncodedQuery = it.split('&').sorted().joinToString(separator = "&")
|
||||
//添加 sign
|
||||
val sign = calculateSign(sortedEncodedQuery, if (isVideo) bilibiliClientProperties.videoAppSecret else bilibiliClientProperties.appSecret)
|
||||
"$sortedEncodedQuery&${Param.SIGN}=$sign"
|
||||
}.let {
|
||||
oldUrl.newBuilder().encodedQuery(it).build()
|
||||
}
|
||||
|
||||
return chain.proceed(
|
||||
request.newBuilder()
|
||||
.headers(header)
|
||||
.url(url)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun StringBuilder.addParamEncode(name: String, value: String) {
|
||||
if (length != 0) append('&')
|
||||
append(name)
|
||||
append('=')
|
||||
append(URLEncoder.encode(value, UTF_8))
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package com.hiczp.bilibili.api.player.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class BangumiPlayUrl(
|
||||
@SerializedName("accept_description")
|
||||
var acceptDescription: List<String>,
|
||||
@SerializedName("accept_format")
|
||||
var acceptFormat: String, // hdflv2,flv,flv720,flv480,mp4
|
||||
@SerializedName("accept_quality")
|
||||
var acceptQuality: List<Int>,
|
||||
@SerializedName("bp")
|
||||
var bp: Int, // 0
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("dash")
|
||||
var dash: Dash,
|
||||
@SerializedName("fnval")
|
||||
var fnval: Int, // 16
|
||||
@SerializedName("fnver")
|
||||
var fnver: Int, // 0
|
||||
@SerializedName("format")
|
||||
var format: String, // flv480
|
||||
@SerializedName("from")
|
||||
var from: String, // local
|
||||
@SerializedName("has_paid")
|
||||
var hasPaid: Boolean, // false
|
||||
@SerializedName("is_preview")
|
||||
var isPreview: Int, // 0
|
||||
@SerializedName("quality")
|
||||
var quality: Int, // 32
|
||||
@SerializedName("result")
|
||||
var result: String, // suee
|
||||
@SerializedName("seek_param")
|
||||
var seekParam: String, // start
|
||||
@SerializedName("seek_type")
|
||||
var seekType: String, // offset
|
||||
@SerializedName("status")
|
||||
var status: Int, // 2
|
||||
@SerializedName("timelength")
|
||||
var timelength: Long, // 1420201
|
||||
@SerializedName("video_codecid")
|
||||
var videoCodecid: Int, // 7
|
||||
@SerializedName("video_project")
|
||||
var videoProject: Boolean, // true
|
||||
@SerializedName("vip_status")
|
||||
var vipStatus: Int, // 0
|
||||
@SerializedName("vip_type")
|
||||
var vipType: Int // 0
|
||||
) {
|
||||
data class Dash(
|
||||
@SerializedName("audio")
|
||||
var audio: List<Audio>,
|
||||
@SerializedName("video")
|
||||
var video: List<Video>
|
||||
) {
|
||||
data class Video(
|
||||
@SerializedName("backupUrl")
|
||||
var backupUrl: List<String>,
|
||||
@SerializedName("backup_url")
|
||||
var backup_url: List<String>,
|
||||
@SerializedName("bandwidth")
|
||||
var bandwidth: Int, // 379067
|
||||
@SerializedName("baseUrl")
|
||||
var baseUrl: String, // http://60.12.119.70/upgcxcode/28/12/74921228/74921228-1-30016.m4s?expires=1550754300&platform=android&ssig=rJUT9lWneFYshCT4p_3YuA&oi=1699214834&trid=83334c4981ed460ea2444f6aab79d1b6&nfb=maPYqpoel5MI3qOUX6YpRA==&nfc=1
|
||||
@SerializedName("base_url")
|
||||
var base_url: String, // http://60.12.119.70/upgcxcode/28/12/74921228/74921228-1-30016.m4s?expires=1550754300&platform=android&ssig=rJUT9lWneFYshCT4p_3YuA&oi=1699214834&trid=83334c4981ed460ea2444f6aab79d1b6&nfb=maPYqpoel5MI3qOUX6YpRA==&nfc=1
|
||||
@SerializedName("codecid")
|
||||
var codecid: Int, // 7
|
||||
@SerializedName("id")
|
||||
var id: Int // 16
|
||||
)
|
||||
|
||||
data class Audio(
|
||||
@SerializedName("backupUrl")
|
||||
var backupUrl: List<String>,
|
||||
@SerializedName("backup_url")
|
||||
var backup_url: List<String>,
|
||||
@SerializedName("bandwidth")
|
||||
var bandwidth: Int, // 193680
|
||||
@SerializedName("baseUrl")
|
||||
var baseUrl: String, // http://60.12.119.68/upgcxcode/28/12/74921228/74921228-1-30280.m4s?expires=1550754300&platform=android&ssig=1-JiBSZvopajZNgVZ3HRdA&oi=1699214834&trid=83334c4981ed460ea2444f6aab79d1b6&nfb=maPYqpoel5MI3qOUX6YpRA==&nfc=1
|
||||
@SerializedName("base_url")
|
||||
var base_url: String, // http://60.12.119.68/upgcxcode/28/12/74921228/74921228-1-30280.m4s?expires=1550754300&platform=android&ssig=1-JiBSZvopajZNgVZ3HRdA&oi=1699214834&trid=83334c4981ed460ea2444f6aab79d1b6&nfb=maPYqpoel5MI3qOUX6YpRA==&nfc=1
|
||||
@SerializedName("id")
|
||||
var id: Int // 30280
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package com.hiczp.bilibili.api.player.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class VideoPlayUrl(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("accept_description")
|
||||
var acceptDescription: List<String>,
|
||||
@SerializedName("accept_format")
|
||||
var acceptFormat: String, // flv_p60,flv720_p60,flv,flv720,flv480,flv360
|
||||
@SerializedName("accept_quality")
|
||||
var acceptQuality: List<Int>,
|
||||
@SerializedName("dash")
|
||||
var dash: Dash,
|
||||
@SerializedName("fnval")
|
||||
var fnval: Int, // 16
|
||||
@SerializedName("fnver")
|
||||
var fnver: Int, // 0
|
||||
@SerializedName("format")
|
||||
var format: String, // flv480
|
||||
@SerializedName("from")
|
||||
var from: String, // local
|
||||
@SerializedName("quality")
|
||||
var quality: Int, // 32
|
||||
@SerializedName("result")
|
||||
var result: String, // suee
|
||||
@SerializedName("seek_param")
|
||||
var seekParam: String, // start
|
||||
@SerializedName("seek_type")
|
||||
var seekType: String, // offset
|
||||
@SerializedName("timelength")
|
||||
var timelength: Long, // 196367
|
||||
@SerializedName("video_codecid")
|
||||
var videoCodecid: Int, // 7
|
||||
@SerializedName("video_project")
|
||||
var videoProject: Boolean // true
|
||||
) {
|
||||
data class Dash(
|
||||
@SerializedName("audio")
|
||||
var audio: List<Audio>,
|
||||
@SerializedName("video")
|
||||
var video: List<Video>
|
||||
) {
|
||||
data class Audio(
|
||||
@SerializedName("backup_url")
|
||||
var backupUrl: List<String>,
|
||||
@SerializedName("bandwidth")
|
||||
var bandwidth: Int, // 191246
|
||||
@SerializedName("base_url")
|
||||
var baseUrl: String, // http://101.75.242.10/upgcxcode/41/36/72913641/72913641-1-30280.m4s?expires=1550754000&platform=android&ssig=2eirz02lIhKUw--w26lpqQ&oi=1699214834&trid=e0d3ad6245d8432887eb12b71f29bb3e&nfb=maPYqpoel5MI3qOUX6YpRA==&nfc=1
|
||||
@SerializedName("codecid")
|
||||
var codecid: Int, // 0
|
||||
@SerializedName("id")
|
||||
var id: Int // 30280
|
||||
)
|
||||
|
||||
data class Video(
|
||||
@SerializedName("backup_url")
|
||||
var backupUrl: List<String>,
|
||||
@SerializedName("bandwidth")
|
||||
var bandwidth: Int, // 288340
|
||||
@SerializedName("base_url")
|
||||
var baseUrl: String, // http://60.12.119.68/upgcxcode/41/36/72913641/72913641-1-30011.m4s?expires=1550754000&platform=android&ssig=Ven-c2XaxfkQIoMkzuq7MQ&oi=1699214834&trid=e0d3ad6245d8432887eb12b71f29bb3e&nfb=maPYqpoel5MI3qOUX6YpRA==&nfc=1
|
||||
@SerializedName("codecid")
|
||||
var codecid: Int, // 12
|
||||
@SerializedName("id")
|
||||
var id: Int // 16
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.hiczp.bilibili.api.retrofit
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
/**
|
||||
* 通用实体, 可表示无 data 的响应 或 错误响应
|
||||
* code 为 0 表示正常响应, 此时 message 为 null
|
||||
* code 不为 0 表示错误响应, 此时 data 可能是各种类型
|
||||
* 一些 API 同时有 msg 和 message
|
||||
*/
|
||||
data class CommonResponse(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
|
||||
@SerializedName("msg")
|
||||
var msg: String?,
|
||||
|
||||
@SerializedName("message")
|
||||
var message: String?,
|
||||
|
||||
@SerializedName("ts")
|
||||
var timestamp: Long, // 1550546539
|
||||
|
||||
/**
|
||||
* data 可能是各种类型, 例如 array, object, string
|
||||
*/
|
||||
@SerializedName("data")
|
||||
var data: JsonElement?,
|
||||
|
||||
/**
|
||||
* ttl, 不明确含义, 如果存在则值总为 1
|
||||
*/
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int?
|
||||
)
|
||||
@@ -1,62 +0,0 @@
|
||||
package com.hiczp.bilibili.api.retrofit
|
||||
|
||||
//该文件用于防止拼写错误
|
||||
|
||||
object Method {
|
||||
const val GET = "GET"
|
||||
const val POST = "POST"
|
||||
const val PATCH = "PATCH"
|
||||
const val PUT = "PUT"
|
||||
const val DELETE = "DELETE"
|
||||
const val OPTION = "OPTION"
|
||||
}
|
||||
|
||||
object Header {
|
||||
const val DISPLAY_ID = "Display-ID"
|
||||
@Suppress("SpellCheckingInspection")
|
||||
const val BUILD_VERSION_ID = "Buvid"
|
||||
const val DEVICE_ID = "Device-ID"
|
||||
const val USER_AGENT = "User-Agent"
|
||||
const val ACCEPT = "Accept"
|
||||
const val ACCEPT_LANGUAGE = "Accept-Language"
|
||||
const val ACCEPT_ENCODING = "Accept-Encoding"
|
||||
|
||||
//强制公共参数添加位置
|
||||
const val FORCE_PARAM = "Retrofit-Force-Param"
|
||||
const val FORCE_PARAM_QUERY = "query"
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
const val FORCE_PARAM_FORM_BODY = "formBody"
|
||||
const val FORCE_QUERY = "$FORCE_PARAM: $FORCE_PARAM_QUERY"
|
||||
const val FORCE_FORM_BODY = "$FORCE_PARAM: $FORCE_PARAM_FORM_BODY"
|
||||
}
|
||||
|
||||
object Param {
|
||||
const val ACCESS_KEY = "access_key"
|
||||
@Suppress("SpellCheckingInspection")
|
||||
const val APP_KEY = "appkey"
|
||||
const val ACTION_KEY = "actionKey"
|
||||
const val BUILD = "build"
|
||||
@Suppress("SpellCheckingInspection")
|
||||
const val BUILD_VERSION_ID = "buvid"
|
||||
const val CHANNEL = "channel"
|
||||
@Suppress("ObjectPropertyName")
|
||||
const val _DEVICE = "_device"
|
||||
const val DEVICE = "device"
|
||||
@Suppress("ObjectPropertyName", "SpellCheckingInspection")
|
||||
const val _HARDWARE_ID = "_hwid"
|
||||
const val SOURCE = "src"
|
||||
const val TRACE_ID = "trace_id"
|
||||
const val USER_ID = "uid"
|
||||
const val VERSION = "version"
|
||||
@Suppress("SpellCheckingInspection")
|
||||
const val MOBILE_APP = "mobi_app"
|
||||
const val PLATFORM = "platform"
|
||||
const val TIMESTAMP = "ts"
|
||||
const val EXPIRE = "expire"
|
||||
const val MID = "mid"
|
||||
const val SIGN = "sign"
|
||||
}
|
||||
|
||||
internal object Charsets {
|
||||
const val UTF_8 = "UTF-8"
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package com.hiczp.bilibili.api.retrofit
|
||||
|
||||
import okhttp3.FormBody
|
||||
|
||||
inline fun FormBody.forEach(block: (String, String) -> Unit) {
|
||||
repeat(size()) {
|
||||
block(encodedName(it), encodedValue(it))
|
||||
}
|
||||
}
|
||||
|
||||
fun FormBody.raw() =
|
||||
StringBuilder().apply {
|
||||
repeat(size()) {
|
||||
if (it != 0) append('&')
|
||||
append(encodedName(it))
|
||||
append('=')
|
||||
append(encodedValue(it))
|
||||
}
|
||||
}.toString()
|
||||
|
||||
fun FormBody.sortedRaw(): String {
|
||||
val nameAndValue = ArrayList<String>()
|
||||
repeat(size()) {
|
||||
nameAndValue.add("${encodedName(it)}=${encodedValue(it)}")
|
||||
}
|
||||
return nameAndValue.sorted().joinToString(separator = "&")
|
||||
}
|
||||
|
||||
fun FormBody.containsEncodedName(name: String): Boolean {
|
||||
repeat(size()) {
|
||||
if (encodedName(it) == name) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun FormBody.Builder.addAllEncoded(formBody: FormBody): FormBody.Builder {
|
||||
with(formBody) {
|
||||
repeat(size()) {
|
||||
addEncoded(encodedName(it), encodedValue(it))
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
internal typealias ParamExpression = Pair<String, () -> String?>
|
||||
|
||||
internal inline fun Array<out ParamExpression>.forEachNonNull(action: (String, String) -> Unit) {
|
||||
forEach { (name, valueExpression) ->
|
||||
val value = valueExpression()
|
||||
if (value != null) {
|
||||
action(name, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.hiczp.bilibili.api.retrofit.exception
|
||||
|
||||
import com.hiczp.bilibili.api.retrofit.CommonResponse
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* 当服务器返回的 code 不等于 0 时抛出
|
||||
*/
|
||||
class BilibiliApiException(
|
||||
commonResponse: CommonResponse
|
||||
) : IOException(commonResponse.message?.takeIf { it.isNotEmpty() } ?: commonResponse.msg)
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.hiczp.bilibili.api.retrofit.interceptor
|
||||
|
||||
import com.hiczp.bilibili.api.retrofit.ParamExpression
|
||||
import com.hiczp.bilibili.api.retrofit.forEachNonNull
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
/**
|
||||
* 为请求添加公共 Header
|
||||
*
|
||||
* @param additionHeaders HeaderName to HeaderValueExpression
|
||||
*/
|
||||
class CommonHeaderInterceptor(private vararg val additionHeaders: ParamExpression) : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request().newBuilder().apply {
|
||||
additionHeaders.forEachNonNull { name, value ->
|
||||
addHeader(name, value)
|
||||
}
|
||||
}.build()
|
||||
return chain.proceed(request)
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package com.hiczp.bilibili.api.retrofit.interceptor
|
||||
|
||||
import com.hiczp.bilibili.api.retrofit.*
|
||||
import mu.KotlinLogging
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
/**
|
||||
* 为请求添加公共参数
|
||||
* 如果请求为 GET 方式则添加到 Query, 如果是其他其他方式则尝试添加到 BODY.
|
||||
*
|
||||
* @param additionParams ParamName to ParamValueExpression
|
||||
*/
|
||||
class CommonParamInterceptor(private vararg val additionParams: ParamExpression) : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
var headers = request.headers()
|
||||
var httpUrl = request.url()
|
||||
var body = request.body()
|
||||
|
||||
//是否强制加到 Query(暂不存在强制加到 FormBody 的情况)
|
||||
var forceQuery = false
|
||||
val forceParam = headers[Header.FORCE_PARAM]
|
||||
if (forceParam != null) {
|
||||
if (forceParam == Header.FORCE_PARAM_QUERY) forceQuery = true
|
||||
headers = headers.newBuilder().removeAll(Header.FORCE_PARAM).build()
|
||||
}
|
||||
|
||||
when {
|
||||
//如果是 GET 则添加到 Query
|
||||
request.method() == Method.GET || forceQuery -> {
|
||||
httpUrl = request.url().newBuilder().apply {
|
||||
additionParams.forEachNonNull { name, value ->
|
||||
addQueryParameter(name, value)
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
//如果 Body 不存在或者为空则创建一个 FormBody
|
||||
body == null || body.contentLength() == 0L -> {
|
||||
body = FormBody.Builder().apply {
|
||||
additionParams.forEachNonNull { name, value ->
|
||||
add(name, value)
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
//如果 Body 为 FormBody 则里面可能已经存在内容
|
||||
body is FormBody -> {
|
||||
body = FormBody.Builder().addAllEncoded(body).apply {
|
||||
additionParams.forEachNonNull { name, value ->
|
||||
add(name, value)
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
//如果方式不为 GET 且 Body 不为空或者为 FormBody 则无法添加公共参数
|
||||
else -> {
|
||||
logger.error {
|
||||
"Cannot add params to request: ${request.method()} ${request.url()} ${body.javaClass.simpleName}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chain.proceed(
|
||||
request.newBuilder()
|
||||
.headers(headers)
|
||||
.url(httpUrl)
|
||||
.method(request.method(), body)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.hiczp.bilibili.api.retrofit.interceptor
|
||||
|
||||
import com.github.salomonbrys.kotson.fromJson
|
||||
import com.github.salomonbrys.kotson.int
|
||||
import com.github.salomonbrys.kotson.obj
|
||||
import com.hiczp.bilibili.api.gson
|
||||
import com.hiczp.bilibili.api.jsonParser
|
||||
import com.hiczp.bilibili.api.retrofit.exception.BilibiliApiException
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
/**
|
||||
* 如果服务器返回的 code 不为 0 则抛出异常
|
||||
*/
|
||||
object FailureResponseInterceptor : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val response = chain.proceed(chain.request())
|
||||
val body = response.body()
|
||||
if (!response.isSuccessful || body == null || body.contentLength() == 0L) return response
|
||||
|
||||
//获取字符集
|
||||
val contentType = body.contentType()
|
||||
val charset = if (contentType == null) {
|
||||
Charsets.UTF_8
|
||||
} else {
|
||||
contentType.charset(Charsets.UTF_8)!!
|
||||
}
|
||||
|
||||
//拷贝流
|
||||
val inputStreamReader = body.source().also {
|
||||
it.request(Long.MAX_VALUE)
|
||||
}.buffer.clone().inputStream().reader(charset)
|
||||
|
||||
//读取其内容
|
||||
val jsonObject = try {
|
||||
jsonParser.parse(inputStreamReader).obj
|
||||
} catch (exception: Exception) {
|
||||
//如果返回内容解析失败, 说明它不是一个合法的 json
|
||||
//如果在拦截器抛出 MalformedJsonException 会导致 Retrofit 的异步请求一直卡着直到超时
|
||||
return response
|
||||
} finally {
|
||||
inputStreamReader.close()
|
||||
}
|
||||
|
||||
//判断 code 是否为 0
|
||||
if (jsonObject["code"].int != 0) {
|
||||
throw BilibiliApiException(gson.fromJson(jsonObject))
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user