Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Yingjie Wang 2025-02-21 00:43:46 -05:00
commit 0ace306d49
9 changed files with 150 additions and 26 deletions

View File

@ -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",

View File

@ -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"",

View File

@ -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

View File

@ -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):
"""
获取自己在直播间的信息粉丝勋章等级直播用户等级等

View File

@ -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"]
})
# 直播数据

View File

@ -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

View File

@ -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)

View File

@ -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
View 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]