├── .gitignore ├── LICENSE ├── nonebot_plugin_no_repeat.py ├── pyproject.toml └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | dist 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 bridgeL 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | Footer -------------------------------------------------------------------------------- /nonebot_plugin_no_repeat.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | from typing import Dict, List, Literal 3 | from loguru import logger 4 | from pydantic import BaseModel 5 | from nonebot import get_driver 6 | from nonebot.adapters import Bot 7 | from nonebot.exception import MockApiException 8 | 9 | 10 | class Config(BaseModel): 11 | no_repeat_mode: Literal["use", "not_use"] = "not_use" 12 | no_repeat_groups: List[int] = [] 13 | no_repeat_threshold: int = 3 14 | no_repeat_gap: int = 20 15 | 16 | 17 | config = Config.parse_obj(get_driver().config.dict()) 18 | 19 | 20 | class Cache(BaseModel): 21 | last: str = "" 22 | last_time: int = 0 23 | cnt: int = 0 24 | 25 | def check_gap(self): 26 | '''是否小于规定间隔''' 27 | t1 = int(time()) 28 | t2 = self.last_time 29 | self.last_time = t1 30 | return t1 - t2 <= config.no_repeat_gap 31 | 32 | def check_same_msg(self, msg: str): 33 | '''是否与上次相同''' 34 | last = self.last 35 | self.last = msg 36 | return msg == last 37 | 38 | def add(self, msg: str): 39 | f1 = self.check_gap() 40 | f2 = self.check_same_msg(msg) 41 | # 小于规定间隔 且 与上次相同 42 | if f1 and f2: 43 | self.cnt += 1 44 | else: 45 | self.cnt = 1 46 | 47 | def check_cnt(self): 48 | # 达到复读标准 49 | return self.cnt >= config.no_repeat_threshold 50 | 51 | 52 | groups: Dict[int, Cache] = {} 53 | 54 | 55 | def msg_is_send_to_group(api: str, data: dict): 56 | if api == "send_group_msg": 57 | return True 58 | if api == "send_msg": 59 | return data["message_type"] == "group" 60 | return False 61 | 62 | 63 | def get_group_cache(group_id: int): 64 | if group_id not in groups: 65 | groups[group_id] = Cache() 66 | return groups[group_id] 67 | 68 | 69 | def group_id_is_using_no_repeat(group_id: int): 70 | if config.no_repeat_mode == "not_use": 71 | return group_id not in config.no_repeat_groups 72 | return group_id in config.no_repeat_groups 73 | 74 | 75 | @Bot.on_calling_api 76 | async def handle_api_call(bot: Bot, api: str, data: dict): 77 | if msg_is_send_to_group(api, data): 78 | group_id = data["group_id"] 79 | if group_id_is_using_no_repeat(group_id): 80 | cache = get_group_cache(group_id) 81 | cache.add(str(data["message"])) 82 | if cache.check_cnt(): 83 | logger.warning(cache) 84 | logger.warning("检测到复读,疑似代码故障") 85 | raise MockApiException("已阻止api调用") 86 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "nonebot_plugin_no_repeat" 3 | version = "0.0.1" 4 | description = "不要复读" 5 | authors = ["Su "] 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/bridgeL/nonebot-plugin-no-repeat" 9 | packages = [ 10 | { include = "nonebot_plugin_no_repeat.py", from = "." }, 11 | ] 12 | 13 | 14 | [tool.poetry.dependencies] 15 | python = "^3.8" 16 | nonebot2 = "^2.0.0b5" 17 | 18 | [build-system] 19 | requires = ["poetry-core"] 20 | build-backend = "poetry.core.masonry.api" 21 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # nonebot_plugin_no_repeat 不要复读 4 | 5 | 防止代码炸了在群里复读刷屏,让你写插件的时候更安全 6 | 7 |
8 | 9 | ## 配置(可选) 10 | 11 | | 名称 | 值 | 意义 | 默认值/推荐值 | 12 | | ------------------- | -------------- | --------------------------------------------------- | ------------- | 13 | | no_repeat_mode | use, not_use | 白名单模式(use) or 黑名单模式(not_use) | not_use | 14 | | no_repeat_groups | [12345, 23456] | 群聊号 | [] | 15 | | no_repeat_threshold | 3 | 发送重复语句达到3条后视为复读(第三条会被阻止发送) | 3 | 16 | | no_repeat_gap | 20 | 与上一条语句的发送间隔超过20s则不视为复读 | 20 | 17 | 18 | 19 | 白名单模式:仅在指定群内开启该功能 20 | 21 | 黑名单模式:仅在指定群内关闭该功能(比如你的机器人专用调试群),其他群均开启该功能 22 | 23 | ## 实现原理 24 | 25 | https://v2.nonebot.dev/docs/next/advanced/runtime-hook#bot-api-%E8%B0%83%E7%94%A8%E9%92%A9%E5%AD%90 26 | 27 | 检测到异常复读情况后抛出`MockApiException` 28 | --------------------------------------------------------------------------------