diff --git a/README.md b/README.md index 022f087..7b029fe 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ val view = bilibiliClient.appAPI.view(aid = 41517911).await() 该接口返回对一个视频页面的描述信息(甚至包含广告和推荐), 客户端根据这些信息生成视频页面. -其中 `data.cid` 为默认 `p` 的 `cid`. `data.pages[n].cid` 为每个 `p` 的 `cid`. 如果没有分 `p` 则使用外层那个 `cid` 来请求视频地址. +其中 `data.cid` 为默认 `p` 的 `cid`. `data.pages[n].cid` 为每个 `p` 的 `cid`. 如果只有一个 `p` 那么说明视频没有分 `p`. 请求视频地址将访问如下结构的内容 @@ -254,6 +254,10 @@ val reply = bilibiliClient.mainAPI.reply(oid = 44154463).await() 评论是不分 `p` 的, 所有评论都是在一起的. +可以额外使用一个 `next` 参数来指定返回的起始楼层(即翻页). + +楼层是越翻越小的, 所以 `next` 也要越来越小. + 看到了傻吊网友们的评论是不够的, 我们还想看到杠精与其隔着屏幕对喷的场景, 因此我们要获取评论的子评论, 即评论的评论 ```kotlin @@ -266,12 +270,52 @@ val childReply = bilibiliClient.mainAPI.childReply(oid = 16622855, root = 140560 (子评论原理上可以再有子评论, 但是 B站 在逻辑上只有一层子评论) -番剧下面的评论用一样的方式获取, 下同. +用额外的 `minId` 参数来指定返回的起始子楼层. + +注意, 子楼层是越翻越大的. + +番剧下面的评论用一样的方式获取. ## 获得一个视频的弹幕 看评论自然不够刺激, 我们想看到弹幕! -//TODO +获取弹幕非常简单 + +```kotlin +val danmakuFile = bilibiliClient.danmakuAPI.list(aid = 810872, oid = 1176840).await() +``` + +弹幕是一个文件, 可能非常大, 里面是二进制内容. + +为了解析弹幕, 我们要用到另一个类 + +```kotlin +val (flagMap, danmakuList) = DanmakuParser.parser(danmakuFile.byteStream()) +``` + +`flagMap` 类型为 `Map` 键和值分别表示 弹幕ID 与 弹幕等级. + +弹幕等级在区间 \[1, 10\] 内, 低于客户端设置的 "弹幕云屏蔽等级" 的弹幕将不会显示出来. + +`danmakuList` 类型为 `List`, 内含所有解析得到的弹幕. + +使用以下代码来输出全部弹幕的内容 + +```kotlin +danmakuList.forEach { + println(it.content) +} +``` + +客户端的弹幕屏蔽设置是对弹幕中的 `user` 属性做的. 而实际上 `danmaku.user` 是一个字符串. + +这个字符串是 用户ID 的 `CRC32` 的校验和. + +众所周知, 一切 hash 算法都有冲突的问题. 这也就意味着, 屏蔽一个用户的同时可能屏蔽掉了多个与该用户 hash 值相同的用户. + +在另一方面, 通过这个 `CRC32` 校验和进行用户 ID 反查, 将查询到多个可能的用户, 因此无法确定一条弹幕到底是哪个用户发送的. + +番剧的弹幕同理. # License GPL V3 diff --git a/build.gradle b/build.gradle index eaed97a..20ae074 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.3.21' - kotlin_coroutines_version= '1.1.1' + kotlin_coroutines_version = '1.1.1' jvm_target = JavaVersion.VERSION_1_8 } @@ -65,6 +65,12 @@ dependencies { compile group: 'com.squareup.okhttp3', name: 'logging-interceptor', version: '3.13.1' } +//utils +dependencies { + // https://mvnrepository.com/artifact/commons-io/commons-io + compile group: 'commons-io', name: 'commons-io', version: '2.6' +} + //unit test dependencies { // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter diff --git a/record/bullet_screen_stream_json/ACITIVITY_EVENT.json b/record/bullet_screen_stream_json/ACITIVITY_EVENT.json deleted file mode 100644 index a4e40fd..0000000 --- a/record/bullet_screen_stream_json/ACITIVITY_EVENT.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "cmd": "ACTIVITY_EVENT", - "data": { - "keyword": "newspring_2018", - "type": "cracker", - "limit": 300000, - "progress": 158912 - } -} diff --git a/record/bullet_screen_stream_json/CHANGE_ROOM_INFO.json b/record/bullet_screen_stream_json/CHANGE_ROOM_INFO.json deleted file mode 100644 index 99f0ac9..0000000 --- a/record/bullet_screen_stream_json/CHANGE_ROOM_INFO.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "cmd": "CHANGE_ROOM_INFO", - "background": "http://static.hdslb.com/live-static/images/bg/4.jpg" -} diff --git a/record/bullet_screen_stream_json/COMBO_END.json b/record/bullet_screen_stream_json/COMBO_END.json deleted file mode 100644 index d3a1a3d..0000000 --- a/record/bullet_screen_stream_json/COMBO_END.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "cmd": "COMBO_END", - "data": { - "uname": "打死安迷修-雷狮", - "r_uname": "Jinko_神子", - "combo_num": 1, - "price": 200, - "gift_name": "flag", - "gift_id": 20002, - "start_time": 1527929335, - "end_time": 1527929335 - } -} diff --git a/record/bullet_screen_stream_json/COMBO_SEND.json b/record/bullet_screen_stream_json/COMBO_SEND.json deleted file mode 100644 index 78169c0..0000000 --- a/record/bullet_screen_stream_json/COMBO_SEND.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "cmd": "COMBO_SEND", - "data": { - "uid": 33012231, - "uname": "我就是讨厌你这样", - "combo_num": 3, - "gift_name": "凉了", - "gift_id": 20010, - "action": "赠送" - } -} diff --git a/record/bullet_screen_stream_json/CUT_OFF.json b/record/bullet_screen_stream_json/CUT_OFF.json deleted file mode 100644 index a0118f3..0000000 --- a/record/bullet_screen_stream_json/CUT_OFF.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "cmd": "CUT_OFF", - "msg": "禁播游戏", - "roomid": 8446134 -} diff --git a/record/bullet_screen_stream_json/DANMU_MSG(with comment).json b/record/bullet_screen_stream_json/DANMU_MSG(with comment).json deleted file mode 100644 index f53c016..0000000 --- a/record/bullet_screen_stream_json/DANMU_MSG(with comment).json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "info": [ - //弹幕基本属性 - [ - 0, - //pool - 1, - //mode - 25, - //fontSize - 16777215, - //color - 1510498713, - //弹幕发送时间 - "1510498712", - //用户进房时间(Android 客户端发送的弹幕, 这个值会是随机数) - 0, - "8a0f75dc", - 0 - ], - "网易云音乐库在当前直播间已停留0天0时39分41秒", - //发送者有关信息 - [ - 39042255, - //发送者 ID - "夏沫丶琉璃浅梦", - //发送者用户名 - 0, - //是否是管理员 - 1, - //是否是 VIP - 0, - //是否是 svip - 10000, - 1, - "" - ], - //发送者的粉丝勋章有关信息(没有粉丝勋章的发送者, 这个 JsonArray 将是空的) - [ - 13, - //勋章等级 - "夏沫", - //勋章名称 - "乄夏沫丶", - //勋章对应的主播的名字 - "1547306", - //勋章对应的主播的直播间 - 16746162, - "" - ], - //用户经验有关信息 - [ - 41, - //发送者的观众等级 - 0, - 16746162, - 6603 - //排名 - ], - //用户头衔有关信息(里面有两个元素, 但是总是一样的, 不知道为什么) - [ - "title-131-1", - "title-131-1" - ], - 0, - 0, - { - "uname_color": "" - } - ], - "cmd": "DANMU_MSG" -} diff --git a/record/bullet_screen_stream_json/DANMU_MSG.json b/record/bullet_screen_stream_json/DANMU_MSG.json deleted file mode 100644 index 08f4d88..0000000 --- a/record/bullet_screen_stream_json/DANMU_MSG.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "info": [ - [ - 0, - 1, - 25, - 16777215, - 1510498713, - "1510498712", - 0, - "8a0f75dc", - 0 - ], - "网易云音乐库在当前直播间已停留0天0时39分41秒", - [ - 39042255, - "夏沫丶琉璃浅梦", - 0, - 1, - 0, - 10000, - 1, - "" - ], - [ - 13, - "夏沫", - "乄夏沫丶", - "1547306", - 16746162, - "" - ], - [ - 41, - 0, - 16746162, - 6603 - ], - [], - 0, - 0, - { - "uname_color": "" - } - ], - "cmd": "DANMU_MSG" -} diff --git a/record/bullet_screen_stream_json/ENTRY_EFFECT.json b/record/bullet_screen_stream_json/ENTRY_EFFECT.json deleted file mode 100644 index df1e693..0000000 --- a/record/bullet_screen_stream_json/ENTRY_EFFECT.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "cmd": "ENTRY_EFFECT", - "data": { - "id": 3, - "uid": 9359447, - "target_id": 275592903, - "show_avatar": 1, - "copy_writing": "欢迎 \u003c%藏拙当成玉%\u003e 进入房间", - "highlight_color": "#FFF100", - "basemap_url": "http://i0.hdslb.com/bfs/live/d208b9654b93a70b4177e1aa7e2f0343f8a5ff1a.png", - "effective_time": 1, - "priority": 50, - "privilege_type": 0, - "face": "http://i1.hdslb.com/bfs/face/12cb1ea6eea79667e3fb722bbd8995bb96f4cd6f.jpg" - } -} diff --git a/record/bullet_screen_stream_json/EVENT_CMD.json b/record/bullet_screen_stream_json/EVENT_CMD.json deleted file mode 100644 index fb1bed7..0000000 --- a/record/bullet_screen_stream_json/EVENT_CMD.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "roomid": 234024, - "cmd": "EVENT_CMD", - "data": { - "event_type": "flower_rain-16915", - "event_img": "http://s1.hdslb.com/bfs/static/blive/live-assets/mobile/activity/lover_2018/raffle.png" - } -} diff --git a/record/bullet_screen_stream_json/GUARD_BUY.json b/record/bullet_screen_stream_json/GUARD_BUY.json deleted file mode 100644 index 0ae435d..0000000 --- a/record/bullet_screen_stream_json/GUARD_BUY.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "cmd": "GUARD_BUY", - "data": { - "uid": 4561799, - "username": "微笑The迪妮莎", - "guard_level": 1, - "num": 1 - }, - "roomid": "5279" -} diff --git a/record/bullet_screen_stream_json/GUARD_LOTTERY_START.json b/record/bullet_screen_stream_json/GUARD_LOTTERY_START.json deleted file mode 100644 index b145335..0000000 --- a/record/bullet_screen_stream_json/GUARD_LOTTERY_START.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "cmd": "GUARD_LOTTERY_START", - "data": { - "id": 396410, - "roomid": 56998, - "message": "ちゆき蝙蝠公主 在【56998】购买了舰长,请前往抽奖", - "type": "guard", - "privilege_type": 3, - "link": "https://live.bilibili.com/56998", - "lottery": { - "id": 396410, - "sender": { - "uid": 11206312, - "uname": "ちゆき蝙蝠公主", - "face": "http://i0.hdslb.com/bfs/face/06d0d58131100acf13d75d3c092b1a58d41b0129.jpg" - }, - "keyword": "guard", - "time": 1200, - "status": 1, - "mobile_display_mode": 2, - "mobile_static_asset": "", - "mobile_animation_asset": "" - } - } -} diff --git a/record/bullet_screen_stream_json/GUARD_MSG.json b/record/bullet_screen_stream_json/GUARD_MSG.json deleted file mode 100644 index 441140e..0000000 --- a/record/bullet_screen_stream_json/GUARD_MSG.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "cmd": "GUARD_MSG", - "msg": "乘客 :?想不想joice:? 成功购买1313366房间总督船票1张,欢迎登船!" -} diff --git a/record/bullet_screen_stream_json/LIVE.json b/record/bullet_screen_stream_json/LIVE.json deleted file mode 100644 index 0d9684b..0000000 --- a/record/bullet_screen_stream_json/LIVE.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "cmd": "LIVE", - "roomid": "1110317" -} diff --git a/record/bullet_screen_stream_json/NOTICE_MSG.json b/record/bullet_screen_stream_json/NOTICE_MSG.json deleted file mode 100644 index 85d6d2a..0000000 --- a/record/bullet_screen_stream_json/NOTICE_MSG.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "cmd": "NOTICE_MSG", - "full": { - "head_icon": "", - "is_anim": 1, - "tail_icon": "", - "background": "#33ffffff", - "color": "#33ffffff", - "highlight": "#33ffffff", - "border": "#33ffffff", - "time": 10 - }, - "half": { - "head_icon": "", - "is_anim": 0, - "tail_icon": "", - "background": "#33ffffff", - "color": "#33ffffff", - "highlight": "#33ffffff", - "border": "#33ffffff", - "time": 8 - }, - "roomid": "11415406", - "real_roomid": "0", - "msg_common": "恭喜\u003c%汤圆老师%\u003e获得大奖\u003c%23333x银瓜子%\u003e, 感谢\u003c%林发发爱林小兔%\u003e的赠送", - "msg_self": "恭喜\u003c%汤圆老师%\u003e获得大奖\u003c%23333x银瓜子%\u003e, 感谢\u003c%林发发爱林小兔%\u003e的赠送", - "link_url": "http://live.bilibili.com/0", - "msg_type": 4 -} diff --git a/record/bullet_screen_stream_json/PK_AGAIN.json b/record/bullet_screen_stream_json/PK_AGAIN.json deleted file mode 100644 index a9233ee..0000000 --- a/record/bullet_screen_stream_json/PK_AGAIN.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "cmd": "PK_AGAIN", - "pk_id": 60672, - "pk_status": 400, - "data": { - "new_pk_id": 60678, - "init_id": 10817769, - "match_id": 1489926, - "escape_all_time": 10, - "escape_time": 10, - "is_portrait": false, - "uname": "穆阿是给你的mua", - "face": "http://i0.hdslb.com/bfs/face/07fa1057b60afe74cdd477f123c6ccf460ee8f2c.jpg", - "uid": 38105366 - }, - "roomid": 1489926 -} diff --git a/record/bullet_screen_stream_json/PK_CLICK_AGAIN.json b/record/bullet_screen_stream_json/PK_CLICK_AGAIN.json deleted file mode 100644 index 8010ed5..0000000 --- a/record/bullet_screen_stream_json/PK_CLICK_AGAIN.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "pk_status": 400, - "pk_id": 60672, - "cmd": "PK_CLICK_AGAIN", - "roomid": 1489926 -} diff --git a/record/bullet_screen_stream_json/PK_END.json b/record/bullet_screen_stream_json/PK_END.json deleted file mode 100644 index 009151b..0000000 --- a/record/bullet_screen_stream_json/PK_END.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "cmd": "PK_END", - "pk_id": 8797, - "pk_status": 400, - "data": { - "init_id": 8049573, - "match_id": 1409458, - "punish_topic": "惩罚:模仿面筋哥" - } -} diff --git a/record/bullet_screen_stream_json/PK_INVITE_FAIL.json b/record/bullet_screen_stream_json/PK_INVITE_FAIL.json deleted file mode 100644 index b36e04e..0000000 --- a/record/bullet_screen_stream_json/PK_INVITE_FAIL.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "cmd": "PK_INVITE_FAIL", - "pk_invite_status": 1100 -} diff --git a/record/bullet_screen_stream_json/PK_INVITE_INIT.json b/record/bullet_screen_stream_json/PK_INVITE_INIT.json deleted file mode 100644 index d6c83c6..0000000 --- a/record/bullet_screen_stream_json/PK_INVITE_INIT.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "cmd": "PK_INVITE_INIT", - "pk_invite_status": 200, - "invite_id": 408, - "face": "http://i0.hdslb.com/bfs/face/e1ad4df39e95180e990fdd565b216662bdb2503c.jpg", - "uname": "Tocci椭奇", - "area_name": "视频聊天", - "user_level": 24, - "master_level": 31, - "roomid": 883802 -} diff --git a/record/bullet_screen_stream_json/PK_INVITE_SWITCH_CLOSE.json b/record/bullet_screen_stream_json/PK_INVITE_SWITCH_CLOSE.json deleted file mode 100644 index 44e8320..0000000 --- a/record/bullet_screen_stream_json/PK_INVITE_SWITCH_CLOSE.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "cmd": "PK_INVITE_SWITCH_CLOSE", - "roomid": 1938890 -} diff --git a/record/bullet_screen_stream_json/PK_INVITE_SWITCH_OPEN.json b/record/bullet_screen_stream_json/PK_INVITE_SWITCH_OPEN.json deleted file mode 100644 index b9227c9..0000000 --- a/record/bullet_screen_stream_json/PK_INVITE_SWITCH_OPEN.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "cmd": "PK_INVITE_SWITCH_OPEN", - "roomid": 9615419 -} diff --git a/record/bullet_screen_stream_json/PK_MATCH.json b/record/bullet_screen_stream_json/PK_MATCH.json deleted file mode 100644 index 040a219..0000000 --- a/record/bullet_screen_stream_json/PK_MATCH.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "cmd": "PK_MATCH", - "pk_status": 100, - "pk_id": 3596, - "data": { - "init_id": 9615419, - "match_id": 10185039, - "escape_time": 5, - "is_portrait": false, - "uname": "茉莉艿", - "face": "http://i2.hdslb.com/bfs/face/3f2833a3ac598d9757ba33b79ec219cf941bdda8.jpg", - "uid": 18963076 - }, - "roomid": 9615419 -} diff --git a/record/bullet_screen_stream_json/PK_MIC_END.json b/record/bullet_screen_stream_json/PK_MIC_END.json deleted file mode 100644 index 88aebe2..0000000 --- a/record/bullet_screen_stream_json/PK_MIC_END.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "cmd": "PK_MIC_END", - "pk_id": 3596, - "pk_status": 1300, - "data": { - "type": 0 - } -} diff --git a/record/bullet_screen_stream_json/PK_PRE.json b/record/bullet_screen_stream_json/PK_PRE.json deleted file mode 100644 index e40f3ac..0000000 --- a/record/bullet_screen_stream_json/PK_PRE.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "cmd": "PK_PRE", - "pk_id": 3597, - "pk_status": 200, - "data": { - "init_id": 9615419, - "match_id": 10185039, - "count_down": 5, - "pk_topic": "模仿游戏角色让对方猜", - "pk_pre_time": 1529476609, - "pk_start_time": 1529476614, - "pk_end_time": 1529476914, - "end_time": 1529477034 - } -} diff --git a/record/bullet_screen_stream_json/PK_PROCESS.json b/record/bullet_screen_stream_json/PK_PROCESS.json deleted file mode 100644 index 62f1517..0000000 --- a/record/bullet_screen_stream_json/PK_PROCESS.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "cmd": "PK_PROCESS", - "pk_id": 8798, - "pk_status": 300, - "data": { - "uid": 0, - "init_votes": 30, - "match_votes": 20, - "user_votes": 0 - }, - "roomid": 346075 -} diff --git a/record/bullet_screen_stream_json/PK_SETTLE.json b/record/bullet_screen_stream_json/PK_SETTLE.json deleted file mode 100644 index 1fd27b1..0000000 --- a/record/bullet_screen_stream_json/PK_SETTLE.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "cmd": "PK_SETTLE", - "pk_id": 8806, - "pk_status": 400, - "data": { - "pk_id": 8806, - "init_info": { - "uid": 7799328, - "init_id": 10979759, - "uname": "筱宇淅淅", - "face": "http://i0.hdslb.com/bfs/face/e16515ac39329aa125bb8de5bb1fa9455f06337c.jpg", - "votes": 0, - "is_winner": false - }, - "match_info": { - "uid": 18654316, - "match_id": 430063, - "uname": "卖丸子尕害羞", - "face": "http://i1.hdslb.com/bfs/face/1c579a244ec0c66bbb6e2ad6c770a2a498268735.jpg", - "votes": 129, - "is_winner": true, - "vip_type": 0, - "exp": { - "color": 5805790, - "user_level": 31, - "master_level": { - "level": 26, - "color": 10512625 - } - }, - "vip": { - "vip": 0, - "svip": 0 - }, - "face_frame": "", - "badge": { - "url": "http://i0.hdslb.com/bfs/live/74b2f9a48ce14d752dd27559c4a0df297243a3fd.png", - "desc": "bilibili直播签约主播\r\n", - "position": 3 - } - }, - "best_user": { - "uid": 31459309, - "uname": "七友球球", - "face": "http://i1.hdslb.com/bfs/face/09406a4fe632dda9d523da14f3e3735ee02efbab.jpg", - "vip_type": 0, - "exp": { - "color": 6406234, - "user_level": 19, - "master_level": { - "level": 1, - "color": 6406234 - } - }, - "vip": { - "vip": 0, - "svip": 0 - }, - "privilege_type": 0, - "face_frame": "", - "badge": { - "url": "", - "desc": "", - "position": 0 - } - }, - "punish_topic": "惩罚:模仿一款表情包" - } -} diff --git a/record/bullet_screen_stream_json/PK_START.json b/record/bullet_screen_stream_json/PK_START.json deleted file mode 100644 index 9fa98bc..0000000 --- a/record/bullet_screen_stream_json/PK_START.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "cmd": "PK_START", - "pk_id": 3597, - "pk_status": 300, - "data": { - "init_id": 9615419, - "match_id": 10185039, - "pk_topic": "模仿游戏角色让对方猜" - } -} diff --git a/record/bullet_screen_stream_json/PREPARING.json b/record/bullet_screen_stream_json/PREPARING.json deleted file mode 100644 index 2cfea1e..0000000 --- a/record/bullet_screen_stream_json/PREPARING.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "cmd": "PREPARING", - "roomid": "1110317" -} diff --git a/record/bullet_screen_stream_json/RAFFLE_END.json b/record/bullet_screen_stream_json/RAFFLE_END.json deleted file mode 100644 index c132aa3..0000000 --- a/record/bullet_screen_stream_json/RAFFLE_END.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "cmd": "RAFFLE_END", - "roomid": 521429, - "data": { - "raffleId": 16897, - "type": "flower_rain", - "from": "鷺沢怜人", - "fromFace": "http://i1.hdslb.com/bfs/face/09eafe44f913012512014e91f25001edf6e072d0.jpg", - "win": { - "uname": "nbqgd", - "face": "http://i1.hdslb.com/bfs/face/09eafe44f913012512014e91f25001edf6e072d0.jpg", - "giftId": 115, - "giftName": "桃花", - "giftNum": 66 - } - } -} diff --git a/record/bullet_screen_stream_json/RAFFLE_START.json b/record/bullet_screen_stream_json/RAFFLE_START.json deleted file mode 100644 index 340117b..0000000 --- a/record/bullet_screen_stream_json/RAFFLE_START.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "cmd": "RAFFLE_START", - "roomid": 234024, - "data": { - "raffleId": 16915, - "type": "flower_rain", - "from": "爱吃喵姐的鱼", - "time": 60 - } -} diff --git a/record/bullet_screen_stream_json/ROOM_ADMINS.json b/record/bullet_screen_stream_json/ROOM_ADMINS.json deleted file mode 100644 index e44f830..0000000 --- a/record/bullet_screen_stream_json/ROOM_ADMINS.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "cmd": "ROOM_ADMINS", - "uids": [ - 4561799, - 432672, - 2179804, - 7928207, - 94380, - 1626161, - 3168349, - 13182672 - ], - "roomid": 5279 -} diff --git a/record/bullet_screen_stream_json/ROOM_BLOCK_MSG.json b/record/bullet_screen_stream_json/ROOM_BLOCK_MSG.json deleted file mode 100644 index e09eb15..0000000 --- a/record/bullet_screen_stream_json/ROOM_BLOCK_MSG.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "cmd": "ROOM_BLOCK_MSG", - "uid": "60244207", - "uname": "承包rose", - "roomid": 5279 -} diff --git a/record/bullet_screen_stream_json/ROOM_LOCK.json b/record/bullet_screen_stream_json/ROOM_LOCK.json deleted file mode 100644 index 2e2a152..0000000 --- a/record/bullet_screen_stream_json/ROOM_LOCK.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "cmd": "ROOM_LOCK", - "expire": "2018-03-15 10:24:18", - "roomid": 6477301 -} diff --git a/record/bullet_screen_stream_json/ROOM_RANK.json b/record/bullet_screen_stream_json/ROOM_RANK.json deleted file mode 100644 index ca793dd..0000000 --- a/record/bullet_screen_stream_json/ROOM_RANK.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "cmd": "ROOM_RANK", - "data": { - "roomid": 1241012, - "rank_desc": "小时榜 182", - "color": "#FB7299", - "h5_url": "https://live.bilibili.com/p/eden/rank-h5-current?anchor_uid\u003d35577726", - "timestamp": 1527148082 - } -} diff --git a/record/bullet_screen_stream_json/ROOM_SHIELD(1).json b/record/bullet_screen_stream_json/ROOM_SHIELD(1).json deleted file mode 100644 index 623612a..0000000 --- a/record/bullet_screen_stream_json/ROOM_SHIELD(1).json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cmd": "ROOM_SHIELD", - "type": 1, - "user": "", - "keyword": "", - "roomid": 234024 -} diff --git a/record/bullet_screen_stream_json/ROOM_SHIELD.json b/record/bullet_screen_stream_json/ROOM_SHIELD.json deleted file mode 100644 index 5df5508..0000000 --- a/record/bullet_screen_stream_json/ROOM_SHIELD.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "cmd": "ROOM_SHIELD", - "type": 1, - "user": [], - "keyword": [ - "暗号", - "摄像头", - "色相头" - ], - "roomid": 505447 -} diff --git a/record/bullet_screen_stream_json/ROOM_SILENT_OFF.json b/record/bullet_screen_stream_json/ROOM_SILENT_OFF.json deleted file mode 100644 index d6e61a0..0000000 --- a/record/bullet_screen_stream_json/ROOM_SILENT_OFF.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "cmd": "ROOM_SILENT_OFF", - "data": [], - "roomid": "29434" -} diff --git a/record/bullet_screen_stream_json/ROOM_SILENT_ON.json b/record/bullet_screen_stream_json/ROOM_SILENT_ON.json deleted file mode 100644 index c17efff..0000000 --- a/record/bullet_screen_stream_json/ROOM_SILENT_ON.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "cmd": "ROOM_SILENT_ON", - "data": { - "type": "level", - "level": 1, - "second": 1520424615 - }, - "roomid": 5279 -} diff --git a/record/bullet_screen_stream_json/SEND_GIFT.json b/record/bullet_screen_stream_json/SEND_GIFT.json deleted file mode 100644 index 95be84d..0000000 --- a/record/bullet_screen_stream_json/SEND_GIFT.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "cmd": "SEND_GIFT", - "data": { - "giftName": "节奏风暴", - "num": 1, - "uname": "爱上熹", - "rcost": 569788, - "uid": 230845505, - "top_list": [ - { - "uid": 288348879, - "uname": "我爱我家一生", - "face": "http://i1.hdslb.com/bfs/face/dd52e4f2dfe881751816e45522f504f10458b514.jpg", - "rank": 1, - "score": 1852300, - "guard_level": 0, - "isSelf": 0 - }, - { - "uid": 287551243, - "uname": "熹上城的专属天使菲", - "face": "http://i1.hdslb.com/bfs/face/c3ef04ba6c267c41067cd7708b7abd60c0c5c49f.jpg", - "rank": 2, - "score": 1245200, - "guard_level": 3, - "isSelf": 0 - }, - { - "uid": 32416351, - "uname": "镜子。。", - "face": "http://i1.hdslb.com/bfs/face/08c54c2c97434811a99e9d070d621ccbb5d3f2c4.jpg", - "rank": 3, - "score": 332862, - "guard_level": 3, - "isSelf": 0 - } - ], - "timestamp": 1520992553, - "giftId": 39, - "giftType": 0, - "action": "赠送", - "super": 1, - "super_gift_num": 1, - "price": 100000, - "rnd": "1980508331", - "newMedal": 0, - "newTitle": 0, - "medal": { - "medalId": "95723", - "medalName": "布丁诶", - "level": 1 - }, - "title": "", - "beatId": "4", - "biz_source": "live", - "metadata": "", - "remain": 0, - "gold": 88570, - "silver": 127492, - "eventScore": 0, - "eventNum": 0, - "smalltv_msg": [], - "specialGift": { - "id": "316221038798", - "time": 90, - "hadJoin": 0, - "num": 1, - "content": "你们城里人真会玩", - "action": "start", - "storm_gif": "http://static.hdslb.com/live-static/live-room/images/gift-section/mobilegift/2/jiezou.gif?2017011901" - }, - "notice_msg": [], - "capsule": { - "normal": { - "coin": 166, - "change": 10, - "progress": { - "now": 3630, - "max": 10000 - } - }, - "colorful": { - "coin": 2, - "change": 0, - "progress": { - "now": 0, - "max": 5000 - } - }, - "move": 1 - }, - "addFollow": 0, - "effect_block": 0, - "coin_type": "gold", - "total_coin": 100000 - } -} diff --git a/record/bullet_screen_stream_json/SPECIAL_GIFT(节奏风暴开始).json b/record/bullet_screen_stream_json/SPECIAL_GIFT(节奏风暴开始).json deleted file mode 100644 index 3288759..0000000 --- a/record/bullet_screen_stream_json/SPECIAL_GIFT(节奏风暴开始).json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "cmd": "SPECIAL_GIFT", - "data": { - "39": { - "id": 214692, - "time": 90, - "hadJoin": 0, - "num": 1, - "content": "前方高能预警,注意这不是演习", - "action": "start", - "storm_gif": "http://static.hdslb.com/live-static/live-room/images/gift-section/mobilegift/2/jiezou.gif?2017011901" - } - } -} diff --git a/record/bullet_screen_stream_json/SPECIAL_GIFT(节奏风暴结束).json b/record/bullet_screen_stream_json/SPECIAL_GIFT(节奏风暴结束).json deleted file mode 100644 index d0bfd31..0000000 --- a/record/bullet_screen_stream_json/SPECIAL_GIFT(节奏风暴结束).json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "cmd": "SPECIAL_GIFT", - "data": { - "39": { - "id": 214692, - "time": 0, - "hadJoin": 0, - "num": 0, - "action": "end" - } - } -} diff --git a/record/bullet_screen_stream_json/SYS_GIFT(普通礼物,不可抽奖).json b/record/bullet_screen_stream_json/SYS_GIFT(普通礼物,不可抽奖).json deleted file mode 100644 index cf55e8d..0000000 --- a/record/bullet_screen_stream_json/SYS_GIFT(普通礼物,不可抽奖).json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "cmd": "SYS_GIFT", - "msg": "jjhghhfgh:? 在蜜桃姐姐w的:?直播间7813816:?内赠送:?6:?共450个", - "msg_text": "jjhghhfgh在蜜桃姐姐w的直播间7813816内赠送亿圆共450个", - "roomid": 0, - "real_roomid": 0, - "giftId": 0, - "msgTips": 0 -} diff --git a/record/bullet_screen_stream_json/SYS_GIFT(活动礼物).json b/record/bullet_screen_stream_json/SYS_GIFT(活动礼物).json deleted file mode 100644 index 8ba9948..0000000 --- a/record/bullet_screen_stream_json/SYS_GIFT(活动礼物).json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "cmd": "SYS_GIFT", - "msg": "【情怀家的尹蓝ovo】在直播间【147191】洒下漫天花雨,快来拾撷桃花,邂逅你的缘分!", - "msg_text": "【情怀家的尹蓝ovo】在直播间【147191】洒下漫天花雨,快来拾撷桃花,邂逅你的缘分!", - "tips": "【情怀家的尹蓝ovo】在直播间【147191】洒下漫天花雨,快来拾撷桃花,邂逅你的缘分!", - "url": "http://live.bilibili.com/147191", - "roomid": 147191, - "real_roomid": 147191, - "giftId": 116, - "msgTips": 0 -} diff --git a/record/bullet_screen_stream_json/SYS_GIFT(节奏风暴).json b/record/bullet_screen_stream_json/SYS_GIFT(节奏风暴).json deleted file mode 100644 index c56d79e..0000000 --- a/record/bullet_screen_stream_json/SYS_GIFT(节奏风暴).json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "cmd": "SYS_GIFT", - "msg": "十四不落:? 在直播间 :?590:? 使用了 20 倍节奏风暴,大家快去跟风领取奖励吧!", - "tips": "【十四不落】在直播间【590】使用了 20 倍节奏风暴,大家快去跟风领取奖励吧!", - "url": "http://live.bilibili.com/590", - "roomid": 847617, - "real_roomid": 0, - "giftId": 39, - "msgTips": 1 -} diff --git a/record/bullet_screen_stream_json/SYS_MSG.json b/record/bullet_screen_stream_json/SYS_MSG.json deleted file mode 100644 index f3e5c02..0000000 --- a/record/bullet_screen_stream_json/SYS_MSG.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "cmd": "SYS_MSG", - "msg": "【天南地狗-】:?在直播间:?【531】:?赠送 小电视一个,请前往抽奖", - "msg_text": "【天南地狗-】:?在直播间:?【531】:?赠送 小电视一个,请前往抽奖", - "rep": 1, - "styleType": 2, - "url": "http://live.bilibili.com/531", - "roomid": 531, - "real_roomid": 22237, - "rnd": 1520992662, - "tv_id": "40478" -} diff --git a/record/bullet_screen_stream_json/TV_END.json b/record/bullet_screen_stream_json/TV_END.json deleted file mode 100644 index 00ce340..0000000 --- a/record/bullet_screen_stream_json/TV_END.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "cmd": "TV_END", - "data": { - "id": "39077", - "uname": "かこゆきこvew", - "sname": "是你的苏苏吖", - "giftName": "10W银瓜子", - "mobileTips": "恭喜 かこゆきこvew 获得10W银瓜子", - "raffleId": "39077", - "type": "small_tv", - "from": "是你的苏苏吖", - "fromFace": "http://i0.hdslb.com/bfs/face/147f137d24138d1cfec5443d98ac8b03c4332398.jpg", - "win": { - "uname": "かこゆきこvew", - "face": "http://i0.hdslb.com/bfs/face/4d63bd62322e7f3ef38723a91440bc6930626d9f.jpg", - "giftName": "银瓜子", - "giftId": "silver", - "giftNum": 100000 - } - } -} diff --git a/record/bullet_screen_stream_json/TV_START.json b/record/bullet_screen_stream_json/TV_START.json deleted file mode 100644 index 6abdc17..0000000 --- a/record/bullet_screen_stream_json/TV_START.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "cmd": "TV_START", - "data": { - "id": "40072", - "dtime": 180, - "msg": { - "cmd": "SYS_MSG", - "msg": "【杰宝Yvan生命倒计时】:?在直播间:?【102】:?赠送 小电视一个,请前往抽奖", - "msg_text": "【杰宝Yvan生命倒计时】:?在直播间:?【102】:?赠送 小电视一个,请前往抽奖", - "rep": 1, - "styleType": 2, - "url": "http://live.bilibili.com/102", - "roomid": 102, - "real_roomid": 5279, - "rnd": 12987955, - "tv_id": "40072" - }, - "raffleId": 40072, - "type": "small_tv", - "from": "杰宝Yvan生命倒计时", - "time": 180 - } -} diff --git a/record/bullet_screen_stream_json/WARNING.json b/record/bullet_screen_stream_json/WARNING.json deleted file mode 100644 index 5c0abbc..0000000 --- a/record/bullet_screen_stream_json/WARNING.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "cmd": "WARNING", - "msg": "违反直播分区规范,请立即更换至游戏区", - "roomid": 1365604 -} diff --git a/record/bullet_screen_stream_json/WELCOME.json b/record/bullet_screen_stream_json/WELCOME.json deleted file mode 100644 index 0ed3e89..0000000 --- a/record/bullet_screen_stream_json/WELCOME.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "cmd": "WELCOME", - "data": { - "uid": 18625858, - "uname": "\u662f\u767d\u8272\u70e4\u6f06", - "isadmin": 0, - "svip": 1 - }, - "roomid": 39189 -} diff --git a/record/bullet_screen_stream_json/WELCOME_ACTIVITY.json b/record/bullet_screen_stream_json/WELCOME_ACTIVITY.json deleted file mode 100644 index 38ebeee..0000000 --- a/record/bullet_screen_stream_json/WELCOME_ACTIVITY.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "cmd": "WELCOME_ACTIVITY", - "data": { - "uid": 49427998, - "uname": "起个名真tm费事", - "type": "forever_love", - "display_mode": 1 - } -} diff --git a/record/bullet_screen_stream_json/WELCOME_GUARD.json b/record/bullet_screen_stream_json/WELCOME_GUARD.json deleted file mode 100644 index 473fe09..0000000 --- a/record/bullet_screen_stream_json/WELCOME_GUARD.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "cmd": "WELCOME_GUARD", - "data": { - "uid": 23598108, - "username": "lovevael", - "guard_level": 3, - "water_god": 0 - }, - "roomid": 43001 -} diff --git a/record/bullet_screen_stream_json/WISH_BOTTLE.json b/record/bullet_screen_stream_json/WISH_BOTTLE.json deleted file mode 100644 index 6e29313..0000000 --- a/record/bullet_screen_stream_json/WISH_BOTTLE.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "cmd": "WISH_BOTTLE", - "data": { - "action": "update", - "id": 1832, - "wish": { - "id": 1832, - "uid": 110631, - "type": 1, - "type_id": 7, - "wish_limit": 99999, - "wish_progress": 14381, - "status": 1, - "content": "女装直播", - "ctime": "2018-01-12 17:25:58", - "count_map": [ - 1, - 3, - 5 - ] - } - } -} diff --git a/record/视频弹幕/danmaku.xml b/record/视频弹幕/danmaku.xml new file mode 100644 index 0000000..eb76693 --- /dev/null +++ b/record/视频弹幕/danmaku.xml @@ -0,0 +1,132 @@ + + + + 77932184 + 0 + 196000 + 1 + 1 + 0 + 0 + + + + + + + + + + + 硬核劈柴 + 2222 + 神奇的三哥,没有什么是他们顶不了的 + 这水是甜的吧 + 真 逃生 + 非常优秀 + 中国,赞 + 逃离生活的窗 + 嘴冲? + 口冲 + 自取其乳 + 666 + 这脖子 + 这脖子是铁打的吧 + 333 + 666啊 + 四倍体草莓 + 高层建筑通云梯的窗台 + 3338 + 哈哈 + 你告诉我哪里有这么高的云梯 + 逃离生活窗 + 自重最少三百公斤的玩意顶在头上还能单手爬楼梯,这是人能做到的吗 + 前功尽弃系列 + 深圳会展中心? + 求这女孩的的体重 马上 + 蘸糖墩儿 + 我们家的 + 牛逼 + 卧槽 + 生活重来窗 + 糖墩儿那个,我们是老乡 + 卧槽 + 上化佛他们能顶么? + 九星虹梅 + 6666 + 快看 是岳云鹏 + 军人nb + 这是练什么,你们成天笑印度人,敢不敢把这个给印度人看 + 一辈子单身 + 这tm成了灵芝了 + 要坚强 + 草莓:我控制不住我的生长 + 铁头功 + 站军姿,身体要前倾 + 牛逼 + 这个上初中时被班主任罚站,就是在台阶上用脚尖站 + 小腿肚子疼 + 江科炸出来 + 一句卧槽行天下 + 禁止自娱自乐 + 一句卧槽行走天下 + 这个是真的难受!!! + 还有卧槽 + 逃出升天 + 我也这么站过 + 除了牛逼还可以说盖帽 + 半挂 + 一个下去一排倒 + 强迫症不能忍 + 硬核劈材 + 我们也这么站过 + 卧槽 + 我大江科 + 功亏一篑 + 我只会说:卧槽 + 亲媳妇 + 手炉? + 甜辣口的 + 这个是高手 + 逃离生命 + 摩托精? + 公的 + 想看三哥顶汽车 + 好了,站5个小时 + 逃离生活的窗户 + 众所周知,逃生=逃出生天=逃出,生天 + 金字塔是不是他们顶上去的 + 腰间盘突出了解一下 + 俺也一样 + 可以说 卧槽 + 我了个大草 + 兄弟帽子不错 + 梯子牛逼 + 老子最讨厌女人了!滚! + 还有这种操作!? + 被谁淹没不知所措 + 奈何本人无文化,一句卧槽走天下 + 好心酸。 + 我顶不住 + 她有一个大胆的想法 + 那个不是西葫芦么。。西葫芦甜的??? + 舒服 + 牛了个逼 + 还可以说卧槽 + 还可以说卧槽 + 他说弯腰下去继续蘸酱么? + + 牛逼 + 水瓜 + 模型出了点问题 + 逃出生天 + 阿三是真牛逼… + 回首掏 惨不忍睹 + 帅啊 + 这孩子的弹跳力惊人 + 这是高手 + 这是高手 + 吉尼斯记录三哥就顶的车 + 这是高手 + 八倍体草莓 + diff --git a/record/视频弹幕/list.so b/record/视频弹幕/list.so new file mode 100644 index 0000000..cf76698 Binary files /dev/null and b/record/视频弹幕/list.so differ diff --git a/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClient.kt b/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClient.kt index 6b6310c..00cd920 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClient.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClient.kt @@ -1,6 +1,7 @@ package com.hiczp.bilibili.api import com.hiczp.bilibili.api.app.AppAPI +import com.hiczp.bilibili.api.danmaku.DanmakuAPI import com.hiczp.bilibili.api.main.MainAPI import com.hiczp.bilibili.api.member.MemberAPI import com.hiczp.bilibili.api.message.MessageAPI @@ -8,6 +9,7 @@ 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.ParamType import com.hiczp.bilibili.api.retrofit.exception.BilibiliApiException @@ -72,21 +74,21 @@ class BilibiliClient( @Suppress("SpellCheckingInspection") private val defaultCommonHeaderInterceptor = CommonHeaderInterceptor( - "Display-ID" to { "${billingClientProperties.buildVersionId}-$initTime" }, - "Buvid" to { billingClientProperties.buildVersionId }, - "User-Agent" to { "Mozilla/5.0 BiliDroid/5.37.0 (bbcallen@gmail.com)" }, - "Device-ID" to { billingClientProperties.hardwareId } + 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 }, - "build" to { billingClientProperties.build }, - "channel" to { billingClientProperties.channel }, - "mobi_app" to { billingClientProperties.platform }, - "platform" to { billingClientProperties.platform }, - "ts" to { Instant.now().epochSecond.toString() } + 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 defaultCommonQueryParamInterceptor = CommonParamInterceptor(ParamType.QUERY, *defaultCommonParamArray) @@ -100,11 +102,11 @@ class BilibiliClient( defaultCommonHeaderInterceptor, CommonParamInterceptor(ParamType.FORM_URL_ENCODED, Param.APP_KEY to { billingClientProperties.appKey }, - "build" to { billingClientProperties.build }, - "channel" to { billingClientProperties.channel }, - "mobi_app" to { billingClientProperties.platform }, - "platform" to { billingClientProperties.platform }, - "ts" to { Instant.now().epochSecond.toString() } + 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() } ) ) } @@ -117,7 +119,7 @@ class BilibiliClient( createAPI(BaseUrl.message, defaultCommonHeaderInterceptor, CommonParamInterceptor(ParamType.QUERY, *defaultCommonParamArray, - "actionKey" to { "appkey" }, + Param.ACTION_KEY to { Param.APP_KEY }, "has_up" to { "1" } ) ) @@ -142,10 +144,10 @@ class BilibiliClient( createAPI(BaseUrl.main, CommonHeaderInterceptor( //如果未登陆则没有 Display-ID - "Display-ID" to { userId?.let { "$it-$initTime" } }, - "Buvid" to { billingClientProperties.buildVersionId }, - "User-Agent" to { "Mozilla/5.0 BiliDroid/5.37.0 (bbcallen@gmail.com)" }, - "Device-ID" to { billingClientProperties.hardwareId } + 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 } ), defaultCommonQueryParamInterceptor ) @@ -159,12 +161,12 @@ class BilibiliClient( createAPI(BaseUrl.vc, defaultCommonHeaderInterceptor, CommonParamInterceptor(ParamType.QUERY, *defaultCommonParamArray, - "_device" to { billingClientProperties.platform }, - "_hwid" to { billingClientProperties.hardwareId }, - "src" to { billingClientProperties.channel }, - "trace_id" to { generateTraceId() }, - "uid" to { userId?.toString() }, - "version" to { billingClientProperties.version } + 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 } ) ) } @@ -190,15 +192,33 @@ class BilibiliClient( .client(OkHttpClient.Builder().apply { addInterceptor(PlayerInterceptor(billingClientProperties) { loginResponse }) addInterceptor(FailureResponseInterceptor) - //log - if (logLevel != HttpLoggingInterceptor.Level.NONE) { - addNetworkInterceptor(HttpLoggingInterceptor().setLevel(logLevel)) - } + addNetworkInterceptor(httpLoggingInterceptor) }.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(defaultCommonQueryParamInterceptor) + addInterceptor(sortAndSignInterceptor) + addNetworkInterceptor(httpLoggingInterceptor) + }.build()) + .build() + .create(DanmakuAPI::class.java) + } + /** * 登陆 * v3 登陆接口会同时返回 cookies 和 token @@ -269,6 +289,7 @@ class BilibiliClient( fun logoutFuture() = GlobalScope.future { logout() } private val sortAndSignInterceptor = SortAndSignInterceptor(billingClientProperties.appSecret) + private val httpLoggingInterceptor = HttpLoggingInterceptor().setLevel(logLevel) private inline fun createAPI( baseUrl: String, vararg interceptors: Interceptor @@ -282,10 +303,7 @@ class BilibiliClient( } addInterceptor(sortAndSignInterceptor) addInterceptor(FailureResponseInterceptor) - //log - if (logLevel != HttpLoggingInterceptor.Level.NONE) { - addNetworkInterceptor(HttpLoggingInterceptor().setLevel(logLevel)) - } + addNetworkInterceptor(httpLoggingInterceptor) }.build()) .build() .create(T::class.java) diff --git a/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClientProperties.kt b/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClientProperties.kt index 39e9a10..d294155 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClientProperties.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClientProperties.kt @@ -5,6 +5,12 @@ 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(该默认值为普通版客户端, 非概念版) */ diff --git a/src/main/kotlin/com/hiczp/bilibili/api/CipherExtension.kt b/src/main/kotlin/com/hiczp/bilibili/api/CipherExtension.kt index 36384d3..7407dfc 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/CipherExtension.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/CipherExtension.kt @@ -5,7 +5,7 @@ import java.security.MessageDigest //MD5 private val md5Instance = MessageDigest.getInstance("MD5") -internal fun String.md5() = +fun String.md5() = StringBuilder(32).apply { //优化过的 md5 字符串生成算法 md5Instance.digest(toByteArray()).forEach { diff --git a/src/main/kotlin/com/hiczp/bilibili/api/StreamExtension.kt b/src/main/kotlin/com/hiczp/bilibili/api/StreamExtension.kt new file mode 100644 index 0000000..7727952 --- /dev/null +++ b/src/main/kotlin/com/hiczp/bilibili/api/StreamExtension.kt @@ -0,0 +1,30 @@ +package com.hiczp.bilibili.api + +import org.apache.commons.io.input.BoundedInputStream +import org.apache.commons.io.input.BoundedReader +import java.io.InputStream +import java.nio.charset.Charset + +/** + * 以大端模式从流中读取一个 int + */ +fun InputStream.readInt(): Int { + val byteArray = ByteArray(4).apply { + read(this) + } + return (byteArray[0].toInt() shl 24) or + (byteArray[1].toInt() shl 16) or + (byteArray[2].toInt() shl 8) or + (byteArray[3].toInt()) +} + +@Suppress("EXPERIMENTAL_API_USAGE") +fun InputStream.readUInt() = readInt().toUInt() + +fun InputStream.boundedReader(maxCharsFromTargetReader: Int, charset: Charset = Charsets.UTF_8) = + BoundedReader(reader(charset), maxCharsFromTargetReader) + +fun InputStream.bounded(size: Long) = BoundedInputStream(this, size) + +@Suppress("EXPERIMENTAL_API_USAGE") +fun InputStream.bounded(size: UInt) = bounded(size.toLong()) 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 52a29de..114978e 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/app/AppAPI.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/app/AppAPI.kt @@ -82,6 +82,7 @@ interface AppAPI { * 视频页面(普通视频, 非番剧) * 包含视频基本信息, 推荐和广告 * 从这个接口得到视频的 cid + * 如果返回内容里的 pages 有多个表明有多个 p, 每个 p 有自己的 cid(外层的 cid 为默认的那个 p 的 cid) * * @param aid 视频的唯一标识 */ diff --git a/src/main/kotlin/com/hiczp/bilibili/api/danmaku/Danmaku.kt b/src/main/kotlin/com/hiczp/bilibili/api/danmaku/Danmaku.kt new file mode 100644 index 0000000..b75655c --- /dev/null +++ b/src/main/kotlin/com/hiczp/bilibili/api/danmaku/Danmaku.kt @@ -0,0 +1,54 @@ +package com.hiczp.bilibili.api.danmaku + +data class Danmaku( + /** + * 弹幕 id + */ + val id: Long, + + /** + * 下标 1, 不明属性 + */ + val unknownAttribute1: String, + + /** + * 弹幕出现时间(播放器时间)(ms) + */ + val time: Long, + + /** + * 弹幕类型 + * (1从右至左滚动弹幕|6从左至右滚动弹幕|5顶端固定弹幕|4底端固定弹幕|7高级弹幕|8脚本弹幕) + */ + val type: Int, + + /** + * 字号 + */ + val fontSize: Int, + + /** + * 颜色 + */ + val color: Int, + + /** + * 弹幕的发送时间(时间戳)(s) + */ + val timestamp: Long, + + /** + * 下标 7, 不明属性 + */ + val unknownAttribute7: String, + + /** + * 弹幕发送者的 hash(用户 id 的 CRC32 校验和) + */ + val user: String, + + /** + * 弹幕的内容 + */ + val content: String +) diff --git a/src/main/kotlin/com/hiczp/bilibili/api/danmaku/DanmakuAPI.kt b/src/main/kotlin/com/hiczp/bilibili/api/danmaku/DanmakuAPI.kt new file mode 100644 index 0000000..8565378 --- /dev/null +++ b/src/main/kotlin/com/hiczp/bilibili/api/danmaku/DanmakuAPI.kt @@ -0,0 +1,26 @@ +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 +} diff --git a/src/main/kotlin/com/hiczp/bilibili/api/danmaku/DanmakuParser.kt b/src/main/kotlin/com/hiczp/bilibili/api/danmaku/DanmakuParser.kt new file mode 100644 index 0000000..8bea41a --- /dev/null +++ b/src/main/kotlin/com/hiczp/bilibili/api/danmaku/DanmakuParser.kt @@ -0,0 +1,126 @@ +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 部分形如 硬核劈柴 + * + * @see com.hiczp.bilibili.api.danmaku.DanmakuAPI.list + */ +@Suppress("SpellCheckingInspection") +class DanmakuParser { + companion object { + /** + * 解析弹幕文件 + * + * @param inputStream 输入流, 可以指向任何位置 + * @param autoClose 是否在解析后自动关闭流 + * + * @return 返回 flags map 与 弹幕列表. 注意, 原始的弹幕顺序是按发送时间来排的, 而非播放器时间. + */ + @JvmStatic + fun parser(inputStream: InputStream, autoClose: Boolean = true): Pair, List> { + //Json 的长度 + val jsonLength = inputStream.readUInt() + + //弹幕ID-Flag + val danmakuFlags = HashMap() + //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() + val danmakus = LinkedList() + //流式解析 xml + val xmlEventReader = XMLInputFactory.newInstance().createXMLEventReader(reader) + 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 + ) + } + danmakus.add(danmaku) + } + } + } + } + + //自动关闭流 + if (autoClose) inputStream.close() + + return danmakuFlags to danmakus + } + + //常量, 用于加快速度 + @JvmStatic + private val P = QName("p") + } +} diff --git a/src/main/kotlin/com/hiczp/bilibili/api/player/PlayerAPI.kt b/src/main/kotlin/com/hiczp/bilibili/api/player/PlayerAPI.kt index b30b2e2..7fab9c2 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/player/PlayerAPI.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/player/PlayerAPI.kt @@ -10,6 +10,11 @@ import java.lang.management.ManagementFactory /** * 这里是播放器会访问的 API + * 返回内容中会有多个视频下载地址, 他们代表不同的视频质量(音频同理) + * 音频和视频是分开的 + * 下载视频得到的是一个 m4s 文件, 但是实际上是完整的视频(例如整个番剧而非片段) + * 下载音频得到的也是一个 m4s 文件, 也是完整的 + * 将视频和音频合在一起, 就可以播放了 */ @Suppress("DeferredIsResult", "SpellCheckingInspection") interface PlayerAPI { @@ -38,7 +43,7 @@ interface PlayerAPI { * * @param aid 番剧的唯一标识 * @param cid 在番剧详情页的返回值里 - * @param seasonType 分级类型, 不明确, 似乎总为 1 + * @param seasonType 分季类型, 不明确, 似乎总为 1 * @param session 其值为 系统已运行时间(ms)的MD5值, 此处的默认值为 JVM 已启动时间, 在 Android 上请使用 SystemClock * @param trackPath 不明确 * diff --git a/src/main/kotlin/com/hiczp/bilibili/api/player/PlayerInterceptor.kt b/src/main/kotlin/com/hiczp/bilibili/api/player/PlayerInterceptor.kt index d2d3125..fdd3eff 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/player/PlayerInterceptor.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/player/PlayerInterceptor.kt @@ -26,9 +26,9 @@ class PlayerInterceptor( //添加 header val header = request.headers().newBuilder().apply { - add("Accept", "*/*") - add("User-Agent", "Bilibili Freedoooooom/MarkII") - add("Accept-Language", Header.ZH_CN) + add(Header.ACCEPT, "*/*") + add(Header.USER_AGENT, "Bilibili Freedoooooom/MarkII") + add(Header.ACCEPT_LANGUAGE, "zh-CN,zh;q=0.8") }.build() //添加 Query Params @@ -50,13 +50,13 @@ class PlayerInterceptor( addParamEncode(Param.MID, "0") } //公共参数 - addParamEncode("device", bilibiliClientProperties.platform) - addParamEncode("mobi_app", bilibiliClientProperties.platform) - addParamEncode("platform", bilibiliClientProperties.platform) + addParamEncode(Param.DEVICE, bilibiliClientProperties.platform) + addParamEncode(Param.MOBILE_APP, bilibiliClientProperties.platform) + addParamEncode(Param.PLATFORM, bilibiliClientProperties.platform) addParamEncode("otype", "json") - addParamEncode("ts", Instant.now().epochSecond.toString()) - addParamEncode("build", bilibiliClientProperties.build) - addParamEncode("buvid", bilibiliClientProperties.buildVersionId) + 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 = "&") diff --git a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/HttpConstant.kt b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/HttpConstant.kt index dd8ac83..90d5ae1 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/HttpConstant.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/HttpConstant.kt @@ -12,15 +12,38 @@ object Method { } object Header { - const val JSON = "application/json" - const val FORM_URLENCODED = "application/x-www-form-urlencoded; charset=utf-8" - const val ZH_CN = "zh-CN,zh;q=0.8" + 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" } 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" diff --git a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/FailureResponseInterceptor.kt b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/FailureResponseInterceptor.kt index a94d65f..e2b3cc4 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/FailureResponseInterceptor.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/FailureResponseInterceptor.kt @@ -30,11 +30,20 @@ object FailureResponseInterceptor : Interceptor { contentType.charset(Charsets.UTF_8)!! } - //拷贝流并读取其内容 - val jsonObject = body.source().also { + //拷贝流 + val inputStreamReader = body.source().also { it.request(Long.MAX_VALUE) - }.buffer.clone().inputStream().reader(charset).let { - jsonParser.parse(it).obj + }.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 diff --git a/src/test/kotlin/com/hiczp/bilibili/api/test/DanmakuTest.kt b/src/test/kotlin/com/hiczp/bilibili/api/test/DanmakuTest.kt new file mode 100644 index 0000000..890682b --- /dev/null +++ b/src/test/kotlin/com/hiczp/bilibili/api/test/DanmakuTest.kt @@ -0,0 +1,21 @@ +package com.hiczp.bilibili.api.test + +import com.hiczp.bilibili.api.danmaku.DanmakuParser +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Test + +class DanmakuTest { + //339.6kib 的弹幕文件在 0.5s 内解析完毕, 通常视频的弹幕不会超过这个容量 + @Test + fun fetchAndParseDanmaku() { + runBlocking { + //著名的炮姐视频 你指尖跃动的闪光是我此生不变的信仰 + bilibiliClient.danmakuAPI.list(aid = 810872, oid = 1176840).await().let { + DanmakuParser.parser(it.byteStream()) + }.let { (map, list) -> + println(map) + println(list) + } + } + } +} diff --git a/src/test/kotlin/com/hiczp/bilibili/api/test/PlayUrlTest.kt b/src/test/kotlin/com/hiczp/bilibili/api/test/PlayUrlTest.kt index 86628b8..cd4d901 100644 --- a/src/test/kotlin/com/hiczp/bilibili/api/test/PlayUrlTest.kt +++ b/src/test/kotlin/com/hiczp/bilibili/api/test/PlayUrlTest.kt @@ -1,5 +1,6 @@ package com.hiczp.bilibili.api.test +import com.google.gson.GsonBuilder import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Test @@ -17,7 +18,9 @@ class PlayUrlTest { fun bangumiPlayUrl() { runBlocking { bilibiliClient.playerAPI.run { - bangumiPlayUrl(aid = 42714241, cid = 74921228).await() + bangumiPlayUrl(aid = 42714241, cid = 74921228).await().let { + GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(it) + }.let(::println) } } }