feat: Transplant and adapt the code required for development based on bilibili-api
This commit is contained in:
6
starbot/__init__.py
Normal file
6
starbot/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import asyncio
|
||||
import platform
|
||||
|
||||
# 如果系统为 Windows,则修改默认策略,以解决代理报错问题
|
||||
if 'windows' in platform.system().lower():
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
362
starbot/api/live.json
Normal file
362
starbot/api/live.json
Normal file
@@ -0,0 +1,362 @@
|
||||
{
|
||||
"info": {
|
||||
"room_play_info": {
|
||||
"url": "https://api.live.bilibili.com/xlive/web-room/v1/index/getRoomPlayInfo",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"room_id": "int: 房间号"
|
||||
},
|
||||
"comment": "获取房间信息(真实房间号,封禁情况等)"
|
||||
},
|
||||
"chat_conf": {
|
||||
"url": "https://api.live.bilibili.com/room/v1/Danmu/getConf",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"room_id": "int: 真实房间号"
|
||||
},
|
||||
"comment": "获取聊天弹幕服务器配置信息(websocket)"
|
||||
},
|
||||
"room_info": {
|
||||
"url": "https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"room_id": "int: 真实房间号"
|
||||
},
|
||||
"comment": "获取直播间信息(标题,简介等)"
|
||||
},
|
||||
"user_info_in_room": {
|
||||
"url": "https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByUser",
|
||||
"method": "GET",
|
||||
"verify": true,
|
||||
"params": {
|
||||
"room_id": "int: 真实房间号"
|
||||
},
|
||||
"comment": "获取自己在直播间的信息(粉丝勋章等级,直播用户等级等)"
|
||||
},
|
||||
"area_info": {
|
||||
"url": "http://api.live.bilibili.com/room/v1/Area/getList",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": null,
|
||||
"comment": "获取直播间分区信息"
|
||||
},
|
||||
"user_info": {
|
||||
"url": "https://api.live.bilibili.com/xlive/web-ucenter/user/get_user_info",
|
||||
"method": "GET",
|
||||
"verify": true,
|
||||
"params": null,
|
||||
"comment": "获取直播用户等级等信息"
|
||||
},
|
||||
"user_guards": {
|
||||
"url": "https://api.live.bilibili.com/xlive/web-ucenter/user/guards",
|
||||
"method": "GET",
|
||||
"verify": true,
|
||||
"params": {
|
||||
"page": "页码",
|
||||
"page_size": "每页数量, 过多可能报错 默认:10"
|
||||
},
|
||||
"comment": "获取用户开通的大航海列表"
|
||||
},
|
||||
"bag_list": {
|
||||
"url": "https://api.live.bilibili.com/xlive/web-room/v1/gift/bag_list",
|
||||
"method": "GET",
|
||||
"verify": "true",
|
||||
"params": null,
|
||||
"comment": "获取自己的礼物包裹"
|
||||
},
|
||||
"dahanghai": {
|
||||
"url": "https://api.live.bilibili.com/xlive/app-room/v1/guardTab/topList",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"roomid": "int: 真实房间号",
|
||||
"page": "int: 页码",
|
||||
"ruid": "int: 全称 room_uid,从 room_play_info 里头的 uid 可以找到",
|
||||
"page_size": 29
|
||||
},
|
||||
"comment": "获取大航海列表"
|
||||
},
|
||||
"gaonengbang": {
|
||||
"url": "https://api.live.bilibili.com/xlive/general-interface/v1/rank/getOnlineGoldRank",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"roomId": "int: 真实房间号",
|
||||
"page": "int: 页码",
|
||||
"ruid": "int: 全称 room_uid,从 room_play_info 里头的 uid 可以找到",
|
||||
"pageSize": 50
|
||||
},
|
||||
"comment": "获取高能榜"
|
||||
},
|
||||
"live_info": {
|
||||
"url": "https://api.live.bilibili.com/xlive/web-ucenter/user/live_info",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": null,
|
||||
"comment": "获取自己粉丝牌,大航海等数据"
|
||||
},
|
||||
"general_info": {
|
||||
"url": "https://api.live.bilibili.com/xlive/fuxi-interface/general/half/initial",
|
||||
"method": "GET",
|
||||
"verify": true,
|
||||
"params": {
|
||||
"actId": "未知,大航海信息:100061",
|
||||
"roomId": "真实房间号",
|
||||
"uid": "直播者uid",
|
||||
"csrf,csrf_token": "要给两个"
|
||||
},
|
||||
"comment": "获取自己在当前房间的大航海信息, 是否开通,等级,当前经验,同时可获得自己开通的所有航海日志"
|
||||
},
|
||||
"seven_rank": {
|
||||
"url": "https://api.live.bilibili.com/rankdb/v1/RoomRank/webSevenRank",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"roomid": "int: 真实房间号",
|
||||
"ruid": "int: 全称 room_uid,从 room_play_info 里头的 uid 可以找到"
|
||||
},
|
||||
"comment": "获取七日榜"
|
||||
},
|
||||
"fans_medal_rank": {
|
||||
"url": "https://api.live.bilibili.com/rankdb/v1/RoomRank/webMedalRank",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"roomid": "int: 真实房间号",
|
||||
"ruid": "int: 全称 room_uid,从 room_play_info 里头的 uid 可以找到"
|
||||
},
|
||||
"comment": "获取粉丝勋章排行榜"
|
||||
},
|
||||
"black_list": {
|
||||
"url": "https://api.live.bilibili.com/xlive/web-ucenter/v1/banned/GetSilentUserList",
|
||||
"method": "POST",
|
||||
"verify": true,
|
||||
"params": {
|
||||
"room_id": "int: 真实房间号",
|
||||
"ps": "const int: 1"
|
||||
},
|
||||
"comment": "获取房间黑名单列表,登录账号需要是该房间房管"
|
||||
},
|
||||
"room_play_url": {
|
||||
"url": "https://api.live.bilibili.com/xlive/web-room/v1/playUrl/playUrl",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"cid": "int: 真实房间号",
|
||||
"platform": "const str: web",
|
||||
"qn": "int: 清晰度编号,原画 10000,蓝光 400,超清 250,高清 150,流畅 80",
|
||||
"https_url_req": "const int: 1",
|
||||
"ptype": "const int: 16"
|
||||
},
|
||||
"comment": "获取房间直播流列表"
|
||||
},
|
||||
"room_play_info_v2": {
|
||||
"url": "https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"room_id": "int: 真实房间号",
|
||||
"protocol": "int: 流协议,0 为 FLV 流,1 为 HLS 流。默认:0,1",
|
||||
"format": "int: 容器格式,0 为 flv 格式;1 为 ts 格式(仅限 hls 流);2 为 fmp4 格式(仅限 hls 流)。默认:0,2",
|
||||
"codec": "int: 视频编码,0 为 avc 编码,1 为 hevc 编码。默认:0,1",
|
||||
"qn": "int: 清晰度编号,原画:10000(建议),4K:800,蓝光(杜比):401,蓝光:400,超清:250,高清:150,流畅:80,默认:0",
|
||||
"platform": "const str: web",
|
||||
"ptype": "const int: 16"
|
||||
},
|
||||
"comment": "获取房间信息及可用清晰度列表"
|
||||
},
|
||||
"gift_common": {
|
||||
"url": "https://api.live.bilibili.com/xlive/web-room/v1/giftPanel/giftData",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"room_id": "int: 显示房间号",
|
||||
"platform": "const str: pc",
|
||||
"source": "const str: live",
|
||||
"area_id": "int: 子分区 ID 可以不用填",
|
||||
"area_parent_id": "int: 父分区 ID 可以不用填, 获取分区 ID 可使用 get_area_info 方法"
|
||||
},
|
||||
"comment": "获取该直播间通用礼物的信息,此 API 只返回 gift_id ,不包含礼物 price 参数"
|
||||
},
|
||||
"gift_special": {
|
||||
"url": "https://api.live.bilibili.com//xlive/web-room/v1/giftPanel/tabRoomGiftList",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"room_id": "int: 显示房间号",
|
||||
"platform": "const str: pc",
|
||||
"source": "const str: live",
|
||||
"tab_id": "int: 礼物tab编号,2 为特权礼物,3 为定制礼物",
|
||||
"build": "int: 未知作用, 默认 1",
|
||||
"area_id": "int: 子分区 ID 可以不用填",
|
||||
"area_parent_id": "int: 父分区 ID 可以不用填, 获取分区id可使用 get_area_info 方法"
|
||||
},
|
||||
"comment": "获取该直播间特殊礼物的信息"
|
||||
},
|
||||
"gift_config": {
|
||||
"url": "https://api.live.bilibili.com/xlive/web-room/v1/giftPanel/giftConfig",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"room_id": "int: 显示房间号 可以不用填",
|
||||
"platform": "const str: pc",
|
||||
"source": "const str: live",
|
||||
"area_id": "int: 子分区 ID 可以不用填",
|
||||
"area_parent_id": "int: 父分区 ID 可以不用填, 获取分区id可使用 get_area_info 方法"
|
||||
},
|
||||
"comment": "获取所有礼物信息,三个字段可以不用填,但填了有助于减小返回内容的大小,置空返回约 2.7w 行,填了三个对应值返回约 1.4w 行"
|
||||
},
|
||||
"followers_live_info": {
|
||||
"url": "https://api.live.bilibili.com/xlive/app-interface/v1/relation/liveAnchor",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"filterRule": "int: 0 ,未知",
|
||||
"need_recommend": "int: 是否接受推荐直播间, 0为不接受, 1为接受"
|
||||
},
|
||||
"comment": "获取关注列表中正在直播的直播间信息, 包括房间直播热度, 房间名称及标题, 清晰度, 是否官方认证等信息."
|
||||
},
|
||||
"followers_unlive_info": {
|
||||
"url": "https://api.live.bilibili.com/xlive/app-interface/v1/relation/unliveAnchor",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"page": "int: 页码",
|
||||
"pagesize": "每页数量,过多可能报错 默认:30"
|
||||
},
|
||||
"comment": "获取关注列表中未在直播的直播间信息, 包括上次开播时间, 上次开播的类别, 直播间公告, 是否有录播等."
|
||||
}
|
||||
},
|
||||
"operate": {
|
||||
"send_danmaku": {
|
||||
"url": "https://api.live.bilibili.com/msg/send",
|
||||
"method": "POST",
|
||||
"verify": true,
|
||||
"params": {
|
||||
"roomid": "int: 真实房间号",
|
||||
"color": "int: 十进制颜色,有权限限制",
|
||||
"fontsize": "int: 字体大小,默认 25",
|
||||
"mode": "int: 弹幕模式,1 飞行 5 顶部 4 底部",
|
||||
"msg": "str: 弹幕信息",
|
||||
"rnd": "int: 当前时间戳",
|
||||
"bubble": "int: 默认 0,功能不知",
|
||||
"csrf,csrf_token": "str: 要给两个"
|
||||
},
|
||||
"comment": "发送直播间弹幕,有的参数不确定因为自己不搞这块没权限发一些样式的弹幕"
|
||||
},
|
||||
"add_block": {
|
||||
"url": "https://api.live.bilibili.com/xlive/web-ucenter/v1/banned/AddSilentUser",
|
||||
"method": "POST",
|
||||
"verify": true,
|
||||
"params": {
|
||||
"room_id": "int: 真实房间号",
|
||||
"tuid": "int: 封禁用户 UID",
|
||||
"mobile_app": "str: 设备类型",
|
||||
"visit_id": "str: 空"
|
||||
},
|
||||
"comment": "封禁用户"
|
||||
},
|
||||
"del_block": {
|
||||
"url": "https://api.live.bilibili.com/banned_service/v1/Silent/del_room_block_user",
|
||||
"method": "POST",
|
||||
"verify": true,
|
||||
"params": {
|
||||
"roomid": "int: 真实房间号",
|
||||
"id": "int: 封禁 ID,从 ID.info.black_list 中获取或者 ID.operate.black_list 的返回值获取",
|
||||
"visit_id": "str: 空"
|
||||
},
|
||||
"comment": "解封用户"
|
||||
},
|
||||
"sign_up_dahanghai": {
|
||||
"url": "https://api.live.bilibili.com/xlive/activity-interface/v2/userTask/UserTaskSignUp",
|
||||
"method": "POST",
|
||||
"verify": true,
|
||||
"params": {
|
||||
"task_id": "int: 任务 id,签到:1447,可能还有别的",
|
||||
"uid": "int: 真实房间号",
|
||||
"csrf,csrf_token": "要给两个"
|
||||
},
|
||||
"comment": "航海日志签到"
|
||||
},
|
||||
"send_gift_from_bag": {
|
||||
"url": "https://api.live.bilibili.com/xlive/revenue/v1/gift/sendBag",
|
||||
"method": "POST",
|
||||
"verify": true,
|
||||
"params": {
|
||||
"uid": "int: 赠送用户的 UID",
|
||||
"bag_id": "int: 礼物包裹的id",
|
||||
"gift_id": "int: 礼物id",
|
||||
"gift_num": "int: 赠送数量",
|
||||
"platform": "const str: pc",
|
||||
"send_ruid": "int: 未知作用,默认:0",
|
||||
"storm_beat_id": "int: 未知作用,默认:0",
|
||||
"price": "int: 礼物单价,背包中的礼物价值默认:0",
|
||||
"biz_code": "const str: live",
|
||||
"biz_id": "int: room_display_id 房间显示 ID",
|
||||
"ruid": "int: 全称 room_uid,从 room_play_info 里头的 UID 可以找到",
|
||||
"csrf,csrf_token": "要给两个"
|
||||
},
|
||||
"comment": "在直播间中赠送包裹中的礼物,包裹信息可用 get_self_bag 方法获取"
|
||||
},
|
||||
"send_gift_gold": {
|
||||
"url": "https://api.live.bilibili.com/xlive/revenue/v1/gift/sendGold",
|
||||
"method": "POST",
|
||||
"verify": true,
|
||||
"params": {
|
||||
"uid": "int: 赠送用户的 UID",
|
||||
"gift_id": "int: 礼物 ID",
|
||||
"ruid": "int: 全称 room_uid,从 room_play_info 里头的uid可以找到",
|
||||
"send_ruid": "int: 未知作用,默认:0",
|
||||
"gift_num": "int: 赠送数量",
|
||||
"coin_type": "const str: gold",
|
||||
"bag_id": "int: 0",
|
||||
"platform": "const str: pc",
|
||||
"biz_code": "const str: Live",
|
||||
"biz_id": "int: room_display_id 房间显示 ID",
|
||||
"rnd": "int: 当前时间戳",
|
||||
"storm_beat_id": "int: 未知作用,默认:0",
|
||||
"price": "int: 礼物单价",
|
||||
"visit_id": "void: 空",
|
||||
"csrf,csrf_token": "要给两个"
|
||||
},
|
||||
"comment": "在直播间中赠送金瓜子礼物"
|
||||
},
|
||||
"send_gift_silver": {
|
||||
"url": "https://api.live.bilibili.com/xlive/revenue/v1/gift/sendSilver",
|
||||
"method": "POST",
|
||||
"verify": true,
|
||||
"params": {
|
||||
"uid": "int: 赠送用户的 UID",
|
||||
"gift_id": "int: 礼物 ID 辣条的 ID 为 1",
|
||||
"ruid": "int: 全称 room_uid,从 room_play_info 里头的 UID 可以找到",
|
||||
"send_ruid": "int: 未知作用,默认:0",
|
||||
"gift_num": "int: 赠送数量",
|
||||
"coin_type": "const str: silver",
|
||||
"bag_id": "int: 0",
|
||||
"platform": "const str: pc",
|
||||
"biz_code": "const str: Live",
|
||||
"biz_id": "int: room_display_id 房间显示id",
|
||||
"rnd": "int: 当前时间戳",
|
||||
"storm_beat_id": "int: 未知作用,默认:0",
|
||||
"price": "int: 礼物单价 辣条单价为100",
|
||||
"visit_id": "int: 空",
|
||||
"csrf,csrf_token": "要给两个"
|
||||
},
|
||||
"comment": "在直播间中赠送银瓜子礼物"
|
||||
},
|
||||
"receive_reward": {
|
||||
"url": "https://api.live.bilibili.com/xlive/activity-interface/v2/spec-act/sep-guard/receive/awards",
|
||||
"method": "POST",
|
||||
"verify": true,
|
||||
"params": {
|
||||
"ruid": "int: 房间真实id",
|
||||
"receive_type": "int: 领取类型, 全部领取:2",
|
||||
"csrf,csrf_token": "要给两个"
|
||||
},
|
||||
"comment": "领取航海日志奖励"
|
||||
}
|
||||
}
|
||||
}
|
||||
244
starbot/api/user.json
Normal file
244
starbot/api/user.json
Normal file
@@ -0,0 +1,244 @@
|
||||
{
|
||||
"info": {
|
||||
"my_info": {
|
||||
"url": "https://api.bilibili.com/x/space/myinfo",
|
||||
"method": "GET",
|
||||
"verify": true,
|
||||
"comment": "获取自己的信息"
|
||||
},
|
||||
"info": {
|
||||
"url": "https://api.bilibili.com/x/space/acc/info",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"mid": "int: uid"
|
||||
},
|
||||
"comment": "用户基本信息"
|
||||
},
|
||||
"relation": {
|
||||
"url": "https://api.bilibili.com/x/relation/stat",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"vmid": "int: uid"
|
||||
},
|
||||
"comment": "关注数,粉丝数"
|
||||
},
|
||||
"upstat": {
|
||||
"url": "https://api.bilibili.com/x/space/upstat",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"mid": "int: uid"
|
||||
},
|
||||
"comment": "视频播放量,文章阅读量,总点赞数"
|
||||
},
|
||||
"live": {
|
||||
"url": "https://api.bilibili.com/x/space/acc/info",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"mid": "int: uid"
|
||||
},
|
||||
"comment": "直播间基本信息"
|
||||
},
|
||||
"video": {
|
||||
"url": "https://api.bilibili.com/x/space/arc/search",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"mid": "int: uid",
|
||||
"ps": "const int: 30",
|
||||
"tid": "int: 分区 ID,0 表示全部",
|
||||
"pn": "int: 页码",
|
||||
"keyword": "str: 关键词,可为空",
|
||||
"order": "str: pubdate 上传日期,pubdate 播放量,pubdate 收藏量"
|
||||
},
|
||||
"comment": "搜索用户视频"
|
||||
},
|
||||
"audio": {
|
||||
"url": "https://api.bilibili.com/audio/music-service/web/song/upper",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"uid": "int: uid",
|
||||
"ps": "const int: 30",
|
||||
"pn": "int: 页码",
|
||||
"order": "int: 1 最新发布,2 最多播放,3 最多收藏"
|
||||
},
|
||||
"comment": "音频"
|
||||
},
|
||||
"article": {
|
||||
"url": "https://api.bilibili.com/x/space/article",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"mid": "int: uid",
|
||||
"ps": "const int: 30",
|
||||
"pn": "int: 页码",
|
||||
"sort": "str: publish_time 最新发布,publish_time 最多阅读,publish_time 最多收藏"
|
||||
},
|
||||
"comment": "专栏"
|
||||
},
|
||||
"article_lists": {
|
||||
"url": "https://api.bilibili.com/x/article/up/lists",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"mid": "int: uid",
|
||||
"sort": "int: 0 最近更新,1 最多阅读"
|
||||
},
|
||||
"comment": "专栏文集"
|
||||
},
|
||||
"dynamic": {
|
||||
"url": "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"host_uid": "int: uid",
|
||||
"offset_dynamic_id": "int: 动态偏移用,第一页为 0",
|
||||
"need_top": "int bool: 是否显示置顶动态"
|
||||
},
|
||||
"comment": "用户动态信息"
|
||||
},
|
||||
"bangumi": {
|
||||
"url": "https://api.bilibili.com/x/space/bangumi/follow/list",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"vmid": "int: uid",
|
||||
"pn": "int: 页码",
|
||||
"ps": "const int: 15",
|
||||
"type": "int: 1 追番,2 追剧"
|
||||
},
|
||||
"comment": "用户追番列表"
|
||||
},
|
||||
"followings": {
|
||||
"url": "https://api.bilibili.com/x/relation/followings",
|
||||
"method": "GET",
|
||||
"verify": true,
|
||||
"params": {
|
||||
"vmid": "int: uid",
|
||||
"ps": "const int: 20",
|
||||
"pn": "int: 页码",
|
||||
"order": "str: desc 倒序, asc 正序"
|
||||
},
|
||||
"comment": "获取用户关注列表(不是自己只能访问前 5 页)"
|
||||
},
|
||||
"followers": {
|
||||
"url": "https://api.bilibili.com/x/relation/followers",
|
||||
"method": "GET",
|
||||
"verify": true,
|
||||
"params": {
|
||||
"vmid": "int: uid",
|
||||
"ps": "const int: 20",
|
||||
"pn": "int: 页码",
|
||||
"order": "str: desc 倒序, asc 正序"
|
||||
},
|
||||
"comment": "获取用户粉丝列表(不是自己只能访问前 5 页,是自己也不能获取全部的样子)"
|
||||
},
|
||||
"overview": {
|
||||
"url": "https://api.bilibili.com/x/space/navnum",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"mid": "int: uid",
|
||||
"jsonp": "const str: jsonp"
|
||||
},
|
||||
"comment": "获取用户的简易订阅和投稿信息(主要是这些的数量统计)"
|
||||
},
|
||||
"self_subscribe_group": {
|
||||
"url": "https://api.bilibili.com/x/relation/tags",
|
||||
"method": "GET",
|
||||
"verify": true,
|
||||
"params": {},
|
||||
"comment": "获取自己的关注分组列表,用于操作关注"
|
||||
},
|
||||
"get_user_in_which_subscribe_groups": {
|
||||
"url": "https://api.bilibili.com/x/relation/tag/user",
|
||||
"method": "GET",
|
||||
"verify": true,
|
||||
"params": {
|
||||
"fid": "int: uid"
|
||||
},
|
||||
"comment": "获取用户在哪一个分组"
|
||||
},
|
||||
"history": {
|
||||
"url": "https://api.bilibili.com/x/v2/history",
|
||||
"method": "GET",
|
||||
"verify": true,
|
||||
"params": {
|
||||
"pn": "int: 页码",
|
||||
"ps": "const int: 100"
|
||||
},
|
||||
"comment": "用户浏览历史记录"
|
||||
}
|
||||
},
|
||||
"operate": {
|
||||
"modify": {
|
||||
"url": "https://api.bilibili.com/x/relation/modify",
|
||||
"method": "POST",
|
||||
"verify": true,
|
||||
"data": {
|
||||
"fid": "int: UID",
|
||||
"act": "int: 1 关注 2 取关 3 悄悄关注 5 拉黑 6 取消拉黑 7 移除粉丝",
|
||||
"re_src": "const int: 11"
|
||||
},
|
||||
"comment": "用户关系操作"
|
||||
},
|
||||
"send_msg": {
|
||||
"url": "https://api.vc.bilibili.com/web_im/v1/web_im/send_msg",
|
||||
"method": "POST",
|
||||
"verify": true,
|
||||
"data": {
|
||||
"msg[sender_uid]": "int: 自己的 UID",
|
||||
"msg[receiver_id]": "int: 对方 UID",
|
||||
"msg[receiver_type]": "const int: 1",
|
||||
"msg[msg_type]": "const int: 1",
|
||||
"msg[msg_status]": "const int: 0",
|
||||
"msg[content]": {
|
||||
"content": "str: 文本内容"
|
||||
}
|
||||
},
|
||||
"comment": "给用户发信息"
|
||||
},
|
||||
"create_subscribe_group": {
|
||||
"url": "https://api.bilibili.com/x/relation/tag/create",
|
||||
"method": "POST",
|
||||
"verify": true,
|
||||
"data": {
|
||||
"tag": "str: 分组名"
|
||||
},
|
||||
"comment": "添加关注分组"
|
||||
},
|
||||
"del_subscribe_group": {
|
||||
"url": "https://api.bilibili.com/x/relation/tag/del",
|
||||
"method": "POST",
|
||||
"verify": true,
|
||||
"data": {
|
||||
"tagid": "int: 分组 id"
|
||||
},
|
||||
"comment": "删除关注分组"
|
||||
},
|
||||
"rename_subscribe_group": {
|
||||
"url": "https://api.bilibili.com/x/relation/tag/update",
|
||||
"method": "POST",
|
||||
"verify": true,
|
||||
"data": {
|
||||
"tagid": "int: 分组 id",
|
||||
"name": "str: 新的分组名"
|
||||
},
|
||||
"comment": "重命名分组"
|
||||
},
|
||||
"set_user_subscribe_group": {
|
||||
"url": "https://api.bilibili.com/x/relation/tags/addUsers",
|
||||
"method": "POST",
|
||||
"verify": true,
|
||||
"data": {
|
||||
"fids": "int: UID",
|
||||
"tagids": "commaSeparatedList[int]: 分组的 tagids,逗号分隔"
|
||||
},
|
||||
"comment": "移动用户到关注分组"
|
||||
}
|
||||
}
|
||||
}
|
||||
0
starbot/core/__init__.py
Normal file
0
starbot/core/__init__.py
Normal file
1053
starbot/core/live.py
Normal file
1053
starbot/core/live.py
Normal file
File diff suppressed because it is too large
Load Diff
491
starbot/core/user.py
Normal file
491
starbot/core/user.py
Normal file
@@ -0,0 +1,491 @@
|
||||
"""
|
||||
由 bilibili-api 二次开发
|
||||
源仓库: https://github.com/MoyuScript/bilibili-api
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
|
||||
from ..utils.Credential import Credential
|
||||
from ..utils.network import request
|
||||
from ..utils.utils import get_api
|
||||
|
||||
API = get_api("user")
|
||||
|
||||
|
||||
class VideoOrder(Enum):
|
||||
"""
|
||||
视频排序顺序
|
||||
|
||||
+ PUBDATE : 上传日期倒序
|
||||
+ FAVORITE : 收藏量倒序
|
||||
+ VIEW : 播放量倒序
|
||||
"""
|
||||
PUBDATE = "pubdate"
|
||||
FAVORITE = "stow"
|
||||
VIEW = "click"
|
||||
|
||||
|
||||
class AudioOrder(Enum):
|
||||
"""
|
||||
音频排序顺序
|
||||
|
||||
+ PUBDATE : 上传日期倒序
|
||||
+ VIEW : 播放量倒序
|
||||
+ FAVORITE : 收藏量倒序
|
||||
"""
|
||||
PUBDATE = 1
|
||||
VIEW = 2
|
||||
FAVORITE = 3
|
||||
|
||||
|
||||
class ArticleOrder(Enum):
|
||||
"""
|
||||
专栏排序顺序
|
||||
|
||||
+ PUBDATE : 发布日期倒序
|
||||
+ FAVORITE : 收藏量倒序
|
||||
+ VIEW : 阅读量倒序
|
||||
"""
|
||||
PUBDATE = "publish_time"
|
||||
FAVORITE = "fav"
|
||||
VIEW = "view"
|
||||
|
||||
|
||||
class ArticleListOrder(Enum):
|
||||
"""
|
||||
文集排序顺序
|
||||
|
||||
+ LATEST : 最近更新倒序
|
||||
+ VIEW : 总阅读量倒序
|
||||
"""
|
||||
LATEST = 0
|
||||
VIEW = 1
|
||||
|
||||
|
||||
class BangumiType(Enum):
|
||||
"""
|
||||
番剧类型
|
||||
|
||||
+ BANGUMI : 番剧
|
||||
+ DRAMA : 电视剧/纪录片等
|
||||
"""
|
||||
BANGUMI = 1
|
||||
DRAMA = 2
|
||||
|
||||
|
||||
class RelationType(Enum):
|
||||
"""
|
||||
用户关系操作类型
|
||||
|
||||
+ SUBSCRIBE : 关注
|
||||
+ UNSUBSCRIBE : 取关
|
||||
+ SUBSCRIBE_SECRETLY : 悄悄关注
|
||||
+ BLOCK : 拉黑
|
||||
+ UNBLOCK : 取消拉黑
|
||||
+ REMOVE_FANS : 移除粉丝
|
||||
"""
|
||||
SUBSCRIBE = 1
|
||||
UNSUBSCRIBE = 2
|
||||
SUBSCRIBE_SECRETLY = 3
|
||||
BLOCK = 5
|
||||
UNBLOCK = 6
|
||||
REMOVE_FANS = 7
|
||||
|
||||
|
||||
class User:
|
||||
"""
|
||||
用户相关
|
||||
"""
|
||||
|
||||
def __init__(self, uid: int, credential: Credential = None):
|
||||
"""
|
||||
Args:
|
||||
uid: 用户 UID
|
||||
credential: 凭据。默认:None
|
||||
"""
|
||||
self.uid = uid
|
||||
|
||||
if credential is None:
|
||||
credential = Credential()
|
||||
self.credential = credential
|
||||
self.__self_info = None
|
||||
|
||||
async def get_user_info(self):
|
||||
"""
|
||||
获取用户信息(昵称,性别,生日,签名,头像 URL,空间横幅 URL 等)
|
||||
"""
|
||||
api = API["info"]["info"]
|
||||
params = {
|
||||
"mid": self.uid
|
||||
}
|
||||
return await request("GET", url=api["url"], params=params, credential=self.credential)
|
||||
|
||||
async def __get_self_info(self):
|
||||
"""
|
||||
获取自己的信息,如果存在缓存则使用缓存
|
||||
"""
|
||||
if self.__self_info is not None:
|
||||
return self.__self_info
|
||||
|
||||
self.__self_info = await get_self_info(credential=self.credential)
|
||||
return self.__self_info
|
||||
|
||||
async def get_relation_info(self):
|
||||
"""
|
||||
获取用户关系信息(关注数,粉丝数,悄悄关注,黑名单数)
|
||||
"""
|
||||
api = API["info"]["relation"]
|
||||
params = {
|
||||
"vmid": self.uid
|
||||
}
|
||||
return await request("GET", url=api["url"], params=params, credential=self.credential)
|
||||
|
||||
async def get_up_stat(self):
|
||||
"""
|
||||
获取 UP 主数据信息(视频总播放量,文章总阅读量,总点赞数)
|
||||
"""
|
||||
self.credential.raise_for_no_bili_jct()
|
||||
|
||||
api = API["info"]["upstat"]
|
||||
params = {
|
||||
"mid": self.uid
|
||||
}
|
||||
return await request("GET", url=api["url"], params=params, credential=self.credential)
|
||||
|
||||
async def get_live_info(self):
|
||||
"""
|
||||
获取用户直播间信息
|
||||
"""
|
||||
api = API["info"]["live"]
|
||||
params = {
|
||||
"mid": self.uid
|
||||
}
|
||||
return await request("GET", url=api["url"], params=params, credential=self.credential)
|
||||
|
||||
async def get_videos(self,
|
||||
tid: int = 0,
|
||||
pn: int = 1,
|
||||
keyword: str = "",
|
||||
order: VideoOrder = VideoOrder.PUBDATE
|
||||
):
|
||||
"""
|
||||
获取用户投稿视频信息
|
||||
|
||||
Args:
|
||||
tid: 分区 ID。默认:0(全部)
|
||||
pn: 页码,从 1 开始。默认:1
|
||||
keyword: 搜索关键词。默认:""
|
||||
order: 排序方式。默认:VideoOrder.PUBDATE
|
||||
"""
|
||||
api = API["info"]["video"]
|
||||
params = {
|
||||
"mid": self.uid,
|
||||
"ps": 30,
|
||||
"tid": tid,
|
||||
"pn": pn,
|
||||
"keyword": keyword,
|
||||
"order": order.value
|
||||
}
|
||||
return await request("GET", url=api["url"], params=params, credential=self.credential)
|
||||
|
||||
async def get_audios(self, order: AudioOrder = AudioOrder.PUBDATE, pn: int = 1):
|
||||
"""
|
||||
获取用户投稿音频
|
||||
|
||||
Args:
|
||||
order: 排序方式。默认:AudioOrder.PUBDATE
|
||||
pn: 页码数,从 1 开始。默认:1
|
||||
"""
|
||||
api = API["info"]["audio"]
|
||||
params = {
|
||||
"uid": self.uid,
|
||||
"ps": 30,
|
||||
"pn": pn,
|
||||
"order": order.value
|
||||
}
|
||||
return await request("GET", url=api["url"], params=params, credential=self.credential)
|
||||
|
||||
async def get_articles(self, pn: int = 1, order: ArticleOrder = ArticleOrder.PUBDATE):
|
||||
"""
|
||||
获取用户投稿专栏
|
||||
|
||||
Args:
|
||||
pn: 页码数,从 1 开始。默认:1
|
||||
order: 排序方式。默认:ArticleOrder.PUBDATE
|
||||
"""
|
||||
api = API["info"]["article"]
|
||||
params = {
|
||||
"mid": self.uid,
|
||||
"ps": 30,
|
||||
"pn": pn,
|
||||
"sort": order.value
|
||||
}
|
||||
return await request("GET", url=api["url"], params=params, credential=self.credential)
|
||||
|
||||
async def get_article_list(self, order: ArticleListOrder = ArticleListOrder.LATEST):
|
||||
"""
|
||||
获取用户专栏文集
|
||||
|
||||
Args:
|
||||
order: 排序方式。默认:ArticleListOrder.LATEST
|
||||
"""
|
||||
api = API["info"]["article_lists"]
|
||||
params = {
|
||||
"mid": self.uid,
|
||||
"sort": order.value
|
||||
}
|
||||
return await request("GET", url=api["url"], params=params, credential=self.credential)
|
||||
|
||||
async def get_dynamics(self, offset: int = 0, need_top: bool = False):
|
||||
"""
|
||||
获取用户动态
|
||||
|
||||
Args:
|
||||
offset: 该值为第一次调用本方法时,数据中会有个 next_offset 字段,指向下一动态列表第一条动态(类似单向链表)。
|
||||
根据上一次获取结果中的 next_offset 字段值,循环填充该值即可获取到全部动态。0 为从头开始。
|
||||
默认:0
|
||||
need_top: 显示置顶动态。默认:False
|
||||
"""
|
||||
api = API["info"]["dynamic"]
|
||||
params = {
|
||||
"host_uid": self.uid,
|
||||
"offset_dynamic_id": offset,
|
||||
"need_top": 1 if need_top else 0
|
||||
}
|
||||
data = await request("GET", url=api["url"], params=params, credential=self.credential)
|
||||
# card 字段自动转换成 JSON
|
||||
if 'cards' in data:
|
||||
for card in data["cards"]:
|
||||
card["card"] = json.loads(card["card"])
|
||||
card["extend_json"] = json.loads(card["extend_json"])
|
||||
return data
|
||||
|
||||
async def get_subscribed_bangumis(self, pn: int = 1, type_: BangumiType = BangumiType.BANGUMI):
|
||||
"""
|
||||
获取用户追番/追剧列表
|
||||
|
||||
Args:
|
||||
pn: 页码数,从 1 开始。默认:1
|
||||
type_: 资源类型。默认:BangumiType.BANGUMI
|
||||
"""
|
||||
api = API["info"]["bangumi"]
|
||||
params = {
|
||||
"vmid": self.uid,
|
||||
"pn": pn,
|
||||
"ps": 15,
|
||||
"type": type_.value
|
||||
}
|
||||
return await request("GET", url=api["url"], params=params, credential=self.credential)
|
||||
|
||||
async def get_followings(self, pn: int = 1, desc: bool = True):
|
||||
"""
|
||||
获取用户关注列表(不是自己只能访问前 5 页)
|
||||
|
||||
Args:
|
||||
pn: 页码,从 1 开始。默认:1
|
||||
desc: 倒序排序。默认:True
|
||||
"""
|
||||
api = API["info"]["followings"]
|
||||
params = {
|
||||
"vmid": self.uid,
|
||||
"ps": 20,
|
||||
"pn": pn,
|
||||
"order": "desc" if desc else "asc"
|
||||
}
|
||||
return await request("GET", url=api["url"], params=params, credential=self.credential)
|
||||
|
||||
async def get_followers(self, pn: int = 1, desc: bool = True):
|
||||
"""
|
||||
获取用户粉丝列表(不是自己只能访问前 5 页,是自己也不能获取全部的样子)
|
||||
|
||||
Args:
|
||||
pn: 页码,从 1 开始。默认:1
|
||||
desc: 倒序排序。默认:True
|
||||
"""
|
||||
api = API["info"]["followers"]
|
||||
params = {
|
||||
"vmid": self.uid,
|
||||
"ps": 20,
|
||||
"pn": pn,
|
||||
"order": "desc" if desc else "asc"
|
||||
}
|
||||
return await request("GET", url=api["url"], params=params, credential=self.credential)
|
||||
|
||||
async def get_overview_stat(self):
|
||||
"""
|
||||
获取用户的简易订阅和投稿信息
|
||||
"""
|
||||
api = API["info"]["overview"]
|
||||
params = {
|
||||
"mid": self.uid,
|
||||
"jsonp": "jsonp"
|
||||
}
|
||||
return await request("GET", url=api["url"], params=params, credential=self.credential)
|
||||
|
||||
# 操作用户
|
||||
async def modify_relation(self, relation: RelationType):
|
||||
"""
|
||||
修改和用户的关系,比如拉黑、关注、取关等
|
||||
|
||||
Args:
|
||||
relation: 操作类型
|
||||
"""
|
||||
self.credential.raise_for_no_sessdata()
|
||||
self.credential.raise_for_no_bili_jct()
|
||||
|
||||
api = API["operate"]["modify"]
|
||||
data = {
|
||||
"fid": self.uid,
|
||||
"act": relation.value,
|
||||
"re_src": 11
|
||||
}
|
||||
return await request("POST", url=api["url"], data=data, credential=self.credential)
|
||||
|
||||
async def send_msg(self, text: str):
|
||||
"""
|
||||
给用户发送私聊信息。目前仅支持纯文本
|
||||
|
||||
Args:
|
||||
text: 信息内容
|
||||
"""
|
||||
self.credential.raise_for_no_sessdata()
|
||||
self.credential.raise_for_no_bili_jct()
|
||||
|
||||
api = API["operate"]["send_msg"]
|
||||
self_info = await self.__get_self_info()
|
||||
sender_uid = self_info["mid"]
|
||||
|
||||
data = {
|
||||
"msg[sender_uid]": sender_uid,
|
||||
"msg[receiver_id]": self.uid,
|
||||
"msg[receiver_type]": 1,
|
||||
"msg[msg_type]": 1,
|
||||
"msg[msg_status]": 0,
|
||||
"msg[content]": json.dumps({"content": text}),
|
||||
"msg[dev_id]": "B9A37BF3-AA9D-4076-A4D3-366AC8C4C5DB",
|
||||
"msg[new_face_version]": "0",
|
||||
"msg[timestamp]": int(time.time()),
|
||||
"from_filework": 0,
|
||||
"build": 0,
|
||||
"mobi_app": "web"
|
||||
}
|
||||
return await request("POST", url=api["url"], data=data, credential=self.credential)
|
||||
|
||||
|
||||
async def get_self_info(credential: Credential):
|
||||
"""
|
||||
获取自己的信息
|
||||
|
||||
Args:
|
||||
credential: 凭据
|
||||
"""
|
||||
api = API["info"]["my_info"]
|
||||
credential.raise_for_no_sessdata()
|
||||
|
||||
return await request("GET", api["url"], credential=credential)
|
||||
|
||||
|
||||
async def create_subscribe_group(name: str, credential: Credential):
|
||||
"""
|
||||
创建用户关注分组
|
||||
|
||||
Args:
|
||||
name: 分组名
|
||||
credential: 凭据
|
||||
"""
|
||||
credential.raise_for_no_sessdata()
|
||||
credential.raise_for_no_bili_jct()
|
||||
|
||||
api = API["operate"]["create_subscribe_group"]
|
||||
data = {
|
||||
"tag": name
|
||||
}
|
||||
|
||||
return await request("POST", api["url"], data=data, credential=credential)
|
||||
|
||||
|
||||
async def delete_subscribe_group(group_id: int, credential: Credential):
|
||||
"""
|
||||
删除用户关注分组
|
||||
|
||||
Args:
|
||||
group_id: 分组 ID
|
||||
credential: 凭据
|
||||
"""
|
||||
credential.raise_for_no_sessdata()
|
||||
credential.raise_for_no_bili_jct()
|
||||
|
||||
api = API["operate"]["del_subscribe_group"]
|
||||
data = {
|
||||
"tagid": group_id
|
||||
}
|
||||
|
||||
return await request("POST", api["url"], data=data, credential=credential)
|
||||
|
||||
|
||||
async def rename_subscribe_group(group_id: int, new_name: str, credential: Credential):
|
||||
"""
|
||||
重命名关注分组
|
||||
|
||||
Args:
|
||||
group_id: 分组 ID
|
||||
new_name: 新的分组名
|
||||
credential: 凭据
|
||||
"""
|
||||
credential.raise_for_no_sessdata()
|
||||
credential.raise_for_no_bili_jct()
|
||||
|
||||
api = API["operate"]["rename_subscribe_group"]
|
||||
data = {
|
||||
"tagid": group_id,
|
||||
"name": new_name
|
||||
}
|
||||
|
||||
return await request("POST", api["url"], data=data, credential=credential)
|
||||
|
||||
|
||||
async def set_subscribe_group(uids: List[int], group_ids: List[int], credential: Credential):
|
||||
"""
|
||||
设置用户关注分组
|
||||
|
||||
Args:
|
||||
uids: 要设置的用户 UID 列表,必须已关注
|
||||
group_ids: 要复制到的分组列表
|
||||
credential: 凭据
|
||||
"""
|
||||
credential.raise_for_no_sessdata()
|
||||
credential.raise_for_no_bili_jct()
|
||||
|
||||
api = API["operate"]["set_user_subscribe_group"]
|
||||
data = {
|
||||
"fids": ",".join(map(lambda x: str(x), uids)),
|
||||
"tagids": ",".join(map(lambda x: str(x), group_ids))
|
||||
}
|
||||
|
||||
return await request("POST", api["url"], data=data, credential=credential)
|
||||
|
||||
|
||||
async def get_self_history(credential: Credential, page_num: int = 1, per_page_item: int = 100):
|
||||
"""
|
||||
获取用户浏览历史记录
|
||||
|
||||
Args:
|
||||
credential: 凭据
|
||||
page_num: 页码数。默认:1
|
||||
per_page_item: 每页多少条历史记录。默认:100
|
||||
"""
|
||||
if not credential:
|
||||
credential = Credential()
|
||||
|
||||
credential.raise_for_no_sessdata()
|
||||
|
||||
api = API["info"]["history"]
|
||||
params = {
|
||||
"pn": page_num,
|
||||
"ps": per_page_item
|
||||
}
|
||||
|
||||
return await request("GET", url=api["url"], params=params, credential=credential)
|
||||
16
starbot/exception/ApiException.py
Normal file
16
starbot/exception/ApiException.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
API 异常基类
|
||||
"""
|
||||
|
||||
|
||||
class ApiException(Exception):
|
||||
"""
|
||||
API 异常基类
|
||||
"""
|
||||
|
||||
def __init__(self, msg: str = "出现了错误, 但是未说明具体原因"):
|
||||
super().__init__(msg)
|
||||
self.msg = msg
|
||||
|
||||
def __str__(self):
|
||||
return self.msg
|
||||
15
starbot/exception/CredentialNoBiliJctException.py
Normal file
15
starbot/exception/CredentialNoBiliJctException.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Credential 类未提供 bili_jct 时的异常
|
||||
"""
|
||||
|
||||
from .ApiException import ApiException
|
||||
|
||||
|
||||
class CredentialNoBiliJctException(ApiException):
|
||||
"""
|
||||
Credential 类未提供 bili_jct 时的异常
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.msg = "Credential 类未提供 bili_jct"
|
||||
15
starbot/exception/CredentialNoBuvid3Exception.py
Normal file
15
starbot/exception/CredentialNoBuvid3Exception.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Credential 类未提供 BUVID3 时的异常
|
||||
"""
|
||||
|
||||
from .ApiException import ApiException
|
||||
|
||||
|
||||
class CredentialNoBuvid3Exception(ApiException):
|
||||
"""
|
||||
Credential 类未提供 BUVID3 时的异常
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.msg = "Credential 类未提供 BUVID3"
|
||||
15
starbot/exception/CredentialNoSessdataException.py
Normal file
15
starbot/exception/CredentialNoSessdataException.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Credential 类未提供 SESSDATA 时的异常
|
||||
"""
|
||||
|
||||
from .ApiException import ApiException
|
||||
|
||||
|
||||
class CredentialNoSessdataException(ApiException):
|
||||
"""
|
||||
Credential 类未提供 SESSDATA 时的异常
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.msg = "Credential 类未提供 SESSDATA"
|
||||
15
starbot/exception/LiveException.py
Normal file
15
starbot/exception/LiveException.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
连接直播间期间发生的异常
|
||||
"""
|
||||
|
||||
from .ApiException import ApiException
|
||||
|
||||
|
||||
class LiveException(ApiException):
|
||||
"""
|
||||
连接直播间期间发生的异常
|
||||
"""
|
||||
|
||||
def __init__(self, msg: str):
|
||||
super().__init__()
|
||||
self.msg = msg
|
||||
24
starbot/exception/NetworkException.py
Normal file
24
starbot/exception/NetworkException.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
网络错误
|
||||
"""
|
||||
|
||||
from .ApiException import ApiException
|
||||
|
||||
|
||||
class NetworkException(ApiException):
|
||||
"""
|
||||
网络错误
|
||||
"""
|
||||
|
||||
def __init__(self, status: int, msg: str):
|
||||
"""
|
||||
Args:
|
||||
status: 状态码
|
||||
msg: 状态消息
|
||||
"""
|
||||
super().__init__(msg)
|
||||
self.status = status
|
||||
self.msg = f"网络错误, 状态码: {status} - {msg}"
|
||||
|
||||
def __str__(self):
|
||||
return self.msg
|
||||
26
starbot/exception/ResponseCodeException.py
Normal file
26
starbot/exception/ResponseCodeException.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
API 返回 code 错误
|
||||
"""
|
||||
|
||||
from .ApiException import ApiException
|
||||
|
||||
|
||||
class ResponseCodeException(ApiException):
|
||||
"""
|
||||
API 返回 code 错误
|
||||
"""
|
||||
|
||||
def __init__(self, code: int, msg: str, raw: dict = None):
|
||||
"""
|
||||
Args:
|
||||
code: 错误代码
|
||||
msg: 错误信息
|
||||
raw: 原始响应数据。默认:None
|
||||
"""
|
||||
super().__init__(msg)
|
||||
self.msg = msg
|
||||
self.code = code
|
||||
self.raw = raw
|
||||
|
||||
def __str__(self):
|
||||
return f"接口返回错误代码: {self.code}, 信息: {self.msg}"
|
||||
19
starbot/exception/ResponseException.py
Normal file
19
starbot/exception/ResponseException.py
Normal file
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
API 响应异常
|
||||
"""
|
||||
|
||||
from .ApiException import ApiException
|
||||
|
||||
|
||||
class ResponseException(ApiException):
|
||||
"""
|
||||
API 响应异常
|
||||
"""
|
||||
|
||||
def __init__(self, msg: str):
|
||||
"""
|
||||
Args:
|
||||
msg: 错误消息
|
||||
"""
|
||||
super().__init__(msg)
|
||||
self.msg = msg
|
||||
8
starbot/exception/__init__.py
Normal file
8
starbot/exception/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .ApiException import *
|
||||
from .CredentialNoBiliJctException import *
|
||||
from .CredentialNoBuvid3Exception import *
|
||||
from .CredentialNoSessdataException import *
|
||||
from .LiveException import *
|
||||
from .NetworkException import *
|
||||
from .ResponseCodeException import *
|
||||
from .ResponseException import *
|
||||
81
starbot/utils/AsyncEvent.py
Normal file
81
starbot/utils/AsyncEvent.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
发布-订阅模式异步事件类支持
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import Any, Coroutine
|
||||
|
||||
|
||||
class AsyncEvent:
|
||||
"""
|
||||
发布-订阅模式异步事件类支持
|
||||
|
||||
特殊事件:__ALL__ 所有事件均触发
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.__handlers = {}
|
||||
|
||||
def add_event_listener(self, name: str, handler: Coroutine):
|
||||
"""
|
||||
注册事件监听器
|
||||
|
||||
Args:
|
||||
name: 事件名
|
||||
handler: 回调异步函数
|
||||
"""
|
||||
name = name.upper()
|
||||
if name not in self.__handlers:
|
||||
self.__handlers[name] = []
|
||||
self.__handlers[name].append(handler)
|
||||
|
||||
def on(self, event_name: str):
|
||||
"""
|
||||
装饰器注册事件监听器
|
||||
|
||||
Args:
|
||||
event_name: 事件名
|
||||
"""
|
||||
|
||||
def decorator(func: Coroutine):
|
||||
self.add_event_listener(event_name, func)
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
def remove_event_listener(self, name: str, handler: Coroutine) -> bool:
|
||||
"""
|
||||
移除事件监听函数
|
||||
|
||||
Args:
|
||||
name: 事件名
|
||||
handler: 要移除的函数
|
||||
|
||||
Returns:
|
||||
是否移除成功
|
||||
"""
|
||||
name = name.upper()
|
||||
if name in self.__handlers:
|
||||
if handler in self.__handlers[name]:
|
||||
self.__handlers[name].remove(handler)
|
||||
return True
|
||||
return False
|
||||
|
||||
def dispatch(self, name: str, data: Any = None):
|
||||
"""
|
||||
异步发布事件
|
||||
|
||||
Args:
|
||||
name: 事件名
|
||||
data: 事件附加数据。默认:None
|
||||
"""
|
||||
name = name.upper()
|
||||
if name in self.__handlers:
|
||||
for coroutine in self.__handlers[name]:
|
||||
asyncio.create_task(coroutine(data))
|
||||
|
||||
if name != '__ALL__':
|
||||
self.dispatch('__ALL__', {
|
||||
"name": name,
|
||||
"data": data
|
||||
})
|
||||
80
starbot/utils/Credential.py
Normal file
80
starbot/utils/Credential.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
凭据类,用于各种请求操作的验证
|
||||
"""
|
||||
from typing import Dict
|
||||
|
||||
from ..exception import CredentialNoBiliJctException, CredentialNoBuvid3Exception, CredentialNoSessdataException
|
||||
|
||||
|
||||
class Credential:
|
||||
"""
|
||||
凭据类,用于各种请求操作的验证
|
||||
"""
|
||||
|
||||
def __init__(self, sessdata: str = None, bili_jct: str = None, buvid3: str = None):
|
||||
"""
|
||||
Args:
|
||||
sessdata: 浏览器 Cookies 中的 SESSDATA 字段值。默认:None
|
||||
bili_jct: 浏览器 Cookies 中的 bili_jct 字段值。默认:None
|
||||
buvid3: 浏览器 Cookies 中的 BUVID3 字段值。默认:None
|
||||
"""
|
||||
self.sessdata = sessdata
|
||||
self.bili_jct = bili_jct
|
||||
self.buvid3 = buvid3
|
||||
|
||||
def get_cookies(self) -> Dict:
|
||||
"""
|
||||
获取请求 Cookies 字典
|
||||
|
||||
Returns:
|
||||
请求 Cookies 字典
|
||||
"""
|
||||
return {"SESSDATA": self.sessdata, "buvid3": self.buvid3, 'bili_jct': self.bili_jct}
|
||||
|
||||
def has_sessdata(self) -> bool:
|
||||
"""
|
||||
是否提供了 SESSDATA
|
||||
|
||||
Returns:
|
||||
是否提供了 SESSDATA
|
||||
"""
|
||||
return self.sessdata is not None
|
||||
|
||||
def has_bili_jct(self) -> bool:
|
||||
"""
|
||||
是否提供了 bili_jct
|
||||
|
||||
Returns:
|
||||
是否提供了 bili_jct
|
||||
"""
|
||||
return self.bili_jct is not None
|
||||
|
||||
def has_buvid3(self) -> bool:
|
||||
"""
|
||||
是否提供了 BUVID3
|
||||
|
||||
Returns:
|
||||
是否提供了 BUVID3
|
||||
"""
|
||||
return self.buvid3 is not None
|
||||
|
||||
def raise_for_no_sessdata(self):
|
||||
"""
|
||||
没有提供 SESSDATA 时抛出异常
|
||||
"""
|
||||
if not self.has_sessdata():
|
||||
raise CredentialNoSessdataException()
|
||||
|
||||
def raise_for_no_bili_jct(self):
|
||||
"""
|
||||
没有提供 bili_jct 时抛出异常
|
||||
"""
|
||||
if not self.has_bili_jct():
|
||||
raise CredentialNoBiliJctException()
|
||||
|
||||
def raise_for_no_buvid3(self):
|
||||
"""
|
||||
没有提供 BUVID3 时抛出异常
|
||||
"""
|
||||
if not self.has_buvid3():
|
||||
raise CredentialNoBuvid3Exception()
|
||||
91
starbot/utils/Danmaku.py
Normal file
91
starbot/utils/Danmaku.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
弹幕类
|
||||
"""
|
||||
|
||||
import time
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class FontSize(Enum):
|
||||
"""
|
||||
字体大小枚举
|
||||
"""
|
||||
EXTREME_SMALL = 12
|
||||
SUPER_SMALL = 16
|
||||
SMALL = 18
|
||||
NORMAL = 25
|
||||
BIG = 36
|
||||
SUPER_BIG = 45
|
||||
EXTREME_BIG = 64
|
||||
|
||||
|
||||
class Mode(Enum):
|
||||
"""
|
||||
弹幕模式枚举
|
||||
"""
|
||||
FLY = 1
|
||||
TOP = 5
|
||||
BOTTOM = 4
|
||||
REVERSE = 6
|
||||
|
||||
|
||||
class Danmaku:
|
||||
"""
|
||||
弹幕类
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
text: str,
|
||||
dm_time: float = 0.0,
|
||||
send_time: float = time.time(),
|
||||
crc32_id: str = None,
|
||||
color: str = 'ffffff',
|
||||
weight: int = -1,
|
||||
id_: int = -1,
|
||||
id_str: str = "",
|
||||
action: str = "",
|
||||
mode: Mode = Mode.FLY,
|
||||
font_size: FontSize = FontSize.NORMAL,
|
||||
is_sub: bool = False,
|
||||
pool: int = -1,
|
||||
attr: int = -1):
|
||||
"""
|
||||
Args:
|
||||
text: 弹幕文本
|
||||
dm_time: 弹幕在视频中的位置,单位为秒。默认:0.0
|
||||
send_time: 弹幕发送的时间。默认:time.time()
|
||||
crc32_id: 弹幕发送者 UID 经 CRC32 算法取摘要后的值。默认:None
|
||||
color: 弹幕十六进制颜色。默认:"ffffff"
|
||||
weight: 弹幕在弹幕列表显示的权重。默认:-1
|
||||
id_: 弹幕 ID。默认:-1
|
||||
id_str: 弹幕字符串 ID。默认:""
|
||||
action: 暂不清楚。默认:""
|
||||
mode: 弹幕模式。默认:Mode.FLY
|
||||
font_size: 弹幕字体大小。默认:FontSize.NORMAL
|
||||
is_sub: 是否为字幕弹幕。默认:False
|
||||
pool: 暂不清楚。默认:-1
|
||||
attr: 暂不清楚。默认:-1
|
||||
"""
|
||||
self.text = text
|
||||
self.dm_time = dm_time
|
||||
self.send_time = send_time
|
||||
self.crc32_id = crc32_id
|
||||
self.color = color
|
||||
self.weight = weight
|
||||
self.id = id_
|
||||
self.id_str = id_str
|
||||
self.action = action
|
||||
self.mode = mode
|
||||
self.font_size = font_size
|
||||
self.is_sub = is_sub
|
||||
self.pool = pool
|
||||
self.attr = attr
|
||||
|
||||
self.uid = None
|
||||
|
||||
def __str__(self):
|
||||
ret = "%s, %s, %s" % (self.send_time, self.dm_time, self.text)
|
||||
return ret
|
||||
|
||||
def __len__(self):
|
||||
return len(self.text)
|
||||
0
starbot/utils/__init__.py
Normal file
0
starbot/utils/__init__.py
Normal file
189
starbot/utils/network.py
Normal file
189
starbot/utils/network.py
Normal file
@@ -0,0 +1,189 @@
|
||||
"""
|
||||
与网络请求相关的模块。能对会话进行管理(复用 TCP 连接)
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import atexit
|
||||
import json
|
||||
import re
|
||||
from typing import Any, Union, Dict
|
||||
|
||||
import aiohttp
|
||||
from aiohttp import TCPConnector
|
||||
|
||||
from ..exception import ResponseCodeException, ResponseException, NetworkException
|
||||
from .Credential import Credential
|
||||
|
||||
__session_pool = {}
|
||||
|
||||
|
||||
@atexit.register
|
||||
def __clean():
|
||||
"""
|
||||
程序退出清理操作
|
||||
"""
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
return
|
||||
|
||||
async def __clean_task():
|
||||
await __session_pool[loop].close()
|
||||
|
||||
if loop.is_closed():
|
||||
loop.run_until_complete(__clean_task())
|
||||
else:
|
||||
loop.create_task(__clean_task())
|
||||
|
||||
|
||||
async def request(method: str,
|
||||
url: str,
|
||||
params: dict = None,
|
||||
data: Any = None,
|
||||
credential: Credential = None,
|
||||
no_csrf: bool = False,
|
||||
json_body: bool = False,
|
||||
**kwargs) -> Union[Dict, None]:
|
||||
"""
|
||||
向接口发送请求
|
||||
|
||||
Args:
|
||||
method: 请求方法
|
||||
url: 请求 URL
|
||||
params: 请求参数。默认:None
|
||||
data: 请求载荷。默认:None
|
||||
credential: Credential 实例。默认:None
|
||||
no_csrf: 不要自动添加 CSRF。默认:False
|
||||
json_body: 载荷是否为 JSON。默认:False
|
||||
kwargs: 暂不使用
|
||||
|
||||
Returns:
|
||||
接口未返回数据时,返回 None,否则返回该接口提供的 data 或 result 字段的数据
|
||||
"""
|
||||
if credential is None:
|
||||
credential = Credential()
|
||||
|
||||
method = method.upper()
|
||||
# 请求为非 GET 且 no_csrf 不为 True 时要求 bili_jct
|
||||
if method != 'GET' and not no_csrf:
|
||||
credential.raise_for_no_bili_jct()
|
||||
|
||||
# 使用 Referer 和 UA 请求头以绕过反爬虫机制
|
||||
default_headers = {
|
||||
"Referer": "https://www.bilibili.com",
|
||||
"User-Agent": "Mozilla/5.0"
|
||||
}
|
||||
headers = default_headers
|
||||
|
||||
if params is None:
|
||||
params = {}
|
||||
|
||||
# 自动添加 csrf
|
||||
if not no_csrf and method in ['POST', 'DELETE', 'PATCH']:
|
||||
if data is None:
|
||||
data = {}
|
||||
data['csrf'] = credential.bili_jct
|
||||
data['csrf_token'] = credential.bili_jct
|
||||
|
||||
# jsonp
|
||||
if params.get("jsonp", "") == "jsonp":
|
||||
params["callback"] = "callback"
|
||||
|
||||
config = {
|
||||
"method": method,
|
||||
"url": url,
|
||||
"params": params,
|
||||
"data": data,
|
||||
"headers": headers,
|
||||
"cookies": credential.get_cookies()
|
||||
}
|
||||
|
||||
config.update(kwargs)
|
||||
|
||||
if json_body:
|
||||
config["headers"]["Content-Type"] = "application/json"
|
||||
config["data"] = json.dumps(config["data"])
|
||||
|
||||
# 如果用户提供代理则设置代理
|
||||
proxy = config.get("PROXY")
|
||||
if proxy:
|
||||
config["proxy"] = proxy
|
||||
|
||||
session = get_session()
|
||||
|
||||
async with session.request(**config) as resp:
|
||||
|
||||
# 检查状态码
|
||||
try:
|
||||
resp.raise_for_status()
|
||||
except aiohttp.ClientResponseError as e:
|
||||
raise NetworkException(e.status, e.message)
|
||||
|
||||
# 检查响应头 Content-Length
|
||||
content_length = resp.headers.get("content-length")
|
||||
if content_length and int(content_length) == 0:
|
||||
return None
|
||||
|
||||
# 检查响应头 Content-Type
|
||||
content_type = resp.headers.get("content-type")
|
||||
|
||||
# 不是 application/json
|
||||
if content_type.lower().index("application/json") == -1:
|
||||
raise ResponseException("响应不是 application/json 类型")
|
||||
|
||||
raw_data = await resp.text()
|
||||
resp_data: dict
|
||||
|
||||
if 'callback' in params:
|
||||
# JSONP 请求
|
||||
resp_data = json.loads(
|
||||
re.match("^.*?({.*}).*$", raw_data, re.S).group(1))
|
||||
else:
|
||||
# JSON
|
||||
resp_data = json.loads(raw_data)
|
||||
|
||||
# 检查 code
|
||||
code = resp_data.get("code", None)
|
||||
|
||||
if code is None:
|
||||
raise ResponseCodeException(-1, "API 返回数据未含 code 字段", resp_data)
|
||||
|
||||
if code != 0:
|
||||
msg = resp_data.get('msg', None)
|
||||
if msg is None:
|
||||
msg = resp_data.get('message', None)
|
||||
if msg is None:
|
||||
msg = "接口未返回错误信息"
|
||||
raise ResponseCodeException(code, msg, resp_data)
|
||||
|
||||
real_data = resp_data.get("data", None)
|
||||
if real_data is None:
|
||||
real_data = resp_data.get("result", None)
|
||||
return real_data
|
||||
|
||||
|
||||
def get_session() -> aiohttp.ClientSession:
|
||||
"""
|
||||
获取当前模块的 aiohttp.ClientSession 对象,用于自定义请求
|
||||
|
||||
Returns:
|
||||
ClientSession 实例
|
||||
"""
|
||||
loop = asyncio.get_running_loop()
|
||||
session = __session_pool.get(loop, None)
|
||||
if session is None:
|
||||
session = aiohttp.ClientSession(loop=loop, connector=TCPConnector(loop=loop, limit=0))
|
||||
__session_pool[loop] = session
|
||||
|
||||
return session
|
||||
|
||||
|
||||
def set_session(session: aiohttp.ClientSession):
|
||||
"""
|
||||
用户手动设置 Session
|
||||
|
||||
Args:
|
||||
session: aiohttp.ClientSession 实例
|
||||
"""
|
||||
loop = asyncio.get_running_loop()
|
||||
__session_pool[loop] = session
|
||||
23
starbot/utils/utils.py
Normal file
23
starbot/utils/utils.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
通用工具库
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Dict
|
||||
|
||||
|
||||
def get_api(field: str) -> Dict:
|
||||
"""
|
||||
获取 API
|
||||
|
||||
Args:
|
||||
field: API 所属分类,即 data/api 下的文件名(不含后缀名)
|
||||
|
||||
Returns:
|
||||
该 API 的内容
|
||||
"""
|
||||
path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "api", f"{field.lower()}.json"))
|
||||
if os.path.exists(path):
|
||||
with open(path, encoding="utf8") as f:
|
||||
return json.loads(f.read())
|
||||
Reference in New Issue
Block a user