diff options
Diffstat (limited to 'cogs/trigger.py')
| -rw-r--r-- | cogs/trigger.py | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/cogs/trigger.py b/cogs/trigger.py new file mode 100644 index 0000000..daf9e7b --- /dev/null +++ b/cogs/trigger.py | |||
| @@ -0,0 +1,305 @@ | |||
| 1 | import os | ||
| 2 | |||
| 3 | import discord | ||
| 4 | from discord.ext import commands | ||
| 5 | import random | ||
| 6 | import sqlite3 | ||
| 7 | import logging | ||
| 8 | |||
| 9 | logger = logging.getLogger(__name__) | ||
| 10 | |||
| 11 | |||
| 12 | DB_PATH = 'data/triggers.db' | ||
| 13 | |||
| 14 | |||
| 15 | def get_connection(): | ||
| 16 | return sqlite3.connect(DB_PATH) | ||
| 17 | |||
| 18 | |||
| 19 | class Trigger(commands.Cog): | ||
| 20 | def __init__(self, bot): | ||
| 21 | self.bot = bot | ||
| 22 | self.db_check() | ||
| 23 | |||
| 24 | trigger_group = discord.SlashCommandGroup("trigger", "trigger commands") | ||
| 25 | |||
| 26 | @staticmethod | ||
| 27 | def db_check(): | ||
| 28 | if not os.path.exists('data'): | ||
| 29 | os.makedirs('data') | ||
| 30 | |||
| 31 | db_exists = os.path.exists(DB_PATH) | ||
| 32 | if not db_exists: | ||
| 33 | logger.warning("Triggers database not found. Creating new database.") | ||
| 34 | |||
| 35 | try: | ||
| 36 | conn = sqlite3.connect(DB_PATH) | ||
| 37 | c = conn.cursor() | ||
| 38 | |||
| 39 | # Enable foreign key constraints | ||
| 40 | c.execute('PRAGMA foreign_keys = ON') | ||
| 41 | |||
| 42 | # Create triggers table | ||
| 43 | c.execute(''' | ||
| 44 | CREATE TABLE IF NOT EXISTS triggers ( | ||
| 45 | id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
| 46 | name TEXT NOT NULL UNIQUE, | ||
| 47 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | ||
| 48 | last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP | ||
| 49 | )''') | ||
| 50 | |||
| 51 | # Create trigger_keywords table with foreign key constraint | ||
| 52 | c.execute(''' | ||
| 53 | CREATE TABLE IF NOT EXISTS trigger_keywords ( | ||
| 54 | id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
| 55 | trigger_id INTEGER NOT NULL, | ||
| 56 | keyword TEXT NOT NULL, | ||
| 57 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | ||
| 58 | FOREIGN KEY (trigger_id) REFERENCES triggers(id) ON DELETE CASCADE, | ||
| 59 | UNIQUE(trigger_id, keyword) | ||
| 60 | )''') | ||
| 61 | |||
| 62 | # Create trigger_responses table with foreign key constraint | ||
| 63 | c.execute(''' | ||
| 64 | CREATE TABLE IF NOT EXISTS trigger_responses ( | ||
| 65 | id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
| 66 | trigger_id INTEGER NOT NULL, | ||
| 67 | response_text TEXT NOT NULL, | ||
| 68 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | ||
| 69 | FOREIGN KEY (trigger_id) REFERENCES triggers(id) ON DELETE CASCADE | ||
| 70 | )''') | ||
| 71 | |||
| 72 | # Create indexes for better query performance | ||
| 73 | c.execute('CREATE INDEX IF NOT EXISTS idx_trigger_keywords_trigger_id ON trigger_keywords(trigger_id)') | ||
| 74 | c.execute('CREATE INDEX IF NOT EXISTS idx_trigger_keywords_keyword ON trigger_keywords(keyword)') | ||
| 75 | c.execute('CREATE INDEX IF NOT EXISTS idx_trigger_responses_trigger_id ON trigger_responses(trigger_id)') | ||
| 76 | |||
| 77 | # Run integrity check | ||
| 78 | integrity_check = c.execute('PRAGMA integrity_check').fetchone() | ||
| 79 | if integrity_check and integrity_check[0] == 'ok': | ||
| 80 | logger.info("Triggers database integrity check passed") | ||
| 81 | else: | ||
| 82 | logger.warning(f"Triggers database integrity check: {integrity_check}") | ||
| 83 | |||
| 84 | conn.commit() | ||
| 85 | |||
| 86 | except sqlite3.Error as e: | ||
| 87 | logger.error(f"Triggers database error: {e}") | ||
| 88 | raise | ||
| 89 | |||
| 90 | finally: | ||
| 91 | if 'conn' in locals(): | ||
| 92 | conn.close() | ||
| 93 | |||
| 94 | if not db_exists: | ||
| 95 | logger.info("Triggers database initialized successfully") | ||
| 96 | |||
| 97 | @commands.Cog.listener() | ||
| 98 | async def on_message(self, message): | ||
| 99 | if message.author == self.bot.user: | ||
| 100 | return | ||
| 101 | |||
| 102 | await self.trigger(message) | ||
| 103 | |||
| 104 | @staticmethod | ||
| 105 | async def trigger(message): | ||
| 106 | content = message.content.lower() | ||
| 107 | with get_connection() as conn: | ||
| 108 | c = conn.cursor() | ||
| 109 | c.execute("SELECT id, name FROM triggers") | ||
| 110 | for trigger_id, name in c.fetchall(): | ||
| 111 | c.execute("SELECT keyword FROM trigger_keywords WHERE trigger_id=?", (trigger_id,)) | ||
| 112 | keywords = [row[0] for row in c.fetchall()] | ||
| 113 | if any(keyword in content for keyword in keywords): | ||
| 114 | c.execute("SELECT response_text FROM trigger_responses WHERE trigger_id=?", (trigger_id,)) | ||
| 115 | responses = [row[0] for row in c.fetchall()] | ||
| 116 | if responses: | ||
| 117 | await message.channel.send(random.choice(responses).replace('\\n', '\n')) | ||
| 118 | logger.info(f"{message.author} triggered {name}") | ||
| 119 | break | ||
| 120 | |||
| 121 | @staticmethod | ||
| 122 | def get_type(name): | ||
| 123 | with sqlite3.connect(DB_PATH) as conn: | ||
| 124 | c = conn.cursor() | ||
| 125 | c.execute("SELECT name FROM triggers") | ||
| 126 | return [row[0] for row in c.fetchall()] | ||
| 127 | |||
| 128 | @trigger_group.command( | ||
| 129 | description="Create a new trigger", | ||
| 130 | ) | ||
| 131 | async def create( | ||
| 132 | self, | ||
| 133 | ctx: discord.ApplicationContext, | ||
| 134 | name = discord.Option( | ||
| 135 | str, | ||
| 136 | "the name of the trigger", | ||
| 137 | autocomplete= discord.utils.basic_autocomplete(get_type), | ||
| 138 | required=True | ||
| 139 | ) | ||
| 140 | ): | ||
| 141 | with get_connection() as conn: | ||
| 142 | c = conn.cursor() | ||
| 143 | c.execute("INSERT OR IGNORE INTO triggers (name) VALUES (?)", (name,)) | ||
| 144 | conn.commit() | ||
| 145 | |||
| 146 | await ctx.respond("trigger added") | ||
| 147 | |||
| 148 | @trigger_group.command( | ||
| 149 | description="Add a trigger or response to the name", | ||
| 150 | ) | ||
| 151 | async def add( | ||
| 152 | self, | ||
| 153 | ctx: discord.ApplicationContext, | ||
| 154 | name = discord.Option( | ||
| 155 | str, | ||
| 156 | "the name of the trigger", | ||
| 157 | autocomplete= discord.utils.basic_autocomplete(get_type), | ||
| 158 | required=True | ||
| 159 | ), | ||
| 160 | trigger = discord.Option( | ||
| 161 | str, | ||
| 162 | "the trigger to add to the name", | ||
| 163 | required=False | ||
| 164 | ), | ||
| 165 | response = discord.Option( | ||
| 166 | str, | ||
| 167 | "the response to add to the name", | ||
| 168 | required=False | ||
| 169 | ) | ||
| 170 | ): | ||
| 171 | with get_connection() as conn: | ||
| 172 | c = conn.cursor() | ||
| 173 | c.execute("SELECT id FROM triggers WHERE name=?", (name,)) | ||
| 174 | row = c.fetchone() | ||
| 175 | if not row: | ||
| 176 | await ctx.respond("Trigger not found.") | ||
| 177 | logger.warning(f"Trigger not found: {name}") | ||
| 178 | return | ||
| 179 | trigger_id = row[0] | ||
| 180 | if trigger: | ||
| 181 | c.execute("INSERT INTO trigger_keywords (trigger_id, keyword) VALUES (?, ?)", (trigger_id, trigger)) | ||
| 182 | if response: | ||
| 183 | c.execute("INSERT INTO trigger_responses (trigger_id, response_text) VALUES (?, ?)", (trigger_id, response)) | ||
| 184 | conn.commit() | ||
| 185 | |||
| 186 | await ctx.respond("trigger updated") | ||
| 187 | logger.info(f"Trigger updated: {name}") | ||
| 188 | |||
| 189 | @trigger_group.command( | ||
| 190 | description="Remove a trigger or response from the name", | ||
| 191 | ) | ||
| 192 | async def remove( | ||
| 193 | self, | ||
| 194 | ctx: discord.ApplicationContext, | ||
| 195 | name = discord.Option( | ||
| 196 | str, | ||
| 197 | "the name of the trigger", | ||
| 198 | autocomplete= discord.utils.basic_autocomplete(get_type), | ||
| 199 | required=True | ||
| 200 | ), | ||
| 201 | trigger = discord.Option( | ||
| 202 | str, | ||
| 203 | "the trigger to remove from the name", | ||
| 204 | required=False | ||
| 205 | ), | ||
| 206 | response = discord.Option( | ||
| 207 | str, | ||
| 208 | "the response to remove from the name", | ||
| 209 | required=False | ||
| 210 | ) | ||
| 211 | ): | ||
| 212 | with get_connection() as conn: | ||
| 213 | c = conn.cursor() | ||
| 214 | c.execute("SELECT id FROM triggers WHERE name=?", (name,)) | ||
| 215 | row = c.fetchone() | ||
| 216 | if not row: | ||
| 217 | await ctx.respond("Trigger not found.") | ||
| 218 | logger.warning(f"Trigger not found: {name}") | ||
| 219 | return | ||
| 220 | trigger_id = row[0] | ||
| 221 | if trigger: | ||
| 222 | c.execute("DELETE FROM trigger_keywords WHERE trigger_id=? AND keyword=?", (trigger_id, trigger)) | ||
| 223 | if response: | ||
| 224 | c.execute("DELETE FROM trigger_responses WHERE trigger_id=? AND response_text=?", (trigger_id, response)) | ||
| 225 | conn.commit() | ||
| 226 | |||
| 227 | await ctx.respond("trigger removed") | ||
| 228 | logger.info(f"Trigger removed: {name}") | ||
| 229 | |||
| 230 | @trigger_group.command( | ||
| 231 | description="Delete the whole trigger", | ||
| 232 | ) | ||
| 233 | async def delete( | ||
| 234 | self, | ||
| 235 | ctx: discord.ApplicationContext, | ||
| 236 | name = discord.Option( | ||
| 237 | str, | ||
| 238 | "the name of the trigger", | ||
| 239 | autocomplete= discord.utils.basic_autocomplete(get_type), | ||
| 240 | required=True | ||
| 241 | ) | ||
| 242 | ): | ||
| 243 | with get_connection() as conn: | ||
| 244 | c = conn.cursor() | ||
| 245 | c.execute("SELECT id FROM triggers WHERE name=?", (name,)) | ||
| 246 | row = c.fetchone() | ||
| 247 | if not row: | ||
| 248 | await ctx.respond("Trigger not found.") | ||
| 249 | return | ||
| 250 | trigger_id = row[0] | ||
| 251 | c.execute("DELETE FROM trigger_keywords WHERE trigger_id=?", (trigger_id,)) | ||
| 252 | c.execute("DELETE FROM trigger_responses WHERE trigger_id=?", (trigger_id,)) | ||
| 253 | c.execute("DELETE FROM triggers WHERE id=?", (trigger_id,)) | ||
| 254 | conn.commit() | ||
| 255 | |||
| 256 | await ctx.respond("trigger deleted") | ||
| 257 | logger.info(f"Trigger deleted: {name}") | ||
| 258 | |||
| 259 | |||
| 260 | @trigger_group.command( | ||
| 261 | description="List all triggers", | ||
| 262 | ) | ||
| 263 | async def list( | ||
| 264 | self, | ||
| 265 | ctx: discord.ApplicationContext, | ||
| 266 | name = discord.Option( | ||
| 267 | str, | ||
| 268 | "the name of the trigger", | ||
| 269 | autocomplete=discord.utils.basic_autocomplete(get_type), | ||
| 270 | required=True | ||
| 271 | ) | ||
| 272 | ): | ||
| 273 | with get_connection() as conn: | ||
| 274 | c = conn.cursor() | ||
| 275 | if name: | ||
| 276 | c.execute("SELECT id FROM triggers WHERE name=?", (name,)) | ||
| 277 | row = c.fetchone() | ||
| 278 | if not row: | ||
| 279 | await ctx.respond("Trigger not found.") | ||
| 280 | logger.warning(f"Trigger not found: {name}") | ||
| 281 | return | ||
| 282 | trigger_id = row[0] | ||
| 283 | c.execute("SELECT keyword FROM trigger_keywords WHERE trigger_id=?", (trigger_id,)) | ||
| 284 | triggers = [row[0] for row in c.fetchall()] | ||
| 285 | c.execute("SELECT response_text FROM trigger_responses WHERE trigger_id=?", (trigger_id,)) | ||
| 286 | responses = [row[0] for row in c.fetchall()] | ||
| 287 | trigger_list = "\n\n".join(triggers) | ||
| 288 | response_list = "\n\n".join(responses) | ||
| 289 | message = f"**Trigger: {name}**\n**Triggers:**\n{trigger_list}\n**Responses:**\n{response_list}" | ||
| 290 | await ctx.respond(message) | ||
| 291 | logger.info(f"Trigger listed: {name}") | ||
| 292 | else: | ||
| 293 | c.execute("SELECT id, name FROM triggers") | ||
| 294 | messages = [] | ||
| 295 | for trigger_id, name in c.fetchall(): | ||
| 296 | c.execute("SELECT keyword FROM trigger_keywords WHERE trigger_id=?", (trigger_id,)) | ||
| 297 | triggers = ", ".join([row[0] for row in c.fetchall()]) | ||
| 298 | c.execute("SELECT response_text FROM trigger_responses WHERE trigger_id=?", (trigger_id,)) | ||
| 299 | responses = ", ".join([row[0] for row in c.fetchall()]) | ||
| 300 | messages.append(f"**{name}**\nTriggers: {triggers}\nResponses: {responses}") | ||
| 301 | await ctx.respond("\n\n".join(messages)) | ||
| 302 | logger.info(f"Triggers listed") | ||
| 303 | |||
| 304 | def setup(bot): | ||
| 305 | bot.add_cog(Trigger(bot)) \ No newline at end of file | ||
