diff --git a/nomen/db.py b/nomen/db.py index d200e14..b9f69f3 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 ); @@ -33,22 +34,34 @@ 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; 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, + target INTEGER NOT NULL, -- channel or user id 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 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); """ 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", diff --git a/nomen/notifications.py b/nomen/notifications.py index 3afdb4d..48e5de1 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 @@ -72,7 +73,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 +89,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 """ @@ -135,6 +138,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, @@ -297,17 +303,32 @@ 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() - 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