diff --git a/plugins/web_ui/api/tabs/database/__init__.py b/plugins/web_ui/api/tabs/database/__init__.py index 3df9d212..0f3eb985 100644 --- a/plugins/web_ui/api/tabs/database/__init__.py +++ b/plugins/web_ui/api/tabs/database/__init__.py @@ -17,7 +17,7 @@ from ....utils import authentication from .models.model import SqlModel, SqlText from .models.sql_log import SqlLog -router = APIRouter() +router = APIRouter(prefix="/database") driver: Driver = nonebot.get_driver() @@ -26,6 +26,18 @@ driver: Driver = nonebot.get_driver() SQL_DICT = {} +SELECT_TABLE_SQL = """ +select a.tablename as name,d.description as desc from pg_tables a + left join pg_class c on relname=tablename + left join pg_description d on oid=objoid and objsubid=0 where a.schemaname = 'public' +""" + +SELECT_TABLE_COLUMN_SQL = """ +SELECT column_name, data_type, character_maximum_length as max_length, is_nullable +FROM information_schema.columns +WHERE table_name = '{}'; +""" + @driver.on_startup async def _(): for matcher in get_matchers(True): @@ -45,6 +57,18 @@ async def _(): ) SQL_DICT[plugin_name] = SqlModel +@router.get("/get_table_list", dependencies=[authentication()], description="获取数据库表") +async def _() -> Result: + db = Tortoise.get_connection("default") + query = await db.execute_query_dict(SELECT_TABLE_SQL) + return Result.ok(query) + +@router.get("/get_table_column", dependencies=[authentication()], description="获取表字段") +async def _(table_name: str) -> Result: + db = Tortoise.get_connection("default") + print(SELECT_TABLE_COLUMN_SQL.format(table_name)) + query = await db.execute_query_dict(SELECT_TABLE_COLUMN_SQL.format(table_name)) + return Result.ok(query) @router.post("/exec_sql", dependencies=[authentication()], description="执行sql") async def _(sql: SqlText, request: Request) -> Result: diff --git a/plugins/web_ui/api/tabs/main/__init__.py b/plugins/web_ui/api/tabs/main/__init__.py index 6fec031c..b5d6e63d 100644 --- a/plugins/web_ui/api/tabs/main/__init__.py +++ b/plugins/web_ui/api/tabs/main/__init__.py @@ -25,7 +25,7 @@ from .model import ActiveGroup, BaseInfo, ChatHistoryCount, HotPlugin run_time = time.time() ws_router = APIRouter() -router = APIRouter() +router = APIRouter(prefix="/main") @@ -183,7 +183,7 @@ async def _(date_type: Optional[QueryDateType] = None) -> Result: if date_type == QueryDateType.YEAR: query = ChatHistory.filter(create_time__gte=now - timedelta(days=365)) data_list = ( - await query.annotate(count=Count("id")) + await query.annotate(count=Count("id")).filter(group_id__not_isnull=True) .group_by("group_id").order_by("-count").limit(5) .values_list("group_id", "count") ) diff --git a/plugins/web_ui/api/tabs/manage/__init__.py b/plugins/web_ui/api/tabs/manage/__init__.py index 96e70ccb..e6ce8e96 100644 --- a/plugins/web_ui/api/tabs/manage/__init__.py +++ b/plugins/web_ui/api/tabs/manage/__init__.py @@ -33,6 +33,7 @@ from .model import ( HandleRequest, LeaveGroup, Message, + MessageItem, Plugin, ReqResult, SendMessage, @@ -42,7 +43,7 @@ from .model import ( ) ws_router = APIRouter() -router = APIRouter() +router = APIRouter(prefix="/manage") SUB_PATTERN = r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))" @@ -50,6 +51,8 @@ GROUP_PATTERN = r'.*?Message (-?\d*) from (\d*)@\[群:(\d*)] "(.*)"' PRIVATE_PATTERN = r'.*?Message (-?\d*) from (\d*) "(.*)"' +IMAGE_PATTERN = r'\[CQ:image,.*,url=(.*);.*?\]' + @router.get("/get_group_list", dependencies=[authentication()], description="获取群组列表") async def _(bot_id: str) -> Result: """ @@ -323,8 +326,10 @@ async def _(bot_id: str, group_id: str) -> Result: like_plugin[name] = data[1] close_plugins = [] for module in g.close_plugins: - plugin = Plugin(module=module, plugin_name=module) - if plugin_data := plugin_data_manager.get(module): + module_ = module.replace(":super", "") + is_super_block = module.endswith(":super") + plugin = Plugin(module=module_, plugin_name=module, is_super_block=is_super_block) + if plugin_data := plugin_data_manager.get(module_): plugin.plugin_name = plugin_data.name close_plugins.append(plugin) task_list = [] @@ -374,48 +379,71 @@ async def _(param: SendMessage) -> Result: MSG_LIST = [] +ID2NAME = {} + + +async def message_handle(sub_log: str, type: Literal["private", "group"]): + global MSG_LIST, ID2NAME + pattern = PRIVATE_PATTERN if type == 'private' else GROUP_PATTERN + msg_id = None + uid = None + gid = None + msg = None + img_list = re.findall(IMAGE_PATTERN, sub_log) + if r := re.search(pattern, sub_log): + if type == 'private': + msg_id = r.group(1) + uid = r.group(2) + msg = r.group(3) + if uid not in ID2NAME: + user = await FriendUser.filter(user_id=uid).first() + ID2NAME[uid] = user.user_name or user.nickname + else: + msg_id = r.group(1) + uid = r.group(2) + gid = r.group(3) + msg = r.group(4) + if gid not in ID2NAME: + user = await GroupInfoUser.filter(user_id=uid, group_id=gid).first() + ID2NAME[gid] = user.user_name or user.nickname + if msg_id in MSG_LIST: + return + MSG_LIST.append(msg_id) + messages = [] + rep = re.split(r'\[CQ:image.*\]', msg) + if img_list: + for i in range(len(rep)): + messages.append(MessageItem(type="text", msg=rep[i])) + if i < len(img_list): + messages.append(MessageItem(type="img", msg=img_list[i])) + else: + messages = [MessageItem(type="text", msg=x) for x in rep] + return Message( + object_id=uid if type == 'private' else gid, + user_id=uid, + group_id=gid, + message=messages, + name=ID2NAME[uid], + ava_url=AVA_URL.format(uid), + ) + @ws_router.websocket("/chat") -async def _(websocket: WebSocket, group_id: Optional[str] = None, user_id: Optional[str] = None): - global MSG_LIST +async def _(websocket: WebSocket): await websocket.accept() async def log_listener(log: str): + global MSG_LIST, ID2NAME sub_log = re.sub(SUB_PATTERN, "", log) + img_list = re.findall(IMAGE_PATTERN, sub_log) if "message.private.friend" in log: - if r := re.search(PRIVATE_PATTERN, sub_log): - msg_id = r.group(1) - uid = r.group(2) - msg = r.group(3) - user = await FriendUser.filter(user_id=user_id).first() - name = user.user_name - if uid and uid == user_id and msg_id not in MSG_LIST: - MSG_LIST.append(msg_id) - message = Message( - user_id=uid, - message=msg, - name=name, - ava_url=AVA_URL.format(uid) - ) - await websocket.send_json(message.dict()) + if message := await message_handle(sub_log, 'private'): + await websocket.send_json(message.dict()) else: if r := re.search(GROUP_PATTERN, sub_log): - msg_id = r.group(1) - uid = r.group(2) - gid = r.group(3) - msg = r.group(4) - user = await GroupInfoUser.filter(user_id=uid, group_id=gid).first() - name = user.user_name or user.nickname - if gid and gid == group_id and msg_id not in MSG_LIST: - MSG_LIST.append(msg_id) - message = Message( - user_id=uid, - group_id=gid, - message=msg, - name=name, - ava_url=AVA_URL.format(uid) - ) + if message := await message_handle(sub_log, 'group'): await websocket.send_json(message.dict()) - + if len(MSG_LIST) > 30: + MSG_LIST = MSG_LIST[-1:] LOG_STORAGE.listeners.add(log_listener) try: while websocket.client_state == WebSocketState.CONNECTED: diff --git a/plugins/web_ui/api/tabs/manage/model.py b/plugins/web_ui/api/tabs/manage/model.py index bc8df37f..ab8b83f1 100644 --- a/plugins/web_ui/api/tabs/manage/model.py +++ b/plugins/web_ui/api/tabs/manage/model.py @@ -41,6 +41,8 @@ class Plugin(BaseModel): """模块名""" plugin_name: str """中文名""" + is_super_block: bool + """是否超级用户禁用""" class GroupResult(BaseModel): @@ -227,23 +229,33 @@ class GroupDetail(BaseModel): task: List[Task] """被动列表""" +class MessageItem(BaseModel): + + type: str + """消息类型""" + msg: str + """内容""" class Message(BaseModel): """ 消息 """ + object_id: str + """主体id user_id 或 group_id""" user_id: str """用户id""" group_id: Optional[str] = None """群组id""" - message: str + message: List[MessageItem] """消息""" name: str """用户名称""" ava_url: str """用户头像""" + + class SendMessage(BaseModel): """ 发送消息 diff --git a/plugins/web_ui/api/tabs/plugin_manage/__init__.py b/plugins/web_ui/api/tabs/plugin_manage/__init__.py index 6179d9c5..20e5fd97 100644 --- a/plugins/web_ui/api/tabs/plugin_manage/__init__.py +++ b/plugins/web_ui/api/tabs/plugin_manage/__init__.py @@ -2,7 +2,7 @@ import re from typing import List, Optional import cattrs -from fastapi import APIRouter +from fastapi import APIRouter, Query from configs.config import Config from services.log import logger @@ -17,26 +17,26 @@ from .model import ( PluginDetail, PluginInfo, PluginSwitch, - UpdateConfig, UpdatePlugin, ) -router = APIRouter() +router = APIRouter(prefix="/plugin") @router.get("/get_plugin_list", dependencies=[authentication()], deprecated="获取插件列表") def _( - plugin_type: PluginType, menu_type: Optional[str] = None + plugin_type: List[PluginType] = Query(None), menu_type: Optional[str] = None ) -> Result: """ 获取插件列表 :param plugin_type: 类型 normal, superuser, hidden, admin + :param menu_type: 菜单类型 """ try: plugin_list: List[PluginInfo] = [] for module in plugin_data_manager.keys(): plugin_data: Optional[PluginData] = plugin_data_manager[module] - if plugin_data and plugin_data.plugin_type == plugin_type: + if plugin_data and plugin_data.plugin_type in plugin_type: setting = plugin_data.plugin_setting or PluginSetting() plugin = plugin_data.plugin_status menu_type_ = getattr(setting, "plugin_type", ["无"])[0]