diff --git a/README.md b/README.md index ff52dca..96310ed 100644 --- a/README.md +++ b/README.md @@ -266,16 +266,28 @@ val reply = bilibiliClient.mainAPI.reply(oid = 44154463).await() val childReply = bilibiliClient.mainAPI.childReply(oid = 16622855, root = 1405602348).await() ``` -其中的 `root` 表示父评论的 id. +其中的 `root` 表示根评论的 id. -(如果一个评论的父 id 为 0 表示它是顶层评论而非子评论) +每个评论都有自己的 `replyId`, `parentId` 以及 `rootId`. -(子评论原理上可以再有子评论, 但是 B站 在逻辑上只有一层子评论) +假如一个人在一个评论的子评论里发布了一个评论并且 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` 控制分页, 原理同上. + 番剧下面的评论用一样的方式获取. ## 获得一个视频的弹幕 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 63e7ab1..f3ab442 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/main/MainAPI.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/main/MainAPI.kt @@ -34,7 +34,7 @@ interface MainAPI { * * @param minId 想要请求的子评论(复数)的第一个子评论的 id(子评论默认升序排序), 为 null 时从 0 楼开始 * @param oid aid - * @param root 父评论的 id + * @param root 根评论的 id * @param size 分页大小 */ @GET("/x/v2/reply/reply/cursor") @@ -48,6 +48,30 @@ interface MainAPI { @Query("type") type: Int = 1 ): Deferred + /** + * 查看 "对话列表" + * 当一个子评论中有多组人在互相 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 + /** * 获得一个番剧的分季信息(生成番剧页面所需的信息), 包含当前选择的季的分集信息 * seasonId 或 episodeId 必须有一个, 如果用 episodeId 将跳转到对应的 season 的页面 diff --git a/src/main/kotlin/com/hiczp/bilibili/api/main/model/ChatList.kt b/src/main/kotlin/com/hiczp/bilibili/api/main/model/ChatList.kt new file mode 100644 index 0000000..e21d06d --- /dev/null +++ b/src/main/kotlin/com/hiczp/bilibili/api/main/model/ChatList.kt @@ -0,0 +1,226 @@ +package com.hiczp.bilibili.api.main.model + +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 + ) { + 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: Int, // 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: Int, // 161745277 + @SerializedName("oid") + var oid: Int, // 34175504 + @SerializedName("parent") + var parent: Int, // 1136656601 + @SerializedName("parent_str") + var parentStr: String, // 1136656601 + @SerializedName("rcount") + var rcount: Int, // 0 + @SerializedName("replies") + var replies: Any?, // null + @SerializedName("root") + var root: Int, // 1136310360 + @SerializedName("root_str") + var rootStr: String, // 1136310360 + @SerializedName("rpid") + var rpid: Int, // 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: Any?, // 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: 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 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, + @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 + ) + } +}