🐛 修复添加插件依赖更新 (#1837)

* 🐛 修复添加插件依赖更新

* 🔧 修改插件依赖安装命令为使用poetry运行pip

* 🐛 修复群组入群与退群提示

* 🐛 修复群组踢出用户提醒

* 🎨 代码优化

* 🎨 群欢迎迁移优化

* 🩹 精确webui调用统计

* 🚨 auto fix by pre-commit hooks

* 🐛 修复测试

* 🎨 fix pre-commit.ci

* 🎨  fix pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
HibiKier 2025-02-03 21:23:14 +08:00 committed by GitHub
parent d6fd5f170a
commit 4ed1791b30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 247 additions and 108 deletions

28
poetry.lock generated
View File

@ -2226,13 +2226,13 @@ reference = "aliyun"
[[package]] [[package]]
name = "nonebot-plugin-uninfo" name = "nonebot-plugin-uninfo"
version = "0.4.1" version = "0.6.8"
description = "Universal Information Model for Nonebot2" description = "Universal Information Model for Nonebot2"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
files = [ files = [
{file = "nonebot_plugin_uninfo-0.4.1-py3-none-any.whl", hash = "sha256:2075874a540f27cb650ff5e64482324121c39e4374b850df323482744910beac"}, {file = "nonebot_plugin_uninfo-0.6.8-py3-none-any.whl", hash = "sha256:0adc7e731885883bfcb873ec715c69cff75b878092884d28c7d6ff314940ad6b"},
{file = "nonebot_plugin_uninfo-0.4.1.tar.gz", hash = "sha256:8c36d62684029d813dd01acd2cc759f07163923b8274935250dcd3e9293ef560"}, {file = "nonebot_plugin_uninfo-0.6.8.tar.gz", hash = "sha256:0a30b500b1172fa15cc175b370c7a5935eb2f0515d188a2c73bf8e8ed7ae81d1"},
] ]
[package.dependencies] [package.dependencies]
@ -4131,6 +4131,26 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple" url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun" reference = "aliyun"
[[package]]
name = "tenacity"
version = "9.0.0"
description = "Retry code until it succeeds"
optional = false
python-versions = ">=3.8"
files = [
{file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"},
{file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"},
]
[package.extras]
doc = ["reno", "sphinx"]
test = ["pytest", "tornado (>=4.5)", "typeguard"]
[package.source]
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
[[package]] [[package]]
name = "text-unidecode" name = "text-unidecode"
version = "1.3" version = "1.3"
@ -4883,4 +4903,4 @@ reference = "aliyun"
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.10" python-versions = "^3.10"
content-hash = "69299e37ab69af7e3a020cc383fc2f2706300cf869a92dc7b76fe133288c405d" content-hash = "2fc15734ee6edc0a9b2b2f025375e7f41204bc21970745b65d5bc445eed897c7"

View File

@ -41,8 +41,9 @@ python-jose = { extras = ["cryptography"], version = "^3.3.0" }
python-multipart = "^0.0.9" python-multipart = "^0.0.9"
aiocache = "^0.12.2" aiocache = "^0.12.2"
py-cpuinfo = "^9.0.0" py-cpuinfo = "^9.0.0"
nonebot-plugin-uninfo = "^0.4.1"
nonebot-plugin-alconna = "^0.54.0" nonebot-plugin-alconna = "^0.54.0"
tenacity = "^9.0.0"
nonebot-plugin-uninfo = ">0.4.1"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
nonebug = "^0.4" nonebug = "^0.4"

View File

@ -12,7 +12,7 @@ from pytest_asyncio import is_async_test
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from respx import MockRouter from respx import MockRouter
from tests.config import BotId, UserId from tests.config import BotId, GroupId, UserId
nonebot.load_plugin("nonebot_plugin_session") nonebot.load_plugin("nonebot_plugin_session")
@ -64,6 +64,37 @@ def _init_bot(nonebug_init: None):
nonebot.load_plugins("zhenxun/builtin_plugins") nonebot.load_plugins("zhenxun/builtin_plugins")
nonebot.load_plugins("zhenxun/plugins") nonebot.load_plugins("zhenxun/plugins")
# 手动缓存 uninfo 所需信息
from nonebot_plugin_uninfo import (
Scene,
SceneType,
Session,
SupportAdapter,
SupportScope,
User,
)
from nonebot_plugin_uninfo.adapters.onebot11.main import fetcher as onebot11_fetcher
from nonebot_plugin_uninfo.adapters.onebot12.main import fetcher as onebot12_fetcher
onebot11_fetcher.session_cache = {
f"group_{GroupId.GROUP_ID_LEVEL_5}_{UserId.SUPERUSER}": Session(
self_id="test",
adapter=SupportAdapter.onebot11,
scope=SupportScope.qq_client,
scene=Scene(str(GroupId.GROUP_ID_LEVEL_0), SceneType.GROUP),
user=User(str(UserId.SUPERUSER)),
),
}
onebot12_fetcher.session_cache = {
f"group_{GroupId.GROUP_ID_LEVEL_5}_{UserId.SUPERUSER}": Session(
self_id="test",
adapter=SupportAdapter.onebot12,
scope=SupportScope.qq_client,
scene=Scene(str(GroupId.GROUP_ID_LEVEL_0), SceneType.GROUP),
user=User(str(UserId.SUPERUSER)),
),
}
@pytest.fixture @pytest.fixture
async def app(app: App, tmp_path: Path, mocker: MockerFixture): async def app(app: App, tmp_path: Path, mocker: MockerFixture):

View File

@ -54,6 +54,8 @@ def migrate(path: Path):
path: 路径 path: 路径
""" """
text_file = path / "text.json" text_file = path / "text.json"
if not text_file.exists():
return
with text_file.open(encoding="utf8") as f: with text_file.open(encoding="utf8") as f:
json_data = json.load(f) json_data = json.load(f)
new_data = {} new_data = {}

View File

@ -103,7 +103,7 @@ group_increase_handle = on_notice(
group_decrease_handle = on_notice( group_decrease_handle = on_notice(
priority=1, priority=1,
block=False, block=False,
rule=notice_rule([GroupMemberDecreaseEvent, GroupMemberIncreaseEvent]), rule=notice_rule([GroupMemberDecreaseEvent, GroupDecreaseNoticeEvent]),
) )
"""群员减少处理""" """群员减少处理"""
add_group = on_request(priority=1, block=False) add_group = on_request(priority=1, block=False)
@ -116,19 +116,19 @@ async def _(
session: Uninfo, session: Uninfo,
event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent, event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent,
): ):
user_id = str(event.user_id) if session.user.id == bot.self_id:
group_id = str(event.group_id)
if user_id == bot.self_id:
"""新成员为bot本身""" """新成员为bot本身"""
group, _ = await GroupConsole.get_or_create( group, _ = await GroupConsole.get_or_create(
group_id=group_id, channel_id__isnull=True group_id=str(event.group_id), channel_id__isnull=True
) )
try: try:
await GroupManager.add_bot(bot, str(event.operator_id), group_id, group) await GroupManager.add_bot(
bot, str(event.operator_id), str(event.group_id), group
)
except ForceAddGroupError as e: except ForceAddGroupError as e:
await PlatformUtils.send_superuser(bot, e.get_info()) await PlatformUtils.send_superuser(bot, e.get_info())
else: else:
await GroupManager.add_user(session, bot, user_id, group_id) await GroupManager.add_user(session, bot)
@group_decrease_handle.handle() @group_decrease_handle.handle()

View File

@ -4,6 +4,7 @@ from pathlib import Path
import random import random
from nonebot.adapters import Bot from nonebot.adapters import Bot
from nonebot.exception import ActionFailed
from nonebot_plugin_alconna import At, UniMessage from nonebot_plugin_alconna import At, UniMessage
from nonebot_plugin_uninfo import Uninfo from nonebot_plugin_uninfo import Uninfo
import ujson as json import ujson as json
@ -54,7 +55,7 @@ class GroupManager:
if plugin_list := await PluginInfo.filter(default_status=False).all(): if plugin_list := await PluginInfo.filter(default_status=False).all():
for plugin in plugin_list: for plugin in plugin_list:
block_plugin += f"<{plugin.module}," block_plugin += f"<{plugin.module},"
group_info = await bot.get_group_info(group_id=group_id) group_info = await bot.get_group_info(group_id=group_id, no_cache=True)
await GroupConsole.create( await GroupConsole.create(
group_id=group_info["group_id"], group_id=group_info["group_id"],
group_name=group_info["group_name"], group_name=group_info["group_name"],
@ -215,33 +216,45 @@ class GroupManager:
msg_list.insert(0, At("user", user_id)) msg_list.insert(0, At("user", user_id))
logger.info("发送群欢迎消息...", "入群检测", session=session) logger.info("发送群欢迎消息...", "入群检测", session=session)
if msg_list: if msg_list:
await MessageUtils.build_message(msg_list).send() # type: ignore await MessageUtils.build_message(msg_list).finish() # type: ignore
else: image = DEFAULT_IMAGE_PATH / random.choice(os.listdir(DEFAULT_IMAGE_PATH))
image = DEFAULT_IMAGE_PATH / random.choice( await MessageUtils.build_message(
os.listdir(DEFAULT_IMAGE_PATH) [
) "新人快跑啊!!本群现状↓(快使用自定义群欢迎消息!)",
await MessageUtils.build_message( image,
[ ]
"新人快跑啊!!本群现状↓(快使用自定义群欢迎消息!)", ).send()
image,
]
).send()
@classmethod @classmethod
async def add_user(cls, session: Uninfo, bot: Bot, user_id: str, group_id: str): async def add_user(cls, session: Uninfo, bot: Bot):
"""拉入用户 """拉入用户
参数: 参数:
session: Uninfo
bot: Bot bot: Bot
user_id: 用户id
group_id: 群组id
""" """
user_id = session.user.id
group_id = ""
if session.group:
if session.group.parent:
group_id = session.group.parent.id
else:
group_id = session.group.id
join_time = datetime.now() join_time = datetime.now()
user_info = await bot.get_group_member_info(group_id=group_id, user_id=user_id) try:
user_info = await bot.get_group_member_info(
group_id=int(group_id), user_id=int(user_id), no_cache=True
)
except ActionFailed as e:
logger.warning("获取用户信息识别...", e=e)
user_info = {"user_id": user_id, "group_id": group_id, "nickname": ""}
await GroupInfoUser.update_or_create( await GroupInfoUser.update_or_create(
user_id=str(user_info["user_id"]), user_id=str(user_info["user_id"]),
group_id=str(user_info["group_id"]), group_id=str(user_info["group_id"]),
defaults={"user_name": user_info["nickname"], "user_join_time": join_time}, defaults={
"user_name": user_info["nickname"],
"user_join_time": join_time,
},
) )
logger.info(f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新成功") logger.info(f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新成功")
if not await CommonUtils.task_is_block( if not await CommonUtils.task_is_block(
@ -310,10 +323,13 @@ class GroupManager:
group_id=group_id, group_id=group_id,
) )
if sub_type == "kick": if sub_type == "kick":
operator = await bot.get_group_member_info( if operator_id != "0":
user_id=int(operator_id), group_id=int(group_id) operator = await bot.get_group_member_info(
) user_id=int(operator_id), group_id=int(group_id)
operator_name = operator["card"] or operator["nickname"] )
operator_name = operator["card"] or operator["nickname"]
else:
operator_name = ""
return f"{user_name}{operator_name} 送走了." return f"{user_name}{operator_name} 送走了."
elif sub_type == "leave": elif sub_type == "leave":
return f"{user_name}离开了我们..." return f"{user_name}离开了我们..."

View File

@ -44,14 +44,14 @@ _matcher = on_alconna(
) )
_matcher.shortcut( _matcher.shortcut(
r"添加插件", r"(添加|安装)插件",
command="插件商店", command="插件商店",
arguments=["add", "{%0}"], arguments=["add", "{%0}"],
prefix=True, prefix=True,
) )
_matcher.shortcut( _matcher.shortcut(
r"移除插件", r"(移除|卸载)插件",
command="插件商店", command="插件商店",
arguments=["remove", "{%0}"], arguments=["remove", "{%0}"],
prefix=True, prefix=True,

View File

@ -51,7 +51,7 @@ def install_requirement(plugin_path: Path):
try: try:
result = subprocess.run( result = subprocess.run(
["pip", "install", "-r", str(existing_requirements)], ["poetry", "run", "pip", "install", "-r", str(existing_requirements)],
check=True, check=True,
capture_output=True, capture_output=True,
text=True, text=True,
@ -232,8 +232,9 @@ class ShopManage:
raise ValueError("所有API获取插件文件失败请检查网络连接") raise ValueError("所有API获取插件文件失败请检查网络连接")
if module_path == ".": if module_path == ".":
module_path = "" module_path = ""
replace_module_path = module_path.replace(".", "/")
files = repo_api.get_files( files = repo_api.get_files(
module_path=module_path.replace(".", "/") + ("" if is_dir else ".py"), module_path=replace_module_path + ("" if is_dir else ".py"),
is_dir=is_dir, is_dir=is_dir,
) )
download_urls = [await repo_info.get_raw_download_urls(file) for file in files] download_urls = [await repo_info.get_raw_download_urls(file) for file in files]
@ -248,25 +249,32 @@ class ShopManage:
else: else:
# 安装依赖 # 安装依赖
plugin_path = base_path / "/".join(module_path.split(".")) plugin_path = base_path / "/".join(module_path.split("."))
req_files = repo_api.get_files(REQ_TXT_FILE_STRING, False) try:
req_files.extend(repo_api.get_files("requirement.txt", False)) req_files = repo_api.get_files(
logger.debug(f"获取插件依赖文件列表: {req_files}", "插件管理") f"{replace_module_path}/{REQ_TXT_FILE_STRING}", False
req_download_urls = [
await repo_info.get_raw_download_urls(file) for file in req_files
]
req_paths: list[Path | str] = [plugin_path / file for file in req_files]
logger.debug(f"插件依赖文件下载路径: {req_paths}", "插件管理")
if req_files:
result = await AsyncHttpx.gather_download_file(
req_download_urls, req_paths
) )
for success in result: req_files.extend(
if not success: repo_api.get_files(f"{replace_module_path}/requirement.txt", False)
raise Exception("插件依赖文件下载失败") )
logger.debug(f"插件依赖文件列表: {req_paths}", "插件管理") logger.debug(f"获取插件依赖文件列表: {req_files}", "插件管理")
install_requirement(plugin_path) req_download_urls = [
await repo_info.get_raw_download_urls(file) for file in req_files
]
req_paths: list[Path | str] = [plugin_path / file for file in req_files]
logger.debug(f"插件依赖文件下载路径: {req_paths}", "插件管理")
if req_files:
result = await AsyncHttpx.gather_download_file(
req_download_urls, req_paths
)
for success in result:
if not success:
raise Exception("插件依赖文件下载失败")
logger.debug(f"插件依赖文件列表: {req_paths}", "插件管理")
install_requirement(plugin_path)
except ValueError as e:
logger.warning("未获取到依赖文件路径...", e=e)
return True return True
raise Exception("插件下载失败") raise Exception("插件下载失败...")
@classmethod @classmethod
async def remove_plugin(cls, plugin_id: str) -> str: async def remove_plugin(cls, plugin_id: str) -> str:

View File

@ -16,6 +16,7 @@ from zhenxun.models.sign_log import SignLog
from zhenxun.models.sign_user import SignUser from zhenxun.models.sign_user import SignUser
from zhenxun.utils.http_utils import AsyncHttpx from zhenxun.utils.http_utils import AsyncHttpx
from zhenxun.utils.image_utils import BuildImage from zhenxun.utils.image_utils import BuildImage
from zhenxun.utils.platform import PlatformUtils
from .config import ( from .config import (
SIGN_BACKGROUND_PATH, SIGN_BACKGROUND_PATH,
@ -430,7 +431,9 @@ async def _generate_html_card(
) )
now = datetime.now() now = datetime.now()
data = { data = {
"ava_url": session.user.avatar, "ava_url": PlatformUtils.get_user_avatar_url(
user.user_id, PlatformUtils.get_platform(session), session.self_id
),
"name": nickname, "name": nickname,
"uid": uid, "uid": uid,
"sign_count": f"{user.sign_count}", "sign_count": f"{user.sign_count}",

View File

@ -248,13 +248,31 @@ class ApiDataSource:
if bot_id: if bot_id:
query = query.filter(bot_id=bot_id) query = query.filter(bot_id=bot_id)
if date_type == QueryDateType.DAY: if date_type == QueryDateType.DAY:
query = query.filter(create_time__gte=now - timedelta(hours=now.hour)) query = query.filter(
create_time__gte=now
- timedelta(hours=now.hour, minutes=now.minute, seconds=now.second)
)
if date_type == QueryDateType.WEEK: if date_type == QueryDateType.WEEK:
query = query.filter(create_time__gte=now - timedelta(days=7)) query = query.filter(
create_time__gte=now
- timedelta(
days=7, hours=now.hour, minutes=now.minute, seconds=now.second
)
)
if date_type == QueryDateType.MONTH: if date_type == QueryDateType.MONTH:
query = query.filter(create_time__gte=now - timedelta(days=30)) query = query.filter(
create_time__gte=now
- timedelta(
days=30, hours=now.hour, minutes=now.minute, seconds=now.second
)
)
if date_type == QueryDateType.YEAR: if date_type == QueryDateType.YEAR:
query = query.filter(create_time__gte=now - timedelta(days=365)) query = query.filter(
create_time__gte=now
- timedelta(
days=365, hours=now.hour, minutes=now.minute, seconds=now.second
)
)
return query return query
@classmethod @classmethod

View File

@ -6,6 +6,8 @@ from pydantic import BaseModel
from .utils import ConfigsManager from .utils import ConfigsManager
__all__ = ["BotConfig", "Config"]
class BotSetting(BaseModel): class BotSetting(BaseModel):
self_nickname: str = "" self_nickname: str = ""

View File

@ -0,0 +1,16 @@
from httpx import ConnectError, HTTPStatusError, TimeoutException
from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_fixed
class Retry:
@staticmethod
def api():
"""接口调用重试"""
return retry(
reraise=True,
stop=stop_after_attempt(3),
wait=wait_fixed(1),
retry=retry_if_exception_type(
(TimeoutException, ConnectError, HTTPStatusError)
),
)

View File

@ -7,6 +7,7 @@ import time
from typing import Any, ClassVar, Literal from typing import Any, ClassVar, Literal
import aiofiles import aiofiles
from anyio import EndOfStream
import httpx import httpx
from httpx import ConnectTimeout, HTTPStatusError, Response from httpx import ConnectTimeout, HTTPStatusError, Response
from nonebot_plugin_alconna import UniMessage from nonebot_plugin_alconna import UniMessage
@ -41,7 +42,7 @@ class AsyncHttpx:
verify: bool = True, verify: bool = True,
use_proxy: bool = True, use_proxy: bool = True,
proxy: dict[str, str] | None = None, proxy: dict[str, str] | None = None,
timeout: int = 30, timeout: int = 30, # noqa: ASYNC109
**kwargs, **kwargs,
) -> Response: ) -> Response:
"""Get """Get
@ -96,7 +97,7 @@ class AsyncHttpx:
verify: bool = True, verify: bool = True,
use_proxy: bool = True, use_proxy: bool = True,
proxy: dict[str, str] | None = None, proxy: dict[str, str] | None = None,
timeout: int = 30, timeout: int = 30, # noqa: ASYNC109
**kwargs, **kwargs,
) -> Response: ) -> Response:
if not headers: if not headers:
@ -123,7 +124,7 @@ class AsyncHttpx:
verify: bool = True, verify: bool = True,
use_proxy: bool = True, use_proxy: bool = True,
proxy: dict[str, str] | None = None, proxy: dict[str, str] | None = None,
timeout: int = 30, timeout: int = 30, # noqa: ASYNC109
**kwargs, **kwargs,
) -> Response: ) -> Response:
"""Get """Get
@ -166,7 +167,7 @@ class AsyncHttpx:
params: dict[str, str] | None = None, params: dict[str, str] | None = None,
headers: dict[str, str] | None = None, headers: dict[str, str] | None = None,
cookies: dict[str, str] | None = None, cookies: dict[str, str] | None = None,
timeout: int = 30, timeout: int = 30, # noqa: ASYNC109
**kwargs, **kwargs,
) -> Response: ) -> Response:
""" """
@ -219,7 +220,7 @@ class AsyncHttpx:
proxy: dict[str, str] | None = None, proxy: dict[str, str] | None = None,
headers: dict[str, str] | None = None, headers: dict[str, str] | None = None,
cookies: dict[str, str] | None = None, cookies: dict[str, str] | None = None,
timeout: int = 30, timeout: int = 30, # noqa: ASYNC109
stream: bool = False, stream: bool = False,
follow_redirects: bool = True, follow_redirects: bool = True,
**kwargs, **kwargs,
@ -311,12 +312,17 @@ class AsyncHttpx:
completed=response.num_bytes_downloaded, completed=response.num_bytes_downloaded,
) )
logger.info( logger.info(
f"下载 {u} 成功.. " f"下载 {u} 成功.. Path{path.absolute()}"
f"Path{path.absolute()}"
) )
return True return True
except (TimeoutError, ConnectTimeout, HTTPStatusError): except (TimeoutError, ConnectTimeout, HTTPStatusError):
logger.warning(f"下载 {u} 失败.. 尝试下一个地址..") logger.warning(f"下载 {u} 失败.. 尝试下一个地址..")
except EndOfStream as e:
logger.warning(
f"下载 {url} EndOfStream 异常 Path{path.absolute()}", e=e
)
if path.exists():
return True
logger.error(f"下载 {url} 下载超时.. Path{path.absolute()}") logger.error(f"下载 {url} 下载超时.. Path{path.absolute()}")
except Exception as e: except Exception as e:
logger.error(f"下载 {url} 错误 Path{path.absolute()}", e=e) logger.error(f"下载 {url} 错误 Path{path.absolute()}", e=e)
@ -334,7 +340,7 @@ class AsyncHttpx:
proxy: dict[str, str] | None = None, proxy: dict[str, str] | None = None,
headers: dict[str, str] | None = None, headers: dict[str, str] | None = None,
cookies: dict[str, str] | None = None, cookies: dict[str, str] | None = None,
timeout: int = 30, timeout: int = 30, # noqa: ASYNC109
**kwargs, **kwargs,
) -> list[bool]: ) -> list[bool]:
"""分组同时下载文件 """分组同时下载文件
@ -374,22 +380,22 @@ class AsyncHttpx:
tasks = [] tasks = []
result_ = [] result_ = []
for x, y in zip(_split_url_list, _split_path_list): for x, y in zip(_split_url_list, _split_path_list):
for url, path in zip(x, y): tasks.extend(
tasks.append( asyncio.create_task(
asyncio.create_task( cls.download_file(
cls.download_file( url,
url, path,
path, params=params,
params=params, headers=headers,
headers=headers, cookies=cookies,
cookies=cookies, use_proxy=use_proxy,
use_proxy=use_proxy, timeout=timeout,
timeout=timeout, proxy=proxy,
proxy=proxy, **kwargs,
**kwargs,
)
) )
) )
for url, path in zip(x, y)
)
_x = await asyncio.gather(*tasks) _x = await asyncio.gather(*tasks)
result_ = result_ + list(_x) result_ = result_ + list(_x)
tasks.clear() tasks.clear()
@ -465,7 +471,7 @@ class AsyncPlaywright:
wait_until: ( wait_until: (
Literal["domcontentloaded", "load", "networkidle"] | None Literal["domcontentloaded", "load", "networkidle"] | None
) = "networkidle", ) = "networkidle",
timeout: float | None = None, timeout: float | None = None, # noqa: ASYNC109
type_: Literal["jpeg", "png"] | None = None, type_: Literal["jpeg", "png"] | None = None,
user_agent: str | None = None, user_agent: str | None = None,
cookies: list[dict[str, Any]] | dict[str, Any] | None = None, cookies: list[dict[str, Any]] | dict[str, Any] | None = None,

View File

@ -17,6 +17,7 @@ from nonebot_plugin_alconna import (
Voice, Voice,
) )
from pydantic import BaseModel from pydantic import BaseModel
import ujson as json
from zhenxun.configs.config import BotConfig from zhenxun.configs.config import BotConfig
from zhenxun.services.log import logger from zhenxun.services.log import logger
@ -141,6 +142,21 @@ class MessageUtils:
) )
return UniMessage(Reference(nodes=node_list)) return UniMessage(Reference(nodes=node_list))
@classmethod
def markdown(cls, content: dict) -> Message:
"""markdown格式消息
参数:
content: 消息内容
返回:
Message: 构造完成的消息
"""
content_data = base64.b64encode(json.dumps(content).encode("utf-8")).decode(
"utf-8"
)
return Message(f"[CQ:markdown,data=base64://{content_data}]")
@classmethod @classmethod
def custom_forward_msg( def custom_forward_msg(
cls, cls,

View File

@ -55,6 +55,8 @@ class PlatformUtils:
""" """
if isinstance(session, Bot): if isinstance(session, Bot):
return bool(BotConfig.get_qbot_uid(session.self_id)) return bool(BotConfig.get_qbot_uid(session.self_id))
if BotConfig.get_qbot_uid(session.self_id):
return True
return session.scope == SupportScope.qq_api return session.scope == SupportScope.qq_api
@classmethod @classmethod
@ -354,32 +356,30 @@ class PlatformUtils:
返回: 返回:
tuple[list[GroupConsole], str]: 群组列表, 平台 tuple[list[GroupConsole], str]: 群组列表, 平台
""" """
if interface := get_interface(bot): if not (interface := get_interface(bot)):
platform = cls.get_platform(bot) return [], ""
result_list = [] platform = cls.get_platform(bot)
scenes = await interface.get_scenes(SceneType.GROUP) result_list = []
for scene in scenes: scenes = await interface.get_scenes(SceneType.GROUP)
group_id = scene.id for scene in scenes:
result_list.append( group_id = scene.id
GroupConsole( result_list.append(
group_id=scene.id, GroupConsole(
group_name=scene.name, group_id=scene.id,
) group_name=scene.name,
) )
if not only_group and platform != "qq": )
if channel_list := await interface.get_scenes( if not only_group and platform != "qq":
parent_scene_id=group_id if channel_list := await interface.get_scenes(parent_scene_id=group_id):
): result_list.extend(
for channel in channel_list: GroupConsole(
result_list.append( group_id=scene.id,
GroupConsole( group_name=channel.name,
group_id=scene.id, channel_id=channel.id,
group_name=channel.name, )
channel_id=channel.id, for channel in channel_list
) )
) return result_list, platform
return result_list, platform
return [], ""
@classmethod @classmethod
async def update_friend(cls, bot: Bot) -> int: async def update_friend(cls, bot: Bot) -> int: