From 8e6ac728ed8786dca03c2bd84d90885a7cbb79cd Mon Sep 17 00:00:00 2001 From: Infinidoge Date: Sat, 28 Dec 2024 03:56:01 -0500 Subject: [PATCH 1/9] setup foreign key constraints --- nomen/db.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/nomen/db.py b/nomen/db.py index d200e14..d28c3c9 100644 --- a/nomen/db.py +++ b/nomen/db.py @@ -13,11 +13,12 @@ schema = """ PRAGMA user_version = 1; PRAGMA main.synchronous = NORMAL; +PRAGMA foreign_keys = ON; CREATE TABLE keywords ( guild_id INTEGER NOT NULL, keyword TEXT NOT NULL, - user_id INTEGER NOT NULL, + user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE, regex INTEGER NOT NULL DEFAULT 0 CHECK(regex IN (0, 1)), count INTEGER NOT NULL DEFAULT 0 ); @@ -38,17 +39,21 @@ CREATE TABLE users ( WITHOUT ROWID; CREATE TABLE user_ignores ( - user_id INTEGER NOT NULL, + user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE, guild_id INTEGER NOT NULL, target INTEGER NOT NULL, PRIMARY KEY (user_id, guild_id, target) ); CREATE TABLE user_blocks ( - user_id INTEGER NOT NULL, + user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE, target INTEGER NOT NULL, PRIMARY KEY (user_id, target) ); + +CREATE INDEX keywords_index ON keywords(user_id); +CREATE INDEX user_ignores_index ON user_ignores(user_id); +CREATE INDEX user_blocks_index ON user_blocks(user_id); """ From 127e02f7e76d68b853a091f77e93bfa0c6961a15 Mon Sep 17 00:00:00 2001 From: Infinidoge Date: Sat, 28 Dec 2024 03:56:16 -0500 Subject: [PATCH 2/9] add ignore_active setting to db --- nomen/db.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nomen/db.py b/nomen/db.py index d28c3c9..7985a28 100644 --- a/nomen/db.py +++ b/nomen/db.py @@ -34,7 +34,8 @@ CREATE TABLE users ( disabled INTEGER NOT NULL DEFAULT 0 CHECK(disabled IN (0, 1)), use_embed INTEGER NOT NULL DEFAULT 1 CHECK(use_embed IN (0, 1)), notify_self INTEGER NOT NULL DEFAULT 0 CHECK(notify_self IN (0, 1)), - bots_notify INTEGER NOT NULL DEFAULT 0 CHECK(bots_notify IN (0, 1)) + bots_notify INTEGER NOT NULL DEFAULT 0 CHECK(bots_notify IN (0, 1)), + ignore_active INTEGER NOT NULL DEFAULT 0 CHECK(bots_notify IN (0, 1)) ) WITHOUT ROWID; From a184ba523980919b26afdc13bd8f3691458276a8 Mon Sep 17 00:00:00 2001 From: Infinidoge Date: Sat, 28 Dec 2024 03:56:29 -0500 Subject: [PATCH 3/9] note that user_ignores target is channel or user id --- nomen/db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nomen/db.py b/nomen/db.py index 7985a28..68ef619 100644 --- a/nomen/db.py +++ b/nomen/db.py @@ -42,7 +42,7 @@ WITHOUT ROWID; CREATE TABLE user_ignores ( user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE, guild_id INTEGER NOT NULL, - target INTEGER NOT NULL, + target INTEGER NOT NULL, -- channel or user id PRIMARY KEY (user_id, guild_id, target) ); From 3fed1603030655be2d202b9fdd37f8393df277ef Mon Sep 17 00:00:00 2001 From: Infinidoge Date: Sat, 28 Dec 2024 03:56:38 -0500 Subject: [PATCH 4/9] add user_pauses table to db --- nomen/db.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nomen/db.py b/nomen/db.py index 68ef619..b9f69f3 100644 --- a/nomen/db.py +++ b/nomen/db.py @@ -52,9 +52,16 @@ CREATE TABLE user_blocks ( PRIMARY KEY (user_id, target) ); +CREATE TABLE user_pauses ( + user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE, + guild_id INTEGER NOT NULL, + PRIMARY KEY (user_id, guild_id) +); + CREATE INDEX keywords_index ON keywords(user_id); CREATE INDEX user_ignores_index ON user_ignores(user_id); CREATE INDEX user_blocks_index ON user_blocks(user_id); +CREATE INDEX user_pauses_index ON user_pauses(user_id); """ From 07b4679238907565b42f4b08d1c93148fe9b6f21 Mon Sep 17 00:00:00 2001 From: Infinidoge Date: Sat, 28 Dec 2024 03:56:53 -0500 Subject: [PATCH 5/9] add helper for toggling user settings --- nomen/main.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nomen/main.py b/nomen/main.py index 241f258..214f69d 100644 --- a/nomen/main.py +++ b/nomen/main.py @@ -89,6 +89,13 @@ class Nomen(Bot): await super().close() await self.db.close() + async def user_toggle(self, user_id, item): + await self.db.execute( + "REPLACE INTO users (user_id, {item}) VALUES(:user_id, iff((SELECT {item} FROM users WHERE user_id=:user_id)), 0, 1)", + {"user_id": user_id}, + ) + await self.db.commit() + bot = Nomen( description="Keeper of Names", From bd6466ff5f9e4a6f02e515f59e6c19a65fe1441f Mon Sep 17 00:00:00 2001 From: Infinidoge Date: Sat, 28 Dec 2024 03:57:27 -0500 Subject: [PATCH 6/9] add sqlite optimization hints and handle all ignores --- nomen/notifications.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/nomen/notifications.py b/nomen/notifications.py index 3afdb4d..884f04e 100644 --- a/nomen/notifications.py +++ b/nomen/notifications.py @@ -72,7 +72,9 @@ async def handle_triggers(ctx, message): "is_bot": ctx.author.bot, } - disabled = await fetch_exists(ctx.bot.db, "SELECT * FROM users WHERE user_id=:author AND disabled IS 1", params) + disabled = await fetch_exists( + ctx.bot.db, "SELECT * FROM users WHERE user_id=:author AND unlikely(disabled IS 1)", params + ) if disabled: log.debug(f"User {ctx.author} ({ctx.author.id}) opted out") @@ -86,17 +88,17 @@ async def handle_triggers(ctx, message): SELECT keyword, user_id, use_embed, row_number() over (partition by user_id) n FROM keywords LEFT JOIN users USING (user_id) - WHERE disabled IS NOT 1 -- Don't notify users who opted out - AND (notify_self IS 1 OR user_id IS NOT :author) -- Don't notify author unless wanted - AND (bots_notify IS 1 OR :is_bot IS NOT 1) -- Don't notify from bots unless wanted - AND :author NOT IN ( -- Don't notify if... - SELECT target FROM user_ignores -- Author is ignored in this guild + WHERE likely(disabled IS NOT 1) -- Don't notify users who opted out + AND (unlikely(notify_self IS 1) OR user_id IS NOT :author) -- Don't notify author unless wanted + AND (unlikely(bots_notify IS 1) OR :is_bot IS NOT 1) -- Don't notify from bots unless wanted + AND likely(NOT EXISTS(SELECT * FROM ( -- Don't notify if... + SELECT target FROM user_ignores -- Author or channel is ignored in this guild WHERE user_id=user_id AND guild_id=guild_id UNION SELECT target FROM user_blocks -- Author is blocked WHERE user_id=user_id - ) - AND guild_id=:guild AND contains(:content, keyword, regex) + ) WHERE target IN (:author, :channel))) + AND guild_id=:guild AND unlikely(contains(:content, keyword, regex)) ) WHERE n=1 """ From 88e967eb1a04595428cfcbc5992b0094534d3519 Mon Sep 17 00:00:00 2001 From: Infinidoge Date: Sat, 28 Dec 2024 03:57:47 -0500 Subject: [PATCH 7/9] always insert user into table to maintain db constraints --- nomen/notifications.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nomen/notifications.py b/nomen/notifications.py index 884f04e..9bb9f8e 100644 --- a/nomen/notifications.py +++ b/nomen/notifications.py @@ -137,6 +137,9 @@ class Notifications(Cog): await handle_triggers(ctx, message) + async def cog_before_invoke(ctx): + await ctx.bot.db.execute("INSERT OR IGNORE INTO users (userid) VALUE(?)", (ctx.author.id,)) + @group( aliases=["kw", "notification", "notifications", "notif", "noti"], invoke_without_command=True, From e9eb68285eda7a888ea53e9f43dd921280cddbf0 Mon Sep 17 00:00:00 2001 From: Infinidoge Date: Sat, 28 Dec 2024 04:05:10 -0500 Subject: [PATCH 8/9] implement ignore and add stub for unignore --- nomen/notifications.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/nomen/notifications.py b/nomen/notifications.py index 9bb9f8e..32ecc12 100644 --- a/nomen/notifications.py +++ b/nomen/notifications.py @@ -1,8 +1,9 @@ import logging from asyncio import TaskGroup from textwrap import indent +from typing import Union -from disnake import Embed +from disnake import Embed, Member, TextChannel from disnake.ext.commands import Cog, group, guild_only from .utils import can_view, confirm, fetch_exists, fetch_unpacked, test_keyword @@ -307,12 +308,21 @@ class Notifications(Cog): @keyword.group(invoke_without_command=True) @guild_only() - async def ignore(self, ctx, channel): + async def ignore(self, ctx, target: Union[TextChannel, Member]): + await ctx.bot.db.execute("INSERT INTO user_ignores VALUES(?, ?, ?)", (ctx.author.id, ctx.guild.id, target.id)) + await ctx.bot.db.commit() + await ctx.send(f"Now ignoring {target}") + + @keyword.command() + @guild_only() + async def uningnore(self, ctx, target: Union[TextChannel, Member]): pass - # TODO: Ignore channels and users + # TODO: Unignore @ignore.command() @guild_only() async def active(self, ctx): - pass - # TODO: Ignore active channel + await ctx.bot.user_toggle(ctx.author.id, "ignore_active") + await ctx.bot.send("Toggled ignore active channel") # TODO: Send current state + + # TODO: Ignore active channel in handler From 49a7ac9fd7f19f0578ea3fe0dd3fe3f556fd854a Mon Sep 17 00:00:00 2001 From: Infinidoge Date: Sat, 28 Dec 2024 04:05:26 -0500 Subject: [PATCH 9/9] implement pause toggle --- nomen/notifications.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/nomen/notifications.py b/nomen/notifications.py index 32ecc12..48e5de1 100644 --- a/nomen/notifications.py +++ b/nomen/notifications.py @@ -303,8 +303,14 @@ class Notifications(Cog): @keyword.command() @guild_only() async def pause(self, ctx): - pass - # TODO: Pause guild notifications + params = (ctx.author.id, ctx.guild_id) + if await fetch_exists("SELECT * FROM user_pauses WHERE user_id=? AND guild_id=?"): + await ctx.bot.db.execute("DELETE FROM user_pauses WHERE user_id=? AND guild_id=?", params) + await ctx.bot.send(f"Resumed notifications in {ctx.guild}") + else: + await ctx.bot.db.execute("INSERT INTO user_pauses VALUES(?, ?)", params) + await ctx.bot.send(f"Paused notifications in {ctx.guild}") + # TODO: Pause guild notifications in handler @keyword.group(invoke_without_command=True) @guild_only()