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
diff --git a/pyproject.toml b/pyproject.toml
index ed0118a..aaebdff 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,7 +11,9 @@ 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",
+ "orjson>=3.11.5",
"pathvalidate>=3.3.1",
"pycryptodome>=3.23.0",
"pydantic>=2.11.9",
@@ -30,5 +32,16 @@ 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",
]
+
+[tool.ruff.lint]
+exclude = [
+ "src/libs/**"
+]
+
+[tool.ruff.format]
+exclude = [
+ "src/libs/**"
+]
diff --git a/src/AudioMaster/__init__.py b/src/AudioMaster/__init__.py
index dcbde92..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_litestore")
+require("nonebot_plugin_localstore")
require("UserInfos")
-from .main import *
+from .main import * # noqa: E402, F403
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 15b6a64..ee367b4 100644
--- a/src/AudioMaster/manager/__init__.py
+++ b/src/AudioMaster/manager/__init__.py
@@ -1 +1,4 @@
-from .main import *
+from .main import 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 fb0de9f..ffd7192 100644
--- a/src/AudioMaster/utils/api/__init__.py
+++ b/src/AudioMaster/utils/api/__init__.py
@@ -1,2 +1,5 @@
-from .bilibili import *
-from .deepseek import *
+from .bilibili import BiliAPI
+from .deepseek import DeepSeekAPI, ChatRequest, ResponseFormat, Message
+
+
+__all__ = ["BiliAPI", "DeepSeekAPI", "ChatRequest", "ResponseFormat", "Message"]
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..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,36 +238,28 @@ 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(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
@@ -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(f"刷新 Cookies 成功")
+ logger.success("刷新 Cookies 成功")
self._client.cookies = resp.cookies
- logger.debug(f"已更新 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/__init__.py b/src/AudioMaster/utils/api/deepseek/__init__.py
index 15b6a64..3b7e624 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, ChatRequest, Message, ResponseFormat
+
+
+__all__ = ["DeepSeekAPI", "ChatRequest", "Message", "ResponseFormat"]
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 9e9db41..4192776 100644
--- a/src/AudioMaster/utils/commands/login.py
+++ b/src/AudioMaster/utils/commands/login.py
@@ -1,13 +1,5 @@
-from typing import Optional
-
-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
@@ -18,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 += [
@@ -55,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)
@@ -68,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)
@@ -92,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)
@@ -117,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)
@@ -139,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)
@@ -162,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/__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/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/__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 061661a..3b4356f 100644
--- a/src/AudioMaster/utils/ffmpeg/main.py
+++ b/src/AudioMaster/utils/ffmpeg/main.py
@@ -3,22 +3,23 @@
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
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
@@ -38,14 +37,17 @@ 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:
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}")
@@ -57,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):
@@ -90,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
@@ -114,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)
@@ -122,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/src/libs/userinfos/PKG-INFO b/src/libs/userinfos/PKG-INFO
index 290b32e..c6fdd4d 100644
--- a/src/libs/userinfos/PKG-INFO
+++ b/src/libs/userinfos/PKG-INFO
@@ -1,17 +1,59 @@
-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.1
+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: 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插件, 用于管理用户权限和为其他插件提供用户信息
+
+
+
+
+
+
+# ⭐UserInfos(用户数据&权限管理)
+
+_🍒轻量、高性能的**用户数据&权限管理**插件🍒_
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+> [!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插件, 用于管理用户权限和为其他插件提供用户信息
+
+
+
+
+
+
+# ⭐UserInfos(用户数据&权限管理)
+
+_🍒轻量、高性能的**用户数据&权限管理**插件🍒_
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+> [!CAUTION]\
+> **UserInfos** 是 **闭源插件**,**禁止在未经允许的情况下传播其源代码!**。
+
+## 特性
+- 🕗支持**多种 Workflow**,**项目管理更高效**
+- 💪使用**高性能的`orjson`存储`JSON`数据**
+- 🌼拥有**美观的消息模板**
+
+## 功能支持情况
+- ✅: 完全支持
+- 🚧: 计划中或部分支持
+- ❌: 不支持
+
+| 功能名称 | 支持情况 |
+|------------------------|-------------------------------------------|
+| 权限管理 | ✅ |
+| 用户数据管理 | ✅ |
diff --git a/src/libs/userinfos/pyproject.toml b/src/libs/userinfos/pyproject.toml
index af27192..b6b775f 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.1"
+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",
+ "orjson>=3.11.5",
+ "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()
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:
diff --git a/uv.lock b/uv.lock
index c847f57..c464814 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,7 +83,9 @@ dependencies = [
{ name = "nonebot-adapter-onebot" },
{ name = "nonebot-plugin-alconna" },
{ name = "nonebot-plugin-apscheduler" },
+ { name = "nonebot-plugin-localstore" },
{ name = "nonebot2", extra = ["fastapi"] },
+ { name = "orjson" },
{ name = "pathvalidate" },
{ name = "pycryptodome" },
{ name = "pydantic" },
@@ -95,6 +97,7 @@ dependencies = [
dev = [
{ name = "pytest" },
{ name = "pytest-asyncio" },
+ { name = "ruff" },
{ name = "userinfos" },
]
@@ -105,7 +108,9 @@ 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 = "orjson", specifier = ">=3.11.5" },
{ name = "pathvalidate", specifier = ">=3.3.1" },
{ name = "pycryptodome", specifier = ">=3.23.0" },
{ name = "pydantic", specifier = ">=2.11.9" },
@@ -117,6 +122,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 +397,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,27 +448,36 @@ 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"
+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]]
@@ -693,6 +710,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 +837,13 @@ wheels = [
[[package]]
name = "userinfos"
-version = "2.0.0"
+version = "3.0.1"
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,9 +853,15 @@ 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" },
+ { name = "orjson", specifier = ">=3.11.5" },
+]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "pytest", specifier = ">=9.0.2" },
+ { name = "ruff", specifier = ">=0.15.1" },
]
[[package]]