feat✨: 新增Web UI功能及数据库、日志等API接口

This commit is contained in:
HibiKier
2024-07-31 04:58:29 +08:00
parent f0b05ec5ed
commit 2bf5fd1a37
28 changed files with 2643 additions and 18 deletions
@@ -0,0 +1,529 @@
import re
from typing import Literal
import nonebot
from fastapi import APIRouter
from nonebot.adapters.onebot.v11 import ActionFailed
from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState
from tortoise.functions import Count
from zhenxun.configs.config import NICKNAME
from zhenxun.models.ban_console import BanConsole
from zhenxun.models.chat_history import ChatHistory
from zhenxun.models.fg_request import FgRequest
from zhenxun.models.friend_user import FriendUser
from zhenxun.models.group_console import GroupConsole
from zhenxun.models.group_member_info import GroupInfoUser
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.models.statistics import Statistics
from zhenxun.models.task_info import TaskInfo
from zhenxun.services.log import logger
from zhenxun.utils.enum import RequestHandleType, RequestType
from zhenxun.utils.exception import NotFoundError
from zhenxun.utils.platform import PlatformUtils
from ....base_model import Result
from ....config import AVA_URL, GROUP_AVA_URL
from ....utils import authentication
from ...logs.log_manager import LOG_STORAGE
from .model import (
DeleteFriend,
Friend,
FriendRequestResult,
GroupDetail,
GroupRequestResult,
GroupResult,
HandleRequest,
LeaveGroup,
Message,
MessageItem,
Plugin,
ReqResult,
SendMessage,
Task,
UpdateGroup,
UserDetail,
)
ws_router = APIRouter()
router = APIRouter(prefix="/manage")
SUB_PATTERN = r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))"
GROUP_PATTERN = r'.*?Message (-?\d*) from (\d*)@\[群:(\d*)] "(.*)"'
PRIVATE_PATTERN = r'.*?Message (-?\d*) from (\d*) "(.*)"'
AT_PATTERN = r"\[CQ:at,qq=(.*)\]"
IMAGE_PATTERN = r"\[CQ:image,.*,url=(.*);.*?\]"
@router.get(
"/get_group_list", dependencies=[authentication()], description="获取群组列表"
)
async def _(bot_id: str) -> Result:
"""
获取群信息
"""
if bots := nonebot.get_bots():
if bot_id not in bots:
return Result.warning_("指定Bot未连接...")
group_list_result = []
try:
group_info = {}
group_list = await bots[bot_id].get_group_list()
for g in group_list:
gid = g["group_id"]
g["ava_url"] = GROUP_AVA_URL.format(gid, gid)
group_list_result.append(GroupResult(**g))
except Exception as e:
logger.error("调用API错误", "/get_group_list", e=e)
return Result.fail(f"{type(e)}: {e}")
return Result.ok(group_list_result, "拿到了新鲜出炉的数据!")
return Result.warning_("无Bot连接...")
@router.post(
"/update_group", dependencies=[authentication()], description="修改群组信息"
)
async def _(group: UpdateGroup) -> Result:
try:
group_id = group.group_id
if db_group := await GroupConsole.get_or_none(group_id=group_id):
db_group.level = group.level
db_group.status = group.status
if group.close_plugins:
db_group.block_plugin = ",".join(group.close_plugins) + ","
# TODO: 关闭task
await db_group.save(update_fields=["level", "status", "block_plugin"])
except Exception as e:
logger.error("调用API错误", "/get_group", e=e)
return Result.fail(f"{type(e)}: {e}")
return Result.ok(info="已完成记录!")
@router.get(
"/get_friend_list", dependencies=[authentication()], description="获取好友列表"
)
async def _(bot_id: str) -> Result:
"""
获取群信息
"""
if bots := nonebot.get_bots():
if bot_id not in bots:
return Result.warning_("指定Bot未连接...")
try:
platform = PlatformUtils.get_platform(bots[bot_id])
if platform != "qq":
return Result.warning_("该平台暂不支持该功能...")
friend_list = await bots[bot_id].get_friend_list()
for f in friend_list:
f["ava_url"] = AVA_URL.format(f["user_id"])
return Result.ok(
[Friend(**f) for f in friend_list if str(f["user_id"]) != bot_id],
"拿到了新鲜出炉的数据!",
)
except Exception as e:
logger.error("调用API错误", "/get_group_list", e=e)
return Result.fail(f"{type(e)}: {e}")
return Result.warning_("无Bot连接...")
@router.get(
"/get_request_count", dependencies=[authentication()], description="获取请求数量"
)
async def _() -> Result:
f_count = await FgRequest.filter(request_type=RequestType.FRIEND).count()
g_count = await FgRequest.filter(request_type=RequestType.GROUP).count()
data = {
"friend_count": f_count,
"group_count": g_count,
}
return Result.ok(data, f"{NICKNAME}带来了最新的数据!")
@router.get(
"/get_request_list", dependencies=[authentication()], description="获取请求列表"
)
async def _() -> Result:
try:
req_result = ReqResult()
data_list = await FgRequest.filter(handle_type__not_isnull=True).all()
for req in data_list:
if req.request_type == RequestType.FRIEND:
req_result.friend.append(
FriendRequestResult(
oid=req.id,
bot_id=req.bot_id,
id=req.user_id,
flag=req.flag,
nickname=req.nickname,
comment=req.comment,
ava_url=AVA_URL.format(req.user_id),
type=str(req.request_type).lower(),
)
)
else:
req_result.group.append(
GroupRequestResult(
oid=req.id,
bot_id=req.bot_id,
id=req.user_id,
flag=req.flag,
nickname=req.nickname,
comment=req.comment,
ava_url=GROUP_AVA_URL.format(req.group_id, req.group_id),
type=str(req.request_type).lower(),
invite_group=req.group_id,
group_name=None,
)
)
req_result.friend.reverse()
req_result.group.reverse()
except Exception as e:
logger.error("调用API错误", "/get_request", e=e)
return Result.fail(f"{type(e)}: {e}")
return Result.ok(req_result, f"{NICKNAME}带来了最新的数据!")
@router.delete(
"/clear_request", dependencies=[authentication()], description="清空请求列表"
)
async def _(request_type: Literal["private", "group"]) -> Result:
await FgRequest.filter(handle_type__not_isnull=True).update(
handle_type=RequestHandleType.IGNORE
)
return Result.ok(info="成功清除了数据!")
@router.post("/refuse_request", dependencies=[authentication()], description="拒绝请求")
async def _(parma: HandleRequest) -> Result:
try:
if bots := nonebot.get_bots():
bot_id = parma.bot_id
if bot_id not in nonebot.get_bots():
return Result.warning_("指定Bot未连接...")
try:
await FgRequest.refused(bots[bot_id], parma.id)
except ActionFailed as e:
await FgRequest.expire(parma.id)
return Result.warning_("请求失败,可能该请求已失效或请求数据错误...")
except NotFoundError:
return Result.warning_("未找到此Id请求...")
return Result.ok(info="成功处理了请求!")
return Result.warning_("无Bot连接...")
except Exception as e:
logger.error("调用API错误", "/refuse_request", e=e)
return Result.fail(f"{type(e)}: {e}")
@router.post("/delete_request", dependencies=[authentication()], description="忽略请求")
async def _(parma: HandleRequest) -> Result:
await FgRequest.expire(parma.id)
return Result.ok(info="成功处理了请求!")
@router.post(
"/approve_request", dependencies=[authentication()], description="同意请求"
)
async def _(parma: HandleRequest) -> Result:
try:
if bots := nonebot.get_bots():
bot_id = parma.bot_id
if bot_id not in nonebot.get_bots():
return Result.warning_("指定Bot未连接...")
if parma.request_type == "group":
if req := await FgRequest.get_or_none(id=parma.id):
if group := await GroupConsole.get_or_none(group_id=req.group_id):
await group.update_or_create(group_flag=1)
else:
group_info = await bots[bot_id].get_group_info(
group_id=req.group_id
)
await GroupConsole.update_or_create(
group_id=str(group_info["group_id"]),
defaults={
"group_name": group_info["group_name"],
"max_member_count": group_info["max_member_count"],
"member_count": group_info["member_count"],
"group_flag": 1,
},
)
else:
return Result.warning_("未找到此Id请求...")
try:
await FgRequest.approve(bots[bot_id], parma.id)
return Result.ok(info="成功处理了请求!")
except ActionFailed as e:
await FgRequest.expire(parma.id)
return Result.warning_("请求失败,可能该请求已失效或请求数据错误...")
return Result.warning_("无Bot连接...")
except Exception as e:
logger.error("调用API错误", "/approve_request", e=e)
return Result.fail(f"{type(e)}: {e}")
@router.post("/leave_group", dependencies=[authentication()], description="退群")
async def _(param: LeaveGroup) -> Result:
try:
if bots := nonebot.get_bots():
bot_id = param.bot_id
platform = PlatformUtils.get_platform(bots[bot_id])
if platform != "qq":
return Result.warning_("该平台不支持退群操作...")
group_list = await bots[bot_id].get_group_list()
if param.group_id not in [str(g["group_id"]) for g in group_list]:
return Result.warning_("Bot未在该群聊中...")
await bots[bot_id].set_group_leave(group_id=param.group_id)
return Result.ok(info="成功处理了请求!")
return Result.warning_("无Bot连接...")
except Exception as e:
logger.error("调用API错误", "/leave_group", e=e)
return Result.fail(f"{type(e)}: {e}")
@router.post("/delete_friend", dependencies=[authentication()], description="删除好友")
async def _(param: DeleteFriend) -> Result:
try:
if bots := nonebot.get_bots():
bot_id = param.bot_id
platform = PlatformUtils.get_platform(bots[bot_id])
if platform != "qq":
return Result.warning_("该平台不支持删除好友操作...")
friend_list = await bots[bot_id].get_friend_list()
if param.user_id not in [str(g["user_id"]) for g in friend_list]:
return Result.warning_("Bot未有其好友...")
await bots[bot_id].delete_friend(user_id=param.user_id)
return Result.ok(info="成功处理了请求!")
return Result.warning_("Bot未连接...")
except Exception as e:
logger.error("调用API错误", "/delete_friend", e=e)
return Result.fail(f"{type(e)}: {e}")
@router.get(
"/get_friend_detail", dependencies=[authentication()], description="获取好友详情"
)
async def _(bot_id: str, user_id: str) -> Result:
if bots := nonebot.get_bots():
if bot_id in bots:
if fd := [
x
for x in await bots[bot_id].get_friend_list()
if str(x["user_id"]) == user_id
]:
like_plugin_list = (
await Statistics.filter(user_id=user_id)
.annotate(count=Count("id"))
.group_by("plugin_name")
.order_by("-count")
.limit(5)
.values_list("plugin_name", "count")
)
like_plugin = {}
module_list = [x[0] for x in like_plugin_list]
plugins = await PluginInfo.filter(module__in=module_list).all()
module2name = {p.module: p.name for p in plugins}
for data in like_plugin_list:
name = module2name.get(data[0]) or data[0]
like_plugin[name] = data[1]
user = fd[0]
user_detail = UserDetail(
user_id=user_id,
ava_url=AVA_URL.format(user_id),
nickname=user["nickname"],
remark=user["remark"],
is_ban=await BanConsole.is_ban(user_id),
chat_count=await ChatHistory.filter(user_id=user_id).count(),
call_count=await Statistics.filter(user_id=user_id).count(),
like_plugin=like_plugin,
)
return Result.ok(user_detail)
else:
return Result.warning_("未添加指定好友...")
return Result.warning_("无Bot连接...")
@router.get(
"/get_group_detail", dependencies=[authentication()], description="获取群组详情"
)
async def _(bot_id: str, group_id: str) -> Result:
if bots := nonebot.get_bots():
if bot_id in bots:
group = await GroupConsole.get_or_none(group_id=group_id)
if not group:
return Result.warning_("指定群组未被收录...")
like_plugin_list = (
await Statistics.filter(group_id=group_id)
.annotate(count=Count("id"))
.group_by("plugin_name")
.order_by("-count")
.limit(5)
.values_list("plugin_name", "count")
)
like_plugin = {}
plugins = await PluginInfo.all()
module2name = {p.module: p.name for p in plugins}
for data in like_plugin_list:
name = module2name.get(data[0]) or data[0]
like_plugin[name] = data[1]
close_plugins = []
if group.block_plugin:
for module in group.block_plugin.split(","):
module_ = module.replace(":super", "")
is_super_block = module.endswith(":super")
plugin = Plugin(
module=module_,
plugin_name=module,
is_super_block=is_super_block,
)
plugin.plugin_name = module2name.get(module) or module
close_plugins.append(plugin)
all_task = await TaskInfo.annotate().values_list("module", "name")
task_module2name = {x[0]: x[1] for x in all_task}
task_list = []
if group.block_task:
split_task = group.block_task.split(",")
for task in all_task:
task_list.append(
Task(
name=task[0],
zh_name=task_module2name.get(task[0]) or task[0],
status=task[0] not in split_task,
)
)
group_detail = GroupDetail(
group_id=group_id,
ava_url=GROUP_AVA_URL.format(group_id, group_id),
name=group.group_name,
member_count=group.member_count,
max_member_count=group.max_member_count,
chat_count=await ChatHistory.filter(group_id=group_id).count(),
call_count=await Statistics.filter(group_id=group_id).count(),
like_plugin=like_plugin,
level=group.level,
status=group.status,
close_plugins=close_plugins,
task=task_list,
)
return Result.ok(group_detail)
else:
return Result.warning_("未添加指定群组...")
return Result.warning_("无Bot连接...")
@router.post(
"/send_message", dependencies=[authentication()], description="获取群组详情"
)
async def _(param: SendMessage) -> Result:
if bots := nonebot.get_bots():
if param.bot_id in bots:
platform = PlatformUtils.get_platform(bots[param.bot_id])
if platform != "qq":
return Result.warning_("暂不支持该平台...")
try:
if param.user_id:
await bots[param.bot_id].send_private_msg(
user_id=str(param.user_id), message=param.message
)
else:
await bots[param.bot_id].send_group_msg(
group_id=str(param.group_id), message=param.message
)
except Exception as e:
return Result.fail(str(e))
return Result.ok("发送成功!")
return Result.warning_("指定Bot未连接...")
return Result.warning_("无Bot连接...")
MSG_LIST = []
ID2NAME = {}
async def message_handle(
sub_log: str, type: Literal["private", "group"]
) -> Message | None:
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:
if user := await FriendUser.get_or_none(user_id=uid):
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:
if user := await GroupInfoUser.get_or_none(user_id=uid, group_id=gid):
ID2NAME[uid] = user.user_name or user.nickname
if at_list := re.findall(AT_PATTERN, msg):
user_list = await GroupInfoUser.filter(
user_id__in=at_list, group_id=gid
).all()
id2name = {u.user_id: (u.user_name or u.nickname) for u in user_list}
for qq in at_list:
msg = re.sub(rf"\[CQ:at,qq={qq}\]", f"@{id2name[qq] or ''}", msg)
if msg_id in MSG_LIST:
return
MSG_LIST.append(msg_id)
messages = []
if msg and uid:
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, # type: ignore
user_id=uid,
group_id=gid,
message=messages,
name=ID2NAME.get(uid) or "",
ava_url=AVA_URL.format(uid),
)
return None
@ws_router.websocket("/chat")
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 message := await message_handle(sub_log, "private"):
await websocket.send_json(message.dict())
else:
if r := re.search(GROUP_PATTERN, sub_log):
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:
recv = await websocket.receive()
except WebSocketDisconnect:
pass
finally:
LOG_STORAGE.listeners.remove(log_listener)
return
@@ -0,0 +1,265 @@
from typing import Literal
from pydantic import BaseModel
class Group(BaseModel):
"""
群组信息
"""
group_id: str
"""群组id"""
group_name: str
"""群组名称"""
member_count: int
"""成员人数"""
max_member_count: int
"""群组最大人数"""
class Task(BaseModel):
"""
被动技能
"""
name: str
"""被动名称"""
zh_name: str
"""被动中文名称"""
status: bool
"""状态"""
class Plugin(BaseModel):
"""
插件
"""
module: str
"""模块名"""
plugin_name: str
"""中文名"""
is_super_block: bool
"""是否超级用户禁用"""
class GroupResult(BaseModel):
"""
群组返回数据
"""
group_id: str
"""群组id"""
group_name: str
"""群组名称"""
ava_url: str
"""群组头像"""
class Friend(BaseModel):
"""
好友数据
"""
user_id: str
"""用户id"""
nickname: str = ""
"""昵称"""
remark: str = ""
"""备注"""
ava_url: str = ""
"""头像url"""
class UpdateGroup(BaseModel):
"""
更新群组信息
"""
group_id: str
"""群号"""
status: bool
"""状态"""
level: int
"""群权限"""
task: list[str]
"""被动状态"""
close_plugins: list[str]
"""关闭插件"""
class FriendRequestResult(BaseModel):
"""
好友/群组请求管理
"""
bot_id: str
"""bot_id"""
oid: int
"""排序"""
id: str
"""id"""
flag: str
"""flag"""
nickname: str | None
"""昵称"""
comment: str | None
"""备注信息"""
ava_url: str
"""头像"""
type: str
"""类型 private group"""
class GroupRequestResult(FriendRequestResult):
"""
群聊邀请请求
"""
invite_group: str
"""邀请群聊"""
group_name: str | None
"""群聊名称"""
class HandleRequest(BaseModel):
"""
操作请求接收数据
"""
bot_id: str | None = None
"""bot_id"""
id: int
"""数据id"""
request_type: Literal["private", "group"]
"""类型"""
class LeaveGroup(BaseModel):
"""
退出群聊
"""
bot_id: str
"""bot_id"""
group_id: str
"""群聊id"""
class DeleteFriend(BaseModel):
"""
删除好友
"""
bot_id: str
"""bot_id"""
user_id: str
"""用户id"""
class ReqResult(BaseModel):
"""
好友/群组请求列表
"""
friend: list[FriendRequestResult] = []
"""好友请求列表"""
group: list[GroupRequestResult] = []
"""群组请求列表"""
class UserDetail(BaseModel):
"""
用户详情
"""
user_id: str
"""用户id"""
ava_url: str
"""头像url"""
nickname: str
"""昵称"""
remark: str
"""备注"""
is_ban: bool
"""是否被ban"""
chat_count: int
"""发言次数"""
call_count: int
"""功能调用次数"""
like_plugin: dict[str, int]
"""最喜爱的功能"""
class GroupDetail(BaseModel):
"""
用户详情
"""
group_id: str
"""群组id"""
ava_url: str
"""头像url"""
name: str
"""名称"""
member_count: int
"""成员数"""
max_member_count: int
"""最大成员数"""
chat_count: int
"""发言次数"""
call_count: int
"""功能调用次数"""
like_plugin: dict[str, int]
"""最喜爱的功能"""
level: int
"""群权限"""
status: bool
"""状态(睡眠)"""
close_plugins: list[Plugin]
"""关闭的插件"""
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: str | None = None
"""群组id"""
message: list[MessageItem]
"""消息"""
name: str
"""用户名称"""
ava_url: str
"""用户头像"""
class SendMessage(BaseModel):
"""
发送消息
"""
bot_id: str
"""bot id"""
user_id: str | None = None
"""用户id"""
group_id: str | None = None
"""群组id"""
message: str
"""消息"""