From eb8789c823e89823546998965a54c3b1f30b5528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wuyi=E6=97=A0=E7=96=91?= <444835641@qq.com> Date: Mon, 20 Feb 2023 23:27:20 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E4=BD=BF=E5=A4=A7=E9=83=A8?= =?UTF-8?q?=E5=88=86=E5=8A=9F=E8=83=BD=E5=8F=AF=E7=94=A8=E3=80=81=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E9=83=A8=E5=88=86=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + README.md | 4 +- gsuid_core/bot.py | 49 ++++++++++++++++++ gsuid_core/client.py | 5 +- gsuid_core/core.py | 2 +- gsuid_core/gs_logger.py | 42 +++++++++++++++ gsuid_core/gss.py | 6 +++ gsuid_core/handler.py | 7 ++- gsuid_core/hook.py | 19 +++++++ gsuid_core/logger.py | 22 ++++++++ gsuid_core/models.py | 8 +-- gsuid_core/server.py | 111 ++++++++++------------------------------ gsuid_core/sv.py | 22 +++++--- 13 files changed, 196 insertions(+), 103 deletions(-) create mode 100644 gsuid_core/bot.py create mode 100644 gsuid_core/gs_logger.py create mode 100644 gsuid_core/gss.py create mode 100644 gsuid_core/hook.py create mode 100644 gsuid_core/logger.py diff --git a/.gitignore b/.gitignore index 773b548..8818051 100644 --- a/.gitignore +++ b/.gitignore @@ -663,3 +663,5 @@ FodyWeavers.xsd # End of https://www.toptal.com/developers/gitignore/api/visualstudio,visualstudiocode,jetbrains+all,python,data config.json +res_data +GsData.db diff --git a/README.md b/README.md index 98e463c..37d464e 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ poetry run python core.py import asyncio from gsuid_core.sv import SL, SV -from gsuid_core.server import Bot +from gsuid_core.bot import Bot from gsuid_core.models import MessageContent @@ -52,7 +52,7 @@ async def get_switch_msg(bot: Bot, msg: MessageContent): im = await process(name) # 自己的业务逻辑 await bot.logger.info('正在进行[关闭/开启开关]') # 发送loger await bot.send(im) # 发送消息 - + sv=SV( name='复杂的服务', # 定义一组服务`开关`, permission=3, # 权限 0为master,1为superuser,2为群的群主&管理员,3为普通 diff --git a/gsuid_core/bot.py b/gsuid_core/bot.py new file mode 100644 index 0000000..cd47120 --- /dev/null +++ b/gsuid_core/bot.py @@ -0,0 +1,49 @@ +import asyncio +from typing import List, Union, Optional + +from logger import logger +from fastapi import WebSocket +from gs_logger import GsLogger +from segment import MessageSegment +from msgspec import json as msgjson +from models import Message, MessageSend + + +class Bot: + def __init__(self, _id: str, ws: WebSocket): + self.bot_id = _id + self.bot = ws + self.logger = GsLogger(self.bot_id, ws) + self.queue = asyncio.queues.Queue() + self.background_tasks = set() + self.user_id: Optional[str] = None + self.group_id: Optional[str] = None + self.user_type: Optional[str] = None + + async def send(self, message: Union[Message, List[Message], str, bytes]): + if isinstance(message, Message): + message = [message] + elif isinstance(message, str): + if message.startswith('base64://'): + message = [MessageSegment.image(message)] + else: + message = [MessageSegment.text(message)] + elif isinstance(message, bytes): + message = [MessageSegment.image(message)] + send = MessageSend( + content=message, + bot_id=self.bot_id, + target_type=self.user_type, + target_id=self.group_id if self.group_id else self.user_id, + ) + logger.info(f'[发送消息] {send}') + await self.bot.send_bytes(msgjson.encode(send)) + + async def _process(self): + while True: + data = await self.queue.get() + task = asyncio.create_task(data) + self.background_tasks.add(task) + task.add_done_callback( + lambda _: self.background_tasks.discard(task) + ) diff --git a/gsuid_core/client.py b/gsuid_core/client.py index 17ba5aa..614e4f5 100644 --- a/gsuid_core/client.py +++ b/gsuid_core/client.py @@ -36,8 +36,9 @@ class GsClient: intent = await self._input() msg = MessageReceive( bot_id='Nonebot', - user_type='group', - group_id='123456789', + user_type='DIRECT', + user_pm=2, + group_id=None, user_id='5253123', content=[Message(type='text', data=intent)], ) diff --git a/gsuid_core/core.py b/gsuid_core/core.py index 1a5c56a..d20069c 100644 --- a/gsuid_core/core.py +++ b/gsuid_core/core.py @@ -1,7 +1,7 @@ import asyncio import uvicorn -from server import gss +from gss import gss from config import core_config from handler import handle_event from models import MessageReceive diff --git a/gsuid_core/gs_logger.py b/gsuid_core/gs_logger.py new file mode 100644 index 0000000..2a00cbd --- /dev/null +++ b/gsuid_core/gs_logger.py @@ -0,0 +1,42 @@ +from typing import Literal + +from fastapi import WebSocket +from models import MessageSend +from segment import MessageSegment +from msgspec import json as msgjson + + +class GsLogger: + def __init__(self, bot_id: str, ws: WebSocket): + self.bot_id = bot_id + self.bot = ws + + def get_msg_send( + self, type: Literal['INFO', 'WARNING', 'ERROR', 'SUCCESS'], msg: str + ): + return MessageSend( + content=[MessageSegment.log(type, msg)], + bot_id=self.bot_id, + target_type=None, + target_id=None, + ) + + async def info(self, msg: str): + await self.bot.send_bytes( + msgjson.encode(self.get_msg_send('INFO', msg)) + ) + + async def warning(self, msg: str): + await self.bot.send_bytes( + msgjson.encode(self.get_msg_send('WARNING', msg)) + ) + + async def error(self, msg: str): + await self.bot.send_bytes( + msgjson.encode(self.get_msg_send('ERROR', msg)) + ) + + async def success(self, msg: str): + await self.bot.send_bytes( + msgjson.encode(self.get_msg_send('SUCCESS', msg)) + ) diff --git a/gsuid_core/gss.py b/gsuid_core/gss.py new file mode 100644 index 0000000..f86aca6 --- /dev/null +++ b/gsuid_core/gss.py @@ -0,0 +1,6 @@ +from server import GsServer + +gss = GsServer() +if not gss.is_load: + gss.is_load = True + gss.load_plugins() diff --git a/gsuid_core/handler.py b/gsuid_core/handler.py index 167f877..f370cfc 100644 --- a/gsuid_core/handler.py +++ b/gsuid_core/handler.py @@ -1,6 +1,6 @@ import asyncio -from server import Bot +from bot import Bot from trigger import Trigger from config import core_config from models import MessageContent, MessageReceive @@ -50,6 +50,11 @@ async def handle_event(ws: Bot, msg: MessageReceive): SL.lst[sv].enabled and user_pm <= SL.lst[sv].permission and msg.group_id not in SL.lst[sv].black_list + and True + if SL.lst[sv].area == 'ALL' + or (msg.group_id and SL.lst[sv].area == 'GROUP') + or (not msg.group_id and SL.lst[sv].area == 'DIRECT') + else False ) ] await asyncio.gather(*pending, return_exceptions=True) diff --git a/gsuid_core/hook.py b/gsuid_core/hook.py new file mode 100644 index 0000000..3d4644c --- /dev/null +++ b/gsuid_core/hook.py @@ -0,0 +1,19 @@ +from functools import wraps +from typing import Callable, Optional + +from gsuid_core.server import gss +from gsuid_core.logger import logger + + +def on_bot_connect(): + def deco(func: Callable) -> Callable: + gss.bot_connect_def.append(func) + + @wraps(func) + async def wrapper(*args, **kwargs) -> Optional[Callable]: + logger.info('@on_bot_connect已成功调用...') + return await func(*args, **kwargs) + + return wrapper + + return deco diff --git a/gsuid_core/logger.py b/gsuid_core/logger.py new file mode 100644 index 0000000..c1498ed --- /dev/null +++ b/gsuid_core/logger.py @@ -0,0 +1,22 @@ +import logging + +import rollbar +from rollbar.logger import RollbarHandler + +# Initialize Rollbar SDK with your server-side access token +rollbar.init( + 'ACCESS_TOKEN', + environment='staging', + handler='async', +) + +# Set root logger to log DEBUG and above +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +# Report ERROR and above to Rollbar +rollbar_handler = RollbarHandler() +rollbar_handler.setLevel(logging.ERROR) + +# Attach Rollbar handler to the root logger +logger.addHandler(rollbar_handler) diff --git a/gsuid_core/models.py b/gsuid_core/models.py index 5a7f83b..a68d5d5 100644 --- a/gsuid_core/models.py +++ b/gsuid_core/models.py @@ -8,19 +8,19 @@ class Message(Struct): data: Optional[Any] = None -class MessageReceive(Struct): +class MessageReceive(Struct, frozen=True): bot_id: str = 'Bot' user_type: Optional[str] = None group_id: Optional[str] = None - user_id: Optional[str] = None + user_id: str = '' user_pm: int = 3 content: List[Message] = [] class MessageContent(Struct): - raw: Optional[MessageReceive] = None + raw: MessageReceive = MessageReceive() raw_text: str = '' - command: Optional[str] = None + command: str = '' text: str = '' image: Optional[str] = None at: Optional[str] = None diff --git a/gsuid_core/server.py b/gsuid_core/server.py index 7adba75..23ff01d 100644 --- a/gsuid_core/server.py +++ b/gsuid_core/server.py @@ -2,94 +2,31 @@ import sys import asyncio import importlib from pathlib import Path -from typing import Dict, List, Union, Literal, Optional +from typing import Dict, Callable +from bot import Bot +from logger import logger from fastapi import WebSocket -from segment import MessageSegment -from msgspec import json as msgjson -from models import Message, MessageSend sys.path.append(str(Path(__file__).parents[1])) -class Bot: - def __init__(self, _id: str): - self.bot_id = _id - self.bot = gss.active_bot[_id] - self.logger = GsLogger(self.bot_id) - self.queue = asyncio.queues.Queue() - self.background_tasks = set() - self.user_id: Optional[str] = None - self.group_id: Optional[str] = None - self.user_type: Optional[str] = None - - async def send(self, message: Union[Message, List[Message], str]): - if isinstance(message, Message): - message = [message] - elif isinstance(message, str): - if message.startswith('base64://'): - message = [MessageSegment.image(message)] - else: - message = [MessageSegment.text(message)] - send = MessageSend( - content=message, - bot_id=self.bot_id, - target_type=self.user_type, - target_id=self.group_id if self.group_id else self.user_id, - ) - print(f'[发送消息] {send}') - await self.bot.send_bytes(msgjson.encode(send)) - - async def _process(self): - while True: - data = await self.queue.get() - task = asyncio.create_task(data) - self.background_tasks.add(task) - task.add_done_callback( - lambda _: self.background_tasks.discard(task) - ) - - -class GsLogger: - def __init__(self, bot_id: str): - self.bot_id = bot_id - self.bot = gss.active_bot[bot_id] - - def get_msg_send( - self, type: Literal['INFO', 'WARNING', 'ERROR', 'SUCCESS'], msg: str - ): - return MessageSend( - content=[MessageSegment.log(type, msg)], - bot_id=self.bot_id, - target_type=None, - target_id=None, - ) - - async def info(self, msg: str): - await self.bot.send_bytes( - msgjson.encode(self.get_msg_send('INFO', msg)) - ) - - async def warning(self, msg: str): - await self.bot.send_bytes( - msgjson.encode(self.get_msg_send('WARNING', msg)) - ) - - async def error(self, msg: str): - await self.bot.send_bytes( - msgjson.encode(self.get_msg_send('ERROR', msg)) - ) - - async def success(self, msg: str): - await self.bot.send_bytes( - msgjson.encode(self.get_msg_send('SUCCESS', msg)) - ) - - class GsServer: + _instance = None + is_initialized = False + is_load = False + bot_connect_def = set() + + def __new__(cls, *args, **kwargs): + # 判断sv是否已经被初始化 + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + def __init__(self): - self.active_bot: Dict[str, WebSocket] = {} - self.load_plugins() + if not self.is_initialized: + self.active_bot: Dict[str, WebSocket] = {} + self.is_initialized = True def load_plugins(self): sys.path.append(str(Path(__file__).parents[1])) @@ -121,12 +58,14 @@ class GsServer: async def connect(self, websocket: WebSocket, bot_id: str) -> Bot: await websocket.accept() self.active_bot[bot_id] = websocket - print(f'{bot_id}已连接!') - return Bot(bot_id) + logger.info(f'{bot_id}已连接!') + _task = [_def() for _def in self.bot_connect_def] + asyncio.gather(*_task) + return Bot(bot_id, websocket) def disconnect(self, bot_id: str): del self.active_bot[bot_id] - print(f'{bot_id}已中断!') + logger.warning(f'{bot_id}已中断!') async def send(self, message: str, bot_id: str): await self.active_bot[bot_id].send_text(message) @@ -135,5 +74,7 @@ class GsServer: for bot_id in self.active_bot: await self.send(message, bot_id) - -gss = GsServer() + @classmethod + def on_bot_connect(cls, func: Callable): + cls.bot_connect_def.add(func) + return func diff --git a/gsuid_core/sv.py b/gsuid_core/sv.py index ad16eeb..8ba4484 100644 --- a/gsuid_core/sv.py +++ b/gsuid_core/sv.py @@ -3,6 +3,7 @@ from __future__ import annotations from functools import wraps from typing import Dict, List, Tuple, Union, Literal, Callable, Optional +from logger import logger from trigger import Trigger from config import core_config @@ -23,7 +24,7 @@ config_sv = core_config.get_config('sv') class SV: is_initialized = False - def __new__(cls, *args): + def __new__(cls, *args, **kwargs): # 判断sv是否已经被初始化 if args[0] in SL.lst: return SL.lst[args[0]] @@ -34,14 +35,15 @@ class SV: def __init__( self, - name: str, + name: str = '', permission: int = 3, priority: int = 5, enabled: bool = True, + area: Literal['GROUP', 'DIRECT', 'ALL'] = 'ALL', black_list: List = [], ): if not self.is_initialized: - print(f'【{name}】模块初始化中...') + logger.info(f'【{name}】模块初始化中...') # sv名称,重复的sv名称将被并入一个sv里 self.name: str = name # sv内包含的触发器 @@ -54,21 +56,25 @@ class SV: self.enabled = config_sv[name]['enabled'] self.permission = config_sv[name]['permission'] self.black_list = config_sv[name]['black_list'] + self.area = config_sv[name]['area'] else: # sv优先级 - self.priority: int = priority + self.priority = priority # sv是否开启 - self.enabled: bool = enabled + self.enabled = enabled # 黑名单群 - self.black_list: List = black_list + self.black_list = black_list # 权限 0为master,1为superuser,2为群的群主&管理员,3为普通 - self.permission: int = permission + self.permission = permission + # 作用范围 + self.area = area # 写入 self.set( priority=priority, enabled=enabled, permission=permission, black_list=black_list, + area=area, ) def set(self, **kwargs): @@ -96,7 +102,7 @@ class SV: keyword_list = (keyword,) for _k in keyword_list: if _k not in self.TL: - print(f'载入{type}触发器【{_k}】!') + logger.info(f'载入{type}触发器【{_k}】!') self.TL[_k] = Trigger(type, _k, func) @wraps(func)