From e33af1de88cac52b57acb55186487f97f99d3886 Mon Sep 17 00:00:00 2001 From: czp3009 Date: Tue, 26 Feb 2019 17:49:31 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=BF=BD=E7=95=AA=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E7=9A=84=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + .../com/hiczp/bilibili/api/StreamExtension.kt | 3 + .../com/hiczp/bilibili/api/app/AppAPI.kt | 9 ++- .../com/hiczp/bilibili/api/main/MainAPI.kt | 52 ++++++++++-- .../bilibili/api/main/model/BangumiMore.kt | 43 ++++++++++ .../bilibili/api/main/model/BangumiPage.kt | 79 +++++++++++++++++++ .../bilibili/api/main/model/MyBangumiNews.kt | 73 +++++++++++++++++ 7 files changed, 254 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/com/hiczp/bilibili/api/main/model/BangumiMore.kt create mode 100644 src/main/kotlin/com/hiczp/bilibili/api/main/model/BangumiPage.kt create mode 100644 src/main/kotlin/com/hiczp/bilibili/api/main/model/MyBangumiNews.kt diff --git a/README.md b/README.md index 7b029fe..ff52dca 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Bilibili API JVM 调用库 该项目提供 Bilibili API 的 JVM 调用, 协议来自 Bilibili Android APP 的逆向工程以及截包分析. +使用一台虚拟的 `Pixel 2` 设备来截取数据包, 一些固定参数可能与真实设备不一致. + # 技术说明 `BilibiliClient` 类表示一个模拟的客户端, 实例化此类即表示打开了 Bilibili APP. diff --git a/src/main/kotlin/com/hiczp/bilibili/api/StreamExtension.kt b/src/main/kotlin/com/hiczp/bilibili/api/StreamExtension.kt index 7727952..8da8033 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/StreamExtension.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/StreamExtension.kt @@ -18,6 +18,9 @@ fun InputStream.readInt(): Int { (byteArray[3].toInt()) } +/** + * 以大端模式从流中读取一个 unsigned int + */ @Suppress("EXPERIMENTAL_API_USAGE") fun InputStream.readUInt() = readInt().toUInt() diff --git a/src/main/kotlin/com/hiczp/bilibili/api/app/AppAPI.kt b/src/main/kotlin/com/hiczp/bilibili/api/app/AppAPI.kt index 114978e..2338f36 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/app/AppAPI.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/app/AppAPI.kt @@ -33,13 +33,17 @@ interface AppAPI { /** * 侧边栏中动态增加的按钮, 返回信息包含 URI 地址(到对应的 activity) + * 侧拉抽屉 */ @GET("/x/resource/sidebar") fun sidebar(): Deferred /** * 首页内容(客户端通过解析返回的内容来生成页面内容, 下同) + * 该 API 没有翻页参数, 同样的参数每次请求都会返回不一样的内容. 刷新和下拉只是简单的重新访问此接口. * 首页 -> 推荐 + * + * @param pull 如果是通过滑动到最顶端来刷新页面的, 那么将是 true, 将页面滑动到最底端来获取更多内容将是 false */ @Suppress("SpellCheckingInspection") @GET("/x/v2/feed/index") @@ -65,6 +69,9 @@ interface AppAPI { /** * 热门页面 * 首页 -> 热门 + * + * @param index 翻页参数, 一开始为 0, 然后每次滑动到底端就会加 10 + * @param ver 第一次请求时没有这个参数, 第二次开始这个参数为上一次请求此接口时的返回值中的 `ver` */ @GET("/x/v2/show/popular/index") fun popularPage( @@ -75,7 +82,7 @@ interface AppAPI { @Query("last_param") lastParam: String? = null, @Query("login_event") loginEvent: Int = 0, @Query("qn") qn: Int = 32, - @Query("ver") ver: Long? = null //ver 的值为上一次请求该接口时的 timestamp-1 + @Query("ver") ver: Long? = null ): Deferred /** diff --git a/src/main/kotlin/com/hiczp/bilibili/api/main/MainAPI.kt b/src/main/kotlin/com/hiczp/bilibili/api/main/MainAPI.kt index aa0fad5..63e7ab1 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/main/MainAPI.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/main/MainAPI.kt @@ -1,9 +1,6 @@ package com.hiczp.bilibili.api.main -import com.hiczp.bilibili.api.main.model.ChildReply -import com.hiczp.bilibili.api.main.model.Recommend -import com.hiczp.bilibili.api.main.model.Reply -import com.hiczp.bilibili.api.main.model.Season +import com.hiczp.bilibili.api.main.model.* import kotlinx.coroutines.Deferred import retrofit2.http.GET import retrofit2.http.Query @@ -52,8 +49,8 @@ interface MainAPI { ): Deferred /** - * 获得一个番剧的分季信息, 包含默认季(通常是最新的一季)的分集信息 - * seasonId 或 episodeId 必须有一个, 返回的结果是一样的 + * 获得一个番剧的分季信息(生成番剧页面所需的信息), 包含当前选择的季的分集信息 + * seasonId 或 episodeId 必须有一个, 如果用 episodeId 将跳转到对应的 season 的页面 * 返回值中, 每个 episode 都有 aid 和 cid * * @param seasonId 季的唯一标识 @@ -74,4 +71,47 @@ interface MainAPI { */ @GET("/pgc/season/app/related/recommend") fun recommend(@Query("season_id") seasonId: Long): Deferred + + /** + * 我的追番动态(追番页面上方的那一条 "我的追番") + * 首页 -> 追番 -> 我的追番 + */ + @Suppress("SpellCheckingInspection") + @GET("/pgc/app/page/bangumi/mine") + fun myBangumiNews( + @Query("fnval") fnval: Int = 16, + @Query("fnver") fnver: Int = 0 + ): Deferred + + /** + * 追番页面(客户端用这里面的数据来生成追番页面) + * 每个模块(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 + + /** + * 获得更多 "编辑推荐" + * 首页 -> 追番 -> (下拉) + * + * @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 } diff --git a/src/main/kotlin/com/hiczp/bilibili/api/main/model/BangumiMore.kt b/src/main/kotlin/com/hiczp/bilibili/api/main/model/BangumiMore.kt new file mode 100644 index 0000000..282ab62 --- /dev/null +++ b/src/main/kotlin/com/hiczp/bilibili/api/main/model/BangumiMore.kt @@ -0,0 +1,43 @@ +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 +) { + 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 + ) +} diff --git a/src/main/kotlin/com/hiczp/bilibili/api/main/model/BangumiPage.kt b/src/main/kotlin/com/hiczp/bilibili/api/main/model/BangumiPage.kt new file mode 100644 index 0000000..8cae155 --- /dev/null +++ b/src/main/kotlin/com/hiczp/bilibili/api/main/model/BangumiPage.kt @@ -0,0 +1,79 @@ +package com.hiczp.bilibili.api.main.model + +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, + @SerializedName("regions") + var regions: List + ) { + data class Module( + @SerializedName("attr") + var attr: Attr, + @SerializedName("headers") + var headers: List, + @SerializedName("items") + var items: List, + @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 + ) { + 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: Int, // 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 + ) + } +} diff --git a/src/main/kotlin/com/hiczp/bilibili/api/main/model/MyBangumiNews.kt b/src/main/kotlin/com/hiczp/bilibili/api/main/model/MyBangumiNews.kt new file mode 100644 index 0000000..41e6de8 --- /dev/null +++ b/src/main/kotlin/com/hiczp/bilibili/api/main/model/MyBangumiNews.kt @@ -0,0 +1,73 @@ +package com.hiczp.bilibili.api.main.model + +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, + @SerializedName("follow") + var follow: Int, // 34 + @SerializedName("follows") + var follows: List, + @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 + ) + } + } +}