From 56573d1d34d6ff6d1dc27eca6c28e257026acc09 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Sun, 1 May 2022 15:53:52 +0800 Subject: [PATCH] update black_word --- .gitignore | 1 - README.md | 5 + .../chat_history/chat_message_handle.py | 2 +- basic_plugins/help/data_source.py | 4 +- plugins/ai/data_source.py | 62 ++-- plugins/black_word/__init__.py | 249 ++++++++++++++ plugins/black_word/data_source.py | 118 +++++++ plugins/black_word/model.py | 149 ++++++++ plugins/black_word/utils.py | 324 ++++++++++++++++++ plugins/group_last_chat/__init__.py | 34 -- plugins/group_last_chat/data_source.py | 67 ---- utils/depends/__init__.py | 0 utils/image_utils.py | 8 +- 13 files changed, 886 insertions(+), 137 deletions(-) create mode 100644 plugins/black_word/__init__.py create mode 100644 plugins/black_word/data_source.py create mode 100644 plugins/black_word/model.py create mode 100644 plugins/black_word/utils.py delete mode 100755 plugins/group_last_chat/__init__.py delete mode 100755 plugins/group_last_chat/data_source.py create mode 100644 utils/depends/__init__.py diff --git a/.gitignore b/.gitignore index 53aa4128..bca28216 100644 --- a/.gitignore +++ b/.gitignore @@ -142,6 +142,5 @@ test.py server_ip.py member_activity_handle.py Yu-Gi-Oh/ -black_word/ csgo/ fantasy_card/ diff --git a/README.md b/README.md index 8dfc015e..45796201 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,11 @@ __Docker 最新版本由 [Sakuracio](https://github.com/Sakuracio) 提供__ ## 更新 +### 2022/5/1 + +* 删除了`group_last_chat`插件(该功能可由`chat_history`替代 +* 新增敏感词检测(全新反击系统,是时候重拳出击了 + ### 2022/4/26 * 修复了群白名单无法正确添加 diff --git a/basic_plugins/chat_history/chat_message_handle.py b/basic_plugins/chat_history/chat_message_handle.py index 64e30e4a..baef6a4c 100644 --- a/basic_plugins/chat_history/chat_message_handle.py +++ b/basic_plugins/chat_history/chat_message_handle.py @@ -42,7 +42,7 @@ __plugin_settings__ = { } -msg_handler = on_regex(r"(周|月)?消息统计(des|DES)?(n=[0-9]{1,2})?", priority=5, block=True) +msg_handler = on_regex(r"^(周|月)?消息统计(des|DES)?(n=[0-9]{1,2})?$", priority=5, block=True) @msg_handler.handle() diff --git a/basic_plugins/help/data_source.py b/basic_plugins/help/data_source.py index bf178a83..923de5a2 100755 --- a/basic_plugins/help/data_source.py +++ b/basic_plugins/help/data_source.py @@ -277,7 +277,7 @@ def _create_help_img( 0, plain_text=msg, font_size=24, - font="yuanshen.ttf", + font="HYWenHei-85W.ttf", ) B.paste(text, (w, h), True) h += 50 @@ -289,7 +289,7 @@ def _create_help_img( 0, plain_text="注: 红字代表功能被群管理员禁用,红线代表功能正在维护", font_size=24, - font="yuanshen.ttf", + font="HYWenHei-85W.ttf", font_color=(231, 74, 57) ), (300, 10), diff --git a/plugins/ai/data_source.py b/plugins/ai/data_source.py index ca5827f6..8aadfa7a 100755 --- a/plugins/ai/data_source.py +++ b/plugins/ai/data_source.py @@ -126,35 +126,39 @@ async def xie_ai(text: str) -> str: """ res = await AsyncHttpx.get(f"http://api.qingyunke.com/api.php?key=free&appid=0&msg={text}") content = "" - data = json.loads(res.text) - if data["result"] == 0: - content = data["content"] - if "菲菲" in content: - content = content.replace("菲菲", NICKNAME) - if "艳儿" in content: - content = content.replace("艳儿", NICKNAME) - if "公众号" in content: - content = "" - if "{br}" in content: - content = content.replace("{br}", "\n") - if "提示" in content: - content = content[: content.find("提示")] - if "淘宝" in content or "taobao.com" in content: - return "" - while True: - r = re.search("{face:(.*)}", content) - if r: - id_ = r.group(1) - content = content.replace( - "{" + f"face:{id_}" + "}", str(face(int(id_))) - ) - else: - break - return ( - content - if not content and not Config.get_config("ai", "ALAPI_AI_CHECK") - else await check_text(content) - ) + try: + data = json.loads(res.text) + if data["result"] == 0: + content = data["content"] + if "菲菲" in content: + content = content.replace("菲菲", NICKNAME) + if "艳儿" in content: + content = content.replace("艳儿", NICKNAME) + if "公众号" in content: + content = "" + if "{br}" in content: + content = content.replace("{br}", "\n") + if "提示" in content: + content = content[: content.find("提示")] + if "淘宝" in content or "taobao.com" in content: + return "" + while True: + r = re.search("{face:(.*)}", content) + if r: + id_ = r.group(1) + content = content.replace( + "{" + f"face:{id_}" + "}", str(face(int(id_))) + ) + else: + break + return ( + content + if not content and not Config.get_config("ai", "ALAPI_AI_CHECK") + else await check_text(content) + ) + except Exception as e: + logger.error(f"Ai xie_ai 发生错误 {type(e)}:{e}") + return "" def hello() -> str: diff --git a/plugins/black_word/__init__.py b/plugins/black_word/__init__.py new file mode 100644 index 00000000..db9c7afd --- /dev/null +++ b/plugins/black_word/__init__.py @@ -0,0 +1,249 @@ +from nonebot.adapters.onebot.v11 import ( + Event, + MessageEvent, + GroupMessageEvent, + Message, + Bot, +) +from nonebot.matcher import Matcher +from nonebot.message import run_preprocessor +from utils.image_utils import BuildImage +from utils.utils import get_message_text, is_number +from nonebot.params import RegexGroup, CommandArg +from .utils import black_word_manager +from nonebot import on_command, on_message, on_regex +from configs.config import Config, NICKNAME +from nonebot.permission import SUPERUSER +from .data_source import show_black_text_image, set_user_punish +from services.log import logger +from models.ban_user import BanUser +from datetime import datetime +from utils.message_builder import image +from .model import BlackWord +from typing import Tuple, Any + + +__zx_plugin_name__ = "敏感词检测" +__plugin_usage__ = """ +usage: + 注意你的发言! + 指令: + 惩罚机制 +""".strip() +__plugin_superuser_usage__ = """ +usage: + 查看和设置惩罚 + Regex:^记录名单(u:\d*)?(g:\d*)?(d[=><]\d*-\d{1,2}-\d{1,2})?$ + 设置惩罚id需要通过 '记录名单u:xxxxxxxx' 获取 + 指令: + 记录名单 + 设置惩罚 [user_id] [id] [punish_level] + 示例:记录名单 + 示例:记录名单u:12345678 + 示例:设置惩罚 12345678 1 4 +""".strip() +__plugin_des__ = "请注意你的发言!!" +__plugin_type__ = ("其他",) +__plugin_version__ = 0.1 +__plugin_author__ = "HibiKier" +__plugin_cmd__ = ["惩罚机制", "记录名单 [_superuser]", "设置惩罚 [_superuser]"] +__plugin_settings__ = { + "cmd": ["敏感词检测"], +} + + +Config.add_plugin_config( + "black_word", "CYCLE_DAYS", 30, name="敏感词检测与惩罚", help_="黑名单词汇记录周期", default_value=30 +) + +Config.add_plugin_config( + "black_word", + "TOLERATE_COUNT", + [5, 1, 1, 1, 1], + help_="各个级别惩罚的容忍次数,依次为:1, 2, 3, 4, 5", + default_value=[5, 1, 1, 1, 1], +) + +Config.add_plugin_config( + "black_word", "AUTO_PUNISH", True, help_="是否启动自动惩罚机制", default_value=True +) + +# Config.add_plugin_config( +# "black_word", "IGNORE_GROUP", [], help_="退出群聊惩罚中忽略的群聊,即不会退出的群聊", default_value=[] +# ) + +Config.add_plugin_config( + "black_word", + "BAN_4_DURATION", + 360, + help_="Union[int, List[int, int]]Ban时长(分钟),四级惩罚,可以为指定数字或指定列表区间(随机),例如 [30, 360]", + default_value=360, +) + +Config.add_plugin_config( + "black_word", + "BAN_3_DURATION", + 7, + help_="Union[int, List[int, int]]Ban时长(天),三级惩罚,可以为指定数字或指定列表区间(随机),例如 [7, 30]", + default_value=360, +) + +Config.add_plugin_config( + "black_word", + "WARNING_RESULT", + f"请注意对{NICKNAME}的发言内容", + help_="口头警告内容", + default_value=f"请注意对{NICKNAME}的发言内容", +) + +Config.add_plugin_config( + "black_word", + "AUTO_ADD_PUNISH_LEVEL", + True, + help_="自动提级机制,当周期内处罚次数大于某一特定值就提升惩罚等级", + default_value=True, +) + +Config.add_plugin_config( + "black_word", + "ADD_PUNISH_LEVEL_TO_COUNT", + 3, + help_="在CYCLE_DAYS周期内触发指定惩罚次数后提升惩罚等级", + default_value=3, +) + +Config.add_plugin_config( + "black_word", + "ALAPI_CHECK_FLAG", + False, + help_="当未检测到已收录的敏感词时,开启ALAPI文本检测并将疑似文本发送给超级用户", + default_value=False, +) + +Config.add_plugin_config( + "black_word", + "CONTAIN_BLACK_STOP_PROPAGATION", + True, + help_="当文本包含任意敏感词时,停止向下级插件传递,即不触发ai", + default_value=True, +) + +message_matcher = on_message(priority=1, block=False) + +set_punish = on_command("设置惩罚", priority=1, permission=SUPERUSER, block=True) + +show_black = on_regex( + r"^记录名单(u:\d*)?(g:\d*)?(d[=><]\d*-\d{1,2}-\d{1,2})?$", + priority=1, + permission=SUPERUSER, + block=True, +) + +show_punish = on_command("惩罚机制", aliases={"敏感词检测"}, priority=1, block=True) + + +# 黑名单词汇检测 +@run_preprocessor +async def _( + bot: Bot, + matcher: Matcher, + event: Event, +): + if ( + isinstance(event, MessageEvent) + and event.is_tome() + and matcher.plugin_name == "black_word" + and not await BanUser.is_ban(event.user_id) + and str(event.user_id) not in bot.config.superusers + ): + user_id = event.user_id + group_id = event.group_id if isinstance(event, GroupMessageEvent) else None + msg = get_message_text(event.json()) + if await black_word_manager.check(user_id, group_id, msg) and Config.get_config( + "black_word", "CONTAIN_BLACK_STOP_PROPAGATION" + ): + matcher.stop_propagation() + + +@show_black.handle() +async def _(bot: Bot, reg_group: Tuple[Any, ...] = RegexGroup()): + user_id, group_id, date = reg_group + date_type = "=" + if date: + date_type = date[1] + date = date[2:] + try: + date = datetime.strptime(date, "%Y-%m-%d") + except ValueError: + await show_black.finish("日期格式错误,需要:年-月-日") + pic = await show_black_text_image( + bot, + int(user_id.split(":")[1]) if user_id else None, + int(group_id.split(":")[1]) if group_id else None, + date, + date_type, + ) + await show_black.send(image(b64=pic.pic2bs4())) + + +@show_punish.handle() +async def _(): + text = f""" + ** 惩罚机制 ** + + 惩罚前包含容忍机制,在指定周期内会容忍偶尔少次数的敏感词只会进行警告提醒 + + 多次触发同级惩罚会使惩罚等级提高,即惩罚自动提级机制 + + 目前公开的惩罚等级: + + 1级:永久ban + + 2级:删除好友 + + 3级:ban指定/随机天数 + + 4级:ban指定/随机时长 + + 5级:警告 + + 备注: + + 该功能为测试阶段,如果你有被误封情况,请联系管理员,会从数据库中提取出你的数据进行审核后判断 + + 目前该功能暂不完善,部分情况会由管理员鉴定,请注意对真寻的发言 + + 关于敏感词: + + 记住不要骂{NICKNAME}就对了! + """.strip() + max_width = 0 + for m in text.split("\n"): + max_width = len(m) * 20 if len(m) * 20 > max_width else max_width + max_height = len(text.split("\n")) * 24 + A = BuildImage( + max_width, max_height, font="CJGaoDeGuo.otf", font_size=24, color="#E3DBD1" + ) + A.text((10, 10), text) + await show_punish.send(image(b64=A.pic2bs4())) + + +@set_punish.handle() +async def _(event: MessageEvent, arg: Message = CommandArg()): + msg = arg.extract_plain_text().strip().split() + if ( + len(msg) < 3 + or not is_number(msg[0]) + or not is_number(msg[1]) + or not is_number(msg[2]) + ): + await set_punish.finish("参数错误,请查看帮助...", at_sender=True) + uid = int(msg[0]) + id_ = int(msg[1]) + punish_level = int(msg[2]) + print(uid, id_, punish_level) + rst = await set_user_punish(uid, id_, punish_level) + await set_punish.send(rst) + logger.info( + f"USER {event.user_id} 设置惩罚 uid:{uid} id_:{id_} punish_level:{punish_level} --> {rst}" + ) diff --git a/plugins/black_word/data_source.py b/plugins/black_word/data_source.py new file mode 100644 index 00000000..7be37d83 --- /dev/null +++ b/plugins/black_word/data_source.py @@ -0,0 +1,118 @@ +from nonebot.adapters.onebot.v11 import Bot +from utils.image_utils import BuildImage, text2image +from services.log import logger +from typing import Optional +from datetime import datetime +from .model import BlackWord +from .utils import _get_punish, Config + + +async def show_black_text_image( + bot: Bot, + user: Optional[int], + group_id: Optional[int], + date: Optional[datetime], + data_type: str = "=", +) -> BuildImage: + """ + 展示记录名单 + :param bot: bot + :param user: 用户qq + :param group_id: 群聊 + :param date: 日期 + :param data_type: 日期搜索类型 + :return: + """ + data = await BlackWord.get_black_data(user, group_id, date, data_type) + A = BuildImage(0, 0, color="#f9f6f2", font_size=20) + image_list = [] + friend_str = await bot.get_friend_list() + id_str = "" + uname_str = "" + uid_str = "" + gid_str = "" + plant_text_str = "" + black_word_str = "" + punish_str = "" + punish_level_str = "" + create_time_str = "" + for i, x in enumerate(data): + try: + if x.group_id: + user_name = ( + await bot.get_group_member_info( + group_id=x.group_id, user_id=x.user_qq + ) + )["card"] + else: + user_name = [ + u["nickname"] for u in friend_str if u["user_id"] == x.user_qq + ][0] + except Exception as e: + logger.warning( + f"show_black_text_image 获取 USER {x.user_qq} user_name 失败 {type(e)}:{e}" + ) + user_name = x.user_qq + id_str += f"{i}\n" + uname_str += f"{user_name}\n" + uid_str += f"{x.user_qq}\n" + gid_str += f"{x.group_id}\n" + plant_text = " ".join(x.plant_text.split("\n")) + if A.getsize(plant_text)[0] > 200: + plant_text = plant_text[:20] + "..." + plant_text_str += f"{plant_text}\n" + black_word_str += f"{x.black_word}\n" + punish_str += f"{x.punish}\n" + punish_level_str += f"{x.punish_level}\n" + create_time_str += f"{x.create_time.replace(microsecond=0)}\n" + _tmp_img = BuildImage(0, 0, font_size=35, font="CJGaoDeGuo.otf") + for s, type_ in [ + (id_str, "Id"), + (uname_str, "昵称"), + (uid_str, "UID"), + (gid_str, "GID"), + (plant_text_str, "文本"), + (black_word_str, "检测"), + (punish_str, "惩罚"), + (punish_level_str, "等级"), + (create_time_str, "记录日期"), + ]: + img = await text2image(s, color="#f9f6f2", _add_height=3.32) + w = _tmp_img.getsize(type_)[0] if _tmp_img.getsize(type_)[0] > img.w else img.w + A = BuildImage(w + 11, img.h + 50, color="#f9f6f2", font_size=35, font="CJGaoDeGuo.otf") + await A.atext((10, 10), type_) + await A.apaste(img, (0, 50)) + image_list.append(A) + horizontal_line = [] + w, h = 0, 0 + for img in image_list: + w += img.w + 20 + h = img.h if img.h > h else h + horizontal_line.append(img.w) + A = BuildImage(w, h, color="#f9f6f2") + current_w = 0 + for img in image_list: + await A.apaste(img, (current_w, 0)) + current_w += img.w + 20 + return A + + +async def set_user_punish(user_id: int, id_: int, punish_level: int) -> str: + """ + 设置惩罚 + :param user_id: 用户id + :param id_: 记录下标 + :param punish_level: 惩罚等级 + """ + result = await _get_punish(punish_level, user_id) + punish = { + 1: "永久ban", + 2: "删除好友", + 3: f"ban {result} 天", + 4: f"ban {result} 分钟", + 5: "口头警告" + } + if await BlackWord.set_user_punish(user_id, punish[punish_level], id_=id_): + return f"已对 USER {user_id} 进行 {punish[punish_level]} 处罚。" + else: + return "操作失败,可能未找到用户,id或敏感词" diff --git a/plugins/black_word/model.py b/plugins/black_word/model.py new file mode 100644 index 00000000..2a7c682c --- /dev/null +++ b/plugins/black_word/model.py @@ -0,0 +1,149 @@ +from services.db_context import db +from typing import Optional, List +from datetime import datetime, timedelta + + +class BlackWord(db.Model): + __tablename__ = "black_word" + + id = db.Column(db.Integer(), primary_key=True, autoincrement=True) + user_qq = db.Column(db.BigInteger(), nullable=False, primary_key=True) + group_id = db.Column(db.BigInteger()) + plant_text = db.Column(db.String()) + black_word = db.Column(db.String()) + punish = db.Column(db.String(), default="") + punish_level = db.Column(db.Integer()) + create_time = db.Column(db.DateTime(timezone=True), nullable=False) + + @classmethod + async def add_user_black_word( + cls, + user_qq: int, + group_id: Optional[int], + black_word: str, + plant_text: str, + punish_level: int, + ): + """ + 说明: + 添加用户发送的敏感词 + 参数: + :param user_qq: 用户id + :param group_id: 群号 + :param black_word: 黑名单词汇 + :param plant_text: 消息文本 + :param punish_level: 惩罚等级 + """ + await cls.create( + user_qq=user_qq, + group_id=group_id, + plant_text=plant_text, + black_word=black_word, + punish_level=punish_level, + create_time=datetime.now(), + ) + + @classmethod + async def set_user_punish( + cls, + user_qq: int, + punish: str, + black_word: Optional[str] = None, + id_: Optional[int] = None, + ) -> bool: + """ + 说明: + 设置处罚 + 参数: + :param user_qq: 用户id + :param punish: 处罚 + :param black_word: 黑名单词汇 + :param id_: 记录下标 + """ + user = None + if (not black_word and not id_) or not punish: + return False + query = cls.query.where(cls.user_qq == user_qq).with_for_update() + if black_word: + user = await query.where(cls.black_word == black_word).order_by(cls.id.desc()).gino.first() + elif id_: + user_list = await query.gino.all() + print(len(user_list)) + if len(user_list) == 0 or (id_ < 0 or id_ > len(user_list)): + return False + user = user_list[id_] + if not user: + return False + await user.update(punish=cls.punish + punish + " ").apply() + return True + + @classmethod + async def get_user_count( + cls, user_qq: int, days: int = 7, punish_level: Optional[int] = None + ) -> int: + """ + 说明: + 获取用户规定周期内的犯事次数 + 参数: + :param user_qq: 用户qq + :param days: 周期天数 + :param punish_level: 惩罚等级 + """ + setattr(BlackWord, "count", db.func.count(cls.id).label("count")) + query = cls.select("count").where( + (cls.user_qq == user_qq) + & (cls.punish_level != -1) + & (cls.create_time > datetime.now() - timedelta(days=days)) + ) + if punish_level is not None: + query = query.where(cls.punish_level == punish_level) + return (await query.gino.first())[0] + + @classmethod + async def get_user_punish_level(cls, user_qq: int, days: int = 7) -> Optional[int]: + """ + 说明: + 获取用户最近一次的惩罚记录等级 + 参数: + :param user_qq: 用户qq + :param days: 周期天数 + """ + if ( + query := await cls.query.where(cls.user_qq == user_qq) + .where(cls.create_time > datetime.now() - timedelta(days=days)) + .order_by(cls.id.desc()) + .gino.first() + ): + return query.punish_level + return None + + @classmethod + async def get_black_data( + cls, + user_qq: Optional[int], + group_id: Optional[int], + date: Optional[datetime], + date_type: str = "=", + ) -> List["BlackWord"]: + """ + 说明: + 通过指定条件查询数据 + 参数: + :param user_qq: 用户qq + :param group_id: 群号 + :param date: 日期 + :param date_type: 日期查询类型 + """ + query = cls.query + if user_qq: + query = query.where(cls.user_qq == user_qq) + if group_id: + query = query.where(cls.group_id == group_id) + if date: + if date_type == "=": + query = query.where(cls.create_time == date) + elif date_type == ">": + query = query.where(cls.create_time > date) + elif date_type == "<": + query = query.where(cls.create_time < date) + return await query.gino.all() diff --git a/plugins/black_word/utils.py b/plugins/black_word/utils.py new file mode 100644 index 00000000..fbc36a6d --- /dev/null +++ b/plugins/black_word/utils.py @@ -0,0 +1,324 @@ +from utils.utils import cn2py, get_bot +from configs.path_config import DATA_PATH +from typing import Optional, Union, Tuple +from .model import BlackWord +from configs.config import Config +from pathlib import Path +from services.log import logger +from models.ban_user import BanUser +from nonebot.adapters.onebot.v11.exception import ActionFailed +from models.group_member_info import GroupInfoUser +from utils.http_utils import AsyncHttpx +import random + +try: + import ujson as json +except ModuleNotFoundError: + import json + + +class BlackWordManager: + + """ + 敏感词管理( 拒绝恶意 + """ + + def __init__(self, word_file: Path, py_file: Path): + self._word_list = { + "1": [], + "2": [], + "3": [], + "4": ["sb", "nmsl", "mdzz", "2b", "jb", "操", "废物", "憨憨", "cnm", "rnm"], + "5": [], + } + self._py_list = { + "1": [], + "2": [], + "3": [], + "4": [ + "shabi", + "wocaonima", + "sima", + "sabi", + "zhizhang", + "naocan", + "caonima", + "rinima", + "simadongxi", + "simawanyi", + "hanbi", + "hanpi", + "laji", + "fw" + ], + "5": [], + } + word_file.parent.mkdir(parents=True, exist_ok=True) + if word_file.exists(): + # 清空默认配置 + with open(word_file, "r", encoding="utf8") as f: + self._word_list = json.load(f) + else: + with open(word_file, "w", encoding="utf8") as f: + json.dump( + self._word_list, + f, + ensure_ascii=False, + indent=4, + ) + if py_file.exists(): + # 清空默认配置 + with open(py_file, "r", encoding="utf8") as f: + self._py_list = json.load(f) + else: + with open(py_file, "w", encoding="utf8") as f: + json.dump( + self._py_list, + f, + ensure_ascii=False, + indent=4, + ) + + async def check( + self, user_id: int, group_id: Optional[int], message: str + ) -> Optional[Union[str, bool]]: + """ + 检查是否包含黑名单词汇 + :param user_id: 用户id + :param group_id: 群号 + :param message: 消息 + """ + print(user_id, group_id, message) + if data := self._check(message): + print(data) + if data[0]: + await _add_user_black_word( + user_id, group_id, data[0], message, int(data[1]) + ) + return True + if Config.get_config( + "black_word", "ALAPI_CHECK_FLAG" + ) and not await check_text(message): + await send_msg( + 0, None, f"USER {user_id} GROUP {group_id} ALAPI 疑似检测:{message}" + ) + return False + + def _check(self, message: str) -> Tuple[Optional[str], int]: + """ + 检测文本是否违规 + :param message: 检测消息 + """ + # 移除空格 + message = message.replace(" ", "") + py_msg = cn2py(message).lower() + # 完全匹配 + for x in [self._word_list, self._py_list]: + for level in x: + if message in x[level] or py_msg in x[level]: + return message if message in x[level] else py_msg, level + # 模糊匹配 + for x in [self._word_list, self._py_list]: + for level in x: + for m in x[level]: + if m in message or m in py_msg: + return m, -1 + return None, 0 + + +async def _add_user_black_word( + user_id: int, + group_id: Optional[int], + black_word: str, + message: str, + punish_level: int, +): + """ + 添加敏感词数据 + :param user_id: 用户id + :param group_id: 群号 + :param black_word: 触发的黑名单词汇 + :param message: 原始文本 + :param punish_level: 惩罚等级 + """ + cycle_days = Config.get_config("black_word", "CYCLE_DAYS") or 7 + user_count = await BlackWord.get_user_count(user_id, cycle_days, punish_level) + # 周期内超过次数直接提升惩罚 + if Config.get_config( + "black_word", "AUTO_ADD_PUNISH_LEVEL" + ) and user_count > Config.get_config("black_word", "ADD_PUNISH_LEVEL_TO_COUNT"): + punish_level -= 1 + await BlackWord.add_user_black_word( + user_id, group_id, black_word, message, punish_level + ) + logger.info( + f"已将 USER {user_id} GROUP {group_id} 添加至黑名单词汇记录 Black_word:{black_word} Plant_text:{message}" + ) + # 自动惩罚 + if Config.get_config("black_word", "AUTO_PUNISH") and punish_level != -1: + await _punish_handle(user_id, group_id, punish_level, black_word) + + +async def _punish_handle( + user_id: int, group_id: Optional[int], punish_level: int, black_word: str +): + """ + 惩罚措施,级别越低惩罚越严 + :param user_id: 用户id + :param group_id: 群号 + :param black_word: 触发的黑名单词汇 + """ + logger.info(f"BlackWord USER {user_id} 触发 {punish_level} 级惩罚...") + # 周期天数 + cycle_days = Config.get_config("black_word", "CYCLE_DAYS") or 7 + # 用户周期内触发punish_level级惩罚的次数 + user_count = await BlackWord.get_user_count(user_id, cycle_days, punish_level) + # 获取最近一次的惩罚等级,将在此基础上增加 + punish_level = await BlackWord.get_user_punish_level(user_id, cycle_days) or punish_level + # 容忍次数:List[int] + tolerate_count = Config.get_config("black_word", "TOLERATE_COUNT") + if not tolerate_count or len(tolerate_count) < 5: + tolerate_count = [5, 2, 2, 2, 2] + if punish_level == 1 and user_count > tolerate_count[punish_level - 1]: + # 永久ban + await _get_punish(1, user_id, group_id) + await BlackWord.set_user_punish(user_id, "永久ban 删除好友", black_word) + elif punish_level == 2 and user_count > tolerate_count[punish_level - 1]: + # 删除好友 + await _get_punish(2, user_id, group_id) + await BlackWord.set_user_punish(user_id, "删除好友", black_word) + elif punish_level == 3 and user_count > tolerate_count[punish_level - 1]: + # 永久ban + ban_day = await _get_punish(3, user_id, group_id) + await BlackWord.set_user_punish(user_id, f"ban {ban_day} 天", black_word) + elif punish_level == 4 and user_count > tolerate_count[punish_level - 1]: + # ban指定时长 + ban_time = await _get_punish(4, user_id, group_id) + await BlackWord.set_user_punish(user_id, f"ban {ban_time} 分钟", black_word) + elif punish_level == 5 and user_count > tolerate_count[punish_level - 1]: + # 口头警告 + warning_result = await _get_punish(5, user_id, group_id) + await BlackWord.set_user_punish(user_id, f"口头警告:{warning_result}", black_word) + else: + await BlackWord.set_user_punish(user_id, f"提示!", black_word) + await send_msg( + user_id, + group_id, + f"BlackWordChecker:该条发言已被记录,目前你在{cycle_days}天内的发表{punish_level}级" + f"言论记录次数为:{user_count}次,请注意你的发言\n" + f"* 如果你不清楚惩罚机制,请发送“惩罚机制” *", + ) + + +async def _get_punish( + id_: int, user_id: int, group_id: Optional[int] = None +) -> Optional[Union[int, str]]: + """ + 通过id_获取惩罚 + :param id_: id + :param user_id: 用户id + :param group_id: 群号 + """ + bot = get_bot() + # 忽略的群聊 + # _ignore_group = Config.get_config("black_word", "IGNORE_GROUP") + # 处罚 id 4 ban 时间:int,List[int] + ban_3_duration = Config.get_config("black_word", "BAN_3_DURATION") + # 处罚 id 4 ban 时间:int,List[int] + ban_4_duration = Config.get_config("black_word", "BAN_4_DURATION") + # 口头警告内容 + warning_result = Config.get_config("black_word", "WARNING_RESULT") + try: + uname = (await GroupInfoUser.get_member_info(user_id, group_id)).user_name + except AttributeError: + uname = user_id + # 永久ban + if id_ == 1: + if str(user_id) not in bot.config.superusers: + await BanUser.ban(user_id, 10, 99999999) + await send_msg(user_id, group_id, f"BlackWordChecker 永久ban USER {uname}({user_id})") + logger.info(f"BlackWord 永久封禁 USER {user_id}...") + # 删除好友(有的话 + elif id_ == 2: + if str(user_id) not in bot.config.superusers: + try: + await bot.delete_friend(user_id=user_id) + await send_msg( + user_id, group_id, f"BlackWordChecker 删除好友 USER {uname}({user_id})" + ) + logger.info(f"BlackWord 删除好友 {user_id}...") + except ActionFailed: + pass + # 封禁用户指定时间,默认7天 + elif id_ == 3: + if isinstance(ban_3_duration, list): + ban_3_duration = random.randint(ban_3_duration[0], ban_3_duration[1]) + await BanUser.ban(user_id, 9, ban_4_duration * 60 * 60 * 24) + await send_msg( + user_id, + group_id, + f"BlackWordChecker 对用户 USER {uname}({user_id}) 进行封禁 {ban_3_duration} 天处罚。", + ) + logger.info(f"BlackWord 封禁 USER {uname}({user_id}) {ban_3_duration} 天...") + return ban_3_duration + # 封禁用户指定时间,默认360分钟 + elif id_ == 4: + if isinstance(ban_4_duration, list): + ban_4_duration = random.randint(ban_4_duration[0], ban_4_duration[1]) + await BanUser.ban(user_id, 9, ban_4_duration * 60) + await send_msg( + user_id, + group_id, + f"BlackWordChecker 对用户 USER {uname}({user_id}) 进行封禁 {ban_4_duration} 分钟处罚。", + ) + logger.info(f"BlackWord 封禁 USER {uname}({user_id}) {ban_4_duration} 分钟...") + return ban_4_duration + # 口头警告 + elif id_ == 5: + if group_id: + await bot.send_group_msg(group_id=group_id, message=warning_result) + else: + await bot.send_private_msg(user_id=user_id, message=warning_result) + logger.info(f"BlackWord 口头警告 USER {user_id}") + return warning_result + return None + + +async def send_msg(user_id: int, group_id: Optional[int], message: str): + """ + 发送消息 + :param user_id: user_id + :param group_id: group_id + :param message: message + """ + bot = get_bot() + if not user_id: + user_id = int(list(bot.config.superusers)[0]) + if group_id: + await bot.send_group_msg(group_id=group_id, message=message) + else: + await bot.send_private_msg(user_id=user_id, message=message) + + +async def check_text(text: str) -> bool: + """ + ALAPI文本检测,检测输入违规 + :param text: 回复 + """ + if not Config.get_config("alapi", "ALAPI_TOKEN"): + return True + params = {"token": Config.get_config("alapi", "ALAPI_TOKEN"), "text": text} + try: + data = ( + await AsyncHttpx.get( + "https://v2.alapi.cn/api/censor/text", timeout=4, params=params + ) + ).json() + if data["code"] == 200: + return data["data"]["conclusion_type"] == 2 + except Exception as e: + logger.error(f"检测违规文本错误...{type(e)}:{e}") + return True + + +black_word_manager = BlackWordManager(DATA_PATH / "black_word" / "black_word.json", DATA_PATH / "black_word" / "black_py.json") diff --git a/plugins/group_last_chat/__init__.py b/plugins/group_last_chat/__init__.py deleted file mode 100755 index e9181cc2..00000000 --- a/plugins/group_last_chat/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -from nonebot import on_message -from nonebot.adapters.onebot.v11.permission import GROUP -from nonebot.adapters.onebot.v11 import GroupMessageEvent -from .data_source import cancel_all_notice, save_data, get_data, set_data_value -from services.log import logger -import time - - -__zx_plugin_name__ = "群聊最后聊天时间记录 [Hidden]" -__plugin_version__ = 0.1 -__plugin_author__ = "HibiKier" - - -last_chat = on_message(priority=1, block=False, permission=GROUP) - - -@last_chat.handle() -async def _(event: GroupMessageEvent): - time_data = await get_data() - set_data_value(event.group_id, time.time()) - if event.group_id in time_data["_group"]: - time_data["_group"].remove(event.group_id) - set_data_value("_group", time_data["_group"]) - for key in time_data.keys(): - if key not in ["check_time", "_group"]: - if key not in time_data["_group"]: - if time.time() - time_data[key] > 60 * 60 * 36: - await cancel_all_notice(key) - time_data["_group"].append(key) - set_data_value("_group", time_data["_group"]) - logger.info(f"GROUP {event.group_id} 因群内发言时间大于36小时被取消全部通知") - if time.time() - time_data["check_time"] > 60 * 60 * 1: - set_data_value("check_time", time.time()) - save_data() diff --git a/plugins/group_last_chat/data_source.py b/plugins/group_last_chat/data_source.py deleted file mode 100755 index 0d411dd4..00000000 --- a/plugins/group_last_chat/data_source.py +++ /dev/null @@ -1,67 +0,0 @@ -from configs.path_config import DATA_PATH -from utils.utils import get_bot -from datetime import datetime -import time -from services.log import logger -from utils.manager import group_manager -try: - import ujson as json -except ModuleNotFoundError: - import json - - -time_data = {} - - -async def init(): - global time_data - bot = get_bot() - gl = await bot.get_group_list() - gl = [g["group_id"] for g in gl] - data = read_data("group_last_chat_time.json") - for g in gl: - if not data.get(g): - time_data[g] = time.time() - if not time_data.get("check_time"): - time_data["check_time"] = time.time() - if not time_data.get("_group"): - time_data["_group"] = [] - save_data() - return time_data - - -def read_data(file_name: str): - try: - with open(DATA_PATH / file_name, "r", encoding="utf8") as f: - return json.load(f) - except (ValueError, FileNotFoundError): - return {} - - -def save_data(): - with open(DATA_PATH / "group_last_chat_time.json", "w") as f: - json.dump(time_data, f, indent=4) - logger.info( - f'自动存储 group_last_chat_time.json 时间:{str(datetime.now()).split(".")[0]}' - ) - - -# 取消全部通知 -async def cancel_all_notice(group_id): - group_id = int(group_id) - for command in group_manager.get_task_data(): - if await group_manager.check_group_task_status(group_id, command): - await group_manager.close_group_task(group_id, command) - logger.info(f"关闭了 {group_id} 群的全部通知") - - -async def get_data(): - global time_data - if not time_data: - time_data = await init() - return time_data - - -def set_data_value(key, value): - global time_data - time_data[key] = value diff --git a/utils/depends/__init__.py b/utils/depends/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/utils/image_utils.py b/utils/image_utils.py index 5641717c..96974082 100755 --- a/utils/image_utils.py +++ b/utils/image_utils.py @@ -1336,6 +1336,7 @@ async def text2image( font: str = "CJGaoDeGuo.otf", font_color: Union[str, Tuple[int, int, int]] = "black", padding: Union[int, Tuple[int, int, int, int]] = 0, + _add_height: float = 0, ) -> BuildImage: """ 说明: @@ -1358,6 +1359,7 @@ async def text2image( :param font: 普通字体 :param font_color: 普通字体颜色 :param padding: 文本外边距,元组类型时为 (上,左,下,右) + :param _add_height: 由于get_size无法返回正确的高度,采用手动方式额外添加高度 """ pw = ph = top_padding = left_padding = 0 if padding: @@ -1489,17 +1491,17 @@ async def text2image( else: width = 0 height = 0 - _tmp = BuildImage(0, 0, font_size=font_size) + _tmp = BuildImage(0, 0, font=font, font_size=font_size) for x in text.split("\n"): x = x if x.strip() else "正" w, h = _tmp.getsize(x) - height += h + height += h + _add_height width = width if width > w else w width += pw height += ph A = BuildImage( width + left_padding, - height + top_padding, + height + top_padding + 2, font_size=font_size, color=color, font=font,