版本更新

This commit is contained in:
HibiKier
2024-10-20 21:11:16 +08:00
parent 4e33bf3a50
commit d594d17bf6
253 changed files with 34987 additions and 2241 deletions
+19 -3
View File
@@ -1,3 +1,6 @@
from nonebot.adapters import Bot
from nonebot_plugin_uninfo import Uninfo, Session, SupportScope, get_interface
from zhenxun.services.log import logger
from zhenxun.configs.config import BotConfig
from zhenxun.models.task_info import TaskInfo
@@ -6,9 +9,10 @@ from zhenxun.models.group_console import GroupConsole
class CommonUtils:
@classmethod
async def task_is_block(cls, module: str, group_id: str | None) -> bool:
async def task_is_block(
cls, session: Uninfo | Bot, module: str, group_id: str | None = None
) -> bool:
"""判断被动技能是否可以发送
参数:
@@ -18,6 +22,19 @@ class CommonUtils:
返回:
bool: 是否可以发送
"""
if isinstance(session, Bot):
if interface := get_interface(session):
info = interface.basic_info()
if info["scope"] == SupportScope.qq_api:
logger.info("q官bot放弃所有被动技能发言...")
"""q官bot放弃所有被动技能发言"""
return False
if session.scene == SupportScope.qq_api:
"""q官bot放弃所有被动技能发言"""
logger.info("q官bot放弃所有被动技能发言...")
return False
if not group_id and isinstance(session, Session):
group_id = session.group.id if session.group else None
if task := await TaskInfo.get_or_none(module=module):
"""被动全局状态"""
if not task.status:
@@ -39,7 +56,6 @@ class CommonUtils:
class SqlUtils:
@classmethod
def random(cls, query, limit: int = 1) -> str:
db_class_name = BotConfig.get_sql_type()
+5 -7
View File
@@ -1,10 +1,10 @@
from typing import Any
from nonebot.internal.params import Depends
from nonebot.matcher import Matcher
from nonebot.params import Command
from nonebot.matcher import Matcher
from nonebot_plugin_uninfo import Uninfo
from nonebot.internal.params import Depends
from nonebot_plugin_session import EventSession
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
from zhenxun.configs.config import Config
from zhenxun.utils.message import MessageUtils
@@ -49,10 +49,8 @@ def UserName():
用户名称
"""
async def dependency(user_info: UserInfo = EventUserInfo()):
return (
user_info.user_displayname or user_info.user_remark or user_info.user_name
) or ""
async def dependency(user_info: Uninfo):
return user_info.user.nick or user_info.user.name or ""
return Depends(dependency)
+12 -4
View File
@@ -1,22 +1,30 @@
import os
import random
from nonebot_plugin_htmlrender import template_to_pic
from zhenxun.configs.path_config import TEMPLATE_PATH
from zhenxun.utils._build_image import BuildImage
from zhenxun.configs.path_config import TEMPLATE_PATH
from .models import Barh
BACKGROUND_PATH = TEMPLATE_PATH / "bar_chart" / "background"
class ChartUtils:
@classmethod
async def barh(cls, data: Barh) -> BuildImage:
"""横向统计图"""
to_json = data.dict()
to_json["background_image"] = (
f"./background/{random.choice(os.listdir(BACKGROUND_PATH))}"
)
pic = await template_to_pic(
template_path=str((TEMPLATE_PATH / "bar_chart").absolute()),
template_name="main.html",
templates={"data": data},
templates={"data": to_json},
pages={
"viewport": {"width": 1000, "height": 500},
"viewport": {"width": 1000, "height": 1000},
"base_url": f"file://{TEMPLATE_PATH}",
},
wait=2,
+2 -1
View File
@@ -2,8 +2,9 @@ from pydantic import BaseModel
class Barh(BaseModel):
category_data: list[str]
"""坐标轴数据"""
data: list[int | float]
"""实际数据"""
title: str
"""标题"""
+1 -1
View File
@@ -1,6 +1,6 @@
from collections.abc import Generator
from .consts import GITHUB_REPO_URL_PATTERN
from .const import GITHUB_REPO_URL_PATTERN
from .func import get_fastest_raw_formats, get_fastest_archive_formats
from .models import RepoAPI, RepoInfo, GitHubStrategy, JsdelivrStrategy
+35
View File
@@ -0,0 +1,35 @@
import re
GITHUB_REPO_URL_PATTERN = re.compile(
r"^https://github.com/(?P<owner>[^/]+)/(?P<repo>[^/]+)(/tree/(?P<branch>[^/]+))?$"
)
"""github仓库地址正则"""
JSD_PACKAGE_API_FORMAT = (
"https://data.jsdelivr.com/v1/packages/gh/{owner}/{repo}@{branch}"
)
"""jsdelivr包地址格式"""
GIT_API_TREES_FORMAT = (
"https://api.github.com/repos/{owner}/{repo}/git/trees/{branch}?recursive=1"
)
"""git api trees地址格式"""
CACHED_API_TTL = 300
"""缓存api ttl"""
RAW_CONTENT_FORMAT = "https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{path}"
"""raw content格式"""
ARCHIVE_URL_FORMAT = "https://github.com/{owner}/{repo}/archive/refs/heads/{branch}.zip"
"""archive url格式"""
RELEASE_ASSETS_FORMAT = (
"https://github.com/{owner}/{repo}/releases/download/{version}/{filename}"
)
"""release assets格式"""
RELEASE_SOURCE_FORMAT = (
"https://codeload.github.com/{owner}/{repo}/legacy.{compress}/refs/tags/{version}"
)
"""release 源码格式"""
+1 -1
View File
@@ -1,7 +1,7 @@
from aiocache import cached
from ..http_utils import AsyncHttpx
from .consts import (
from .const import (
ARCHIVE_URL_FORMAT,
RAW_CONTENT_FORMAT,
RELEASE_ASSETS_FORMAT,
+6 -4
View File
@@ -5,7 +5,7 @@ from strenum import StrEnum
from pydantic import BaseModel
from ..http_utils import AsyncHttpx
from .consts import CACHED_API_TTL, GIT_API_TREES_FORMAT, JSD_PACKAGE_API_FORMAT
from .const import CACHED_API_TTL, GIT_API_TREES_FORMAT, JSD_PACKAGE_API_FORMAT
from .func import (
get_fastest_raw_formats,
get_fastest_archive_formats,
@@ -199,13 +199,15 @@ class GitHubStrategy:
body: TreeInfo
def export_files(self, module_path: str) -> list[str]:
def export_files(self, module_path: str, is_dir: bool) -> list[str]:
"""导出文件路径"""
tree_info = self.body
return [
file.path
for file in tree_info.tree
if file.type == TreeType.FILE and file.path.startswith(module_path)
if file.type == TreeType.FILE
and file.path.startswith(module_path)
and (not is_dir or file.path[len(module_path)] == "/")
]
@classmethod
@@ -229,4 +231,4 @@ class GitHubStrategy:
def get_files(self, module_path: str, is_dir: bool = True) -> list[str]:
"""获取文件路径"""
return self.export_files(module_path)
return self.export_files(module_path, is_dir)
+13 -15
View File
@@ -101,7 +101,7 @@ class AsyncHttpx:
) -> Response:
if not headers:
headers = get_user_agent()
_proxy = proxy if proxy else cls.proxy if use_proxy else None
_proxy = proxy or (cls.proxy if use_proxy else None)
async with httpx.AsyncClient(proxies=_proxy, verify=verify) as client: # type: ignore
return await client.get(
url,
@@ -140,7 +140,7 @@ class AsyncHttpx:
"""
if not headers:
headers = get_user_agent()
_proxy = proxy if proxy else cls.proxy if use_proxy else None
_proxy = proxy or (cls.proxy if use_proxy else None)
async with httpx.AsyncClient(proxies=_proxy, verify=verify) as client: # type: ignore
return await client.head(
url,
@@ -187,7 +187,7 @@ class AsyncHttpx:
"""
if not headers:
headers = get_user_agent()
_proxy = proxy if proxy else cls.proxy if use_proxy else None
_proxy = proxy or (cls.proxy if use_proxy else None)
async with httpx.AsyncClient(proxies=_proxy, verify=verify) as client: # type: ignore
return await client.post(
url,
@@ -202,6 +202,11 @@ class AsyncHttpx:
**kwargs,
)
@classmethod
async def get_content(cls, url: str, **kwargs) -> bytes | None:
res = await cls.get(url, **kwargs)
return res.content if res and res.status_code == 200 else None
@classmethod
async def download_file(
cls,
@@ -259,13 +264,10 @@ class AsyncHttpx:
async with aiofiles.open(path, "wb") as wf:
await wf.write(content)
logger.info(f"下载 {u} 成功.. Path:{path.absolute()}")
return True
else:
if not headers:
headers = get_user_agent()
_proxy = (
proxy if proxy else cls.proxy if use_proxy else None
)
_proxy = proxy or (cls.proxy if use_proxy else None)
async with httpx.AsyncClient(
proxies=_proxy, # type: ignore
verify=verify,
@@ -298,7 +300,7 @@ class AsyncHttpx:
) as progress:
download_task = progress.add_task(
"Download",
total=total if total else None,
total=total or None,
)
async for chunk in response.aiter_bytes():
await wf.write(chunk)
@@ -311,11 +313,10 @@ class AsyncHttpx:
f"下载 {u} 成功.. "
f"Path:{path.absolute()}"
)
return True
return True
except (TimeoutError, ConnectTimeout, HTTPStatusError):
logger.warning(f"下载 {u} 失败.. 尝试下一个地址..")
else:
logger.error(f"下载 {url} 下载超时.. Path:{path.absolute()}")
logger.error(f"下载 {url} 下载超时.. Path:{path.absolute()}")
except Exception as e:
logger.error(f"下载 {url} 错误 Path:{path.absolute()}", e=e)
return False
@@ -479,10 +480,7 @@ class AsyncPlaywright:
if isinstance(path, str):
path = Path(path)
wait_time = wait_time * 1000 if wait_time else None
if isinstance(element, str):
element_list = [element]
else:
element_list = element
element_list = [element] if isinstance(element, str) else element
async with cls.new_page(
viewport=viewport_size,
user_agent=user_agent,
+61 -29
View File
@@ -7,20 +7,24 @@ import nonebot
from pydantic import BaseModel
from nonebot.adapters import Bot
from nonebot.utils import is_coroutine_callable
from nonebot_plugin_alconna import SupportScope
from nonebot.adapters.dodo import Bot as DodoBot
from nonebot.adapters.onebot.v11 import Bot as v11Bot
from nonebot.adapters.onebot.v12 import Bot as v12Bot
from nonebot.adapters.discord import Bot as DiscordBot
from nonebot_plugin_uninfo import Uninfo, get_interface
from nonebot.adapters.kaiheila import Bot as KaiheilaBot
from nonebot_plugin_alconna.uniseg import Target, Receipt, UniMessage
from zhenxun.services.log import logger
from zhenxun.configs.config import BotConfig
from zhenxun.utils.message import MessageUtils
from zhenxun.utils.http_utils import AsyncHttpx
from zhenxun.models.friend_user import FriendUser
from zhenxun.utils.exception import NotFindSuperuser
from zhenxun.models.group_console import GroupConsole
driver = nonebot.get_driver()
class UserData(BaseModel):
name: str
@@ -40,6 +44,20 @@ class UserData(BaseModel):
class PlatformUtils:
@classmethod
def is_qbot(cls, session: Uninfo | Bot) -> bool:
"""判断bot是否为qq官bot
参数:
session: Uninfo
返回:
bool: 是否为官bot
"""
if isinstance(session, Bot):
return bool(BotConfig.get_qbot_uid(session.self_id))
return session.scope == SupportScope.qq_api
@classmethod
async def ban_user(cls, bot: Bot, user_id: str, group_id: str, duration: int):
"""禁言
@@ -244,30 +262,27 @@ class PlatformUtils:
return None
@classmethod
async def get_user_avatar(cls, user_id: str, platform: str) -> bytes | None:
async def get_user_avatar(
cls, user_id: str, platform: str, appid: str | None = None
) -> bytes | None:
"""快捷获取用户头像
参数:
user_id: 用户id
platform: 平台
"""
url = None
if platform == "qq":
url = f"http://q1.qlogo.cn/g?b=qq&nk={user_id}&s=160"
async with httpx.AsyncClient() as client:
for _ in range(3):
try:
return (await client.get(url)).content
except Exception:
logger.error(
"获取用户头像错误",
"Util",
target=user_id,
platform=platform,
)
return None
if user_id.isdigit():
url = f"http://q1.qlogo.cn/g?b=qq&nk={user_id}&s=160"
else:
url = f"https://q.qlogo.cn/qqapp/{appid}/{user_id}/100"
return await AsyncHttpx.get_content(url) if url else None
@classmethod
def get_user_avatar_url(cls, user_id: str, platform: str) -> str | None:
def get_user_avatar_url(
cls, user_id: str, platform: str, appid: str | None = None
) -> str | None:
"""快捷获取用户头像url
参数:
@@ -275,8 +290,13 @@ class PlatformUtils:
platform: 平台
"""
if platform == "qq":
return f"http://q1.qlogo.cn/g?b=qq&nk={user_id}&s=160"
return None
return (
f"http://q1.qlogo.cn/g?b=qq&nk={user_id}&s=160"
if user_id.isdigit()
else f"https://q.qlogo.cn/qqapp/{appid}/{user_id}/100"
)
else:
return None
@classmethod
async def get_group_avatar(cls, gid: str, platform: str) -> bytes | None:
@@ -371,7 +391,7 @@ class PlatformUtils:
return len(create_list)
@classmethod
def get_platform(cls, bot: Bot) -> str | None:
def get_platform(cls, t: Bot | Uninfo) -> str:
"""获取平台
参数:
@@ -380,13 +400,15 @@ class PlatformUtils:
返回:
str | None: 平台
"""
if isinstance(bot, v11Bot | v12Bot):
return "qq"
if isinstance(bot, DodoBot):
return "dodo"
if isinstance(bot, KaiheilaBot):
return "kaiheila"
return "discord" if isinstance(bot, DiscordBot) else None
if isinstance(t, Bot):
if interface := get_interface(t):
info = interface.basic_info()
platform = info["scope"].lower()
return "qq" if platform.startswith("qq") else platform
else:
platform = t.basic["scope"].lower()
return "qq" if platform.startswith("qq") else platform
return "unknown"
@classmethod
async def get_group_list(cls, bot: Bot) -> tuple[list[GroupConsole], str]:
@@ -550,7 +572,7 @@ async def broadcast_group(
bot: Bot | list[Bot] | None = None,
bot_id: str | set[str] | None = None,
ignore_group: set[int] | None = None,
check_func: Callable[[str], Awaitable] | None = None,
check_func: Callable[[Bot, str], Awaitable] | None = None,
log_cmd: str | None = None,
platform: Literal["qq", "dodo", "kaiheila"] | None = None,
):
@@ -604,14 +626,24 @@ async def broadcast_group(
or group.channel_id in ignore_group
)
) or key in _used_group:
logger.debug(
"广播方法群组重复, 已跳过...",
log_cmd,
group_id=group.group_id,
)
continue
is_run = False
if check_func:
if is_coroutine_callable(check_func):
is_run = await check_func(group.group_id)
is_run = await check_func(_bot, group.group_id)
else:
is_run = check_func(group.group_id)
is_run = check_func(_bot, group.group_id)
if not is_run:
logger.debug(
"广播方法检测运行方法为 False, 已跳过...",
log_cmd,
group_id=group.group_id,
)
continue
target = PlatformUtils.get_target(
_bot, None, group.group_id, group.channel_id
-1
View File
@@ -2,7 +2,6 @@ from pydantic import BaseModel
class CommonSql(BaseModel):
sql: str
"""sql语句"""
remark: str
+39 -9
View File
@@ -1,10 +1,12 @@
from nonebot.adapters import Bot, Event
from nonebot.internal.rule import Rule
from nonebot.adapters import Bot, Event
from nonebot.permission import SUPERUSER
from nonebot_plugin_session import EventSession, SessionLevel
from nonebot_plugin_uninfo import Uninfo
from nonebot_plugin_session import EventSession
from zhenxun.configs.config import Config
from zhenxun.models.level_user import LevelUser
from zhenxun.utils.platform import PlatformUtils
def admin_check(a: int | str, key: str | None = None) -> Rule:
@@ -19,33 +21,38 @@ def admin_check(a: int | str, key: str | None = None) -> Rule:
Rule: Rule
"""
async def _rule(bot: Bot, event: Event, session: EventSession) -> bool:
async def _rule(bot: Bot, event: Event, session: Uninfo) -> bool:
if await SUPERUSER(bot, event):
return True
if session.id1 and session.id2:
if PlatformUtils.is_qbot(session):
"""官bot接口,放弃所有权限检查"""
return False
if session.id and session.group:
level = a
if type(a) == str and key:
if isinstance(a, str) and key:
level = Config.get_config(a, key)
if level is not None:
return bool(
await LevelUser.check_level(session.id1, session.id2, int(level))
await LevelUser.check_level(
session.id, session.group.id, int(level)
)
)
return False
return Rule(_rule)
def ensure_group(session: EventSession) -> bool:
def ensure_group(session: Uninfo) -> bool:
"""
是否在群聊中
参数:
session: session
session: Uninfo
返回:
bool: bool
"""
return session.level in [SessionLevel.LEVEL2, SessionLevel.LEVEL3]
return bool(session.group)
def ensure_private(session: EventSession) -> bool:
@@ -59,3 +66,26 @@ def ensure_private(session: EventSession) -> bool:
bool: bool
"""
return not session.id3 and not session.id2
def notice_rule(event_type: type | list[type]) -> Rule:
"""
Notice限制
参数:
event_type: Event类型
返回:
Rule: Rule
"""
async def _rule(event: Event) -> bool:
if isinstance(event_type, list):
for et in event_type:
if isinstance(event, et):
return True
else:
return isinstance(event, event_type)
return False
return Rule(_rule)