From 52da79db7ec5eced69c5cedc73539272e0fd0131 Mon Sep 17 00:00:00 2001 From: perforat Date: Wed, 27 May 2026 15:50:17 +0000 Subject: [PATCH] react to keyword with message --- cogs/autoreply/autoreply.py | 106 ++++++++++++++++++++++++++++++++++++ cogs/autoreply/config.json | 8 +++ cogs/autoreply/replies.json | 18 ++++++ 3 files changed, 132 insertions(+) create mode 100644 cogs/autoreply/autoreply.py create mode 100644 cogs/autoreply/config.json create mode 100644 cogs/autoreply/replies.json diff --git a/cogs/autoreply/autoreply.py b/cogs/autoreply/autoreply.py new file mode 100644 index 0000000..aa51847 --- /dev/null +++ b/cogs/autoreply/autoreply.py @@ -0,0 +1,106 @@ +import discord +from discord.ext import commands +import json +import logging +import os +import re + +logger = logging.getLogger(__name__) + +CONFIG_PATH = os.path.join(os.path.dirname(__file__), "config.json") +DATA_PATH = os.path.join(os.path.dirname(__file__), "replies.json") + + +def load_json(path: str) -> dict: + with open(path, "r", encoding="utf-8") as f: + return json.load(f) + + +def save_json(path: str, data: dict) -> None: + with open(path, "w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=2) + + +class AutoReply(commands.Cog): + def __init__(self, bot: commands.Bot): + self.bot = bot + self.config: dict = {} + self.rules: list[dict] = [] + + async def cog_load(self): + self._load_config() + self._load_rules() + + def _load_config(self): + try: + self.config = load_json(CONFIG_PATH) + except (FileNotFoundError, json.JSONDecodeError) as e: + logger.error("[AutoReply] config error: %s", e) + + def _load_rules(self): + try: + self.rules = load_json(DATA_PATH).get("rules", []) + logger.info("[AutoReply] %d rules loaded.", len(self.rules)) + except FileNotFoundError: + self.rules = [] + save_json(DATA_PATH, {"rules": []}) + except json.JSONDecodeError as e: + logger.error("[AutoReply] replies.json parse error: %s", e) + + def _save_rules(self): + save_json(DATA_PATH, {"rules": self.rules}) + + def _match(self, rule: dict, content: str) -> bool: + mode = rule.get("match_mode", "contains") + case_sensitive = rule.get("case_sensitive", False) + text = content if case_sensitive else content.lower() + + for kw in rule.get("keywords", []): + kw_cmp = kw if case_sensitive else kw.lower() + if mode == "exact" and text == kw_cmp: return True + if mode == "contains" and kw_cmp in text: return True + if mode == "startswith" and text.startswith(kw_cmp): return True + if mode == "regex": + flags = 0 if case_sensitive else re.IGNORECASE + if re.search(kw, content, flags): return True + return False + + @commands.Cog.listener() + async def on_message(self, message: discord.Message): + if message.author.bot or not message.guild: + return + + for rule in self.rules: + if not rule.get("enabled", True): + continue + if self._match(rule, message.content): + await message.channel.send(rule["reply"]) + if not self.config.get("match_all", False): + break + + @commands.command(name="add_rule") + @commands.has_permissions(administrator=True) + async def add_rule(self, ctx: commands.Context, keywords: str, *, reply: str): + # add rules + rule = { + "keywords": [k.strip() for k in keywords.split(",")], + "match_mode": "contains", + "case_sensitive": False, + "reply": reply, + "enabled": True, + } + self.rules.append(rule) + self._save_rules() + kws = ", ".join(f"`{k}`" for k in rule["keywords"]) + await ctx.send(f"Rule added") + + @add_rule.error + async def add_rule_error(self, ctx: commands.Context, error): + if isinstance(error, commands.MissingPermissions): + await ctx.send("Шпацируй нахуй отсюда") + elif isinstance(error, commands.MissingRequiredArgument): + await ctx.send("Wrong usage") + + +async def setup(bot: commands.Bot): + await bot.add_cog(AutoReply(bot)) \ No newline at end of file diff --git a/cogs/autoreply/config.json b/cogs/autoreply/config.json new file mode 100644 index 0000000..8c62cb1 --- /dev/null +++ b/cogs/autoreply/config.json @@ -0,0 +1,8 @@ +{ + "description": "Usage is admin-only; Reacts to messages containing keywords with a configured reply", + "usages": [ + "!add_rule " + ], + "hidden": false, + "match_all": false +} \ No newline at end of file diff --git a/cogs/autoreply/replies.json b/cogs/autoreply/replies.json new file mode 100644 index 0000000..45c4362 --- /dev/null +++ b/cogs/autoreply/replies.json @@ -0,0 +1,18 @@ +{ + "rules": [ + { + "keywords": ["ping", "pong"], + "match_mode": "contains", + "case_sensitive": false, + "reply": "Pong", + "enabled": true + }, + { + "keywords": ["kitty", "cat", "IMMEDIATE CAT REQUEST!!!!!!"], + "match_mode": "contains", + "case_sensitive": false, + "reply": "https://cataas.com/cat", + "enabled": true + } + ] +} \ No newline at end of file