Compare commits

...
28 Commits
Author SHA1 Message Date
wyj 7293845847 new: use local module 2025-02-21 02:45:46 -05:00
wyj 0ace306d49 Merge remote-tracking branch 'upstream/master' 2025-02-21 00:43:46 -05:00
LWR ebf7343586 docs: Update version 2025-01-08 00:08:15 +08:00
LWR 4e70b4605d fix: Fixed None error caused by api risk control 2025-01-08 00:07:56 +08:00
LWR 79afbf8a96 refactor: Refactor wbi utils 2025-01-07 22:41:19 +08:00
方天宬 06334653da feat: Add wbi utils 2025-01-07 22:37:39 +08:00
LWR 354b1b661f docs: Update version 2024-12-04 00:27:40 +08:00
LWR 7965198c85 fix: Fixed error caused by api risk control 2024-12-04 00:25:59 +08:00
LWR e683569d92 feat: Added a tip for expired cookie 2024-12-02 00:00:28 +08:00
LWR f50b81e958 fix: Fixed exception when API returns non-JSON data 2024-12-01 23:54:51 +08:00
LWR a7df9e58de fix: Fixed possible slice step is 0 when generate box profit diagram 2024-11-11 23:02:53 +08:00
wyj 474932c05d fix: fix the error when profits are zeros 2024-11-09 09:41:16 -05:00
LWR f92b4d7136 docs: Update version 2024-11-09 00:15:17 +08:00
LWR fbc766520a fix: Fixed possible ZeroDivisionError when generate double ranking diagram 2024-11-09 00:07:29 +08:00
wyj 8ac3078ea3 fix: fix numpy version 2024-11-07 02:10:12 +08:00
wyj 89532428d5 fix: make top_count nonzero 2024-11-06 13:06:16 -05:00
wyj 0e396728c6 fix: add dependences 2024-11-02 22:15:04 +08:00
wyj c832e1da52 update: add fork info 2024-10-26 12:36:08 -04:00
wyj 221b2c8b0f set pipenv 2024-10-26 22:44:59 +08:00
LWR 6a90fd646e docs: Update version 2024-06-30 15:56:01 +08:00
LWR b62c95580a fix: Fixed an issue in data ranking command when multiple anchors are bound to the same group 2024-06-30 15:44:20 +08:00
LWR ad6ba2886f fix: Fixed TypeError in dynamic push when cards field is None 2024-05-10 22:09:58 +08:00
LWR 2c0a2de421 docs: Update readme 2024-03-25 00:44:56 +08:00
Diam 9372bdf00e feat: Auto remove "UID:" prefix in bind command 2024-03-25 00:06:24 +08:00
LWR ac6e483dd1 docs: Update version 2024-01-29 23:28:06 +08:00
LWR a58242147b fix: Fixed ResponseCodeException -352 in dynamic push 2024-01-29 23:27:09 +08:00
Xi 3f098fa399 feat: Add title to text dynamic picture generate 2024-01-12 22:39:51 +08:00
LWR 5c8beaf125 docs: Update version 2024-01-10 20:04:55 +08:00
23 changed files with 1826 additions and 39 deletions
+11
View File
@@ -0,0 +1,11 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[requires]
python_version = "3.11"
[packages]
starbot-bilibili = { path = "./", editable = true }
Generated
+1548
View File
File diff suppressed because it is too large Load Diff
+18 -2
View File
@@ -1,6 +1,15 @@
# StarBot <div align="center">
一款极速,多功能的哔哩哔哩推送机器人。 ![logo](https://bot.starlwr.com/images/static/logo.jpg)
[![PyPI](https://img.shields.io/pypi/v/starbot-bilibili)](https://pypi.org/project/starbot-bilibili)
[![Python](https://img.shields.io/badge/python-3.10%20|%203.11-blue)](https://www.python.org)
[![License](https://img.shields.io/github/license/Starlwr/StarBot)](https://github.com/Starlwr/StarBot/blob/master/LICENSE)
[![STARS](https://img.shields.io/github/stars/Starlwr/StarBot?color=yellow&label=Stars)](https://github.com/Starlwr/StarBot/stargazers)
**一个极速,多功能的哔哩哔哩推送机器人**
</div>
## 特性 ## 特性
@@ -44,3 +53,10 @@ bot.run()
* [Ariadne](https://github.com/GraiaProject/Ariadne): 一个优雅且完备的 Python QQ 自动化框架,基于 Mirai API HTTP v2 * [Ariadne](https://github.com/GraiaProject/Ariadne): 一个优雅且完备的 Python QQ 自动化框架,基于 Mirai API HTTP v2
* [Fake-QQ-Chat-Window](https://github.com/Redlnn/Fake-QQ-Chat-Window): 伪 QQ 移动客户端聊天窗口,当前官网的推送配置在线预览基于此项目魔改实现 * [Fake-QQ-Chat-Window](https://github.com/Redlnn/Fake-QQ-Chat-Window): 伪 QQ 移动客户端聊天窗口,当前官网的推送配置在线预览基于此项目魔改实现
* [为本项目赞赏支持的小伙伴们](https://bot.starlwr.com/about): 名单较长,详细名单可点击链接在官网查看 * [为本项目赞赏支持的小伙伴们](https://bot.starlwr.com/about): 名单较长,详细名单可点击链接在官网查看
* [JetBrains](https://www.jetbrains.com/?from=StarBot): 为开源项目免费提供的优秀 IDE
[<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg"/>](https://www.jetbrains.com/?from=StarBot)
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=Starlwr/StarBot&type=Date)](https://star-history.com/#Starlwr/StarBot&Date)
+4
View File
@@ -0,0 +1,4 @@
[egg_info]
tag_build =
tag_date = 0
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env python
# coding: utf-8
from setuptools import setup, find_namespace_packages
from os import path
this_directory = path.abspath(path.dirname(__file__))
with open("README.md", "r", encoding="utf-8") as f:
long_description = f.read()
setup(
name='starbot-bilibili',
version='2.0.14',
license='GNU Affero General Public License v3.0',
description='一款极速,多功能的哔哩哔哩推送机器人',
author='LWR',
author_email='lwr1104@qq.com',
url='https://github.com/Starlwr/StarBot',
packages=find_namespace_packages(),
package_data={
'starbot.api': ['*.json'],
'starbot.resource': ['*.png', '*.ttf']
},
install_requires=[
'Brotli>=1.0.9',
'aiomysql>=0.1.1',
'redis>=4.5.5',
'emoji>=2.2.0',
'graia-broadcast==0.19.2',
'creart==0.2.2',
'creart-graia==0.1.5',
'graia-ariadne==0.9.8',
'graia-saya==0.0.16',
'jieba>=0.42.1',
'scipy>=1.10.0',
'Pillow==9.5.0',
'numpy==1.24.3',
'matplotlib==3.7.1',
'wordcloud>=1.8.2.2'
],
keywords='starbot',
long_description=long_description,
long_description_content_type='text/markdown',
python_requires=">=3.8"
)
+22
View File
@@ -15,6 +15,17 @@
}, },
"comment": "用户基本信息" "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": { "relation": {
"url": "https://api.bilibili.com/x/relation/stat", "url": "https://api.bilibili.com/x/relation/stat",
"method": "GET", "method": "GET",
@@ -42,6 +53,17 @@
}, },
"comment": "直播间基本信息" "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": { "video": {
"url": "https://api.bilibili.com/x/space/arc/search", "url": "https://api.bilibili.com/x/space/arc/search",
"method": "GET", "method": "GET",
+3
View File
@@ -44,6 +44,9 @@ async def bind(app: Ariadne,
uid = remove_command_param_placeholder(uid.display) uid = remove_command_param_placeholder(uid.display)
# Replace
uid = uid.replace("UID:", "")
if not uid.isdigit() or int(uid) == 0: if not uid.isdigit() or int(uid) == 0:
await app.send_message(sender, MessageChain(f"请输入正确的UID~\n命令示例:{prefix}绑定 114514"), quote=source) await app.send_message(sender, MessageChain(f"请输入正确的UID~\n命令示例:{prefix}绑定 114514"), quote=source)
return return
@@ -117,6 +117,8 @@ async def room_data(app: Ariadne, source: Source, sender: Union[Friend, Group]):
pic.draw_text("") pic.draw_text("")
pic.draw_text_right(25, "Designed By StarBot", Color.GRAY) pic.draw_text_right(25, "Designed By StarBot", Color.GRAY)
pic.draw_text_right(25, "https://github.com/Starlwr/StarBot", Color.LINK) pic.draw_text_right(25, "https://github.com/Starlwr/StarBot", Color.LINK)
pic.draw_text_right(25, "本Bot由薛定谔的大喵维护", Color.LIGHTBLUE)
pic.draw_text_right(25, "https://gitea.phywyj.dynv6.net/wyj/starbot", Color.LINK)
pic.crop_and_paste_bottom() pic.crop_and_paste_bottom()
await app.send_message(sender, MessageChain(Image(base64=pic.base64()))) await app.send_message(sender, MessageChain(Image(base64=pic.base64())))
@@ -109,6 +109,8 @@ async def room_data_total(app: Ariadne, source: Source, sender: Union[Friend, Gr
pic.draw_text("") pic.draw_text("")
pic.draw_text_right(25, "Designed By StarBot", Color.GRAY) pic.draw_text_right(25, "Designed By StarBot", Color.GRAY)
pic.draw_text_right(25, "https://github.com/Starlwr/StarBot", Color.LINK) pic.draw_text_right(25, "https://github.com/Starlwr/StarBot", Color.LINK)
pic.draw_text_right(25, "本Bot由薛定谔的大喵维护", Color.LIGHTBLUE)
pic.draw_text_right(25, "https://gitea.phywyj.dynv6.net/wyj/starbot", Color.LINK)
pic.crop_and_paste_bottom() pic.crop_and_paste_bottom()
await app.send_message(sender, MessageChain(Image(base64=pic.base64()))) await app.send_message(sender, MessageChain(Image(base64=pic.base64())))
@@ -170,6 +170,8 @@ async def user_data(app: Ariadne, source: Source, sender: Union[Friend, Group],
pic.draw_text("") pic.draw_text("")
pic.draw_text_right(25, "Designed By StarBot", Color.GRAY) pic.draw_text_right(25, "Designed By StarBot", Color.GRAY)
pic.draw_text_right(25, "https://github.com/Starlwr/StarBot", Color.LINK) pic.draw_text_right(25, "https://github.com/Starlwr/StarBot", Color.LINK)
pic.draw_text_right(25, "本Bot由薛定谔的大喵维护", Color.LIGHTBLUE)
pic.draw_text_right(25, "https://gitea.phywyj.dynv6.net/wyj/starbot", Color.LINK)
pic.crop_and_paste_bottom() pic.crop_and_paste_bottom()
await app.send_message(sender, MessageChain(Image(base64=pic.base64()))) await app.send_message(sender, MessageChain(Image(base64=pic.base64())))
@@ -183,6 +183,8 @@ async def user_data_total(app: Ariadne, source: Source, sender: Union[Friend, Gr
pic.draw_text("") pic.draw_text("")
pic.draw_text_right(25, "Designed By StarBot", Color.GRAY) pic.draw_text_right(25, "Designed By StarBot", Color.GRAY)
pic.draw_text_right(25, "https://github.com/Starlwr/StarBot", Color.LINK) pic.draw_text_right(25, "https://github.com/Starlwr/StarBot", Color.LINK)
pic.draw_text_right(25, "本Bot由薛定谔的大喵维护", Color.LIGHTBLUE)
pic.draw_text_right(25, "https://gitea.phywyj.dynv6.net/wyj/starbot", Color.LINK)
pic.crop_and_paste_bottom() pic.crop_and_paste_bottom()
await app.send_message(sender, MessageChain(Image(base64=pic.base64()))) await app.send_message(sender, MessageChain(Image(base64=pic.base64())))
+2
View File
@@ -158,6 +158,8 @@ async def _help(app: Ariadne, sender: Union[Friend, Group]):
# 底部版权信息,请务必保留此处 # 底部版权信息,请务必保留此处
pic.draw_text_right(25, "Designed By StarBot", Color.GRAY) pic.draw_text_right(25, "Designed By StarBot", Color.GRAY)
pic.draw_text_right(25, "https://github.com/Starlwr/StarBot", Color.LINK) pic.draw_text_right(25, "https://github.com/Starlwr/StarBot", Color.LINK)
pic.draw_text_right(25, "本Bot由薛定谔的大喵维护", Color.LIGHTBLUE)
pic.draw_text_right(25, "https://gitea.phywyj.dynv6.net/wyj/starbot", Color.LINK)
pic.crop_and_paste_bottom() pic.crop_and_paste_bottom()
await app.send_message(sender, MessageChain(Image(base64=pic.base64()))) await app.send_message(sender, MessageChain(Image(base64=pic.base64())))
+4 -2
View File
@@ -90,7 +90,7 @@ async def ranking(app: Ariadne,
if length == 0: if length == 0:
await app.send_message(sender, MessageChain(f"{up.uname} 的房间暂无{type_map[_type][2]}数据~"), quote=source) await app.send_message(sender, MessageChain(f"{up.uname} 的房间暂无{type_map[_type][2]}数据~"), quote=source)
return continue
if page > page_length: if page > page_length:
await app.send_message( await app.send_message(
@@ -98,7 +98,7 @@ async def ranking(app: Ariadne,
MessageChain(f"{up.uname} 的房间{type_map[_type][3]}榜页码范围为 1 ~ {page_length}\n请重新输入正确的页码~"), MessageChain(f"{up.uname} 的房间{type_map[_type][3]}榜页码范围为 1 ~ {page_length}\n请重新输入正确的页码~"),
quote=source quote=source
) )
return continue
data = await type_map[_type][1](up.room_id, start, end) data = await type_map[_type][1](up.room_id, start, end)
top_count = (await type_map[_type][1](up.room_id, 0, 0))[0][1] top_count = (await type_map[_type][1](up.room_id, 0, 0))[0][1]
@@ -129,6 +129,8 @@ async def ranking(app: Ariadne,
pic.draw_text("") pic.draw_text("")
pic.draw_text_right(25, "Designed By StarBot", Color.GRAY) pic.draw_text_right(25, "Designed By StarBot", Color.GRAY)
pic.draw_text_right(25, "https://github.com/Starlwr/StarBot", Color.LINK) pic.draw_text_right(25, "https://github.com/Starlwr/StarBot", Color.LINK)
pic.draw_text_right(25, "本Bot由薛定谔的大喵维护", Color.LIGHTBLUE)
pic.draw_text_right(25, "https://gitea.phywyj.dynv6.net/wyj/starbot", Color.LINK)
pic.crop_and_paste_bottom() pic.crop_and_paste_bottom()
await app.send_message(sender, MessageChain(Image(base64=pic.base64()))) await app.send_message(sender, MessageChain(Image(base64=pic.base64())))
@@ -80,7 +80,7 @@ async def ranking_double(app: Ariadne,
if length == 0: if length == 0:
await app.send_message(sender, MessageChain(f"{up.uname} 的房间暂无{type_map[_type][2]}数据~"), quote=source) await app.send_message(sender, MessageChain(f"{up.uname} 的房间暂无{type_map[_type][2]}数据~"), quote=source)
return continue
if page > page_length: if page > page_length:
await app.send_message( await app.send_message(
@@ -88,7 +88,7 @@ async def ranking_double(app: Ariadne,
MessageChain(f"{up.uname} 的房间{type_map[_type][3]}榜页码范围为 1 ~ {page_length}\n请重新输入正确的页码~"), MessageChain(f"{up.uname} 的房间{type_map[_type][3]}榜页码范围为 1 ~ {page_length}\n请重新输入正确的页码~"),
quote=source quote=source
) )
return continue
data = await type_map[_type][1](up.room_id, start, end) data = await type_map[_type][1](up.room_id, start, end)
top_count = max( top_count = max(
@@ -124,6 +124,8 @@ async def ranking_double(app: Ariadne,
pic.draw_text("") pic.draw_text("")
pic.draw_text_right(25, "Designed By StarBot", Color.GRAY) pic.draw_text_right(25, "Designed By StarBot", Color.GRAY)
pic.draw_text_right(25, "https://github.com/Starlwr/StarBot", Color.LINK) pic.draw_text_right(25, "https://github.com/Starlwr/StarBot", Color.LINK)
pic.draw_text_right(25, "本Bot由薛定谔的大喵维护", Color.LIGHTBLUE)
pic.draw_text_right(25, "https://gitea.phywyj.dynv6.net/wyj/starbot", Color.LINK)
pic.crop_and_paste_bottom() pic.crop_and_paste_bottom()
await app.send_message(sender, MessageChain(Image(base64=pic.base64()))) await app.send_message(sender, MessageChain(Image(base64=pic.base64())))
+2 -2
View File
@@ -26,7 +26,7 @@ class StarBot:
""" """
StarBot 类 StarBot 类
""" """
VERSION = "2.0.7" VERSION = "2.0.14"
STARBOT_ASCII_LOGO = "\n".join( STARBOT_ASCII_LOGO = "\n".join(
( (
r" _____ _ ____ _ ", r" _____ _ ____ _ ",
@@ -35,7 +35,7 @@ class StarBot:
r" \___ \| __/ _` | '__| _ < / _ \| __|", r" \___ \| __/ _` | '__| _ < / _ \| __|",
r" ____) | || (_| | | | |_) | (_) | |_ ", r" ____) | || (_| | | | |_) | (_) | |_ ",
r" |_____/ \__\__,_|_| |____/ \___/ \__|", r" |_____/ \__\__,_|_| |____/ \___/ \__|",
f" StarBot - (v{VERSION}) 2023-10-15", f" StarBot - (v{VERSION}) 2025-01-08",
r" Github: https://github.com/Starlwr/StarBot", r" Github: https://github.com/Starlwr/StarBot",
r"", r"",
r"", r"",
+5
View File
@@ -31,6 +31,9 @@ async def dynamic_spider(datasource: DataSource):
except ResponseCodeException as ex: except ResponseCodeException as ex:
if ex.code == -6: if ex.code == -6:
continue continue
if ex.code == 4100000:
logger.error("B 站登录凭据已失效, 无法继续抓取动态,请配置新登录凭据后重启服务")
continue
logger.error(f"动态推送任务抓取最新动态异常, HTTP 错误码: {ex.code} ({ex.msg})") logger.error(f"动态推送任务抓取最新动态异常, HTTP 错误码: {ex.code} ({ex.msg})")
except NetworkException: except NetworkException:
continue continue
@@ -61,6 +64,8 @@ async def dynamic_spider(datasource: DataSource):
detail = latest_dynamic["cards"][i] detail = latest_dynamic["cards"][i]
except IndexError: except IndexError:
break break
except TypeError:
break
dynamic_id = detail['desc']['dynamic_id'] dynamic_id = detail['desc']['dynamic_id']
if await redis.exists_dynamic(dynamic_id): if await redis.exists_dynamic(dynamic_id):
+42
View File
@@ -167,6 +167,48 @@ class LiveRoom:
} }
return await request(api['method'], api["url"], params, credential=self.credential) 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): async def get_user_info_in_room(self):
""" """
获取自己在直播间的信息(粉丝勋章等级,直播用户等级等) 获取自己在直播间的信息(粉丝勋章等级,直播用户等级等)
+31 -19
View File
@@ -2,6 +2,7 @@ import asyncio
import time import time
import typing import typing
from asyncio import AbstractEventLoop from asyncio import AbstractEventLoop
from datetime import datetime
from typing import Optional, Any, Union, List from typing import Optional, Any, Union, List
from loguru import logger from loguru import logger
@@ -183,6 +184,8 @@ class Up(BaseModel):
locked = False locked = False
room_info = {} room_info = {}
fans_medal_info = {}
guards_info = {}
# 是否为真正开播 # 是否为真正开播
if "live_time" in event["data"]: 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})</>") logger.opt(colors=True).info(f"<magenta>[开播] {self.uname} ({self.room_id})</>")
try: 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: except ResponseCodeException as ex:
if ex.code == 19002005: if ex.code == 19002005:
locked = True locked = True
logger.warning(f"{self.uname} ({self.room_id}) 的直播间已加密") logger.warning(f"{self.uname} ({self.room_id}) 的直播间已加密")
else:
logger.error(f"{self.uname} ({self.room_id}) 的直播间信息获取失败, 错误信息: {ex.code} ({ex.msg})")
if not locked: 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) await redis.set_live_start_time(self.room_id, live_start_time)
if not locked: if not locked:
fans_count = room_info["anchor_info"]["relation_info"]["attention"] fans_count = room_info["attention"]
if room_info["anchor_info"]["medal_info"] is None: fans_medal_count = fans_medal_info["fans_medal_light_count"]
fans_medal_count = 0 guard_count = guards_info["info"]["num"]
else:
fans_medal_count = room_info["anchor_info"]["medal_info"]["fansclub"]
guard_count = room_info["guard_info"]["count"]
await redis.set_fans_count(self.room_id, live_start_time, fans_count) 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_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) await redis.set_guard_count(self.room_id, live_start_time, guard_count)
@@ -231,12 +243,11 @@ class Up(BaseModel):
# 推送开播消息 # 推送开播消息
if not locked: if not locked:
arg_base = room_info["room_info"]
args = { args = {
"{uname}": self.uname, "{uname}": self.uname,
"{title}": arg_base["title"], "{title}": room_info["title"],
"{url}": f"https://live.bilibili.com/{self.room_id}", "{url}": f"https://live.bilibili.com/{self.room_id}",
"{cover}": "".join(["{urlpic=", arg_base["cover"], "}"]) "{cover}": "".join(["{urlpic=", room_info["user_cover"], "}"])
} }
else: else:
args = { args = {
@@ -482,11 +493,15 @@ class Up(BaseModel):
locked = False locked = False
room_info = {} room_info = {}
fans_medal_info = {}
guards_info = {}
# 基础数据变动 # 基础数据变动
if self.__any_live_report_item_enabled(["fans_change", "fans_medal_change", "guard_change"]): if self.__any_live_report_item_enabled(["fans_change", "fans_medal_change", "guard_change"]):
try: 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: except ResponseCodeException as ex:
if ex.code == 19002005: if ex.code == 19002005:
locked = True locked = True
@@ -506,21 +521,18 @@ class Up(BaseModel):
else: else:
guard_count = -1 guard_count = -1
if room_info["anchor_info"]["medal_info"] is None: fans_medal_count_after = fans_medal_info["fans_medal_light_count"]
fans_medal_count_after = 0
else:
fans_medal_count_after = room_info["anchor_info"]["medal_info"]["fansclub"]
live_report_param.update({ live_report_param.update({
# 粉丝变动 # 粉丝变动
"fans_before": fans_count, "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_before": fans_medal_count,
"fans_medal_after": fans_medal_count_after, "fans_medal_after": fans_medal_count_after,
# 大航海变动 # 大航海变动
"guard_before": guard_count, "guard_before": guard_count,
"guard_after": room_info["guard_info"]["count"] "guard_after": guards_info["info"]["num"]
}) })
# 直播数据 # 直播数据
+24 -5
View File
@@ -11,7 +11,7 @@ from .PicGenerator import Color, PicGenerator
from ..utils import config from ..utils import config
from ..utils.network import request from ..utils.network import request
from ..utils.utils import open_url_image, timestamp_format, split_list, limit_str_length, \ from ..utils.utils import open_url_image, timestamp_format, split_list, limit_str_length, \
mask_round, mask_rounded_rectangle mask_round, mask_rounded_rectangle, get_credential
class DynamicPicGenerator: class DynamicPicGenerator:
@@ -69,6 +69,8 @@ class DynamicPicGenerator:
pic.move_pos(0, 15) pic.move_pos(0, 15)
pic.draw_text_right(25, "Designed By StarBot", Color.GRAY) pic.draw_text_right(25, "Designed By StarBot", Color.GRAY)
pic.draw_text_right(25, "https://github.com/Starlwr/StarBot", Color.LINK) pic.draw_text_right(25, "https://github.com/Starlwr/StarBot", Color.LINK)
pic.draw_text_right(25, "本Bot由薛定谔的大喵维护", Color.LIGHTBLUE)
pic.draw_text_right(25, "https://gitea.phywyj.dynv6.net/wyj/starbot", Color.LINK)
pic.crop_and_paste_bottom() pic.crop_and_paste_bottom()
return pic.base64() return pic.base64()
@@ -168,10 +170,27 @@ class DynamicPicGenerator:
mod: 表情区块字典 mod: 表情区块字典
""" """
mod["img"] = await open_url_image(mod["emoji"]["icon_url"]) mod["img"] = await open_url_image(mod["emoji"]["icon_url"])
modules_url = f"https://api.bilibili.com/x/polymer/web-dynamic/v1/detail?timezone_offset=-480&id={dynamic_id}" if dynamic_type == 2 or dynamic_type == 4:
modules = (await request("GET", modules_url))["item"]["modules"]["module_dynamic"]["desc"] # 有些动态带有标题,不显示标题会缺少上下文
modules = modules["rich_text_nodes"] if modules else [] modules_url =f"https://api.bilibili.com/x/polymer/web-dynamic/v1/detail?timezone_offset=-480&id={dynamic_id}&features=itemOpusStyle,opusBigCover,onlyfansVote"
modules = (await request("GET", modules_url, credential=get_credential()))["item"]["modules"]["module_dynamic"]["major"]["opus"]
title = modules["title"]
modules = modules["summary"]
if modules:
modules = modules["rich_text_nodes"]
if title is not None:
modules.insert(0, {
'orig_text': f"{title}\n",
'text': f"{title}\n",
'type': 'RICH_TEXT_NODE_TYPE_TEXT'
})
else:
modules = []
else :
modules_url = f"https://api.bilibili.com/x/polymer/web-dynamic/v1/detail?timezone_offset=-480&id={dynamic_id}"
modules = (await request("GET", modules_url, credential=get_credential()))["item"]["modules"]["module_dynamic"]["desc"]
modules = modules["rich_text_nodes"] if modules else []
# 下载表情 # 下载表情
download_picture_tasks = [] download_picture_tasks = []
+4 -2
View File
@@ -405,6 +405,8 @@ class LiveReportGenerator:
pic.set_row_space(10) pic.set_row_space(10)
pic.draw_text_right(50, "Designed By StarBot", Color.GRAY, logo_limit) pic.draw_text_right(50, "Designed By StarBot", Color.GRAY, logo_limit)
pic.draw_text_right(50, "https://github.com/Starlwr/StarBot", Color.LINK, logo_limit) pic.draw_text_right(50, "https://github.com/Starlwr/StarBot", Color.LINK, logo_limit)
pic.draw_text_right(50, "本Bot由薛定谔的大喵维护", Color.LIGHTBLUE, logo_limit)
pic.draw_text_right(50, "https://gitea.phywyj.dynv6.net/wyj/starbot", Color.LINK, logo_limit)
pic.crop_and_paste_bottom() pic.crop_and_paste_bottom()
return pic.base64() return pic.base64()
@@ -669,12 +671,12 @@ class LiveReportGenerator:
length = len(profits) length = len(profits)
indexs = list(range(0, length)) indexs = list(range(0, length))
abs_max = math.ceil(max(max(profits), abs(min(profits)))) abs_max = math.ceil(max(max(profits), abs(min(profits)), 0.01))
start = -abs_max - (-abs_max % 10) start = -abs_max - (-abs_max % 10)
end = abs_max + (-abs_max % 10) end = abs_max + (-abs_max % 10)
step = int((end - start) / 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) yticks.append(end)
return cls.__get_line_diagram( return cls.__get_line_diagram(
indexs, profits, [], yticks, [], [], (-1, length), (start, end), width indexs, profits, [], yticks, [], [], (-1, length), (start, end), width
+2 -2
View File
@@ -153,12 +153,12 @@ class RankingGenerator:
reverse_bar_x = face_x + offset reverse_bar_x = face_x + offset
top_bar_width = (width - face_size) / 2 + offset top_bar_width = (width - face_size) / 2 + offset
if top_count is None: if top_count is None:
top_count = max(max(counts), abs(min(counts))) top_count = max(max(counts), abs(min(counts)), 0.001)
chart = PicGenerator(width, (face_size * count) + (row_space * (count - 1))) chart = PicGenerator(width, (face_size * count) + (row_space * (count - 1)))
chart.set_row_space(row_space) chart.set_row_space(row_space)
for i in range(count): 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 bar_width != 0:
if counts[i] > 0: if counts[i] > 0:
bar = cls.__get_rank_bar_pic(bar_width, bar_height, start_color, end_color) bar = cls.__get_rank_bar_pic(bar_width, bar_height, start_color, end_color)
+3 -3
View File
@@ -73,7 +73,7 @@ async def request(method: str,
# 使用 Referer 和 UA 请求头以绕过反爬虫机制 # 使用 Referer 和 UA 请求头以绕过反爬虫机制
default_headers = { default_headers = {
"Referer": "https://www.bilibili.com", "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 headers = default_headers
@@ -134,7 +134,7 @@ async def request(method: str,
content_type = resp.headers.get("content-type") content_type = resp.headers.get("content-type")
# 不是 application/json # 不是 application/json
if content_type.lower().index("application/json") == -1: if content_type.lower().find("application/json") == -1:
raise ResponseException("响应不是 application/json 类型") raise ResponseException("响应不是 application/json 类型")
raw_data = await resp.text() raw_data = await resp.text()
@@ -156,7 +156,7 @@ async def request(method: str,
if code != 0: if code != 0:
# 4101131: 加载错误,请稍后再试, 22015: 您的账号异常,请稍后再试 # 4101131: 加载错误,请稍后再试, 22015: 您的账号异常,请稍后再试
if code == 4101131 or code == 22015: if code == 4101131 or code == 22015 or code == -352:
await asyncio.sleep(10) await asyncio.sleep(10)
continue continue
+45
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]