From 57166b0f45550343658e3b89d99fe10b08f890cd Mon Sep 17 00:00:00 2001 From: KanbeReina Date: Fri, 13 Feb 2026 18:34:02 +0800 Subject: [PATCH 1/7] =?UTF-8?q?feat=EF=BC=9A=E6=9B=B4=E6=96=B0=20`UserInfo?= =?UTF-8?q?s`=20=E7=89=88=E6=9C=AC=E5=8F=B7=E8=87=B3=20`v3.0.0`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/libs/userinfos/PKG-INFO | 13 ++- src/libs/userinfos/pyproject.toml | 28 ++--- src/libs/userinfos/src/UserInfos/__init__.py | 10 +- src/libs/userinfos/src/UserInfos/db.py | 41 +++---- src/libs/userinfos/src/UserInfos/log.py | 18 --- src/libs/userinfos/src/UserInfos/main.py | 76 ++++++------ .../src/UserInfos/models/permission.py | 1 + .../userinfos/src/UserInfos/permission.py | 108 ++++++++---------- .../userinfos/src/UserInfos/utils/__init__.py | 7 ++ .../userinfos/src/UserInfos/utils/cache.py | 13 ++- .../src/UserInfos/utils/commands/__init__.py | 21 +++- .../src/UserInfos/utils/commands/ban_user.py | 47 +++----- .../src/UserInfos/utils/commands/change.py | 78 +++++-------- .../UserInfos/utils/commands/error_shorten.py | 2 +- .../src/UserInfos/utils/commands/lookup.py | 46 +++----- .../UserInfos/utils/commands/unban_user.py | 47 +++----- .../userinfos/src/UserInfos/utils/cooldown.py | 2 + .../userinfos/src/UserInfos/utils/json.py | 12 +- src/libs/userinfos/tests/__init__.py | 0 src/libs/userinfos/tests/bot.py | 12 -- 20 files changed, 232 insertions(+), 350 deletions(-) delete mode 100644 src/libs/userinfos/src/UserInfos/log.py delete mode 100644 src/libs/userinfos/tests/__init__.py delete mode 100644 src/libs/userinfos/tests/bot.py diff --git a/src/libs/userinfos/PKG-INFO b/src/libs/userinfos/PKG-INFO index 290b32e..6a3f506 100644 --- a/src/libs/userinfos/PKG-INFO +++ b/src/libs/userinfos/PKG-INFO @@ -1,16 +1,17 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.3 Name: UserInfos -Version: 2.0.0 -Summary: A plugin for get user's permisson or other infos -Author-Email: KanbeReina +Version: 3.0.0 +Summary: 适用于 SariBot 的用户数据&权限管理插件 +Author: KanbeReina +Author-email: KanbeReina License: Private -Requires-Python: <3.14,>=3.12 Requires-Dist: nonebot2[fastapi]>=2.4.2 Requires-Dist: aiofiles>=24.1.0 -Requires-Dist: nonebot-plugin-litestore>=v0.1.1 Requires-Dist: nonebot-adapter-onebot>=2.4.6 Requires-Dist: nonebot-plugin-alconna>=0.57.4 Requires-Dist: orjson>=3.11.3 +Requires-Dist: nonebot-plugin-localstore>=0.7.4 +Requires-Python: >=3.12, <3.14 Description-Content-Type: text/markdown # UserInfos diff --git a/src/libs/userinfos/pyproject.toml b/src/libs/userinfos/pyproject.toml index af27192..b10f93b 100644 --- a/src/libs/userinfos/pyproject.toml +++ b/src/libs/userinfos/pyproject.toml @@ -1,29 +1,31 @@ [project] name = "UserInfos" -version = "2.0.0" -description = "A plugin for get user's permisson or other infos" +version = "3.0.0" +description = "适用于 SariBot 的用户数据&权限管理插件" authors = [ - { name = "KanbeReina", email = "kano.2525@qq.com" }, + {name = "KanbeReina", email = "kano.2525@qq.com"}, ] dependencies = [ "nonebot2[fastapi]>=2.4.2", "aiofiles>=24.1.0", - "nonebot-plugin-litestore>=v0.1.1", "nonebot-adapter-onebot>=2.4.6", "nonebot-plugin-alconna>=0.57.4", "orjson>=3.11.3", + "nonebot-plugin-localstore>=0.7.4", ] requires-python = ">=3.12,<3.14" readme = "README.md" - -[project.license] -text = "Private" +license = {text = "Private"} [build-system] -requires = [ - "pdm-backend", -] -build-backend = "pdm.backend" +requires = ["uv_build"] +build-backend = "uv_build" -[tool.pdm] -distribution = true +[tool.uv.build-backend] +module-name = "UserInfos" + +[dependency-groups] +dev = [ + "pytest>=9.0.2", + "ruff>=0.15.1", +] diff --git a/src/libs/userinfos/src/UserInfos/__init__.py b/src/libs/userinfos/src/UserInfos/__init__.py index 81cc356..7aa4b8a 100644 --- a/src/libs/userinfos/src/UserInfos/__init__.py +++ b/src/libs/userinfos/src/UserInfos/__init__.py @@ -1,10 +1,10 @@ from nonebot import require -require("nonebot_plugin_litestore") + +require("nonebot_plugin_localstore") require("nonebot_plugin_alconna") -from .main import * -from .db import get_database -from .log import * +from .main import * # noqa: E402, F403 +from .db import DATABASE # noqa: E402 -__all__ = ["get_database"] +__all__ = ["DATABASE"] diff --git a/src/libs/userinfos/src/UserInfos/db.py b/src/libs/userinfos/src/UserInfos/db.py index 07216c6..d002506 100644 --- a/src/libs/userinfos/src/UserInfos/db.py +++ b/src/libs/userinfos/src/UserInfos/db.py @@ -1,15 +1,13 @@ import time from typing import Optional +import nonebot_plugin_localstore as store from nonebot import get_driver from nonebot.log import logger -from nonebot_plugin_litestore import PluginStore as Store # 已在__init__.py中提提前require from .models.permission import PermissionLevel from .models.user import User -from .utils import ( - AsyncJson, PermissionCache as Cache -) +from .utils import AsyncJson, PermissionCache as Cache Driver = get_driver() @@ -20,7 +18,7 @@ class UserDatabase: def __init__(self): # 创建数据存储目录 self._file = AsyncJson( - path=Store.data.get_file("userdata.json") + path=store.get_data_file(plugin_name="UserInfos", filename="userdata.json") ) self._cache = Cache() @@ -45,21 +43,21 @@ async def register_user(self, user_id: str, permission: PermissionLevel): raise ValueError(f"重复创建用户(ID:{user_id})!") # 更新缓存 self.cache.update_one(user_id, permission) - data.update({ - user_id: User( - id_str=user_id, - permission=permission, - create_at=int(time.time()) - ).model_dump() - }) + data.update( + { + user_id: User( + id_str=user_id, permission=permission, create_at=int(time.time()) + ).model_dump() + } + ) await self._file.write(data) logger.info(f"初始化用户(ID:{user_id})数据") async def update_user( - self, - user_id: str, - permission: Optional[PermissionLevel] = None, - extra_data: Optional[dict] = None + self, + user_id: str, + permission: Optional[PermissionLevel] = None, + extra_data: Optional[dict] = None, ): """更新用户的相关数据""" data = await self._file.read() @@ -71,9 +69,7 @@ async def update_user( uinfo = User.model_validate(result) uinfo.permission = uinfo.permission if permission is None else permission uinfo.extra_data = uinfo.extra_data if extra_data is None else extra_data - data.update({ - user_id: uinfo.model_dump() - }) + data.update({user_id: uinfo.model_dump()}) await self._file.write(data) logger.info(f"更新用户(ID:{user_id})数据") @@ -125,9 +121,4 @@ async def init_database(): await DATABASE.ensure_init() -def get_database(): - """获取数据库实例""" - return DATABASE - - -__all__ = ["get_database"] +__all__ = ["DATABASE"] diff --git a/src/libs/userinfos/src/UserInfos/log.py b/src/libs/userinfos/src/UserInfos/log.py deleted file mode 100644 index 4eee88d..0000000 --- a/src/libs/userinfos/src/UserInfos/log.py +++ /dev/null @@ -1,18 +0,0 @@ -from nonebot.log import logger, default_filter, default_format -from nonebot_plugin_litestore import PluginStore as Store - - -save_path = Store.data.get_dir()/"logs" -save_path.mkdir(exist_ok=True) - - -logger.add( - save_path/"bot_{time}.log", - level=0, - diagnose=False, - filter=default_filter, - format=default_format, - rotation="200 MB", - retention="14 days", - encoding="utf-8" -) diff --git a/src/libs/userinfos/src/UserInfos/main.py b/src/libs/userinfos/src/UserInfos/main.py index 08205cb..193bfac 100644 --- a/src/libs/userinfos/src/UserInfos/main.py +++ b/src/libs/userinfos/src/UserInfos/main.py @@ -1,52 +1,42 @@ from nonebot.adapters.onebot.v11 import MessageEvent from nonebot.params import T_State + # Alconna from nonebot_plugin_alconna import ( - on_alconna, Alconna, - Args, Subcommand, - CommandResult, Query, - At + on_alconna, + Alconna, + Args, + Subcommand, + CommandResult, + Query, + At, ) -from .permission import ( - User, SubAdmin -) +from .permission import User, SubAdmin from .utils.commands import ( - lookup_self, lookup_user, + lookup_self, + lookup_user, change_permission, - ban_user, unban_user, + ban_user, + unban_user, user_exists, - shorten + shorten, ) permission = on_alconna( Alconna( "权限", + Subcommand("查看", Args["target?", At | str], alias={"查询"}), Subcommand( - "查看", - Args["target?", At | str], - alias={"查询"} - ), - Subcommand( - "更改", - Args["target?", At | str]["permission_text?", str], - alias={"修改"} - ), - Subcommand( - "封禁", - Args["target?", At | str], - alias={"禁用"} + "更改", Args["target?", At | str]["permission_text?", str], alias={"修改"} ), - Subcommand( - "解封", - Args["target?", At | str], - alias={"解禁"} - ) + Subcommand("封禁", Args["target?", At | str], alias={"禁用"}), + Subcommand("解封", Args["target?", At | str], alias={"解禁"}), ), skip_for_unmatch=False, auto_send_output=False, - permission=User() + permission=User(), ) lookup = permission.dispatch("查看", block=True, permission=User()) change = permission.dispatch("更改", block=True, permission=SubAdmin()) @@ -67,6 +57,7 @@ async def _(cmd: CommandResult, event: MessageEvent): await lookup_self(event) ).finish() + @lookup.assign("~") async def _(event: MessageEvent, target: Query[At | str] = Query("~target")): if target.available: @@ -79,6 +70,7 @@ async def _(event: MessageEvent, target: Query[At | str] = Query("~target")): await lookup_self(event, access=False) ).finish() + @lookup.got_path("~target", prompt="请输入需要查看的对象:") async def _(target: At | str, event: MessageEvent): if isinstance(target, At): @@ -88,62 +80,66 @@ async def _(target: At | str, event: MessageEvent): await lookup_user(event, target) ).finish() + @change.assign("~") -async def _(target: Query[At | str] = Query("~target"), permission_text: Query[str] = Query("~permission_text")): +async def _( + target: Query[At | str] = Query("~target"), + permission_text: Query[str] = Query("~permission_text"), +): if target.available: change.set_path_arg("更改.target", target.result) if permission_text.available: change.set_path_arg("更改.permission_text", permission_text.result.strip()) + @change.got_path("~target", prompt="请输入需要更改的对象:") async def _(target: At | str, state: T_State, event: MessageEvent): if isinstance(target, At): target = target.target if ( # 检查用户是否存在 - msg := await user_exists(event, user_id=target) + msg := await user_exists(event, user_id=target) ) is not None: await msg.finish() state.update(UIF_target_id=target) + @change.got_path("~permission_text", prompt="请输入需要更改的权限:") async def _(permission_text: str, event: MessageEvent, state: T_State): await ( # 更改用户权限并返回 await change_permission( - event, - target_id=state["UIF_target_id"], - target_permission=permission_text + event, target_id=state["UIF_target_id"], target_permission=permission_text ) ).finish() + @ban.assign("~") async def _(target: Query[At | str] = Query("~target")): if target.available: ban.set_path_arg("封禁.target", target.result) + @ban.got_path("~target", prompt="请输入需要封禁的对象:") async def _(target: At | str, event: MessageEvent): if isinstance(target, At): target = target.target await ( # 封禁用户并返回 - await ban_user( - event, target_id=target - ) + await ban_user(event, target_id=target) ).finish() + @unban.assign("~") async def _(target: Query[At | str] = Query("~target")): if target.available: unban.set_path_arg("解封.target", target.result) + @unban.got_path("~target", prompt="请输入需要解封的对象:") async def _(target: At | str, event: MessageEvent): if isinstance(target, At): target = target.target await ( # 封禁用户并返回 - await unban_user( - event, target_id=target - ) + await unban_user(event, target_id=target) ).finish() diff --git a/src/libs/userinfos/src/UserInfos/models/permission.py b/src/libs/userinfos/src/UserInfos/models/permission.py index 1e8f875..98c2bc4 100644 --- a/src/libs/userinfos/src/UserInfos/models/permission.py +++ b/src/libs/userinfos/src/UserInfos/models/permission.py @@ -3,6 +3,7 @@ class PermissionLevel(IntEnum): """定义用户权限等级""" + BANNED_USER = auto() USER = auto() SUB_ADMIN = auto() diff --git a/src/libs/userinfos/src/UserInfos/permission.py b/src/libs/userinfos/src/UserInfos/permission.py index 6d5fc7e..b567739 100644 --- a/src/libs/userinfos/src/UserInfos/permission.py +++ b/src/libs/userinfos/src/UserInfos/permission.py @@ -5,16 +5,10 @@ from nonebot.matcher import Matcher from nonebot.log import logger from nonebot.exception import IgnoredException -from nonebot.adapters.onebot.v11 import ( - MessageEvent, GroupMessageEvent, - Bot -) -from nonebot_plugin_alconna import ( - UniMessage, - At, Text -) - -from .db import get_database +from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent, Bot +from nonebot_plugin_alconna import UniMessage, At, Text + +from .db import DATABASE from .models.permission import PermissionLevel from .utils import Cooldown @@ -36,17 +30,18 @@ def cooldown(self) -> Cooldown: raise NotImplementedError @staticmethod - async def get_user_permission(user_id: str) -> PermissionLevel: """获取该用户当前权限""" - db = get_database() # 假设用户不存在, 则进行用户注册(此处已知权限已同步至缓存) - if not db.cache.user_exists(user_id): - await db.register_user(user_id, permission=PermissionLevel.USER) + if not DATABASE.cache.user_exists(user_id): + await DATABASE.register_user(user_id, permission=PermissionLevel.USER) - return PermissionLevel.SUPERUSER if ( # 检查用户是否为超级用户 - user_id in SuperUserList - ) else db.cache.get_permission(user_id) # 若不为超级用户, 则从缓存中获取用户权限 + return ( + PermissionLevel.SUPERUSER + # 检查用户是否为超级用户 + if (user_id in SuperUserList) + else DATABASE.cache.get_permission(user_id) + ) # 若不为超级用户, 则从缓存中获取用户权限 @staticmethod @abstractmethod @@ -73,13 +68,11 @@ def cooldown(self) -> Cooldown: @staticmethod async def check(user_id: str) -> bool: - return ( - await User.get_user_permission(user_id) - ) in [ + return (await User.get_user_permission(user_id)) in [ PermissionLevel.USER, PermissionLevel.SUB_ADMIN, PermissionLevel.ADMIN, - PermissionLevel.SUPERUSER + PermissionLevel.SUPERUSER, ] @@ -97,12 +90,10 @@ def cooldown(self) -> Cooldown: @staticmethod async def check(user_id: str) -> bool: - return ( - await SubAdmin.get_user_permission(user_id) - ) in [ + return (await SubAdmin.get_user_permission(user_id)) in [ PermissionLevel.SUB_ADMIN, PermissionLevel.ADMIN, - PermissionLevel.SUPERUSER + PermissionLevel.SUPERUSER, ] @@ -120,12 +111,10 @@ def cooldown(self) -> Cooldown: @staticmethod async def check(user_id: str) -> bool: - return ( - await Admin.get_user_permission(user_id) - ) in [ + return (await Admin.get_user_permission(user_id)) in [ PermissionLevel.SUB_ADMIN, PermissionLevel.ADMIN, - PermissionLevel.SUPERUSER + PermissionLevel.SUPERUSER, ] @@ -153,38 +142,35 @@ async def _(event: MessageEvent, matcher: Matcher, bot: Bot): for checker in matcher.permission.checkers: if isinstance(permission := checker.call, Permission): user_id = event.get_user_id() - if not ( # 检查是否不满足所需权限,满足则忽略 - await permission.check(user_id) + if ( # 检查是否不满足所需权限,满足则忽略 + await permission.check(user_id) ): - # 如果在冷却时间内,则进行提示 - if result := permission.cooldown.check(user_id): - # 对群聊事件的回复作特殊处理 - msgs = [] - if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=user_id), - Text("\n") - ] - msgs.append( - Text("你没有该功能的使用权限。") - ) - await UniMessage(msgs).send( - target=event, - bot=bot - ) - logger.debug( - f"用户(ID:{user_id})不满足命令所需权限(value: {permission.need_permission()})," - f"已{'提示并忽略请求' if result else '忽略请求'}" - ) - raise IgnoredException(f"用户(ID:{user_id})不满足命令所需权限(value: {permission.need_permission()})") - else: - logger.warning(f"用户(ID:{user_id})频繁使用无权限命令,忽略请求") - raise IgnoredException(f"用户(ID:{user_id})频繁使用无权限命令") - else: - logger.debug(f"用户(ID:{user_id})满足命令所需权限(value: {permission.need_permission()})") + logger.debug( + f"用户(ID:{user_id})满足命令所需权限(value={permission.need_permission()})" + ) break - -__all__ = [ - "User", "SubAdmin", "Admin", "SuperUser" -] + if not ( # 如果在冷却时间内,则进行提示 + result := permission.cooldown.check(user_id) + ): + logger.warning( + f"忽略用户(ID:{user_id})的命令请求(reason=无权限并频繁触发命令)" + ) + raise IgnoredException(f"用户(ID:{user_id})无权限并频繁触发命令") + + # 对群聊事件的回复作特殊处理 + msgs = [] + if isinstance(event, GroupMessageEvent): + msgs += [At(flag="user", target=user_id), Text("\n")] + msgs.append(Text("你没有该功能的使用权限。")) + await UniMessage(msgs).send(target=event, bot=bot) + logger.debug( + f"用户(ID:{user_id})不满足命令所需权限(value={permission.need_permission()})," + f"已{'提示并忽略请求' if result else '忽略请求'}" + ) + raise IgnoredException( + f"用户(ID:{user_id})不满足命令所需权限(value={permission.need_permission()})" + ) + + +__all__ = ["User", "SubAdmin", "Admin", "SuperUser"] diff --git a/src/libs/userinfos/src/UserInfos/utils/__init__.py b/src/libs/userinfos/src/UserInfos/utils/__init__.py index 77be4c8..df5b825 100644 --- a/src/libs/userinfos/src/UserInfos/utils/__init__.py +++ b/src/libs/userinfos/src/UserInfos/utils/__init__.py @@ -1,3 +1,10 @@ from .json import AsyncJson from .cache import PermissionCache from .cooldown import Cooldown + + +__all__ = [ + "AsyncJson", + "PermissionCache", + "Cooldown", +] diff --git a/src/libs/userinfos/src/UserInfos/utils/cache.py b/src/libs/userinfos/src/UserInfos/utils/cache.py index 7000c55..95578d7 100644 --- a/src/libs/userinfos/src/UserInfos/utils/cache.py +++ b/src/libs/userinfos/src/UserInfos/utils/cache.py @@ -10,16 +10,17 @@ def __init__(self): def update_all(self, data: dict[str, User]): """更新所有用户至缓存""" - self._data.update({ - user_id: PermissionLevel(uinfo["permission"]) for user_id, uinfo in data.items() - }) + self._data.update( + { + user_id: PermissionLevel(uinfo["permission"]) + for user_id, uinfo in data.items() + } + ) logger.info(f"更新 {len(data)} 位用户的权限缓存") def update_one(self, user_id: str, permission: PermissionLevel): """更新单个用户至缓存""" - self._data.update({ - user_id: permission - }) + self._data.update({user_id: permission}) logger.info(f"更新用户(ID:{user_id})的权限缓存(value: {permission.name})") def delete_one(self, user_id: str): diff --git a/src/libs/userinfos/src/UserInfos/utils/commands/__init__.py b/src/libs/userinfos/src/UserInfos/utils/commands/__init__.py index 48783a3..469a1a7 100644 --- a/src/libs/userinfos/src/UserInfos/utils/commands/__init__.py +++ b/src/libs/userinfos/src/UserInfos/utils/commands/__init__.py @@ -1,5 +1,16 @@ -from .lookup import * -from .error_shorten import * -from .change import * -from .unban_user import * -from .ban_user import * +from .lookup import lookup_self, lookup_user +from .error_shorten import shorten +from .change import change_permission, user_exists +from .unban_user import unban_user +from .ban_user import ban_user + + +__all__ = [ + "shorten", + "lookup_user", + "lookup_self", + "change_permission", + "user_exists", + "unban_user", + "ban_user", +] diff --git a/src/libs/userinfos/src/UserInfos/utils/commands/ban_user.py b/src/libs/userinfos/src/UserInfos/utils/commands/ban_user.py index 5389dbe..e2419e4 100644 --- a/src/libs/userinfos/src/UserInfos/utils/commands/ban_user.py +++ b/src/libs/userinfos/src/UserInfos/utils/commands/ban_user.py @@ -1,10 +1,7 @@ from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent -from nonebot_plugin_alconna import ( - UniMessage, - At, Text -) +from nonebot_plugin_alconna import UniMessage, At, Text -from UserInfos.db import get_database +from UserInfos.db import DATABASE from UserInfos.models.permission import PermissionLevel @@ -14,59 +11,43 @@ async def ban_user(event: MessageEvent, target_id: str) -> UniMessage: msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=user_id), - Text("\n") - ] + msgs += [At(flag="user", target=user_id), Text("\n")] # 防止操作者封禁自身 if user_id == target_id: - msgs.append( - Text("封禁失败,无法封禁自己。") - ) + msgs.append(Text("封禁失败,无法封禁自己。")) return UniMessage(msgs) # 获取操作者与作用对象的权限 - db = get_database() try: - self_uinfo = await db.get_user(user_id=user_id) + self_uinfo = await DATABASE.get_user(user_id=user_id) except ValueError: - msgs.append( - Text("封禁失败,操作者权限数据异常。") - ) + msgs.append(Text("封禁失败,操作者权限数据异常。")) return UniMessage(msgs) try: - target_uinfo = await db.get_user(user_id=target_id) + target_uinfo = await DATABASE.get_user(user_id=target_id) except ValueError: - msgs.append( - Text("封禁失败,该用户可能不存在。") - ) + msgs.append(Text("封禁失败,该用户可能不存在。")) return UniMessage(msgs) # 判断操作者与作用对象权限关系 if self_uinfo.permission == target_uinfo.permission: - msgs.append( - Text("封禁失败,该用户与操作者权限相当。") - ) + msgs.append(Text("封禁失败,该用户与操作者权限相当。")) return UniMessage(msgs) elif self_uinfo.permission < target_uinfo.permission: - msgs.append( - Text("封禁失败,该用户权限较高。") - ) + msgs.append(Text("封禁失败,该用户权限较高。")) return UniMessage(msgs) # 操作者权限高于作用对象 # 防止用户已经被封禁 if target_uinfo.permission == PermissionLevel.BANNED_USER: - msgs.append( - Text("封禁失败,该用户已经被封禁。") - ) + msgs.append(Text("封禁失败,该用户已经被封禁。")) return UniMessage(msgs) # 更改用户权限 - await db.update_user(user_id=target_id, permission=PermissionLevel.BANNED_USER) - msgs.append( - Text(f"封禁成功,已将该用户永久封禁。") + await DATABASE.update_user( + user_id=target_id, permission=PermissionLevel.BANNED_USER ) + msgs.append(Text("封禁成功,已将该用户永久封禁。")) return UniMessage(msgs) diff --git a/src/libs/userinfos/src/UserInfos/utils/commands/change.py b/src/libs/userinfos/src/UserInfos/utils/commands/change.py index 27f5f8d..c989aca 100644 --- a/src/libs/userinfos/src/UserInfos/utils/commands/change.py +++ b/src/libs/userinfos/src/UserInfos/utils/commands/change.py @@ -1,12 +1,9 @@ from typing import Annotated, Optional, Literal from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent -from nonebot_plugin_alconna import ( - UniMessage, - At, Text -) +from nonebot_plugin_alconna import UniMessage, At, Text -from UserInfos.db import get_database +from UserInfos.db import DATABASE from UserInfos.models.permission import PermissionLevel @@ -15,23 +12,20 @@ async def user_exists(event: MessageEvent, user_id: str) -> Optional[UniMessage] msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=event.get_user_id()), - Text("\n") - ] - - # 查看用户是否存在 - db = get_database() - if (await db.user_exists(user_id)) is False: - msgs.append( - Text("更改失败,该用户可能不存在。") - ) + msgs += [At(flag="user", target=event.get_user_id()), Text("\n")] + + if not ( # 查看用户是否存在 + await DATABASE.user_exists(user_id) + ): + msgs.append(Text("更改失败,该用户可能不存在。")) return UniMessage(msgs) return None -def permission_convert(text: str) -> Literal["机器人管理员", "机器人子管理员", "普通用户"]: +def permission_convert( + text: str, +) -> Literal["机器人管理员", "机器人子管理员", "普通用户"]: """权限文本转换""" match text: case "管理员" | "Bot管理员" | "机器人管理员": @@ -45,9 +39,9 @@ def permission_convert(text: str) -> Literal["机器人管理员", "机器人子 async def change_permission( - event: MessageEvent, - target_id: str, - target_permission: Annotated[str, "仅支持‘permission_convert’中可转换的权限"] + event: MessageEvent, + target_id: str, + target_permission: Annotated[str, "仅支持‘permission_convert’中可转换的权限"], ) -> UniMessage: """ 修改用户权限 @@ -59,44 +53,30 @@ async def change_permission( msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=user_id), - Text("\n") - ] + msgs += [At(flag="user", target=user_id), Text("\n")] # 防止操作者更改自身权限 if user_id == target_id: - msgs.append( - Text("更改失败,无法更改自身权限。") - ) + msgs.append(Text("更改失败,无法更改自身权限。")) return UniMessage(msgs) # 获取操作者与作用对象的权限 - db = get_database() try: - self_uinfo = await db.get_user(user_id=user_id) + self_uinfo = await DATABASE.get_user(user_id=user_id) except ValueError: - msgs.append( - Text("更改失败,操作者权限数据异常。") - ) + msgs.append(Text("更改失败,操作者权限数据异常。")) return UniMessage(msgs) try: - target_uinfo = await db.get_user(user_id=target_id) + target_uinfo = await DATABASE.get_user(user_id=target_id) except ValueError: - msgs.append( - Text("更改失败,该用户可能不存在。") - ) + msgs.append(Text("更改失败,该用户可能不存在。")) return UniMessage(msgs) # 判断操作者与作用对象权限关系 if self_uinfo.permission == target_uinfo.permission: - msgs.append( - Text("更改失败,该用户与操作者权限相当。") - ) + msgs.append(Text("更改失败,该用户与操作者权限相当。")) return UniMessage(msgs) elif self_uinfo.permission < target_uinfo.permission: - msgs.append( - Text("更改失败,该用户权限较高。") - ) + msgs.append(Text("更改失败,该用户权限较高。")) return UniMessage(msgs) # 操作者权限高于作用对象 @@ -104,9 +84,7 @@ async def change_permission( try: target_permission = permission_convert(target_permission) except ValueError: - msgs.append( - Text("更改失败,权限名称可能错误。") - ) + msgs.append(Text("更改失败,权限名称可能错误。")) return UniMessage(msgs) match target_permission: @@ -122,17 +100,13 @@ async def change_permission( # 防止用户权限等级已经为对应等级 # noinspection PyUnboundLocalVariable if permission_value == target_uinfo.permission: - msgs.append( - Text(f"更改失败,该用户权限已为:{target_permission}。") - ) + msgs.append(Text(f"更改失败,该用户权限已为:{target_permission}。")) return UniMessage(msgs) # 更改用户权限 # noinspection PyUnboundLocalVariable - await db.update_user(user_id=target_id, permission=permission_value) - msgs.append( - Text(f"更改成功,将用户权限设置为: {target_permission}。") - ) + await DATABASE.update_user(user_id=target_id, permission=permission_value) + msgs.append(Text(f"更改成功,将用户权限设置为: {target_permission}。")) return UniMessage(msgs) diff --git a/src/libs/userinfos/src/UserInfos/utils/commands/error_shorten.py b/src/libs/userinfos/src/UserInfos/utils/commands/error_shorten.py index 9d90976..ec41219 100644 --- a/src/libs/userinfos/src/UserInfos/utils/commands/error_shorten.py +++ b/src/libs/userinfos/src/UserInfos/utils/commands/error_shorten.py @@ -9,7 +9,7 @@ def shorten(cmd: CommandResult): truncated = textwrap.shorten( text=error_arg, width=5, - placeholder=f"{error_arg[0:2] if len(error_arg) else ''}..." + placeholder=f"{error_arg[0:2] if len(error_arg) else ''}...", ) return UniMessage(f"参数 {truncated} 匹配失败。") diff --git a/src/libs/userinfos/src/UserInfos/utils/commands/lookup.py b/src/libs/userinfos/src/UserInfos/utils/commands/lookup.py index 1aae142..3f59e27 100644 --- a/src/libs/userinfos/src/UserInfos/utils/commands/lookup.py +++ b/src/libs/userinfos/src/UserInfos/utils/commands/lookup.py @@ -1,11 +1,9 @@ from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent + # Alconna -from nonebot_plugin_alconna import ( - UniMessage, - At, Text -) +from nonebot_plugin_alconna import UniMessage, At, Text -from UserInfos.db import get_database +from UserInfos.db import DATABASE from UserInfos.models.permission import PermissionLevel @@ -16,14 +14,10 @@ async def lookup_self(event: MessageEvent, access: bool = True) -> UniMessage: msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=user_id), - Text("\n") - ] + msgs += [At(flag="user", target=user_id), Text("\n")] # 检查用户当前权限 - db = get_database() - uinfo = await db.get_user(str(event.user_id)) + uinfo = await DATABASE.get_user(str(event.user_id)) match uinfo.permission: case PermissionLevel.SUPERUSER: perm_text = "超级用户" @@ -49,34 +43,26 @@ async def lookup_self(event: MessageEvent, access: bool = True) -> UniMessage: # 生成权限提示消息 msgs.append( - Text( - f"你当前的权限为:{perm_text}" - ) if access else Text( - f"当前权限不足,仅允许查看自身权限\n" - f"你当前的权限为:{perm_text}" - ) + Text(f"你当前的权限为:{perm_text}") + if access + else Text(f"当前权限不足,仅允许查看自身权限\n你当前的权限为:{perm_text}") ) return UniMessage(msgs) + async def lookup_user(event: MessageEvent, target_id: str) -> UniMessage: """查看该用户的权限""" msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=event.get_user_id()), - Text("\n") - ] + msgs += [At(flag="user", target=event.get_user_id()), Text("\n")] # 检查该用户权限 - db = get_database() try: - uinfo = await db.get_user(target_id) + uinfo = await DATABASE.get_user(target_id) except ValueError: # 查询失败 - msgs.append( - Text("查询失败,该用户可能不存在。") - ) + msgs.append(Text("查询失败,该用户可能不存在。")) return UniMessage(msgs) else: match uinfo.permission: @@ -95,12 +81,8 @@ async def lookup_user(event: MessageEvent, target_id: str) -> UniMessage: raise ValueError(f"出现了异常的值: {uinfo.permission}") # 构建消息并返回 # noinspection PyUnboundLocalVariable - msgs.append( - f"该用户权限为:{perm_text}" - ) + msgs.append(f"该用户权限为:{perm_text}") return UniMessage(msgs) -__all__ = [ - "lookup_self", "lookup_user" -] +__all__ = ["lookup_self", "lookup_user"] diff --git a/src/libs/userinfos/src/UserInfos/utils/commands/unban_user.py b/src/libs/userinfos/src/UserInfos/utils/commands/unban_user.py index 4f2a115..eaf3fa5 100644 --- a/src/libs/userinfos/src/UserInfos/utils/commands/unban_user.py +++ b/src/libs/userinfos/src/UserInfos/utils/commands/unban_user.py @@ -1,10 +1,7 @@ from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent -from nonebot_plugin_alconna import ( - UniMessage, - At, Text -) +from nonebot_plugin_alconna import UniMessage, At, Text -from UserInfos.db import get_database +from UserInfos.db import DATABASE from UserInfos.models.permission import PermissionLevel @@ -14,59 +11,41 @@ async def unban_user(event: MessageEvent, target_id: str) -> UniMessage: msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=user_id), - Text("\n") - ] + msgs += [At(flag="user", target=user_id), Text("\n")] # 防止操作者解封自身 if user_id == target_id: - msgs.append( - Text("解封失败,无法解封自己。") - ) + msgs.append(Text("解封失败,无法解封自己。")) return UniMessage(msgs) # 获取操作者与作用对象的权限 - db = get_database() try: - self_uinfo = await db.get_user(user_id=user_id) + self_uinfo = await DATABASE.get_user(user_id=user_id) except ValueError: - msgs.append( - Text("解封失败,操作者权限数据异常。") - ) + msgs.append(Text("解封失败,操作者权限数据异常。")) return UniMessage(msgs) try: - target_uinfo = await db.get_user(user_id=target_id) + target_uinfo = await DATABASE.get_user(user_id=target_id) except ValueError: - msgs.append( - Text("解封失败,该用户可能不存在。") - ) + msgs.append(Text("解封失败,该用户可能不存在。")) return UniMessage(msgs) # 判断操作者与作用对象权限关系 if self_uinfo.permission == target_uinfo.permission: - msgs.append( - Text("解封失败,该用户与操作者权限相当。") - ) + msgs.append(Text("解封失败,该用户与操作者权限相当。")) return UniMessage(msgs) elif self_uinfo.permission < target_uinfo.permission: - msgs.append( - Text("解封失败,该用户权限较高。") - ) + msgs.append(Text("解封失败,该用户权限较高。")) return UniMessage(msgs) # 操作者权限高于作用对象 # 防止用户已经被解封 if target_uinfo.permission != PermissionLevel.BANNED_USER: - msgs.append( - Text("解封失败,该用户当前未被封禁。") - ) + msgs.append(Text("解封失败,该用户当前未被封禁。")) return UniMessage(msgs) # 更改用户权限 - await db.update_user(user_id=target_id, permission=PermissionLevel.USER) - msgs.append( - Text(f"解封成功,该用户已解除封禁状态。") - ) + await DATABASE.update_user(user_id=target_id, permission=PermissionLevel.USER) + msgs.append(Text("解封成功,该用户已解除封禁状态。")) return UniMessage(msgs) diff --git a/src/libs/userinfos/src/UserInfos/utils/cooldown.py b/src/libs/userinfos/src/UserInfos/utils/cooldown.py index caeafe1..522ce2b 100644 --- a/src/libs/userinfos/src/UserInfos/utils/cooldown.py +++ b/src/libs/userinfos/src/UserInfos/utils/cooldown.py @@ -1,7 +1,9 @@ from time import time + class Cooldown: """冷却时间""" + def __init__(self, cooldown: float): self._data: dict[str, float] = {} self._cooldown = cooldown diff --git a/src/libs/userinfos/src/UserInfos/utils/json.py b/src/libs/userinfos/src/UserInfos/utils/json.py index 46b9226..5cf288c 100644 --- a/src/libs/userinfos/src/UserInfos/utils/json.py +++ b/src/libs/userinfos/src/UserInfos/utils/json.py @@ -13,7 +13,9 @@ class AsyncJson: def __init__(self, path: Path, default_data: Optional[Jsonable] = None): self._file_path = path - self._default_data = dict() if default_data is None else default_data # 如果未指定,则默认写入空字典 + self._default_data = ( + dict() if default_data is None else default_data + ) # 如果未指定,则默认写入空字典 self._lock = asyncio.Lock() self._auto_create_file(default_data) @@ -44,17 +46,13 @@ async def read(self) -> Jsonable: """读取 JSON 数据""" async with self._lock: async with aiofiles.open(self._file_path, mode="rb") as f: - return json.loads( - await f.read() - ) + return json.loads(await f.read()) async def write(self, data: Jsonable): """写入 JSON 数据""" async with self._lock: async with aiofiles.open(self._file_path, mode="wb") as f: - await f.write( - json.dumps(data) - ) + await f.write(json.dumps(data)) async def clear(self, default_data: Optional[Jsonable] = None): """清除 JSON 文件数据并重新写入指定数据""" diff --git a/src/libs/userinfos/tests/__init__.py b/src/libs/userinfos/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/libs/userinfos/tests/bot.py b/src/libs/userinfos/tests/bot.py deleted file mode 100644 index ddcbbab..0000000 --- a/src/libs/userinfos/tests/bot.py +++ /dev/null @@ -1,12 +0,0 @@ -import nonebot -from nonebot.adapters.onebot.v11 import Adapter - - -nonebot.init(_env_file=".env.dev") -driver = nonebot.get_driver() -driver.register_adapter(Adapter) -nonebot.load_plugin("UserInfos") - -# 运行 Bot -if __name__ == "__main__": - nonebot.run() From c49867abe51bce4c31742d539241d69162e3486e Mon Sep 17 00:00:00 2001 From: KanbeReina Date: Fri, 13 Feb 2026 18:36:00 +0800 Subject: [PATCH 2/7] =?UTF-8?q?feat=EF=BC=9A=E5=A2=9E=E5=8A=A0=20`ruff`=20?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{release-pkg-build.yml => auto-release.yml} | 4 ++-- .github/workflows/ruff.yml | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) rename .github/workflows/{release-pkg-build.yml => auto-release.yml} (96%) create mode 100644 .github/workflows/ruff.yml diff --git a/.github/workflows/release-pkg-build.yml b/.github/workflows/auto-release.yml similarity index 96% rename from .github/workflows/release-pkg-build.yml rename to .github/workflows/auto-release.yml index 03e1dd7..2893da5 100644 --- a/.github/workflows/release-pkg-build.yml +++ b/.github/workflows/auto-release.yml @@ -30,7 +30,7 @@ jobs: id: version run: | echo "VERSION=$(uv version --short)" >> $GITHUB_ENV - + - name: Publish package to GitHub uses: softprops/action-gh-release@v2 with: @@ -42,4 +42,4 @@ jobs: generate_release_notes: true files: | ./dist/*.tar.gz - ./dist/*.whl + ./dist/*.whl \ No newline at end of file diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 0000000..bad0ffc --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,17 @@ +name: Ruff Lint + +on: + push: + branches: + - master + pull_request: + +jobs: + ruff: + name: Ruff Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Run Ruff Lint + uses: chartboost/ruff-action@v1 \ No newline at end of file From 71667afa655ebc8ab26ce513dc6541b2d31c51be Mon Sep 17 00:00:00 2001 From: KanbeReina Date: Fri, 13 Feb 2026 21:03:11 +0800 Subject: [PATCH 3/7] =?UTF-8?q?feat=EF=BC=9A=E7=A7=BB=E9=99=A4=20`nonebot?= =?UTF-8?q?=5Fplugin=5Flitestore`=20=E4=BE=9D=E8=B5=96=EF=BC=8C=E7=94=A8?= =?UTF-8?q?=20`nonebot-plugin-localstore`=20=E4=BB=A3=E6=9B=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 4 +- src/AudioMaster/__init__.py | 2 +- src/AudioMaster/utils/ffmpeg/main.py | 7 ++- uv.lock | 66 +++++++++++++++++++++++----- 4 files changed, 65 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ed0118a..70f80d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "AudioMaster" -version = "3.1.2" +version = "3.2.0" description = "一个基于 NoneBot2 的音频转换插件" authors = [ {name = "KanbeReina", email = "kano.2525@qq.com"}, @@ -11,6 +11,7 @@ dependencies = [ "nonebot-adapter-onebot>=2.4.6", "nonebot-plugin-alconna>=0.59.4", "nonebot-plugin-apscheduler>=0.5.0", + "nonebot-plugin-localstore>=0.7.4", "nonebot2[fastapi]>=2.4.3", "pathvalidate>=3.3.1", "pycryptodome>=3.23.0", @@ -30,5 +31,6 @@ build-backend = "hatchling.build" dev = [ "pytest>=9.0.2", "pytest-asyncio>=1.3.0", + "ruff>=0.15.1", "UserInfos @ file:///${PROJECT_ROOT}/src/libs/userinfos", ] diff --git a/src/AudioMaster/__init__.py b/src/AudioMaster/__init__.py index dcbde92..8f66624 100644 --- a/src/AudioMaster/__init__.py +++ b/src/AudioMaster/__init__.py @@ -2,7 +2,7 @@ require("nonebot_plugin_apscheduler") require("nonebot_plugin_alconna") -require("nonebot_plugin_litestore") +require("nonebot-plugin-localstore") require("UserInfos") from .main import * diff --git a/src/AudioMaster/utils/ffmpeg/main.py b/src/AudioMaster/utils/ffmpeg/main.py index 061661a..6aab156 100644 --- a/src/AudioMaster/utils/ffmpeg/main.py +++ b/src/AudioMaster/utils/ffmpeg/main.py @@ -3,10 +3,10 @@ from pathlib import Path from typing import Literal +import nonebot_plugin_localstore as store from nonebot import get_driver from nonebot.log import logger from nonebot.utils import run_sync -from nonebot_plugin_litestore import PluginStore as Store from pathvalidate import sanitize_filename from pydub import AudioSegment from pydub.utils import which @@ -38,7 +38,10 @@ def convert( io.BytesIO(raw_file) if isinstance(raw_file, bytes) else raw_file ) audio_format = "flac" if audio_code in (30250, 30251) else "mp3" - file_path = Store.cache.get_file(f"{AudioHandler._safe_rename(file_name)}.{audio_format}") + file_path = store.get_cache_file( + plugin_name="AudioMaster", + filename=f"{AudioHandler._safe_rename(file_name)}.{audio_format}" + ) logger.debug(f"当前音频峰值 dBFS:{(max_dBFS := audio.max_dBFS)}") if max_dBFS < -0.1: diff --git a/uv.lock b/uv.lock index c847f57..1829542 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = "==3.12.*" [[package]] @@ -75,7 +75,7 @@ wheels = [ [[package]] name = "audiomaster" -version = "3.1.0" +version = "3.2.0" source = { editable = "." } dependencies = [ { name = "httpx", extra = ["socks"] }, @@ -83,6 +83,7 @@ dependencies = [ { name = "nonebot-adapter-onebot" }, { name = "nonebot-plugin-alconna" }, { name = "nonebot-plugin-apscheduler" }, + { name = "nonebot-plugin-localstore" }, { name = "nonebot2", extra = ["fastapi"] }, { name = "pathvalidate" }, { name = "pycryptodome" }, @@ -95,6 +96,7 @@ dependencies = [ dev = [ { name = "pytest" }, { name = "pytest-asyncio" }, + { name = "ruff" }, { name = "userinfos" }, ] @@ -105,6 +107,7 @@ requires-dist = [ { name = "nonebot-adapter-onebot", specifier = ">=2.4.6" }, { name = "nonebot-plugin-alconna", specifier = ">=0.59.4" }, { name = "nonebot-plugin-apscheduler", specifier = ">=0.5.0" }, + { name = "nonebot-plugin-localstore", specifier = ">=0.7.4" }, { name = "nonebot2", extras = ["fastapi"], specifier = ">=2.4.3" }, { name = "pathvalidate", specifier = ">=3.3.1" }, { name = "pycryptodome", specifier = ">=3.23.0" }, @@ -117,6 +120,7 @@ requires-dist = [ dev = [ { name = "pytest", specifier = ">=9.0.2" }, { name = "pytest-asyncio", specifier = ">=1.3.0" }, + { name = "ruff", specifier = ">=0.15.1" }, { name = "userinfos", directory = "src/libs/userinfos" }, ] @@ -391,16 +395,18 @@ wheels = [ ] [[package]] -name = "nonebot-plugin-litestore" -version = "0.1.2" +name = "nonebot-plugin-localstore" +version = "0.7.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nonebot2", extra = ["fastapi"] }, + { name = "nonebot2" }, + { name = "nonestorage" }, + { name = "pydantic" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b0/9d/a9d9328cf8b2a7ff3bb7b54882dd63ab73e23f8dba6c373a3cac339cd037/nonebot_plugin_litestore-0.1.2.tar.gz", hash = "sha256:c542b7dfa35209645f82d2174efba345426d4e0c776ace339e15e718d5521ae4", size = 4878, upload-time = "2025-07-03T08:37:40.159Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/22/67ec1e604c0385729bf2d71db406c032de9015363588785617113dfb76ac/nonebot_plugin_localstore-0.7.4.tar.gz", hash = "sha256:85ddc13814bfcd484ab311306823651390020bf44f4fb4733b343a58e72723ce", size = 5535, upload-time = "2025-03-01T12:54:04.077Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/fc/43b4ba9be8630b4dd1493f316dc0cfd37b5f3ace2ca5c4577143ec7ec87b/nonebot_plugin_litestore-0.1.2-py3-none-any.whl", hash = "sha256:a2b9eb4031dcce25439a69b283b1e955999f719c229ba014ff82bc9c61a2b5e2", size = 6490, upload-time = "2025-07-03T08:37:39.134Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c8/0fcb2c6d994579e53342cbf11bf9326ddfab010d1a8063e1fc0cc19c13bf/nonebot_plugin_localstore-0.7.4-py3-none-any.whl", hash = "sha256:3b08030878eadcdd8b9ce3d079da0dc2d0e41dc91f0b2d8cf7fa862a27de9090", size = 6963, upload-time = "2025-03-01T12:54:02.137Z" }, ] [[package]] @@ -440,6 +446,15 @@ fastapi = [ { name = "uvicorn", extra = ["standard"] }, ] +[[package]] +name = "nonestorage" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/58/04676895dc6aa4fa6b6bfb069692d1469bfeb175bf6c25ea071762d2b006/nonestorage-0.1.0.tar.gz", hash = "sha256:818232236455c79cabbb69e716f73aa1b9c21d579f1c1fcbdba273b60bac72d9", size = 19257, upload-time = "2024-12-21T08:35:18.1Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/54/7c796d764ef0c53d94862b1cfe55fcc383a0ba7c2764c0ce40307438a052/nonestorage-0.1.0-py3-none-any.whl", hash = "sha256:35811adf67c680c272bcb71fa9d6c3613cc2d1bb79f5bfc7d83c4412a79537cb", size = 4127, upload-time = "2024-12-21T08:35:15.732Z" }, +] + [[package]] name = "orjson" version = "3.11.3" @@ -693,6 +708,31 @@ pil = [ { name = "pillow" }, ] +[[package]] +name = "ruff" +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/dc/4e6ac71b511b141cf626357a3946679abeba4cf67bc7cc5a17920f31e10d/ruff-0.15.1.tar.gz", hash = "sha256:c590fe13fb57c97141ae975c03a1aedb3d3156030cabd740d6ff0b0d601e203f", size = 4540855, upload-time = "2026-02-12T23:09:09.998Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/bf/e6e4324238c17f9d9120a9d60aa99a7daaa21204c07fcd84e2ef03bb5fd1/ruff-0.15.1-py3-none-linux_armv6l.whl", hash = "sha256:b101ed7cf4615bda6ffe65bdb59f964e9f4a0d3f85cbf0e54f0ab76d7b90228a", size = 10367819, upload-time = "2026-02-12T23:09:03.598Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ea/c8f89d32e7912269d38c58f3649e453ac32c528f93bb7f4219258be2e7ed/ruff-0.15.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:939c995e9277e63ea632cc8d3fae17aa758526f49a9a850d2e7e758bfef46602", size = 10798618, upload-time = "2026-02-12T23:09:22.928Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0f/1d0d88bc862624247d82c20c10d4c0f6bb2f346559d8af281674cf327f15/ruff-0.15.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d83466455fdefe60b8d9c8df81d3c1bbb2115cede53549d3b522ce2bc703899", size = 10148518, upload-time = "2026-02-12T23:08:58.339Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c8/291c49cefaa4a9248e986256df2ade7add79388fe179e0691be06fae6f37/ruff-0.15.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9457e3c3291024866222b96108ab2d8265b477e5b1534c7ddb1810904858d16", size = 10518811, upload-time = "2026-02-12T23:09:31.865Z" }, + { url = "https://files.pythonhosted.org/packages/c3/1a/f5707440e5ae43ffa5365cac8bbb91e9665f4a883f560893829cf16a606b/ruff-0.15.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92c92b003e9d4f7fbd33b1867bb15a1b785b1735069108dfc23821ba045b29bc", size = 10196169, upload-time = "2026-02-12T23:09:17.306Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ff/26ddc8c4da04c8fd3ee65a89c9fb99eaa5c30394269d424461467be2271f/ruff-0.15.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fe5c41ab43e3a06778844c586251eb5a510f67125427625f9eb2b9526535779", size = 10990491, upload-time = "2026-02-12T23:09:25.503Z" }, + { url = "https://files.pythonhosted.org/packages/fc/00/50920cb385b89413f7cdb4bb9bc8fc59c1b0f30028d8bccc294189a54955/ruff-0.15.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66a6dd6df4d80dc382c6484f8ce1bcceb55c32e9f27a8b94c32f6c7331bf14fb", size = 11843280, upload-time = "2026-02-12T23:09:19.88Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6d/2f5cad8380caf5632a15460c323ae326f1e1a2b5b90a6ee7519017a017ca/ruff-0.15.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a4a42cbb8af0bda9bcd7606b064d7c0bc311a88d141d02f78920be6acb5aa83", size = 11274336, upload-time = "2026-02-12T23:09:14.907Z" }, + { url = "https://files.pythonhosted.org/packages/a3/1d/5f56cae1d6c40b8a318513599b35ea4b075d7dc1cd1d04449578c29d1d75/ruff-0.15.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab064052c31dddada35079901592dfba2e05f5b1e43af3954aafcbc1096a5b2", size = 11137288, upload-time = "2026-02-12T23:09:07.475Z" }, + { url = "https://files.pythonhosted.org/packages/cd/20/6f8d7d8f768c93b0382b33b9306b3b999918816da46537d5a61635514635/ruff-0.15.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5631c940fe9fe91f817a4c2ea4e81f47bee3ca4aa646134a24374f3c19ad9454", size = 11070681, upload-time = "2026-02-12T23:08:55.43Z" }, + { url = "https://files.pythonhosted.org/packages/9a/67/d640ac76069f64cdea59dba02af2e00b1fa30e2103c7f8d049c0cff4cafd/ruff-0.15.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68138a4ba184b4691ccdc39f7795c66b3c68160c586519e7e8444cf5a53e1b4c", size = 10486401, upload-time = "2026-02-12T23:09:27.927Z" }, + { url = "https://files.pythonhosted.org/packages/65/3d/e1429f64a3ff89297497916b88c32a5cc88eeca7e9c787072d0e7f1d3e1e/ruff-0.15.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:518f9af03bfc33c03bdb4cb63fabc935341bb7f54af500f92ac309ecfbba6330", size = 10197452, upload-time = "2026-02-12T23:09:12.147Z" }, + { url = "https://files.pythonhosted.org/packages/78/83/e2c3bade17dad63bf1e1c2ffaf11490603b760be149e1419b07049b36ef2/ruff-0.15.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:da79f4d6a826caaea95de0237a67e33b81e6ec2e25fc7e1993a4015dffca7c61", size = 10693900, upload-time = "2026-02-12T23:09:34.418Z" }, + { url = "https://files.pythonhosted.org/packages/a1/27/fdc0e11a813e6338e0706e8b39bb7a1d61ea5b36873b351acee7e524a72a/ruff-0.15.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3dd86dccb83cd7d4dcfac303ffc277e6048600dfc22e38158afa208e8bf94a1f", size = 11227302, upload-time = "2026-02-12T23:09:36.536Z" }, + { url = "https://files.pythonhosted.org/packages/f6/58/ac864a75067dcbd3b95be5ab4eb2b601d7fbc3d3d736a27e391a4f92a5c1/ruff-0.15.1-py3-none-win32.whl", hash = "sha256:660975d9cb49b5d5278b12b03bb9951d554543a90b74ed5d366b20e2c57c2098", size = 10462555, upload-time = "2026-02-12T23:09:29.899Z" }, + { url = "https://files.pythonhosted.org/packages/e0/5e/d4ccc8a27ecdb78116feac4935dfc39d1304536f4296168f91ed3ec00cd2/ruff-0.15.1-py3-none-win_amd64.whl", hash = "sha256:c820fef9dd5d4172a6570e5721704a96c6679b80cf7be41659ed439653f62336", size = 11599956, upload-time = "2026-02-12T23:09:01.157Z" }, + { url = "https://files.pythonhosted.org/packages/2a/07/5bda6a85b220c64c65686bc85bd0bbb23b29c62b3a9f9433fa55f17cda93/ruff-0.15.1-py3-none-win_arm64.whl", hash = "sha256:5ff7d5f0f88567850f45081fac8f4ec212be8d0b963e385c3f7d0d2eb4899416", size = 10874604, upload-time = "2026-02-12T23:09:05.515Z" }, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -795,13 +835,13 @@ wheels = [ [[package]] name = "userinfos" -version = "2.0.0" +version = "3.0.0" source = { directory = "src/libs/userinfos" } dependencies = [ { name = "aiofiles" }, { name = "nonebot-adapter-onebot" }, { name = "nonebot-plugin-alconna" }, - { name = "nonebot-plugin-litestore" }, + { name = "nonebot-plugin-localstore" }, { name = "nonebot2", extra = ["fastapi"] }, { name = "orjson" }, ] @@ -811,11 +851,17 @@ requires-dist = [ { name = "aiofiles", specifier = ">=24.1.0" }, { name = "nonebot-adapter-onebot", specifier = ">=2.4.6" }, { name = "nonebot-plugin-alconna", specifier = ">=0.57.4" }, - { name = "nonebot-plugin-litestore", specifier = ">=0.1.1" }, + { name = "nonebot-plugin-localstore", specifier = ">=0.7.4" }, { name = "nonebot2", extras = ["fastapi"], specifier = ">=2.4.2" }, { name = "orjson", specifier = ">=3.11.3" }, ] +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=9.0.2" }, + { name = "ruff", specifier = ">=0.15.1" }, +] + [[package]] name = "uvicorn" version = "0.37.0" From 5c0c3c94c7a359ed3bb76b057fcd8ccde79bbddf Mon Sep 17 00:00:00 2001 From: KanbeReina Date: Fri, 13 Feb 2026 21:08:11 +0800 Subject: [PATCH 4/7] =?UTF-8?q?feat=EF=BC=9A=E6=9B=B4=E6=96=B0=20`UserInfo?= =?UTF-8?q?s`=20=E7=89=88=E6=9C=AC=E8=87=B3=20`v3.0.1`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 11 +++++++ src/libs/userinfos/PKG-INFO | 49 ++++++++++++++++++++++++++++--- src/libs/userinfos/README.md | 45 ++++++++++++++++++++++++++-- src/libs/userinfos/pyproject.toml | 4 +-- uv.lock | 40 +++++++++++++------------ 5 files changed, 122 insertions(+), 27 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 70f80d1..aaebdff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ dependencies = [ "nonebot-plugin-apscheduler>=0.5.0", "nonebot-plugin-localstore>=0.7.4", "nonebot2[fastapi]>=2.4.3", + "orjson>=3.11.5", "pathvalidate>=3.3.1", "pycryptodome>=3.23.0", "pydantic>=2.11.9", @@ -34,3 +35,13 @@ dev = [ "ruff>=0.15.1", "UserInfos @ file:///${PROJECT_ROOT}/src/libs/userinfos", ] + +[tool.ruff.lint] +exclude = [ + "src/libs/**" +] + +[tool.ruff.format] +exclude = [ + "src/libs/**" +] diff --git a/src/libs/userinfos/PKG-INFO b/src/libs/userinfos/PKG-INFO index 6a3f506..c6fdd4d 100644 --- a/src/libs/userinfos/PKG-INFO +++ b/src/libs/userinfos/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.3 Name: UserInfos -Version: 3.0.0 +Version: 3.0.1 Summary: 适用于 SariBot 的用户数据&权限管理插件 Author: KanbeReina Author-email: KanbeReina @@ -9,10 +9,51 @@ Requires-Dist: nonebot2[fastapi]>=2.4.2 Requires-Dist: aiofiles>=24.1.0 Requires-Dist: nonebot-adapter-onebot>=2.4.6 Requires-Dist: nonebot-plugin-alconna>=0.57.4 -Requires-Dist: orjson>=3.11.3 +Requires-Dist: orjson>=3.11.5 Requires-Dist: nonebot-plugin-localstore>=0.7.4 Requires-Python: >=3.12, <3.14 Description-Content-Type: text/markdown -# UserInfos -一个NoneBot2插件, 用于管理用户权限和为其他插件提供用户信息 +

+ SariBot +

+ +
+ +# ⭐UserInfos(用户数据&权限管理) + +_🍒轻量、高性能的**用户数据&权限管理**插件🍒_ + +

+ python + + nonebot + + + onebot + +
+ + QQ Group + +

+ +
+ +> [!CAUTION]\ +> **UserInfos** 是 **闭源插件**,**禁止在未经允许的情况下传播其源代码!**。 + +## 特性 +- 🕗支持**多种 Workflow**,**项目管理更高效** +- 💪使用**高性能的`orjson`存储`JSON`数据** +- 🌼拥有**美观的消息模板** + +## 功能支持情况 +- ✅: 完全支持 +- 🚧: 计划中或部分支持 +- ❌: 不支持 + +| 功能名称 | 支持情况 | +|------------------------|-------------------------------------------| +| 权限管理 | ✅ | +| 用户数据管理 | ✅ | diff --git a/src/libs/userinfos/README.md b/src/libs/userinfos/README.md index a5ef63e..445bf46 100644 --- a/src/libs/userinfos/README.md +++ b/src/libs/userinfos/README.md @@ -1,2 +1,43 @@ -# UserInfos -一个NoneBot2插件, 用于管理用户权限和为其他插件提供用户信息 +

+ SariBot +

+ +
+ +# ⭐UserInfos(用户数据&权限管理) + +_🍒轻量、高性能的**用户数据&权限管理**插件🍒_ + +

+ python + + nonebot + + + onebot + +
+ + QQ Group + +

+ +
+ +> [!CAUTION]\ +> **UserInfos** 是 **闭源插件**,**禁止在未经允许的情况下传播其源代码!**。 + +## 特性 +- 🕗支持**多种 Workflow**,**项目管理更高效** +- 💪使用**高性能的`orjson`存储`JSON`数据** +- 🌼拥有**美观的消息模板** + +## 功能支持情况 +- ✅: 完全支持 +- 🚧: 计划中或部分支持 +- ❌: 不支持 + +| 功能名称 | 支持情况 | +|------------------------|-------------------------------------------| +| 权限管理 | ✅ | +| 用户数据管理 | ✅ | diff --git a/src/libs/userinfos/pyproject.toml b/src/libs/userinfos/pyproject.toml index b10f93b..b6b775f 100644 --- a/src/libs/userinfos/pyproject.toml +++ b/src/libs/userinfos/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "UserInfos" -version = "3.0.0" +version = "3.0.1" description = "适用于 SariBot 的用户数据&权限管理插件" authors = [ {name = "KanbeReina", email = "kano.2525@qq.com"}, @@ -10,7 +10,7 @@ dependencies = [ "aiofiles>=24.1.0", "nonebot-adapter-onebot>=2.4.6", "nonebot-plugin-alconna>=0.57.4", - "orjson>=3.11.3", + "orjson>=3.11.5", "nonebot-plugin-localstore>=0.7.4", ] requires-python = ">=3.12,<3.14" diff --git a/uv.lock b/uv.lock index 1829542..c464814 100644 --- a/uv.lock +++ b/uv.lock @@ -85,6 +85,7 @@ dependencies = [ { name = "nonebot-plugin-apscheduler" }, { name = "nonebot-plugin-localstore" }, { name = "nonebot2", extra = ["fastapi"] }, + { name = "orjson" }, { name = "pathvalidate" }, { name = "pycryptodome" }, { name = "pydantic" }, @@ -109,6 +110,7 @@ requires-dist = [ { name = "nonebot-plugin-apscheduler", specifier = ">=0.5.0" }, { name = "nonebot-plugin-localstore", specifier = ">=0.7.4" }, { name = "nonebot2", extras = ["fastapi"], specifier = ">=2.4.3" }, + { name = "orjson", specifier = ">=3.11.5" }, { name = "pathvalidate", specifier = ">=3.3.1" }, { name = "pycryptodome", specifier = ">=3.23.0" }, { name = "pydantic", specifier = ">=2.11.9" }, @@ -457,25 +459,25 @@ wheels = [ [[package]] name = "orjson" -version = "3.11.3" +version = "3.11.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/4d/8df5f83256a809c22c4d6792ce8d43bb503be0fb7a8e4da9025754b09658/orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a", size = 5482394, upload-time = "2025-08-26T17:46:43.171Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/45/b268004f745ede84e5798b48ee12b05129d19235d0e15267aa57dcdb400b/orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49", size = 6144992, upload-time = "2026-02-02T15:38:49.29Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/b0/a7edab2a00cdcb2688e1c943401cb3236323e7bfd2839815c6131a3742f4/orjson-3.11.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8c752089db84333e36d754c4baf19c0e1437012242048439c7e80eb0e6426e3b", size = 238259, upload-time = "2025-08-26T17:45:15.093Z" }, - { url = "https://files.pythonhosted.org/packages/e1/c6/ff4865a9cc398a07a83342713b5932e4dc3cb4bf4bc04e8f83dedfc0d736/orjson-3.11.3-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:9b8761b6cf04a856eb544acdd82fc594b978f12ac3602d6374a7edb9d86fd2c2", size = 127633, upload-time = "2025-08-26T17:45:16.417Z" }, - { url = "https://files.pythonhosted.org/packages/6e/e6/e00bea2d9472f44fe8794f523e548ce0ad51eb9693cf538a753a27b8bda4/orjson-3.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b13974dc8ac6ba22feaa867fc19135a3e01a134b4f7c9c28162fed4d615008a", size = 123061, upload-time = "2025-08-26T17:45:17.673Z" }, - { url = "https://files.pythonhosted.org/packages/54/31/9fbb78b8e1eb3ac605467cb846e1c08d0588506028b37f4ee21f978a51d4/orjson-3.11.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f83abab5bacb76d9c821fd5c07728ff224ed0e52d7a71b7b3de822f3df04e15c", size = 127956, upload-time = "2025-08-26T17:45:19.172Z" }, - { url = "https://files.pythonhosted.org/packages/36/88/b0604c22af1eed9f98d709a96302006915cfd724a7ebd27d6dd11c22d80b/orjson-3.11.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6fbaf48a744b94091a56c62897b27c31ee2da93d826aa5b207131a1e13d4064", size = 130790, upload-time = "2025-08-26T17:45:20.586Z" }, - { url = "https://files.pythonhosted.org/packages/0e/9d/1c1238ae9fffbfed51ba1e507731b3faaf6b846126a47e9649222b0fd06f/orjson-3.11.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc779b4f4bba2847d0d2940081a7b6f7b5877e05408ffbb74fa1faf4a136c424", size = 132385, upload-time = "2025-08-26T17:45:22.036Z" }, - { url = "https://files.pythonhosted.org/packages/a3/b5/c06f1b090a1c875f337e21dd71943bc9d84087f7cdf8c6e9086902c34e42/orjson-3.11.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd4b909ce4c50faa2192da6bb684d9848d4510b736b0611b6ab4020ea6fd2d23", size = 135305, upload-time = "2025-08-26T17:45:23.4Z" }, - { url = "https://files.pythonhosted.org/packages/a0/26/5f028c7d81ad2ebbf84414ba6d6c9cac03f22f5cd0d01eb40fb2d6a06b07/orjson-3.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:524b765ad888dc5518bbce12c77c2e83dee1ed6b0992c1790cc5fb49bb4b6667", size = 132875, upload-time = "2025-08-26T17:45:25.182Z" }, - { url = "https://files.pythonhosted.org/packages/fe/d4/b8df70d9cfb56e385bf39b4e915298f9ae6c61454c8154a0f5fd7efcd42e/orjson-3.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:84fd82870b97ae3cdcea9d8746e592b6d40e1e4d4527835fc520c588d2ded04f", size = 130940, upload-time = "2025-08-26T17:45:27.209Z" }, - { url = "https://files.pythonhosted.org/packages/da/5e/afe6a052ebc1a4741c792dd96e9f65bf3939d2094e8b356503b68d48f9f5/orjson-3.11.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fbecb9709111be913ae6879b07bafd4b0785b44c1eb5cac8ac76da048b3885a1", size = 403852, upload-time = "2025-08-26T17:45:28.478Z" }, - { url = "https://files.pythonhosted.org/packages/f8/90/7bbabafeb2ce65915e9247f14a56b29c9334003536009ef5b122783fe67e/orjson-3.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9dba358d55aee552bd868de348f4736ca5a4086d9a62e2bfbbeeb5629fe8b0cc", size = 146293, upload-time = "2025-08-26T17:45:29.86Z" }, - { url = "https://files.pythonhosted.org/packages/27/b3/2d703946447da8b093350570644a663df69448c9d9330e5f1d9cce997f20/orjson-3.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eabcf2e84f1d7105f84580e03012270c7e97ecb1fb1618bda395061b2a84a049", size = 135470, upload-time = "2025-08-26T17:45:31.243Z" }, - { url = "https://files.pythonhosted.org/packages/38/70/b14dcfae7aff0e379b0119c8a812f8396678919c431efccc8e8a0263e4d9/orjson-3.11.3-cp312-cp312-win32.whl", hash = "sha256:3782d2c60b8116772aea8d9b7905221437fdf53e7277282e8d8b07c220f96cca", size = 136248, upload-time = "2025-08-26T17:45:32.567Z" }, - { url = "https://files.pythonhosted.org/packages/35/b8/9e3127d65de7fff243f7f3e53f59a531bf6bb295ebe5db024c2503cc0726/orjson-3.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:79b44319268af2eaa3e315b92298de9a0067ade6e6003ddaef72f8e0bedb94f1", size = 131437, upload-time = "2025-08-26T17:45:34.949Z" }, - { url = "https://files.pythonhosted.org/packages/51/92/a946e737d4d8a7fd84a606aba96220043dcc7d6988b9e7551f7f6d5ba5ad/orjson-3.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:0e92a4e83341ef79d835ca21b8bd13e27c859e4e9e4d7b63defc6e58462a3710", size = 125978, upload-time = "2025-08-26T17:45:36.422Z" }, + { url = "https://files.pythonhosted.org/packages/80/bf/76f4f1665f6983385938f0e2a5d7efa12a58171b8456c252f3bae8a4cf75/orjson-3.11.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd03ea7606833655048dab1a00734a2875e3e86c276e1d772b2a02556f0d895f", size = 228545, upload-time = "2026-02-02T15:37:46.376Z" }, + { url = "https://files.pythonhosted.org/packages/79/53/6c72c002cb13b5a978a068add59b25a8bdf2800ac1c9c8ecdb26d6d97064/orjson-3.11.7-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:89e440ebc74ce8ab5c7bc4ce6757b4a6b1041becb127df818f6997b5c71aa60b", size = 125224, upload-time = "2026-02-02T15:37:47.697Z" }, + { url = "https://files.pythonhosted.org/packages/2c/83/10e48852865e5dd151bdfe652c06f7da484578ed02c5fca938e3632cb0b8/orjson-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ede977b5fe5ac91b1dffc0a517ca4542d2ec8a6a4ff7b2652d94f640796342a", size = 128154, upload-time = "2026-02-02T15:37:48.954Z" }, + { url = "https://files.pythonhosted.org/packages/6e/52/a66e22a2b9abaa374b4a081d410edab6d1e30024707b87eab7c734afe28d/orjson-3.11.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7b1dae39230a393df353827c855a5f176271c23434cfd2db74e0e424e693e10", size = 123548, upload-time = "2026-02-02T15:37:50.187Z" }, + { url = "https://files.pythonhosted.org/packages/de/38/605d371417021359f4910c496f764c48ceb8997605f8c25bf1dfe58c0ebe/orjson-3.11.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed46f17096e28fb28d2975834836a639af7278aa87c84f68ab08fbe5b8bd75fa", size = 129000, upload-time = "2026-02-02T15:37:51.426Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/af32e842b0ffd2335c89714d48ca4e3917b42f5d6ee5537832e069a4b3ac/orjson-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3726be79e36e526e3d9c1aceaadbfb4a04ee80a72ab47b3f3c17fefb9812e7b8", size = 141686, upload-time = "2026-02-02T15:37:52.607Z" }, + { url = "https://files.pythonhosted.org/packages/96/0b/fc793858dfa54be6feee940c1463370ece34b3c39c1ca0aa3845f5ba9892/orjson-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0724e265bc548af1dedebd9cb3d24b4e1c1e685a343be43e87ba922a5c5fff2f", size = 130812, upload-time = "2026-02-02T15:37:53.944Z" }, + { url = "https://files.pythonhosted.org/packages/dc/91/98a52415059db3f374757d0b7f0f16e3b5cd5976c90d1c2b56acaea039e6/orjson-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7745312efa9e11c17fbd3cb3097262d079da26930ae9ae7ba28fb738367cbad", size = 133440, upload-time = "2026-02-02T15:37:55.615Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/cb540117bda61791f46381f8c26c8f93e802892830a6055748d3bb1925ab/orjson-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867", size = 138386, upload-time = "2026-02-02T15:37:56.814Z" }, + { url = "https://files.pythonhosted.org/packages/63/1a/50a3201c334a7f17c231eee5f841342190723794e3b06293f26e7cf87d31/orjson-3.11.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b9fc4d0f81f394689e0814617aadc4f2ea0e8025f38c226cbf22d3b5ddbf025d", size = 408853, upload-time = "2026-02-02T15:37:58.291Z" }, + { url = "https://files.pythonhosted.org/packages/87/cd/8de1c67d0be44fdc22701e5989c0d015a2adf391498ad42c4dc589cd3013/orjson-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:849e38203e5be40b776ed2718e587faf204d184fc9a008ae441f9442320c0cab", size = 144130, upload-time = "2026-02-02T15:38:00.163Z" }, + { url = "https://files.pythonhosted.org/packages/0f/fe/d605d700c35dd55f51710d159fc54516a280923cd1b7e47508982fbb387d/orjson-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4682d1db3bcebd2b64757e0ddf9e87ae5f00d29d16c5cdf3a62f561d08cc3dd2", size = 134818, upload-time = "2026-02-02T15:38:01.507Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e4/15ecc67edb3ddb3e2f46ae04475f2d294e8b60c1825fbe28a428b93b3fbd/orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f", size = 127923, upload-time = "2026-02-02T15:38:02.75Z" }, + { url = "https://files.pythonhosted.org/packages/34/70/2e0855361f76198a3965273048c8e50a9695d88cd75811a5b46444895845/orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74", size = 125007, upload-time = "2026-02-02T15:38:04.032Z" }, + { url = "https://files.pythonhosted.org/packages/68/40/c2051bd19fc467610fed469dc29e43ac65891571138f476834ca192bc290/orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5", size = 126089, upload-time = "2026-02-02T15:38:05.297Z" }, ] [[package]] @@ -835,7 +837,7 @@ wheels = [ [[package]] name = "userinfos" -version = "3.0.0" +version = "3.0.1" source = { directory = "src/libs/userinfos" } dependencies = [ { name = "aiofiles" }, @@ -853,7 +855,7 @@ requires-dist = [ { name = "nonebot-plugin-alconna", specifier = ">=0.57.4" }, { name = "nonebot-plugin-localstore", specifier = ">=0.7.4" }, { name = "nonebot2", extras = ["fastapi"], specifier = ">=2.4.2" }, - { name = "orjson", specifier = ">=3.11.3" }, + { name = "orjson", specifier = ">=3.11.5" }, ] [package.metadata.requires-dev] From e7857156cbee4f317c8da4b8fb318a1144419785 Mon Sep 17 00:00:00 2001 From: KanbeReina Date: Fri, 13 Feb 2026 21:13:13 +0800 Subject: [PATCH 5/7] =?UTF-8?q?style=EF=BC=9A=E6=A0=BC=E5=BC=8F=E5=8C=96?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/AudioMaster/__init__.py | 2 +- src/AudioMaster/manager/__init__.py | 7 ++++++- src/AudioMaster/utils/api/__init__.py | 9 +++++++-- src/AudioMaster/utils/api/bilibili/__init__.py | 5 ++++- src/AudioMaster/utils/api/bilibili/main.py | 8 ++++---- src/AudioMaster/utils/api/deepseek/__init__.py | 5 ++++- src/AudioMaster/utils/commands/login.py | 2 -- src/AudioMaster/utils/drawer/__init__.py | 5 ++++- src/AudioMaster/utils/ffmpeg/__init__.py | 5 ++++- src/AudioMaster/utils/ffmpeg/main.py | 2 +- 10 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/AudioMaster/__init__.py b/src/AudioMaster/__init__.py index 8f66624..e2f959f 100644 --- a/src/AudioMaster/__init__.py +++ b/src/AudioMaster/__init__.py @@ -5,4 +5,4 @@ require("nonebot-plugin-localstore") require("UserInfos") -from .main import * +from .main import * # noqa: E402, F403 diff --git a/src/AudioMaster/manager/__init__.py b/src/AudioMaster/manager/__init__.py index 15b6a64..0cba550 100644 --- a/src/AudioMaster/manager/__init__.py +++ b/src/AudioMaster/manager/__init__.py @@ -1 +1,6 @@ -from .main import * +from .main import BiliManager + + +__all__ = [ + "BiliManager" +] diff --git a/src/AudioMaster/utils/api/__init__.py b/src/AudioMaster/utils/api/__init__.py index fb0de9f..1fd36cd 100644 --- a/src/AudioMaster/utils/api/__init__.py +++ b/src/AudioMaster/utils/api/__init__.py @@ -1,2 +1,7 @@ -from .bilibili import * -from .deepseek import * +from .bilibili import BiliAPI +from .deepseek import DeepSeekAPI + + +__all__ = [ + "BiliAPI", "DeepSeekAPI", +] diff --git a/src/AudioMaster/utils/api/bilibili/__init__.py b/src/AudioMaster/utils/api/bilibili/__init__.py index 15b6a64..d0fab15 100644 --- a/src/AudioMaster/utils/api/bilibili/__init__.py +++ b/src/AudioMaster/utils/api/bilibili/__init__.py @@ -1 +1,4 @@ -from .main import * +from .main import BiliAPI + + +__all__ = ["BiliAPI"] diff --git a/src/AudioMaster/utils/api/bilibili/main.py b/src/AudioMaster/utils/api/bilibili/main.py index ce5d863..9f44a58 100644 --- a/src/AudioMaster/utils/api/bilibili/main.py +++ b/src/AudioMaster/utils/api/bilibili/main.py @@ -219,12 +219,12 @@ async def qrcode_poll(self, qrcode_key: str) -> QRCodePoll: ) # 当扫码登录成功时刷新 Cookies if poll_result.code == 0: - logger.success(f"扫码登录成功") + logger.success("扫码登录成功") self._client.cookies = resp.cookies logger.debug("已更新 Cookies") # 缓存持久化刷新口令,便于在 Cookies 过期时进行刷新 self._refresh_token = poll_result.refresh_token - logger.debug(f"已更新持久化刷新令牌") + logger.debug("已更新持久化刷新令牌") return poll_result @@ -355,9 +355,9 @@ async def refresh_cookies(self, timestamp: int): raise ValueError(f"刷新 Cookies 失败:{resp_json.get("message")}") # 刷新 Cookies - logger.success(f"刷新 Cookies 成功") + logger.success("刷新 Cookies 成功") self._client.cookies = resp.cookies - logger.debug(f"已更新 Cookies") + logger.debug("已更新 Cookies") # 存储新的持久化刷新令牌 old_refresh_token = self._refresh_token # 将旧的持久化刷新令牌存储,以销毁旧的 Cookie self._refresh_token: str = resp.json()["data"]["refresh_token"] diff --git a/src/AudioMaster/utils/api/deepseek/__init__.py b/src/AudioMaster/utils/api/deepseek/__init__.py index 15b6a64..3e20082 100644 --- a/src/AudioMaster/utils/api/deepseek/__init__.py +++ b/src/AudioMaster/utils/api/deepseek/__init__.py @@ -1 +1,4 @@ -from .main import * +from .main import DeepSeekAPI + + +__all__ = ["DeepSeekAPI"] diff --git a/src/AudioMaster/utils/commands/login.py b/src/AudioMaster/utils/commands/login.py index 9e9db41..029f4ce 100644 --- a/src/AudioMaster/utils/commands/login.py +++ b/src/AudioMaster/utils/commands/login.py @@ -1,5 +1,3 @@ -from typing import Optional - from nonebot.adapters.onebot.v11 import ( MessageEvent, GroupMessageEvent diff --git a/src/AudioMaster/utils/drawer/__init__.py b/src/AudioMaster/utils/drawer/__init__.py index 15b6a64..49003c5 100644 --- a/src/AudioMaster/utils/drawer/__init__.py +++ b/src/AudioMaster/utils/drawer/__init__.py @@ -1 +1,4 @@ -from .main import * +from .main import Drawer + + +__all__ = ["Drawer"] diff --git a/src/AudioMaster/utils/ffmpeg/__init__.py b/src/AudioMaster/utils/ffmpeg/__init__.py index 15b6a64..2ae97b4 100644 --- a/src/AudioMaster/utils/ffmpeg/__init__.py +++ b/src/AudioMaster/utils/ffmpeg/__init__.py @@ -1 +1,4 @@ -from .main import * +from .main import AudioHandler + + +__all__ = ["AudioHandler"] diff --git a/src/AudioMaster/utils/ffmpeg/main.py b/src/AudioMaster/utils/ffmpeg/main.py index 6aab156..7be8584 100644 --- a/src/AudioMaster/utils/ffmpeg/main.py +++ b/src/AudioMaster/utils/ffmpeg/main.py @@ -48,7 +48,7 @@ def convert( logger.info("对音频进行标准化至 -0.1 dB") audio = normalize(audio) - logger.info(f"对音频进行转码") + logger.info("对音频进行转码") audio.export(file_path, format=audio_format) logger.success(f"处理完成,音频文件已保存至:{file_path}") From 4ca297c79d0670f6f0b2ebc3ef80ff8161999289 Mon Sep 17 00:00:00 2001 From: KanbeReina Date: Fri, 13 Feb 2026 21:13:36 +0800 Subject: [PATCH 6/7] =?UTF-8?q?style=EF=BC=9A=E6=A0=BC=E5=BC=8F=E5=8C=96?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/AudioMaster/config.py | 3 +- src/AudioMaster/db.py | 9 +- src/AudioMaster/main.py | 240 +++++--------- src/AudioMaster/manager/__init__.py | 4 +- src/AudioMaster/manager/main.py | 62 ++-- .../manager/models/analyse_video.py | 21 +- src/AudioMaster/utils/api/__init__.py | 3 +- src/AudioMaster/utils/api/bilibili/main.py | 293 +++++++++--------- .../utils/api/bilibili/models/dash.py | 15 +- .../utils/api/bilibili/models/nav.py | 4 +- .../utils/api/bilibili/models/qr_code.py | 7 +- .../api/bilibili/models/refresh_cookies.py | 4 +- .../utils/api/bilibili/models/video.py | 19 +- .../utils/api/bilibili/models/wbi_key.py | 5 +- src/AudioMaster/utils/api/bilibili/typing.py | 2 +- src/AudioMaster/utils/api/bilibili/utils.py | 11 +- src/AudioMaster/utils/api/deepseek/main.py | 29 +- .../utils/api/deepseek/models/chat.py | 28 +- src/AudioMaster/utils/commands/auto.py | 68 +--- src/AudioMaster/utils/commands/balance.py | 19 +- src/AudioMaster/utils/commands/login.py | 93 ++---- src/AudioMaster/utils/commands/logout.py | 28 +- src/AudioMaster/utils/drawer/db.py | 12 +- src/AudioMaster/utils/drawer/main.py | 248 +++++---------- src/AudioMaster/utils/drawer/typing.py | 5 +- src/AudioMaster/utils/ffmpeg/main.py | 45 ++- tests/audio/test_handle.py | 9 +- tests/bilibili/test_api.py | 33 +- tests/deepseek/test_chat.py | 10 +- tests/drawer/test_draw_card.py | 2 +- 30 files changed, 489 insertions(+), 842 deletions(-) diff --git a/src/AudioMaster/config.py b/src/AudioMaster/config.py index cdf2ae1..c1e114d 100644 --- a/src/AudioMaster/config.py +++ b/src/AudioMaster/config.py @@ -5,8 +5,7 @@ class Config(BaseModel): am_deepseek_api_key: Annotated[ - Optional[str], - "用于自动转换功能的 DeepSeek API 密钥" + Optional[str], "用于自动转换功能的 DeepSeek API 密钥" ] = None diff --git a/src/AudioMaster/db.py b/src/AudioMaster/db.py index f7a0773..8d54784 100644 --- a/src/AudioMaster/db.py +++ b/src/AudioMaster/db.py @@ -1,11 +1,8 @@ from pathlib import Path -RESOURCES_PATH = Path(__file__).parent/"resources" -EMOJIS_PATH = RESOURCES_PATH/"emojis" +RESOURCES_PATH = Path(__file__).parent / "resources" +EMOJIS_PATH = RESOURCES_PATH / "emojis" -__all__ = [ - "RESOURCES_PATH", - "EMOJIS_PATH" -] +__all__ = ["RESOURCES_PATH", "EMOJIS_PATH"] diff --git a/src/AudioMaster/main.py b/src/AudioMaster/main.py index 49f2f82..36b1868 100644 --- a/src/AudioMaster/main.py +++ b/src/AudioMaster/main.py @@ -5,69 +5,52 @@ import httpx from nonebot.log import logger from nonebot.params import T_State -from nonebot.adapters.onebot.v11 import ( - MessageEvent -) -from nonebot_plugin_alconna import ( - on_alconna, Alconna, - Subcommand, Args, - Query -) +from nonebot.adapters.onebot.v11 import MessageEvent +from nonebot_plugin_alconna import on_alconna, Alconna, Subcommand, Args, Query import aiofiles.os as aio_os from UserInfos.permission import User, Admin from .manager import BiliManager from .manager.models.analyse_video import VideoInfos, AnalyseResult from .utils.api.bilibili.models.qr_code import QRCodeLogin -from .utils.api.bilibili.models.video import ( - VideoDetails, VideoPage -) +from .utils.api.bilibili.models.video import VideoDetails, VideoPage from .utils.api.bilibili.models.dash import Dash, AudioStreamDetails from .utils.commands.login import ( only_refresh_cookies, action_failed, - login_succeed, login_qrcode, re_login, - prompt_confirm_login + login_succeed, + login_qrcode, + re_login, + prompt_confirm_login, ) from .utils.commands.logout import logout_succeed from .utils.commands.auto import ( bvid_format_error, page_input_error, - show_cinfo, file, - upload_success + show_cinfo, + file, + upload_success, ) from .utils.commands.balance import balance_reply am_main = on_alconna( - Alconna( - "鹿歌转换", - Args["bvid?", str]["page?", int]["download_flac?", str] - ), + Alconna("鹿歌转换", Args["bvid?", str]["page?", int]["download_flac?", str]), block=True, priority=30, permission=User(), - aliases={"转换鹿歌"} + aliases={"转换鹿歌"}, ) am = on_alconna( Alconna( "鹿歌转换", - Subcommand( - "登录", - alias={"登录账号"} - ), - Subcommand( - "退登", - alias={"退出登录"} - ), - Subcommand( - "余额", - alias={"查询余额"} - ) + Subcommand("登录", alias={"登录账号"}), + Subcommand("退登", alias={"退出登录"}), + Subcommand("余额", alias={"查询余额"}), ), priority=15, permission=User(), - aliases={"转换鹿歌"} + aliases={"转换鹿歌"}, ) login = am.dispatch("登录", block=True, permission=Admin()) logout = am.dispatch("退登", block=True, permission=Admin()) @@ -79,14 +62,12 @@ async def check_login_status(event: MessageEvent): # 检查当前是否登录过 if BiliManager.bili_api.has_cookies: # 已登录 if ( # Cookie 过期,但可刷新 Cookie,进行询问是否只进行刷新 Cookie 操作 - check_result := await BiliManager.bili_api.check_cookies() + check_result := await BiliManager.bili_api.check_cookies() ).refresh: if ( # 询问并处理超时情况 - result := await login.prompt( - only_refresh_cookies(event), - timeout=45, - block=True - ) + result := await login.prompt( + only_refresh_cookies(event), timeout=45, block=True + ) ) is None: await login.finish("已超时,自动取消操作。") else: @@ -102,9 +83,7 @@ async def check_login_status(event: MessageEvent): await login_succeed(event, card=user_card).send() except Exception as e: await action_failed( - event, - action_name="自动登录", - exception=e + event, action_name="自动登录", exception=e ).send() raise e else: @@ -115,11 +94,7 @@ async def check_login_status(event: MessageEvent): await login.finish("已取消操作。") if ( # Cookie 未过期,询问是否重新登录 - result := await login.prompt( - re_login(event), - timeout=45, - block=True - ) + result := await login.prompt(re_login(event), timeout=45, block=True) ) is None: # 超时 await login.finish("已超时,自动取消操作。") else: @@ -130,26 +105,25 @@ async def check_login_status(event: MessageEvent): case _: await login.finish("已取消操作。") + @login.handle() async def show_login_qrcode(event: MessageEvent, state: T_State): BiliManager.bili_api.clear_login() # 确保处于退登状态 try: state.update( - {"qrcode_info": (qrcode_info := await BiliManager.bili_api.qrcode_generate())} + { + "qrcode_info": ( + qrcode_info := await BiliManager.bili_api.qrcode_generate() + ) + } ) - await login_qrcode( - event, - qrcode=qrcode_info.qrcode_data - ).send() + await login_qrcode(event, qrcode=qrcode_info.qrcode_data).send() except Exception as e: - await action_failed( - event, - action_name="生成二维码", - exception=e - ).send() + await action_failed(event, action_name="生成二维码", exception=e).send() raise e + @login.handle() async def wait_for_login(event: MessageEvent, state: T_State): qrcode_info: QRCodeLogin = state["qrcode_info"] @@ -180,33 +154,25 @@ async def wait_for_login(event: MessageEvent, state: T_State): # 等待 6 秒后再确认扫码结果 await asyncio.sleep(6) except Exception as e: - await action_failed( - event, - action_name="扫码登录", - exception=e - ).send() + await action_failed(event, action_name="扫码登录", exception=e).send() raise e + @login.handle() async def show_user_card(event: MessageEvent): try: - await login_succeed( - event, - card=await BiliManager.get_self_card() - ).send() + await login_succeed(event, card=await BiliManager.get_self_card()).send() except Exception as e: - await action_failed( - event, - action_name="绘制卡片", - exception=e - ).send() + await action_failed(event, action_name="绘制卡片", exception=e).send() raise e + @logout.assign("~") async def _(event: MessageEvent): BiliManager.bili_api.clear_login() await logout_succeed(event).finish() + @balance.assign("~") async def _(event: MessageEvent): try: @@ -214,18 +180,15 @@ async def _(event: MessageEvent): event, balance=await BiliManager.deepseek_api.get_balance() ).send() except Exception as e: - action_failed( - event, - action_name="查询余额", - exception=e - ) + action_failed(event, action_name="查询余额", exception=e) raise e + @am_main.assign("$main") async def set_args( - bvid: Query[str] = Query("~bvid"), - page: Query[str] = Query("~page"), - download_flac: Query[str] = Query("~download_flac"), + bvid: Query[str] = Query("~bvid"), + page: Query[str] = Query("~page"), + download_flac: Query[str] = Query("~download_flac"), ): if bvid.available: am_main.set_path_arg("~bvid", bvid.result) @@ -234,6 +197,7 @@ async def set_args( if download_flac.available: am_main.set_path_arg("~download_flac", download_flac.result) + @am_main.got_path("~bvid", prompt="请输入需要转换的视频 BV 号:") async def get_video_details(bvid: str, event: MessageEvent, state: T_State): # 粗略检查 BV 号格式是否正确 @@ -244,21 +208,16 @@ async def get_video_details(bvid: str, event: MessageEvent, state: T_State): try: details = await BiliManager.bili_api.get_video_details(bvid) except Exception as e: - await action_failed( - event, - action_name="获取视频信息", - exception=e - ).send() + await action_failed(event, action_name="获取视频信息", exception=e).send() raise e # 临时存储视频详细信息 - state.update( - {"am_video_details": details} - ) + state.update({"am_video_details": details}) # 判断视频是否只有一个分P if details.videos == 1: am_main.set_path_arg("~page", 1) + @am_main.got_path("~page", prompt="请输入需要转换的视频分P(序号):") async def get_video_pagelist(page: int, event: MessageEvent, state: T_State): # 重新提取视频详细信息 @@ -271,20 +230,15 @@ async def get_video_pagelist(page: int, event: MessageEvent, state: T_State): # 若视频存在多个分P,则获取分P列表 if details.videos > 1: try: - pagelist = await BiliManager.bili_api.get_video_pagelist( - bvid=details.bvid - ) + pagelist = await BiliManager.bili_api.get_video_pagelist(bvid=details.bvid) except Exception as e: await action_failed( - event, - action_name="获取视频分P列表", - exception=e + event, action_name="获取视频分P列表", exception=e ).send() raise e - state.update( - {"am_video_page": pagelist[page - 1]} - ) + state.update({"am_video_page": pagelist[page - 1]}) + @am_main.handle() async def get_video_playurl(event: MessageEvent, state: T_State): @@ -295,35 +249,28 @@ async def get_video_playurl(event: MessageEvent, state: T_State): try: # 获取音视频流下载地址 dash = await BiliManager.bili_api.get_video_playurl( - bvid=details.bvid, - cid=details.cid if video_page is None else video_page.cid + bvid=details.bvid, cid=details.cid if video_page is None else video_page.cid ) state.update( # 临时存储 Dash 信息 {"am_dash": dash} ) except Exception as e: - await action_failed( - event, - action_name="请求下载地址", - exception=e - ).send() + await action_failed(event, action_name="请求下载地址", exception=e).send() raise e # 检查是否存在无损格式音频 if not dash.flac_audio_exists: # 临时存储设定 - state.update( - {"am_allow_flac": False} - ) + state.update({"am_allow_flac": False}) am_main.set_path_arg("~download_flac", "否") + @am_main.got_path("~download_flac", prompt="是否下载无损音质?(是/否):") async def prompt_download_flac(download_flac: str, state: T_State): if download_flac == "是": # 临时存储设定 - state.update( - {"am_allow_flac": True} - ) + state.update({"am_allow_flac": True}) + @am_main.handle() async def choose_audio_quality(event: MessageEvent, state: T_State): @@ -339,13 +286,10 @@ async def choose_audio_quality(event: MessageEvent, state: T_State): {"am_audio_details": dash.audio[0]} ) except Exception as e: - await action_failed( - event, - action_name="自动选择音质", - exception=e - ).send() + await action_failed(event, action_name="自动选择音质", exception=e).send() raise e + @am_main.handle() async def analyse_video_info(event: MessageEvent, state: T_State): details: VideoDetails = state["am_video_details"] @@ -356,20 +300,17 @@ async def analyse_video_info(event: MessageEvent, state: T_State): vinfo=VideoInfos( title=details.title if video_page is None else video_page.part, author=details.owner.name, - desc=details.desc + desc=details.desc, ) ) state.update( # 临时存储分析结果 {"am_analyse_result": result} ) except Exception as e: - await action_failed( - event, - action_name="提取信息", - exception=e - ).send() + await action_failed(event, action_name="提取信息", exception=e).send() raise e + @am_main.handle() async def show_cinfo_card(event: MessageEvent, state: T_State): details: VideoDetails = state["am_video_details"] @@ -384,23 +325,17 @@ async def show_cinfo_card(event: MessageEvent, state: T_State): analyse_result=analyse_result, dash=dash, audio_stream_details=audio_details, - page=video_page + page=video_page, ) state.update( # 存储专辑封面 {"am_album_cover": cinfo_card.cover} ) except Exception as e: - await action_failed( - event, - action_name="绘制卡片", - exception=e - ).send() + await action_failed(event, action_name="绘制卡片", exception=e).send() raise e - await show_cinfo( - event, - card=cinfo_card.data - ).send() + await show_cinfo(event, card=cinfo_card.data).send() + @am_main.handle() async def download_raw_audio(event: MessageEvent, state: T_State): @@ -409,33 +344,32 @@ async def download_raw_audio(event: MessageEvent, state: T_State): try: logger.info(f"正在下载音频:{audio_details.base_url}") - logger.info(f"预计下载文件大小:{round((dash.duration * audio_details.bandwidth / 8) / (1024 ** 2), 3)} MB") + logger.info( + f"预计下载文件大小:{round((dash.duration * audio_details.bandwidth / 8) / (1024**2), 3)} MB" + ) # 开始下载原始音频文件 async with httpx.AsyncClient() as client: resp = await client.get( url=audio_details.base_url, headers={ "user-agent": "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/58.0.3029.110 Safari/537.36", - "referer": "https://www.bilibili.com/" - } + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/58.0.3029.110 Safari/537.36", + "referer": "https://www.bilibili.com/", + }, ) resp.raise_for_status() except Exception as e: - await action_failed( - event, - action_name="下载音频", - exception=e - ).send() + await action_failed(event, action_name="下载音频", exception=e).send() raise e - logger.success(f"下载音频完毕,共下载 {round(len(resp.content) / (1024 ** 2), 3)} MB") + logger.success(f"下载音频完毕,共下载 {round(len(resp.content) / (1024**2), 3)} MB") state.update( # 临时存储音频数据 {"am_raw_audio": resp.content} ) + @am_main.handle() async def handle_raw_audio(event: MessageEvent, state: T_State): raw_audio: bytes = state["am_raw_audio"] @@ -446,19 +380,16 @@ async def handle_raw_audio(event: MessageEvent, state: T_State): audio_path = await BiliManager.audio_handler.convert( raw_file=raw_audio, file_name=analyse_result.title, - audio_code=audio_details.id + audio_code=audio_details.id, ) state.update( # 缓存音频路径 {"am_audio_path": audio_path} ) except Exception as e: - await action_failed( - event, - action_name="音频转码", - exception=e - ).send() + await action_failed(event, action_name="音频转码", exception=e).send() raise e + @am_main.handle() async def add_music_tags(event: MessageEvent, state: T_State): audio_path: Path = state["am_audio_path"] @@ -470,16 +401,13 @@ async def add_music_tags(event: MessageEvent, state: T_State): audio_path=audio_path, title=analyse_result.title, artists=analyse_result.artists, - cover=album_cover + cover=album_cover, ) except Exception as e: - await action_failed( - event, - action_name="添加音乐元数据", - exception=e - ).send() + await action_failed(event, action_name="添加音乐元数据", exception=e).send() raise e + @am_main.handle() async def upload_file(event: MessageEvent, state: T_State): audio_path: Path = state["am_audio_path"] @@ -487,11 +415,7 @@ async def upload_file(event: MessageEvent, state: T_State): try: await file(audio_path).send() except Exception as e: - await action_failed( - event, - action_name="上传文件", - exception=e - ).send() + await action_failed(event, action_name="上传文件", exception=e).send() raise e else: await upload_success(event).finish() diff --git a/src/AudioMaster/manager/__init__.py b/src/AudioMaster/manager/__init__.py index 0cba550..ee367b4 100644 --- a/src/AudioMaster/manager/__init__.py +++ b/src/AudioMaster/manager/__init__.py @@ -1,6 +1,4 @@ from .main import BiliManager -__all__ = [ - "BiliManager" -] +__all__ = ["BiliManager"] diff --git a/src/AudioMaster/manager/main.py b/src/AudioMaster/manager/main.py index 2cc1eb6..9f3c095 100644 --- a/src/AudioMaster/manager/main.py +++ b/src/AudioMaster/manager/main.py @@ -11,19 +11,14 @@ from AudioMaster.utils.api import ( BiliAPI, DeepSeekAPI, - ChatRequest, Message, ResponseFormat -) -from AudioMaster.utils.api.bilibili.models.video import ( - VideoDetails, VideoPage -) -from AudioMaster.utils.api.bilibili.models.dash import ( - Dash, AudioStreamDetails + ChatRequest, + Message, + ResponseFormat, ) +from AudioMaster.utils.api.bilibili.models.video import VideoDetails, VideoPage +from AudioMaster.utils.api.bilibili.models.dash import Dash, AudioStreamDetails from AudioMaster.utils.ffmpeg import AudioHandler -from .models.analyse_video import ( - VideoInfos, AnalyseResult, - PROMPT -) +from .models.analyse_video import VideoInfos, AnalyseResult, PROMPT DRIVER = get_driver() @@ -53,10 +48,10 @@ async def get_self_card(self) -> bytes: nav = await self._bili_api.get_nav() headers = { - "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" - } + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" + } async with httpx.AsyncClient(headers=headers) as client: return await self._drawer.draw_user_card( raw_avatar=( # 获取头像图片 @@ -70,18 +65,20 @@ async def get_self_card(self) -> bytes: raw_frame=( # 获取可能存在的头像框 await client.get(nav.pendant.image) ).content - if len(nav.pendant.image) else None + if len(nav.pendant.image) + else None, ) async def get_cinfo_card( - self, - video_details: VideoDetails, - analyse_result: AnalyseResult, - dash: Dash, - audio_stream_details: AudioStreamDetails, - page: Optional[VideoPage] = None + self, + video_details: VideoDetails, + analyse_result: AnalyseResult, + dash: Dash, + audio_stream_details: AudioStreamDetails, + page: Optional[VideoPage] = None, ) -> CinfoCard: """获取音频转换信息卡片""" + def convert_time(total_seconds: int) -> str: """转换时长为字符串""" hours = total_seconds // 3600 @@ -107,7 +104,7 @@ def convert_time(total_seconds: int) -> str: def convert_audio_formats(details: AudioStreamDetails, duration: int) -> str: """根据音频流信息输出音频格式""" file_size_mb = round( # 文件预估大小,保留一位小数 - ((details.bandwidth * duration) / 8) / (1024 ** 2), 1 + ((details.bandwidth * duration) / 8) / (1024**2), 1 ) formats_str = "" match details.id: @@ -130,13 +127,13 @@ def convert_audio_formats(details: AudioStreamDetails, duration: int) -> str: ) audio_formats = convert_audio_formats( # 转换音频格式 audio_stream_details, - duration=dash.duration if page is None else page.duration + duration=dash.duration if page is None else page.duration, ) headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" } async with httpx.AsyncClient(headers=headers) as client: return await self._drawer.draw_cinfo_card( @@ -146,7 +143,7 @@ def convert_audio_formats(details: AudioStreamDetails, duration: int) -> str: title=analyse_result.title, artists=analyse_result.artists, audio_formats=audio_formats, - durations=duration_str + durations=duration_str, ) async def analyse_video_details(self, vinfo: VideoInfos) -> AnalyseResult: @@ -155,16 +152,13 @@ async def analyse_video_details(self, vinfo: VideoInfos) -> AnalyseResult: request=ChatRequest( model="deepseek-chat", messages=[ - Message( - role="system", - content=PROMPT - ), + Message(role="system", content=PROMPT), Message( role="user", content=f"视频标题:{vinfo.title}\n" - f"视频发布者:{vinfo.author}\n" - f"视频简介:{vinfo.desc}" - ) + f"视频发布者:{vinfo.author}\n" + f"视频简介:{vinfo.desc}", + ), ], response_format=ResponseFormat(type="json_object"), temperature=1.0, diff --git a/src/AudioMaster/manager/models/analyse_video.py b/src/AudioMaster/manager/models/analyse_video.py index 49f15ca..7460d2b 100644 --- a/src/AudioMaster/manager/models/analyse_video.py +++ b/src/AudioMaster/manager/models/analyse_video.py @@ -4,8 +4,7 @@ # 提示词 -PROMPT = ( - """ +PROMPT = """ # 角色 你是一名信息提取助手,擅长从视频标题、视频发布者、视频简介中精准提取歌曲的歌手名和歌曲名。 @@ -29,7 +28,6 @@ - 输出的格式必须为 JSON。 - 所输出的内容必须按照给定的格式进行组织,不能偏离框架要求。 """ -) class VideoInfos(BaseModel): @@ -39,16 +37,14 @@ class VideoInfos(BaseModel): class AnalyseResult(BaseModel): - artists: Annotated[ - list[str], "歌曲艺术家名称" - ] + artists: Annotated[list[str], "歌曲艺术家名称"] title: Annotated[str, "歌曲名称"] @field_validator("title") @classmethod def handle_title(cls, value: str): if not len( # 去除标题两端空格 - value := value.strip() + value := value.strip() ): return "未知歌曲" @@ -60,19 +56,14 @@ def handle_artists(cls, values: list[str]): values = [ new_name for name in values - if ( # 去除艺术家名称两端空格 + if ( # 去除艺术家名称两端空格 len(new_name := name.strip()) ) ] - if not len( - values): + if not len(values): return ["未知艺术家"] return values -__all__ = [ - "VideoInfos", - "AnalyseResult", - "PROMPT" -] +__all__ = ["VideoInfos", "AnalyseResult", "PROMPT"] diff --git a/src/AudioMaster/utils/api/__init__.py b/src/AudioMaster/utils/api/__init__.py index 1fd36cd..1c4d680 100644 --- a/src/AudioMaster/utils/api/__init__.py +++ b/src/AudioMaster/utils/api/__init__.py @@ -3,5 +3,6 @@ __all__ = [ - "BiliAPI", "DeepSeekAPI", + "BiliAPI", + "DeepSeekAPI", ] diff --git a/src/AudioMaster/utils/api/bilibili/main.py b/src/AudioMaster/utils/api/bilibili/main.py index 9f44a58..a5ddbb3 100644 --- a/src/AudioMaster/utils/api/bilibili/main.py +++ b/src/AudioMaster/utils/api/bilibili/main.py @@ -17,41 +17,94 @@ from Crypto.Hash import SHA256 from .typing import ( - SignedParams, UnsignedParams, + SignedParams, + UnsignedParams, MixinKey, - RefreshCSRFToken, OldRefreshToken -) -from .utils import ( - rsa_public_key + RefreshCSRFToken, + OldRefreshToken, ) +from .utils import rsa_public_key from .models.wbi_key import WbiKeys -from .models.qr_code import ( - QRCodeLogin, - QRCodePoll -) +from .models.qr_code import QRCodeLogin, QRCodePoll from .models.nav import Nav from .models.refresh_cookies import CheckCookiesResult -from .models.video import ( - VideoDetails, - VideoPage -) +from .models.video import VideoDetails, VideoPage from .models.dash import Dash RP = Repr(maxother=200) -RSA_KEY = RSA.importKey( - rsa_public_key() -) +RSA_KEY = RSA.importKey(rsa_public_key()) MIXIN_KEY_ENC_TAB = [ - 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 + 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 api_logger(func: Callable[..., Coroutine]): """接口日志装饰器""" + @functools.wraps(func) async def wrapper(*args, **kwargs): logger.debug(f"调用接口:'{func.__name__}'") @@ -95,14 +148,10 @@ def enc_wbi(self, params: UnsignedParams) -> SignedParams: """对 Params 进行签名""" params["wts"] = round(time.time()) # 添加 wts 字段 # 按照 key 重排参数 - params = dict( - sorted(params.items()) - ) + params = dict(sorted(params.items())) # 过滤 value 中的 "!'()*" 字符 params: dict = { - k: str(v).translate( - str.maketrans("", "", "!'()*") - ) + k: str(v).translate(str.maketrans("", "", "!'()*")) for k, v in params.items() } # 计算 w_rid @@ -130,20 +179,17 @@ async def refresh_wbi_keys(self) -> WbiKeys: api_url = "https://api.bilibili.com/x/web-interface/nav" headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" } - resp = await self._client.get( - url=api_url, - headers=headers - ) + resp = await self._client.get(url=api_url, headers=headers) resp.raise_for_status() # 提取签名密钥 wbi_img = resp.json()["data"]["wbi_img"] self._wbi_keys = WbiKeys( - img_key=(wbi_img["img_url"]).rsplit('/', 1)[1].split('.')[0], - sub_key=(wbi_img["sub_url"]).rsplit('/', 1)[1].split('.')[0] + img_key=(wbi_img["img_url"]).rsplit("/", 1)[1].split(".")[0], + sub_key=(wbi_img["sub_url"]).rsplit("/", 1)[1].split(".")[0], ) logger.success(f"已更新签名密钥:{self._wbi_keys}") return self._wbi_keys @@ -163,19 +209,16 @@ async def qrcode_generate(self) -> QRCodeLogin: api_url = "https://passport.bilibili.com/x/passport-login/web/qrcode/generate" headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" } - resp = await self._client.get( - url=api_url, - headers=headers - ) + resp = await self._client.get(url=api_url, headers=headers) resp.raise_for_status() if ( # 二次检查请求是否成功 resp_json := resp.json() ).get("code") != 0: - raise ValueError(f"申请二维码失败:{resp_json.get("message")}") + raise ValueError(f"申请二维码失败:{resp_json.get('message')}") # 处理数据 data: dict = resp_json["data"] @@ -183,7 +226,7 @@ async def qrcode_generate(self) -> QRCodeLogin: return QRCodeLogin( url=(url := data["url"]), qrcode_key=data["qrcode_key"], - qrcode_data=await self._gen_qrcode(url) + qrcode_data=await self._gen_qrcode(url), ) @api_logger @@ -195,28 +238,20 @@ async def qrcode_poll(self, qrcode_key: str) -> QRCodePoll: api_url = "https://passport.bilibili.com/x/passport-login/web/qrcode/poll" headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" } - params = { - "qrcode_key": qrcode_key - } - resp = await self._client.get( - url=api_url, - headers=headers, - params=params - ) + params = {"qrcode_key": qrcode_key} + resp = await self._client.get(url=api_url, headers=headers, params=params) resp.raise_for_status() if ( # 二次检查请求是否成功 - resp_json := resp.json() + resp_json := resp.json() ).get("code") != 0: - raise ValueError(f"查询扫码登录状态失败:{resp_json.get("message")}") + raise ValueError(f"查询扫码登录状态失败:{resp_json.get('message')}") # 处理数据 - poll_result = QRCodePoll.model_validate( - resp_json.get("data") - ) + poll_result = QRCodePoll.model_validate(resp_json.get("data")) # 当扫码登录成功时刷新 Cookies if poll_result.code == 0: logger.success("扫码登录成功") @@ -236,23 +271,18 @@ async def get_nav(self) -> Nav: api_url = "https://api.bilibili.com/x/web-interface/nav" headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" } - resp = await self._client.get( - url=api_url, - headers=headers - ) + resp = await self._client.get(url=api_url, headers=headers) resp.raise_for_status() if ( # 二次检查请求是否成功 - resp_json := resp.json() + resp_json := resp.json() ).get("code") != 0: - raise ValueError(f"获取用户信息失败:{resp_json.get("message")}") + raise ValueError(f"获取用户信息失败:{resp_json.get('message')}") - return Nav.model_validate( - resp_json.get("data") - ) + return Nav.model_validate(resp_json.get("data")) @property def has_cookies(self) -> bool: @@ -267,27 +297,19 @@ async def check_cookies(self) -> CheckCookiesResult: api_url = "https://passport.bilibili.com/x/passport-login/web/cookie/info" headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" } - params = { - "csrf": self._client.cookies.get("bili_jct") - } - resp = await self._client.get( - url=api_url, - headers=headers, - params=params - ) + params = {"csrf": self._client.cookies.get("bili_jct")} + resp = await self._client.get(url=api_url, headers=headers, params=params) resp.raise_for_status() if ( # 二次检查请求是否成功 - resp_json := resp.json() + resp_json := resp.json() ).get("code") != 0: - raise ValueError(f"检查 Cookies 可用性失败:{resp_json.get("message")}") + raise ValueError(f"检查 Cookies 可用性失败:{resp_json.get('message')}") - return CheckCookiesResult.model_validate( - resp_json.get("data") - ) + return CheckCookiesResult.model_validate(resp_json.get("data")) @api_logger async def _get_refresh_csrf(self, timestamp: int) -> RefreshCSRFToken: @@ -308,17 +330,14 @@ async def _get_refresh_csrf(self, timestamp: int) -> RefreshCSRFToken: api_url = f"https://www.bilibili.com/correspond/1/{correspond_path}" headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" } - resp = await self._client.get( - url=api_url, - headers=headers - ) + resp = await self._client.get(url=api_url, headers=headers) resp.raise_for_status() if not len( # 使用正则表达式从 HTML 中提取实时刷新口令 - results := re.findall('
(.+?)
', resp.text) + results := re.findall('
(.+?)
', resp.text) ): raise ValueError("从页面中提取实时刷新口令失败") @@ -333,33 +352,31 @@ async def refresh_cookies(self, timestamp: int): api_url = "https://passport.bilibili.com/x/passport-login/web/cookie/refresh" headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" } params = { "csrf": self._client.cookies.get("bili_jct"), "refresh_csrf": await self._get_refresh_csrf(timestamp), "source": "main_web", - "refresh_token": self._refresh_token + "refresh_token": self._refresh_token, } - resp = await self._client.post( - url=api_url, - headers=headers, - params=params - ) + resp = await self._client.post(url=api_url, headers=headers, params=params) resp.raise_for_status() if ( # 二次检查请求是否成功 - resp_json := resp.json() + resp_json := resp.json() ).get("code") != 0: - raise ValueError(f"刷新 Cookies 失败:{resp_json.get("message")}") + raise ValueError(f"刷新 Cookies 失败:{resp_json.get('message')}") # 刷新 Cookies logger.success("刷新 Cookies 成功") self._client.cookies = resp.cookies logger.debug("已更新 Cookies") # 存储新的持久化刷新令牌 - old_refresh_token = self._refresh_token # 将旧的持久化刷新令牌存储,以销毁旧的 Cookie + old_refresh_token = ( + self._refresh_token + ) # 将旧的持久化刷新令牌存储,以销毁旧的 Cookie self._refresh_token: str = resp.json()["data"]["refresh_token"] logger.debug("已更新持久化刷新令牌") @@ -373,24 +390,20 @@ async def _refresh_confirm(self, old_refresh_token: OldRefreshToken): api_url = "https://passport.bilibili.com/x/passport-login/web/confirm/refresh" headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" } params = { "csrf": self._client.cookies.get("bili_jct"), - "refresh_token": old_refresh_token + "refresh_token": old_refresh_token, } - resp = await self._client.post( - url=api_url, - headers=headers, - params=params - ) + resp = await self._client.post(url=api_url, headers=headers, params=params) resp.raise_for_status() if ( # 二次检查请求是否成功 - resp_json := resp.json() + resp_json := resp.json() ).get("code") != 0: - raise ValueError(f"确认 Cookies 更新失败:{resp_json.get("message")}") + raise ValueError(f"确认 Cookies 更新失败:{resp_json.get('message')}") logger.success("确认 Cookies 更新成功") return None @@ -404,27 +417,21 @@ async def get_video_details(self, bvid: str) -> VideoDetails: api_url = "https://api.bilibili.com/x/web-interface/wbi/view" headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" - } - params = { - "bvid": bvid + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" } + params = {"bvid": bvid} resp = await self._client.get( - url=api_url, - headers=headers, - params=self.enc_wbi(params) + url=api_url, headers=headers, params=self.enc_wbi(params) ) resp.raise_for_status() if ( # 二次检查请求是否成功 - resp_json := resp.json() + resp_json := resp.json() ).get("code") != 0: - raise ValueError(f"获取视频详细信息失败:{resp_json.get("message")}") + raise ValueError(f"获取视频详细信息失败:{resp_json.get('message')}") - return VideoDetails.model_validate( - resp_json["data"] - ) + return VideoDetails.model_validate(resp_json["data"]) @api_logger async def get_video_pagelist(self, bvid: str) -> list[VideoPage]: @@ -432,27 +439,19 @@ async def get_video_pagelist(self, bvid: str) -> list[VideoPage]: api_url = "https://api.bilibili.com/x/player/pagelist" headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" - } - params = { - "bvid": bvid + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" } - resp = await self._client.get( - url=api_url, - headers=headers, - params=params - ) + params = {"bvid": bvid} + resp = await self._client.get(url=api_url, headers=headers, params=params) resp.raise_for_status() if ( # 二次检查请求是否成功 - resp_json := resp.json() + resp_json := resp.json() ).get("code") != 0: - raise ValueError(f"获取视频分 P 列表失败:{resp_json.get("message")}") + raise ValueError(f"获取视频分 P 列表失败:{resp_json.get('message')}") - return [ - VideoPage.model_validate(item) for item in resp_json["data"] - ] + return [VideoPage.model_validate(item) for item in resp_json["data"]] @api_logger async def get_video_playurl(self, bvid: str, cid: int) -> Dash: @@ -463,26 +462,24 @@ async def get_video_playurl(self, bvid: str, cid: int) -> Dash: api_url = "https://api.bilibili.com/x/player/wbi/playurl" headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0", - "referer": "https://www.bilibili.com/" + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0", + "referer": "https://www.bilibili.com/", } params = { "bvid": bvid, "cid": cid, - "fnval": 16 | 256 # 需求 DASH 格式 + 杜比音频 + "fnval": 16 | 256, # 需求 DASH 格式 + 杜比音频 } resp = await self._client.get( - url=api_url, - headers=headers, - params=self.enc_wbi(params) + url=api_url, headers=headers, params=self.enc_wbi(params) ) resp.raise_for_status() if ( # 二次检查请求是否成功 - resp_json := resp.json() + resp_json := resp.json() ).get("code") != 0: - raise ValueError(f"获取视频流下载地址失败:{resp_json.get("message")}") + raise ValueError(f"获取视频流下载地址失败:{resp_json.get('message')}") # 处理数据 data: dict = resp_json["data"] diff --git a/src/AudioMaster/utils/api/bilibili/models/dash.py b/src/AudioMaster/utils/api/bilibili/models/dash.py index 75fb0b9..165b8d0 100644 --- a/src/AudioMaster/utils/api/bilibili/models/dash.py +++ b/src/AudioMaster/utils/api/bilibili/models/dash.py @@ -11,7 +11,7 @@ class AudioStreamDetails(BaseModel): "30232:132K" "30280:192K" "30250:杜比全景声" - "30251:Hi-Res无损" + "30251:Hi-Res无损", ] base_url: Annotated[str, "默认流 URL"] bandwidth: Annotated[int, " 所需最低带宽(单位为 Byte)"] @@ -27,13 +27,7 @@ class Dash(BaseModel): @field_validator("audio", mode="after") @classmethod def sort_stream_details(cls, details: list[AudioStreamDetails]): - priorities = { - 30251: 1, - 30250: 2, - 30280: 3, - 30232: 4, - 30216: 5 - } + priorities = {30251: 1, 30250: 2, 30280: 3, 30232: 4, 30216: 5} details.sort( # 不在列表中的放最后 key=lambda x: priorities.get(x.id, float("inf")) ) @@ -49,7 +43,4 @@ def check_flac_exists(cls, details: list[dict]): return False -__all__ = [ - "Dash", - "AudioStreamDetails" -] +__all__ = ["Dash", "AudioStreamDetails"] diff --git a/src/AudioMaster/utils/api/bilibili/models/nav.py b/src/AudioMaster/utils/api/bilibili/models/nav.py index 7512843..92f8de9 100644 --- a/src/AudioMaster/utils/api/bilibili/models/nav.py +++ b/src/AudioMaster/utils/api/bilibili/models/nav.py @@ -24,6 +24,4 @@ class Nav(BaseModel): vip_label: Annotated[VIPLabel, "会员信息"] -__all__ = [ - "Nav" -] +__all__ = ["Nav"] diff --git a/src/AudioMaster/utils/api/bilibili/models/qr_code.py b/src/AudioMaster/utils/api/bilibili/models/qr_code.py index 2c9213c..50c65a5 100644 --- a/src/AudioMaster/utils/api/bilibili/models/qr_code.py +++ b/src/AudioMaster/utils/api/bilibili/models/qr_code.py @@ -20,12 +20,9 @@ class QRCodePoll(BaseModel): 86038:二维码已失效 86090:二维码已扫码未确认 86101:未扫码 - """ + """, ] message: Annotated[str, "扫码状态信息"] -__all__ = [ - "QRCodeLogin", - "QRCodePoll" -] +__all__ = ["QRCodeLogin", "QRCodePoll"] diff --git a/src/AudioMaster/utils/api/bilibili/models/refresh_cookies.py b/src/AudioMaster/utils/api/bilibili/models/refresh_cookies.py index a1d3d8c..c07b880 100644 --- a/src/AudioMaster/utils/api/bilibili/models/refresh_cookies.py +++ b/src/AudioMaster/utils/api/bilibili/models/refresh_cookies.py @@ -8,6 +8,4 @@ class CheckCookiesResult(BaseModel): timestamp: Annotated[int, "当前毫秒时间戳(用于获取 `refresh_csrf`)"] -__all__ = [ - "CheckCookiesResult" -] +__all__ = ["CheckCookiesResult"] diff --git a/src/AudioMaster/utils/api/bilibili/models/video.py b/src/AudioMaster/utils/api/bilibili/models/video.py index 2d6a3e1..6fc5cc6 100644 --- a/src/AudioMaster/utils/api/bilibili/models/video.py +++ b/src/AudioMaster/utils/api/bilibili/models/video.py @@ -4,12 +4,7 @@ class VideoRights(BaseModel): - download: Annotated[ - int, - "是否允许下载:" - "1:是" - "0:否" - ] + download: Annotated[int, "是否允许下载:1:是0:否"] class VideoOwner(BaseModel): @@ -23,12 +18,7 @@ class VideoDetails(BaseModel): aid: Annotated[int, "稿件 AV 号"] videos: Annotated[int, "视频分 P 总数"] owner: Annotated[VideoOwner, "视频作者"] - copyright: Annotated[ - int, - "视频转载类型:" - "1:原创" - "2:转载" - ] + copyright: Annotated[int, "视频转载类型:1:原创2:转载"] pic: Annotated[str, "稿件封面图片 URL"] title: Annotated[str, "稿件标题"] pubdate: Annotated[int, "稿件发布时间(秒级时间戳)"] @@ -46,7 +36,4 @@ class VideoPage(BaseModel): ctime: Annotated[int, "用户投稿时间(秒级时间戳)"] -__all__ = [ - "VideoDetails", - "VideoPage" -] +__all__ = ["VideoDetails", "VideoPage"] diff --git a/src/AudioMaster/utils/api/bilibili/models/wbi_key.py b/src/AudioMaster/utils/api/bilibili/models/wbi_key.py index e819573..4863a3e 100644 --- a/src/AudioMaster/utils/api/bilibili/models/wbi_key.py +++ b/src/AudioMaster/utils/api/bilibili/models/wbi_key.py @@ -3,10 +3,9 @@ class WbiKeys(BaseModel): """B 站 wbi 签名算法必要的两个关键参数""" + img_key: str sub_key: str -__all__ = [ - "WbiKeys" -] +__all__ = ["WbiKeys"] diff --git a/src/AudioMaster/utils/api/bilibili/typing.py b/src/AudioMaster/utils/api/bilibili/typing.py index 50bbc0a..5aa69cd 100644 --- a/src/AudioMaster/utils/api/bilibili/typing.py +++ b/src/AudioMaster/utils/api/bilibili/typing.py @@ -13,5 +13,5 @@ "UnsignedParams", "MixinKey", "RefreshCSRFToken", - "OldRefreshToken" + "OldRefreshToken", ] diff --git a/src/AudioMaster/utils/api/bilibili/utils.py b/src/AudioMaster/utils/api/bilibili/utils.py index d4433c3..1eb4a24 100644 --- a/src/AudioMaster/utils/api/bilibili/utils.py +++ b/src/AudioMaster/utils/api/bilibili/utils.py @@ -4,7 +4,7 @@ def rsa_public_key() -> bytes: """从文件中读取 RSA 公钥""" with ( # 从路径中读取公钥文件 - Path(__file__).parent/"resources"/"public-key.pem" + Path(__file__).parent / "resources" / "public-key.pem" ).open("rb") as f: return f.read() @@ -28,7 +28,9 @@ def avid_to_bvid(avid: int) -> str: bvid = [""] * 9 tmp = (VideoIDConverter.MAX_AID | avid) ^ VideoIDConverter.XOR_CODE for i in range(VideoIDConverter.CODE_LEN): - bvid[VideoIDConverter.ENCODE_MAP[i]] = VideoIDConverter.ALPHABET[tmp % VideoIDConverter.BASE] + bvid[VideoIDConverter.ENCODE_MAP[i]] = VideoIDConverter.ALPHABET[ + tmp % VideoIDConverter.BASE + ] tmp //= VideoIDConverter.BASE return VideoIDConverter.PREFIX + "".join(bvid) @@ -45,7 +47,4 @@ def bvid_to_avid(bvid: str) -> int: return (tmp & VideoIDConverter.MASK_CODE) ^ VideoIDConverter.XOR_CODE -__all__ = [ - "rsa_public_key", - "VideoIDConverter" -] +__all__ = ["rsa_public_key", "VideoIDConverter"] diff --git a/src/AudioMaster/utils/api/deepseek/main.py b/src/AudioMaster/utils/api/deepseek/main.py index 506c81b..da3e440 100644 --- a/src/AudioMaster/utils/api/deepseek/main.py +++ b/src/AudioMaster/utils/api/deepseek/main.py @@ -7,16 +7,14 @@ from AudioMaster.config import Config from .models.balance import Balance -from .models.chat import ( - ChatRequest, Message, ResponseFormat, - ChatResponse -) +from .models.chat import ChatRequest, Message, ResponseFormat, ChatResponse PLUGIN_CONFIG = get_plugin_config(Config) def api_logger(func: Callable[..., Coroutine]): """接口日志装饰器""" + @functools.wraps(func) async def wrapper(*args, **kwargs): logger.debug(f"[DeepSeek] 调用接口:'{func.__name__}'") @@ -31,6 +29,7 @@ async def wrapper(*args, **kwargs): logger.debug(f"[DeepSeek] 接口返回值:{repr(result)}") return result + return wrapper @@ -59,16 +58,10 @@ async def get_balance(self) -> Balance: "Accept": "application/json", "Authorization": f"Bearer {self._api_key}", } - resp = await self._client.get( - url=api_url, - headers=headers, - timeout=8 - ) + resp = await self._client.get(url=api_url, headers=headers, timeout=8) resp.raise_for_status() - return Balance.model_validate( - resp.json() - ) + return Balance.model_validate(resp.json()) @api_logger async def chat(self, request: ChatRequest) -> ChatResponse: @@ -79,22 +72,22 @@ async def chat(self, request: ChatRequest) -> ChatResponse: headers = { "Content-Type": "application/json", "Accept": "application/json", - "Authorization": f"Bearer {self._api_key}" + "Authorization": f"Bearer {self._api_key}", } resp = await self._client.post( url=api_url, headers=headers, json=request.model_dump(), - timeout=httpx.Timeout(12, read=20, connect=15) + timeout=httpx.Timeout(12, read=20, connect=15), ) resp.raise_for_status() - return ChatResponse.model_validate( - resp.json() - ) + return ChatResponse.model_validate(resp.json()) __all__ = [ "DeepSeekAPI", - "ChatRequest", "Message", "ResponseFormat", + "ChatRequest", + "Message", + "ResponseFormat", ] diff --git a/src/AudioMaster/utils/api/deepseek/models/chat.py b/src/AudioMaster/utils/api/deepseek/models/chat.py index 1e8416e..6452422 100644 --- a/src/AudioMaster/utils/api/deepseek/models/chat.py +++ b/src/AudioMaster/utils/api/deepseek/models/chat.py @@ -21,15 +21,18 @@ class StreamOptions(BaseModel): class ChatRequest(BaseModel): """对话补全的请求""" + messages: Annotated[list[Message], "对话的消息列表"] model: Annotated[str, "使用的模型的ID"] - frequency_penalty: Annotated[float, "用于操作新生成的token在已有文本重复的频率, 介于 -2.0 和 2.0 之间"] = 0 + frequency_penalty: Annotated[ + float, "用于操作新生成的token在已有文本重复的频率, 介于 -2.0 和 2.0 之间" + ] = 0 max_tokens: Annotated[int, "用于限制生成文本长度, 介于 1 和 8192 之间"] = 4096 - presence_penalty: Annotated[float, "用于操作新生成的token谈论新主题的可能性, 介于 -2.0 和 2.0 之间"] = 0 + presence_penalty: Annotated[ + float, "用于操作新生成的token谈论新主题的可能性, 介于 -2.0 和 2.0 之间" + ] = 0 response_format: ResponseFormat = ResponseFormat() - stop: Annotated[ - Optional[list[str]], "自定义的屏蔽词, 最多16个" - ] = None + stop: Annotated[Optional[list[str]], "自定义的屏蔽词, 最多16个"] = None stream: Annotated[bool, "是否使用流式传输数据"] = False temperature: Annotated[float, "采样温度, 介于 0 和 2 之间"] = 1.0 # stream_options: Annotated[Optional[StreamOptions], "流式输出相关选项, 'stream' 为 True 时才可设置"] = None @@ -39,19 +42,24 @@ class Choice(BaseModel): index: Annotated[int, "该对话在所有生成对话中的选择列表中的索引"] message: Annotated[Message, "模型生成的对话消息"] finish_reason: Annotated[ - Literal["stop", "length", "content_filter", "tool_calls", "insufficient_system_resource"], "模型停止生成 token 的原因" + Literal[ + "stop", + "length", + "content_filter", + "tool_calls", + "insufficient_system_resource", + ], + "模型停止生成 token 的原因", ] class ChatResponse(BaseModel): """对话补全的返回值""" + id: Annotated[str, "该对话的唯一标识符"] choices: Annotated[list[Choice], "模型生成对话的选择列表"] created: Annotated[int, "创建聊天完成时的 Unix 时间戳(以秒为单位)"] model: Annotated[str, "生成该对话的模型名"] -__all__ = [ - "ChatRequest", - "ChatResponse", "Message", "ResponseFormat" -] +__all__ = ["ChatRequest", "ChatResponse", "Message", "ResponseFormat"] diff --git a/src/AudioMaster/utils/commands/auto.py b/src/AudioMaster/utils/commands/auto.py index 33dac4c..29f4cbe 100644 --- a/src/AudioMaster/utils/commands/auto.py +++ b/src/AudioMaster/utils/commands/auto.py @@ -1,13 +1,7 @@ from pathlib import Path -from nonebot.adapters.onebot.v11 import ( - MessageEvent, - GroupMessageEvent -) -from nonebot_plugin_alconna import ( - UniMessage, - At, Text, Image, File -) +from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent +from nonebot_plugin_alconna import UniMessage, At, Text, Image, File from AudioMaster.db import EMOJIS_PATH @@ -18,19 +12,12 @@ def bvid_format_error(event: MessageEvent) -> UniMessage: msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=user_id), - Text("\n") - ] + msgs += [At(flag="user", target=user_id), Text("\n")] # 构建消息 msgs += [ - Text( - "输入的 BV 号格式不正确哦...\n" - ), - Image( - path=EMOJIS_PATH/"9-shock.png" - ) + Text("输入的 BV 号格式不正确哦...\n"), + Image(path=EMOJIS_PATH / "9-shock.png"), ] return UniMessage(msgs) @@ -41,19 +28,12 @@ def page_input_error(event: MessageEvent) -> UniMessage: msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=user_id), - Text("\n") - ] + msgs += [At(flag="user", target=user_id), Text("\n")] # 构建消息 msgs += [ - Text( - "输入的视频分P是无效的哦...\n" - ), - Image( - path=EMOJIS_PATH/"9-shock.png" - ) + Text("输入的视频分P是无效的哦...\n"), + Image(path=EMOJIS_PATH / "9-shock.png"), ] return UniMessage(msgs) @@ -64,26 +44,16 @@ def show_cinfo(event: MessageEvent, card: bytes) -> UniMessage: msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=user_id), - Text("\n") - ] + msgs += [At(flag="user", target=user_id), Text("\n")] # 构建消息 - msgs += [ - Text( - "已自动解析结果,正在转换中...\n" - ), - Image(raw=card) - ] + msgs += [Text("已自动解析结果,正在转换中...\n"), Image(raw=card)] return UniMessage(msgs) def file(file_path: Path) -> UniMessage: """传输文件""" - return UniMessage( - File(path=file_path) - ) + return UniMessage(File(path=file_path)) def upload_success(event: MessageEvent) -> UniMessage: @@ -92,20 +62,10 @@ def upload_success(event: MessageEvent) -> UniMessage: msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=user_id), - Text("\n") - ] + msgs += [At(flag="user", target=user_id), Text("\n")] # 构建消息 - msgs += [ - Text( - "桜铃的任务完成啦~!\n" - ), - Image( - path=EMOJIS_PATH/"7-ok.png" - ) - ] + msgs += [Text("桜铃的任务完成啦~!\n"), Image(path=EMOJIS_PATH / "7-ok.png")] return UniMessage(msgs) @@ -114,5 +74,5 @@ def upload_success(event: MessageEvent) -> UniMessage: "page_input_error", "show_cinfo", "file", - "upload_success" + "upload_success", ] diff --git a/src/AudioMaster/utils/commands/balance.py b/src/AudioMaster/utils/commands/balance.py index 005e307..76cab72 100644 --- a/src/AudioMaster/utils/commands/balance.py +++ b/src/AudioMaster/utils/commands/balance.py @@ -1,8 +1,5 @@ from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent -from nonebot_plugin_alconna import ( - UniMessage, - At, Text -) +from nonebot_plugin_alconna import UniMessage, At, Text from AudioMaster.utils.api.deepseek.models.balance import Balance @@ -13,10 +10,7 @@ def balance_reply(event: MessageEvent, balance: Balance) -> UniMessage: msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=user_id), - Text("\n") - ] + msgs += [At(flag="user", target=user_id), Text("\n")] # 构建消息 msgs += [ @@ -27,13 +21,8 @@ def balance_reply(event: MessageEvent, balance: Balance) -> UniMessage: ] # 余额状态 for info in balance.balance_infos: - msgs += [ - Text( - "\n" - f"——账户余额:{info.total_balance} {info.currency}" - ) - ] + msgs += [Text(f"\n——账户余额:{info.total_balance} {info.currency}")] return UniMessage(msgs) -__all__ = ["balance_reply"] \ No newline at end of file +__all__ = ["balance_reply"] diff --git a/src/AudioMaster/utils/commands/login.py b/src/AudioMaster/utils/commands/login.py index 029f4ce..4192776 100644 --- a/src/AudioMaster/utils/commands/login.py +++ b/src/AudioMaster/utils/commands/login.py @@ -1,11 +1,5 @@ -from nonebot.adapters.onebot.v11 import ( - MessageEvent, - GroupMessageEvent -) -from nonebot_plugin_alconna import ( - UniMessage, - At, Text, Image -) +from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent +from nonebot_plugin_alconna import UniMessage, At, Text, Image from AudioMaster.db import EMOJIS_PATH @@ -16,35 +10,25 @@ def only_refresh_cookies(event: MessageEvent) -> UniMessage: msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=user_id), - Text("\n") - ] + msgs += [At(flag="user", target=user_id), Text("\n")] # 构建消息 msgs += [ - Text( - "当前登录已过期,但可重新自动登录!\n" - "需要桜铃帮你登录吗?\n" - "(是/否)\n" - ), - Image( - path=EMOJIS_PATH/"4-good.png" - ) + Text("当前登录已过期,但可重新自动登录!\n需要桜铃帮你登录吗?\n(是/否)\n"), + Image(path=EMOJIS_PATH / "4-good.png"), ] return UniMessage(msgs) -def action_failed(event: MessageEvent, action_name: str, exception: Exception) -> UniMessage: +def action_failed( + event: MessageEvent, action_name: str, exception: Exception +) -> UniMessage: """操作失败""" user_id = event.get_user_id() msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=user_id), - Text("\n") - ] + msgs += [At(flag="user", target=user_id), Text("\n")] # 构建消息 msgs += [ @@ -53,9 +37,7 @@ def action_failed(event: MessageEvent, action_name: str, exception: Exception) - f"{action_name}失败:{exception.__class__.__name__}\n" "(详细请查看控制台)\n" ), - Image( - path=EMOJIS_PATH/"8-sorry.png" - ) + Image(path=EMOJIS_PATH / "8-sorry.png"), ] return UniMessage(msgs) @@ -66,20 +48,13 @@ def login_succeed(event: MessageEvent, card: bytes) -> UniMessage: msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=user_id), - Text("\n") - ] + msgs += [At(flag="user", target=user_id), Text("\n")] # 构建消息 msgs += [ - Text( - "登录成功啦~看!\n" - ), + Text("登录成功啦~看!\n"), Image(raw=card), - Image( - path=EMOJIS_PATH/"7-ok.png" - ), + Image(path=EMOJIS_PATH / "7-ok.png"), ] return UniMessage(msgs) @@ -90,21 +65,12 @@ def re_login(event: MessageEvent) -> UniMessage: msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=user_id), - Text("\n") - ] + msgs += [At(flag="user", target=user_id), Text("\n")] # 构建消息 msgs += [ - Text( - "当前已经登录过了~\n" - "需要桜铃帮你登录其他账号吗?\n" - "(是/否)\n" - ), - Image( - path=EMOJIS_PATH/"14-question-text.png" - ) + Text("当前已经登录过了~\n需要桜铃帮你登录其他账号吗?\n(是/否)\n"), + Image(path=EMOJIS_PATH / "14-question-text.png"), ] return UniMessage(msgs) @@ -115,18 +81,12 @@ def login_qrcode(event: MessageEvent, qrcode: bytes) -> UniMessage: msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=user_id), - Text("\n") - ] + msgs += [At(flag="user", target=user_id), Text("\n")] # 构建消息 msgs += [ - Text( - "请打开“哔哩哔哩”客户端,\n" - "扫描以下二维码进行登录:\n" - ), - Image(raw=qrcode) + Text("请打开“哔哩哔哩”客户端,\n扫描以下二维码进行登录:\n"), + Image(raw=qrcode), ] return UniMessage(msgs) @@ -137,19 +97,12 @@ def prompt_confirm_login(event: MessageEvent) -> UniMessage: msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=user_id), - Text("\n") - ] + msgs += [At(flag="user", target=user_id), Text("\n")] # 构建消息 msgs += [ - Text( - "桜铃看见你扫码了,请尽快确认登录哦~\n" - ), - Image( - path=EMOJIS_PATH/"4-good.png" - ) + Text("桜铃看见你扫码了,请尽快确认登录哦~\n"), + Image(path=EMOJIS_PATH / "4-good.png"), ] return UniMessage(msgs) @@ -160,5 +113,5 @@ def prompt_confirm_login(event: MessageEvent) -> UniMessage: "login_succeed", "re_login", "login_qrcode", - "prompt_confirm_login" + "prompt_confirm_login", ] diff --git a/src/AudioMaster/utils/commands/logout.py b/src/AudioMaster/utils/commands/logout.py index 8ddd232..9340017 100644 --- a/src/AudioMaster/utils/commands/logout.py +++ b/src/AudioMaster/utils/commands/logout.py @@ -1,11 +1,5 @@ -from nonebot.adapters.onebot.v11 import ( - MessageEvent, - GroupMessageEvent -) -from nonebot_plugin_alconna import ( - UniMessage, - At, Text, Image -) +from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent +from nonebot_plugin_alconna import UniMessage, At, Text, Image from AudioMaster.db import EMOJIS_PATH @@ -16,23 +10,11 @@ def logout_succeed(event: MessageEvent) -> UniMessage: msgs = [] # 对群的消息结构作特殊处理 if isinstance(event, GroupMessageEvent): - msgs += [ - At(flag="user", target=user_id), - Text("\n") - ] + msgs += [At(flag="user", target=user_id), Text("\n")] # 构建消息 - msgs += [ - Text( - "已登出当前账号~\n" - ), - Image( - path=EMOJIS_PATH/"7-ok.png" - ) - ] + msgs += [Text("已登出当前账号~\n"), Image(path=EMOJIS_PATH / "7-ok.png")] return UniMessage(msgs) -__all__ = [ - "logout_succeed" -] +__all__ = ["logout_succeed"] diff --git a/src/AudioMaster/utils/drawer/db.py b/src/AudioMaster/utils/drawer/db.py index 869e779..9bcf075 100644 --- a/src/AudioMaster/utils/drawer/db.py +++ b/src/AudioMaster/utils/drawer/db.py @@ -1,11 +1,11 @@ from pathlib import Path -RESOURCES_PATH = Path(__file__).parent/"resources" -FONTS_PATH = RESOURCES_PATH/"fonts" -ICONS_PATH = RESOURCES_PATH/"icons" -TEMPLATES_PATH = RESOURCES_PATH/"templates" -LEVELS_PATH = ICONS_PATH/"levels" +RESOURCES_PATH = Path(__file__).parent / "resources" +FONTS_PATH = RESOURCES_PATH / "fonts" +ICONS_PATH = RESOURCES_PATH / "icons" +TEMPLATES_PATH = RESOURCES_PATH / "templates" +LEVELS_PATH = ICONS_PATH / "levels" __all__ = [ @@ -13,5 +13,5 @@ "FONTS_PATH", "ICONS_PATH", "LEVELS_PATH", - "TEMPLATES_PATH" + "TEMPLATES_PATH", ] diff --git a/src/AudioMaster/utils/drawer/main.py b/src/AudioMaster/utils/drawer/main.py index fef1f84..6b165b8 100644 --- a/src/AudioMaster/utils/drawer/main.py +++ b/src/AudioMaster/utils/drawer/main.py @@ -2,18 +2,11 @@ import math from typing import Optional, Literal -from PIL import ( - Image, - ImageDraw, ImageFont -) +from PIL import Image, ImageDraw, ImageFont from nonebot.utils import run_sync from nonebot.log import logger -from .db import ( - FONTS_PATH, - LEVELS_PATH, - TEMPLATES_PATH -) +from .db import FONTS_PATH, LEVELS_PATH, TEMPLATES_PATH from .typing import Size, CinfoCard @@ -26,27 +19,17 @@ def round_avatar(raw_avatar: bytes) -> Image.Image: ).convert("RGBA") # 添加透明圆形蒙版 - mask = Image.new( - mode="L", - size=avatar.size, - color=0 - ) + mask = Image.new(mode="L", size=avatar.size, color=0) draw = ImageDraw.Draw(mask) - draw.ellipse( - xy=( - (0, 0), - avatar.size - ), - fill=255 - ) + draw.ellipse(xy=((0, 0), avatar.size), fill=255) avatar.putalpha(mask) return avatar @staticmethod def resize_avatar( - avatar: Image.Image, - avatar_resize: Size = (300, 300), - total_size: Size = (500, 500) + avatar: Image.Image, + avatar_resize: Size = (300, 300), + total_size: Size = (500, 500), ) -> Image.Image: """ 按一定比例缩放头像 @@ -56,33 +39,25 @@ def resize_avatar( """ avatar = avatar.convert("RGBA") # 高质量缩放 - avatar = avatar.resize( - size=avatar_resize, - resample=Image.Resampling.LANCZOS - ) + avatar = avatar.resize(size=avatar_resize, resample=Image.Resampling.LANCZOS) # 添加圆形透明蒙版 - mask = Image.new( - mode="L", - size=avatar_resize, - color=0 - ) + mask = Image.new(mode="L", size=avatar_resize, color=0) draw = ImageDraw.Draw(mask) draw.ellipse( xy=( - (0, 0,), - (avatar_resize[0] - 1, avatar_resize[1] - 1) + ( + 0, + 0, + ), + (avatar_resize[0] - 1, avatar_resize[1] - 1), ), - fill=255 + fill=255, ) avatar.putalpha(mask) # 创建透明画布 - canvas = Image.new( - mode="RGBA", - size=total_size, - color=0 - ) + canvas = Image.new(mode="RGBA", size=total_size, color=0) # 合并图像 canvas.paste( @@ -90,16 +65,13 @@ def resize_avatar( mask=avatar, box=( # 居中粘贴 (total_size[0] - avatar_resize[0]) // 2, - (total_size[1] - avatar_resize[1]) // 2 - ) + (total_size[1] - avatar_resize[1]) // 2, + ), ) return canvas @staticmethod - def add_frame( - avatar: Image.Image, - raw_frame: bytes - ) -> Image.Image: + def add_frame(avatar: Image.Image, raw_frame: bytes) -> Image.Image: """ 添加头像框 :param avatar: 头像图片 @@ -116,36 +88,28 @@ def add_frame( frame = frame.resize( size=( # 计算新尺寸 int(frame.width * base_scale), - int(frame.height * base_scale) + int(frame.height * base_scale), ), - resample=Image.Resampling.LANCZOS + resample=Image.Resampling.LANCZOS, ) # 创建画布并合成图像 - new_image = Image.new( - mode="RGBA", - size=avatar.size - ) - new_image.paste( - im=avatar, - box=(0, 0) - ) + new_image = Image.new(mode="RGBA", size=avatar.size) + new_image.paste(im=avatar, box=(0, 0)) # 叠加头像框 new_image.alpha_composite( im=frame, dest=( # 计算居中坐标 (avatar.width - frame.width) // 2, - (avatar.height - frame.height) // 2 - ) + (avatar.height - frame.height) // 2, + ), ) return new_image @staticmethod def draw_name( - user_name: str, - font_size: int = 150, - color: str = "#FB7299" + user_name: str, font_size: int = 150, color: str = "#FB7299" ) -> Image.Image: """ 绘制用户名称(大于 7 长度的用户名会被截断) @@ -154,45 +118,37 @@ def draw_name( :param color: RGB十六进制颜色代码 """ if ( # 截断长度大于 7 的用户名 - name_length := len(user_name) + name_length := len(user_name) ) > 7: user_name = user_name[:7] + "..." # 加载字体 - font = ImageFont.truetype( - font=FONTS_PATH/"MiSans-Medium.ttf", - size=font_size - ) + font = ImageFont.truetype(font=FONTS_PATH / "MiSans-Medium.ttf", size=font_size) # 创建透明背景画布 image = Image.new( - mode='RGBA', + mode="RGBA", size=( # 根据字体长度扩大透明画布宽度, 高度为原来的两倍,防止字体超出画布 - (font_size * name_length), (font_size * 2) + (font_size * name_length), + (font_size * 2), ), - color=0 + color=0, ) draw = ImageDraw.Draw(image) # 绘制文字 draw.text( - xy=(0, 0), - text=user_name, - font=font, - fill=color if len(color) else "black" + xy=(0, 0), text=user_name, font=font, fill=color if len(color) else "black" ) # 裁剪多余的透明部分 bbox = image.getbbox(alpha_only=True) image = image.crop(bbox) image = image.resize( size=(math.ceil(image.width / 100) * 100, 150), # 使结果为宽度整百图像 - resample=Image.Resampling.LANCZOS + resample=Image.Resampling.LANCZOS, ) return image @staticmethod - def resize_level( - level: int, - size: Size = (160, 80) - ) -> Image.Image: + def resize_level(level: int, size: Size = (160, 80)) -> Image.Image: """ 扩大等级图标 :param level: 用户等级 @@ -200,23 +156,13 @@ def resize_level( """ assert 0 <= level <= 6, f"出现非法的用户等级:{level}" - level = Image.open( - LEVELS_PATH/f"user-level_{level}-256.png" - ).convert("RGBA") - level = level.crop( - box=level.getbbox(alpha_only=True) - ) - level = level.resize( - size=size, - resample=Image.Resampling.LANCZOS - ) + level = Image.open(LEVELS_PATH / f"user-level_{level}-256.png").convert("RGBA") + level = level.crop(box=level.getbbox(alpha_only=True)) + level = level.resize(size=size, resample=Image.Resampling.LANCZOS) return level @staticmethod - def resize_vip_icon( - raw_vip_icon: bytes, - factor: float = 1.5 - ) -> Image.Image: + def resize_vip_icon(raw_vip_icon: bytes, factor: float = 1.5) -> Image.Image: """ 放大 VIP 图标 :param raw_vip_icon: VIP 图标的字节数据 @@ -227,7 +173,7 @@ def resize_vip_icon( ).convert("RGBA") vip_icon = vip_icon.resize( size=(int(vip_icon.width * factor), int(vip_icon.height * factor)), - resample=Image.Resampling.LANCZOS + resample=Image.Resampling.LANCZOS, ) return vip_icon @@ -246,9 +192,7 @@ def cut_square(raw_cover: bytes, total_size: Size = (512, 512)) -> Image.Image: top = 0 if result else ((cover.height - cover.width) // 2) bottom = cover.height if result else (top + cover.width) # 裁剪 - cropped_cover = cover.crop( - box=(left, top, right, bottom) - ) + cropped_cover = cover.crop(box=(left, top, right, bottom)) # 调整到目标尺寸 resized_cover = cropped_cover.resize(total_size, Image.Resampling.LANCZOS) return resized_cover @@ -258,21 +202,14 @@ def template_add_cover(resized_cover: Image.Image) -> Image.Image: """将裁剪后的封面作为专辑封面,移动至图片模板上""" assert resized_cover.size == (512, 512), "专辑封面图片的尺寸必须为 512x512" - template = Image.open( - TEMPLATES_PATH/"audio_info.png" - ).convert("RGBA") + template = Image.open(TEMPLATES_PATH / "audio_info.png").convert("RGBA") # 将专辑封面图放在指定区域上 - template.paste( - resized_cover, - box=(23, 138, 535, 650) - ) + template.paste(resized_cover, box=(23, 138, 535, 650)) return template @staticmethod def add_texts( - template: Image.Image, - text: str, - location: Literal[1, 2, 3, 4] + template: Image.Image, text: str, location: Literal[1, 2, 3, 4] ) -> Image.Image: """ 添加文本 @@ -281,10 +218,7 @@ def add_texts( :param location: 需要添加的位置选项 """ # 加载字体 - font = ImageFont.truetype( - font=FONTS_PATH/"MiSans-Medium.ttf", - size=36 - ) + font = ImageFont.truetype(font=FONTS_PATH / "MiSans-Medium.ttf", size=36) draw = ImageDraw.Draw(template) # 选择绘制位置 match location: @@ -303,7 +237,7 @@ def add_texts( xy, # noqa text=text, fill=(47, 47, 47), - font=font + font=font, ) return template @@ -312,11 +246,11 @@ class Drawer: @staticmethod @run_sync def draw_user_card( - raw_avatar: bytes, - raw_vip_icon: bytes, - user_name: str, - user_level: int, - raw_frame: Optional[bytes] = None + raw_avatar: bytes, + raw_vip_icon: bytes, + user_name: str, + user_level: int, + raw_frame: Optional[bytes] = None, ) -> bytes: """ 绘制用户卡片 @@ -328,13 +262,9 @@ def draw_user_card( """ logger.info(f"绘制用户(ID:{user_name})的个人卡片") # 裁剪成圆形头像并缩放,适配可能存在的头像框 - avatar = UserCardDrawer.resize_avatar( - UserCardDrawer.round_avatar(raw_avatar) - ) + avatar = UserCardDrawer.resize_avatar(UserCardDrawer.round_avatar(raw_avatar)) if raw_frame is not None: # 为头像添加头像框 - avatar = UserCardDrawer.add_frame( - avatar, raw_frame - ) + avatar = UserCardDrawer.add_frame(avatar, raw_frame) # 绘制用户名 name = UserCardDrawer.draw_name(user_name) @@ -349,71 +279,48 @@ def draw_user_card( total_width = width if width > min_width else min_width # 创建新的透明画布 - new_image = Image.new( - mode="RGBA", - size=( - total_width, - avatar.height - ), - color=0 - ) + new_image = Image.new(mode="RGBA", size=(total_width, avatar.height), color=0) # 粘贴头像 - new_image.paste( - im=avatar, - box=(0, 0, avatar.width, avatar.height) - ) + new_image.paste(im=avatar, box=(0, 0, avatar.width, avatar.height)) # 粘贴用户名并保持居中 name_offsets = 120 # 名称纵向偏移量 x1 = int(avatar.width + (new_image.width - avatar.width - name.width) / 2) new_image.paste( im=name, - box=( - x1, name_offsets, - x1 + name.width, (name.height + name_offsets) - ) + box=(x1, name_offsets, x1 + name.width, (name.height + name_offsets)), ) # 粘贴等级图标 level_offsets = 300 # 等级纵向偏移量 new_image.paste( im=level, box=( - avatar.width, level_offsets, - (avatar.width + level.width), (level_offsets + level.height) - ) + avatar.width, + level_offsets, + (avatar.width + level.width), + (level_offsets + level.height), + ), ) # 粘贴 VIP 图标 vip_icon_offsets = 295 # 会员纵向偏移量 x1, y1 = (avatar.width + level.width + 20), vip_icon_offsets new_image.paste( - im=vip_icon, - box=( - x1, y1, - (x1 + vip_icon.width), (y1 + vip_icon.height) - ) + im=vip_icon, box=(x1, y1, (x1 + vip_icon.width), (y1 + vip_icon.height)) ) # 绘制白色圆角矩形 - round_rectangle = Image.new( - mode="RGBA", - size=new_image.size, - color=0 - ) + round_rectangle = Image.new(mode="RGBA", size=new_image.size, color=0) draw = ImageDraw.Draw(round_rectangle) draw.rounded_rectangle( xy=( # 坐标从左上角 (0,0) 到右下角 (width - 1, height - 1) (0, 0), - (new_image.width - 1, new_image.height - 1) + (new_image.width - 1, new_image.height - 1), ), radius=50, - fill="white" + fill="white", ) # 粘贴矩形,以卡片为蒙版 - round_rectangle.paste( - im=new_image, - box=(0, 0), - mask=new_image - ) + round_rectangle.paste(im=new_image, box=(0, 0), mask=new_image) # 保存图片,转换为字节数据 with io.BytesIO() as f: @@ -423,11 +330,11 @@ def draw_user_card( @staticmethod @run_sync def draw_cinfo_card( - raw_cover: bytes, - title: str, - artists: list[str], - audio_formats: str, - durations: str + raw_cover: bytes, + title: str, + artists: list[str], + audio_formats: str, + durations: str, ) -> CinfoCard: """ 绘制转换信息卡片 @@ -437,6 +344,7 @@ def draw_cinfo_card( :param audio_formats: 音频格式信息 :param durations: 音频总时长 """ + def cut_text(text: str) -> str: """截断过长的文本""" return f"{text[:18]}..." if len(text) > 18 else text @@ -447,18 +355,12 @@ def cut_text(text: str) -> str: cover := CInfoDrawer.cut_square(raw_cover) ) # 添加文字 - image = CInfoDrawer.add_texts( - image, text=cut_text(title), location=1 - ) + image = CInfoDrawer.add_texts(image, text=cut_text(title), location=1) image = CInfoDrawer.add_texts( image, text=cut_text("、".join(artists)), location=2 ) - image = CInfoDrawer.add_texts( - image, text=cut_text(audio_formats), location=3 - ) - image = CInfoDrawer.add_texts( - image, text=cut_text(durations), location=4 - ) + image = CInfoDrawer.add_texts(image, text=cut_text(audio_formats), location=3) + image = CInfoDrawer.add_texts(image, text=cut_text(durations), location=4) # 存储卡片数据 with io.BytesIO() as f: diff --git a/src/AudioMaster/utils/drawer/typing.py b/src/AudioMaster/utils/drawer/typing.py index 38a5eaf..355a297 100644 --- a/src/AudioMaster/utils/drawer/typing.py +++ b/src/AudioMaster/utils/drawer/typing.py @@ -11,7 +11,4 @@ class CinfoCard(BaseModel): cover: bytes -__all__ = [ - "Size", - "CinfoCard" -] +__all__ = ["Size", "CinfoCard"] diff --git a/src/AudioMaster/utils/ffmpeg/main.py b/src/AudioMaster/utils/ffmpeg/main.py index 7be8584..3b4356f 100644 --- a/src/AudioMaster/utils/ffmpeg/main.py +++ b/src/AudioMaster/utils/ffmpeg/main.py @@ -11,14 +11,15 @@ from pydub import AudioSegment from pydub.utils import which from pydub.effects import normalize + # noinspection PyProtectedMember from mutagen.id3 import ID3, TIT2, TPE1, TALB, TYER, APIC from mutagen.mp3 import MP3 + # noinspection PyProtectedMember from mutagen.flac import FLAC, Picture - DRIVER = get_driver() @@ -26,11 +27,9 @@ class AudioHandler: @staticmethod @run_sync def convert( - raw_file: bytes | Path, - file_name: str, - audio_code: Literal[ - 30216, 30232, 30280, 30250, 30251 - ], + raw_file: bytes | Path, + file_name: str, + audio_code: Literal[30216, 30232, 30280, 30250, 30251], ) -> Path: """自动对音频执行转码""" # noinspection PyTypeChecker @@ -40,7 +39,7 @@ def convert( audio_format = "flac" if audio_code in (30250, 30251) else "mp3" file_path = store.get_cache_file( plugin_name="AudioMaster", - filename=f"{AudioHandler._safe_rename(file_name)}.{audio_format}" + filename=f"{AudioHandler._safe_rename(file_name)}.{audio_format}", ) logger.debug(f"当前音频峰值 dBFS:{(max_dBFS := audio.max_dBFS)}") @@ -60,16 +59,17 @@ def _safe_rename(filename: str) -> str: for old, new in (("/", "/"), ("\\", "\"), ("?", "?"), ("|", "︱")): filename = filename.replace(old, new) - return result if len( # 确保不为空字符 + return ( + result + if len( # 确保不为空字符 result := sanitize_filename(filename) - ) else "Unknown" + ) + else "Unknown" + ) @staticmethod def add_music_tags( - audio_path: Path, - title: str, - artists: list[str] | str, - cover: bytes + audio_path: Path, title: str, artists: list[str] | str, cover: bytes ) -> Path: """添加音频元数据""" if isinstance(artists, str): @@ -93,11 +93,7 @@ def add_music_tags( # 添加封面 audio["APIC"] = APIC( - encoding=3, - mime="image/png", - type=3, - desc='Cover', - data=cover + encoding=3, mime="image/png", type=3, desc="Cover", data=cover ) audio.save(audio_path) return audio_path @@ -117,7 +113,7 @@ def add_music_tags( image.data = cover image.type = 3 image.mime = "image/png" - image.desc = 'Cover' + image.desc = "Cover" audio.clear_pictures() audio.add_picture(image) @@ -125,21 +121,20 @@ def add_music_tags( audio.save(audio_path) return audio_path case _: - raise ValueError(f"不支持为 '{audio_path.suffix}' 格式音频添加音乐元数据!") + raise ValueError( + f"不支持为 '{audio_path.suffix}' 格式音频添加音乐元数据!" + ) @DRIVER.on_startup async def ensure_ffmpeg(): """检查 FFmpeg 是否安装,若未安装则自动进行安装""" if ( # 检查 FFmpeg 是否存在 - ffmpeg_path := which("ffmpeg") + ffmpeg_path := which("ffmpeg") ): logger.success(f"FFmpeg 已安装:{ffmpeg_path}") else: logger.warning("未检测到 FFmpeg 路径,请确保 FFmpeg 安装后再使用本插件!") -__all__ = [ - "ensure_ffmpeg", - "AudioHandler" -] +__all__ = ["ensure_ffmpeg", "AudioHandler"] diff --git a/tests/audio/test_handle.py b/tests/audio/test_handle.py index cb19630..eb18de4 100644 --- a/tests/audio/test_handle.py +++ b/tests/audio/test_handle.py @@ -19,13 +19,8 @@ @pytest.mark.asyncio(loop_scope="session") async def test_handle(): audio_path = await BiliManager.audio_handler.convert( - raw_file=FILE_PATH, - file_name="UNDEAD(Band ver.)", - audio_code="30280" + raw_file=FILE_PATH, file_name="UNDEAD(Band ver.)", audio_code="30280" ) BiliManager.audio_handler.add_music_tags( - audio_path=audio_path, - title="UNDEAD", - artists="鹿乃", - cover=cover + audio_path=audio_path, title="UNDEAD", artists="鹿乃", cover=cover ) diff --git a/tests/bilibili/test_api.py b/tests/bilibili/test_api.py index 540b047..031b980 100644 --- a/tests/bilibili/test_api.py +++ b/tests/bilibili/test_api.py @@ -33,9 +33,7 @@ async def test_api(): logger.warning("请在 60 秒内完成扫码登录!") while True: - poll_result = await BiliAPI.qrcode_poll( - qrcode_key=login_qrcode.qrcode_key - ) + poll_result = await BiliAPI.qrcode_poll(qrcode_key=login_qrcode.qrcode_key) if poll_result.code == 0: break @@ -58,7 +56,12 @@ async def test_api(): if not (0 < VIDEO_PAGE <= video_details.videos): raise ValueError(f"指定分 P 不能为:{VIDEO_PAGE}") - dash = await BiliAPI.get_video_playurl(bvid=VIDEO_ID, cid=pagelist[VIDEO_PAGE - 1].cid if video_details.videos > 1 else video_details.cid) + dash = await BiliAPI.get_video_playurl( + bvid=VIDEO_ID, + cid=pagelist[VIDEO_PAGE - 1].cid + if video_details.videos > 1 + else video_details.cid, + ) # 下载示例音频 if not ALLOW_DOWNLOAD_FLAC: # 检查是否允许下载 FLAC 音质 @@ -66,23 +69,29 @@ async def test_api(): audio_details = dash.audio[0] logger.info(f"正在下载音频:{audio_details.base_url}") - logger.info(f"预计下载文件大小:{round((dash.duration * audio_details.bandwidth / 8) / (1024 ** 2), 3)} MB") + logger.info( + f"预计下载文件大小:{round((dash.duration * audio_details.bandwidth / 8) / (1024**2), 3)} MB" + ) async with httpx.AsyncClient() as client: resp = await client.get( url=audio_details.base_url, headers={ "user-agent": "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/58.0.3029.110 Safari/537.36", - "referer": "https://www.bilibili.com/" - } + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/58.0.3029.110 Safari/537.36", + "referer": "https://www.bilibili.com/", + }, ) resp.raise_for_status() - logger.success(f"下载音频完毕,共下载 {round(len(resp.content) / (1024 ** 2), 3)} MB") + logger.success( + f"下载音频完毕,共下载 {round(len(resp.content) / (1024**2), 3)} MB" + ) await BiliManager.audio_handler.convert( raw_file=resp.content, - file_name=pagelist[VIDEO_PAGE - 1].part if video_details.videos > 1 else video_details.title, - audio_code=audio_details.id + file_name=pagelist[VIDEO_PAGE - 1].part + if video_details.videos > 1 + else video_details.title, + audio_code=audio_details.id, ) diff --git a/tests/deepseek/test_chat.py b/tests/deepseek/test_chat.py index f96906c..b611b3e 100644 --- a/tests/deepseek/test_chat.py +++ b/tests/deepseek/test_chat.py @@ -6,21 +6,15 @@ VIDEO_TITLE = "【主旋律】武陵印象" VIDEO_AUTHOR = "碧翠克斯度假中" -VIDEO_DESC = ( - """ +VIDEO_DESC = """ 武以止戈,安民合众;陵水经地,厌难折冲。 《明日方舟终末地》武陵BGM合集,目前共有57首,持续更新中。其中CG-穆如清风“贵人相助”/进入武陵城“大当家驾到!”剧情为游戏内实录,部分曲目为全面测试版本限定。 P12“东风就绪 战斗”的视频内容图来源:https://b23.tv/KfK5zuW(现已换源)。 """ -) @pytest.mark.asyncio(loop_scope="session") async def test_chat(): await BiliManager.analyse_video_details( - vinfo=VideoInfos( - title=VIDEO_TITLE, - author=VIDEO_AUTHOR, - desc=VIDEO_DESC - ) + vinfo=VideoInfos(title=VIDEO_TITLE, author=VIDEO_AUTHOR, desc=VIDEO_DESC) ) diff --git a/tests/drawer/test_draw_card.py b/tests/drawer/test_draw_card.py index 7dc7d12..3c607e6 100644 --- a/tests/drawer/test_draw_card.py +++ b/tests/drawer/test_draw_card.py @@ -21,7 +21,7 @@ async def test_draw_cinfo_card(): title="求婚 / propose / プロポーズ", artists=["鹿乃"], audio_formats="MP3(192kbps)", - durations="3分52秒" + durations="3分52秒", ) with io.BytesIO(image.data) as f: From df144348d514b684b3e6d2d0707f59729e369513 Mon Sep 17 00:00:00 2001 From: KanbeReina Date: Fri, 13 Feb 2026 21:23:49 +0800 Subject: [PATCH 7/7] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8D=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E6=AD=A3=E5=B8=B8=E5=8A=A0=E8=BD=BD=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/AudioMaster/__init__.py | 2 +- src/AudioMaster/utils/api/__init__.py | 7 ++----- src/AudioMaster/utils/api/deepseek/__init__.py | 4 ++-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/AudioMaster/__init__.py b/src/AudioMaster/__init__.py index e2f959f..e7bd0ec 100644 --- a/src/AudioMaster/__init__.py +++ b/src/AudioMaster/__init__.py @@ -2,7 +2,7 @@ require("nonebot_plugin_apscheduler") require("nonebot_plugin_alconna") -require("nonebot-plugin-localstore") +require("nonebot_plugin_localstore") require("UserInfos") from .main import * # noqa: E402, F403 diff --git a/src/AudioMaster/utils/api/__init__.py b/src/AudioMaster/utils/api/__init__.py index 1c4d680..ffd7192 100644 --- a/src/AudioMaster/utils/api/__init__.py +++ b/src/AudioMaster/utils/api/__init__.py @@ -1,8 +1,5 @@ from .bilibili import BiliAPI -from .deepseek import DeepSeekAPI +from .deepseek import DeepSeekAPI, ChatRequest, ResponseFormat, Message -__all__ = [ - "BiliAPI", - "DeepSeekAPI", -] +__all__ = ["BiliAPI", "DeepSeekAPI", "ChatRequest", "ResponseFormat", "Message"] diff --git a/src/AudioMaster/utils/api/deepseek/__init__.py b/src/AudioMaster/utils/api/deepseek/__init__.py index 3e20082..3b7e624 100644 --- a/src/AudioMaster/utils/api/deepseek/__init__.py +++ b/src/AudioMaster/utils/api/deepseek/__init__.py @@ -1,4 +1,4 @@ -from .main import DeepSeekAPI +from .main import DeepSeekAPI, ChatRequest, Message, ResponseFormat -__all__ = ["DeepSeekAPI"] +__all__ = ["DeepSeekAPI", "ChatRequest", "Message", "ResponseFormat"]