mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-05 03:39:59 +08:00
modified: basic_plugins/admin_bot_manage/_data_source.py
modified: basic_plugins/admin_bot_manage/admin_config.py modified: basic_plugins/admin_bot_manage/custom_welcome_message.py modified: basic_plugins/admin_bot_manage/timing_task.py modified: basic_plugins/apscheduler/__init__.py modified: basic_plugins/ban/__init__.py modified: basic_plugins/ban/data_source.py modified: basic_plugins/chat_history/chat_message.py modified: basic_plugins/chat_history/chat_message_handle.py modified: basic_plugins/group_handle/__init__.py modified: basic_plugins/hooks/_utils.py modified: basic_plugins/hooks/ban_hook.py modified: basic_plugins/hooks/chkdsk_hook.py modified: basic_plugins/init_plugin_config/__init__.py deleted: basic_plugins/init_plugin_config/init_group_manager.py modified: basic_plugins/invite_manager/__init__.py new file: basic_plugins/invite_manager/utils.py modified: basic_plugins/nickname.py modified: basic_plugins/plugin_shop/__init__.py modified: basic_plugins/plugin_shop/data_source.py modified: basic_plugins/scripts.py modified: basic_plugins/shop/__init__.py modified: basic_plugins/shop/buy.py modified: basic_plugins/shop/gold.py modified: basic_plugins/shop/my_props/__init__.py deleted: basic_plugins/shop/reset_today_gold.py modified: basic_plugins/shop/shop_handle/__init__.py modified: basic_plugins/shop/shop_handle/data_source.py modified: basic_plugins/shop/use/__init__.py modified: basic_plugins/shop/use/data_source.py modified: basic_plugins/super_cmd/__init__.py modified: basic_plugins/super_cmd/bot_friend_group.py modified: basic_plugins/super_cmd/clear_data.py modified: basic_plugins/super_cmd/exec_sql.py modified: basic_plugins/super_cmd/manager_group.py modified: basic_plugins/super_cmd/reload_setting.py modified: basic_plugins/super_cmd/set_admin_permissions.py deleted: basic_plugins/super_cmd/super_task_switch.py modified: basic_plugins/super_cmd/update_friend_group_info.py modified: basic_plugins/super_help/__init__.py modified: basic_plugins/update_info.py modified: configs/config.py modified: configs/utils/__init__.py modified: models/bag_user.py modified: models/ban_user.py modified: models/chat_history.py modified: models/friend_user.py modified: models/goods_info.py modified: models/group_info.py modified: models/group_member_info.py modified: models/level_user.py modified: models/sign_group_user.py modified: models/user_shop_gold_log.py modified: plugins/aconfig/__init__.py modified: plugins/ai/__init__.py modified: plugins/ai/data_source.py modified: plugins/bilibili_sub/__init__.py modified: plugins/bilibili_sub/data_source.py modified: plugins/bilibili_sub/model.py modified: plugins/black_word/__init__.py modified: plugins/black_word/model.py modified: plugins/black_word/utils.py modified: plugins/bt/data_source.py modified: plugins/genshin/almanac/__init__.py modified: plugins/genshin/material_remind/__init__.py modified: plugins/genshin/query_user/_models/__init__.py modified: plugins/genshin/query_user/_utils/__init__.py modified: plugins/genshin/query_user/bind/__init__.py modified: plugins/genshin/query_user/genshin_sign/__init__.py modified: plugins/genshin/query_user/genshin_sign/data_source.py modified: plugins/genshin/query_user/genshin_sign/init_task.py modified: plugins/genshin/query_user/mihoyobbs_sign/__init__.py modified: plugins/genshin/query_user/query_memo/__init__.py modified: plugins/genshin/query_user/query_memo/data_source.py modified: plugins/genshin/query_user/query_role/__init__.py modified: plugins/genshin/query_user/query_role/data_source.py modified: plugins/genshin/query_user/reset_today_query_user_data/__init__.py modified: plugins/genshin/query_user/resin_remind/__init__.py modified: plugins/genshin/query_user/resin_remind/init_task.py modified: plugins/gold_redbag/model.py modified: plugins/image_management/send_image/__init__.py modified: plugins/my_info/__init__.py modified: plugins/open_cases/models/buff_prices.py modified: plugins/open_cases/models/open_cases_user.py modified: plugins/open_cases/open_cases_c.py modified: plugins/open_cases/utils.py modified: plugins/parse_bilibili_json.py modified: plugins/pid_search.py modified: plugins/pix_gallery/__init__.py modified: plugins/pix_gallery/_data_source.py modified: plugins/pix_gallery/_model/omega_pixiv_illusts.py modified: plugins/pix_gallery/_model/pixiv.py modified: plugins/pix_gallery/_model/pixiv_keyword_user.py modified: plugins/pix_gallery/pix_add_keyword.py modified: plugins/pix_gallery/pix_pass_del_keyword.py modified: plugins/pix_gallery/pix_show_info.py modified: plugins/pix_gallery/pix_update.py modified: plugins/pixiv_rank_search/data_source.py modified: plugins/poke/__init__.py modified: plugins/russian/__init__.py modified: plugins/russian/data_source.py modified: plugins/russian/model.py modified: plugins/send_dinggong_voice/__init__.py modified: plugins/send_setu_/_model.py modified: plugins/send_setu_/send_setu/__init__.py modified: plugins/send_setu_/send_setu/data_source.py modified: plugins/send_setu_/update_setu/data_source.py modified: plugins/sign_in/goods_register.py modified: plugins/sign_in/group_user_checkin.py modified: plugins/sign_in/random_event.py modified: plugins/sign_in/utils.py modified: plugins/statistics/_model.py modified: plugins/statistics/statistics_handle.py modified: plugins/statistics/statistics_hook.py modified: plugins/update_picture.py modified: plugins/web_ui/api/request.py modified: plugins/word_bank/_model.py deleted: plugins/word_bank/_old_model.py modified: plugins/word_bank/_rule.py modified: plugins/word_bank/word_handle.py modified: plugins/word_clouds/data_source.py modified: resources/image/sign/sign_res/bar.png modified: resources/image/sign/sign_res/bar_white.png modified: services/db_context.py modified: services/log.py modified: utils/browser.py modified: utils/data_utils.py modified: utils/depends/__init__.py modified: utils/http_utils.py modified: utils/image_utils.py modified: utils/manager/admin_manager.py modified: utils/message_builder.py modified: utils/utils.py
This commit is contained in:
+24
-13
@@ -1,34 +1,45 @@
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
|
||||
from nonebot import get_driver
|
||||
from nonebot.log import logger
|
||||
from playwright.async_api import Browser, async_playwright
|
||||
from playwright.async_api import Browser, Playwright, async_playwright
|
||||
|
||||
from services.log import logger
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
_playwright: Optional[Playwright] = None
|
||||
_browser: Optional[Browser] = None
|
||||
|
||||
|
||||
async def init(**kwargs) -> Optional[Browser]:
|
||||
@driver.on_startup
|
||||
async def start_browser():
|
||||
global _playwright
|
||||
global _browser
|
||||
browser = await async_playwright().start()
|
||||
try:
|
||||
_browser = await browser.chromium.launch(**kwargs)
|
||||
return _browser
|
||||
except Exception as e:
|
||||
# logger.warning(f"启动chromium发生错误 {type(e)}:{e}")
|
||||
await asyncio.get_event_loop().run_in_executor(None, install)
|
||||
_browser = await browser.chromium.launch(**kwargs)
|
||||
return None
|
||||
_playwright = await async_playwright().start()
|
||||
_browser = await _playwright.chromium.launch()
|
||||
|
||||
|
||||
async def get_browser(**kwargs) -> Browser:
|
||||
return _browser or await init(**kwargs)
|
||||
@driver.on_shutdown
|
||||
async def shutdown_browser():
|
||||
if _browser:
|
||||
await _browser.close()
|
||||
if _playwright:
|
||||
_playwright.stop()
|
||||
|
||||
|
||||
def get_browser() -> Browser:
|
||||
if not _browser:
|
||||
raise RuntimeError("playwright is not initalized")
|
||||
return _browser
|
||||
|
||||
|
||||
def install():
|
||||
"""自动安装、更新 Chromium"""
|
||||
logger.info("正在检查 Chromium 更新")
|
||||
import sys
|
||||
|
||||
from playwright.__main__ import main
|
||||
|
||||
sys.argv = ["", "install", "chromium"]
|
||||
|
||||
+15
-10
@@ -1,13 +1,18 @@
|
||||
from models.group_member_info import GroupInfoUser
|
||||
from utils.image_utils import BuildMat
|
||||
from configs.path_config import IMAGE_PATH
|
||||
from typing import List, Union
|
||||
import asyncio
|
||||
import os
|
||||
from typing import List, Union
|
||||
|
||||
from configs.path_config import IMAGE_PATH
|
||||
from models.group_member_info import GroupInfoUser
|
||||
from utils.image_utils import BuildMat
|
||||
|
||||
|
||||
async def init_rank(
|
||||
title: str, all_user_id: List[int], all_user_data: List[int], group_id: int, total_count: int = 10
|
||||
title: str,
|
||||
all_user_id: List[int],
|
||||
all_user_data: List[int],
|
||||
group_id: int,
|
||||
total_count: int = 10,
|
||||
) -> BuildMat:
|
||||
"""
|
||||
说明:
|
||||
@@ -26,11 +31,11 @@ async def init_rank(
|
||||
max_user_id = all_user_id[all_user_data.index(_max)]
|
||||
all_user_id.remove(max_user_id)
|
||||
all_user_data.remove(_max)
|
||||
try:
|
||||
user_name = (
|
||||
await GroupInfoUser.get_member_info(max_user_id, group_id)
|
||||
).user_name
|
||||
except AttributeError:
|
||||
if user := await GroupInfoUser.get_or_none(
|
||||
user_qq=max_user_id, group_id=group_id
|
||||
):
|
||||
user_name = user.user_name
|
||||
else:
|
||||
user_name = f"{max_user_id}"
|
||||
_uname_lst.append(user_name)
|
||||
_num_lst.append(_max)
|
||||
|
||||
+54
-25
@@ -1,13 +1,18 @@
|
||||
from typing import Callable, List, Optional, Union
|
||||
from typing import Callable, List, Optional, Tuple, Union
|
||||
|
||||
from configs.config import Config
|
||||
from models.bag_user import BagUser
|
||||
from models.level_user import LevelUser
|
||||
from models.user_shop_gold_log import UserShopGoldLog
|
||||
from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent
|
||||
from nonebot.internal.matcher import Matcher
|
||||
from nonebot.internal.params import Depends
|
||||
from nonebot.params import Command
|
||||
|
||||
from configs.config import Config
|
||||
from models.bag_user import BagUser
|
||||
|
||||
# from models.bag_user import BagUser
|
||||
from models.level_user import LevelUser
|
||||
from models.user_shop_gold_log import UserShopGoldLog
|
||||
|
||||
# from models.user_shop_gold_log import UserShopGoldLog
|
||||
from utils.manager import admin_manager
|
||||
from utils.message_builder import at
|
||||
from utils.utils import (
|
||||
@@ -18,6 +23,19 @@ from utils.utils import (
|
||||
)
|
||||
|
||||
|
||||
def OneCommand():
|
||||
"""
|
||||
获取单个命令Command
|
||||
"""
|
||||
|
||||
async def dependency(
|
||||
cmd: Tuple[str, ...] = Command(),
|
||||
):
|
||||
return cmd[0] if cmd else None
|
||||
|
||||
return Depends(dependency)
|
||||
|
||||
|
||||
def AdminCheck(level: Optional[int] = None):
|
||||
"""
|
||||
说明:
|
||||
@@ -27,16 +45,19 @@ def AdminCheck(level: Optional[int] = None):
|
||||
"""
|
||||
|
||||
async def dependency(matcher: Matcher, event: GroupMessageEvent):
|
||||
plugin_level = admin_manager.get_plugin_module(matcher.plugin_name)
|
||||
user_level = await LevelUser.get_user_level(event.user_id, event.group_id)
|
||||
if level is None:
|
||||
if user_level < plugin_level:
|
||||
await matcher.finish(
|
||||
at(event.user_id) + f"你的权限不足喔,该功能需要的权限等级:{plugin_level}"
|
||||
)
|
||||
else:
|
||||
if user_level < level:
|
||||
await matcher.finish(at(event.user_id) + f"你的权限不足喔,该功能需要的权限等级:{level}")
|
||||
if name := matcher.plugin_name:
|
||||
plugin_level = admin_manager.get_plugin_level(name)
|
||||
user_level = await LevelUser.get_user_level(event.user_id, event.group_id)
|
||||
if level is None:
|
||||
if user_level < plugin_level:
|
||||
await matcher.finish(
|
||||
at(event.user_id) + f"你的权限不足喔,该功能需要的权限等级:{plugin_level}"
|
||||
)
|
||||
else:
|
||||
if user_level < level:
|
||||
await matcher.finish(
|
||||
at(event.user_id) + f"你的权限不足喔,该功能需要的权限等级:{level}"
|
||||
)
|
||||
|
||||
return Depends(dependency)
|
||||
|
||||
@@ -53,8 +74,13 @@ def CostGold(gold: int):
|
||||
if (await BagUser.get_gold(event.user_id, event.group_id)) < gold:
|
||||
await matcher.finish(at(event.user_id) + f"金币不足..该功能需要{gold}金币..")
|
||||
await BagUser.spend_gold(event.user_id, event.group_id, gold)
|
||||
await UserShopGoldLog.add_shop_log(
|
||||
event.user_id, event.group_id, 2, matcher.plugin_name, gold, 1
|
||||
await UserShopGoldLog.create(
|
||||
user_qq=event.user_id,
|
||||
group_id=event.group_id,
|
||||
type=2,
|
||||
name=matcher.plugin_name,
|
||||
num=1,
|
||||
spend_gold=gold,
|
||||
)
|
||||
|
||||
return Depends(dependency)
|
||||
@@ -78,10 +104,12 @@ def GetConfig(
|
||||
|
||||
async def dependency(matcher: Matcher):
|
||||
module_ = module or matcher.plugin_name
|
||||
value = Config.get_config(module_, config, default_value)
|
||||
if value is None:
|
||||
await matcher.finish(prompt or f"配置项 {config} 未填写!")
|
||||
return value
|
||||
if module_:
|
||||
value = Config.get_config(module_, config, default_value)
|
||||
if value is None and prompt:
|
||||
# await matcher.finish(prompt or f"配置项 {config} 未填写!")
|
||||
await matcher.finish(prompt)
|
||||
return value
|
||||
|
||||
return Depends(dependency)
|
||||
|
||||
@@ -102,10 +130,11 @@ def CheckConfig(
|
||||
|
||||
async def dependency(matcher: Matcher):
|
||||
module_ = module or matcher.plugin_name
|
||||
config_list = [config] if isinstance(config, str) else config
|
||||
for c in config_list:
|
||||
if Config.get_config(module_, c) is None:
|
||||
await matcher.finish(prompt or f"配置项 {c} 未填写!")
|
||||
if module_:
|
||||
config_list = [config] if isinstance(config, str) else config
|
||||
for c in config_list:
|
||||
if Config.get_config(module_, c) is None:
|
||||
await matcher.finish(prompt or f"配置项 {c} 未填写!")
|
||||
|
||||
return Depends(dependency)
|
||||
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
from typing import Optional, Dict, Union
|
||||
|
||||
from nonebot.adapters.onebot.v11 import Message, MessageSegment
|
||||
from pydantic import BaseModel
|
||||
import time
|
||||
|
||||
|
||||
class GameEntry(BaseModel):
|
||||
game_name: str
|
||||
module: str
|
||||
default_msg: str
|
||||
msg_data: Dict[int, Union[str, Message, MessageSegment]]
|
||||
timeout: int # 超时时限
|
||||
anti_concurrency: bool # 是否阻断
|
||||
|
||||
|
||||
class GroupGameStatus(BaseModel):
|
||||
game: GameEntry
|
||||
status: int
|
||||
time: time.time() # 创建时间
|
||||
|
||||
|
||||
class GameManager:
|
||||
def __init__(self):
|
||||
self._data = {}
|
||||
self._status = {}
|
||||
|
||||
def add_game(
|
||||
self,
|
||||
game_name: str,
|
||||
module: str,
|
||||
timeout: int,
|
||||
default_msg: Optional[str] = "游戏还未结束!",
|
||||
msg_data: Dict[int, Union[str, Message, MessageSegment]] = None,
|
||||
anti_concurrency: bool = True,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
参数:
|
||||
将游戏添加到游戏管理器
|
||||
说明:
|
||||
:param game_name: 游戏名称
|
||||
:param module: 模块名
|
||||
:param timeout: 超时时长
|
||||
:param default_msg: 默认回复消息
|
||||
:param msg_data: 不同状态回复的消息
|
||||
:param anti_concurrency: 是否阻断反并发
|
||||
"""
|
||||
self._data[module] = GameEntry(
|
||||
game_name=game_name,
|
||||
module=module,
|
||||
timeout=timeout,
|
||||
default_msg=default_msg,
|
||||
msg_data=msg_data or {},
|
||||
anti_concurrency=anti_concurrency,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def start(self, group_id: int, module: str):
|
||||
"""
|
||||
说明:
|
||||
游戏开始标记
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
:param module: 模块名
|
||||
"""
|
||||
if not self._status.get(group_id):
|
||||
self._status[group_id] = []
|
||||
if module not in [x.game.module for x in self._status[module]]:
|
||||
self._status[group_id].append(
|
||||
GroupGameStatus(game=self._data[module], status=0)
|
||||
)
|
||||
|
||||
def end(self, group_id: int, module: str):
|
||||
"""
|
||||
说明:
|
||||
游戏结束标记
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
:param module: 模块名
|
||||
"""
|
||||
if self._status.get(group_id) and module in [
|
||||
x.game.module for x in self._status[group_id]
|
||||
]:
|
||||
for x in self._status[group_id]:
|
||||
if self._status[group_id][x].game.module == module:
|
||||
self._status[group_id].remove(x)
|
||||
break
|
||||
|
||||
def set_status(self, group_id: int, module: str, status: int):
|
||||
"""
|
||||
说明:
|
||||
设置游戏状态,根据状态发送不同的提示消息 msg_data
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
:param module: 模块名
|
||||
:param status: 状态码
|
||||
"""
|
||||
if self._status.get(group_id) and module in [
|
||||
x.game.module for x in self._status[group_id]
|
||||
]:
|
||||
[x.game.module for x in self._status[group_id] if x.game.module == module][
|
||||
0
|
||||
].status = status
|
||||
|
||||
def check(self, group_id, module: str) -> Optional[str]:
|
||||
"""
|
||||
说明:
|
||||
检查群游戏当前状态并返回提示语句
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
:param module: 模块名
|
||||
"""
|
||||
if module in self._data and self._status.get(group_id):
|
||||
for x in self._status[group_id]:
|
||||
if x.game.anti_concurrency:
|
||||
return f"{x.game.game_name} 还未结束,请等待 {x.game.game_name} 游戏结束!"
|
||||
if self._status.get(group_id) and module in [
|
||||
x.game.module for x in self._status[group_id]
|
||||
]:
|
||||
group_game_status = [
|
||||
x.game.module for x in self._status[group_id] if x.game.module == module
|
||||
][0]
|
||||
if time.time() - group_game_status.time > group_game_status.game.timeout:
|
||||
# 超时结束
|
||||
self.end(group_id, module)
|
||||
else:
|
||||
return (
|
||||
group_game_status.game.msg_data.get(group_game_status.status)
|
||||
or group_game_status.game.default_msg
|
||||
)
|
||||
|
||||
|
||||
game_manager = GameManager()
|
||||
|
||||
|
||||
class Game:
|
||||
"""
|
||||
反并发,游戏重复开始
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
game_name: str,
|
||||
module: str,
|
||||
timeout: int = 60,
|
||||
default_msg: Optional[str] = None,
|
||||
msg_data: Dict[int, Union[str, Message, MessageSegment]] = None,
|
||||
anti_concurrency: bool = True,
|
||||
):
|
||||
"""
|
||||
参数:
|
||||
将游戏添加到游戏管理器
|
||||
说明:
|
||||
:param game_name: 游戏名称
|
||||
:param module: 模块名
|
||||
:param timeout: 超时时长
|
||||
:param default_msg: 默认回复消息
|
||||
:param msg_data: 不同状态回复的消息
|
||||
:param anti_concurrency: 是否阻断反并发
|
||||
"""
|
||||
self.module = module
|
||||
game_manager.add_game(
|
||||
game_name, module, timeout, default_msg, msg_data, anti_concurrency
|
||||
)
|
||||
|
||||
def start(self, group_id: int):
|
||||
"""
|
||||
说明:
|
||||
游戏开始标记
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
"""
|
||||
game_manager.start(group_id, self.module)
|
||||
|
||||
def end(self, group_id: int):
|
||||
"""
|
||||
说明:
|
||||
游戏结束标记
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
"""
|
||||
game_manager.end(group_id, self.module)
|
||||
|
||||
def set_status(self, group_id: int, status: int):
|
||||
"""
|
||||
说明:
|
||||
设置游戏状态,根据状态发送不同的提示消息 msg_data
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
:param status: 状态码
|
||||
"""
|
||||
game_manager.set_status(group_id, self.module, status)
|
||||
+62
-99
@@ -1,20 +1,23 @@
|
||||
from typing import Dict, Union, Optional, List, Any, Literal
|
||||
from utils.user_agent import get_user_agent
|
||||
from .utils import get_local_proxy
|
||||
from services.log import logger
|
||||
from pathlib import Path
|
||||
from httpx import Response
|
||||
from asyncio.exceptions import TimeoutError
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
from playwright.async_api import Page, BrowserContext
|
||||
from .message_builder import image
|
||||
from httpx import ConnectTimeout
|
||||
from .browser import get_browser
|
||||
from retrying import retry
|
||||
import asyncio
|
||||
from asyncio.exceptions import TimeoutError
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
from typing import Any, AsyncGenerator, Dict, List, Literal, Optional, Union
|
||||
|
||||
import aiofiles
|
||||
import httpx
|
||||
import rich
|
||||
from httpx import ConnectTimeout, Response
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
from playwright.async_api import BrowserContext, Page
|
||||
from retrying import retry
|
||||
|
||||
from services.log import logger
|
||||
from utils.user_agent import get_user_agent
|
||||
|
||||
from .browser import get_browser
|
||||
from .message_builder import image
|
||||
from .utils import get_local_proxy
|
||||
|
||||
|
||||
class AsyncHttpx:
|
||||
@@ -32,7 +35,7 @@ class AsyncHttpx:
|
||||
cookies: Optional[Dict[str, str]] = None,
|
||||
verify: bool = True,
|
||||
use_proxy: bool = True,
|
||||
proxy: Dict[str, str] = None,
|
||||
proxy: Optional[Dict[str, str]] = None,
|
||||
timeout: Optional[int] = 30,
|
||||
**kwargs,
|
||||
) -> Response:
|
||||
@@ -59,7 +62,7 @@ class AsyncHttpx:
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
timeout=timeout,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -174,7 +177,9 @@ class AsyncHttpx:
|
||||
headers = get_user_agent()
|
||||
proxy = proxy if proxy else cls.proxy if use_proxy else None
|
||||
try:
|
||||
async with httpx.AsyncClient(proxies=proxy, verify=verify) as client:
|
||||
async with httpx.AsyncClient(
|
||||
proxies=proxy, verify=verify
|
||||
) as client:
|
||||
async with client.stream(
|
||||
"GET",
|
||||
url,
|
||||
@@ -182,9 +187,11 @@ class AsyncHttpx:
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
timeout=timeout,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
) as response:
|
||||
logger.info(f"开始下载 {path.name}.. Path: {path.absolute()}")
|
||||
logger.info(
|
||||
f"开始下载 {path.name}.. Path: {path.absolute()}"
|
||||
)
|
||||
async with aiofiles.open(path, "wb") as wf:
|
||||
total = int(response.headers["Content-Length"])
|
||||
with rich.progress.Progress(
|
||||
@@ -192,13 +199,18 @@ class AsyncHttpx:
|
||||
"[progress.percentage]{task.percentage:>3.0f}%",
|
||||
rich.progress.BarColumn(bar_width=None),
|
||||
rich.progress.DownloadColumn(),
|
||||
rich.progress.TransferSpeedColumn()
|
||||
rich.progress.TransferSpeedColumn(),
|
||||
) as progress:
|
||||
download_task = progress.add_task("Download", total=total)
|
||||
download_task = progress.add_task(
|
||||
"Download", total=total
|
||||
)
|
||||
async for chunk in response.aiter_bytes():
|
||||
await wf.write(chunk)
|
||||
await wf.flush()
|
||||
progress.update(download_task, completed=response.num_bytes_downloaded)
|
||||
progress.update(
|
||||
download_task,
|
||||
completed=response.num_bytes_downloaded,
|
||||
)
|
||||
logger.info(f"下载 {url} 成功.. Path:{path.absolute()}")
|
||||
return True
|
||||
except (TimeoutError, ConnectTimeout):
|
||||
@@ -274,7 +286,7 @@ class AsyncHttpx:
|
||||
use_proxy=use_proxy,
|
||||
timeout=timeout,
|
||||
proxy=proxy,
|
||||
** kwargs,
|
||||
**kwargs,
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -285,64 +297,23 @@ class AsyncHttpx:
|
||||
|
||||
|
||||
class AsyncPlaywright:
|
||||
|
||||
@classmethod
|
||||
async def _new_page(cls, user_agent: Optional[str] = None, **kwargs) -> Page:
|
||||
@asynccontextmanager
|
||||
async def new_page(cls, **kwargs) -> AsyncGenerator[Page, None]:
|
||||
"""
|
||||
说明:
|
||||
获取一个新页面
|
||||
参数:
|
||||
:param user_agent: 请求头
|
||||
"""
|
||||
browser = await get_browser()
|
||||
if browser:
|
||||
return await browser.new_page(user_agent=user_agent, **kwargs)
|
||||
raise BrowserIsNone("获取Browser失败...")
|
||||
|
||||
@classmethod
|
||||
async def new_context(cls, user_agent: Optional[str] = None, **kwargs) -> BrowserContext:
|
||||
"""
|
||||
说明:
|
||||
获取一个新上下文
|
||||
参数:
|
||||
:param user_agent: 请求头
|
||||
"""
|
||||
browser = await get_browser()
|
||||
if browser:
|
||||
return await browser.new_context(user_agent=user_agent, **kwargs)
|
||||
raise BrowserIsNone("获取Browser失败...")
|
||||
|
||||
@classmethod
|
||||
async def goto(
|
||||
cls,
|
||||
url: str,
|
||||
*,
|
||||
timeout: Optional[float] = 100000,
|
||||
wait_until: Optional[
|
||||
Literal["domcontentloaded", "load", "networkidle"]
|
||||
] = "networkidle",
|
||||
referer: str = None,
|
||||
**kwargs
|
||||
) -> Optional[Page]:
|
||||
"""
|
||||
说明:
|
||||
goto
|
||||
参数:
|
||||
:param url: 网址
|
||||
:param timeout: 超时限制
|
||||
:param wait_until: 等待类型
|
||||
:param referer:
|
||||
"""
|
||||
page = None
|
||||
browser = get_browser()
|
||||
ctx = await browser.new_context(**kwargs)
|
||||
page = await ctx.new_page()
|
||||
try:
|
||||
page = await cls._new_page(**kwargs)
|
||||
await page.goto(url, timeout=timeout, wait_until=wait_until, referer=referer)
|
||||
return page
|
||||
except Exception as e:
|
||||
logger.warning(f"Playwright 访问 url:{url} 发生错误 {type(e)}:{e}")
|
||||
if page:
|
||||
await page.close()
|
||||
return None
|
||||
yield page
|
||||
finally:
|
||||
await page.close()
|
||||
await ctx.close()
|
||||
|
||||
@classmethod
|
||||
async def screenshot(
|
||||
@@ -352,13 +323,13 @@ class AsyncPlaywright:
|
||||
element: Union[str, List[str]],
|
||||
*,
|
||||
wait_time: Optional[int] = None,
|
||||
viewport_size: Dict[str, int] = None,
|
||||
viewport_size: Optional[Dict[str, int]] = None,
|
||||
wait_until: Optional[
|
||||
Literal["domcontentloaded", "load", "networkidle"]
|
||||
] = "networkidle",
|
||||
timeout: float = None,
|
||||
type_: Literal["jpeg", "png"] = None,
|
||||
**kwargs
|
||||
timeout: Optional[float] = None,
|
||||
type_: Optional[Literal["jpeg", "png"]] = None,
|
||||
**kwargs,
|
||||
) -> Optional[MessageSegment]:
|
||||
"""
|
||||
说明:
|
||||
@@ -373,33 +344,25 @@ class AsyncPlaywright:
|
||||
:param timeout: 超时限制
|
||||
:param type_: 保存类型
|
||||
"""
|
||||
page = None
|
||||
if viewport_size is None:
|
||||
viewport_size = dict(width=2560, height=1080)
|
||||
if isinstance(path, str):
|
||||
path = Path(path)
|
||||
try:
|
||||
page = await cls.goto(url, wait_until=wait_until, **kwargs)
|
||||
await page.set_viewport_size(viewport_size)
|
||||
if isinstance(element, str):
|
||||
if wait_time:
|
||||
card = await page.wait_for_selector(element, timeout=wait_time * 1000)
|
||||
else:
|
||||
card = await page.query_selector(element)
|
||||
else:
|
||||
card = page
|
||||
for e in element:
|
||||
if wait_time:
|
||||
card = await card.wait_for_selector(e, timeout=wait_time * 1000)
|
||||
else:
|
||||
card = await card.query_selector(e)
|
||||
await card.screenshot(path=path, timeout=timeout, type=type_)
|
||||
return image(path)
|
||||
except Exception as e:
|
||||
logger.warning(f"Playwright 截图 url:{url} element:{element} 发生错误 {type(e)}:{e}")
|
||||
finally:
|
||||
if page:
|
||||
await page.close()
|
||||
wait_time = wait_time * 1000 if wait_time else None
|
||||
if isinstance(element, str):
|
||||
element_list = [element]
|
||||
else:
|
||||
element_list = element
|
||||
async with cls.new_page(viewport=viewport_size) as page:
|
||||
await page.goto(url, timeout=timeout, wait_until=wait_until)
|
||||
card = page
|
||||
for e in element_list:
|
||||
if not card:
|
||||
return None
|
||||
card = await card.wait_for_selector(e, timeout=wait_time)
|
||||
if card:
|
||||
await card.screenshot(path=path, timeout=timeout, type=type_)
|
||||
return image(path)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
+39
-15
@@ -7,23 +7,28 @@ import uuid
|
||||
from io import BytesIO
|
||||
from math import ceil
|
||||
from pathlib import Path
|
||||
from typing import List, Literal, Optional, Tuple, Union, Callable, Awaitable
|
||||
|
||||
from PIL.ImageFont import FreeTypeFont
|
||||
from nonebot.utils import is_coroutine_callable
|
||||
from typing import Awaitable, Callable, List, Literal, Optional, Tuple, Union
|
||||
|
||||
import cv2
|
||||
import imagehash
|
||||
from configs.path_config import FONT_PATH, IMAGE_PATH
|
||||
from imagehash import ImageHash
|
||||
from matplotlib import pyplot as plt
|
||||
from nonebot.utils import is_coroutine_callable
|
||||
from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont
|
||||
from PIL.ImageFont import FreeTypeFont
|
||||
|
||||
from configs.path_config import FONT_PATH, IMAGE_PATH
|
||||
from services import logger
|
||||
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||
Image.MAX_IMAGE_PIXELS = None
|
||||
|
||||
|
||||
ModeType = Literal[
|
||||
"1", "CMYK", "F", "HSV", "I", "L", "LAB", "P", "RGB", "RGBA", "RGBX", "YCbCr"
|
||||
]
|
||||
|
||||
|
||||
def compare_image_with_hash(
|
||||
image_file1: str, image_file2: str, max_dif: int = 1.5
|
||||
) -> bool:
|
||||
@@ -154,7 +159,7 @@ class BuildImage:
|
||||
paste_image_width: int = 0,
|
||||
paste_image_height: int = 0,
|
||||
color: Union[str, Tuple[int, int, int], Tuple[int, int, int, int]] = None,
|
||||
image_mode: str = "RGBA",
|
||||
image_mode: ModeType = "RGBA",
|
||||
font_size: int = 10,
|
||||
background: Union[Optional[str], BytesIO, Path] = None,
|
||||
font: str = "yz.ttf",
|
||||
@@ -162,7 +167,7 @@ class BuildImage:
|
||||
is_alpha: bool = False,
|
||||
plain_text: Optional[str] = None,
|
||||
font_color: Optional[Union[str, Tuple[int, int, int]]] = None,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
参数:
|
||||
@@ -320,6 +325,19 @@ class BuildImage:
|
||||
self.markImg.paste(img, pos)
|
||||
self._current_w += self.paste_image_width
|
||||
|
||||
@classmethod
|
||||
def get_text_size(cls, msg: str, font: str, font_size: int) -> Tuple[int, int]:
|
||||
"""
|
||||
说明:
|
||||
获取文字在该图片 font_size 下所需要的空间
|
||||
参数:
|
||||
:param msg: 文字内容
|
||||
:param font: 字体
|
||||
:param font_size: 字体大小
|
||||
"""
|
||||
font = cls.load_font(font, font_size)
|
||||
return font.getsize(msg)
|
||||
|
||||
def getsize(self, msg: str) -> Tuple[int, int]:
|
||||
"""
|
||||
说明:
|
||||
@@ -395,7 +413,7 @@ class BuildImage:
|
||||
center_type: Optional[Literal["center", "by_height", "by_width"]] = None,
|
||||
font: Union[FreeTypeFont, str] = None,
|
||||
font_size: Optional[int] = None,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
说明:
|
||||
@@ -408,7 +426,9 @@ class BuildImage:
|
||||
:param font: 字体
|
||||
:param font_size: 字体大小
|
||||
"""
|
||||
await self.loop.run_in_executor(None, self.text, pos, text, fill, center_type, font, font_size, **kwargs)
|
||||
await self.loop.run_in_executor(
|
||||
None, self.text, pos, text, fill, center_type, font, font_size, **kwargs
|
||||
)
|
||||
|
||||
def text(
|
||||
self,
|
||||
@@ -418,7 +438,7 @@ class BuildImage:
|
||||
center_type: Optional[Literal["center", "by_height", "by_width"]] = None,
|
||||
font: Union[FreeTypeFont, str] = None,
|
||||
font_size: Optional[int] = None,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
说明:
|
||||
@@ -577,9 +597,9 @@ class BuildImage:
|
||||
buf = BytesIO()
|
||||
self.markImg.save(buf, format="PNG")
|
||||
base64_str = base64.b64encode(buf.getvalue()).decode()
|
||||
return base64_str
|
||||
return "base64://" + base64_str
|
||||
|
||||
def convert(self, type_: str):
|
||||
def convert(self, type_: ModeType):
|
||||
"""
|
||||
说明:
|
||||
修改图片类型
|
||||
@@ -792,7 +812,7 @@ class BuildImage:
|
||||
"""
|
||||
self.markImg = self.markImg.rotate(angle, expand=expand)
|
||||
|
||||
async def atranspose(self, angle: int):
|
||||
async def atranspose(self, angle: Literal[0, 1, 2, 3, 4, 5, 6]):
|
||||
"""
|
||||
说明:
|
||||
异步 旋转图片(包括边框)
|
||||
@@ -801,7 +821,7 @@ class BuildImage:
|
||||
"""
|
||||
await self.loop.run_in_executor(None, self.transpose, angle)
|
||||
|
||||
def transpose(self, angle: int):
|
||||
def transpose(self, angle: Literal[0, 1, 2, 3, 4, 5, 6]):
|
||||
"""
|
||||
说明:
|
||||
旋转图片(包括边框)
|
||||
@@ -1663,7 +1683,11 @@ async def build_sort_image(
|
||||
image_group: List[List[BuildImage]],
|
||||
h: Optional[int] = None,
|
||||
padding_top: int = 200,
|
||||
color: Union[str, Tuple[int, int, int], Tuple[int, int, int, int]] = (255, 255, 255),
|
||||
color: Union[str, Tuple[int, int, int], Tuple[int, int, int, int]] = (
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
),
|
||||
background_path: Optional[Path] = None,
|
||||
background_handle: Callable[[BuildImage], Optional[Awaitable]] = None,
|
||||
) -> BuildImage:
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from .models import AdminSetting
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from utils.manager.data_class import StaticData
|
||||
from typing import List, Optional, Dict
|
||||
|
||||
from .models import AdminSetting
|
||||
|
||||
|
||||
class AdminManager(StaticData):
|
||||
@@ -59,7 +61,7 @@ class AdminManager(StaticData):
|
||||
def get_plugin_level(self, plugin: str) -> int:
|
||||
"""
|
||||
说明:
|
||||
获取插件等级
|
||||
获取插件权限
|
||||
参数:
|
||||
:param plugin: 模块名
|
||||
"""
|
||||
@@ -75,6 +77,7 @@ class AdminManager(StaticData):
|
||||
:param cmd: 命令
|
||||
"""
|
||||
for key in self._data.keys():
|
||||
if self._data[key].cmd and cmd in self._data[key].cmd:
|
||||
return key
|
||||
if data := self._data.get(key):
|
||||
if data.cmd and cmd in data.cmd:
|
||||
return key
|
||||
return None
|
||||
|
||||
+41
-47
@@ -1,46 +1,43 @@
|
||||
import io
|
||||
from pathlib import Path
|
||||
from typing import List, Union
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from nonebot.adapters.onebot.v11.message import Message, MessageSegment
|
||||
|
||||
from configs.config import NICKNAME
|
||||
from configs.path_config import IMAGE_PATH, RECORD_PATH
|
||||
from nonebot.adapters.onebot.v11.message import MessageSegment, Message
|
||||
from services.log import logger
|
||||
from utils.image_utils import BuildImage
|
||||
|
||||
|
||||
def image(
|
||||
file: Union[str, Path, bytes] = None,
|
||||
path: str = None,
|
||||
b64: str = None,
|
||||
) -> Union[MessageSegment, str]:
|
||||
file: Optional[Union[str, Path, bytes, BuildImage, io.BytesIO]] = None,
|
||||
b64: Optional[str] = None,
|
||||
) -> MessageSegment:
|
||||
"""
|
||||
说明:
|
||||
生成一个 MessageSegment.image 消息
|
||||
生成顺序:绝对路径(abspath) > base64(b64) > img_name
|
||||
参数:
|
||||
:param file: 图片文件名称,默认在 resource/img 目录下
|
||||
:param path: 图片所在路径,默认在 resource/img 目录下
|
||||
:param b64: 图片base64
|
||||
:param file: 图片文件
|
||||
:param b64: 图片base64(兼容旧方法)
|
||||
"""
|
||||
if b64:
|
||||
file = b64 if b64.startswith("base64://") else ("base64://" + b64)
|
||||
if isinstance(file, str):
|
||||
if file.startswith(("http", "base64://")):
|
||||
return MessageSegment.image(file)
|
||||
else:
|
||||
return MessageSegment.image(IMAGE_PATH / file)
|
||||
if isinstance(file, Path):
|
||||
if file.exists():
|
||||
return MessageSegment.image(file)
|
||||
logger.warning(f"图片 {file.absolute()}缺失...")
|
||||
return ""
|
||||
elif isinstance(file, (bytes, io.BytesIO)):
|
||||
if isinstance(file, (bytes, io.BytesIO)):
|
||||
return MessageSegment.image(file)
|
||||
elif b64:
|
||||
return MessageSegment.image(b64 if "base64://" in b64 else "base64://" + b64)
|
||||
else:
|
||||
if file.startswith("http"):
|
||||
return MessageSegment.image(file)
|
||||
if len(file.split(".")) == 1:
|
||||
file += ".jpg"
|
||||
if (file := IMAGE_PATH / path / file if path else IMAGE_PATH / file).exists():
|
||||
return MessageSegment.image(file)
|
||||
else:
|
||||
logger.warning(f"图片 {file} 缺失...")
|
||||
return ""
|
||||
if isinstance(file, BuildImage):
|
||||
return MessageSegment.image(file.pic2bs4())
|
||||
return MessageSegment.image("")
|
||||
|
||||
|
||||
def at(qq: Union[int, str]) -> MessageSegment:
|
||||
@@ -53,29 +50,25 @@ def at(qq: Union[int, str]) -> MessageSegment:
|
||||
return MessageSegment.at(qq)
|
||||
|
||||
|
||||
def record(voice_name: str, path: str = None) -> MessageSegment or str:
|
||||
def record(file: Union[Path, str, bytes, io.BytesIO]) -> Union[MessageSegment, str]:
|
||||
"""
|
||||
说明:
|
||||
生成一个 MessageSegment.record 消息
|
||||
参数:
|
||||
:param voice_name: 音频文件名称,默认在 resource/voice 目录下
|
||||
:param path: 音频文件路径,默认在 resource/voice 目录下
|
||||
:param file: 音频文件名称,默认在 resource/voice 目录下
|
||||
"""
|
||||
if len(voice_name.split(".")) == 1:
|
||||
voice_name += ".mp3"
|
||||
file = (
|
||||
Path(RECORD_PATH) / path / voice_name
|
||||
if path
|
||||
else Path(RECORD_PATH) / voice_name
|
||||
)
|
||||
if "http" in voice_name:
|
||||
return MessageSegment.record(voice_name)
|
||||
if file.exists():
|
||||
result = MessageSegment.record(f"file:///{file.absolute()}")
|
||||
return result
|
||||
else:
|
||||
logger.warning(f"语音{file.absolute()}缺失...")
|
||||
return ""
|
||||
if isinstance(file, Path):
|
||||
if file.exists():
|
||||
return MessageSegment.record(file)
|
||||
logger.warning(f"音频 {file.absolute()}缺失...")
|
||||
if isinstance(file, (bytes, io.BytesIO)):
|
||||
return MessageSegment.record(file)
|
||||
if isinstance(file, str):
|
||||
if "http" in file:
|
||||
return MessageSegment.record(file)
|
||||
else:
|
||||
return MessageSegment.record(RECORD_PATH / file)
|
||||
return ""
|
||||
|
||||
|
||||
def text(msg: str) -> MessageSegment:
|
||||
@@ -99,7 +92,7 @@ def contact_user(qq: int) -> MessageSegment:
|
||||
|
||||
|
||||
def share(
|
||||
url: str, title: str, content: str = None, image_url: str = None
|
||||
url: str, title: str, content: Optional[str] = None, image_url: Optional[str] = None
|
||||
) -> MessageSegment:
|
||||
"""
|
||||
说明:
|
||||
@@ -158,7 +151,9 @@ def music(type_: str, id_: int) -> MessageSegment:
|
||||
|
||||
|
||||
def custom_forward_msg(
|
||||
msg_list: List[Union[str, Message]], uin: Union[int, str], name: str = f"这里是{NICKNAME}"
|
||||
msg_list: List[Union[str, Message]],
|
||||
uin: Union[int, str],
|
||||
name: str = f"这里是{NICKNAME}",
|
||||
) -> List[dict]:
|
||||
"""
|
||||
说明:
|
||||
@@ -202,11 +197,10 @@ class MessageBuilder:
|
||||
|
||||
def image(
|
||||
self,
|
||||
file: Union[str, Path, bytes] = None,
|
||||
path: str = None,
|
||||
b64: str = None,
|
||||
file: Optional[Union[str, Path, bytes]] = None,
|
||||
b64: Optional[str] = None,
|
||||
):
|
||||
return MessageBuilder(self._msg + image(file, path, b64))
|
||||
return MessageBuilder(self._msg + image(file, b64))
|
||||
|
||||
def at(self, qq: int):
|
||||
return MessageBuilder(self._msg + at(qq))
|
||||
|
||||
+131
-53
@@ -1,17 +1,20 @@
|
||||
from datetime import datetime
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from nonebot import require
|
||||
from configs.config import SYSTEM_PROXY, Config
|
||||
from typing import List, Union, Optional, Type, Any
|
||||
from nonebot.adapters.onebot.v11 import Bot, Message
|
||||
from nonebot.matcher import matchers, Matcher
|
||||
from services.log import logger
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, List, Optional, Set, Type, Union
|
||||
|
||||
import httpx
|
||||
import nonebot
|
||||
import pytz
|
||||
import pypinyin
|
||||
import time
|
||||
import pytz
|
||||
from nonebot import require
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.adapters.onebot.v11 import Message, MessageSegment
|
||||
from nonebot.matcher import Matcher, matchers
|
||||
|
||||
from configs.config import SYSTEM_PROXY, Config
|
||||
from services.log import logger
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
@@ -25,9 +28,9 @@ scheduler = scheduler
|
||||
|
||||
# 全局字典
|
||||
GDict = {
|
||||
"run_sql": [], # 需要启动前运行的sql语句
|
||||
"_shop_before_handle": {}, # 商品使用前函数
|
||||
"_shop_after_handle": {}, # 商品使用后函数
|
||||
"run_sql": [], # 需要启动前运行的sql语句
|
||||
"_shop_before_handle": {}, # 商品使用前函数
|
||||
"_shop_after_handle": {}, # 商品使用后函数
|
||||
}
|
||||
|
||||
|
||||
@@ -119,8 +122,8 @@ class BanCheckLimiter:
|
||||
self.mint[key] = 0
|
||||
return False
|
||||
if (
|
||||
self.mint[key] >= self.default_count
|
||||
and time.time() - self.mtime[key] < self.default_check_time
|
||||
self.mint[key] >= self.default_count
|
||||
and time.time() - self.mtime[key] < self.default_check_time
|
||||
):
|
||||
self.mtime[key] = time.time()
|
||||
self.mint[key] = 0
|
||||
@@ -157,13 +160,15 @@ class DailyNumberLimiter:
|
||||
self.count[key] = 0
|
||||
|
||||
|
||||
def is_number(s: str) -> bool:
|
||||
def is_number(s: Union[int, str]) -> bool:
|
||||
"""
|
||||
说明:
|
||||
检测 s 是否为数字
|
||||
参数:
|
||||
:param s: 文本
|
||||
"""
|
||||
if isinstance(s, int):
|
||||
return True
|
||||
try:
|
||||
float(s)
|
||||
return True
|
||||
@@ -179,14 +184,14 @@ def is_number(s: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def get_bot() -> Optional[Bot]:
|
||||
def get_bot(id_: Optional[str] = None) -> Optional[Bot]:
|
||||
"""
|
||||
说明:
|
||||
获取 bot 对象
|
||||
"""
|
||||
try:
|
||||
return list(nonebot.get_bots().values())[0]
|
||||
except IndexError:
|
||||
return nonebot.get_bot(id_)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
@@ -213,14 +218,15 @@ def get_message_at(data: Union[str, Message]) -> List[int]:
|
||||
说明:
|
||||
获取消息中所有的 at 对象的 qq
|
||||
参数:
|
||||
:param data: event.json()
|
||||
:param data: event.json(), event.message
|
||||
"""
|
||||
qq_list = []
|
||||
if isinstance(data, str):
|
||||
data = json.loads(data)
|
||||
for msg in data["message"]:
|
||||
if msg["type"] == "at":
|
||||
qq_list.append(int(msg["data"]["qq"]))
|
||||
event = json.loads(data)
|
||||
if data and (message := event.get("message")):
|
||||
for msg in message:
|
||||
if msg and msg.get("type") == "at":
|
||||
qq_list.append(int(msg["data"]["qq"]))
|
||||
else:
|
||||
for seg in data:
|
||||
if seg.type == "at":
|
||||
@@ -237,10 +243,11 @@ def get_message_img(data: Union[str, Message]) -> List[str]:
|
||||
"""
|
||||
img_list = []
|
||||
if isinstance(data, str):
|
||||
data = json.loads(data)
|
||||
for msg in data["message"]:
|
||||
if msg["type"] == "image":
|
||||
img_list.append(msg["data"]["url"])
|
||||
event = json.loads(data)
|
||||
if data and (message := event.get("message")):
|
||||
for msg in message:
|
||||
if msg["type"] == "image":
|
||||
img_list.append(msg["data"]["url"])
|
||||
else:
|
||||
for seg in data["image"]:
|
||||
img_list.append(seg.data["url"])
|
||||
@@ -256,10 +263,11 @@ def get_message_face(data: Union[str, Message]) -> List[str]:
|
||||
"""
|
||||
face_list = []
|
||||
if isinstance(data, str):
|
||||
data = json.loads(data)
|
||||
for msg in data["message"]:
|
||||
if msg["type"] == "face":
|
||||
face_list.append(msg["data"]["id"])
|
||||
event = json.loads(data)
|
||||
if data and (message := event.get("message")):
|
||||
for msg in message:
|
||||
if msg["type"] == "face":
|
||||
face_list.append(msg["data"]["id"])
|
||||
else:
|
||||
for seg in data["face"]:
|
||||
face_list.append(seg.data["id"])
|
||||
@@ -275,10 +283,11 @@ def get_message_img_file(data: Union[str, Message]) -> List[str]:
|
||||
"""
|
||||
file_list = []
|
||||
if isinstance(data, str):
|
||||
data = json.loads(data)
|
||||
for msg in data["message"]:
|
||||
if msg["type"] == "image":
|
||||
file_list.append(msg["data"]["file"])
|
||||
event = json.loads(data)
|
||||
if data and (message := event.get("message")):
|
||||
for msg in message:
|
||||
if msg["type"] == "image":
|
||||
file_list.append(msg["data"]["file"])
|
||||
else:
|
||||
for seg in data["image"]:
|
||||
file_list.append(seg.data["file"])
|
||||
@@ -294,10 +303,13 @@ def get_message_text(data: Union[str, Message]) -> str:
|
||||
"""
|
||||
result = ""
|
||||
if isinstance(data, str):
|
||||
data = json.loads(data)
|
||||
for msg in data["message"]:
|
||||
if msg["type"] == "text":
|
||||
result += msg["data"]["text"].strip() + " "
|
||||
event = json.loads(data)
|
||||
if data and (message := event.get("message")):
|
||||
if isinstance(message, str):
|
||||
return message.strip()
|
||||
for msg in message:
|
||||
if msg["type"] == "text":
|
||||
result += msg["data"]["text"].strip() + " "
|
||||
return result.strip()
|
||||
else:
|
||||
for seg in data["text"]:
|
||||
@@ -314,10 +326,11 @@ def get_message_record(data: Union[str, Message]) -> List[str]:
|
||||
"""
|
||||
record_list = []
|
||||
if isinstance(data, str):
|
||||
data = json.loads(data)
|
||||
for msg in data["message"]:
|
||||
if msg["type"] == "record":
|
||||
record_list.append(msg["data"]["url"])
|
||||
event = json.loads(data)
|
||||
if data and (message := event.get("message")):
|
||||
for msg in message:
|
||||
if msg["type"] == "record":
|
||||
record_list.append(msg["data"]["url"])
|
||||
else:
|
||||
for seg in data["record"]:
|
||||
record_list.append(seg.data["url"])
|
||||
@@ -333,21 +346,22 @@ def get_message_json(data: str) -> List[dict]:
|
||||
"""
|
||||
try:
|
||||
json_list = []
|
||||
data = json.loads(data)
|
||||
for msg in data["message"]:
|
||||
if msg["type"] == "json":
|
||||
json_list.append(msg["data"])
|
||||
event = json.loads(data)
|
||||
if data and (message := event.get("message")):
|
||||
for msg in message:
|
||||
if msg["type"] == "json":
|
||||
json_list.append(msg["data"])
|
||||
return json_list
|
||||
except KeyError:
|
||||
return []
|
||||
|
||||
|
||||
def get_local_proxy():
|
||||
def get_local_proxy() -> Optional[str]:
|
||||
"""
|
||||
说明:
|
||||
获取 config.py 中设置的代理
|
||||
"""
|
||||
return SYSTEM_PROXY if SYSTEM_PROXY else None
|
||||
return SYSTEM_PROXY or None
|
||||
|
||||
|
||||
def is_chinese(word: str) -> bool:
|
||||
@@ -411,7 +425,7 @@ def cn2py(word: str) -> str:
|
||||
|
||||
|
||||
def change_pixiv_image_links(
|
||||
url: str, size: Optional[str] = None, nginx_url: Optional[str] = None
|
||||
url: str, size: Optional[str] = None, nginx_url: Optional[str] = None
|
||||
):
|
||||
"""
|
||||
说明:
|
||||
@@ -431,8 +445,8 @@ def change_pixiv_image_links(
|
||||
if nginx_url:
|
||||
url = (
|
||||
url.replace("i.pximg.net", nginx_url)
|
||||
.replace("i.pixiv.cat", nginx_url)
|
||||
.replace("_webp", "")
|
||||
.replace("i.pixiv.cat", nginx_url)
|
||||
.replace("_webp", "")
|
||||
)
|
||||
return url
|
||||
|
||||
@@ -449,5 +463,69 @@ def change_img_md5(path_file: Union[str, Path]) -> bool:
|
||||
f.write(str(int(time.time() * 1000)))
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"改变图片MD5发生错误 {type(e)}:{e} Path:{path_file}")
|
||||
logger.warning(f"改变图片MD5错误 Path:{path_file}", e=e)
|
||||
return False
|
||||
|
||||
|
||||
async def broadcast_group(
|
||||
message: Union[str, Message, MessageSegment],
|
||||
bot: Optional[Union[Bot, List[Bot]]] = None,
|
||||
bot_id: Optional[Union[str, Set[str]]] = None,
|
||||
ignore_group: Optional[Set[int]] = None,
|
||||
check_func: Optional[Callable[[int], bool]] = None,
|
||||
log_cmd: Optional[str] = None,
|
||||
):
|
||||
"""获取所有Bot或指定Bot对象广播群聊
|
||||
|
||||
Args:
|
||||
message (Any): 广播消息内容
|
||||
bot (Optional[Bot], optional): 指定bot对象. Defaults to None.
|
||||
bot_id (Optional[str], optional): 指定bot id. Defaults to None.
|
||||
ignore_group (Optional[List[int]], optional): 忽略群聊列表. Defaults to None.
|
||||
check_func (Optional[Callable[[int], bool]], optional): 发送前对群聊检测方法,判断是否发送. Defaults to None.
|
||||
log_cmd (Optional[str], optional): 日志标记. Defaults to None.
|
||||
"""
|
||||
if not message:
|
||||
raise ValueError("群聊广播消息不能为空")
|
||||
bot_dict = nonebot.get_bots()
|
||||
bot_list: List[Bot] = []
|
||||
if bot:
|
||||
if isinstance(bot, list):
|
||||
bot_list = bot
|
||||
else:
|
||||
bot_list.append(bot)
|
||||
elif bot_id:
|
||||
_bot_id_list = bot_id
|
||||
if isinstance(bot_id, str):
|
||||
_bot_id_list = [bot_id]
|
||||
for id_ in _bot_id_list:
|
||||
if bot_id in bot_dict:
|
||||
bot_list.append(bot_dict[bot_id])
|
||||
else:
|
||||
logger.warning(f"Bot:{id_} 对象未连接或不存在")
|
||||
else:
|
||||
bot_list = list(bot_dict.values())
|
||||
_used_group = []
|
||||
for _bot in bot_list:
|
||||
try:
|
||||
if _group_list := await _bot.get_group_list():
|
||||
group_id_list = [g["group_id"] for g in _group_list]
|
||||
for group_id in set(group_id_list):
|
||||
try:
|
||||
if (
|
||||
ignore_group and group_id in ignore_group
|
||||
) or group_id in _used_group:
|
||||
continue
|
||||
if check_func and not check_func(group_id):
|
||||
continue
|
||||
_used_group.append(group_id)
|
||||
await _bot.send_group_msg(group_id=group_id, message=message)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"广播群发消息失败: {message}",
|
||||
command=log_cmd,
|
||||
group_id=group_id,
|
||||
e=e,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Bot: {_bot.self_id} 获取群聊列表失败", command=log_cmd, e=e)
|
||||
|
||||
Reference in New Issue
Block a user