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:
HibiKier
2023-02-18 18:46:54 +08:00
parent a6105e8a1b
commit 7685b95031
134 changed files with 5402 additions and 6033 deletions
+24 -13
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+192
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+8 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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)