From 611f0b62ba4803f6ee3f468314478fc7e556daff Mon Sep 17 00:00:00 2001 From: HibiKier <45528451+HibiKier@users.noreply.github.com> Date: Mon, 9 Jun 2025 14:39:28 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E5=BC=95=E7=94=A8=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=90=8C=E6=84=8F=E5=A5=BD=E5=8F=8B/=E7=BE=A4?= =?UTF-8?q?=E7=BB=84=E8=AF=B7=E6=B1=82=20(#1902)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :sparkles: 提供event日志表, 新增引用消息同意好友/群组请求 * :zap: 移除城市列表更新 * :sparkles: 新增申请入群功能 * :speech_balloon: 文本修改 --- zhenxun/builtin_plugins/help_help.py | 2 +- .../platform/qq/group_handle/__init__.py | 20 +++- .../platform/qq/user_group_request.py | 100 ++++++++++++++++++ zhenxun/builtin_plugins/record_request.py | 35 ++++-- zhenxun/builtin_plugins/scripts.py | 55 ---------- zhenxun/builtin_plugins/shop/__init__.py | 1 + .../superuser/request_manage.py | 45 +++++--- zhenxun/models/event_log.py | 21 ++++ zhenxun/models/fg_request.py | 29 ++++- zhenxun/utils/enum.py | 15 +++ zhenxun/utils/platform.py | 34 ++++-- 11 files changed, 265 insertions(+), 92 deletions(-) create mode 100644 zhenxun/builtin_plugins/platform/qq/user_group_request.py create mode 100644 zhenxun/models/event_log.py diff --git a/zhenxun/builtin_plugins/help_help.py b/zhenxun/builtin_plugins/help_help.py index fec04a8d..6b5ecce9 100644 --- a/zhenxun/builtin_plugins/help_help.py +++ b/zhenxun/builtin_plugins/help_help.py @@ -21,7 +21,7 @@ from zhenxun.utils.message import MessageUtils __plugin_meta__ = PluginMetadata( name="笨蛋检测", description="功能名称当命令检测", - usage="""被动""".strip(), + usage="""当一些笨蛋直接输入功能名称时,提示笨蛋使用帮助指令查看功能帮助""".strip(), extra=PluginExtraData( author="HibiKier", version="0.1", diff --git a/zhenxun/builtin_plugins/platform/qq/group_handle/__init__.py b/zhenxun/builtin_plugins/platform/qq/group_handle/__init__.py index 4a88919e..f4c28f04 100644 --- a/zhenxun/builtin_plugins/platform/qq/group_handle/__init__.py +++ b/zhenxun/builtin_plugins/platform/qq/group_handle/__init__.py @@ -1,4 +1,4 @@ -from nonebot import on_notice, on_request +from nonebot import on_notice from nonebot.adapters import Bot from nonebot.adapters.onebot.v11 import ( GroupDecreaseNoticeEvent, @@ -14,9 +14,10 @@ from nonebot_plugin_uninfo import Uninfo from zhenxun.builtin_plugins.platform.qq.exception import ForceAddGroupError from zhenxun.configs.config import BotConfig, Config from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task +from zhenxun.models.event_log import EventLog from zhenxun.models.group_console import GroupConsole from zhenxun.utils.common_utils import CommonUtils -from zhenxun.utils.enum import PluginType +from zhenxun.utils.enum import EventLogType, PluginType from zhenxun.utils.platform import PlatformUtils from zhenxun.utils.rules import notice_rule @@ -106,8 +107,6 @@ group_decrease_handle = on_notice( rule=notice_rule([GroupMemberDecreaseEvent, GroupDecreaseNoticeEvent]), ) """群员减少处理""" -add_group = on_request(priority=1, block=False) -"""加群同意请求""" @group_increase_handle.handle() @@ -142,7 +141,20 @@ async def _( if event.sub_type == "kick_me": """踢出Bot""" await GroupManager.kick_bot(bot, group_id, str(event.operator_id)) + await EventLog.create( + user_id=user_id, group_id=group_id, event_type=EventLogType.KICK_BOT + ) elif event.sub_type in ["leave", "kick"]: + if event.sub_type == "leave": + """主动退群""" + await EventLog.create( + user_id=user_id, group_id=group_id, event_type=EventLogType.LEAVE_MEMBER + ) + else: + """被踢出群""" + await EventLog.create( + user_id=user_id, group_id=group_id, event_type=EventLogType.KICK_MEMBER + ) result = await GroupManager.run_user( bot, user_id, group_id, str(event.operator_id), event.sub_type ) diff --git a/zhenxun/builtin_plugins/platform/qq/user_group_request.py b/zhenxun/builtin_plugins/platform/qq/user_group_request.py new file mode 100644 index 00000000..ae1d32ed --- /dev/null +++ b/zhenxun/builtin_plugins/platform/qq/user_group_request.py @@ -0,0 +1,100 @@ +import asyncio +from datetime import datetime +import random + +from nonebot.adapters import Bot +from nonebot.plugin import PluginMetadata +from nonebot.rule import to_me +from nonebot_plugin_alconna import Alconna, Args, Arparma, Field, on_alconna +from nonebot_plugin_uninfo import Uninfo + +from zhenxun.configs.utils import PluginCdBlock, PluginExtraData +from zhenxun.models.fg_request import FgRequest +from zhenxun.services.log import logger +from zhenxun.utils.depends import UserName +from zhenxun.utils.enum import RequestHandleType, RequestType +from zhenxun.utils.platform import PlatformUtils + +__plugin_meta__ = PluginMetadata( + name="群组申请", + description=""" + 一些小群直接邀请入群导致无法正常生成审核请求,需要用该方法手动生成审核请求。 + 当管理员同意同意时会发送消息进行提示,之后再进行拉群不会退出。 + 该消息会发送至管理员,多次发送不存在的群组id或相同群组id可能导致ban。 + """.strip(), + usage=""" + 指令: + 申请入群 [群号] + 示例: 申请入群 123123123 + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + menu_type="其他", + limits=[PluginCdBlock(cd=300, result="每5分钟只能申请一次哦~")], + ).to_dict(), +) + + +_matcher = on_alconna( + Alconna( + "申请入群", + Args[ + "group_id", + int, + Field( + missing_tips=lambda: "请在命令后跟随群组id!", + unmatch_tips=lambda _: "群组id必须为数字!", + ), + ], + ), + skip_for_unmatch=False, + priority=5, + block=True, + rule=to_me(), +) + + +@_matcher.handle() +async def _( + bot: Bot, session: Uninfo, arparma: Arparma, group_id: int, uname: str = UserName() +): + # 旧请求全部设置为过期 + await FgRequest.filter( + request_type=RequestType.GROUP, + user_id=session.user.id, + group_id=str(group_id), + handle_type__isnull=True, + ).update(handle_type=RequestHandleType.EXPIRE) + f = await FgRequest.create( + request_type=RequestType.GROUP, + platform=PlatformUtils.get_platform(session), + bot_id=bot.self_id, + flag="0", + user_id=session.user.id, + nickname=uname, + group_id=str(group_id), + ) + results = await PlatformUtils.send_superuser( + bot, + f"*****一份入群申请*****\n" + f"ID:{f.id}\n" + f"申请人:{uname}({session.user.id})\n群聊:" + f"{group_id}\n邀请日期:{datetime.now().replace(microsecond=0)}\n" + "注:该请求为手动申请入群", + ) + if message_ids := [ + str(r[1].msg_ids[0]["message_id"]) for r in results if r[1] and r[1].msg_ids + ]: + f.message_ids = ",".join(message_ids) + await f.save(update_fields=["message_ids"]) + await asyncio.sleep(random.randint(1, 5)) + await bot.send_private_msg( + user_id=int(session.user.id), + message=f"已发送申请,请等待管理员审核,ID:{f.id}。", + ) + logger.info( + f"用户 {uname}({session.user.id}) 申请入群 {group_id},ID:{f.id}。", + arparma.header_result, + session=session, + ) diff --git a/zhenxun/builtin_plugins/record_request.py b/zhenxun/builtin_plugins/record_request.py index d4b0c694..32d5d551 100644 --- a/zhenxun/builtin_plugins/record_request.py +++ b/zhenxun/builtin_plugins/record_request.py @@ -17,11 +17,12 @@ from nonebot_plugin_session import EventSession from zhenxun.configs.config import BotConfig, Config from zhenxun.configs.utils import PluginExtraData, RegisterConfig +from zhenxun.models.event_log import EventLog from zhenxun.models.fg_request import FgRequest from zhenxun.models.friend_user import FriendUser from zhenxun.models.group_console import GroupConsole from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType, RequestHandleType, RequestType +from zhenxun.utils.enum import EventLogType, PluginType, RequestHandleType, RequestType from zhenxun.utils.platform import PlatformUtils base_config = Config.get("invite_manager") @@ -112,21 +113,29 @@ async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSessi nickname=nickname, comment=comment, ) - await PlatformUtils.send_superuser( + results = await PlatformUtils.send_superuser( bot, f"*****一份好友申请*****\n" f"ID: {f.id}\n" f"昵称:{nickname}({event.user_id})\n" f"自动同意:{'√' if base_config.get('AUTO_ADD_FRIEND') else '×'}\n" - f"日期:{str(datetime.now()).split('.')[0]}\n" + f"日期:{datetime.now().replace(microsecond=0)}\n" f"备注:{event.comment}", ) + if message_ids := [ + str(r[1].msg_ids[0]["message_id"]) + for r in results + if r[1] and r[1].msg_ids + ]: + f.message_ids = ",".join(message_ids) + await f.save(update_fields=["message_ids"]) else: logger.debug("好友请求五分钟内重复, 已忽略", "好友请求", target=event.user_id) @group_req.handle() async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSession): + # sourcery skip: low-code-quality if event.sub_type != "invite": return if str(event.user_id) in bot.config.superusers or base_config.get("AUTO_ADD_GROUP"): @@ -186,7 +195,7 @@ async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSessio group_id=str(event.group_id), handle_type=RequestHandleType.APPROVE, ) - await PlatformUtils.send_superuser( + results = await PlatformUtils.send_superuser( bot, f"*****一份入群申请*****\n" f"ID:{f.id}\n" @@ -230,13 +239,27 @@ async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSessio nickname=nickname, group_id=str(event.group_id), ) - await PlatformUtils.send_superuser( + kick_count = await EventLog.filter( + group_id=str(event.group_id), event_type=EventLogType.KICK_BOT + ).count() + kick_message = ( + f"\n该群累计踢出{BotConfig.self_nickname} <{kick_count}>次" + if kick_count + else "" + ) + results = await PlatformUtils.send_superuser( bot, f"*****一份入群申请*****\n" f"ID:{f.id}\n" f"申请人:{nickname}({event.user_id})\n群聊:" - f"{event.group_id}\n邀请日期:{datetime.now().replace(microsecond=0)}", + f"{event.group_id}\n邀请日期:{datetime.now().replace(microsecond=0)}" + f"{kick_message}", ) + if message_ids := [ + str(r[1].msg_ids[0]["message_id"]) for r in results if r[1] and r[1].msg_ids + ]: + f.message_ids = ",".join(message_ids) + await f.save(update_fields=["message_ids"]) else: logger.debug( "群聊请求五分钟内重复, 已忽略", diff --git a/zhenxun/builtin_plugins/scripts.py b/zhenxun/builtin_plugins/scripts.py index 27705301..0be7527c 100644 --- a/zhenxun/builtin_plugins/scripts.py +++ b/zhenxun/builtin_plugins/scripts.py @@ -1,66 +1,11 @@ -from asyncio.exceptions import TimeoutError - -import aiofiles import nonebot from nonebot.drivers import Driver -from nonebot_plugin_apscheduler import scheduler -import ujson as json -from zhenxun.configs.path_config import TEXT_PATH from zhenxun.models.group_console import GroupConsole -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx driver: Driver = nonebot.get_driver() -@driver.on_startup -async def update_city(): - """ - 部分插件需要中国省份城市 - 这里直接更新,避免插件内代码重复 - """ - china_city = TEXT_PATH / "china_city.json" - if not china_city.exists(): - data = {} - try: - logger.debug("开始更新城市列表...") - res = await AsyncHttpx.get( - "http://www.weather.com.cn/data/city3jdata/china.html", timeout=5 - ) - res.encoding = "utf8" - provinces_data = json.loads(res.text) - for province in provinces_data.keys(): - data[provinces_data[province]] = [] - res = await AsyncHttpx.get( - f"http://www.weather.com.cn/data/city3jdata/provshi/{province}.html", - timeout=5, - ) - res.encoding = "utf8" - city_data = json.loads(res.text) - for city in city_data.keys(): - data[provinces_data[province]].append(city_data[city]) - async with aiofiles.open(china_city, "w", encoding="utf8") as f: - json.dump(data, f, indent=4, ensure_ascii=False) - logger.info("自动更新城市列表完成.....") - except TimeoutError as e: - logger.warning("自动更新城市列表超时...", e=e) - except ValueError as e: - logger.warning("自动城市列表失败.....", e=e) - except Exception as e: - logger.error("自动城市列表未知错误", e=e) - - -# 自动更新城市列表 -@scheduler.scheduled_job( - "cron", - hour=6, - minute=1, -) -async def _(): - await update_city() - - @driver.on_startup async def _(): """开启/禁用插件格式修改""" diff --git a/zhenxun/builtin_plugins/shop/__init__.py b/zhenxun/builtin_plugins/shop/__init__.py index 89282d63..432b9b92 100644 --- a/zhenxun/builtin_plugins/shop/__init__.py +++ b/zhenxun/builtin_plugins/shop/__init__.py @@ -33,6 +33,7 @@ __plugin_meta__ = PluginMetadata( usage=""" 商品操作 指令: + 商店 我的金币 我的道具 使用道具 [名称/Id] diff --git a/zhenxun/builtin_plugins/superuser/request_manage.py b/zhenxun/builtin_plugins/superuser/request_manage.py index 23b235bf..e6eb6b77 100644 --- a/zhenxun/builtin_plugins/superuser/request_manage.py +++ b/zhenxun/builtin_plugins/superuser/request_manage.py @@ -2,7 +2,7 @@ from io import BytesIO from arclet.alconna import Args, Option from arclet.alconna.typing import CommandMeta -from nonebot.adapters import Bot +from nonebot.adapters import Bot, Event from nonebot.permission import SUPERUSER from nonebot.plugin import PluginMetadata from nonebot.rule import to_me @@ -10,10 +10,13 @@ from nonebot_plugin_alconna import ( Alconna, AlconnaQuery, Arparma, + Match, Query, + Reply, on_alconna, store_true, ) +from nonebot_plugin_alconna.uniseg.tools import reply_fetch from nonebot_plugin_session import EventSession from zhenxun.configs.config import BotConfig @@ -54,7 +57,7 @@ __plugin_meta__ = PluginMetadata( _req_matcher = on_alconna( Alconna( "请求处理", - Args["handle", ["-fa", "-fr", "-fi", "-ga", "-gr", "-gi"]]["id", int], + Args["handle", ["-fa", "-fr", "-fi", "-ga", "-gr", "-gi"]]["id?", int], meta=CommandMeta( description="好友/群组请求处理", usage=usage, @@ -105,12 +108,12 @@ _clear_matcher = on_alconna( ) reg_arg_list = [ - (r"同意好友请求", ["-fa", "{%0}"]), - (r"拒绝好友请求", ["-fr", "{%0}"]), - (r"忽略好友请求", ["-fi", "{%0}"]), - (r"同意群组请求", ["-ga", "{%0}"]), - (r"拒绝群组请求", ["-gr", "{%0}"]), - (r"忽略群组请求", ["-gi", "{%0}"]), + (r"同意好友请求\s*(?P\d*)", ["-fa", "{id}"]), + (r"拒绝好友请求\s*(?P\d*)", ["-fr", "{id}"]), + (r"忽略好友请求\s*(?P\d*)", ["-fi", "{id}"]), + (r"同意群组请求\s*(?P\d*)", ["-ga", "{id}"]), + (r"拒绝群组请求\s*(?P\d*)", ["-gr", "{id}"]), + (r"忽略群组请求\s*(?P\d*)", ["-gi", "{id}"]), ] for r in reg_arg_list: @@ -125,32 +128,48 @@ for r in reg_arg_list: @_req_matcher.handle() async def _( bot: Bot, + event: Event, session: EventSession, handle: str, - id: int, + id: Match[int], arparma: Arparma, ): + reply: Reply | None = None type_dict = { "a": RequestHandleType.APPROVE, "r": RequestHandleType.REFUSED, "i": RequestHandleType.IGNORE, } + if not id.available: + reply = await reply_fetch(event, bot) + if not reply: + await MessageUtils.build_message("请引用消息处理或添加处理Id.").finish() + handle_id = id.result + if reply: + db_data = await FgRequest.get_or_none(message_ids__contains=reply.id) + if not db_data: + await MessageUtils.build_message( + "未发现此消息的Id,请使用Id进行处理..." + ).finish(reply_to=True) + handle_id = db_data.id req = None handle_type = type_dict[handle[-1]] try: if handle_type == RequestHandleType.APPROVE: - req = await FgRequest.approve(bot, id) + req = await FgRequest.approve(bot, handle_id) if handle_type == RequestHandleType.REFUSED: - req = await FgRequest.refused(bot, id) + req = await FgRequest.refused(bot, handle_id) if handle_type == RequestHandleType.IGNORE: - req = await FgRequest.ignore(id) + req = await FgRequest.ignore(handle_id) except NotFoundError: await MessageUtils.build_message("未发现此id的请求...").finish(reply_to=True) except Exception: await MessageUtils.build_message("其他错误, 可能flag已失效...").finish( reply_to=True ) - logger.info("处理请求", arparma.header_result, session=session) + logger.info( + f"处理请求 Id: {req.id if req else ''}", arparma.header_result, session=session + ) await MessageUtils.build_message("成功处理请求!").send(reply_to=True) if req and handle_type == RequestHandleType.APPROVE: await bot.send_private_msg( diff --git a/zhenxun/models/event_log.py b/zhenxun/models/event_log.py new file mode 100644 index 00000000..6737f619 --- /dev/null +++ b/zhenxun/models/event_log.py @@ -0,0 +1,21 @@ +from tortoise import fields + +from zhenxun.services.db_context import Model +from zhenxun.utils.enum import EventLogType + + +class EventLog(Model): + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_id = fields.CharField(255, description="用户id") + """用户id""" + group_id = fields.CharField(255, description="群组id") + """群组id""" + event_type = fields.CharEnumField(EventLogType, default=None, description="类型") + """类型""" + create_time = fields.DatetimeField(auto_now_add=True, description="创建时间") + """创建时间""" + + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] + table = "event_log" + table_description = "各种请求通知记录表" diff --git a/zhenxun/models/fg_request.py b/zhenxun/models/fg_request.py index 4aee1d73..4362a7d3 100644 --- a/zhenxun/models/fg_request.py +++ b/zhenxun/models/fg_request.py @@ -3,8 +3,10 @@ from typing_extensions import Self from nonebot.adapters import Bot from tortoise import fields +from zhenxun.configs.config import BotConfig from zhenxun.models.group_console import GroupConsole from zhenxun.services.db_context import Model +from zhenxun.utils.common_utils import SqlUtils from zhenxun.utils.enum import RequestHandleType, RequestType from zhenxun.utils.exception import NotFoundError @@ -34,6 +36,8 @@ class FgRequest(Model): RequestHandleType, null=True, description="处理类型" ) """处理类型""" + message_ids = fields.CharField(max_length=255, null=True, description="消息id列表") + """消息id列表""" class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "fg_request" @@ -123,9 +127,24 @@ class FgRequest(Model): await GroupConsole.update_or_create( group_id=req.group_id, defaults={"group_flag": 1} ) - await bot.set_group_add_request( - flag=req.flag, - sub_type="invite", - approve=handle_type == RequestHandleType.APPROVE, - ) + if req.flag == "0": + # 用户手动申请入群,创建群认证后提醒用户拉群 + await bot.send_private_msg( + user_id=req.user_id, + message=f"已同意你对{BotConfig.self_nickname}的申请群组:" + f"{req.group_id},可以直接手动拉入群组,{BotConfig.self_nickname}会自动同意。", + ) + else: + # 正常同意群组请求 + await bot.set_group_add_request( + flag=req.flag, + sub_type="invite", + approve=handle_type == RequestHandleType.APPROVE, + ) return req + + @classmethod + async def _run_script(cls): + return [ + SqlUtils.add_column("fg_request", "message_ids", "character varying(255)") + ] diff --git a/zhenxun/utils/enum.py b/zhenxun/utils/enum.py index 91834ec2..2ddf5297 100644 --- a/zhenxun/utils/enum.py +++ b/zhenxun/utils/enum.py @@ -14,6 +14,19 @@ class BankHandleType(StrEnum): """利息""" +class EventLogType(StrEnum): + GROUP_MEMBER_INCREASE = "GROUP_MEMBER_INCREASE" + """群成员增加""" + GROUP_MEMBER_DECREASE = "GROUP_MEMBER_DECREASE" + """群成员减少""" + KICK_MEMBER = "KICK_MEMBER" + """踢出群成员""" + KICK_BOT = "KICK_BOT" + """踢出Bot""" + LEAVE_MEMBER = "LEAVE_MEMBER" + """主动退群""" + + class GoldHandle(StrEnum): """ 金币处理 @@ -105,7 +118,9 @@ class RequestType(StrEnum): """ FRIEND = "FRIEND" + """好友""" GROUP = "GROUP" + """群组""" class RequestHandleType(StrEnum): diff --git a/zhenxun/utils/platform.py b/zhenxun/utils/platform.py index 6d379131..6a13293a 100644 --- a/zhenxun/utils/platform.py +++ b/zhenxun/utils/platform.py @@ -83,7 +83,7 @@ class PlatformUtils: bot: Bot, message: UniMessage | str, superuser_id: str | None = None, - ) -> Receipt | None: + ) -> list[tuple[str, Receipt]]: """发送消息给超级用户 参数: @@ -97,15 +97,33 @@ class PlatformUtils: 返回: Receipt | None: Receipt """ - if not superuser_id: - if platform := cls.get_platform(bot): - if platform_superusers := BotConfig.get_superuser(platform): - superuser_id = random.choice(platform_superusers) - else: - raise NotFindSuperuser() + superuser_ids = [] + if superuser_id: + superuser_ids.append(superuser_id) + elif platform := cls.get_platform(bot): + if platform_superusers := BotConfig.get_superuser(platform): + superuser_ids = platform_superusers + else: + raise NotFindSuperuser() if isinstance(message, str): message = MessageUtils.build_message(message) - return await cls.send_message(bot, superuser_id, None, message) + result = [] + for superuser_id in superuser_ids: + try: + result.append( + ( + superuser_id, + await cls.send_message(bot, superuser_id, None, message), + ) + ) + except Exception as e: + logger.error( + "发送消息给超级用户失败", + "PlatformUtils:send_superuser", + target=superuser_id, + e=e, + ) + return result @classmethod async def get_group_member_list(cls, bot: Bot, group_id: str) -> list[UserData]: