diff --git a/.gitignore b/.gitignore index 7a5efd3..564bdc2 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ result # Python .ruff_cache .ropeproject -**/__pycache__/ # Runtime *.sqlite3 diff --git a/nomen/db.py b/nomen/db.py index 6eb708d..b9f69f3 100644 --- a/nomen/db.py +++ b/nomen/db.py @@ -12,6 +12,9 @@ log.setLevel(logging.INFO) 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, @@ -32,7 +35,7 @@ CREATE TABLE users ( 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)), - ignore_active INTEGER NOT NULL DEFAULT 0 CHECK(ignore_active IN (0, 1)) + ignore_active INTEGER NOT NULL DEFAULT 0 CHECK(bots_notify IN (0, 1)) ) WITHOUT ROWID; @@ -70,37 +73,6 @@ def run_db_migrations(db_file): log.debug("Finished running automatic migration") -async def fetch_singleton(db, sql, params=None): - """ - Fetch an object from the database, with the assumption that the result is 1 row by 1 column - """ - - result = await db.execute_fetchall(f"{sql} LIMIT 1", params) - return result[0][0] - - -async def fetch_exists(db, sql, params=None): - return await fetch_singleton(db, f"SELECT EXISTS({sql})", params) - - -async def fetch_unpacked(db, sql, params=None): - cur = await db.cursor() - cur.row_factory = lambda cursor, row: row[0] - cur = await cur.execute(sql, params) - return await cur.fetchall() - - -log.debug("Monkeypatching in helpers") -aiosqlite.Connection.fetch_singleton = fetch_singleton -aiosqlite.Connection.fetch_exists = fetch_exists -aiosqlite.Connection.fetch_unpacked = fetch_unpacked - - -class Row(sqlite3.Row): - def __repr__(self): - return f"Row<{repr(dict(self))}>" - - async def setup_db(db_file): log.debug(f"Connecting to {db_file}") db = await aiosqlite.connect(db_file) @@ -108,13 +80,8 @@ async def setup_db(db_file): log.debug("Running start script") await db.executescript(""" PRAGMA optimize(0x10002); - PRAGMA main.synchronous = NORMAL; - PRAGMA foreign_keys = ON; """) - log.debug("Setting row factory") - db.row_factory = Row - log.debug("Adding contains function") await db.create_function("contains", 3, contains, deterministic=True) diff --git a/nomen/main.py b/nomen/main.py index ed8e449..214f69d 100644 --- a/nomen/main.py +++ b/nomen/main.py @@ -1,7 +1,6 @@ import io import logging import os -import pprint import sys import textwrap import traceback @@ -9,7 +8,7 @@ from contextlib import redirect_stdout from disnake import Guild, Intents, Message from disnake.ext import commands -from disnake.ext.commands import Bot, Paginator +from disnake.ext.commands import Bot from dotenv import find_dotenv, load_dotenv from .db import run_db_migrations, setup_db @@ -90,11 +89,11 @@ class Nomen(Bot): await super().close() await self.db.close() - async def get_setting(self, user_id, setting): - return await self.db.fetch_singleton(f"SELECT {setting} FROM users WHERE user_id=?", (user_id,)) - - async def set_setting(self, user_id, setting, value: bool): - await self.db.execute(f"REPLACE INTO users (user_id, {setting}) VALUES(?, ?)", (user_id, value)) + 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() @@ -121,68 +120,18 @@ async def on_ready(): log.info(bot.user.id) log.info("-------------") - bot.app_info = await bot.application_info() - bot.owner = bot.app_info.owner - @bot.event async def on_command_error(ctx, error): if isinstance(error, commands.CommandNotFound): return - if isinstance( - error, - ( - commands.UserInputError, - commands.CheckFailure, - ), - ): + if isinstance(error, commands.NoPrivateMessage): await ctx.send(error) return - log.error(error, exc_info=error) - timestamp = int(ctx.message.created_at.timestamp()) - paginator = Paginator() - ptraceback = "".join(traceback.format_exception(error)) - for line in ptraceback.split("\n"): - paginator.add_line(line) - await ctx.bot.owner.send( - f"# Command Error Occurred: {error}\n" - f"## Context\n" - f"**From:** {ctx.author.mention} ({ctx.author.name})\n" - f", | [Jump to Command]({ctx.message.jump_url}) | <#{ctx.channel.id}>\n" - f"## Traceback" - ) - for page in paginator.pages: - await ctx.bot.owner.send(page) - await ctx.send( - f"Error: {error}\n\n**Note:** Infinidoge (Developer) has already been sent a notification that an error occurred and will come investigate when available." - ) - - -@bot.event -async def on_error(event, *args, **kwargs): - pargs = pprint.pformat(args) - pkwargs = pprint.pformat(kwargs) - - try: - paginator = Paginator() - for line in traceback.format_exc().split("\n"): - paginator.add_line(line) - await bot.owner.send( - f"# Error Occurred: Event `{event[:100]}`\n" - f"## args\n" - f"```\n{pargs}\n```\n" - f"## kwargs\n" - f"```\n{pkwargs}\n```\n" - f"## Traceback" - ) - for page in paginator.pages: - await bot.owner.send(page) - except: # Don't want recursive errors - pass - - log.error(f"Error in event {event}.\nargs={pargs}\nkwargs={pkwargs}", exc_info=sys.exc_info()) + log.exception(error) + await ctx.send(error) @bot.command(hidden=True) @@ -209,13 +158,13 @@ async def sql(ctx, *, query): @bot.command(hidden=True, name="eval") @commands.is_owner() -async def admin_eval(ctx, *, body: str): +async def admin_eval(self, ctx, *, body: str): env = { - "bot": ctx.bot, + "bot": self.bot, "ctx": ctx, - "channel": ctx.channel, - "author": ctx.author, - "guild": ctx.guild, + "channel": ctx.message.channel, + "author": ctx.message.author, + "guild": ctx.message.guild, "message": ctx.message, } @@ -237,11 +186,7 @@ async def admin_eval(ctx, *, body: str): ret = await func() except Exception: value = stdout.getvalue() - ret = traceback.format_exc() - try: - await ctx.message.add_reaction("\u2705") - except: - pass + await ctx.send("```py\n{}{}\n```".format(value, traceback.format_exc())) else: value = stdout.getvalue() try: @@ -249,18 +194,11 @@ async def admin_eval(ctx, *, body: str): except: pass - paginator = Paginator(prefix="```py", suffix="```") - if value: - for line in str(value).split("\n"): - paginator.add_line(line) - paginator.close_page() - if ret: - for line in str(ret).split("\n"): - paginator.add_line(line) - paginator.close_page() - - for page in paginator.pages: - await ctx.send(page) + if ret is None: + if value: + await ctx.send("```py\n{}\n```".format(value)) + else: + await ctx.send("```py\n{}{}\n```".format(value, ret)) @bot.command() @@ -269,7 +207,8 @@ async def ping(ctx): @bot.command() -@commands.has_guild_permissions(manage_guild=True) +@commands.guild_only() +@commands.is_owner() async def prefix(ctx, prefix=None): if prefix is None: prefix = await ctx.bot.get_guild_prefix(ctx.guild) @@ -287,7 +226,8 @@ async def prefix(ctx, prefix=None): def run(): try: - run_db_migrations(DB_FILE) + if run_db_migrations(DB_FILE): + log.info(f"Migrated DB {DB_FILE}") except RuntimeError: pass else: diff --git a/nomen/notifications.py b/nomen/notifications.py index f1a0bf3..48e5de1 100644 --- a/nomen/notifications.py +++ b/nomen/notifications.py @@ -3,11 +3,10 @@ from asyncio import TaskGroup from textwrap import indent from typing import Union -from disnake import Embed, Member, Thread, User -from disnake.abc import GuildChannel +from disnake import Embed, Member, TextChannel from disnake.ext.commands import Cog, group, guild_only -from .utils import can_view, confirm, test_keyword +from .utils import can_view, confirm, fetch_exists, fetch_unpacked, test_keyword log = logging.getLogger("nomen.notifications") log.setLevel(logging.DEBUG) @@ -22,19 +21,6 @@ class NotifierLogAdapter(logging.LoggerAdapter): ) -class KeywordError(Exception): - def __init__(self, msg=None, dm_msg=None, *args): - self.msg = msg - self.dm_msg = dm_msg - super().__init__(msg, dm_msg, *args) - - async def send(self, ctx): - if self.msg: - await ctx.send(self.msg + " (check DMs)") - if self.dm_msg: - await ctx.author.send(self.dm_msg) - - async def handle_notification(db_updates, ctx, message, keyword, user_id, use_embed): """ Async task to dispatch a notification @@ -82,18 +68,13 @@ async def handle_triggers(ctx, message): params = { "author": ctx.author.id, "channel": ctx.channel.id, - "category": ctx.channel.category_id, "guild": ctx.guild.id, "content": message.content, "is_bot": ctx.author.bot, - "parent": 0, } - if isinstance(ctx.channel, Thread) and ctx.channel.parent: - params["parent"] = ctx.channel.parent.id - - disabled = await ctx.bot.db.fetch_exists( - "SELECT * FROM users WHERE user_id=:author AND unlikely(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: @@ -113,12 +94,11 @@ async def handle_triggers(ctx, message): 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 + WHERE user_id=user_id AND guild_id=guild_id UNION SELECT target FROM user_blocks -- Author is blocked WHERE user_id=user_id - ) WHERE target IN (:author, :channel, :category, :parent))) - AND likely(NOT EXISTS(SELECT * FROM user_pauses WHERE user_id=user_id AND guild_id=:guild)) + ) WHERE target IN (:author, :channel))) AND guild_id=:guild AND unlikely(contains(:content, keyword, regex)) ) WHERE n=1 @@ -158,8 +138,8 @@ class Notifications(Cog): await handle_triggers(ctx, message) - async def cog_before_invoke(self, ctx): - await ctx.bot.db.execute("INSERT OR IGNORE INTO users (user_id) VALUES(?)", (ctx.author.id,)) + 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"], @@ -171,47 +151,64 @@ class Notifications(Cog): """ await ctx.send_help(self.keyword) - async def _check_conflicts(self, guild_id, user_id, keyword): - return await self.bot.db.fetch_unpacked( - "SELECT keyword FROM keywords WHERE guild_id=? AND user_id=? AND contains(?, keyword, regex)", - (guild_id, user_id, keyword), - ) - - async def _check_redundants(self, guild_id, user_id, keyword, regex): - return await self.bot.db.fetch_unpacked( - "SELECT keyword FROM keywords WHERE guild_id=? AND user_id=? AND contains(keyword, ?, ?)", - (guild_id, user_id, keyword, regex), - ) - - async def add_keyword(self, guild_id: int, user_id: int, keyword: str, regex: bool, initial_count: int = 0): + async def _add_keyword(self, ctx, keyword: str, regex: bool): log.debug( - f"Adding {'regex' if regex else 'keyword'}: {keyword} of {user_id} on {guild_id} with count {initial_count}" + f"Adding {'regex' if regex else 'keyword'}: {keyword} of {ctx.author} ({ctx.author.id}) on {ctx.guild} ({ctx.guild.id})" ) + params = { + "keyword": keyword, + "guild_id": ctx.guild.id, + "user_id": ctx.author.id, + "regex": regex, + } + + existing = """ + SELECT keyword FROM keywords + WHERE guild_id=:guild_id + AND user_id=:user_id + AND contains(:keyword, keyword, regex) + """ + redundant = """ + SELECT keyword FROM keywords + WHERE guild_id=:guild_id + AND user_id=:user_id + AND contains(keyword, :keyword, :regex) + """ + if test_keyword(keyword, regex): log.debug("Keyword too common") - raise KeywordError(f"{'Regex' if regex else 'Keyword'} matches a word that is too common") + await ctx.send(f"{'Regex' if regex else 'Keyword'} matches a word that is too common") + return - if conflicts := await self._check_conflicts(guild_id, user_id, keyword): + conflicts = await fetch_unpacked(ctx.bot.db, existing, params) + + if conflicts: log.debug("Keyword conflicts with existing keyword") - raise KeywordError( - f"Any instance of `{keyword}` would be matched by existing keywords", - f"Conflicts with keyword `{keyword}`:\n" + "\n".join(f"- `{conflict}`" for conflict in conflicts), + await ctx.send(f"Any instance of `{keyword}` would be matched by existing keywords (check DMs)") + await ctx.author.send( + f"Conflicts with keyword `{keyword}`:\n" + "\n".join(f"- `{conflict}`" for conflict in conflicts) ) + return - if redundants := await self._check_redundants(guild_id, user_id, keyword, regex): + conflicts = await fetch_unpacked(ctx.bot.db, redundant, params) + + if conflicts: log.debug("Keyword renders existing redundant") - raise KeywordError( - f"Adding `{keyword}` will cause existing keywords to never match", - f"Keywords redundant from `{keyword}`:\n" + "\n".join(f" - `{conflict}`" for conflict in redundants), + await ctx.send(f"Adding `{keyword}` will cause existing keywords to never match (check DMs)") + await ctx.author.send( + f"Keywords redundant from `{keyword}`:\n" + "\n".join(f" - `{conflict}`" for conflict in conflicts) ) + return log.debug("Keyword valid, adding") - await self.bot.db.execute( - "INSERT INTO keywords VALUES (?, ?, ?, ?, ?)", (guild_id, keyword, user_id, regex, initial_count) + await ctx.bot.db.execute( + "INSERT INTO keywords (guild_id, keyword, user_id, regex) VALUES (:guild_id, :keyword, :user_id, :regex)", + params, ) - await self.bot.db.commit() + await ctx.bot.db.commit() + await ctx.send(f"Added `{keyword}` to your list of keywords") @keyword.command() @guild_only() @@ -222,12 +219,7 @@ class Notifications(Cog): Use quotes for a keyword with spaces! """ - try: - await self.add_keyword(ctx.guild.id, ctx.author.id, keyword, False) - except KeywordError as e: - await e.send(ctx) - else: - await ctx.send(f"Added `{keyword}` to your list of keywords") + await self._add_keyword(ctx, keyword, False) @keyword.command() @guild_only() @@ -238,13 +230,7 @@ class Notifications(Cog): Use quotes for a regex with spaces! """ - # TODO: Add regex names to make notifications cleaner - try: - await self.add_keyword(ctx.guild.id, ctx.author.id, keyword, True) - except KeywordError as e: - await e.send(ctx) - else: - await ctx.send(f"Added regex `{keyword}` to your list of keywords") + await self._add_keyword(ctx, keyword, True) @keyword.command(aliases=["delete", "del"]) @guild_only() @@ -272,9 +258,11 @@ class Notifications(Cog): log.debug(f"Listing keywords: {ctx.author} ({ctx.author.id}) on {ctx.guild} ({ctx.guild.id})") - keywords = await ctx.bot.db.execute_fetchall( - "SELECT keyword, count, regex FROM keywords WHERE guild_id=? AND user_id=?", (ctx.guild.id, ctx.author.id) - ) + async with ctx.bot.db.execute( + "SELECT keyword, count, regex FROM keywords WHERE guild_id=? AND user_id=?", + (ctx.guild.id, ctx.author.id), + ) as cur: + keywords = await cur.fetchall() embed = Embed( description="\n".join( @@ -315,110 +303,32 @@ class Notifications(Cog): @keyword.command() @guild_only() async def pause(self, ctx): - """ - Pause notifications in this guild - """ - - params = (ctx.author.id, ctx.guild.id) - if await ctx.bot.db.fetch_exists("SELECT * FROM user_pauses WHERE user_id=? AND guild_id=?", params): - await ctx.send(f"Notifications in {ctx.guild} are already paused") + 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.db.commit() - await ctx.send(f"Paused notifications in {ctx.guild}") + 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, 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 resume(self, ctx): - """ - Resume notifications in this guild - """ + async def uningnore(self, ctx, target: Union[TextChannel, Member]): + pass + # TODO: Unignore - params = (ctx.author.id, ctx.guild_id) - if await ctx.bot.db.fetch_exists("SELECT * FROM user_pauses WHERE user_id=? AND guild_id=?", params): - await ctx.bot.db.execute("DELETE FROM user_pauses WHERE user_id=? AND guild_id=?", params) - await ctx.bot.db.commit() - await ctx.send(f"Resumed notifications in {ctx.guild}") - else: - await ctx.send(f"Notifications in {ctx.guild} aren't paused") - - @keyword.command() + @ignore.command() @guild_only() - async def ignore(self, ctx, target: Union[GuildChannel, Member]): - """ - Ignore a channel or member in this guild + async def active(self, ctx): + await ctx.bot.user_toggle(ctx.author.id, "ignore_active") + await ctx.bot.send("Toggled ignore active channel") # TODO: Send current state - This prevents you from receiving any notifications from that channel or user in this guild - """ - - params = (ctx.author.id, ctx.guild.id, target.id) - if await ctx.bot.db.fetch_exists( - "SELECT * FROM user_ignores WHERE user_id=? AND guild_id=? AND target=?", params - ): - await ctx.send(f"Target `{repr(target)}` already ignored") - return - - await ctx.bot.db.execute("INSERT INTO user_ignores VALUES(?, ?, ?)", params) - await ctx.bot.db.commit() - await ctx.send(f"Now ignoring `{repr(target)}`") - - @keyword.command() - @guild_only() - async def unignore(self, ctx, target: Union[GuildChannel, Member]): - """ - Unignore a channel or member in this guild - - This allows you to receive notifications from that channel or user again - """ - - params = (ctx.author.id, ctx.guild.id, target.id) - if not await ctx.bot.db.fetch_exists( - "SELECT * FROM user_ignores WHERE user_id=? AND guild_id=? AND target=?", params - ): - await ctx.send(f"Target `{repr(target)}` is not currently being ignored") - return - - await ctx.bot.db.execute("DELETE FROM user_ignores WHERE user_id=? AND guild_id=? AND target=?", params) - await ctx.bot.db.commit() - await ctx.send(f"No longer ignoring `{repr(target)}`") - - @keyword.command() - async def block(self, ctx, target: User): - """ - Block a user from triggering your notifications - - This prevents you from receiving notifications from that user in any guild - NOTE: They will still receive notifications from you - """ - await ctx.message.delete(delay=0) # Delete invoking message if able to - - params = (ctx.author.id, ctx.target.id) - if await ctx.bot.db.fetch_exists("SELECT * FROM user_blocks WHERE user_id=? AND target=?", params): - await ctx.author.send(f"Target {target} already blocked") - return - - await ctx.bot.db.execute( - "INSERT INTO user_blocks VALUES(?, ?)", - (ctx.author.id, target.id), - ) - await ctx.bot.db.commit() - await ctx.author.send(f"Blocked {target}") - - @keyword.command() - async def unblock(self, ctx, target: User): - """ - Unblock a user - - This allows you to receive notifications from that user again - """ - - await ctx.message.delete(delay=0) # Delete invoking message if able to - - params = (ctx.author.id, ctx.target.id) - if await ctx.bot.db.fetch_exists("SELECT * FROM user_blocks WHERE user_id=? AND target=?", params): - await ctx.author.send(f"Target {target} already blocked") - return - - await ctx.bot.db.execute("DELETE FROM user_ignores WHERE user_id=? AND target=?", params) - await ctx.bot.db.commit() - await ctx.author.send(f"Unblocked {target}") + # TODO: Ignore active channel in handler diff --git a/nomen/settings.py b/nomen/settings.py index b86434c..ce6c5fa 100644 --- a/nomen/settings.py +++ b/nomen/settings.py @@ -1,23 +1,12 @@ -import json import logging -from typing import Literal -import disnake -import re2 as re from disnake.ext.commands import Cog, group, guild_only -from .notifications import KeywordError from .utils import confirm log = logging.getLogger("nomen.settings") log.setLevel(logging.DEBUG) -DUSTY_DATA_LINE = re.compile(r"^(.+) – notified (\d+) times$") - - -class InvalidImportFormat(Exception): - pass - class Settings(Cog): def __init__(self, bot): @@ -26,7 +15,7 @@ class Settings(Cog): @group(invoke_without_command=True) async def nomen(self, ctx): """ - Managing your data in Nomen + Settings for Nomen """ await ctx.send_help(self.nomen) @@ -46,64 +35,24 @@ You may want to `{ctx.clean_prefix}nomen export` first""" to_purge = await confirm(ctx, msg) if to_purge: - # Foreign key constraints will automatically cascade to delete dependent data - await self.bot.db.execute("DELETE FROM users WHERE user_id=?", (ctx.author.id,)) + await self.bot.db.execute( + """ + DELETE FROM keywords WHERE user_id=?; + DELETE FROM users WHERE user_id=?; + DELETE FROM user_ignores WHERE user_id=?; + """, + (ctx.author.id,), + ) await self.bot.db.commit() await ctx.send("Deleted all user data.") else: await ctx.send("Cancelled.") - async def import_json(self, ctx, data): - try: - data = json.loads(data) - except json.JSONDecodeError as e: - raise InvalidImportFormat from e - await ctx.send("Importing JSON") - - async def import_dusty(self, ctx, data): - lines = map(str.strip, data.split("\n")) - to_add = [] - - for line in lines: - if match := DUSTY_DATA_LINE.fullmatch(line): - to_add.append((match.group(1), int(match.group(2)))) - else: - raise InvalidImportFormat - - await ctx.send(f"Importing from Dusty notification list to {ctx.guild}...") - - added = [] - errors = [] - - async with ctx.typing(): - notifications = self.bot.get_cog("Notifications") - for keyword, count in to_add: - try: - await notifications.add_keyword(ctx.guild.id, ctx.author.id, keyword, False, count) - added.append(keyword) - except KeywordError as e: - errors.append(e) - - if not errors: - await ctx.send("Successfully imported all keywords!") - else: - await ctx.send( - f"Import successful, with some errors\n\n" - f"Sucessfully imported:\n{"\n".join(f"- {keyword}" for keyword in added)}\n" - f"Errors (check PMs):\n{"\n".join(f"- {e.msg}" for e in errors)}\n\n" - f"If you want to import erroring keywords, delete conflicting keywords and modify the list to only include the ones that failed to import, and run the command again." - ) - for e in errors: - await ctx.author.send(e.dm_msg) - - async def import_raw(self, data): - pass - @nomen.command(name="import") @guild_only() - async def _import(self, ctx, *, data): + async def _import(self, ctx): """ - Imports user data, either from JSON or from a simple list + Imports a JSON of all of your user data """ schema = { "disabled": bool, @@ -120,25 +69,13 @@ You may want to `{ctx.clean_prefix}nomen export` first""" ], } - notifications = self.bot.get_cog("Notifications") - - try: - await self.import_json(ctx, data) - await ctx.send("Sorry, JSON imports and exports haven't been implemented yet.") - return - except InvalidImportFormat: - try: - await self.import_dusty(ctx, data) - except InvalidImportFormat: - await ctx.send("Invalid import format") - - # @nomen.command() - # @guild_only() - # async def export(self, ctx): - # """ - # Exports a JSON of all of your user data - # """ - # pass + @nomen.command() + @guild_only() + async def export(self, ctx): + """ + Exports a JSON of all of your user data + """ + pass @nomen.command(name="opt-out") async def opt_out(self, ctx): @@ -171,42 +108,3 @@ You may want to `{ctx.clean_prefix}nomen export` first""" await self.bot.db.execute("REPLACE INTO users (user_id, disabled) VALUES(?, 0)", (ctx.author.id,)) await self.bot.db.commit() - - @group(invoke_without_command=True) - async def settings( - self, - ctx, - setting: Literal["use-embed", "notify-self", "bots-notify"], - value: bool, - ): - """ - Change settings for Nomen - - Valid settings are: - - `use-embed` - - `notify-self` - - `bots-notify` - """ - setting_db = setting.replace("-", "_") - - await ctx.bot.set_setting(ctx.author.id, setting_db, value) - await self.settings_list(ctx) - - @settings.command(name="list") - async def settings_list(self, ctx): - """ - Print all of your current settings - """ - - settings = await ctx.bot.db.execute_fetchall( - "SELECT use_embed, notify_self, bots_notify FROM users WHERE user_id=?", (ctx.author.id,) - ) - - settings = ["Yes" if v else "No" for v in settings[0]] - - embed = disnake.Embed(title="Current Settings") - embed.add_field("Use Embeds in Notifications (`use-embed`)", settings[0], inline=False) - embed.add_field("Notify Self (`notify-self`)", settings[1], inline=False) - embed.add_field("Notifications from Bots (`bots-notify`)", settings[2], inline=False) - - await ctx.send(embed=embed) diff --git a/nomen/utils.py b/nomen/utils.py index 417a9c8..3b9861e 100644 --- a/nomen/utils.py +++ b/nomen/utils.py @@ -111,6 +111,18 @@ def unpack(lst_of_tpl): return list(map(first, lst_of_tpl)) +async def fetch_unpacked(db, sql, params=None): + cur = await db.cursor() + cur.row_factory = lambda cursor, row: first(row) + cur = await cur.execute(sql, params) + return await cur.fetchall() + + +async def fetch_exists(db, sql, params=None): + result = await db.execute_fetchall(f"SELECT EXISTS({sql})", params) + return result[0][0] + + async def in_thread(member, thread): # FIXME: Currently overlooks the situation where a moderator isn't in a thread but has manage threads return any(member.id == thread_member.id for thread_member in await thread.fetch_members())