diff options
author | Kyomotoi <[email protected]> | 2022-11-04 10:20:26 +0800 |
---|---|---|
committer | Kyomotoi <[email protected]> | 2022-11-04 10:20:26 +0800 |
commit | 5a245fd7132b5d4da13ee83bd0eadb91d41735b6 (patch) | |
tree | 5f3e1ccbaca0339e757838a6f7f555bff6fa6aa7 | |
parent | b2507f8d4dd051490e49bf9a0d235ab930b63ddd (diff) | |
download | ATRI-5a245fd7132b5d4da13ee83bd0eadb91d41735b6.tar.gz ATRI-5a245fd7132b5d4da13ee83bd0eadb91d41735b6.tar.bz2 ATRI-5a245fd7132b5d4da13ee83bd0eadb91d41735b6.zip |
♻️ 重构插件: 控制台后端部分
-rw-r--r-- | ATRI/plugins/console/__init__.py | 82 | ||||
-rw-r--r-- | ATRI/plugins/console/data_source.py | 96 | ||||
-rw-r--r-- | ATRI/plugins/console/driver/__init__.py | 48 | ||||
-rw-r--r-- | ATRI/plugins/console/driver/api.py | 262 | ||||
-rw-r--r-- | ATRI/plugins/console/driver/data_source.py | 167 | ||||
-rw-r--r-- | ATRI/plugins/console/driver/depends.py | 38 | ||||
-rw-r--r-- | ATRI/plugins/console/driver/models.py | 38 | ||||
-rw-r--r-- | ATRI/plugins/console/driver/path.py | 14 | ||||
-rw-r--r-- | ATRI/plugins/console/driver/view.py | 101 | ||||
-rw-r--r-- | ATRI/plugins/console/listener.py | 2 | ||||
-rw-r--r-- | ATRI/plugins/console/models.py | 27 |
11 files changed, 431 insertions, 444 deletions
diff --git a/ATRI/plugins/console/__init__.py b/ATRI/plugins/console/__init__.py index b09cb29..3711f86 100644 --- a/ATRI/plugins/console/__init__.py +++ b/ATRI/plugins/console/__init__.py @@ -1,5 +1,4 @@ -import json -from datetime import datetime, timedelta +from datetime import datetime from nonebot.params import ArgPlainText from nonebot.adapters.onebot.v11 import PrivateMessageEvent, GroupMessageEvent @@ -9,14 +8,9 @@ from ATRI.log import log from ATRI.service import Service from ATRI.permission import MASTER from ATRI.message import MessageBuilder -from ATRI.exceptions import WriteFileError from ATRI.utils.apscheduler import scheduler -from .data_source import Console, CONSOLE_DIR -from .models import AuthData - - -__AUTH_FILE_PATH = CONSOLE_DIR / "data.json" +from .data_source import AuthDealer, get_host_ip plugin = ( @@ -28,9 +22,8 @@ plugin = ( ) -def __del_auth_key(): - with open(__AUTH_FILE_PATH, "w", encoding="utf-8") as w: - w.write(json.dumps({})) +async def __del_auth_key(): + await AuthDealer.clear() log.warning("控制台验证密钥已过期") @@ -48,54 +41,40 @@ async def _(event: PrivateMessageEvent, is_pub_n: str = ArgPlainText("is_pub_n") .text("Tip: 该内容请尽可能地复杂, 请勿使用中文") ) - if not __AUTH_FILE_PATH.is_file(): - with open(__AUTH_FILE_PATH, "w", encoding="utf-8") as w: - w.write(json.dumps(dict())) - else: - raw_data = json.loads(__AUTH_FILE_PATH.read_bytes()) - - data = raw_data.get("data") - if data: - now_time = datetime.now().timestamp() - if now_time < data["dead_time"] and data.get("dead_time"): - raw_last_time = data["dead_time"] - now_time - last_time = datetime.fromtimestamp(raw_last_time).minute - await gen_console_key.finish( - MessageBuilder("之前生成的密钥还在有效时间内奥") - .text(f"Token: {data['token']}") - .text(f"剩余有效时间: {last_time} min") - ) - else: - with open(__AUTH_FILE_PATH, "w", encoding="utf-8") as w: - w.write(json.dumps(dict())) + auth_info = AuthDealer.get() + if auth_info: + now_time = datetime.now().timestamp() + if now_time < auth_info.dead_time: + raw_last_time = auth_info.dead_time - now_time + last_time = datetime.fromtimestamp(raw_last_time).minute + await gen_console_key.finish( + MessageBuilder("之前生成的密钥还在有效时间内奥") + .text(f"Token: {auth_info.token}") + .text(f"剩余有效时间: {last_time} min") + ) + await AuthDealer.clear() if is_pub_n != "y": - host = str(await Console().get_host_ip(False)) + host = str(await get_host_ip(False)) await gen_console_key.send("没有公网吗...嗯知道了") else: - host = str(await Console().get_host_ip(True)) - + host = str(await get_host_ip(True)) port = conf.BotConfig.port - token = Console().get_random_str(20) - dead_time = datetime.now() + timedelta(minutes=15) - - data = json.loads(__AUTH_FILE_PATH.read_bytes()) - data["data"] = AuthData(token=token, dead_time=dead_time.timestamp()).dict() - with open(__AUTH_FILE_PATH, "w", encoding="utf-8") as w: - w.write(json.dumps(data)) + auth = AuthDealer() + data = await auth.store() msg = ( MessageBuilder("控制台信息已生成!") .text(f"请访问: {host}:{port}") - .text(f"Token: {token}") + .text(f"Token: {auth.get_token()}") .text("该 token 有效时间为 15min") ) scheduler.add_job( __del_auth_key, name="清除后台验证凭证", - next_run_time=dead_time, # type: ignore - misfire_grace_time=15, # type: ignore + next_run_time=datetime.fromtimestamp(data.dead_time), + misfire_grace_time=15, ) await gen_console_key.finish(msg) @@ -114,20 +93,11 @@ async def _(is_sure: str = ArgPlainText("is_sure_d")): if is_sure != "y": await del_console_key.finish("反悔了呢...") - df = CONSOLE_DIR / "data.json" - if not df.is_file(): + data = AuthDealer.get() + if data is None: await del_console_key.finish("你还没向咱索取凭证呢...私戳咱键入 /con.auth 以获取") - try: - data = json.loads(df.read_bytes()) - - del data["data"] - - with open(df, "w", encoding="utf-8") as w: - w.write(json.dumps(data)) - except Exception: - await del_console_key.send("销毁失败了...请至此处自行删除文件:\n" + str(df)) - raise WriteFileError("Writing / Reading file: " + str(df) + " failed!") + await AuthDealer.clear() await del_console_key.finish("销毁成功!如需再次获取: /con.auth") diff --git a/ATRI/plugins/console/data_source.py b/ATRI/plugins/console/data_source.py index b4a28cb..ab6b51b 100644 --- a/ATRI/plugins/console/data_source.py +++ b/ATRI/plugins/console/data_source.py @@ -2,59 +2,75 @@ import json import socket import string import zipfile -from random import sample +import hashlib from pathlib import Path +from random import sample +from typing import Optional +from datetime import datetime, timedelta -from ATRI.utils import request -from ATRI.exceptions import WriteFileError +from ATRI.utils import request, FileDealer from ATRI.log import log +from .models import AuthData + CONSOLE_DIR = Path(".") / "data" / "plugins" / "console" CONSOLE_DIR.mkdir(parents=True, exist_ok=True) -class Console: - @staticmethod - async def get_host_ip(is_pub: bool): - if is_pub: - data = await request.get("https://ifconfig.me/ip") - return data.text - - s = None - try: - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.connect(("8.8.8.8", 80)) - ip = s.getsockname()[0] - return ip - finally: - if s: - s.close() - - @staticmethod - def get_random_str(k: int) -> str: - return "".join(sample(string.ascii_letters + string.digits, k)) - - @staticmethod - def get_auth_info() -> dict: - df = CONSOLE_DIR / "data.json" - if not df.is_file(): - try: - with open(df, "w", encoding="utf-8") as w: - w.write(json.dumps(dict())) - except Exception: - raise WriteFileError("Writing file: " + str(df) + " failed!") - - base_data: dict = json.loads(df.read_bytes()) - data = base_data.get("data", None) - if not data: - return {"data": None} +class AuthDealer: + AUTH_FILE_PATH = CONSOLE_DIR / "data.json" + + def __init__(self): + self.token = str().join(sample(string.ascii_letters + string.digits, 20)) + + def get_token(self): + return self.token + + def get_md5(self): + hl = hashlib.md5() + hl.update(self.token.encode(encoding="utf-8")) + return hl.hexdigest() + + async def store(self) -> AuthData: + dead_time = (datetime.now() + timedelta(minutes=15)).timestamp() + data = AuthData(token=self.token, md5=self.get_md5(), dead_time=int(dead_time)) + await FileDealer(self.AUTH_FILE_PATH).write(json.dumps(data.dict())) return data + @classmethod + def get(cls) -> Optional[AuthData]: + return ( + AuthData.parse_file(cls.AUTH_FILE_PATH) + if cls.AUTH_FILE_PATH.is_file() + and json.loads(cls.AUTH_FILE_PATH.read_bytes()) + else None + ) + + @classmethod + async def clear(cls): + await FileDealer(cls.AUTH_FILE_PATH).write(json.dumps(dict())) + + +async def get_host_ip(is_pub: bool): + if is_pub: + data = await request.get("https://ifconfig.me/ip") + return data.text + + s = None + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + ip = s.getsockname()[0] + return ip + finally: + if s: + s.close() + FRONTEND_DIR = CONSOLE_DIR / "frontend" FRONTEND_DIR.mkdir(parents=True, exist_ok=True) -CONSOLE_RESOURCE_URL = ( +__CONSOLE_RESOURCE_URL = ( "https://jsd.imki.moe/gh/kyomotoi/Project-ATRI-Console@main/archive/dist.zip" ) @@ -63,7 +79,7 @@ async def init_resource(): log.info("控制台初始化中...") try: - resp = await request.get(CONSOLE_RESOURCE_URL) + resp = await request.get(__CONSOLE_RESOURCE_URL) except Exception: log.error("控制台资源装载失败, 将无法访问管理界面") return diff --git a/ATRI/plugins/console/driver/__init__.py b/ATRI/plugins/console/driver/__init__.py index e5dfdc6..b6b9f89 100644 --- a/ATRI/plugins/console/driver/__init__.py +++ b/ATRI/plugins/console/driver/__init__.py @@ -5,32 +5,14 @@ from fastapi.middleware.cors import CORSMiddleware from ATRI import conf from ATRI.log import log -from ATRI.plugins.console.data_source import FRONTEND_DIR -from .view import ( - handle_auther, - handle_base_uri, - handle_control_service, - handle_edit_block, - handle_get_block_list, - handle_get_service_list, - handle_runtime_info, - handle_message_deal_info, -) +from .path import * +from .api import * -CONSOLE_API_URI = "/capi" # base point -CONSOLE_API_AUTH_URI = "/capi/auth" # 验证后台许可 -CONSOLE_API_RUNTIME_URI = "/capi/runtime" # 获取运行占用信息 -CONSOLE_API_MESSAGE_URI = "/capi/message" # 获取信息处理信息 +from ..data_source import FRONTEND_DIR -CONSOLE_API_SERVICE_LIST_URI = "/capi/service/list" # 获取服务列表 -CONSOLE_API_SERVICE_CONTROL_URI = "/capi/service/control" # 对服务作出修改 -CONSOLE_API_BLOCK_LIST_URI = "/capi/block/list" # 获取封禁列表 -CONSOLE_API_BLOCK_EDIT_URI = "/capi/block/edit" # 编辑封禁列表 - - -def register_routes(driver: Driver): +def register_driver(driver: Driver): app = driver.server_app origins = ["*"] @@ -42,16 +24,18 @@ def register_routes(driver: Driver): allow_headers=["Content-Type"], ) - app.get(CONSOLE_API_URI)(handle_base_uri) - app.get(CONSOLE_API_AUTH_URI)(handle_auther) - app.get(CONSOLE_API_RUNTIME_URI)(handle_runtime_info) - app.get(CONSOLE_API_MESSAGE_URI)(handle_message_deal_info) + app.get(CONSOLE_BASE_URL)(base_url) + + app.get(CONSOLE_AUTH_URL)(auth_info) + + app.websocket(CONSOLE_RUNTIME_INFO_URL)(runtime_info) + app.websocket(CONSOLE_MESSAGE_INFO_URL)(message_info) - app.get(CONSOLE_API_SERVICE_LIST_URI)(handle_get_service_list) - app.get(CONSOLE_API_SERVICE_CONTROL_URI)(handle_control_service) + app.get(CONSOLE_SERVICE_LIST_URL)(service_list) + app.get(CONSOLE_SERVICE_EDIT_URL)(edit_service) - app.get(CONSOLE_API_BLOCK_LIST_URI)(handle_get_block_list) - app.get(CONSOLE_API_BLOCK_EDIT_URI)(handle_edit_block) + app.get(CONSOLE_BLOCK_LIST_URL)(block_list_info) + app.get(CONSOLE_BLOCK_EDIT_URL)(edit_block_list) static_path = str(FRONTEND_DIR) app.mount( @@ -64,6 +48,6 @@ def register_routes(driver: Driver): def init_driver(): from ATRI import driver + register_driver(driver()) # type: ignore c_url = f"{conf.BotConfig.host}:{conf.BotConfig.port}" - log.info(f"控制台将运行于: http://{c_url} 对应API节点为: /capi") - register_routes(driver()) # type: ignore + log.success(f"控制台将运行于: http://{c_url} 对应API节点为: /capi") diff --git a/ATRI/plugins/console/driver/api.py b/ATRI/plugins/console/driver/api.py index 4750148..4b78906 100644 --- a/ATRI/plugins/console/driver/api.py +++ b/ATRI/plugins/console/driver/api.py @@ -1,202 +1,86 @@ -import os -import json -import time -import psutil -from pathlib import Path -from datetime import datetime +import asyncio -from ATRI.service import ServiceTools, SERVICES_DIR -from ATRI.exceptions import GetStatusError -from ..models import PlatformRuntimeInfo, BotRuntimeInfo, ServiceInfo +from fastapi import Depends, status +from starlette.websockets import WebSocket, WebSocketState +from websockets.exceptions import ConnectionClosedOK - -def get_processing_data() -> tuple: - try: - p_cpu = psutil.cpu_percent(interval=1) - p_mem = psutil.virtual_memory().percent - disk = psutil.disk_usage("/").percent - inte_send = psutil.net_io_counters().bytes_sent / 1000000 # type: ignore - inte_recv = psutil.net_io_counters().bytes_recv / 1000000 # type: ignore - - process = psutil.Process(os.getpid()) - b_cpu = process.cpu_percent(interval=1) - b_mem = process.memory_percent(memtype="rss") - - now = time.time() - boot = psutil.boot_time() - b = process.create_time() - boot_time = str( - datetime.utcfromtimestamp(now).replace(microsecond=0) - - datetime.utcfromtimestamp(boot).replace(microsecond=0) - ) - bot_time = str( - datetime.utcfromtimestamp(now).replace(microsecond=0) - - datetime.utcfromtimestamp(b).replace(microsecond=0) - ) - except Exception: - raise GetStatusError("Getting runtime failed.") - - if p_cpu > 90: # type: ignore - msg = "咱感觉有些头晕..." - if p_mem > 90: - msg = "咱感觉有点头晕并且有点累..." - elif p_mem > 90: - msg = "咱感觉有点累..." - elif disk > 90: - msg = "咱感觉身体要被塞满了..." - else: - msg = "アトリは、高性能ですから!" - - return ( - PlatformRuntimeInfo( - stat_msg=msg, - cpu_percent=str(p_cpu), - mem_percent=str(p_mem), - disk_percent=str(disk), - inte_send=str(inte_send), - inte_recv=str(inte_recv), - boot_time=boot_time, - ).dict(), - BotRuntimeInfo( - cpu_percent=str(b_cpu), mem_percent=str(b_mem), bot_run_time=bot_time - ).dict(), - ) - - -def get_service_list() -> dict: - result = dict() - - files = os.listdir(SERVICES_DIR) - for f in files: - # Thank you, MacOS - if f == ".DS_Store": - continue - - serv_path = SERVICES_DIR / f - data = json.loads(serv_path.read_bytes()) - - serv_name = data["service"] - serv_docs = data["docs"] - serv_is_enabled = data["enabled"] - serv_disable_user = data["disable_user"] - serv_disable_group = data["disable_group"] - - result[serv_name] = ServiceInfo( - service_name=serv_name, - service_docs=serv_docs, - is_enabled=serv_is_enabled, - disable_user=serv_disable_user, - disable_group=serv_disable_group, - ).dict() - - return result - - -def control_service( - serv_name: str, is_enab: int, enab_u: str, enab_g: str, disab_u: str, disab_g: str -) -> tuple: - try: - serv_data = ServiceTools().load_service(serv_name) - except Exception: - return False, dict() - - if is_enab != 1: - if is_enab == 0: - serv_data["enabled"] = False - else: - serv_data["enabled"] = True - - if enab_u: - if enab_u not in serv_data["disable_user"]: - return False, {"msg": "Target not in list"} - serv_data["disable_user"].remove(enab_u) - - if enab_g: - if enab_g not in serv_data["disable_user"]: - return False, {"msg": "Target not in list"} - serv_data["disable_group"].remove(enab_g) - - if disab_u: - if disab_u in serv_data["disable_user"]: - return False, {"msg": "Target already exists in list"} - serv_data["disable_user"].append(disab_u) - - if disab_g: - if disab_g in serv_data["disable_user"]: - return False, {"msg": "Target already exists in list"} - serv_data["disable_group"].append(disab_g) - - try: - ServiceTools().save_service(serv_data, serv_name) - except Exception: - return False, dict() - - return True, serv_data - - -MANEGE_DIR = Path(".") / "data" / "plugins" / "manege" +from .depends import * +from .data_source import ( + get_process_info, + get_service_list, + edit_service as _edit_service, + get_block_list, + edit_block_list as _edit_block_list, +) +from ..listener import get_message_info -def get_block_list() -> dict: - u_data = dict() - g_data = dict() +def base_url(_=Depends(http_author)): + return {"status": status.HTTP_204_NO_CONTENT, "msg": "该路径仅供控制台加载"} - u_f = "block_user.json" - path = MANEGE_DIR / u_f - if path.is_file(): - u_data = json.loads(path.read_bytes()) - g_f = "block_group.json" - path = MANEGE_DIR / g_f - if path.is_file(): - g_data = json.loads(path.read_bytes()) +def auth_info(_=Depends(http_author)): + return {"status": status.HTTP_200_OK, "detail": "OK"} - return {"user": u_data, "group": g_data} - -def edit_block_list(is_enab: bool, user_id: str, group_id: str) -> tuple: - d = get_block_list() - u_d = d["user"] - g_d = d["group"] - - now_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - if is_enab: - if user_id: - if user_id in u_d: - return False, {"msg": "Target already exists in list"} - u_d[user_id] = now_time - - if group_id: - if group_id in g_d: - return False, {"msg": "Target already exists in list"} - g_d[group_id] = now_time - else: - if user_id: - if user_id not in u_d: - return False, {"msg": "Target not in list"} - del u_d[user_id] - - if group_id: - if group_id not in g_d: - return False, {"msg": "Target not in list"} - del g_d[group_id] +async def runtime_info(websocket: WebSocket, _pass=Depends(websocket_author)): + if not _pass: + return + await websocket.accept() try: - u_f = "block_user.json" - path = MANEGE_DIR / u_f - with open(path, "w", encoding="utf-8") as w: - w.write(json.dumps(u_d)) - - g_f = "block_group.json" - path = MANEGE_DIR / g_f - with open(path, "w", encoding="utf-8") as w: - w.write(json.dumps(g_d)) - except Exception: - return False, dict() + while websocket.client_state == WebSocketState.CONNECTED: + await websocket.send_json(get_process_info()) + await asyncio.sleep(1) + except ConnectionClosedOK: + pass + finally: + await websocket.close() + return - return True, {"user": u_d, "group": g_d} +async def message_info(websocket: WebSocket, _pass=Depends(websocket_author)): + if not _pass: + return + await websocket.accept() -def get_group_list(): - ... + try: + while websocket.client_state == WebSocketState.CONNECTED: + await websocket.send_json(get_message_info()) + await asyncio.sleep(1) + except ConnectionClosedOK: + pass + finally: + await websocket.close() + return + + +def service_list(_=Depends(http_author)): + return {"status": status.HTTP_200_OK, "data": get_service_list()} + + +def edit_service( + service: str, + global_enabled: str = "2", + enabled: str = "2", + user: str = str(), + group: str = str(), + _=Depends(http_author), +): + return { + "status": status.HTTP_200_OK, + "data": _edit_service( + service, int(global_enabled), bool(int(enabled)), user, group + ), + } + + +def block_list_info(_=Depends(http_author)): + return {"status": status.HTTP_200_OK, "data": get_block_list()} + + +def edit_block_list(enabled: str, user_id: str = str(), group_id: str = str(), _=Depends(http_author)): + return { + "status": status.HTTP_200_OK, + "data": _edit_block_list(bool(int(enabled)), user_id, group_id), + } diff --git a/ATRI/plugins/console/driver/data_source.py b/ATRI/plugins/console/driver/data_source.py new file mode 100644 index 0000000..12fb938 --- /dev/null +++ b/ATRI/plugins/console/driver/data_source.py @@ -0,0 +1,167 @@ +import os +import json +import psutil +from pathlib import Path +from datetime import datetime + +from ATRI.exceptions import GetStatusError + +from ATRI.utils import FileDealer +from ATRI.service import ServiceTools, SERVICES_DIR + +from . import models + + +def get_process_info() -> dict: + try: + platform_cpu = psutil.cpu_percent(interval=1) + platform_mem = psutil.virtual_memory().percent + platform_disk = psutil.disk_usage("/").percent + platform_net_sent = str(psutil.net_io_counters().bytes_sent / 1000000) + platform_net_recv = str(psutil.net_io_counters().bytes_recv / 1000000) + + process = psutil.Process(os.getpid()) + bot_cpu = str(process.cpu_percent(interval=1)) + bot_mem = str(process.memory_percent(memtype="rss")) + + now_time = datetime.now().timestamp() + _boot_time = psutil.boot_time() + _bot_run_time = process.create_time() + boot_time = str( + datetime.utcfromtimestamp(now_time).replace(microsecond=0) + - datetime.utcfromtimestamp(_boot_time).replace(microsecond=0) + ) + bot_run_time = str( + datetime.utcfromtimestamp(now_time).replace(microsecond=0) + - datetime.utcfromtimestamp(_bot_run_time).replace(microsecond=0) + ) + except Exception: + raise GetStatusError("获取实例运行信息失败") + + stat_msg = "アトリは、高性能ですから!" + if platform_cpu > 90: + stat_msg = "咱感觉有些头晕..." + if platform_mem > 90: + stat_msg = "咱感觉有点头晕并且有点累..." + elif platform_mem > 90: + stat_msg = "咱感觉有点累..." + elif platform_disk > 90: + stat_msg = "咱感觉身体要被塞满了..." + + platform_cpu = str(platform_cpu) + platform_mem = str(platform_mem) + platform_disk = str(platform_disk) + + return models.RuntimeInfo( + status_message=stat_msg, + platform_info=models.PlatformRuntimeInfo( + cpu_percent=platform_cpu, + mem_percent=platform_mem, + disk_percent=platform_disk, + net_sent=platform_net_sent, + net_recv=platform_net_recv, + boot_time=boot_time, + ), + bot_info=models.BotRuntimeInfo( + cpu_percent=bot_cpu, mem_percent=bot_mem, run_time=bot_run_time + ), + ).dict() + + +def get_service_list() -> dict: + result = dict() + + files = os.listdir(SERVICES_DIR) + for file in files: + # Thank you, MacOS + if file == ".DS_Store": + continue + + service_path = SERVICES_DIR / file + data = models.ServiceInfo.parse_file(service_path) + result[data.service] = data.dict() + + return result + + +def edit_service( + service: str, global_enabled: int, enabled: bool, user_id: str, group_id: str +): + data = ServiceTools.load_service(service) + + if global_enabled != 2 and global_enabled: + data.enabled = bool(global_enabled) + else: + data.enabled = False + if user_id or group_id: + if enabled: + if user_id not in data.disable_user: + return {"detail": "用户不存在于禁用名单"} + else: + data.disable_user.remove(user_id) + + if group_id not in data.disable_group: + return {"detail": "群不存在于禁用名单"} + else: + data.disable_group.remove(group_id) + else: + if user_id not in data.disable_user: + data.disable_user.append(user_id) + else: + return {"detail": "用户已存在于禁用名单"} + + if group_id not in data.disable_group: + data.disable_group.append(group_id) + else: + return {"detail": "群已存在于禁用名单"} + + ServiceTools.save_service(data.dict(), service) + + return {"detail": "操作完成~"} + + +def get_block_list() -> models.BlockInfo: + file_dir = Path(".") / "data" / "plugins" / "manege" + path = file_dir / "block_user.json" + user_data = json.loads(path.read_bytes()) + + path = file_dir / "block_group.json" + group_data = json.loads(path.read_bytes()) + + return models.BlockInfo(user=user_data, group=group_data) + + +async def edit_block_list(enabled: bool, user_id: str, group_id: str): + data = get_block_list() + now_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + if enabled: + if user_id: + if user_id in data.user: + return {"detail": "用户已存在于黑名单"} + else: + data.user[user_id] = now_time + if group_id: + if group_id in data.group: + return {"detail": "群已存在于黑名单"} + else: + data.group[group_id] = now_time + else: + if user_id: + if user_id in data.user: + del data.user[user_id] + else: + return {"detail": "用户不存在于黑名单"} + if group_id: + if group_id in data.group: + del data.group[group_id] + else: + return {"detail": "群不存在于黑名单"} + + file_dir = Path(".") / "data" / "plugins" / "manege" + path = file_dir / "block_user.json" + await FileDealer(path).write(json.dumps(data.user)) + + path = file_dir / "block_group.json" + await FileDealer(path).write(json.dumps(data.group)) + + return {"detail": "操作完成~"} diff --git a/ATRI/plugins/console/driver/depends.py b/ATRI/plugins/console/driver/depends.py new file mode 100644 index 0000000..8205f44 --- /dev/null +++ b/ATRI/plugins/console/driver/depends.py @@ -0,0 +1,38 @@ +from typing import Union +from datetime import datetime + +from fastapi import Query, HTTPException, status +from starlette.websockets import WebSocket + +from ..data_source import AuthDealer + + +def http_author(token: Union[str, None] = Query(default=None)): + data = AuthDealer.get() + if data is None: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="验证信息不存在") + + now_time = datetime.now().timestamp() + if token != data.token: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="密钥不匹配, 请检查") + elif now_time > data.dead_time: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="密钥已过期") + else: + return token + + +async def websocket_author( + websocket: WebSocket, token: Union[str, None] = Query(default=None) +): + data = AuthDealer.get() + if not data: + await websocket.close(code=status.WS_1008_POLICY_VIOLATION) + return + + now_time = datetime.now().timestamp() + if token != data.token: + await websocket.close(code=status.WS_1008_POLICY_VIOLATION) + elif now_time > data.dead_time: + await websocket.close(code=status.WS_1008_POLICY_VIOLATION) + else: + return token diff --git a/ATRI/plugins/console/driver/models.py b/ATRI/plugins/console/driver/models.py new file mode 100644 index 0000000..cbc110d --- /dev/null +++ b/ATRI/plugins/console/driver/models.py @@ -0,0 +1,38 @@ +from pydantic import BaseModel + + +class PlatformRuntimeInfo(BaseModel): + cpu_percent: str + mem_percent: str + disk_percent: str + net_sent: str + net_recv: str + boot_time: str + + +class BotRuntimeInfo(BaseModel): + cpu_percent: str + mem_percent: str + run_time: str + + +class RuntimeInfo(BaseModel): + status_message: str + platform_info: PlatformRuntimeInfo + bot_info: BotRuntimeInfo + + +class ServiceInfo(BaseModel): + service: str + docs: str + permission: list + cmd_list: dict + enabled: bool + only_admin: bool + disable_user: list + disable_group: list + + +class BlockInfo(BaseModel): + user: dict + group: dict diff --git a/ATRI/plugins/console/driver/path.py b/ATRI/plugins/console/driver/path.py new file mode 100644 index 0000000..7009eb7 --- /dev/null +++ b/ATRI/plugins/console/driver/path.py @@ -0,0 +1,14 @@ +CONSOLE_BASE_URL = "/capi" + +CONSOLE_AUTH_URL = CONSOLE_BASE_URL + "/auth" + +CONSOLE_RUNTIME_INFO_URL = CONSOLE_BASE_URL + "/runtime" +CONSOLE_MESSAGE_INFO_URL = CONSOLE_BASE_URL + "/message" + +CONSOLE_SERVICE_BASE_URL = CONSOLE_BASE_URL + "/service" +CONSOLE_SERVICE_LIST_URL = CONSOLE_SERVICE_BASE_URL + "/list" +CONSOLE_SERVICE_EDIT_URL = CONSOLE_SERVICE_BASE_URL + "/edit" + +CONSOLE_BLOCK_BASE_URL = CONSOLE_BASE_URL + "/block" +CONSOLE_BLOCK_LIST_URL = CONSOLE_BLOCK_BASE_URL + "/list" +CONSOLE_BLOCK_EDIT_URL = CONSOLE_BLOCK_BASE_URL + "/edit" diff --git a/ATRI/plugins/console/driver/view.py b/ATRI/plugins/console/driver/view.py deleted file mode 100644 index 624e41f..0000000 --- a/ATRI/plugins/console/driver/view.py +++ /dev/null @@ -1,101 +0,0 @@ -from ..data_source import Console -from ..listener import get_message_deal_info -from .api import ( - control_service, - edit_block_list, - get_block_list, - get_processing_data, - get_service_list, -) - - -def auth_token(token: str) -> tuple: - auth_data: dict = Console().get_auth_info() - if not auth_data.get("token", None): - return False, {"status": 500, "msg": "This bot is not create auth data yet."} - _token = auth_data["token"] - if token != _token: - return False, {"status": 403, "msg": "Token error, please check again."} - else: - return True, {"status": 200, "msg": "OK"} - - -def handle_base_uri(): - return {"status": 204, "msg": "This path just for console load."} - - -def handle_auther(token: str): - auth, data = auth_token(token) - return data if auth else data - - -def handle_runtime_info(token: str): - auth, data = auth_token(token) - if not auth: - return data - - plat, bot = get_processing_data() - return {"status": 200, "data": {"platform": plat, "bot": bot}} - - -def handle_message_deal_info(token: str): - auth, data = auth_token(token) - if not auth: - return data - - return {"status": 200, "data": get_message_deal_info()} - - -def handle_get_service_list(token: str): - auth, data = auth_token(token) - if not auth: - return data - - return {"status": 200, "data": get_service_list()} - - -def handle_control_service( - token: str, - service: str, - is_enabled: int = 1, - enabled_user: str = str(), - enabled_group: str = str(), - disable_user: str = str(), - disable_group: str = str(), -): - auth, data = auth_token(token) - if not auth: - return data - - is_ok, data = control_service( - service, is_enabled, enabled_user, enabled_group, disable_user, disable_group - ) - if not is_ok: - return {"status": 422, "msg": "Dealing service data failed"} - - return {"status": 200, "data": data} - - -def handle_get_block_list(token: str): - auth, data = auth_token(token) - if not auth: - return data - - return {"status": 200, "data": get_block_list()} - - -def handle_edit_block( - token: str, - is_enabled: bool, - user_id: str = str(), - group_id: str = str(), -): - auth, data = auth_token(token) - if not auth: - return data - - is_ok, data = edit_block_list(is_enabled, user_id, group_id) - if not is_ok: - return {"status": 422, "msg": "Dealing block data failed"} - - return {"status": 200, "data": data} diff --git a/ATRI/plugins/console/listener.py b/ATRI/plugins/console/listener.py index 0ae5c25..2c12c75 100644 --- a/ATRI/plugins/console/listener.py +++ b/ATRI/plugins/console/listener.py @@ -30,7 +30,7 @@ async def _(exception: Optional[Exception]): total_r_m += 1 -def get_message_deal_info() -> dict: +def get_message_info() -> dict: return MessageDealerInfo( recv_msg=str(recv_msg), deal_msg=str(deal_msg), diff --git a/ATRI/plugins/console/models.py b/ATRI/plugins/console/models.py index 92371f8..f7ddf8d 100644 --- a/ATRI/plugins/console/models.py +++ b/ATRI/plugins/console/models.py @@ -3,23 +3,8 @@ from pydantic import BaseModel class AuthData(BaseModel): token: str - dead_time: float - - -class PlatformRuntimeInfo(BaseModel): - stat_msg: str - cpu_percent: str - mem_percent: str - disk_percent: str - inte_send: str - inte_recv: str - boot_time: str - - -class BotRuntimeInfo(BaseModel): - cpu_percent: str - mem_percent: str - bot_run_time: str + md5: str + dead_time: int class MessageDealerInfo(BaseModel): @@ -29,11 +14,3 @@ class MessageDealerInfo(BaseModel): total_r_m: str total_d_m: str total_f_m: str - - -class ServiceInfo(BaseModel): - service_name: str - service_docs: str - is_enabled: bool - disable_user: list - disable_group: list |