Repository: yzyyz1387/nonebot_plugin_admin (language: python)

Issue:
💇 别在这里发癫
别在这里发癫
- 把json读取与写入换成了非异步函数，从而影响部分文件
- 新加入`broadcast.py`广播功能，则`path.py`中需要做出部分更改 
- 将此前用户pr的早晚安加入开关
- `switcher.py`做了（负）优化
【广播功能是指，适用于 su 在复习考研时发癫向所有群广播消息 暂时未写入README】
用例：
![image](https://user-images.githubusercontent.com/51691024/208243731-dd52f8f1-bba8-4c07-a745-6b2515f820bc.png)



Discussion / hints:

Fix patch:
diff --git a/.deepsource.toml b/.deepsource.toml
new file mode 100644
index 00000000..aebeb0fc
--- /dev/null
+++ b/.deepsource.toml
@@ -0,0 +1,8 @@
+version = 1
+
+[[analyzers]]
+name = "python"
+enabled = true
+
+  [analyzers.meta]
+  runtime_version = "3.x.x"
\ No newline at end of file
diff --git "a/.github/ISSUE_TEMPLATE/\345\207\272\347\216\260\344\272\206xxx\351\227\256\351\242\230.md" "b/.github/ISSUE_TEMPLATE/\345\207\272\347\216\260\344\272\206xxx\351\227\256\351\242\230.md"
new file mode 100644
index 00000000..887fbd19
--- /dev/null
+++ "b/.github/ISSUE_TEMPLATE/\345\207\272\347\216\260\344\272\206xxx\351\227\256\351\242\230.md"
@@ -0,0 +1,30 @@
+---
+name: 出现了xxx问题
+about: 详细说明问题以及它如何出现
+title: "[BUG]"
+labels: bug
+assignees: ''
+
+---
+
+**描述 bug**
+救命啊，插件出现了xxxx 问题
+
+**如何复现**
+1. 我通过xxx 方式安装并导入了插件
+2. 在 xx 时候 xx 情况下，出现了 xx 问题
+
+**报错截图 （python的报错请截最下面）**
+这里放截图，可使用任何截图工具截图，然后在这里按下 ** Ctrl + V **
+
+**环境**
+ - 操作系统 
+ - Python 版本 
+ - Nonebot2 版本号
+ - 在那里下载的本插件
+   - pypi (商店安装 或者 pip 安装（本质一样）)
+   - github ( 说明分支 )
+
+
+**其他内容**
+...
diff --git a/.github/workflows/auto-assign.yml b/.github/workflows/auto-assign.yml
new file mode 100644
index 00000000..bee00a48
--- /dev/null
+++ b/.github/workflows/auto-assign.yml
@@ -0,0 +1,14 @@
+name: Auto Assign
+on:
+  issues:
+    types: [opened]
+  pull_request:
+    types: [opened]
+jobs:
+  run:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: wow-actions/auto-assign@v1
+        with:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          CONFIG_FILE: .github/auto-assign.yml
diff --git a/README.md b/README.md
index 17591e58..d1c1d8cc 100644
--- a/README.md
+++ b/README.md
@@ -10,8 +10,11 @@
 **↓**  
 <img src="https://img.shields.io/github/stars/yzyyz1387/nonebot_plugin_admin.svg?style=social">  
 # 简易群管 （考研缓更）  
+
+
    **pypi没更 github最新**   
    **[main分支](https://github.com/yzyyz1387/nonebot_plugin_admin/tree/main)**  
+
   _✨ NoneBot2 (有点不)简易群管✨ _    
 
  
@@ -56,6 +59,8 @@ tenkeys="xxxxxx"
 # 是否开启禁言等操作的成功提示【不开启的话踢人/禁言等成功没有QQ消息提示】
 callback_notice=true # 如果不想开启设置成 false 或者不添加此配置项【默认关闭】
 ```
+✨Pay tribute to A60 [https://github.com/djkcyl/ABot-Graia](https://github.com/djkcyl/ABot-Graia)
+
 **早晚安配置**
 
 额外依赖pip install nonebot_plugin_apscheduler
@@ -73,11 +78,11 @@ send_time_night = "23 0"    # 选填 晚上发送时间默认为22:00
 
 更多配置项请查看 [config.py](./nonebot_plugin_admin/config.py)
 
-
 ✨Pay tribute to A60 [https://github.com/djkcyl/ABot-Graia](https://github.com/djkcyl/ABot-Graia)
 
 ## 注意
-**由于API的加入，首次使用本插件时，会终止机器人程序，需要再启动一次**  
+**[dev分支](https://github.com/yzyyz1387/nonebot_plugin_admin/tree/dev)由于API的加入，首次使用本插件时，会终止机器人程序，需要再启动一次**  
+
 **控制台会有对应提示** 
 
 ## 指令💻
@@ -122,7 +127,7 @@ send_time_night = "23 0"    # 选填 晚上发送时间默认为22:00
     回复某条消息 + 加精
   取消精华
     回复某条消息 + 取消精华
-   
+
 【头衔】
   改头衔
     自助领取：头衔 xxx 
@@ -213,8 +218,32 @@ send_time_night = "23 0"    # 选填 晚上发送时间默认为22:00
     ['词云', '群词云', 'wordcloud'] #群词云
     ['违禁词', '违禁词检测'] #违禁词检测
     ['图片检测', '图片鉴黄', '涩图检测', '色图检测'] #图片检测
+    ['消息记录', '群消息记录', '发言记录'],
+    ['早安晚安', '早安', '晚安'],
+    ['广播消息', '群广播', '广播'],
+    ['事件通知', '变动通知', '事件提醒'],
+     ['防撤回', '防止撤回']
 图片检测和违禁词检测默认关,其他默认开
 
+【广播】permission = SUPERUSER
+本功能默认关闭
+   "发送【广播】/【广播+[消息]】可广播消息" 
+   "发送【群列表】可查看能广播到的所有群" 
+   "发送【排除列表】可查看已排除的群" 
+   "发送【广播排除+】可添加群到广播排除列表" 
+   "发送【广播排除-】可从广播排除列表删除群"
+   "发送【广播帮助】可查看广播帮助"
+   发送【开关广播】来开启/关闭（意义不大）
+   
+【特殊事件提醒】
+包括管理员变动，加群退群等...
+待完善
+  发送【开关事件通知】来开启/关闭功能 permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER
+
+
+【防撤回】
+默认关闭
+ 发送【开关防撤回】开启或关闭功能 permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER
 ```
 
 
diff --git a/nonebot_plugin_admin/__init__.py b/nonebot_plugin_admin/__init__.py
index bf01c234..66872422 100644
--- a/nonebot_plugin_admin/__init__.py
+++ b/nonebot_plugin_admin/__init__.py
@@ -7,27 +7,24 @@
 # @Software: PyCharm
 
 import json
-import asyncio
 import os
-from traceback import print_exc
-from random import randint
-from nonebot.params import CommandArg
+
 import nonebot
-from nonebot.adapters import Message
-from nonebot import on_command, logger, on_notice
-from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, NoticeEvent
-from nonebot.adapters.onebot.v11.exception import ActionFailed
-from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER
-from nonebot.matcher import Matcher
-from nonebot.permission import SUPERUSER
+from nonebot import logger, on_notice
+from nonebot.adapters.onebot.v11 import Bot, NoticeEvent
+
 from . import (
+    admin,
     approve,
     auto_ban,
     auto_ban_,
+    broadcast,
     func_hook,
     group_request_verify,
+    group_recall,
     img_check,
     notice,
+    particular_e_notice,
     requests,
     request_manual,
     word_analyze,
@@ -35,10 +32,10 @@
     switcher,
     utils,
 )
-
-
 from .path import *
-from .utils import At, Reply, MsgText, banSb, check_func_status, change_s_title, log_sd, fi, log_fi, copyFile
+from .switcher import switcher_integrity_check
+from .utils import At, Reply, MsgText, banSb, change_s_title, log_sd, sd, fi, log_fi, copyFile, mk, \
+    dirs, init
 
 if admin_models_path.exists() and admin_models_init_path.exists():
     from . import web
@@ -46,6 +43,7 @@
     db_dirs = []
     models_path = Path(__file__).parent / 'web' / 'api' / 'models'
     from pathlib import Path
+
     if not admin_models_path.exists():
         os.mkdir(admin_models_path)
     if not admin_models_init_path.exists():
@@ -78,396 +76,22 @@
     else:
         print('Undefined os.name')
 
-
 from .group_request_verify import verify
 from .config import global_config
 
 su = global_config.superusers
 
-ban = on_command('禁', priority=1, block=True, permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER)
-
-
-@ban.handle()
-async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
-    """
-    /禁 @user 禁言
-    """
-    try:
-        msg = MsgText(event.json()).replace(' ', '').replace('禁', '')
-        time = int(''.join(map(str, list(map(lambda x: int(x), filter(lambda x: x.isdigit(), msg))))))
-        # 提取消息中所有数字作为禁言时间
-    except ValueError:
-        time = None
-    sb = At(event.json())
-    gid = event.group_id
-    if sb:
-        baning = banSb(gid, ban_list=sb, time=time)
-        try:
-            async for baned in baning:
-                if baned:
-                    await baned
-            await log_fi(matcher, '禁言操作成功' if time is not None else '该用户已被禁言随机时长')
-        except ActionFailed:
-            await fi(matcher, '权限不足')
-
-
-unban = on_command('解', priority=1, block=True, permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER)
-
-
-@unban.handle()
-async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
-    """
-    /解 @user 解禁
-    """
-    sb = At(event.json())
-    gid = event.group_id
-    if sb:
-        baning = banSb(gid, ban_list=sb, time=0)
-        try:
-            async for baned in baning:
-                if baned:
-                    await baned
-            await log_fi(matcher, '解禁操作成功')
-        except ActionFailed:
-            await fi(matcher, '权限不足')
-
-
-ban_all = on_command('/all', aliases={'/全员'}, permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER, priority=1,
-                     block=True)
-
-
-@ban_all.handle()
-async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
-    """
-    （测试时没用..） 
-    # note: 如果在 .env.* 文件内设置了 COMMAND_START ，且不包含 "" (即所有指令都有前缀，假设 '/' 是其中一个前缀)，则应该发 //all 触发 
-    /all 全员禁言
-    /all  解 关闭全员禁言
-    """
-    msg = event.get_message()
-    if msg and '解' in str(msg):
-        enable = False
-    else:
-        enable = True
-    try:
-        await bot.set_group_whole_ban(
-            group_id=event.group_id,
-            enable=enable
-        )
-        await log_fi(matcher, f"全体操作成功: {'禁言' if enable else '解禁'}")
-    except ActionFailed:
-        await fi(matcher, '权限不足')
-
-
-change = on_command('改', permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER, priority=1, block=True)
-
-
-@change.handle()
-async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
-    """
-    /改 @user xxx 改群昵称
-    """
-    msg = str(event.get_message())
-    logger.info(msg.split())
-    sb = At(event.json())
-    gid = event.group_id
-    if sb:
-        try:
-            for user_ in sb:
-                await bot.set_group_card(
-                    group_id=gid,
-                    user_id=int(user_),
-                    card=msg.split()[-1:][0]
-                )
-            await log_fi(matcher, '改名片操作成功')
-        except ActionFailed:
-            await fi(matcher, '权限不足')
-
-
-title = on_command('头衔', priority=1, block=True)
-
-
-@title.handle()
-async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
-    """
-    /头衔 @user  xxx  给某人头衔
-    """
-    # msg = str(event.get_message())
-    msg = MsgText(event.json())
-    s_title = msg.replace(' ', '').replace('头衔', '', 1)
-    sb = At(event.json())
-    gid = event.group_id
-    uid = event.user_id
-    if not sb or (len(sb) == 1 and sb[0] == uid):
-        await change_s_title(bot, matcher, gid, uid, s_title)
-    elif sb:
-        if 'all' not in sb:
-            if uid in su or (str(uid) in su):
-                for qq in sb:
-                    await change_s_title(bot, matcher, gid, int(qq), s_title)
-            else:
-                await fi(matcher, '超级用户才可以更改他人头衔，更改自己头衔请直接使用【头衔 xxx】')
-        else:
-            await fi(matcher, '不能含有@全体成员')
-
-
-title_ = on_command('删头衔', priority=1, block=True)
-
-
-@title_.handle()
-async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
-    """
-    /删头衔 @user 删除头衔
-    """
-    msg = MsgText(event.json())
-    s_title = ''
-    sb = At(event.json())
-    gid = event.group_id
-    uid = event.user_id
-    if not sb or (len(sb) == 1 and sb[0] == uid):
-        await change_s_title(bot, matcher, gid, uid, s_title)
-    elif sb:
-        if 'all' not in sb:
-            if uid in su or (str(uid) in su):
-                for qq in sb:
-                    await change_s_title(bot, matcher, gid, int(qq), s_title)
-            else:
-                await fi(matcher, '超级用户才可以删他人头衔，删除自己头衔请直接使用【删头衔】')
-        else:
-            await fi(matcher, '不能含有@全体成员')
-
-
-kick = on_command('踢', permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER, priority=1, block=True)
-
-
-@kick.handle()
-async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
-    """
-    /踢 @user 踢出某人
-    """
-    msg = str(event.get_message())
-    sb = At(event.json())
-    gid = event.group_id
-    if sb:
-        if 'all' not in sb:
-            try:
-                for qq in sb:
-                    await bot.set_group_kick(
-                        group_id=gid,
-                        user_id=int(qq),
-                        reject_add_request=False
-                    )
-                await log_fi(matcher, '踢人操作成功')
-            except ActionFailed:
-                await fi(matcher, '权限不足')
-        await fi(matcher, '不能含有@全体成员')
-
-
-kick_ = on_command('黑', permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER, priority=1, block=True)
-
+driver = nonebot.get_driver()
 
-@kick_.handle()
-async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
-    """
-    黑 @user 踢出并拉黑某人
-    """
-    msg = str(event.get_message())
-    sb = At(event.json())
-    gid = event.group_id
-    if sb:
-        if 'all' not in sb:
-            try:
-                for qq in sb:
-                    await bot.set_group_kick(
-                        group_id=gid,
-                        user_id=int(qq),
-                        reject_add_request=True
-                    )
-                await log_fi(matcher, '踢人并拉黑操作成功')
-            except ActionFailed:
-                await fi(matcher, '权限不足')
-        await fi(matcher, '不能含有@全体成员')
 
+@driver.on_bot_connect
+async def _():
+    await init()
+    bot = nonebot.get_bot()
+    await switcher_integrity_check(bot)
 
-set_g_admin = on_command('管理员+', permission=SUPERUSER | GROUP_OWNER, priority=1, block=True)
 
 
-@set_g_admin.handle()
-async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
-    """
-    管理员+ @user 添加群管理员
-    """
-    msg = str(event.get_message())
-    logger.info(msg)
-    logger.info(msg.split())
-    sb = At(event.json())
-    logger.info(sb)
-    gid = event.group_id
-    if sb:
-        if 'all' not in sb:
-            try:
-                for qq in sb:
-                    await bot.set_group_admin(
-                        group_id=gid,
-                        user_id=int(qq),
-                        enable=True
-                    )
-                await log_fi(matcher, '设置管理员操作成功')
-            except ActionFailed:
-                await fi(matcher, '权限不足')
-        else:
-            await fi(matcher, '指令不正确 或 不能含有@全体成员')
-    else:
-        await fi(matcher, '没有@人捏')
-
-
-unset_g_admin = on_command('管理员-', permission=SUPERUSER | GROUP_OWNER, priority=1, block=True)
-
-
-@unset_g_admin.handle()
-async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
-    """
-    管理员+ @user 添加群管理员
-    """
-    msg = str(event.get_message())
-    logger.info(msg)
-    logger.info(msg.split())
-    sb = At(event.json())
-    logger.info(sb)
-    gid = event.group_id
-    if sb:
-        if 'all' not in sb:
-            try:
-                for qq in sb:
-                    await bot.set_group_admin(
-                        group_id=gid,
-                        user_id=int(qq),
-                        enable=False
-                    )
-                await log_fi(matcher, '取消管理员操作成功')
-            except ActionFailed:
-                await fi(matcher, '权限不足')
-        await fi(matcher, '指令不正确 或 不能含有@全体成员')
-
-
-msg_recall = on_command('撤回', priority=1, aliases={'删除', 'recall'}, block=True,
-                        permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER)
-
-
-@msg_recall.handle()
-async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):  # by: @tom-snow
-    """
-    指令格式:
-    /撤回 @user n
-    回复指定消息时撤回该条消息；使用艾特时撤回被艾特的人在本群 n*19 历史消息内的所有消息。
-    不输入 n 则默认 n = 5
-    """
-    # msg = str(event.get_message())
-    msg = MsgText(event.json())
-    sb = At(event.json())
-    rp = Reply(event.json())
-    gid = event.group_id
-    recall_msg_id = []
-    if rp:
-        recall_msg_id.append(rp['message_id'])
-    elif sb:
-        seq = None
-        if len(msg.split(' ')) > 1:
-            try:  # counts = n
-                counts = int(msg.split(' ')[-1])
-            except ValueError:
-                counts = 5  # 出现错误就默认为 5 【理论上除非是 /撤回 @user n 且 n 不是数值时才有可能触发】
-        else:
-            counts = 5
-
-        try:
-            for i in range(counts):  # 获取 n 次
-                await asyncio.sleep(randint(0, 5))  # 睡眠随机时间，避免黑号
-                res = await bot.call_api('get_group_msg_history', group_id=gid, message_seq=seq)  # 获取历史消息
-                flag = True
-                for message in res['messages']:  # 历史消息列表
-                    if flag:
-                        seq = int(message['message_seq']) - 1
-                        flag = False
-                    if int(message['user_id']) in sb:  # 将消息id加入列表
-                        recall_msg_id.append(int(message['message_id']))
-        except ActionFailed as e:
-            await log_sd(matcher, '获取群历史消息时发生错误', f"获取群历史消息时发生错误：{e}, seq: {seq}", err=True)
-            print_exc()
-    else:
-        await fi(matcher,
-                 '指令格式：\n/撤回 @user n\n回复指定消息时撤回该条消息；使用艾特时撤回被艾特的人在本群 n*19 历史消息内的所有消息。\n不输入 n 则默认 n = 5')
-
-    # 实际进行撤回的部分
-    if recall_msg_id:
-        try:
-            for msg_id in recall_msg_id:
-                await asyncio.sleep(randint(0, 2))  # 睡眠随机时间，避免黑号
-                await bot.delete_msg(message_id=msg_id)
-            await log_fi(matcher, f"操作成功，一共撤回了 {len(recall_msg_id)} 条消息")
-        except ActionFailed as e:
-            await log_fi(matcher, '撤回失败', f"撤回失败 {e}")
-    else:
-        pass
-
-"""
-! 消息防撤回模块，默认不开启，有需要的自行开启，想对部分群生效也需自行实现(可以并入本插件的开关系统内，也可控制 go-cqhttp 的事件过滤器)
-
-如果在 go-cqhttp 开启了事件过滤器，请确保允许 post_type = notice 通行
-【至少也得允许 notice_type = group_recall 通行】
-"""
-
-
-async def _group_recall(bot: Bot, event: NoticeEvent) -> bool:
-    # 有需要自行取消注释
-    # if event.notice_type == 'group_recall':
-    #     return True
-    return False
-
-
-group_recall = on_notice(_group_recall, priority=5)
-
-
-@group_recall.handle()
-async def _(bot: Bot, event: NoticeEvent):
-    event_obj = json.loads(event.json())
-    user_id = event_obj['user_id']  # 消息发送者
-    operator_id = event_obj['operator_id']  # 撤回消息的人
-    group_id = event_obj['group_id']  # 群号
-    message_id = event_obj['message_id']  # 消息 id
-
-    if int(user_id) != int(operator_id): return  # 撤回人不是发消息人，是管理员撤回成员消息，不处理
-    if int(operator_id) in su or str(operator_id) in su: return  # 发起撤回的人是超管，不处理
-    # 管理员撤回自己的也不处理
-    operator_info = await bot.get_group_member_info(group_id=group_id, user_id=operator_id, no_cache=True)
-    if operator_info['role'] != 'member': return
-    # 防撤回
-    recalled_message = await bot.get_msg(message_id=message_id)
-    recall_notice = f"检测到{operator_info['card'] if operator_info['card'] else operator_info['nickname']}({operator_info['user_id']})撤回了一条消息：\n\n"
-    await bot.send_group_msg(group_id=group_id, message=recall_notice + recalled_message['message'])
-
-
-set_essence = on_command("加精", aliases={'加精', 'set_essence'}, priority=5, block=True)
-
-
-@set_essence.handle()
-async def _(bot: Bot, event: GroupMessageEvent):
-    rp = Reply(event.json())
-    if rp:
-        msg_id = rp['message_id']
-        await bot.call_api(api='set_essence_msg', message_id=msg_id)
-
-
-del_essence = on_command("取消精华", aliases={'取消加精', 'del_essence'}, priority=5, block=True)
-
-
-@del_essence.handle()
-async def _(bot: Bot, event: GroupMessageEvent):
-    rp = Reply(event.json())
-    if rp:
-        msg_id = rp['message_id']
-        await bot.call_api(api='delete_essence_msg', message_id=msg_id)
-
 
 __usage__ = """
 【群管】：
@@ -489,15 +113,15 @@ async def _(bot: Bot, event: GroupMessageEvent):
   踢出：
     踢 @某人
   踢出并拉黑：
-   黑 @某人
+  黑 @某人
   撤回:
-   撤回 (回复某条消息即可撤回对应消息)
-   撤回 @user [(可选，默认n = 5)历史消息倍数n] (实际检查的历史数为 n*19)
-   
+  撤回 (回复某条消息即可撤回对应消息)
+  撤回 @user [(可选，默认n = 5)历史消息倍数n] (实际检查的历史数为 n*19)
+
 【管理员】permission = SUPERUSER | GROUP_OWNER
   管理员+ @xxx 设置某人为管理员
   管理员- @xxx 取消某人管理员
-  
+
 【加群自动审批】：
 群内发送 permission = GROUP_ADMIN | GROUP_OWNER | SUPERUSER
   查看词条 ： 查看本群审批词条   或/审批
@@ -519,14 +143,14 @@ async def _(bot: Bot, event: GroupMessageEvent):
 群内或私聊 permission = SUPERUSER
   所有分管 ：查看所有分群管理员
   群管接收 ：打开或关闭超管消息接收（关闭则审批结果不会发送给superusers）
-  
+
 【群词云统计】
 该功能所用库 wordcloud 未写入依赖，请自行安装
 群内发送：
   记录本群 ： 开始统计聊天记录 permission = GROUP_ADMIN | GROUP_OWNER | SUPERUSER
   停止记录本群 ：停止统计聊天记录
   群词云 ： 发送词云图片
-  
+
 【被动识别】
 涩图检测：将禁言随机时间
 
diff --git a/nonebot_plugin_admin/admin.py b/nonebot_plugin_admin/admin.py
new file mode 100644
index 00000000..b3f60ad6
--- /dev/null
+++ b/nonebot_plugin_admin/admin.py
@@ -0,0 +1,378 @@
+# python3
+# -*- coding: utf-8 -*-
+# @Time    : 2022/12/19 3:01
+# @Author  : yzyyz
+# @Email   :  youzyyz1384@qq.com
+# @File    : admin.py
+# @Software: PyCharm
+import asyncio
+from random import randint
+from traceback import print_exc
+
+from nonebot import on_command, logger
+from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent
+from nonebot.adapters.onebot.v11.exception import ActionFailed
+from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER
+from nonebot.matcher import Matcher
+from nonebot.permission import SUPERUSER
+
+from .config import global_config
+from .utils import At, MsgText, banSb, change_s_title, fi, log_fi, sd, Reply, log_sd
+
+su = global_config.superusers
+
+ban = on_command('禁', priority=1, block=True, permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER)
+
+
+@ban.handle()
+async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
+    """
+    /禁 @user 禁言
+    """
+    try:
+        msg = MsgText(event.json()).replace(' ', '').replace('禁', '')
+        time = int(''.join(map(str, list(map(lambda x: int(x), filter(lambda x: x.isdigit(), msg))))))
+        # 提取消息中所有数字作为禁言时间
+    except ValueError:
+        time = None
+    sb = At(event.json())
+    gid = event.group_id
+    if sb:
+        baning = banSb(gid, ban_list=sb, time=time)
+        try:
+            async for baned in baning:
+                if baned:
+                    await baned
+            await log_fi(matcher, '禁言操作成功' if time is not None else '用户已被禁言随机时')
+        except ActionFailed:
+            await fi(matcher, '权限不足')
+
+
+unban = on_command('解', priority=1, block=True, permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER)
+
+
+@unban.handle()
+async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
+    """
+    /解 @user 解禁
+    """
+    sb = At(event.json())
+    gid = event.group_id
+    if sb:
+        baning = banSb(gid, ban_list=sb, time=0)
+        try:
+            async for baned in baning:
+                if baned:
+                    await baned
+            await log_fi(matcher, '解禁操作成功')
+        except ActionFailed:
+            await fi(matcher, '权限不足')
+
+
+ban_all = on_command('/all', aliases={'/全员'}, permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER, priority=1,
+                     block=True)
+
+
+@ban_all.handle()
+async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
+    """
+    # note: 如果在 .env.* 文件内设置了 COMMAND_START ，且不包含 "" (即所有指令都有前缀，假设 '/' 是其中一个前缀)，则应该发 //all 触发
+    /all 全员禁言
+    /all  解 关闭全员禁言
+    """
+    msg = event.get_message()
+    if msg and '解' in str(msg):
+        enable = False
+    else:
+        enable = True
+    try:
+        await bot.set_group_whole_ban(
+            group_id=event.group_id,
+            enable=enable
+        )
+        await log_fi(matcher, f"全体操作成功: {'禁言' if enable else '解禁'}")
+    except ActionFailed:
+        await fi(matcher, '权限不足')
+
+
+change = on_command('改', permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER, priority=1, block=True)
+
+
+@change.handle()
+async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
+    """
+    /改 @user xxx 改群昵称
+    """
+    msg = str(event.get_message())
+    logger.info(msg.split())
+    sb = At(event.json())
+    gid = event.group_id
+    if sb:
+        try:
+            for user_ in sb:
+                await bot.set_group_card(
+                    group_id=gid,
+                    user_id=int(user_),
+                    card=msg.split()[-1:][0]
+                )
+            await log_fi(matcher, '改名片操作成功')
+        except ActionFailed:
+            await fi(matcher, '权限不足')
+
+
+title = on_command('头衔', priority=1, block=True)
+
+
+@title.handle()
+async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
+    """
+    /头衔 @user  xxx  给某人头衔
+    """
+    # msg = str(event.get_message())
+    msg = MsgText(event.json())
+    s_title = msg.replace(' ', '').replace('头衔', '', 1)
+    sb = At(event.json())
+    gid = event.group_id
+    uid = event.user_id
+    if not sb or (len(sb) == 1 and sb[0] == uid):
+        await change_s_title(bot, matcher, gid, uid, s_title)
+    elif sb:
+        if 'all' not in sb:
+            if uid in su or (str(uid) in su):
+                for qq in sb:
+                    await change_s_title(bot, matcher, gid, int(qq), s_title)
+            else:
+                await fi(matcher, '超级用户才可以更改他人头衔，更改自己头衔请直接使用【头衔 xxx】')
+        else:
+            await fi(matcher, '不能含有@全体成员')
+
+
+title_ = on_command('删头衔', priority=1, block=True)
+
+
+@title_.handle()
+async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
+    """
+    /删头衔 @user 删除头衔
+    """
+    s_title = ''
+    sb = At(event.json())
+    gid = event.group_id
+    uid = event.user_id
+    if not sb or (len(sb) == 1 and sb[0] == uid):
+        await change_s_title(bot, matcher, gid, uid, s_title)
+    elif sb:
+        if 'all' not in sb:
+            if uid in su or (str(uid) in su):
+                for qq in sb:
+                    await change_s_title(bot, matcher, gid, int(qq), s_title)
+            else:
+                await fi(matcher, '超级用户才可以删他人头衔，删除自己头衔请直接使用【删头衔】')
+        else:
+            await fi(matcher, '不能含有@全体成员')
+
+
+kick = on_command('踢', permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER, priority=1, block=True)
+
+
+@kick.handle()
+async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
+    """
+    /踢 @user 踢出某人
+    """
+    sb = At(event.json())
+    gid = event.group_id
+    if sb:
+        if 'all' not in sb:
+            try:
+                for qq in sb:
+                    if qq == event.user_id:
+                        await sd(matcher, '你在玩一种很新的东西，不能踢自己!')
+                        continue
+                    if qq in su or (str(qq) in su):
+                        await sd(matcher, '超级用户不能被踢')
+                        continue
+                    await bot.set_group_kick(
+                        group_id=gid,
+                        user_id=int(qq),
+                        reject_add_request=False
+                    )
+                await log_fi(matcher, '踢人操作执行完毕')
+            except ActionFailed:
+                await fi(matcher, '权限不足')
+        await fi(matcher, '不能含有@全体成员')
+
+
+kick_ = on_command('黑', permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER, priority=1, block=True)
+
+
+@kick_.handle()
+async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
+    """
+    黑 @user 踢出并拉黑某人
+    """
+    sb = At(event.json())
+    gid = event.group_id
+    if sb:
+        if 'all' not in sb:
+            try:
+                for qq in sb:
+                    if qq == event.user_id:
+                        await sd(matcher, '你在玩一种很新的东西，不能踢自己!')
+                        continue
+                    if qq in su or (str(qq) in su):
+                        await sd(matcher, '超级用户不能被踢')
+                        continue
+                    await bot.set_group_kick(
+                        group_id=gid,
+                        user_id=int(qq),
+                        reject_add_request=True
+                    )
+                await log_fi(matcher, '踢人并拉黑操作执行完毕')
+            except ActionFailed:
+                await fi(matcher, '权限不足')
+        await fi(matcher, '不能含有@全体成员')
+
+
+set_g_admin = on_command('管理员+', permission=SUPERUSER | GROUP_OWNER, priority=1)
+
+
+@set_g_admin.handle()
+async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
+    """
+    管理员+ @user 添加群管理员
+    """
+    msg = str(event.get_message())
+    logger.info(msg)
+    logger.info(msg.split())
+    sb = At(event.json())
+    logger.info(sb)
+    gid = event.group_id
+    if sb:
+        if 'all' not in sb:
+            try:
+                for qq in sb:
+                    await bot.set_group_admin(
+                        group_id=gid,
+                        user_id=int(qq),
+                        enable=True
+                    )
+                await log_fi(matcher, '设置管理员操作成功')
+            except ActionFailed:
+                await fi(matcher, '权限不足')
+        else:
+            await fi(matcher, '指令不正确 或 不能含有@全体成员')
+    else:
+        await fi(matcher, '没有@人捏')
+
+
+unset_g_admin = on_command('管理员-', permission=SUPERUSER | GROUP_OWNER, priority=1)
+
+
+@unset_g_admin.handle()
+async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
+    """
+    管理员+ @user 添加群管理员
+    """
+    msg = str(event.get_message())
+    logger.info(msg)
+    logger.info(msg.split())
+    sb = At(event.json())
+    logger.info(sb)
+    gid = event.group_id
+    if sb:
+        if 'all' not in sb:
+            try:
+                for qq in sb:
+                    await bot.set_group_admin(
+                        group_id=gid,
+                        user_id=int(qq),
+                        enable=False
+                    )
+                await log_fi(matcher, '取消管理员操作成功')
+            except ActionFailed:
+                await fi(matcher, '权限不足')
+        await fi(matcher, '指令不正确 或 不能含有@全体成员')
+
+
+set_essence = on_command("加精", aliases={'加精', 'set_essence'}, priority=5, block=True)
+
+
+@set_essence.handle()
+async def _(bot: Bot, event: GroupMessageEvent):
+    rp = Reply(event.json())
+    if rp:
+        msg_id = rp['message_id']
+        await bot.call_api(api='set_essence_msg', message_id=msg_id)
+
+
+del_essence = on_command("取消精华", aliases={'取消加精', 'del_essence'}, priority=5, block=True)
+
+
+@del_essence.handle()
+async def _(bot: Bot, event: GroupMessageEvent):
+    rp = Reply(event.json())
+    if rp:
+        msg_id = rp['message_id']
+        await bot.call_api(api='delete_essence_msg', message_id=msg_id)
+
+
+msg_recall = on_command('撤回', priority=1, aliases={'recall'}, block=True,
+                        permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER)
+
+
+@msg_recall.handle()
+async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):  # by: @tom-snow
+    """
+    指令格式:
+    /撤回 @user n
+    回复指定消息时撤回该条消息；使用艾特时撤回被艾特的人在本群 n*19 历史消息内的所有消息。
+    不输入 n 则默认 n = 5
+    """
+    # msg = str(event.get_message())
+    msg = MsgText(event.json())
+    sb = At(event.json())
+    rp = Reply(event.json())
+    gid = event.group_id
+    recall_msg_id = []
+    if rp:
+        recall_msg_id.append(rp['message_id'])
+    elif sb:
+        seq = None
+        if len(msg.split(' ')) > 1:
+            try:  # counts = n
+                counts = int(msg.split(' ')[-1])
+            except ValueError:
+                counts = 5  # 出现错误就默认为 5 【理论上除非是 /撤回 @user n 且 n 不是数值时才有可能触发】
+        else:
+            counts = 5
+
+        try:
+            for _ in range(counts):  # 获取 n 次
+                await asyncio.sleep(randint(0, 5))  # 睡眠随机时间，避免黑号
+                res = await bot.call_api('get_group_msg_history', group_id=gid, message_seq=seq)  # 获取历史消息
+                flag = True
+                for message in res['messages']:  # 历史消息列表
+                    if flag:
+                        seq = int(message['message_seq']) - 1
+                        flag = False
+                    if int(message['user_id']) in sb:  # 将消息id加入列表
+                        recall_msg_id.append(int(message['message_id']))
+        except ActionFailed as e:
+            await log_sd(matcher, '获取群历史消息时发生错误', f"获取群历史消息时发生错误：{e}, seq: {seq}", err=True)
+            print_exc()
+    else:
+        await fi(matcher,
+                 '指令格式：\n/撤回 @user n\n回复指定消息时撤回该条消息；使用艾特时撤回被艾特的人在本群 n*19 历史消息内的所有消息。\n不输入 n 则默认 n = 5')
+
+    # 实际进行撤回的部分
+    if recall_msg_id:
+        try:
+            for msg_id in recall_msg_id:
+                await asyncio.sleep(randint(0, 2))  # 睡眠随机时间，避免黑号
+                await bot.delete_msg(message_id=msg_id)
+            await log_fi(matcher, f"操作成功，一共撤回了 {len(recall_msg_id)} 条消息")
+        except ActionFailed as e:
+            await log_fi(matcher, '撤回失败', f"撤回失败 {e}")
+    else:
+        pass
diff --git a/nonebot_plugin_admin/approve.py b/nonebot_plugin_admin/approve.py
index 3fa77263..e38f7053 100644
--- a/nonebot_plugin_admin/approve.py
+++ b/nonebot_plugin_admin/approve.py
@@ -11,7 +11,7 @@
 from nonebot import logger
 
 from .path import *
-from .utils import load
+from .utils import json_load
 
 
 async def g_admin():
@@ -19,8 +19,7 @@ async def g_admin():
     :return : 分群管理json对象
     """
     with open(config_group_admin, mode='r') as f:
-        admins_ = f.read()
-        admins = json.loads(admins_)
+        admins = json.load(f)
     return admins
 
 
@@ -103,7 +102,7 @@ async def write(gid: str, answer: str) -> Optional[bool]:
     :param answer: 词条
     :return: bool
     """
-    contents = await load(config_admin)
+    contents = json_load(config_admin)
     if gid in contents:
         data = contents[gid]
         if answer in data:
@@ -132,7 +131,7 @@ async def delete(gid: str, answer: str) -> Optional[bool]:
     :param answer: 词条
     :return: bool
     """
-    contents = await load(config_admin)
+    contents = json_load(config_admin)
     if gid in contents:
         if answer in contents[gid]:
             data = contents[gid]
diff --git a/nonebot_plugin_admin/auto_ban.py b/nonebot_plugin_admin/auto_ban.py
index 74a4a547..56ca9a09 100644
--- a/nonebot_plugin_admin/auto_ban.py
+++ b/nonebot_plugin_admin/auto_ban.py
@@ -27,7 +27,7 @@ async def _(bot: Bot, event: GroupMessageEvent, matcher: Matcher):
     :return:
     """
     rules = [re.sub(r'\t+', '\t', rule).split('\t') for rule in
-             open(limit_word_path, 'r', encoding='utf-8').read().split('\n')]
+             limit_word_path.read_text(encoding='utf-8').split('\n')]
     msg = re.sub(r'\s', '', str(event.get_message()))
     gid = event.group_id
     uid = event.user_id
diff --git a/nonebot_plugin_admin/broadcast.py b/nonebot_plugin_admin/broadcast.py
new file mode 100644
index 00000000..333efeed
--- /dev/null
+++ b/nonebot_plugin_admin/broadcast.py
@@ -0,0 +1,234 @@
+# python3
+# -*- coding: utf-8 -*-
+# @Time    : 2022/12/17 18:07
+# @Author  : yzyyz
+# @Email   :  youzyyz1384@qq.com
+# @File    : broadcast.py
+# @Software: PyCharm
+import asyncio
+
+from nonebot import logger, on_command, get_bot
+from nonebot.adapters import Message
+from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent, ActionFailed
+from nonebot.matcher import Matcher
+from nonebot.params import CommandArg, ArgStr, Arg
+from nonebot.permission import SUPERUSER
+from nonebot.typing import T_State
+
+from .config import global_config
+from .path import broadcast_avoid_path
+from .utils import json_load, json_upload, sd, fi
+
+try:
+    su = global_config.superusers
+except AttributeError:
+    su = []
+    logger.error("请配置超级用户")
+
+if broadcast_avoid_path.exists():
+    broadcast_config = json_load(broadcast_avoid_path)
+else:
+    broadcast_config = {}
+    if su:
+        for su_ in su:
+            broadcast_config.update({str(su_): []})
+            json_upload(broadcast_avoid_path, broadcast_config)
+
+on_broadcast = on_command('广播', aliases={'告诉所有人', '告诉大家', '告诉全世界'}, priority=1, block=True,
+                          permission=SUPERUSER)
+
+
+@on_broadcast.handle()
+async def _(bot: Bot, event: MessageEvent, state: T_State, matcher: Matcher, args: Message = CommandArg()):
+    if args:
+        state['b_args'] = args
+        await broadcast(str(event.user_id), args, bot, matcher)
+
+
+@on_broadcast.got('b_args', prompt='请输入要广播的内容，发送【取消】取消')
+async def _(
+        bot: Bot,
+        event: MessageEvent,
+        state: T_State,
+        matcher: Matcher,
+        b_args: Message = Arg("b_args")):
+    uid = str(event.user_id)
+    for i in ["取消", "算了", "退出"]:
+        if i in str(b_args):
+            await fi(matcher, "已取消广播")
+        else:
+            await broadcast(uid, b_args, bot, matcher)
+
+
+add_broadcast_avoid = on_command('广播排除', priority=1, block=True, permission=SUPERUSER)
+
+
+@add_broadcast_avoid.handle()
+async def _(bot: Bot, event: MessageEvent, state: T_State, matcher: Matcher, args: Message = CommandArg()):
+    """
+    添加广播排除群
+    """
+    if args:
+        if "+" in str(args):
+            await add_avoid_group(event, args, matcher, state)
+        elif "-" in str(args):
+            await del_avoid_group(event, args, matcher)
+    else:
+        await fi(matcher, "请发送【广播排除+12345 / 广播排除-123456】\n多个群用空格分隔，查看所有群号请发送【群列表】")
+
+
+all_group_list = on_command('群列表', priority=1, block=True, permission=SUPERUSER)
+
+
+@all_group_list.handle()
+async def _(bot: Bot, event: MessageEvent, state: T_State, matcher: Matcher):
+    """
+    获取所有群号
+    """
+    groups = await bot.get_group_list()
+    r = ""
+    g_list = []
+    for g in groups:
+        g_list.append(str(g["group_id"]))
+        r += f"{g['group_name']}: {g['group_id']} \n"
+    state['g_list'] = g_list
+    await sd(matcher, f"共【{len(g_list)}】个群\n{r}")
+
+
+@all_group_list.got("gid",
+                    prompt="你现在可以直接告诉我群号，我可以帮你添加到广播排除列表\n多个用【空格】分隔\n只保留本群其他全部排除请回复【0】\n发送【取消】取消")
+async def _(
+        bot: Bot,
+        event: MessageEvent,
+        state: T_State,
+        matcher: Matcher,
+        gid: str = ArgStr("gid")):
+    """
+    添加群号到广播排除列表
+    """
+    g_list = state['g_list']
+    for i in ["取消", "算了", "退出"]:
+        if i in gid:
+            await fi(matcher, "已取消添加")
+    if gid == "0":
+        if isinstance(event, GroupMessageEvent):
+            g_list.remove(str(event.group_id))
+            g_avoid = " ".join(g_list)
+            await add_avoid_group(event, g_avoid, matcher, state)
+        else:
+            await fi(matcher, "当前不在群聊中")
+    else:
+        await add_avoid_group(event, gid, matcher, state)
+
+
+async def add_avoid_group(event: MessageEvent, args, matcher: Matcher, state: T_State):
+    uid = str(event.user_id)
+    args = str(args).replace("+", "").strip()
+    groups = str(args).split(" ")
+    groups_bot = await get_bot().get_group_list()
+    g_list = []
+    for g in groups_bot:
+        g_list.append(str(g["group_id"]))
+    r = ""
+    try:
+        history_group = broadcast_config[uid]
+    except KeyError:
+        history_group = []
+    for g in groups:
+        if g in g_list:
+            if g in history_group:
+                r += f"{g} 已存在,跳过\n"
+                continue
+            else:
+                r += f"{g} 添加到广播排除列表成功\n"
+                history_group.append(str(g))
+        else:
+            r += f"我不在群 {g} 捏,跳过\n"
+            continue
+    broadcast_config.update({uid: history_group})
+    json_upload(broadcast_avoid_path, broadcast_config)
+    await sd(matcher, f"{r}\n 发送【排除列表】可查看已排除的群")
+
+
+avoided_group_list = on_command('排除列表', priority=1, block=True, permission=SUPERUSER)
+
+
+@avoided_group_list.handle()
+async def _(bot: Bot, event: MessageEvent, state: T_State, matcher: Matcher):
+    """
+    获取广播排除列表
+    """
+    uid = str(event.user_id)
+    _config = json_load(broadcast_avoid_path)
+    try:
+        history_group = _config[uid]
+    except KeyError:
+        history_group = []
+    r = "【排除列表】"
+    for g in history_group:
+        g_name = (await bot.get_group_info(group_id=int(g)))['group_name']
+        r += f"{g_name}: {g} \n"
+    await sd(matcher, f"共【{len(history_group)}】个群\n{r}")
+
+
+async def del_avoid_group(event: MessageEvent, args, matcher: Matcher):
+    args = str(args).replace("-", "").strip()
+    uid = str(event.user_id)
+    groups = str(args).split(" ")
+    try:
+        history_group = broadcast_config[uid]
+        for g in groups:
+            if g in history_group:
+                history_group.remove(g)
+            else:
+                groups.remove(g)
+        broadcast_config.update({uid: history_group})
+        json_upload(broadcast_avoid_path, broadcast_config)
+        r = '\n'.join(groups)
+        await sd(matcher, f"已从广播排除列表中删除\n{r}")
+    except KeyError:
+        await fi(matcher, "广播排除列表为空")
+
+
+async def broadcast(uid: str, args: Message, bot: Bot, matcher: Matcher):
+    b_config = json_load(broadcast_avoid_path)
+    if uid in b_config:
+        groups = await bot.get_group_list()
+        group_list = [g["group_id"] for g in groups]
+        success = 0
+        failed = 0
+        excluded = 0
+        for g in group_list:
+            if str(g) not in b_config[uid]:
+                try:
+                    success += 1
+                    logger.debug(f"正在向群{g}广播{args}")
+                    await bot.send_group_msg(group_id=g, message="广播：")
+                    await bot.send_group_msg(group_id=g, message=args)
+                except ActionFailed as e:
+                    failed += 1
+                    logger.error(f"广播时发生错误{e}")
+            else:
+                excluded += 1
+                logger.debug(f"群{g}已在广播排除列表中")
+        await asyncio.sleep(1)
+        await fi(matcher, f"广播完成\n成功：{success}\n失败：{failed}\n排除：{excluded}")
+
+
+broad_cast_help = on_command('广播帮助', priority=1, block=True)
+
+
+@broad_cast_help.handle()
+async def _(bot: Bot, event: MessageEvent, state: T_State, matcher: Matcher):
+    """
+    广播帮助
+    """
+    r = "【广播帮助】\n" \
+        "发送【广播】/【广播+[消息]】可广播消息\n" \
+        "发送【群列表】可查看能广播到的所有群\n" \
+        "发送【排除列表】可查看已排除的群\n" \
+        "发送【广播排除+】可添加群到广播排除列表\n" \
+        "发送【广播排除-】可从广播排除列表删除群\n" \
+        "发送【广播帮助】可查看广播帮助"
+    await sd(matcher, r)
+# FIXME 适用于 su 在复习考研时发癫向所有群广播消息 暂时未写入README
diff --git a/nonebot_plugin_admin/config.py b/nonebot_plugin_admin/config.py
index e5c2cd6d..4f3e8d00 100644
--- a/nonebot_plugin_admin/config.py
+++ b/nonebot_plugin_admin/config.py
@@ -1,7 +1,6 @@
 # from typing import Optional
 from nonebot import get_driver
 from pydantic import BaseModel, Extra
-from . import utils
 
 
 class Config(BaseModel, extra=Extra.ignore):
@@ -17,6 +16,4 @@ class Config(BaseModel, extra=Extra.ignore):
 plugin_config = Config.parse_obj(global_config)
 
 
-@driver.on_bot_connect
-async def _():
-    await utils.init()
+
diff --git a/nonebot_plugin_admin/func_hook.py b/nonebot_plugin_admin/func_hook.py
index 7fcdbb78..5a155012 100644
--- a/nonebot_plugin_admin/func_hook.py
+++ b/nonebot_plugin_admin/func_hook.py
@@ -5,44 +5,56 @@
 # @Email   :  youzyyz1384@qq.com
 # @File    : func_hook.py
 # @Software: PyCharm
+import nonebot
 from nonebot import logger
 from nonebot.adapters.onebot.v11 import (
     Bot,
     ActionFailed,
     GroupMessageEvent,
     GroupRequestEvent,
-    Event,
+    Event, HonorNotifyEvent, GroupUploadNoticeEvent, GroupDecreaseNoticeEvent, GroupIncreaseNoticeEvent,
+    GroupAdminNoticeEvent, LuckyKingNotifyEvent,
 )
 from nonebot.matcher import Matcher
 from nonebot.message import run_preprocessor, IgnoredException
 from nonebot.typing import T_State
 from pathlib import Path
 
+from .switcher import switcher_integrity_check
 from .config import plugin_config, global_config
 from .path import *
-from .utils import check_func_status
+from .utils import json_load
 
 cb_notice = plugin_config.callback_notice
 su = global_config.superusers
 admin_path = Path(__file__).parts[-2]
 
+
 @run_preprocessor
 async def _(matcher: Matcher, bot: Bot, state: T_State, event: Event):
     module = str(matcher.module_name).split('.')
     if len(module) < 2 or module[-2] != admin_path: return  # 位置与文件路径有关
     which_module = module[-1]
     # logger.info(f"{which_module}插件开始hook处理")
-    if isinstance(event, GroupMessageEvent):
+    if isinstance(event, (
+            GroupMessageEvent,
+            HonorNotifyEvent,
+            GroupUploadNoticeEvent,
+            GroupDecreaseNoticeEvent,
+            GroupIncreaseNoticeEvent,
+            GroupAdminNoticeEvent,
+            LuckyKingNotifyEvent)
+                  ):
         gid = event.group_id
         try:
             if which_module in admin_funcs:
                 status = await check_func_status(which_module, str(gid))
-                if not status and which_module not in ['auto_ban', 'img_check']:  # 违禁词检测和图片检测日志太多了，不用logger记录或者发消息记录
+                if not status and which_module not in ['auto_ban', 'img_check', 'particular_e_notice', 'word_analyze']:  # 违禁词检测和图片检测日志太多了，不用logger记录或者发消息记录
                     if cb_notice:
                         await bot.send_group_msg(group_id=gid,
                                                  message=f"功能处于关闭状态，发送【开关{admin_funcs[which_module][0]}】开启")
                     raise IgnoredException('未开启此功能...')
-                elif not status and which_module in ['auto_ban', 'img_check']:
+                elif not status and which_module in ['auto_ban', 'img_check', 'particular_e_notice', 'word_analyze']:
                     raise IgnoredException('未开启此功能...')
         except ActionFailed:
             pass
@@ -72,3 +84,27 @@ async def _(matcher: Matcher, bot: Bot, state: T_State, event: Event):
             pass
         except FileNotFoundError:
             pass
+
+
+async def check_func_status(func_name: str, gid: str) -> bool:
+    """
+    检查某个群的某个功能是否开启
+    :param func_name: 功能名
+    :param gid: 群号
+    :return: bool
+    """
+    funcs_status = json_load(switcher_path)
+    if funcs_status is None:
+        raise FileNotFoundError(switcher_path)
+    try:
+        return bool(funcs_status[gid][func_name])
+    except KeyError:  # 新加入的群
+        logger.info(
+            f"本群({gid})尚未初始化！将自动初始化：关闭所有开关且设置过滤级别为简单。\n\n请重新发送指令继续之前的操作")
+        if cb_notice:
+            # await nonebot.get_bot().send_group_msg(group_id = gid, message = '本群尚未初始化，将自动初始化：开启所有开关且设置过滤级别为简单。\n\n'
+            #                                                              '请重新发送指令继续之前的操作')
+            logger.info('错误发生在 utils.py line 398')
+        bot = nonebot.get_bot()
+        await switcher_integrity_check(bot)
+        return False  # 直接返回 false
diff --git a/nonebot_plugin_admin/group_msg.py b/nonebot_plugin_admin/group_msg.py
index 083c09c0..a780ea89 100644
--- a/nonebot_plugin_admin/group_msg.py
+++ b/nonebot_plugin_admin/group_msg.py
@@ -10,7 +10,7 @@
 send_time_moring = "8 0"                          # 选填 早上发送时间默认为7:00
 send_time_night = "23 0"                          # 选填 晚上发送时间默认为22:00              
 """
-
+# FIXME 此功能为用户PR，目前先用配置形式，后续修改为动态配置
 import asyncio
 import json
 import random
@@ -108,8 +108,8 @@ async def send_morning():
                     await get_bot().send_group_msg(group_id=gid, message=hitokoto())
             logger.info('群聊推送消息')
             sendSuccess = True
-        except ValueError as e:
-            logger.error("ValueError:{}", e)
+        except ValueError as E:
+            logger.error("ValueError:{}", E)
             logger.error('群聊推送消息插件获取bot失败，1s后重试')
             await asyncio.sleep(1)  # 重试前时延，防止阻塞
 
@@ -132,8 +132,8 @@ async def send_night():
                     await get_bot().send_group_msg(group_id=gid, message=hitokoto())
             logger.info('群聊推送消息')
             sendSuccess = True
-        except ValueError as e:
-            logger.error("ValueError:{}", e)
+        except ValueError as E:
+            logger.error("ValueError:{}", E)
             logger.error('群聊推送消息插件获取bot失败，1s后重试')
             await asyncio.sleep(1)  # 重试前时延，防止阻塞
 
diff --git a/nonebot_plugin_admin/group_recall.py b/nonebot_plugin_admin/group_recall.py
new file mode 100644
index 00000000..c9f007d0
--- /dev/null
+++ b/nonebot_plugin_admin/group_recall.py
@@ -0,0 +1,45 @@
+# python3
+# -*- coding: utf-8 -*-
+# @Time    : 2022/12/19 3:57
+# @Author  : yzyyz
+# @Email   :  youzyyz1384@qq.com
+# @File    : group_recall.py
+# @Software: PyCharm
+import json
+
+from nonebot import on_notice
+from nonebot.adapters.onebot.v11 import NoticeEvent, Bot
+
+from .config import global_config
+
+su = global_config.superusers
+
+
+async def _group_recall(bot: Bot, event: NoticeEvent) -> bool:
+    # 有需要自行取消注释
+    if event.notice_type == 'group_recall':
+        return True
+    return False
+
+
+group_recall = on_notice(_group_recall, priority=5)
+
+
+@group_recall.handle()
+async def _(bot: Bot, event: NoticeEvent):
+    event_obj = json.loads(event.json())
+    user_id = event_obj['user_id']  # 消息发送者
+    operator_id = event_obj['operator_id']  # 撤回消息的人
+    group_id = event_obj['group_id']  # 群号
+    message_id = event_obj['message_id']  # 消息 id
+
+    if int(user_id) != int(operator_id): return  # 撤回人不是发消息人，是管理员撤回成员消息，不处理
+    if int(operator_id) in su or str(operator_id) in su: return  # 发起撤回的人是超管，不处理
+    # 管理员撤回自己的也不处理
+    operator_info = await bot.get_group_member_info(group_id=group_id, user_id=operator_id, no_cache=True)
+    if operator_info['role'] != 'member': return
+    # 防撤回
+    recalled_message = await bot.get_msg(message_id=message_id)
+    recall_notice = f"检测到{operator_info['card'] if operator_info['card'] else operator_info['nickname']}({operator_info['user_id']})撤回了一条消息：\n\n"
+    await bot.send_group_msg(group_id=group_id, message=recall_notice + recalled_message['message'])
+
diff --git a/nonebot_plugin_admin/group_request_verify.py b/nonebot_plugin_admin/group_request_verify.py
index 4dd62bcd..8ba328f2 100644
--- a/nonebot_plugin_admin/group_request_verify.py
+++ b/nonebot_plugin_admin/group_request_verify.py
@@ -22,7 +22,7 @@ async def verify(word: str, group_id: str) -> Optional[bool]:
     :return: bool
     """
     with open(config_admin, mode='r') as f:
-        answers = json.loads(f.read())
+        answers = json.load(f)
         if group_id in answers:
             answer = answers[group_id]
             suggestions = fuzzyfinder(word, answer)
diff --git a/nonebot_plugin_admin/img_check.py b/nonebot_plugin_admin/img_check.py
index 10ff3b0e..1d23cffe 100644
--- a/nonebot_plugin_admin/img_check.py
+++ b/nonebot_plugin_admin/img_check.py
@@ -6,12 +6,13 @@
 # @File    : img_check.py
 # @Software: PyCharm
 from nonebot import logger, on_message
-from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, MessageEvent, MessageSegment
+from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, MessageEvent
+
 from nonebot.adapters.onebot.v11.exception import ActionFailed
 from nonebot.internal.matcher import Matcher
 
-from .utils import banSb, image_moderation_async, check_func_status, get_user_violation, sd, fi
 from .path import *
+from .utils import banSb, image_moderation_async, get_user_violation, sd, fi
 
 find_pic = on_message(priority=2, block=False)
 
diff --git a/nonebot_plugin_admin/notice.py b/nonebot_plugin_admin/notice.py
index af2de91f..a0430097 100644
--- a/nonebot_plugin_admin/notice.py
+++ b/nonebot_plugin_admin/notice.py
@@ -14,7 +14,8 @@
 from nonebot.typing import T_State
 
 from . import approve
-from .utils import At, check_func_status, fi
+from .utils import At, fi
+from .func_hook import check_func_status
 
 # 查看当前群分管
 gad = on_command('分管', aliases={'/gad', '/分群管理'}, priority=1, block=True,
@@ -49,7 +50,6 @@ async def _(bot: Bot, event: MessageEvent):
 
 @g_admin.handle()
 async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent, state: T_State):
-    msg = str(event.get_message())
     sb = At(event.json())
     gid = str(event.group_id)
     if sb and 'all' not in sb:
@@ -86,7 +86,6 @@ async def _(bot: Bot, event: MessageEvent):
 
 @g_admin_.handle()
 async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent, state: T_State):
-    msg = str(event.get_message())
     sb = At(event.json())
     gid = str(event.group_id)
     status = await check_func_status('requests', str(gid))
diff --git a/nonebot_plugin_admin/particular_e_notice.py b/nonebot_plugin_admin/particular_e_notice.py
new file mode 100644
index 00000000..167eb888
--- /dev/null
+++ b/nonebot_plugin_admin/particular_e_notice.py
@@ -0,0 +1,162 @@
+# python3
+# -*- coding: utf-8 -*-
+# @Time    : 2022/12/19 0:23
+# @Author  : yzyyz
+# @Email   :  youzyyz1384@qq.com
+# @File    : particular_e_notice.py
+# @Software: PyCharm
+import asyncio
+from datetime import datetime
+
+from nonebot.adapters.onebot.v11 import (
+    Bot, Event, PokeNotifyEvent,
+    HonorNotifyEvent,
+    GroupUploadNoticeEvent,
+    GroupDecreaseNoticeEvent,
+    GroupIncreaseNoticeEvent,
+    GroupAdminNoticeEvent,
+    LuckyKingNotifyEvent,
+    MessageSegment
+)
+from nonebot.matcher import Matcher
+from nonebot.plugin import on_notice
+from nonebot.typing import T_State
+
+from .utils import fi
+
+
+# 获取戳一戳状态
+async def _is_poke(bot: Bot, event: Event, state: T_State) -> bool:
+    return isinstance(event, PokeNotifyEvent) and event.is_tome()
+
+
+# 获取群荣誉变更
+async def _is_honor(bot: Bot, event: Event, state: T_State) -> bool:
+    return isinstance(event, HonorNotifyEvent)
+
+
+# 获取文件上传
+async def _is_checker(bot: Bot, event: Event, state: T_State) -> bool:
+    return isinstance(event, GroupUploadNoticeEvent)
+
+
+# 群成员减少
+async def _is_user_decrease(bot: Bot, event: Event, state: T_State) -> bool:
+    return isinstance(event, GroupDecreaseNoticeEvent)
+
+
+# 群成员增加
+async def _is_user_increase(bot: Bot, event: Event, state: T_State) -> bool:
+    return isinstance(event, GroupIncreaseNoticeEvent)
+
+
+# 管理员变动
+async def _is_admin_change(bot: Bot, event: Event, state: T_State) -> bool:
+    return isinstance(event, GroupAdminNoticeEvent)
+
+
+# 红包运气王
+async def _is_red_packet(bot: Bot, event: Event, state: T_State) -> bool:
+    return isinstance(event, LuckyKingNotifyEvent)
+
+
+poke = on_notice(_is_poke, priority=50, block=True)
+honor = on_notice(_is_honor, priority=50, block=True)
+upload_files = on_notice(_is_checker, priority=50, block=True)
+user_decrease = on_notice(_is_user_decrease, priority=50, block=True)
+user_increase = on_notice(_is_user_increase, priority=50, block=True)
+admin_change = on_notice(_is_admin_change, priority=50, block=True)
+red_packet = on_notice(_is_red_packet, priority=50, block=True)
+
+
+@poke.handle()
+async def _(bot: Bot, event: Event, state: T_State):
+    # TODO 在本地做记录 不太想写文本，因为dev分支已经用了数据库，后再在写
+    ...
+
+
+@honor.handle()
+async def _(bot: Bot, event: HonorNotifyEvent, state: T_State, matcher: Matcher):
+    honor_type = event.honor_type
+    uid = event.user_id
+    reply = ""
+    honor_map = {"performer": ["🔥", "群聊之火"], "emotion": ["🤣", "快乐源泉"]}
+    # 龙王
+    u_info = await bot.get_group_member_info(user_id=event.user_id)
+    u_name = u_info["card"] if u_info["card"] else u_info["nickname"]
+    if honor_type == "talkative":
+        if uid == bot.self_id:
+            reply = "💦 新龙王诞生，原来是我自己~"
+        else:
+            reply = f"💦 恭喜 {u_name} 荣获龙王标识~"
+    for key, value in honor_map.items():
+        if honor_type == key:
+            reply = f"{value[0]} 恭喜{u_name}荣获【{value[1]}】标识~"
+    await fi(matcher, reply)
+
+
+@upload_files.handle()
+async def _(bot: Bot, event: GroupUploadNoticeEvent, state: T_State, matcher: Matcher):
+    # TODO 在本地做记录
+    ...
+
+
+@user_decrease.handle()
+async def _(bot: Bot, event: GroupDecreaseNoticeEvent, state: T_State, matcher: Matcher):
+    op = await bot.get_group_member_info(group_id=event.group_id, user_id=event.operator_id)
+    casualty_name = (await bot.get_stranger_info(user_id=event.user_id)).get("nickname")
+    op_name = op['card'] if op.get('card') else op['nickname']
+    e_time = datetime.fromtimestamp(event.time).strftime("%Y-%m-%d %H:%M:%S")
+    avatar = get_avatar(event.user_id)
+    farewell_words = "感谢/o给/n送上的飞机，谢谢/o"
+    farewell_self_words = "/n离群出走/n"
+    # TODO 为以后自定义欢送词做准备
+    if event.operator_id != event.user_id:
+        reply = f"🛫 成员变动\n {farewell_words.replace('/o', f' {op_name} ').replace('/n', f' {casualty_name} ')}"
+        reply += MessageSegment.image(avatar) + f" \n {e_time}\n"
+    else:
+        reply = f"🛫 成员变动\n {farewell_self_words.replace('/n', f' {casualty_name} ')}"
+    await fi(matcher, reply)
+
+
+@user_increase.handle()
+async def _(bot: Bot, event: GroupIncreaseNoticeEvent, state: T_State, matcher: Matcher):
+    await asyncio.sleep(1)
+    avatar = get_avatar(event.user_id)
+    new_be = (await bot.get_group_member_info(group_id=event.group_id, user_id=event.user_id))['nickname']
+    wel_words = "欢迎/n加入"
+    # TODO 为以后自定义欢迎词做准备
+    reply = "✨ 成员变动\n"+MessageSegment.image(avatar) + MessageSegment.at(event.user_id) + f"\n {wel_words.replace('/n', f' {new_be} ')}\n "
+    await fi(matcher, reply)
+
+
+@admin_change.handle()
+async def _(bot: Bot, event: GroupAdminNoticeEvent, state: T_State, matcher: Matcher):
+    reply = ""
+    sub_type = event.sub_type
+    uid = event.user_id
+    user = await bot.get_group_member_info(group_id=event.group_id, user_id=uid)
+    u_name = user['card'] if user.get('card') else user['nickname']
+    cong_words = "恭喜/n成为管理"
+    re_words = "Ops! /n不再是本群管理"
+    if uid == bot.self_id:
+        if sub_type == "set":
+            reply = f"🚔 管理员变动\n{cong_words.replace('/n', '我')}"
+        if sub_type == "unset":
+            reply = f"🚔 管理员变动\n{re_words.replace('/n', '我')}"
+    else:
+        if sub_type == "set":
+            reply = f"🚔 管理员变动\n{cong_words.replace('/n', f' {u_name} ')}"
+        if sub_type == "unset":
+            reply = f"🚔  管理员变动\n{re_words.replace('/n', f' {u_name} ')}"
+    await fi(matcher, reply)
+
+
+@red_packet.handle()
+async def _(bot: Bot, event: LuckyKingNotifyEvent, state: T_State, matcher: Matcher):
+    # TODO 也许做点本记录（运气王）
+    ...
+
+
+def get_avatar(uid):
+    return f"https://q4.qlogo.cn/headimg_dl?dst_uin={uid}&spec=640"
diff --git a/nonebot_plugin_admin/path.py b/nonebot_plugin_admin/path.py
index 1097d0fc..346cf0d5 100644
--- a/nonebot_plugin_admin/path.py
+++ b/nonebot_plugin_admin/path.py
@@ -26,19 +26,25 @@
 user_violation_info_path = config_path / '群内用户违规信息'
 group_message_data_path = config_path / '群消息数据'
 error_path = config_path / 'admin插件错误数据'
+broadcast_avoid_path = config_path / '广播排除群聊.json'
 admin_models_path = Path() / 'admin_db_models'
 admin_models_init_path = admin_models_path / '__init__.py'
 
 
+
 admin_funcs = {
     'admin': ['管理', '踢', '禁', '改', '基础群管'],
     'requests': ['审批', '加群审批', '加群', '自动审批'],
     'wordcloud': ['群词云', '词云', 'wordcloud'],
     'auto_ban': ['违禁词', '违禁词检测'],
     'img_check': ['图片检测', '图片鉴黄', '涩图检测', '色图检测'],
-    'word_analyze': ['消息记录', '群消息记录', '发言记录']
+    'word_analyze': ['消息记录', '群消息记录', '发言记录'],
+    'group_msg': ['早安晚安', '早安', '晚安'],
+    'broadcast': ['广播消息', '群广播', '广播'],
+    'particular_e_notice': ['事件通知', '变动通知', '事件提醒'],
+    'group_recall': ['防撤回', '防止撤回']
 }
-
+# TODO 后续在这里对功能加 {‘default': True} 以便于初始化时自动设置开关状态
 funcs_name_cn = ['基础群管', '加群审批', '群词云', '违禁词检测', '图片检测']
 
 # 交给Copilot
@@ -52,6 +58,9 @@
     5: [24 * 60 * 60, 7 * 24 * 60 * 60],
     6: [7 * 24 * 60 * 60, 14 * 24 * 60 * 60],
     7: [14 * 24 * 60 * 60, 2591999]
+
 }
 
+
 localhost = "http://" + str(get_driver().config.host) + ":" + str(get_driver().config.port)
+
diff --git a/nonebot_plugin_admin/requests.py b/nonebot_plugin_admin/requests.py
index bb5a43b2..2c8bb927 100644
--- a/nonebot_plugin_admin/requests.py
+++ b/nonebot_plugin_admin/requests.py
@@ -19,7 +19,8 @@
 from .config import global_config
 from .group_request_verify import verify
 from .path import *
-from .utils import load
+from .utils import json_load
+
 
 su = global_config.superusers
 
@@ -29,7 +30,7 @@
 
 @super_sp.handle()
 async def _(bot: Bot, event: MessageEvent):
-    answers = (await load(config_admin))
+    answers = json_load(config_admin)
     rely = ''
     for i in answers:
         rely += i + ' : ' + str(answers[i]) + '\n'
@@ -93,7 +94,7 @@ async def _(bot: Bot, event: GroupMessageEvent):
     """
     /sp 查看本群词条
     """
-    a_config = (await load(config_admin))
+    a_config = json_load(config_admin)
     gid = str(event.group_id)
     if gid in a_config:
         this_config = a_config[gid]
@@ -114,8 +115,8 @@ async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
     sp_write = await approve.write(str(event.group_id), msg)
     gid = str(event.group_id)
     if sp_write:
-        await config.finish(f"群{event.group_id}添加词条：{msg}")
-    await config.finish(f"{msg} 已存在于群{event.group_id}的词条中")
+        await config.finish(f"群{gid}添加词条：{msg}")
+    await config.finish(f"{msg} 已存在于群{gid}的词条中")
 
 
 config_ = on_command('词条-', aliases={'/sp-', '/审批-'}, priority=1, block=True,
@@ -128,10 +129,10 @@ async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
     /sp- 删除本群某词条
     """
     msg = str(state['_prefix']['command_arg'])
-    sp_delete = await approve.delete(str(event.group_id), msg)
     gid = str(event.group_id)
+    sp_delete = await approve.delete(gid, msg)
     if sp_delete:
-        await config_.finish(f"群{event.group_id}删除入群审批词条：{msg}")
+        await config_.finish(f"群{gid}删除入群审批词条：{msg}")
     elif not sp_delete:
         await config_.finish("当前群不存在此词条")
     elif sp_delete is None:
@@ -162,7 +163,8 @@ async def gr_(bot: Bot, event: GroupRequestEvent):
                 approve=True,
                 reason=' ',
             )
-            admins = (await load(config_group_admin))
+            admins = json_load(config_group_admin)
+
             if admins['su'] == 'True':
                 for q in su:
                     await bot.send_msg(user_id=int(q), message=f"同意{uid}加入群 {gid},验证消息为 “{word}”")
@@ -179,7 +181,7 @@ async def gr_(bot: Bot, event: GroupRequestEvent):
                 approve=False,
                 reason='答案未通过群管验证，可修改答案后再次申请',
             )
-            admins = (await load(config_group_admin))
+            admins = json_load(config_group_admin)
             if admins['su'] == 'True':
                 for q in su:
                     await bot.send_msg(user_id=int(q), message=f"拒绝{uid}加入群 {gid},验证消息为 “{word}”")
diff --git a/nonebot_plugin_admin/switcher.py b/nonebot_plugin_admin/switcher.py
index 4deabad0..b254d91a 100644
--- a/nonebot_plugin_admin/switcher.py
+++ b/nonebot_plugin_admin/switcher.py
@@ -6,35 +6,31 @@
 # @File    : switcher.py
 # @Software: PyCharm
 from nonebot import on_command
+from nonebot.adapters import Message
 from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, MessageSegment, ActionFailed
 from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER
 from nonebot.exception import FinishedException
 from nonebot.internal.matcher import Matcher
+from nonebot.params import CommandArg
 from nonebot.permission import SUPERUSER
 from nonebot.typing import T_State
 from pyppeteer import launch
 
 from .path import *
-from .utils import load, upload, fi, log_fi
+from .utils import json_load, json_upload, fi, log_fi
 
 switcher = on_command('开关', priority=1, block=True, permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER)
 
 
 @switcher.handle()
-async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent, state: T_State):
+async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent, state: T_State, args: Message = CommandArg()):
     gid = str(event.group_id)
-    user_input_func_name = str(state['_prefix']['command_arg'])
-    for func in admin_funcs:
-        if user_input_func_name in admin_funcs[func]:
-            funcs_status = (await load(switcher_path))
-            if funcs_status[gid][func]:
-                funcs_status[gid][func] = False
-                await upload(switcher_path, funcs_status)
-                await fi(matcher, '已关闭' + user_input_func_name)
-            else:
-                funcs_status[gid][func] = True
-                await upload(switcher_path, funcs_status)
-                await fi(matcher, '已开启' + user_input_func_name)
+    user_input_func_name = str(args)
+    try:
+        await switcher_handle(gid, matcher, user_input_func_name)
+    except KeyError:
+        await switcher_integrity_check(bot)
+        await switcher_handle(gid, matcher, user_input_func_name)
 
 
 switcher_html = on_command('开关状态', priority=1, block=True, permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER)
@@ -43,7 +39,7 @@ async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent, state: T_State
 @switcher_html.handle()
 async def _(bot: Bot, matcher: Matcher, event: GroupMessageEvent):
     gid = str(event.group_id)
-    funcs_status = (await load(switcher_path))
+    funcs_status = json_load(switcher_path)
     try:
         from os.path import dirname
         from jinja2 import Environment, FileSystemLoader
@@ -95,5 +91,42 @@ async def save_image(url, img_path):
         width: 图片宽度
         height: 图片高度
     """
-    await page.screenshot({'path': img_path, 'clip': {'x': 0, 'y': 0, 'width': 320, 'height': 500}})
+    await page.screenshot({'path': img_path, 'clip': {'x': 0, 'y': 0, 'width': 320, 'height': 800}})
     await browser.close()
+
+
+async def switcher_integrity_check(bot: Bot):
+    g_list = (await bot.get_group_list())
+    switcher_dict = json_load(switcher_path)
+    for group in g_list:
+        gid = str(group['group_id'])
+        if not switcher_dict.get(gid):
+            switcher_dict[gid] = {}
+            for func in admin_funcs:
+                if func in ['img_check', 'auto_ban', 'group_msg', 'particular_e_notice', 'group_recall']:
+                    switcher_dict[gid][func] = False
+                else:
+                    switcher_dict[gid][func] = True
+        else:
+            this_group_switcher = switcher_dict[gid]
+            for func in admin_funcs:
+                if this_group_switcher.get(func) is None:
+                    if func in ['img_check', 'auto_ban', 'group_msg', 'particular_e_notice', 'group_recall']:
+                        this_group_switcher[func] = False
+                    else:
+                        this_group_switcher[func] = True
+    json_upload(switcher_path, switcher_dict)
+
+
+async def switcher_handle(gid, matcher, user_input_func_name):
+    for func in admin_funcs:
+        if user_input_func_name in admin_funcs[func]:
+            funcs_status = json_load(switcher_path)
+            if funcs_status[gid][func]:
+                funcs_status[gid][func] = False
+                json_upload(switcher_path, funcs_status)
+                await fi(matcher, '已关闭' + user_input_func_name)
+            else:
+                funcs_status[gid][func] = True
+                json_upload(switcher_path, funcs_status)
+                await fi(matcher, '已开启' + user_input_func_name)
diff --git a/nonebot_plugin_admin/utils.py b/nonebot_plugin_admin/utils.py
index 669749be..8afaccd2 100644
--- a/nonebot_plugin_admin/utils.py
+++ b/nonebot_plugin_admin/utils.py
@@ -12,7 +12,7 @@
 import os
 import random
 import re
-from typing import Union, Optional, Type
+from typing import Union, Optional
 
 import httpx
 import nonebot
@@ -35,6 +35,7 @@
 cb_notice = plugin_config.callback_notice
 
 
+
 def At(data: str) -> Union[list[str], list[int], list]:
     """
     检测at了谁，返回[qq, qq, qq,...]
@@ -110,37 +111,39 @@ async def init():
     :return:
     """
     for d in dirs:
-        if not os.path.exists(d):
+        if not d.exists():
             await mk('dir', d, mode=None)
-    if not os.path.exists(config_admin):
+    if not config_admin.exists():
         await mk('file', config_admin, 'w', content='{"1008611": ["This_is_an_example"]}')
-    if not os.path.exists(config_group_admin):
+    if not config_group_admin.exists():
         await mk('file', config_group_admin, 'w', content='{"su": "True"}')
-    if not os.path.exists(word_path):
+    if not word_path.exists():
         await mk('file', word_path, 'w', content='123456789\n')
-    if not os.path.exists(switcher_path):
+    if not switcher_path.exists():
         bot = nonebot.get_bot()
-        logger.info('创建开关配置文件,分群设置, 图片检测和违禁词检测默认关,其他默认开')
+        logger.info('创建开关配置文件,分群设置, 图片检测和违禁词检测,防撤回，广播，早晚安默认关,其他默认开')
         g_list = (await bot.get_group_list())
         switcher_dict = {}
         for group in g_list:
-            switcher_dict.update({str(group['group_id']): {'admin': True, 'requests': True,
-                                                           'wordcloud': True, 'auto_ban': False,
-                                                           'img_check': False, 'word_analyze': True}})
+            switchers = {}
+            for fn_name in admin_funcs:
+                switchers.update({fn_name: True})
+                if fn_name in ['img_check', 'auto_ban', 'group_msg', 'particular_e_notice', 'group_recall']:
+                    switchers.update({fn_name: False})
+            switcher_dict.update({str(group['group_id']): switchers})
         with open(switcher_path, 'w', encoding='utf-8') as swp:
             swp.write(f"{json.dumps(switcher_dict)}")
-            swp.close()
-    if not os.path.exists(limit_word_path):  # 要联网的都丢最后面去
-        if os.path.exists(config_path / '违禁词_简单.txt'):
+    if not limit_word_path.exists():  # 要联网的都丢最后面去
+        if (config_path / '违禁词_简单.txt').exists():
             with open(config_path / '违禁词_简单.txt', 'r', encoding='utf-8') as f:
                 content = f.read()
             with open(limit_word_path, 'w', encoding='utf-8') as f:
                 f.write(content)
-            os.remove(config_path / '违禁词_简单.txt')
+            (config_path / '违禁词_简单.txt').unlink()
         else:
             await mk('file', limit_word_path, 'w',
                      url='https://fastly.jsdelivr.net/gh/yzyyz1387/nwafu/f_words/f_word_easy', dec='简单违禁词词库')
-    if not os.path.exists(ttf_name):
+    if not ttf_name.exists():
         await mk('file', ttf_name, 'wb', url='https://fastly.jsdelivr.net/gh/yzyyz1387/blogimages/msyhblod.ttf',
                  dec='资源字体')
     logger.info('Admin 插件 初始化检测完成')
@@ -330,7 +333,7 @@ def image_moderation(img):
         req = models.ImageModerationRequest()
         params = (
             {'BizType': 'group_recall', 'FileUrl': img}
-            if type(img) == str
+            if type(img) is str
             else {'BizType': 'group_recall', 'FileContent': bytes_to_base64(img)}
         )
         req.from_json_string(json.dumps(params))
@@ -356,22 +359,20 @@ def bytes_to_base64(data):
     return base64.b64encode(data).decode('utf-8')
 
 
-async def load(path) -> Optional[dict]:
+def json_load(path) -> Optional[dict]:
     """
     加载json文件
     :return: Optional[dict]
     """
     try:
         with open(path, mode='r', encoding='utf-8') as f:
-            contents_ = f.read()
-            contents = json.loads(contents_)
-            f.close()
+            contents = json.load(f)
             return contents
     except FileNotFoundError:
         return None
 
 
-async def upload(path, dict_content) -> None:
+def json_upload(path, dict_content) -> None:
     """
     更新json文件
     :param path: 路径
@@ -379,35 +380,6 @@ async def upload(path, dict_content) -> None:
     """
     with open(path, mode='w', encoding='utf-8') as c:
         c.write(json.dumps(dict_content, ensure_ascii=False, indent=2))
-        c.close()
-
-
-async def check_func_status(func_name: str, gid: str) -> bool:
-    """
-    检查某个群的某个功能是否开启
-    :param func_name: 功能名
-    :param gid: 群号
-    :return: bool
-    """
-    funcs_status = (await load(switcher_path))
-    if funcs_status is None:
-        raise FileNotFoundError(switcher_path)
-    try:
-        if funcs_status[gid][func_name]:
-            return True
-        else:
-            return False
-    except KeyError:  # 新加入的群
-        logger.info(
-            f"本群({gid})尚未初始化！将自动初始化：关闭所有开关且设置过滤级别为简单。\n\n请重新发送指令继续之前的操作")
-        if cb_notice:
-            # await nonebot.get_bot().send_group_msg(group_id = gid, message = '本群尚未初始化，将自动初始化：开启所有开关且设置过滤级别为简单。\n\n'
-            #                                                              '请重新发送指令继续之前的操作')
-            logger.info('错误发生在 utils.py line 398')
-        funcs_status.update({str(gid): {'admin': True, 'requests': True, 'wordcloud': True,
-                                        'auto_ban': True, 'img_check': True, 'word_analyze': True}})
-        await upload(switcher_path, funcs_status)
-        return False  # 直接返回 false
 
 
 async def del_txt_line(path: Path, matcher: Matcher, event: GroupMessageEvent, args: Message, dec: str,
@@ -419,6 +391,7 @@ async def del_txt_line(path: Path, matcher: Matcher, event: GroupMessageEvent, a
     :param event: 事件
     :param args: 文本
     :param dec: 描述
+    :param group: 是否为群聊
     """
     gid = str(event.group_id)
     logger.info(args)
@@ -429,7 +402,6 @@ async def del_txt_line(path: Path, matcher: Matcher, event: GroupMessageEvent, a
         try:
             with open(this_path, mode='r+', encoding='utf-8') as c:
                 is_saved = c.read().split("\n")
-                c.close()
             with open(this_path, mode='w', encoding='utf-8') as c:
                 success_del = []
                 already_del = []
@@ -461,6 +433,7 @@ async def add_txt_line(path: Path, matcher: Matcher, event: GroupMessageEvent, a
     :param event: 事件
     :param args: 文本
     :param dec: 描述
+    :param group: 是否为群聊
     """
     gid = str(event.group_id)
     logger.info(args)
@@ -506,6 +479,7 @@ async def get_txt_line(path: Path, matcher: Matcher, event: GroupMessageEvent, a
     :param event: 事件
     :param args: 文本
     :param dec: 描述
+    :param group: 是否为群聊
     """
     gid = str(event.group_id)
     try:
@@ -565,12 +539,12 @@ async def get_user_violation(gid: int, uid: int, label: str, content: str, add_:
         await vio_level_init(path_user, uid, this_time, label, content)
         return 0
     try:
-        info = (await load(path_user))
+        info = json_load(path_user)
         level = info[uid]['level']
         if add_:
             info[uid]['level'] += 1
         info[uid]['info'][this_time] = [label, content]
-        await upload(path_user, info)
+        json_upload(path_user, info)
         if level >= 7:
             return 7
         else:
@@ -587,7 +561,6 @@ async def get_user_violation(gid: int, uid: int, label: str, content: str, add_:
 async def vio_level_init(path_user, uid, this_time, label, content) -> None:
     with open(path_user, mode='w', encoding='utf-8') as c:
         c.write(json.dumps({uid: {'level': 0, 'info': {this_time: [label, content]}}}, ensure_ascii=False))
-        c.close()
 
 
 async def error_log(gid: int, time: str, matcher: Matcher, err: str) -> None:
@@ -595,12 +568,12 @@ async def error_log(gid: int, time: str, matcher: Matcher, err: str) -> None:
     if not os.path.exists(error_path):
         await mk('dir', error_path, mode=None)
     if not os.path.exists(error_path / f"{str(gid)}.json"):
-        await upload(error_path / f"{str(gid)}.json", {str(gid): {time: [module, err]}})
+        json_upload(error_path / f"{str(gid)}.json", {str(gid): {time: [module, err]}})
     else:
         try:
-            info = (await load(error_path / f"{str(gid)}.json"))
+            info = json_load(error_path / f"{str(gid)}.json")
             info[str(gid)][time] = [module, err]
-            await upload(error_path / f"{str(gid)}.json", info)
+            json_upload(error_path / f"{str(gid)}.json", info)
         except Exception as e:
             logger.error(f"写入错误日志出错：{e}")
 
@@ -623,8 +596,6 @@ async def log_fi(cmd: Matcher, msg, log: str = None, err=False) -> None:
     (logger.error if err else logger.info)(log if log else msg)
     await fi(cmd, msg)
 
-
 def copyFile(origin, target):
-    with open(origin, "rb") as f:
-        with open(target, "wb") as f2:
+    with open(origin, "rb") as f, open(target, "wb") as f2:
             f2.write(f.read())
diff --git a/nonebot_plugin_admin/web/api/routers/group.py b/nonebot_plugin_admin/web/api/routers/group.py
index e24d3bbc..41f3c360 100644
--- a/nonebot_plugin_admin/web/api/routers/group.py
+++ b/nonebot_plugin_admin/web/api/routers/group.py
@@ -5,6 +5,7 @@
 # @Email   :  youzyyz1384@qq.com
 # @File    : group.py
 # @Software: PyCharm
+from typing import Optional
 from pathlib import Path
 from nonebot import logger
 from fastapi import APIRouter
diff --git a/nonebot_plugin_admin/web/api/routers/msg.py b/nonebot_plugin_admin/web/api/routers/msg.py
index 789992e9..dde208a1 100644
--- a/nonebot_plugin_admin/web/api/routers/msg.py
+++ b/nonebot_plugin_admin/web/api/routers/msg.py
@@ -5,6 +5,7 @@
 # @Email   :  youzyyz1384@qq.com
 # @File    : msg.py
 # @Software: PyCharm
+from typing import Optional
 from fastapi import APIRouter
 from fastapi import FastAPI, Request
 from fastapi.responses import RedirectResponse
diff --git a/nonebot_plugin_admin/web/api/routers/switcher.py b/nonebot_plugin_admin/web/api/routers/switcher.py
index 8e1bab1f..28b024ae 100644
--- a/nonebot_plugin_admin/web/api/routers/switcher.py
+++ b/nonebot_plugin_admin/web/api/routers/switcher.py
@@ -5,6 +5,7 @@
 # @Email   :  youzyyz1384@qq.com
 # @File    : switcher.py
 # @Software: PyCharm
+from typing import Optional
 
 from fastapi import APIRouter
 from fastapi import FastAPI, Request
diff --git a/nonebot_plugin_admin/web/api/utils/tools.py b/nonebot_plugin_admin/web/api/utils/tools.py
index de32ab73..c6a931fd 100644
--- a/nonebot_plugin_admin/web/api/utils/tools.py
+++ b/nonebot_plugin_admin/web/api/utils/tools.py
@@ -13,6 +13,5 @@ async def creatClass(prefix, group_id, extend_class):
 
 
 def copyFile(origin, target):
-    with open(origin, "rb") as f:
-        with open(target, "wb") as f2:
+    with open(origin, "rb") as f, open(target, "wb") as f2:
             f2.write(f.read())
diff --git a/nonebot_plugin_admin/word_analyze.py b/nonebot_plugin_admin/word_analyze.py
index 1d3661a5..6e594630 100644
--- a/nonebot_plugin_admin/word_analyze.py
+++ b/nonebot_plugin_admin/word_analyze.py
@@ -18,7 +18,7 @@
 from nonebot.permission import SUPERUSER
 
 from .path import *
-from .utils import replace_tmr, del_txt_line, add_txt_line, get_txt_line, upload, load, At, MsgText
+from .utils import replace_tmr, del_txt_line, add_txt_line, get_txt_line, json_upload, json_load, At, MsgText
 
 word_start = on_command('记录本群', block=True, priority=1, permission=GROUP_ADMIN | GROUP_OWNER | SUPERUSER)
 
@@ -43,7 +43,7 @@ async def _(bot: Bot, event: GroupMessageEvent):
 @word_stop.handle()
 async def _(bot: Bot, event: GroupMessageEvent):
     gid = str(event.group_id)
-    txt = open(word_path, 'r', encoding='utf-8').read()
+    txt = word_path.read_text(encoding='utf-8')
     if gid in txt:
         with open(word_path, 'w', encoding='utf-8') as c:
             c.write(txt.replace(gid, ''))
@@ -85,32 +85,28 @@ async def _(bot: Bot, event: GroupMessageEvent, matcher: Matcher):
     if not os.path.exists(message_path_group):
         os.mkdir(message_path_group)
     if not os.path.exists(message_path_group / f"{today}.json"):  # 日消息条数记录 {uid：消息数}
-        await upload(message_path_group / f"{today}.json", {uid: 1})
+        json_upload(message_path_group / f"{today}.json", {uid: 1})
     else:
-        dic_ = await load(message_path_group / f"{today}.json")
+        dic_ = json_load(message_path_group / f"{today}.json")
         if uid not in dic_:
             dic_[uid] = 1
         else:
             dic_[uid] += 1
-        await upload(message_path_group / f"{today}.json", dic_)
+        json_upload(message_path_group / f"{today}.json", dic_)
     if not os.path.exists(message_path_group / 'history.json'):  # 历史发言条数记录 {uid：消息数}
-        await upload(message_path_group / 'history.json', {uid: 1})
+        json_upload(message_path_group / 'history.json', {uid: 1})
     else:
-        dic_ = await load(message_path_group / 'history.json')
+        dic_ = json_load(message_path_group / 'history.json')
         if uid not in dic_:
             dic_[uid] = 1
         else:
             dic_[uid] += 1
-        await upload(message_path_group / 'history.json', dic_)
-    txt = open(word_path, 'r', encoding='utf-8').read().split('\n')
+        json_upload(message_path_group / 'history.json', dic_)
+    txt = word_path.read_text(encoding='utf-8').split('\n')
     if gid in txt:
         msg = await replace_tmr(msg)
         with open(path_temp, 'a+', encoding='utf-8') as c:
             c.write(msg + '\n')
-    # except Exception as e:
-    #     logger.error('word_analyze.py 消息记录发生错误：' + str(e))
-    #     await error_log(event.group_id, this_time, matcher, str(e))
-
 
 stop_words_add = on_command('添加停用词', aliases={'增加停用词', '新增停用词'}, block=True, priority=1,
                             permission=GROUP_ADMIN | GROUP_OWNER | SUPERUSER)
@@ -186,10 +182,9 @@ async def _(bot: Bot, event: GroupMessageEvent):
 async def _(bot: Bot, event: GroupMessageEvent, matcher: Matcher, args: Message = CommandArg()):
     gid = event.group_id
     today = datetime.date.today().strftime('%Y-%m-%d')
-    dic_ = await load(group_message_data_path / f"{gid}" / f"{today}.json")
+    dic_ = json_load(group_message_data_path / f"{gid}" / f"{today}.json")
     top = sorted(dic_.items(), key=lambda x: x[1], reverse=True)
     top = (await member_in_group(bot, gid, top))
-
     if len(top) == 0:
         await who_speak_most_today.finish('没有任何人说话')
     nickname = (await bot.get_group_member_info(group_id=gid, user_id=top[0][0]))['card']
@@ -205,7 +200,7 @@ async def _(bot: Bot, event: GroupMessageEvent, matcher: Matcher, args: Message
 async def _(bot: Bot, event: GroupMessageEvent, matcher: Matcher, args: Message = CommandArg()):
     gid = event.group_id
     today = datetime.date.today().strftime('%Y-%m-%d')
-    dic_ = await load(group_message_data_path / f"{gid}" / f"{today}.json")
+    dic_ = json_load(group_message_data_path / f"{gid}" / f"{today}.json")
     top = sorted(dic_.items(), key=lambda x: x[1], reverse=True)
     top = (await member_in_group(bot, gid, top))
     if len(top) == 0:
@@ -229,9 +224,10 @@ async def _(bot: Bot, event: GroupMessageEvent, matcher: Matcher, args: Message
     today = datetime.date.today().strftime('%Y-%m-%d')
     yesterday = (datetime.date.today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d')
     if os.path.exists(group_message_data_path / f"{gid}" / f"{yesterday}.json"):
-        dic_ = await load(group_message_data_path / f"{gid}" / f"{yesterday}.json")
+        dic_ = json_load(group_message_data_path / f"{gid}" / f"{yesterday}.json")
         top = sorted(dic_.items(), key=lambda x: x[1], reverse=True)
         top = (await member_in_group(bot, gid, top))
+
         if len(top) == 0:
             await speak_top_yesterday.finish('没有任何人说话')
         top_list = []
@@ -251,7 +247,9 @@ async def _(bot: Bot, event: GroupMessageEvent, matcher: Matcher, args: Message
 @who_speak_most.handle()
 async def _(bot: Bot, event: GroupMessageEvent, matcher: Matcher, args: Message = CommandArg()):
     gid = event.group_id
-    dic_ = await load(group_message_data_path / f"{gid}" / 'history.json')
+    dic_ = json_load(group_message_data_path / f"{gid}" / 'history.json')
+    if not dic_:
+        await who_speak_most.finish('没有任何人说话')
     top = sorted(dic_.items(), key=lambda x: x[1], reverse=True)
     top = (await member_in_group(bot, gid, top))
     if len(top) == 0:
@@ -271,7 +269,7 @@ async def _(bot: Bot, event: GroupMessageEvent, matcher: Matcher, args: Message
 @get_speak_num.handle()
 async def _(bot: Bot, event: GroupMessageEvent, matcher: Matcher, args: Message = CommandArg()):
     gid = event.group_id
-    dic_ = await load(group_message_data_path / f"{gid}" / 'history.json')
+    dic_ = json_load(group_message_data_path / f"{gid}" / 'history.json')
     at_list = At(event.json())
     if at_list:
         for qq in at_list:
@@ -292,7 +290,7 @@ async def _(bot: Bot, event: GroupMessageEvent, matcher: Matcher, args: Message
 async def _(bot: Bot, event: GroupMessageEvent, matcher: Matcher, args: Message = CommandArg()):
     gid = event.group_id
     today = datetime.date.today().strftime('%Y-%m-%d')
-    dic_ = await load(group_message_data_path / f"{gid}" / f"{today}.json")
+    dic_ = json_load(group_message_data_path / f"{gid}" / f"{today}.json")
     at_list = At(event.json())
     if at_list:
         for qq in at_list:
@@ -307,7 +305,7 @@ async def _(bot: Bot, event: GroupMessageEvent, matcher: Matcher, args: Message
 
 
 async def member_in_group(bot: Bot, gid: int, top: list):
-    """成员不在本群则不仅从排行（不在本圈查询信息时会报错）"""
+    """成员不在本群则不仅从排行（不在本群查询信息时会报错）"""
     member_dict = (await bot.get_group_member_list(group_id=gid))
     member_list = []
     for i in member_dict:
diff --git a/nonebot_plugin_admin/wordcloud.py b/nonebot_plugin_admin/wordcloud.py
index 8cfd25a1..77a84353 100644
--- a/nonebot_plugin_admin/wordcloud.py
+++ b/nonebot_plugin_admin/wordcloud.py
@@ -5,19 +5,16 @@
 # @Email   :  youzyyz1384@qq.com
 # @File    : wordcloud.py
 # @Software: PyCharm
-import httpx
-from nonebot import on_command, logger, on_message
-from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, MessageSegment
-from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER
-from nonebot.permission import SUPERUSER
-from .utils import replace_tmr, participle_simple_handle, check_func_status
-from pathlib import Path
 import os
-from .path import *
-import numpy as np
 import random
-import os
+
+import httpx
 from imageio import imread
+from nonebot import on_command, logger
+from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, MessageSegment
+
+from .path import *
+from .utils import participle_simple_handle
 
 cloud = on_command('群词云', priority=1)
 
@@ -63,11 +60,11 @@ async def _(bot: Bot, event: GroupMessageEvent):
             wordcloud_bg = random.choice(os.listdir(wordcloud_bg_path))
         background_image = imread(wordcloud_bg_path / wordcloud_bg)
         if gid + '.txt' in dir_list:
-            text = open(path_temp, encoding='utf-8').read()
+            text = path_temp.read_text(encoding='utf-8')
             txt = jieba.lcut(text)
             this_stop_ = stop_words_path / f"{gid}.txt"
-            if os.path.exists(this_stop_):
-                stop_ = set(open(this_stop_, encoding='utf-8').read().split('\n') + (await participle_simple_handle()))
+            if this_stop_.exists():
+                stop_ = set(this_stop_.read_text(encoding='utf-8').split('\n') + (await participle_simple_handle()))
             else:
                 stop_ = set(await participle_simple_handle())
             string = ' '.join(txt)
diff --git a/setup.py b/setup.py
new file mode 100644
index 00000000..f6fb2504
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,31 @@
+import io
+import sys
+
+import setuptools
+
+with open("README.md", "r", encoding="utf-8", errors="ignore") as fh:
+    long_description = fh.read()
+
+setuptools.setup(
+    name="nonebot-plugin-admin",
+    version="0.4.1",
+    author="yzyyz1387",
+    author_email="youzyyz1384@qq.com",
+    keywords=("pip", "nonebot2", "nonebot", "admin", "nonebot_plugin"),
+    description="""nonebot2 plugin for group administration""",
+    long_description=long_description,
+    long_description_content_type="text/markdown",
+    url="https://github.com/yzyyz1387/nonebot_plugin_admin",
+    packages=setuptools.find_packages(),
+    classifiers=[
+        "Programming Language :: Python :: 3",
+        "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
+        "Operating System :: OS Independent",
+    ],
+    include_package_data=True,
+    platforms="any",
+    install_requires=["fuzzyfinder", 'nonebot-adapter-onebot>=2.0.0-beta.1', 'nonebot2>=2.0.0-beta'
+                                                                                                '.4', "jieba",
+                      "httpx", "tencentcloud-sdk-python>=3.0.580", "jinja2", "pyppeteer", "imageio", "numpy",
+                      "nonebot_plugin_apscheduler"]
+)


Test patch:
diff --git a/.github/workflows/publish-to-test-pypi.yml b/.github/workflows/publish-to-test-pypi.yml
new file mode 100644
index 00000000..6bcb439f
--- /dev/null
+++ b/.github/workflows/publish-to-test-pypi.yml
@@ -0,0 +1,31 @@
+name: Publish Python 🐍 distributions 📦 to PyPI
+
+on:
+  release:
+    types: [published]
+
+permissions:
+  contents: read
+
+jobs:
+  deploy:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+    - name: Set up Python
+      uses: actions/setup-python@v3
+      with:
+        python-version: '3.9'
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install build
+    - name: Build package
+      run: python -m build
+    - name: Publish package
+      uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
+      with:
+        user: __token__
+        password: ${{ secrets.PYPI_API_TOKEN }}