Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
0ace306d49
@ -15,6 +15,17 @@
|
||||
},
|
||||
"comment": "用户基本信息"
|
||||
},
|
||||
"info_wbi": {
|
||||
"url": "https://api.bilibili.com/x/space/wbi/acc/info",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"mid": "int: uid",
|
||||
"w_rid": "str: Wbi签名",
|
||||
"wts": "int: 当前时间戳"
|
||||
},
|
||||
"comment": "用户基本信息(WBI版本)"
|
||||
},
|
||||
"relation": {
|
||||
"url": "https://api.bilibili.com/x/relation/stat",
|
||||
"method": "GET",
|
||||
@ -42,6 +53,17 @@
|
||||
},
|
||||
"comment": "直播间基本信息"
|
||||
},
|
||||
"live_wbi": {
|
||||
"url": "https://api.bilibili.com/x/space/wbi/acc/info",
|
||||
"method": "GET",
|
||||
"verify": false,
|
||||
"params": {
|
||||
"mid": "int: uid",
|
||||
"w_rid": "str: Wbi签名",
|
||||
"wts": "int: 当前时间戳"
|
||||
},
|
||||
"comment": "直播间基本信息(WBI版本)"
|
||||
},
|
||||
"video": {
|
||||
"url": "https://api.bilibili.com/x/space/arc/search",
|
||||
"method": "GET",
|
||||
|
@ -26,7 +26,7 @@ class StarBot:
|
||||
"""
|
||||
StarBot 类
|
||||
"""
|
||||
VERSION = "2.0.10"
|
||||
VERSION = "2.0.14"
|
||||
STARBOT_ASCII_LOGO = "\n".join(
|
||||
(
|
||||
r" _____ _ ____ _ ",
|
||||
@ -35,7 +35,7 @@ class StarBot:
|
||||
r" \___ \| __/ _` | '__| _ < / _ \| __|",
|
||||
r" ____) | || (_| | | | |_) | (_) | |_ ",
|
||||
r" |_____/ \__\__,_|_| |____/ \___/ \__|",
|
||||
f" StarBot - (v{VERSION}) 2024-06-30",
|
||||
f" StarBot - (v{VERSION}) 2025-01-08",
|
||||
r" Github: https://github.com/Starlwr/StarBot",
|
||||
r"",
|
||||
r"",
|
||||
|
@ -31,6 +31,9 @@ async def dynamic_spider(datasource: DataSource):
|
||||
except ResponseCodeException as ex:
|
||||
if ex.code == -6:
|
||||
continue
|
||||
if ex.code == 4100000:
|
||||
logger.error("B 站登录凭据已失效, 无法继续抓取动态,请配置新登录凭据后重启服务")
|
||||
continue
|
||||
logger.error(f"动态推送任务抓取最新动态异常, HTTP 错误码: {ex.code} ({ex.msg})")
|
||||
except NetworkException:
|
||||
continue
|
||||
|
@ -167,6 +167,48 @@ class LiveRoom:
|
||||
}
|
||||
return await request(api['method'], api["url"], params, credential=self.credential)
|
||||
|
||||
async def get_room_info_v2(self):
|
||||
"""
|
||||
获取直播间信息(标题,简介等)
|
||||
"""
|
||||
api = "https://api.live.bilibili.com/room/v1/Room/get_info"
|
||||
params = {
|
||||
"room_id": self.room_display_id
|
||||
}
|
||||
return await request("GET", api, params, credential=self.credential)
|
||||
|
||||
async def get_fans_medal_info(self, uid: int):
|
||||
"""
|
||||
获取粉丝勋章信息
|
||||
|
||||
Args:
|
||||
uid: 用户 UID
|
||||
"""
|
||||
api = "https://api.live.bilibili.com/xlive/app-ucenter/v1/fansMedal/fans_medal_info"
|
||||
params = {
|
||||
"target_id": uid,
|
||||
"room_id": self.room_display_id
|
||||
}
|
||||
return await request("GET", api, params, credential=self.credential)
|
||||
|
||||
async def get_guards_info(self, uid: int):
|
||||
"""
|
||||
获取大航海信息
|
||||
|
||||
Args:
|
||||
uid: 用户 UID
|
||||
"""
|
||||
api = "https://api.live.bilibili.com/xlive/app-room/v2/guardTab/topListNew"
|
||||
params = {
|
||||
"roomid": self.room_display_id,
|
||||
"page": 1,
|
||||
"ruid": uid,
|
||||
"page_size": 20,
|
||||
"typ": 5,
|
||||
"platform": "web"
|
||||
}
|
||||
return await request("GET", api, params, credential=self.credential)
|
||||
|
||||
async def get_user_info_in_room(self):
|
||||
"""
|
||||
获取自己在直播间的信息(粉丝勋章等级,直播用户等级等)
|
||||
|
@ -2,6 +2,7 @@ import asyncio
|
||||
import time
|
||||
import typing
|
||||
from asyncio import AbstractEventLoop
|
||||
from datetime import datetime
|
||||
from typing import Optional, Any, Union, List
|
||||
|
||||
from loguru import logger
|
||||
@ -183,6 +184,8 @@ class Up(BaseModel):
|
||||
|
||||
locked = False
|
||||
room_info = {}
|
||||
fans_medal_info = {}
|
||||
guards_info = {}
|
||||
|
||||
# 是否为真正开播
|
||||
if "live_time" in event["data"]:
|
||||
@ -204,25 +207,34 @@ class Up(BaseModel):
|
||||
logger.opt(colors=True).info(f"<magenta>[开播] {self.uname} ({self.room_id})</>")
|
||||
|
||||
try:
|
||||
room_info = await self.__live_room.get_room_info()
|
||||
room_info = await self.__live_room.get_room_info_v2()
|
||||
fans_medal_info = await self.__live_room.get_fans_medal_info(self.uid)
|
||||
guards_info = await self.__live_room.get_guards_info(self.uid)
|
||||
except ResponseCodeException as ex:
|
||||
if ex.code == 19002005:
|
||||
locked = True
|
||||
logger.warning(f"{self.uname} ({self.room_id}) 的直播间已加密")
|
||||
else:
|
||||
logger.error(f"{self.uname} ({self.room_id}) 的直播间信息获取失败, 错误信息: {ex.code} ({ex.msg})")
|
||||
|
||||
if not locked:
|
||||
self.uname = room_info["anchor_info"]["base_info"]["uname"]
|
||||
# 此处若有合适 API 需更新一下最新昵称
|
||||
pass
|
||||
|
||||
live_start_time = room_info["room_info"]["live_start_time"] if not locked else int(time.time())
|
||||
if locked:
|
||||
live_start_time = int(time.time())
|
||||
else:
|
||||
if room_info["live_time"] != "0000-00-00 00:00:00":
|
||||
time_format = "%Y-%m-%d %H:%M:%S"
|
||||
live_start_time = int(datetime.strptime(room_info["live_time"], time_format).timestamp())
|
||||
else:
|
||||
live_start_time = int(time.time())
|
||||
await redis.set_live_start_time(self.room_id, live_start_time)
|
||||
|
||||
if not locked:
|
||||
fans_count = room_info["anchor_info"]["relation_info"]["attention"]
|
||||
if room_info["anchor_info"]["medal_info"] is None:
|
||||
fans_medal_count = 0
|
||||
else:
|
||||
fans_medal_count = room_info["anchor_info"]["medal_info"]["fansclub"]
|
||||
guard_count = room_info["guard_info"]["count"]
|
||||
fans_count = room_info["attention"]
|
||||
fans_medal_count = fans_medal_info["fans_medal_light_count"]
|
||||
guard_count = guards_info["info"]["num"]
|
||||
await redis.set_fans_count(self.room_id, live_start_time, fans_count)
|
||||
await redis.set_fans_medal_count(self.room_id, live_start_time, fans_medal_count)
|
||||
await redis.set_guard_count(self.room_id, live_start_time, guard_count)
|
||||
@ -231,12 +243,11 @@ class Up(BaseModel):
|
||||
|
||||
# 推送开播消息
|
||||
if not locked:
|
||||
arg_base = room_info["room_info"]
|
||||
args = {
|
||||
"{uname}": self.uname,
|
||||
"{title}": arg_base["title"],
|
||||
"{title}": room_info["title"],
|
||||
"{url}": f"https://live.bilibili.com/{self.room_id}",
|
||||
"{cover}": "".join(["{urlpic=", arg_base["cover"], "}"])
|
||||
"{cover}": "".join(["{urlpic=", room_info["user_cover"], "}"])
|
||||
}
|
||||
else:
|
||||
args = {
|
||||
@ -482,11 +493,15 @@ class Up(BaseModel):
|
||||
|
||||
locked = False
|
||||
room_info = {}
|
||||
fans_medal_info = {}
|
||||
guards_info = {}
|
||||
|
||||
# 基础数据变动
|
||||
if self.__any_live_report_item_enabled(["fans_change", "fans_medal_change", "guard_change"]):
|
||||
try:
|
||||
room_info = await self.__live_room.get_room_info()
|
||||
room_info = await self.__live_room.get_room_info_v2()
|
||||
fans_medal_info = await self.__live_room.get_fans_medal_info(self.uid)
|
||||
guards_info = await self.__live_room.get_guards_info(self.uid)
|
||||
except ResponseCodeException as ex:
|
||||
if ex.code == 19002005:
|
||||
locked = True
|
||||
@ -506,21 +521,18 @@ class Up(BaseModel):
|
||||
else:
|
||||
guard_count = -1
|
||||
|
||||
if room_info["anchor_info"]["medal_info"] is None:
|
||||
fans_medal_count_after = 0
|
||||
else:
|
||||
fans_medal_count_after = room_info["anchor_info"]["medal_info"]["fansclub"]
|
||||
fans_medal_count_after = fans_medal_info["fans_medal_light_count"]
|
||||
|
||||
live_report_param.update({
|
||||
# 粉丝变动
|
||||
"fans_before": fans_count,
|
||||
"fans_after": room_info["anchor_info"]["relation_info"]["attention"],
|
||||
"fans_after": room_info["attention"],
|
||||
# 粉丝团(粉丝勋章数)变动
|
||||
"fans_medal_before": fans_medal_count,
|
||||
"fans_medal_after": fans_medal_count_after,
|
||||
# 大航海变动
|
||||
"guard_before": guard_count,
|
||||
"guard_after": room_info["guard_info"]["count"]
|
||||
"guard_after": guards_info["info"]["num"]
|
||||
})
|
||||
|
||||
# 直播数据
|
||||
|
@ -676,7 +676,7 @@ class LiveReportGenerator:
|
||||
end = abs_max + (-abs_max % 10)
|
||||
step = int((end - start) / 10)
|
||||
|
||||
yticks = list(range(start, end)[::step])
|
||||
yticks = list(range(start, end)[::step]) if step != 0 else [0]
|
||||
yticks.append(end)
|
||||
return cls.__get_line_diagram(
|
||||
indexs, profits, [], yticks, [], [], (-1, length), (start, end), width
|
||||
|
@ -158,7 +158,7 @@ class RankingGenerator:
|
||||
chart = PicGenerator(width, (face_size * count) + (row_space * (count - 1)))
|
||||
chart.set_row_space(row_space)
|
||||
for i in range(count):
|
||||
bar_width = int(abs(counts[i]) / top_count * top_bar_width)
|
||||
bar_width = int(abs(counts[i]) / top_count * top_bar_width) if top_count != 0 else 0.1
|
||||
if bar_width != 0:
|
||||
if counts[i] > 0:
|
||||
bar = cls.__get_rank_bar_pic(bar_width, bar_height, start_color, end_color)
|
||||
|
@ -73,7 +73,7 @@ async def request(method: str,
|
||||
# 使用 Referer 和 UA 请求头以绕过反爬虫机制
|
||||
default_headers = {
|
||||
"Referer": "https://www.bilibili.com",
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Core/1.94.218.400 QQBrowser/12.1.5496.400"
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36 Core/1.116.462.400 QQBrowser/13.3.6197.400"
|
||||
}
|
||||
headers = default_headers
|
||||
|
||||
@ -134,7 +134,7 @@ async def request(method: str,
|
||||
content_type = resp.headers.get("content-type")
|
||||
|
||||
# 不是 application/json
|
||||
if content_type.lower().index("application/json") == -1:
|
||||
if content_type.lower().find("application/json") == -1:
|
||||
raise ResponseException("响应不是 application/json 类型")
|
||||
|
||||
raw_data = await resp.text()
|
||||
@ -156,7 +156,7 @@ async def request(method: str,
|
||||
|
||||
if code != 0:
|
||||
# 4101131: 加载错误,请稍后再试, 22015: 您的账号异常,请稍后再试
|
||||
if code == 4101131 or code == 22015:
|
||||
if code == 4101131 or code == 22015 or code == -352:
|
||||
await asyncio.sleep(10)
|
||||
continue
|
||||
|
||||
|
45
starbot/utils/wbi.py
Normal file
45
starbot/utils/wbi.py
Normal file
@ -0,0 +1,45 @@
|
||||
# https://socialsisteryi.github.io/bilibili-API-collect/docs/misc/sign/wbi.html#python
|
||||
|
||||
from functools import reduce
|
||||
from hashlib import md5
|
||||
import urllib.parse
|
||||
import time
|
||||
|
||||
from starbot.utils.network import request
|
||||
from starbot.utils.utils import get_credential
|
||||
|
||||
mixinKeyEncTab = [
|
||||
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
|
||||
33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
|
||||
61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
|
||||
36, 20, 34, 44, 52
|
||||
]
|
||||
|
||||
|
||||
def get_mixin_key(orig: str):
|
||||
return reduce(lambda s, i: s + orig[i], mixinKeyEncTab, '')[:32]
|
||||
|
||||
|
||||
def enc_wbi(params: dict, img_key: str, sub_key: str):
|
||||
mixin_key = get_mixin_key(img_key + sub_key)
|
||||
curr_time = round(time.time())
|
||||
params['wts'] = curr_time # 添加 wts 字段
|
||||
params = dict(sorted(params.items())) # 按照 key 重排参数
|
||||
# 过滤 value 中的 "!'()*" 字符
|
||||
params = {
|
||||
k: ''.join(filter(lambda c: c not in "!'()*", str(v)))
|
||||
for k, v
|
||||
in params.items()
|
||||
}
|
||||
query = urllib.parse.urlencode(params) # 序列化参数
|
||||
wbi_sign = md5((query + mixin_key).encode()).hexdigest() # 计算 w_rid
|
||||
params['w_rid'] = wbi_sign
|
||||
return params
|
||||
|
||||
|
||||
async def get_wbi_keys() -> tuple[str, str]:
|
||||
"""获取最新的 img_key 和 sub_key"""
|
||||
result = await request("GET", "https://api.bilibili.com/x/web-interface/nav", credential=get_credential())
|
||||
img_url = result['wbi_img']['img_url']
|
||||
sub_url = result['wbi_img']['sub_url']
|
||||
return img_url.rsplit('/', 1)[1].split('.')[0], sub_url.rsplit('/', 1)[1].split('.')[0]
|
Loading…
x
Reference in New Issue
Block a user