diff --git a/.gitignore b/.gitignore index e0a77da0..dccf5e1b 100644 --- a/.gitignore +++ b/.gitignore @@ -158,4 +158,6 @@ data/ # ignore db data db/* /deploy -.env \ No newline at end of file +.env +/Pipfile +/Pipfile.lock diff --git a/Resources/ServerConfig/Zorak-Dev/reaction_roles.toml b/Resources/ServerConfig/Zorak-Dev/reaction_roles.toml deleted file mode 100644 index 04a72b29..00000000 --- a/Resources/ServerConfig/Zorak-Dev/reaction_roles.toml +++ /dev/null @@ -1,169 +0,0 @@ -#### PRODUCTION -# -#[reaction_roles.experience] -#beginner = 965926311539388457 -#intermediate = 965926533984288778 -#professional = 965926542481956906 -# -#[reaction_roles.location] -#north_america = 974950715892072488 -#europe = 974950822133776425 -#asia = 974950853452636200 -#africa = 974950895387303977 -#south_america = 974950944586489856 -#oceana = 974951025050025984 -# -#[reaction_roles.employment] -#open_to_work = 1113020825596067850 -#employer = 1113020504148815944 -# -#[reaction_roles.color] -#red = 1148145845770915870 -#orange = 1148145321302560769 -#yellow = 1148145167736520764 -#light_green = 1148144840735981569 -#dark_green = 1148147946068643900 -#light_blue = 1148144625836625961 -#blue = 1148144828631224391 -#pink = 1143109787316396082 -#purple = 1148146380880216065 -# -#[selectors.experience] -#name = "experience" -#single_choice = true -#description = "Select your skill level!" -#options = [ -# { label = "Beginner", emoji = "🟢", description = "Have little to no Python Experience", id = 965926311539388457 }, -# { label = "Intermediate", emoji = "🟡", description = "Can solve issues and diagnose problems", id = 965926533984288778 }, -# { label = "Professional", emoji = "🔴", description = "Using Python in professional life.", id = 965926542481956906 }, -#] -# -#[selectors.location] -#name = "location" -#single_choice = true -#description = "Select your continent!" -#options = [ -# { label = "North America", emoji = "🦅", description = "", id = 974950715892072488 }, -# { label = "Europe", emoji = "🇪🇺", description = "", id = 974950822133776425 }, -# { label = "Asia", emoji = "🐼", description = "", id = 974950853452636200 }, -# { label = "Oceana", emoji = "🐨", description = "", id = 974951025050025984 }, -# { label = "South America", emoji = "💃", description = "", id = 974950944586489856 }, -# { label = "Africa", emoji = "🦒", description = "", id = 974950895387303977 }, -#] -# -#[selectors.employment] -#name = "employment" -#single_choice = false -#description = "Are you open to work / Looking for a developer?!" -#options = [ -# { label = "Open to Work", emoji = "🛠️", description = "You are open to taking Python related jobs.", id = 1113020825596067850 }, -# { label = "Employer", emoji = "💼", description = "You are looking to hiring a Python developer.", id = 1113020504148815944 } -# -#] -# -#[selectors.color] -#name = "color" -#single_choice = true -#description = "Select your color!" -#options = [ -# { label = "Red", emoji = "🔴", description = "", id = 1148145845770915870 }, -# { label = "Orange", emoji = "🟠", description = "", id = 1148145321302560769 }, -# { label = "Yellow", emoji = "🟡", description = "", id = 1148145167736520764 }, -# { label = "Light Green", emoji = "🍐", description = "", id = 1148144840735981569 }, -# { label = "Dark Green", emoji = "🟢", description = "", id = 1148147946068643900 }, -# { label = "Light Blue", emoji = "🧊", description = "", id = 1148144625836625961 }, -# { label = "Blue", emoji = "🔵", description = "", id = 1148144828631224391 }, -# { label = "Pink", emoji = "🌸", description = "", id = 1143109787316396082 }, -# { label = "Purple", emoji = "🟣", description = "", id = 1148146380880216065 }, -#] -# - -### DEVELOPMENT - -[reaction_roles.experience] -beginner = 1064259543250522112 -intermediate = 1064259555497877534 -professional = 1064259557049761863 - -[reaction_roles.location] -north_america = 1064259558819770379 -europe = 1064259561302794361 -asia = 1064259687924637768 -africa = 1064259690948722708 -south_america = 1064259693545013288 -oceana = 1064259760674840666 - -[reaction_roles.employment] -open_to_work = 1113020825596067850 -employer = 1113020504148815944 -ping = 1213857615537250324 - -[reaction_roles.color] -red = 1149951355000201267 -orange = 1149951496872546337 -yellow = 1149951592968241213 -light_green = 1149951635842420809 -dark_green = 1149951734345637888 -light_blue = 1149951815773868092 -blue = 1149951872711528529 -pink = 1149951973513252914 -purple = 1149951930253185084 - -[selectors.notifications] -name = "Server notifications" -single_choice = true -description = "Do you want to receive relevant server pings for news, events and announcements?" -options = [ - { label = "Yes!", emoji = "🟢", description = "I want to receive Pings from the server.", id = 1213857615537250324 }, -] - - -[selectors.experience] -name = "experience" -single_choice = true -description = "Select your skill level!" -options = [ - { label = "Beginner", emoji = "🟢", description = "Have little to no Python Experience", id = 1064259543250522112 }, - { label = "Intermediate", emoji = "🟡", description = "Can solve issues and diagnose problems", id = 1064259555497877534 }, - { label = "Professional", emoji = "🔴", description = "Using Python in professional life.", id = 1064259557049761863 }, -] - - -[selectors.location] -name = "location" -single_choice = true -description = "Select your continent!" -options = [ - { label = "North America", emoji = "🦅", description = "", id = 1064259558819770379 }, - { label = "Europe", emoji = "🇪🇺", description = "", id = 1064259561302794361 }, - { label = "Asia", emoji = "🐼", description = "", id = 1064259687924637768 }, - { label = "Oceana", emoji = "🐨", description = "", id = 1064259760674840666 }, - { label = "South America", emoji = "💃", description = "", id = 1064259693545013288 }, - { label = "Africa", emoji = "🦒", description = "", id = 1064259690948722708 }, -] - -[selectors.employment] -name = "employment" -single_choice = false -description = "Are you open to work / Looking for a developer?!" -options = [ - { label = "Open to Work", emoji = "🛠️", description = "You are open to taking Python related jobs.", id = 1114611004182102218 }, - { label = "Employer", emoji = "💼", description = "You are looking to hiring a Python developer.", id = 1114611092677722113 } - -] - -[selectors.color] -name = "color" -single_choice = true -description = "Select your color!" -options = [ - { label = "Red", emoji = "🔴", description = "", id = 1149951355000201267 }, - { label = "Orange", emoji = "🟠", description = "", id = 1149951496872546337 }, - { label = "Yellow", emoji = "🟡", description = "", id = 1149951592968241213 }, - { label = "Light Green", emoji = "🍐", description = "", id = 1149951635842420809 }, - { label = "Dark Green", emoji = "🟢", description = "", id = 1149951734345637888 }, - { label = "Light Blue", emoji = "🧊", description = "", id = 1149951815773868092 }, - { label = "Blue", emoji = "🔵", description = "", id = 1149951872711528529 }, - { label = "Pink", emoji = "🌸", description = "", id = 1149951973513252914 }, - { label = "Purple", emoji = "🟣", description = "", id = 1149951930253185084 }, -] diff --git a/Resources/ServerConfig/dev/FAKE_DB_settings.json b/Resources/ServerConfig/dev/FAKE_DB_settings.json new file mode 100644 index 00000000..416eb920 --- /dev/null +++ b/Resources/ServerConfig/dev/FAKE_DB_settings.json @@ -0,0 +1,73 @@ +{ + "1031644670436061324": { + "info": { + "id": 1031644670436061324, + "name": "Practical Python", + "website": "https://practical-python-org.github.io/Home/", + "email": "Practicalpython-staff@pm.me", + "review": "https://disboard.org/review/create/900302240559018015", + "invite": "https://discord.gg/vgZmgNwuHw", + "logo": "https://raw.githubusercontent.com/Xarlos89/PracticalPython/main/logo.png" + }, + "channels": { + "verification": { + "verification_channel": 1055126587512721439 + }, + "quarantine": { + "quarantine_channel": 1055126882691076177 + }, + "support": { + "server_support": 1068103243734994964 + }, + "general": { + "role_channel": 1062268986726813758, + "rules_channel": 1062269049121275914, + "general_channel": 1031644670436061327, + "resources_channel": 1062300735338983485, + "python_help_1": 1062302503351365632, + "python_help_2": 12345, + "challenges_channel": 1045104938071633994, + "news_channel": 1071095960538718261, + "suggestions_channel": 117939062999902620 + }, + "logging": { + "chat_log": 1043607758685089932, + "join_log": 1043967844934762556, + "mod_log": 1055124663526752297, + "server_change_log": 1062269270047862884, + "user_log": 1044331443884654692, + "verification_log": 1043621605940674580, + "zorak_log": 1062754277543645324 + } + }, + "roles": { + "admin": { + "owner": 1054762426614157354, + "admin": 1134861924882989097, + "staff": 1031903615310385232, + "staff_networking": 1134861783358775367, + "bot": 1054763353538576437 + }, + "reaction": { + "beginner": 1064259543250522112, + "intermediate": 1064259555497877534, + "professional": 1064259557049761863, + "north_america": 1064259558819770379, + "europe": 1064259561302794361, + "asia": 1064259687924637768, + "africa": 1064259690948722708, + "south_america": 1064259693545013288, + "oceana": 1064259760674840666, + "open_to_work": 1114611004182102218, + "employer": 1114611092677722113 + }, + "punishment": { + "naughty": 1062270109839790110 + }, + "verified": { + "verified": 1173219859878924298 + } + } + } +} + diff --git a/Resources/ServerConfig/Zorak-Dev/channel_info.toml b/Resources/ServerConfig/dev/channel_info.toml similarity index 100% rename from Resources/ServerConfig/Zorak-Dev/channel_info.toml rename to Resources/ServerConfig/dev/channel_info.toml diff --git a/Resources/ServerConfig/Zorak-Dev/verification_options.toml b/Resources/ServerConfig/dev/verification_options.toml similarity index 100% rename from Resources/ServerConfig/Zorak-Dev/verification_options.toml rename to Resources/ServerConfig/dev/verification_options.toml diff --git a/Resources/ServerConfig/PracticalPython/channel_info.toml b/Resources/ServerConfig/prod/channel_info.toml similarity index 100% rename from Resources/ServerConfig/PracticalPython/channel_info.toml rename to Resources/ServerConfig/prod/channel_info.toml diff --git a/Resources/ServerConfig/PracticalPython/reaction_roles.toml b/Resources/ServerConfig/prod/reaction_roles.toml similarity index 100% rename from Resources/ServerConfig/PracticalPython/reaction_roles.toml rename to Resources/ServerConfig/prod/reaction_roles.toml diff --git a/Resources/ServerConfig/PracticalPython/verification_options.toml b/Resources/ServerConfig/prod/verification_options.toml similarity index 100% rename from Resources/ServerConfig/PracticalPython/verification_options.toml rename to Resources/ServerConfig/prod/verification_options.toml diff --git a/src/zorak/utilities/cog_utilities/__init__.py b/__init__.py similarity index 100% rename from src/zorak/utilities/cog_utilities/__init__.py rename to __init__.py diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/zorak/utilities/cog_utilities/README.md b/src/zorak/_ARCHIVE/README.md similarity index 100% rename from src/zorak/utilities/cog_utilities/README.md rename to src/zorak/_ARCHIVE/README.md diff --git a/src/zorak/_ARCHIVE/__init__.py b/src/zorak/_ARCHIVE/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/zorak/utilities/cog_utilities/blackjack.py b/src/zorak/_ARCHIVE/blackjack.py similarity index 100% rename from src/zorak/utilities/cog_utilities/blackjack.py rename to src/zorak/_ARCHIVE/blackjack.py diff --git a/src/zorak/utilities/cog_utilities/card_deck.py b/src/zorak/_ARCHIVE/card_deck.py similarity index 100% rename from src/zorak/utilities/cog_utilities/card_deck.py rename to src/zorak/_ARCHIVE/card_deck.py diff --git a/src/zorak/__main__.py b/src/zorak/__main__.py index 0d2ee8e9..9ed978f0 100644 --- a/src/zorak/__main__.py +++ b/src/zorak/__main__.py @@ -5,20 +5,19 @@ - Practicalpython-staff@pm.me """ import logging +from datetime import datetime import os import discord from discord.ext import commands from zorak.utilities.core.args_utils import parse_args -from zorak.utilities.core.logging_utils import setup_logger +from zorak.utilities.core.logging_options import setup_logger from zorak.utilities.core.mongo import initialise_bot_db -from zorak.utilities.core.server_settings import Settings +from zorak.utilities.core.settings import Settings -logger = logging.getLogger(__name__) -COGS_ROOT_PATH = os.path.join(os.path.dirname(__file__), "cogs") -logger.debug(f"COG_PATH: {COGS_ROOT_PATH}") +logger = logging.getLogger(__name__) server_settings_path = None @@ -28,7 +27,9 @@ def load_cogs(bot): Loads the directories under the /cogs/ folder, then digs through those directories and loads the cogs. """ + COGS_ROOT_PATH = os.path.join(os.path.dirname(__file__), "cogs") logger.info("Loading Cogs...") + logger.info(f"Loading from {COGS_ROOT_PATH}") failed_to_load = [] for directory in os.listdir(COGS_ROOT_PATH): if directory.startswith("_"): @@ -60,29 +61,34 @@ def init_bot(token, bot): try: load_cogs(bot) bot.run(token) + except TypeError as e: print(e) + def main(): args = parse_args() + bot = commands.Bot(command_prefix="/", intents=discord.Intents.all()) + bot.remove_command("help") + + + # Set up global logging across the bot. setup_logger( level=args.log_level if args.log_level else int(os.getenv("LOGGING_LEVEL", 20)), stream_logs=args.console_log if args.console_log != None else bool(os.getenv("STREAM_LOGS", False)), ) - - bot = commands.Bot(command_prefix="/", intents=discord.Intents.all()) - bot.remove_command("help") - if not args.drop_db: logger.info("Initialising Database...") initialise_bot_db(bot) - server_settings_path = args.server_settings_path if args.server_settings_path else os.environ.get("SERVER_SETTINGS") - if server_settings_path: - logger.info(f"Loading server settings from {server_settings_path}") - bot.server_settings = Settings(server_settings_path) # type: ignore + settings_path = args.server_settings_path if args.server_settings_path else os.environ.get( + "SERVER_SETTINGS") + + if settings_path: + logger.info(f"Loading all server settings from {settings_path}") + bot.settings = Settings(settings_path, bot.guilds) # type: ignore if args.discord_token: init_bot(args.discord_token, bot) diff --git a/src/zorak/cogs/admin/admin_automod_spam_messages.py b/src/zorak/cogs/admin/admin_automod_spam_messages.py index 65a2f72a..3d30fd77 100644 --- a/src/zorak/cogs/admin/admin_automod_spam_messages.py +++ b/src/zorak/cogs/admin/admin_automod_spam_messages.py @@ -39,9 +39,16 @@ async def on_message(self, message): if isinstance(message.channel, discord.DMChannel): return + settings = self.bot.db_client.get_guild_settings(message.guild) + # new speaker. Welcome to auto mod. - if message.author.id not in self.records: - self.records[message.author.id] = { + if message.guild.id not in self.records: + self.records[message.guild.id] = {} + + guild_record = self.records[message.guild.id] + + if message.author.id not in guild_record: + guild_record[message.author.id] = { "last_message": message.content , "occurrence": 1 , "1st": {"message_id": message.id, "channel_id": message.channel.id} @@ -52,7 +59,7 @@ async def on_message(self, message): # Old speaker. We are watching you... else: # Check if the last message is the same as the new one. - the_archive = self.records[message.author.id] + the_archive = guild_record[message.author.id] if the_archive["last_message"] == message.content: # If so, increase the occurance by 1 @@ -93,10 +100,9 @@ async def on_message(self, message): # timeout right away await message.author.timeout(until=datetime.utcnow() + timedelta(seconds=30)) - naughty = message.author.guild.get_role(self.bot.server_settings.user_roles["bad"]["naughty"]) - verified = message.author.guild.get_role(self.bot.server_settings.verified_role['verified']) - quarantine = await self.bot.fetch_channel( - self.bot.server_settings.channels["moderation"]["quarantine_channel"]) + naughty = message.author.guild.get_role(settings["naughty_role"]) + verified = message.author.guild.get_role(settings["verified_role"]) + quarantine = await self.bot.fetch_channel(settings["quarantine_channel"]) # assign Naughty roll member = message.author diff --git a/src/zorak/cogs/admin/admin_ban.py b/src/zorak/cogs/admin/admin_ban.py index ac1b7c5f..e7836076 100644 --- a/src/zorak/cogs/admin/admin_ban.py +++ b/src/zorak/cogs/admin/admin_ban.py @@ -23,7 +23,6 @@ def __init__(self, bot): @commands.slash_command(description="Ban a user.") @commands.has_permissions(ban_members=True) - @commands.has_role("Staff") async def ban_member(self, ctx, target: discord.Member, reason): """ Take in a user mention, and a string reason. @@ -42,7 +41,9 @@ async def ban_member(self, ctx, target: discord.Member, reason): await target.ban(reason=f"{ctx.author.name} - {reason}") logger.info("{%s} banned {%s}. Reason: {%s}", ctx.author.name, target.name, reason) # Then we publicly announce what happened. - await ctx.respond(embed=embed_cant_do_that(f"**{ctx.author.name}** banned **{target.name}**" f"\n**Reason:** {reason}")) + await ctx.respond( + embed=embed_cant_do_that(f"**{ctx.author.name}** banned **{target.name}**" f"\n" + f"**Reason:** {reason}")) else: await ctx.respond(embed=embed_cant_do_that("You can't ban an Admin."), ephemeral=True) diff --git a/src/zorak/cogs/admin/admin_delete_user_messages.py b/src/zorak/cogs/admin/admin_delete_user_messages.py index a5eb6c66..317dd58d 100644 --- a/src/zorak/cogs/admin/admin_delete_user_messages.py +++ b/src/zorak/cogs/admin/admin_delete_user_messages.py @@ -1,6 +1,5 @@ """ Admin command for deleting a users historical messages -TODO: Finish this cog. It is currently named with a _ prefix so that it is not loaded. """ import logging import time @@ -26,7 +25,6 @@ def __init__(self, bot): @commands.slash_command(description="Deletes all messages from a user over a timeperiod in minutes.") @commands.has_permissions(manage_messages=True) - @commands.has_role("Staff") async def delete_messages(self, ctx, target: discord.Member, minutes): """ Take in a user, and a timeperiod in minutes. @@ -35,15 +33,15 @@ async def delete_messages(self, ctx, target: discord.Member, minutes): counter = 0 time_ago = datetime.utcnow() - timedelta(minutes=int(minutes)) - for guild in self.bot.guilds: - for channel in guild.text_channels: - async for message in channel.history(limit=300, after=time_ago): - if message.author.id == target.id: - await message.delete() - time.sleep(0.3) - counter += 1 + for channel in ctx.guild.text_channels: + async for message in channel.history(limit=300, after=time_ago): + if message.author.id == target.id: + await message.delete() + time.sleep(0.2) + counter += 1 - await ctx.followup.send(embed=embed_cant_do_that(f"{ctx.author.name} deleted {str(counter)} messages by {target.name}.")) + await ctx.followup.send( + embed=embed_cant_do_that(f"{ctx.author.name} deleted {str(counter)} messages by {target.name}.")) def setup(bot): diff --git a/src/zorak/cogs/admin/admin_kick.py b/src/zorak/cogs/admin/admin_kick.py index 52dc5e4c..f81cd47d 100644 --- a/src/zorak/cogs/admin/admin_kick.py +++ b/src/zorak/cogs/admin/admin_kick.py @@ -23,7 +23,6 @@ def __init__(self, bot): @commands.slash_command(description="Kick a user.") @commands.has_permissions(kick_members=True) - @commands.has_role("Staff") async def kick_member(self, ctx, target: discord.Member, reason): """ Take in a user mention, and a string reason. @@ -42,7 +41,9 @@ async def kick_member(self, ctx, target: discord.Member, reason): await target.kick(reason=f"{ctx.author.name} - {reason}") logger.info("{%s} kicked {%s}. Reason: {%s}", ctx.author.name, target.name, reason) # Then we publicly announce what happened. - await ctx.respond(embed=embed_cant_do_that(f"**{ctx.author.name}** kicked **{target.name}**" f"\n**Reason:** {reason}")) + await ctx.respond( + embed=embed_cant_do_that(f"**{ctx.author.name}** kicked **{target.name}**" f"\n" + f"**Reason:** {reason}")) else: await ctx.respond(embed=embed_cant_do_that("You can't kick an Admin."), ephemeral=True) diff --git a/src/zorak/cogs/admin/admin_mute.py b/src/zorak/cogs/admin/admin_mute.py index 22a6ef7a..6dfab3e7 100644 --- a/src/zorak/cogs/admin/admin_mute.py +++ b/src/zorak/cogs/admin/admin_mute.py @@ -24,7 +24,6 @@ def __init__(self, bot): @commands.slash_command(description="Mute a user.") @commands.has_permissions(moderate_members=True) - @commands.has_role("Staff") async def mute_member(self, ctx, target: discord.Member, time, reason): """ Take in a user mention, and a string reason. diff --git a/src/zorak/cogs/admin/admin_purge.py b/src/zorak/cogs/admin/admin_purge.py index 402c3e5e..35fd7711 100644 --- a/src/zorak/cogs/admin/admin_purge.py +++ b/src/zorak/cogs/admin/admin_purge.py @@ -7,7 +7,6 @@ class AdminPurge(commands.Cog): """ We are limited to 100 messages per command by Discord API - TODO: This command should be hidden from non-staff users. """ def __init__(self, bot): @@ -15,27 +14,23 @@ def __init__(self, bot): @commands.slash_command(description="Removes up to 100 messages from channel.") @commands.has_permissions(manage_messages=True) - @commands.has_role("Staff") + @commands.has_permissions(moderate_members=True) async def purge_messages(self, ctx, number_messages): """ We currently have permissions on this command set, which throws an error when the user does not have the correct perms. We handle this with an error_handler block """ + settings = self.bot.db_client.get_guild_settings(ctx.guild) # removes the need for a response await ctx.defer() - logs_channel = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) # Welcome channel - - # Check that users are not removing from log channels - if ctx.channel.id in self.bot.server_settings.log_channel.values(): - await ctx.channel.send(f"{ctx.author.mention}, you should not be purging the {ctx.channel.mention}.") - await logs_channel.send(f"{ctx.author.mention} atempted to purge the {ctx.channel.mention}.") - else: - # Do the purge - await ctx.channel.purge(limit=int(number_messages)) - # Log the purge - await logs_channel.send(f"{number_messages} messages purged" f" from {ctx.channel.mention}" f" by {ctx.author.mention}.") + logs_channel = await self.bot.fetch_channel(settings["mod_log"]) # Welcome channel + + # Do the purge + await ctx.channel.purge(limit=int(number_messages)) + # Log the purge + await logs_channel.send(f"{number_messages} messages purged" f" from {ctx.channel.mention}" f" by {ctx.author.mention}.") async def cog_command_error(self, ctx: commands.Context, error: commands.CommandError): """ diff --git a/src/zorak/cogs/admin/admin_startup.py b/src/zorak/cogs/admin/admin_startup.py index a770cf0a..6d7f6964 100644 --- a/src/zorak/cogs/admin/admin_startup.py +++ b/src/zorak/cogs/admin/admin_startup.py @@ -2,10 +2,13 @@ Called upon startup of our bot. Just logs some info. Nothing really. """ +import os +import json import logging from datetime import datetime from discord.ext import commands +from zorak.utilities.core.args_utils import parse_args logger = logging.getLogger(__name__) @@ -27,6 +30,9 @@ async def on_ready(self): logger.info("Started at: {%s}", datetime.now()) logger.info("Greetings, puny earth-creature.") + # logger.info("---------------------------------") + # logger.critical(self.bot.settings.server) + # logger.info("---------------------------------") def setup(bot): """ diff --git a/src/zorak/cogs/admin/error_handler.py b/src/zorak/cogs/admin/error_handler.py index dc7ebdfd..b70660fb 100644 --- a/src/zorak/cogs/admin/error_handler.py +++ b/src/zorak/cogs/admin/error_handler.py @@ -4,18 +4,18 @@ from discord.ext import commands from datetime import datetime - logger = logging.getLogger(__name__) class error_handler(commands.Cog): def __init__(self, bot): self.bot = bot - self.error_channel = self.bot.server_settings.log_channel["zorak_log"] @commands.Cog.listener() async def on_application_command_error(self, ctx, error): - error_log = await self.bot.fetch_channel(self.error_channel) + settings = self.bot.db_client.get_guild_settings(ctx.guild) + + error_log = await self.bot.fetch_channel(settings["zorak_log"]) # # This is just an interesting way of handling errors PER ERROR. # # For now, let's just catch all and redirect to logs and channel @@ -37,4 +37,4 @@ async def on_application_command_error(self, ctx, error): def setup(bot): - bot.add_cog(error_handler(bot)) \ No newline at end of file + bot.add_cog(error_handler(bot)) diff --git a/src/zorak/cogs/admin/moderation_invites.py b/src/zorak/cogs/admin/moderation_invites.py index 4ab69be9..eab1af52 100644 --- a/src/zorak/cogs/admin/moderation_invites.py +++ b/src/zorak/cogs/admin/moderation_invites.py @@ -21,80 +21,83 @@ async def on_message(self, message): """ Scans every message with the regex below. """ - txt = message.content - current_channel = message.channel + if not message.author.bot: + txt = message.content + current_channel = message.channel + author = message.author + settings = self.bot.db_client.get_guild_settings(message.guild) - def is_invite(arg_message): - """ - - invitation types - -> Official covers official invites "discord.gg/s7s8df9a" - -> unofficial urls that start with d and end with letter numbers "dxxxx.gg/23bn2u2" - """ - official = re.search( - "(?:https?://)?(?:www\.|ptb\.|canary\.)?(?:discord(?:app)?\.(?:(?:com|gg)" # pylint: disable=W1401 - "/invite/[a-z0-9-_]+)|discord\.gg/[a-z0-9-_]+)", # pylint: disable=W1401 - arg_message, - ) - unofficial = re.search( - "(?:https?://)?(?:www\.)?(?:dsc\.gg|invite\.gg+|discord\.link)" # pylint: disable=W1401 - "/[a-z0-9-_]+", # pylint: disable=W1401 - arg_message, - ) - if official is not None or unofficial is not None: - return True - return False + def is_invite(arg_message): + """ + - invitation types + -> Official covers official invites "discord.gg/s7s8df9a" + -> unofficial urls that start with d and end with letter numbers "dxxxx.gg/23bn2u2" + """ + official = re.search( + "(?:https?://)?(?:www\.|ptb\.|canary\.)?(?:discord(?:app)?\.(?:(?:com|gg)" # pylint: disable=W1401 + "/invite/[a-z0-9-_]+)|discord\.gg/[a-z0-9-_]+)", # pylint: disable=W1401 + arg_message, + ) + unofficial = re.search( + "(?:https?://)?(?:www\.)?(?:dsc\.gg|invite\.gg+|discord\.link)" # pylint: disable=W1401 + "/[a-z0-9-_]+", # pylint: disable=W1401 + arg_message, + ) + if official is not None or unofficial is not None: + return True + return False - def log_message(arg_message): - """ - If it finds something, it logs the message - """ - author = arg_message.author - embed = discord.Embed( - title="<:red_circle:1043616578744357085> Invite removed", - description=f"Posted by {arg_message.author}\nIn {'a DM.' if isinstance(arg_message.channel, discord.DMChannel) else arg_message.channel.mention}", - color=discord.Color.dark_red(), - timestamp=datetime.utcnow(), - ) - embed.set_thumbnail(url=author.avatar) - embed.add_field( - name="Message: ", - value=message.content, # ToDo: This throws an error when deleting an embed. - inline=True, - ) - return embed + def log_message(arg_message): + """ + If it finds something, it logs the message + """ + author = arg_message.author + embed = discord.Embed( + title="<:red_circle:1043616578744357085> Invite removed", + description=f"Posted by {arg_message.author}\nIn {'a DM.' if isinstance(arg_message.channel, discord.DMChannel) else arg_message.channel.mention}", + color=discord.Color.dark_red(), + timestamp=datetime.utcnow(), + ) + embed.set_thumbnail(url=author.avatar) + embed.add_field( + name="Message: ", + value=message.content, # ToDo: This throws an error when deleting an embed. + inline=True, + ) + return embed - def embed_warning(arg_message): - """ - If it finds something, it sends a warning that the user should quit that shit. - """ - embed = discord.Embed( - title="<:x:1055080113336762408> External Invites are not allowed here!", - description=f"{arg_message.author}, your message was removed " - f"because it contained an external invite.\nIf this " - f"was a mistake, contact the @staff", - color=discord.Color.dark_red(), - timestamp=datetime.utcnow(), - ) - return embed + def embed_warning(arg_message): + """ + If it finds something, it sends a warning that the user should quit that shit. + """ + embed = discord.Embed( + title="<:x:1055080113336762408> External Invites are not allowed here!", + description=f"{arg_message.author}, your message was removed " + f"because it contained an external invite.\nIf this " + f"was a mistake, contact the @staff", + color=discord.Color.dark_red(), + timestamp=datetime.utcnow(), + ) + return embed - def check_for_admin_override(arg_message): - """ - Handling for when a MOD user needs to post an invitation - """ - if not message.content.startswith('z.invite '): - return False + def check_for_admin_override(arg_message): + """ + Handling for when a MOD user needs to post an invitation + """ + if not message.content.startswith('z.invite '): + return False - return any(role.id in self.bot.server_settings.admin_roles.values() for role in message.author.roles) + return any(role.id in settings.admin_roles.values() for role in message.author.roles) - if is_invite(txt): - if isinstance(message.channel, discord.DMChannel): - return + if is_invite(txt): + if isinstance(message.channel, discord.DMChannel): + return - if not check_for_admin_override(txt): - logs_channel = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) - await logs_channel.send(embed=log_message(message)) - await message.delete() - await current_channel.send(embed=embed_warning(message)) + if not check_for_admin_override(txt): + logs_channel = await self.bot.fetch_channel(settings["mod_log"]) + await logs_channel.send(embed=log_message(message)) + await message.delete() + await current_channel.send(embed=embed_warning(message)) def setup(bot): diff --git a/src/zorak/cogs/admin/reaction_roles.py b/src/zorak/cogs/admin/reaction_roles.py index 05731bba..9b725dab 100644 --- a/src/zorak/cogs/admin/reaction_roles.py +++ b/src/zorak/cogs/admin/reaction_roles.py @@ -95,13 +95,13 @@ def __init__(self, bot): @commands.slash_command(description="Get new roles, or change the ones you have!") async def roles(self, ctx): """The slash command that initiates the fancy menus.""" - if hasattr(self.bot.server_settings, "reaction_role_data"): - if "reaction_roles" in self.bot.server_settings.reaction_role_data: - await ctx.respond("Edit Reaction Roles", view=SelectView(self.bot.server_settings.reaction_role_data), ephemeral=True) - else: - await ctx.respond("No reaction roles have been set up!", ephemeral=True) + guild_roles = self.bot.db_client.see_all_reaction_role_sets(ctx.guild) + logger.critical(guild_roles) + if "ReactionRoles" in guild_roles: + await ctx.respond("Edit Reaction Roles", view=SelectView(guild_roles), ephemeral=True) else: - await ctx.respond("No server settings have been set up!", ephemeral=True) + await ctx.respond("No reaction roles have been set up!", ephemeral=True) + def setup(bot): diff --git a/src/zorak/cogs/admin/update_settings.py b/src/zorak/cogs/admin/update_settings.py new file mode 100644 index 00000000..8b69804f --- /dev/null +++ b/src/zorak/cogs/admin/update_settings.py @@ -0,0 +1,189 @@ +""" +A simple hello command. +""" +import logging +import discord +from discord import option +from discord.ext import commands + +logger = logging.getLogger(__name__) + + +class Settings(commands.Cog): + """ + This is the class that defines the actual slash command. + It uses the view above to execute actual logic. + """ + + def __init__(self, bot): + self.bot = bot # Passed in from main.py + + @commands.slash_command(description="See the bot settings for your guild!") + async def setup_zorak(self, ctx): + if not self.bot.db_client.guild_exists_in_db(ctx.guild): + self.bot.db_client.add_guild_to_table(ctx.guild) + await ctx.respond( + f"## **Added {ctx.guild.name} to database.**" + f"\n\nPlease use the **/update_roles** command to set Guild roles." + f"\nPlease use the **/update_channels** command to set Guild channels." + f"\nPlease use the **/update_logging_channels** command to set Guild log channels." + f"\n\nTo see roles, use **/see_roles**." + f"\nTo see channels, use **/see_channels**." + f"\nTo see logging channels, use **/see_logging_channels**." + ) + else: + await ctx.respond( + f"Looks like {ctx.guild.name} is already in the database." + f"\n\nTo see roles, use **/see_roles**." + f"\nTo see channels, use **/see_channels**." + f"\nTo see logging channels, use **/see_logging_channels**." + ) + + + #################### + # Roles + #################### + @commands.slash_command(description="See the roles for your guild!") + async def see_roles(self, ctx): + """The slash command that initiates the fancy menus.""" + settings = self.bot.db_client.get_guild_settings(ctx.guild) + pretty_printed_settings = "" + for key, value in settings.items(): + if "_role" in key: + pretty_printed_settings += f"{key}: <@{value}>\n" + + await ctx.respond(f"### Guild Roles for {ctx.guild}\n{pretty_printed_settings}") + + @commands.slash_command() + @option( + "position" + , description="All options for roles." + , choices=[ + "Guild Owner role" + , "Administrator role" + , "Staff/Moderatior role" + , "Networking role" + , "Zorak's Bot role" + , "Punishment/Quarantine role" + , "Verification role" + ]) + async def update_roles( + self + , ctx: discord.ApplicationContext + , position: str + , role: discord.Role + ): + mapper = { + "Guild Owner role": "owner_role", + "Administrator role": "admin_role", + "Staff/Moderator role": "staff_role", + "Networking role": "networking_role", + "Zorak's Bot role": "bot_role", + "Punishment/Quarantine role": "naughty_role", + "Verification role": "verified_role" + } + self.bot.db_client.update_guild_settings(ctx.guild, mapper[position], int(role.id)) + logger.info(f"{ctx.author.name} updated {mapper[position]} in {ctx.guild.name} to {role.id}") + await ctx.respond(f"Updated {position} in {ctx.guild.name}. New Value: {role.mention}") + + #################### + # Normal Channels + #################### + @commands.slash_command(description="See the channels for your guild!") + async def see_channels(self, ctx): + """The slash command that initiates the fancy menus.""" + settings = self.bot.db_client.get_guild_settings(ctx.guild) + pretty_printed_settings = "" + for key, value in settings.items(): + if "_channel" in key: + pretty_printed_settings += f"{key}: <#{value}>\n" + + await ctx.respond(f"### Guild channels for {ctx.guild}\n{pretty_printed_settings}") + + @commands.slash_command() + @option( + "option" + , description="All options for Guild channels." + , choices=[ + "Verification Channel" + , "Quarantine Channel" + , "Support/Ticket Channel" + , "Reaction Role Channel" + , "Rules Channel" + , "General Channel" + , "Resources Channel" + , "Challenges Channel" + ]) + async def update_channels( + self + , ctx: discord.ApplicationContext + , option: str + , channel: discord.TextChannel + ): + mapper = { + "Verification Channel": "verification_channel", + "Quarantine Channel": "quarantine_channel", + "Support/Ticket Channel": "support_channel", + "Reaction Role Channel": "role_channel", + "Rules Channel": "rules_channel", + "General Channel": "general_channel", + "Resources Channel": "resources_channel", + "Challenges Channel": "challenges_channel" + } + self.bot.db_client.update_guild_settings(ctx.guild, mapper[option], int(channel.id)) + logger.info(f"{ctx.author.name} updated {mapper[option]} in {ctx.guild.name} to {channel.id}") + await ctx.respond(f"Updated {option} in {ctx.guild.name}. New Value: {channel.mention}") + + #################### + # Logging Channels + #################### + + @commands.slash_command(description="See the logging channels for your guild!") + async def see_logging_channels(self, ctx): + """The slash command that initiates the fancy menus.""" + settings = self.bot.db_client.get_guild_settings(ctx.guild) + pretty_printed_settings = "" + for key, value in settings.items(): + if "_log" in key: + pretty_printed_settings += f"{key}: <#{value}>\n" + + await ctx.respond(f"### Guild logging channels for {ctx.guild}\n{pretty_printed_settings}") + + @commands.slash_command() + @option( + "logs" + , description="All options for logging channels." + , choices=[ + "User chat logs" + , "Join/leave logs" + , "Moderation action logs" + , "Server change logs" + , "User change logs" + , "Verification logs" + , "Zorak error logging" + ]) + async def update_logging_channels( + self + , ctx: discord.ApplicationContext + , logs: str + , channel: discord.TextChannel + ): + mapper = { + "User chat logs": "chat_log", + "Join/leave logs": "join_log", + "Moderation action logs": "mod_log", + "Server change logs": "server_change_log", + "User change logs": "user_log", + "Verification logs": "verification_log", + "Zorak error logging": "zorak_log" + } + self.bot.db_client.update_guild_settings(ctx.guild, mapper[logs], int(channel.id)) + logger.info(f"{ctx.author.name} updated {mapper[logs]} in {ctx.guild.name} to {channel.id}") + await ctx.respond(f"Updated {logs} in {ctx.guild.name}. New Value: {channel.mention}") + + +def setup(bot): + """ + Required. + """ + bot.add_cog(Settings(bot)) diff --git a/src/zorak/cogs/admin/verification_on_join.py b/src/zorak/cogs/admin/verification_on_join.py index c9107f14..b7da2697 100644 --- a/src/zorak/cogs/admin/verification_on_join.py +++ b/src/zorak/cogs/admin/verification_on_join.py @@ -18,25 +18,27 @@ class LoggingVerification(commands.Cog): def __init__(self, bot): self.bot = bot - self.feature_flag = True - + async def log_unverified_join(self, member, logging_channel): + await logging_channel.send(f"<@{member.id}> joined, but has not verified.") - # OLD LOGIC + async def kick_if_not_verified(self, member, time_to_kick, logging_channel): + await sleep(time_to_kick) - async def add_role_and_log(self, member, logging_channel): - # Add verification role - await member.add_roles(member.guild.get_role(self.bot.server_settings.unverified_role["needs_approval"])) + if "✅" not in [role.name for role in member.roles]: + await logging_channel.send( + f"{member.mention} did not verify after {int((time_to_kick / 3600))} hour/s, auto-removed.") + await member.kick(reason="Did not verify.") - async def send_welcome_message(self, guild, member): + async def send_welcome_message(self, guild, member, settings): welcome_message = f""" Hi there, {member.mention} I'm Zorak, the moderator of {guild.name}. We are very happy that you have decided to join us. Before you are allowed to chat, you need to verify that you aren't a bot. - Dont worry, it's easy. Just go to - {self.bot.get_channel(self.bot.server_settings.mod_channel['verification_channel']).mention} + Dont worry, it's easy. Just go to + {self.bot.get_channel(settings.verification_channel).mention} and click the green button. After you do, all of {guild.name} is available to you. Have a great time :-) @@ -47,55 +49,16 @@ async def send_welcome_message(self, guild, member): except discord.errors.Forbidden as catch_dat_forbidden: logger.debug(f'{member.name} cannot be sent a DM.') - async def kick_if_unverified(self, member, time_to_kick, logging_channel): - - await sleep(time_to_kick) - - # Start verification timer - if "Needs Approval" in [role.name for role in member.roles]: - # Kick timer, in seconds. - - await sleep(time_to_kick) - - if "Needs Approval" in [role.name for role in member.roles]: - # Log the kick - await logging_channel.send( - f"{member.mention} did not verify, auto-removed." f" ({int((time_to_kick / 3600))} hour/s)") - await member.kick(reason="Did not verify.") - - - - - # NEW LOGIC - - async def log_unverified_join(self, member, logging_channel): - await logging_channel.send(f"<@{member.id}> joined, but has not verified.") - - async def kick_if_not_verified(self, member, time_to_kick, logging_channel): - await sleep(time_to_kick) - - if "✅" not in [role.name for role in member.roles]: - await logging_channel.send( - f"{member.mention} did not verify after {int((time_to_kick / 3600))} hour/s, auto-removed.") - await member.kick(reason="Did not verify.") - - - - # EXECUTION @commands.Cog.listener() async def on_member_join(self, member: discord.Member): guild = member.guild - logs_channel = await self.bot.fetch_channel(self.bot.server_settings.log_channel["verification_log"]) + settings = self.bot.db_client.get_guild_settings(member.guild) - if not self.feature_flag: - await self.add_role_and_log(member, logs_channel) - await self.send_welcome_message(guild, member) - await self.kick_if_unverified(member, 3600, logs_channel) + logs_channel = await self.bot.fetch_channel(settings["verification_log"]) - if self.feature_flag: - await self.log_unverified_join(member, logs_channel) - await self.send_welcome_message(guild, member) - await self.kick_if_not_verified(member, 3600, logs_channel) + await self.log_unverified_join(member, logs_channel) + await self.send_welcome_message(guild, member, settings) + await self.kick_if_not_verified(member, 3600, logs_channel) def setup(bot): diff --git a/src/zorak/cogs/admin/verification_on_verified.py b/src/zorak/cogs/admin/verification_on_verified.py index 9514e10f..5e96a3a8 100644 --- a/src/zorak/cogs/admin/verification_on_verified.py +++ b/src/zorak/cogs/admin/verification_on_verified.py @@ -11,6 +11,8 @@ from discord.ext import commands from time import sleep + + logger = logging.getLogger(__name__) @@ -19,8 +21,9 @@ class VerificationSelector(discord.ui.Select): This is the dropdown selection menu that the user interacts with. """ - def __init__(self, discord_bot, selector_data): + def __init__(self, discord_bot, selector_data, settings): self.bot = discord_bot + self.settings = settings self.name = selector_data["name"] self.single_choice = selector_data["single_choice"] self.description = selector_data["description"] @@ -33,7 +36,7 @@ def __init__(self, discord_bot, selector_data): super().__init__(placeholder=selector_data["description"], options=options) - async def send_wrong_button_message(self, guild, member): + async def send_wrong_button_message(self, member): try: await member.send( f""" @@ -43,7 +46,7 @@ async def send_wrong_button_message(self, guild, member): ** _Make sure you press the "Verify me!" button to verify yourself._ ** Please join the server again and try again. - {self.bot.server_settings.server_info['invite']} + {self.settings.info['invite']} """ ) except discord.errors.Forbidden as catch_dat_forbidden: @@ -98,12 +101,12 @@ class SelectView(discord.ui.View): under it. We also define our button timeout here. Consider this the entrypoint for all the other classes defined above. """ - def __init__(self, bot, verification_data, *, timeout=180): + def __init__(self, bot, verification_data, settings, *, timeout=180): super().__init__(timeout=timeout) selectors = verification_data["selectors"] shuffle(selectors) for menu in selectors: - self.add_item(VerificationSelector(bot, selectors[menu])) + self.add_item(VerificationSelector(bot, selectors[menu], settings)) class Verification(commands.Cog): @@ -121,14 +124,15 @@ def __init__(self, bot): @commands.slash_command(description="Verification!") async def verify(self, ctx): """The slash command that initiates the fancy menus.""" - if hasattr(self.bot.server_settings, "verification_options"): - if "selectors" in self.bot.server_settings.verification_options: + settings = self.bot.db_client.get_guild_settings(ctx.guild) + if hasattr(self.bot.settings, "verification_options"): # TODO: THIS IS FROM THE OLD SETTINGS + if "selectors" in self.bot.settings.verification_options: # TODO: THIS IS FROM THE OLD SETTINGS if "✅" not in [role.name for role in ctx.author.roles]: await ctx.respond( "# ~ Verification ~ \n" "_Before you can join the server, we need to make sure you are not a robot._\n" "_Please answer the following question._" - , view=SelectView(self.bot, self.bot.server_settings.verification_options) + , view=SelectView(self.bot, self.bot.settings.verification_options, settings) # TODO: THIS IS FROM THE OLD SETTINGS , ephemeral=True ) else: diff --git a/src/zorak/cogs/general/general_help.py b/src/zorak/cogs/general/general_help.py index aa1913dd..c6511acc 100644 --- a/src/zorak/cogs/general/general_help.py +++ b/src/zorak/cogs/general/general_help.py @@ -30,16 +30,16 @@ async def on_timeout(self): async def first_button_callback(self, button, interaction): # staff_role = interaction.guild.get_role(admin_roles["staff"]) embed = discord.Embed( - title=f"{self.server_settings.server_info['name']}", - description=f"**Website** \n{self.server_settings.server_info['website']}\n\n" + title=f"{self.server_settings.info['name']}", + description=f"**Website** \n{self.server_settings.info['website']}\n\n" f"**Owner** \n{interaction.guild.owner.mention}\n\n" # pylint: disable=W1401 - f"**Email** \n{self.server_settings.server_info['email']}\n\n" # pylint: disable=W1401 - f"**Invite Link** \n{self.server_settings.server_info['invite']}\n\n" # pylint: disable=W1401 - f"**Leave a reveiw** \n{self.server_settings.server_info['review']}\n\n" # pylint: disable=W1401 + f"**Email** \n{self.server_settings.info['email']}\n\n" # pylint: disable=W1401 + f"**Invite Link** \n{self.server_settings.info['invite']}\n\n" # pylint: disable=W1401 + f"**Leave a reveiw** \n{self.server_settings.info['review']}\n\n" # pylint: disable=W1401 f"**Questions?** \nMake a ticket using **/ticket**, or send us an email.", color=discord.Color.yellow(), ) - embed.set_thumbnail(url=self.server_settings.server_info["logo"]) + embed.set_thumbnail(url=self.server_settings.info["logo"]) await interaction.response.send_message(embed=embed) @discord.ui.button(label="Running code", row=0, style=discord.ButtonStyle.success) @@ -69,7 +69,7 @@ async def help(self, ctx): A standard slash command. """ logger.info("%s used the %s command.", ctx.author.name, ctx.command) - await ctx.respond("What do you want, human?", view=HelpButtons(self.bot.server_settings, timeout=120)) + await ctx.respond("What do you want, human?", view=HelpButtons(self.bot.settings, timeout=120)) def setup(bot): diff --git a/src/zorak/cogs/general/general_suggest.py b/src/zorak/cogs/general/general_suggest.py deleted file mode 100644 index 5801253a..00000000 --- a/src/zorak/cogs/general/general_suggest.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -Adds an embed question, with a thumbsup and thumbsdown emoji -for voting on things. -""" -import logging -import discord -from discord.ext import commands - - -from zorak.utilities.cog_helpers._embeds import embed_suggestions, embed_suggestion_error # pylint: disable=E0401 - - -logger = logging.getLogger(__name__) - - -class GeneralSuggest(commands.Cog): - """ - Adds an embed question, with a thumbsup and thumbsdown emoji - for voting on things. - """ - def __init__(self, bot): - self.bot = bot - - @commands.slash_command() - async def suggest(self, ctx, question): - """ - Adds an embed question, with a thumbsup and thumbsdown emoji - for voting on things. - """ - logger.info("%s used the %s command." - , ctx.author.name - , ctx.command) - - ''' - If the channel name/ID of the command matches with the current - channel, the suggestion will be posted. If not, an error message will occur. - ''' - suggest_channel = await self.bot.fetch_channel(self.bot.server_settings.normal_channel["suggestions_channel"]) - if ctx.channel_id == suggest_channel.id: - await ctx.response.send_message(embed=embed_suggestions(ctx.author, question)) - msg = await ctx.interaction.original_response() - - await msg.add_reaction(emoji="👍") - await msg.add_reaction(emoji="👎") - - else: - await ctx.respond(embed=embed_suggestion_error(suggest_channel)) - - -def setup(bot): - """Required.""" - bot.add_cog(GeneralSuggest(bot)) diff --git a/src/zorak/cogs/general/general_tickets.py b/src/zorak/cogs/general/general_tickets.py index 45c80f6a..a576789d 100644 --- a/src/zorak/cogs/general/general_tickets.py +++ b/src/zorak/cogs/general/general_tickets.py @@ -6,6 +6,9 @@ import discord from discord.ext import commands + + + logger = logging.getLogger(__name__) @@ -22,10 +25,11 @@ async def ticket(self, ctx): """ A simple command with a view. """ + settings = self.bot.db_client.get_guild_settings(ctx.guild) logger.info("%s used the %s command.", ctx.author.name, ctx.command) await ctx.respond( "Do you need help, or do you have a question for the Staff?", - view=MakeATicket(self.bot.server_settings), + view=MakeATicket(settings), ephemeral=True, ) @@ -35,9 +39,9 @@ class MakeATicket(discord.ui.View): A UI component that sends a button, which does other things. """ - def __init__(self, server_settings, *, timeout=None): + def __init__(self, settings, *, timeout=None): super().__init__(timeout=timeout) - self.server_settings = server_settings + self.settings = settings @discord.ui.button(label="Open a support Ticket", style=discord.ButtonStyle.primary) async def button_callback(self, button, interaction): @@ -49,8 +53,8 @@ async def button_callback(self, button, interaction): button.disabled = True await interaction.edit_original_response(view=self) - support = await interaction.guild.fetch_channel(self.server_settings.mod_channel["server_support"]) - staff = interaction.guild.get_role(self.server_settings.admin_roles["staff"]) + support = await interaction.guild.fetch_channel(self.settings["support_channel"]) + staff = interaction.guild.get_role(self.settings["staff_role"]) ticket = await support.create_thread( name=f"[Ticket] - {interaction.user}", @@ -60,6 +64,7 @@ async def button_callback(self, button, interaction): reason=None, ) + # TODO: Can just mention the staff role here instead of a for loop for person in interaction.guild.members: if staff in person.roles: await ticket.add_user(person) diff --git a/src/zorak/cogs/logging/logging_avatars.py b/src/zorak/cogs/logging/logging_avatars.py index 96ebe276..6f34dbcd 100644 --- a/src/zorak/cogs/logging/logging_avatars.py +++ b/src/zorak/cogs/logging/logging_avatars.py @@ -11,19 +11,21 @@ class LoggingAvatars(commands.Cog): """ Simple listener to on_user_update """ - def __init__(self, bot): self.bot = bot @commands.Cog.listener() async def on_user_update(self, before, after): + """ if the avatar before is != to the avatar after, do stuff. """ + settings = self.bot.db_client.get_guild_settings(after.guild) + if before.avatar != after.avatar: embed = embed_avatar(before, after) - logs_channel = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) + logs_channel = await self.bot.fetch_channel(settings["mod_log"]) await logs_channel.send(embed=embed) diff --git a/src/zorak/cogs/logging/logging_member_ban.py b/src/zorak/cogs/logging/logging_member_ban.py index 34e8b7ce..7c259c51 100644 --- a/src/zorak/cogs/logging/logging_member_ban.py +++ b/src/zorak/cogs/logging/logging_member_ban.py @@ -24,14 +24,16 @@ async def on_member_remove(self, member): if "Needs Approval" in [role.name for role in member.roles]: return - current_guild = self.bot.get_guild(self.bot.server_settings.server_info["id"]) + settings = self.bot.db_client.get_guild_settings(member.guild) + + current_guild = self.bot.get_guild(settings["id"]) audit_log = [entry async for entry in current_guild.audit_logs(limit=1)][0] if str(audit_log.action) == "AuditLogAction.ban": if audit_log.target == member: embed = embed_ban(member, audit_log) - logs_channel = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) + logs_channel = await self.bot.fetch_channel(settings["mod_log"]) await logs_channel.send(embed=embed) return diff --git a/src/zorak/cogs/logging/logging_member_kick.py b/src/zorak/cogs/logging/logging_member_kick.py index 156f5c5d..5fd57c9c 100644 --- a/src/zorak/cogs/logging/logging_member_kick.py +++ b/src/zorak/cogs/logging/logging_member_kick.py @@ -24,14 +24,15 @@ async def on_member_remove(self, member): if "Needs Approval" in [role.name for role in member.roles]: return - current_guild = self.bot.get_guild(self.bot.server_settings.server_info["id"]) + settings = self.bot.db_client.get_guild_settings(member.guild) + current_guild = self.bot.get_guild(settings["id"]) audit_log = [entry async for entry in current_guild.audit_logs(limit=1)][0] if str(audit_log.action) == "AuditLogAction.kick": if audit_log.target == member: embed = embed_kick(member, audit_log) - logs_channel = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) + logs_channel = await self.bot.fetch_channel(settings["mod_log"]) await logs_channel.send(embed=embed) return diff --git a/src/zorak/cogs/logging/logging_member_leaving.py b/src/zorak/cogs/logging/logging_member_leaving.py index 2786c60e..c2b9baa5 100644 --- a/src/zorak/cogs/logging/logging_member_leaving.py +++ b/src/zorak/cogs/logging/logging_member_leaving.py @@ -24,13 +24,14 @@ async def on_member_remove(self, member): if "Needs Approval" in [role.name for role in member.roles]: return - current_guild = self.bot.get_guild(self.bot.server_settings.server_info["id"]) + settings = self.bot.db_client.get_guild_settings(member.guild) + current_guild = self.bot.get_guild(settings["id"]) audit_log = [entry async for entry in current_guild.audit_logs(limit=1)][0] if str(audit_log.action) != "AuditLogAction.ban" and str(audit_log.action) != "AuditLogAction.kick": embed = embed_leave(member) - logs_channel = await self.bot.fetch_channel(self.bot.server_settings.log_channel["join_log"]) + logs_channel = await self.bot.fetch_channel(settings["join_log"]) await logs_channel.send(embed=embed) diff --git a/src/zorak/cogs/logging/logging_member_roles.py b/src/zorak/cogs/logging/logging_member_roles.py index 9227e0c2..3c7ffed4 100644 --- a/src/zorak/cogs/logging/logging_member_roles.py +++ b/src/zorak/cogs/logging/logging_member_roles.py @@ -23,7 +23,9 @@ async def on_member_update(self, before, after): Checks what roles were changed, and logs it in the log channel. Can be quite spammy. """ - current_guild = self.bot.get_guild(self.bot.server_settings.server_info["id"]) + settings = self.bot.db_client.get_guild_settings(after.guild) + + current_guild = self.bot.get_guild(settings["id"]) audit_log = [entry async for entry in current_guild.audit_logs(limit=1)][0] if str(audit_log.action) == "AuditLogAction.member_role_update": @@ -31,7 +33,7 @@ async def on_member_update(self, before, after): responsible_member = audit_log.user changed_roles = [] - logs_channel = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) + logs_channel = await self.bot.fetch_channel(settings["mod_log"]) if len(before.roles) > len(after.roles): for role in before.roles: if role not in after.roles: diff --git a/src/zorak/cogs/logging/logging_member_unban.py b/src/zorak/cogs/logging/logging_member_unban.py index 5f10ea28..b490e4a9 100644 --- a/src/zorak/cogs/logging/logging_member_unban.py +++ b/src/zorak/cogs/logging/logging_member_unban.py @@ -20,8 +20,9 @@ async def on_member_unban(self, member): Just listen for the event, embed it, and send it off. """ embed = embed_unban(member) + settings = self.bot.db_client.get_guild_settings(member.guild) - logs_channel = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) + logs_channel = await self.bot.fetch_channel(settings["mod_log"]) await logs_channel.send(embed=embed) diff --git a/src/zorak/cogs/logging/logging_message_delete.py b/src/zorak/cogs/logging/logging_message_delete.py index 0b1f1f57..34c1249c 100644 --- a/src/zorak/cogs/logging/logging_message_delete.py +++ b/src/zorak/cogs/logging/logging_message_delete.py @@ -2,12 +2,14 @@ logs when a message is deleted. """ from datetime import datetime - +import logging import discord from discord.ext import commands from zorak.utilities.cog_helpers._embeds import embed_message_delete +logger = logging.getLogger(__name__) + class LoggingMessageDelete(commands.Cog): """ @@ -23,14 +25,15 @@ async def on_message_delete(self, message): """ If a mod deletes, take the audit log event. If a user deletes, handle it normally. """ - current_guild = self.bot.get_guild(self.bot.server_settings.server_info['id']) + settings = self.bot.db_client.get_guild_settings(message.guild) + current_guild = settings["id"] audit_log = [entry async for entry in current_guild.audit_logs(limit=1)][0] - logs_channel = await self.bot.fetch_channel(self.bot.server_settings.log_channel["chat_log"]) + logs_channel = await self.bot.fetch_channel(settings["chat_log"]) # If the audit log is triggered, it means someone OTHER than the author deleted the message. # https://discordpy.readthedocs.io/en/stable/api.html # ?highlight=audit%20log#discord.AuditLogAction.message_delete - if str(audit_log.action) == 'AuditLogAction.message_delete': + if str(audit_log.action) == 'AuditLogAction.message_delete' and audit_log.user == message.author: embed = embed_message_delete(audit_log.user, message) await logs_channel.send(embed=embed) @@ -40,7 +43,7 @@ async def on_message_delete(self, message): username = message.author for role in message.author.roles: - if role.id in self.bot.server_settings.admin_roles.values(): + if role.id in [settings['staff_role'], settings["admin_role"], settings["owner_role"]]: await logs_channel.send(embed=embed_message_delete(username, message)) return diff --git a/src/zorak/cogs/logging/logging_message_edit.py b/src/zorak/cogs/logging/logging_message_edit.py index bca8489a..80595105 100644 --- a/src/zorak/cogs/logging/logging_message_edit.py +++ b/src/zorak/cogs/logging/logging_message_edit.py @@ -1,12 +1,14 @@ """ Logs when messages are edited. """ +import logging from discord.ext import commands from zorak.utilities.cog_helpers._embeds import ( embed_message_edit, # pylint: disable=E0401 ) +logger = logging.getLogger(__name__) class LoggingMessageEdit(commands.Cog): """ @@ -18,6 +20,11 @@ def __init__(self, bot): @commands.Cog.listener() async def on_message_edit(self, message_before, message_after): + # Ignore any bot messages + if message_before.author.bot or message_after.author.bot: + return + + settings = self.bot.db_client.get_guild_settings(message_before.guild) # IGNORE /run, since we will set up an on_message_edit handler there with opposite logic if message_before.content.startswith('/run') or message_after.content.startswith('/run'): @@ -33,13 +40,12 @@ async def on_message_edit(self, message_before, message_after): author = message_before.author for role in message_before.author.roles: - if role.id not in self.bot.server_settings.admin_roles.values(): + if role.id not in settings.admin_roles.values(): # Dont log admin actions. # This just gets really messy when we are cleaning things up # or doing dodgy business in secret places. - embed = embed_message_edit(username, author, message_before, message_after) - logs_channel = await self.bot.fetch_channel(self.bot.server_settings.log_channel["chat_log"]) + logs_channel = await self.bot.fetch_channel(settings["chat_log"]) await logs_channel.send(embed=embed) return diff --git a/src/zorak/cogs/logging/logging_name_changes.py b/src/zorak/cogs/logging/logging_name_changes.py index a34f5cd5..30a657ce 100644 --- a/src/zorak/cogs/logging/logging_name_changes.py +++ b/src/zorak/cogs/logging/logging_name_changes.py @@ -22,6 +22,8 @@ async def on_member_update(self, before, after): """ Just checking if the name before is != to the name after. """ + + if before.nick is None: username_before = before else: @@ -32,11 +34,13 @@ async def on_member_update(self, before, after): else: username_after = after.nick + settings = self.bot.db_client.get_guild_settings(before.guild if before.guild is not None else after.guild) + if before.nick != after.nick and before.nick is not None: embed = embed_name_change(before, after, username_before, username_after) - logs_channel = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) - await logs_channel.send(f"{username_after.mention}", embed=embed) + logs_channel = await self.bot.fetch_channel(settings["mod_log"]) + await logs_channel.send(f"{after.mention}", embed=embed) def setup(bot): diff --git a/src/zorak/cogs/logging/logging_tickets.py b/src/zorak/cogs/logging/logging_tickets.py index 9520d1ab..43326334 100644 --- a/src/zorak/cogs/logging/logging_tickets.py +++ b/src/zorak/cogs/logging/logging_tickets.py @@ -20,18 +20,20 @@ def __init__(self, bot): self.bot = bot @commands.Cog.listener() - async def on_thread_create(self): + async def on_thread_create(self, thread): """ When a thread is created """ - current_guild = self.bot.get_guild(self.bot.server_settings.server_info["id"]) + settings = self.bot.db_client.get_guild_settings(thread.guild) + + current_guild = settings["id"] audit_log = [entry async for entry in current_guild.audit_logs(limit=1)] entry = audit_log[0] target = "AuditLogAction.thread_create" if str(entry.action) == target and str(entry.target).startswith("[Ticket]"): - mod_log = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) + mod_log = await self.bot.fetch_channel(settings["mod_log"]) embed = embed_ticket_create(entry.user, entry.target.mention) await mod_log.send(embed=embed) return @@ -41,7 +43,8 @@ async def on_thread_update(self, before): """ When a thread is updated, deleted or removed. """ - current_guild = self.bot.get_guild(self.bot.server_settings.server_info["id"]) + settings = self.bot.db_client.get_guild_settings(before.guild) + current_guild = settings["id"] audit_log = [entry async for entry in current_guild.audit_logs(limit=1)] entry = audit_log[0] @@ -50,19 +53,19 @@ async def on_thread_update(self, before): remove = "AuditLogAction.thread_remove" if str(entry.action) == update and str(entry.target).startswith("[Ticket]"): - logs_channel = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) + logs_channel = await self.bot.fetch_channel(settings["mod_log"]) embed = embed_ticket_update(entry.user, before.id) await logs_channel.send(embed=embed) return if str(entry.action) == delete and str(entry.target).startswith("[Ticket]"): - logs_channel = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) + logs_channel = await self.bot.fetch_channel(settings["mod_log"]) embed = embed_ticket_delete(entry.user, before.id) await logs_channel.send(embed=embed) return if str(entry.action) == remove and str(entry.target).startswith("[Ticket]"): - logs_channel = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) + logs_channel = await self.bot.fetch_channel(settings["mod_log"]) embed = embed_ticket_remove(entry.user, before.id) await logs_channel.send(embed=embed) return diff --git a/src/zorak/cogs/utility/_event_challenges.py b/src/zorak/cogs/utility/_event_challenges.py index cee5d63a..fdc978cf 100644 --- a/src/zorak/cogs/utility/_event_challenges.py +++ b/src/zorak/cogs/utility/_event_challenges.py @@ -39,7 +39,7 @@ async def challenge(self, ctx, day): ) embed.set_thumbnail( url="https://raw.githubusercontent.com/Xarlos89/" - "PracticalPython/main/logo.png" + "prod/main/logo.png" ) embed.add_field( name=f"Challenge #{day}:", value=current_day, inline=True diff --git a/src/zorak/cogs/utility/_rss.py b/src/zorak/cogs/utility/_rss.py index b9bc3bda..c2c81cef 100644 --- a/src/zorak/cogs/utility/_rss.py +++ b/src/zorak/cogs/utility/_rss.py @@ -1,134 +1,134 @@ -""" -Grabs info from an RSS feed, and posts it in the server. TODO Clean - -this cog is retired, and not loaded by the bot. If we want to add the features again, we can. -""" -from asyncio import sleep - -import discord -import feedparser -import html2text -from discord.ext import commands - - -class RSS(commands.Cog): - """ - The main class for the RSS handler. - """ - - def __init__(self, bot): - self.bot = bot - - @commands.Cog.listener() - async def rss_on_ready(self): - """ - This gets called to check if new stories are ready. - """ - - def get_ids(): - """Grabs the most recent article ID from each URL in - the TOML, so that we can check what we have already sent""" - most_recent_ids = [] - - for news_source in self.bot.server_settings.rss_feed.keys(): - news_feed = feedparser.parse(self.bot.server_settings.rss_feed[news_source]) - - id_code = news_feed["entries"][0]["id"] - most_recent_ids.append(id_code) - - return most_recent_ids - - def send_news(id_to_send): - """Uses the RSS ID's we grab from get_IDs that are NOT already in the DB. - Sends out the news story if it is not in our DB. - Each news story is parsed 'slightly' different, which is annoying.""" - - def send_python_software_foundation(id_tag): - newsfeed = feedparser.parse(self.bot.server_settings.rss_feed["Python_Software_foundation"]) - id_code = newsfeed["entries"][0]["id"] - if id_tag == id_code: - # if the ID code in the article matches the ID we grabbed in get_IDs(), send. - html_reader = html2text.HTML2Text() - html_reader.ignore_links = False - - embed = discord.Embed( - title=newsfeed["entries"][0]["title"], - description=f"By: {newsfeed['entries'][0]['authors'][0]['name']}" f" at the Python Software Foundation!", - color=discord.Color.blue(), - ) - - embed.add_field( - name="Preview: ", - value=f"{html_reader.handle(newsfeed['entries'][0]['summary'])[0:1015]}" f" ...", - inline=False, - ) - - embed.add_field( - name="Read more: ", - value=f"[{newsfeed['entries'][0]['link']}]" f"({newsfeed['entries'][0]['link']})", - inline=False, - ) - embed.set_thumbnail(url="https://www.python.org/static/img/python-logo@2x.png") - - return embed - return None - - def send_jetbrains(id_tag): - jetbrains = self.bot.server_settings.rss_feed["jetbrains"] - newsfeed = feedparser.parse(jetbrains) - id_code = newsfeed["entries"][0]["id"] - if id_tag == id_code: - # if the ID code in the article matches the ID we grabbed in get_IDs(), send. - - html_reader = html2text.HTML2Text() - html_reader.ignore_links = False - - # html = html_reader.handle(NewsFeed['entries'][0]['content'][0]['value']) - - embed = discord.Embed( - title=f"**{newsfeed['entries'][0]['title']}**", - description=f"By: **{newsfeed['entries'][0]['author']}** at Jetbrains!", - color=discord.Color.blue(), - ) - - summary_text = f"{newsfeed['entries'][0]['summary'][0:1015]} ..." - embed.add_field(name="**Preview: **", value=summary_text, inline=False) - embed.add_field( - name="**Read more: **", - value=f"[{newsfeed['entries'][0]['link']}]" f"({newsfeed['entries'][0]['link']})", - inline=False, - ) - embed.set_thumbnail(url=newsfeed["entries"][0]["featuredimage"]) - return embed - return None - - embed = send_python_software_foundation(id_to_send) - embed2 = send_jetbrains(id_to_send) - if embed is not None: - return embed - return embed2 - - while True: - sent_id_list = [] - - all_ids = self.bot.db_client.get_all_stories() - for id_feed in all_ids: - sent_id_list.append(id_feed) - - print(sent_id_list) - story_queue = get_ids() - - for entry in story_queue: - if entry not in sent_id_list: - news_channel = await self.bot.fetch_channel(self.bot.server_settings.normal_channel["news_channel"]) - - await news_channel.send(embed=send_news(entry)) - self.bot.db_client.add_story_to_table(entry) - - wait_time_in_seconds = 86400 # 24 hour - await sleep(wait_time_in_seconds) - - -def setup(bot): - """required""" - bot.add_cog(RSS(bot)) +# """ +# Grabs info from an RSS feed, and posts it in the server. TODO Clean +# +# this cog is retired, and not loaded by the bot. If we want to add the features again, we can. +# """ +# from asyncio import sleep +# +# import discord +# import feedparser +# import html2text +# from discord.ext import commands +# +# +# class RSS(commands.Cog): +# """ +# The main class for the RSS handler. +# """ +# +# def __init__(self, bot): +# self.bot = bot +# +# @commands.Cog.listener() +# async def rss_on_ready(self): +# """ +# This gets called to check if new stories are ready. +# """ +# +# def get_ids(): +# """Grabs the most recent article ID from each URL in +# the TOML, so that we can check what we have already sent""" +# most_recent_ids = [] +# +# for news_source in self.bot.server_settings.rss_feed.keys(): +# news_feed = feedparser.parse(self.bot.server_settings.rss_feed[news_source]) +# +# id_code = news_feed["entries"][0]["id"] +# most_recent_ids.append(id_code) +# +# return most_recent_ids +# +# def send_news(id_to_send): +# """Uses the RSS ID's we grab from get_IDs that are NOT already in the DB. +# Sends out the news story if it is not in our DB. +# Each news story is parsed 'slightly' different, which is annoying.""" +# +# def send_python_software_foundation(id_tag): +# newsfeed = feedparser.parse(self.bot.server_settings.rss_feed["Python_Software_foundation"]) +# id_code = newsfeed["entries"][0]["id"] +# if id_tag == id_code: +# # if the ID code in the article matches the ID we grabbed in get_IDs(), send. +# html_reader = html2text.HTML2Text() +# html_reader.ignore_links = False +# +# embed = discord.Embed( +# title=newsfeed["entries"][0]["title"], +# description=f"By: {newsfeed['entries'][0]['authors'][0]['name']}" f" at the Python Software Foundation!", +# color=discord.Color.blue(), +# ) +# +# embed.add_field( +# name="Preview: ", +# value=f"{html_reader.handle(newsfeed['entries'][0]['summary'])[0:1015]}" f" ...", +# inline=False, +# ) +# +# embed.add_field( +# name="Read more: ", +# value=f"[{newsfeed['entries'][0]['link']}]" f"({newsfeed['entries'][0]['link']})", +# inline=False, +# ) +# embed.set_thumbnail(url="https://www.python.org/static/img/python-logo@2x.png") +# +# return embed +# return None +# +# def send_jetbrains(id_tag): +# jetbrains = self.bot.server_settings.rss_feed["jetbrains"] +# newsfeed = feedparser.parse(jetbrains) +# id_code = newsfeed["entries"][0]["id"] +# if id_tag == id_code: +# # if the ID code in the article matches the ID we grabbed in get_IDs(), send. +# +# html_reader = html2text.HTML2Text() +# html_reader.ignore_links = False +# +# # html = html_reader.handle(NewsFeed['entries'][0]['content'][0]['value']) +# +# embed = discord.Embed( +# title=f"**{newsfeed['entries'][0]['title']}**", +# description=f"By: **{newsfeed['entries'][0]['author']}** at Jetbrains!", +# color=discord.Color.blue(), +# ) +# +# summary_text = f"{newsfeed['entries'][0]['summary'][0:1015]} ..." +# embed.add_field(name="**Preview: **", value=summary_text, inline=False) +# embed.add_field( +# name="**Read more: **", +# value=f"[{newsfeed['entries'][0]['link']}]" f"({newsfeed['entries'][0]['link']})", +# inline=False, +# ) +# embed.set_thumbnail(url=newsfeed["entries"][0]["featuredimage"]) +# return embed +# return None +# +# embed = send_python_software_foundation(id_to_send) +# embed2 = send_jetbrains(id_to_send) +# if embed is not None: +# return embed +# return embed2 +# +# while True: +# sent_id_list = [] +# +# all_ids = self.bot.db_client.get_all_stories() +# for id_feed in all_ids: +# sent_id_list.append(id_feed) +# +# print(sent_id_list) +# story_queue = get_ids() +# +# for entry in story_queue: +# if entry not in sent_id_list: +# news_channel = await self.bot.fetch_channel(self.bot.server_settings.normal_channel["news_channel"]) +# +# await news_channel.send(embed=send_news(entry)) +# self.bot.db_client.add_story_to_table(entry) +# +# wait_time_in_seconds = 86400 # 24 hour +# await sleep(wait_time_in_seconds) +# +# +# def setup(bot): +# """required""" +# bot.add_cog(RSS(bot)) diff --git a/src/zorak/cogs/utility/points.py b/src/zorak/cogs/utility/points.py index 16d34d02..66267a29 100644 --- a/src/zorak/cogs/utility/points.py +++ b/src/zorak/cogs/utility/points.py @@ -40,10 +40,12 @@ async def on_message(self, message: discord.Message): @commands.Cog.listener() async def on_message_delete(self, message: discord.Message): """When a member deletes a message, remove a point.""" + settings = self.bot.db_client.get_guild_settings(message.guild) message_value = len(message.content.split(" ")) - mod_log = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) + mod_log = await self.bot.fetch_channel(settings["mod_log"]) await mod_log.send(f"{message_value} Point/s removed from {message.author} for deleting a message.") self.bot.db_client.remove_points_from_user(message.author.id, abs(message_value)) + # # # TODO: Fix the backup command. # # @commands.slash_command() @@ -64,9 +66,10 @@ async def add_all_members_to_db(self, ctx): @commands.has_any_role("Admin", "Sudo", "Staff", "Project Manager") async def add_points_to_user(self, ctx, mention, points): """Add points to a user.""" + settings = self.bot.db_client.get_guild_settings(ctx.guild) user = self.bot.get_user(int(mention.split("@")[1].split(">")[0])) self.bot.db_client.add_points_to_user(user.id, int(points)) - mod_log = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) + mod_log = await self.bot.fetch_channel(settings["mod_log"]) await mod_log.send(f"{points} point{('s', '')[abs(int(points)) == 1]} added to {mention} by {ctx.author}.") await ctx.respond(f"{points} point{('s', '')[abs(int(points)) == 1]} added to {mention}.") @@ -74,8 +77,9 @@ async def add_points_to_user(self, ctx, mention, points): @commands.has_any_role("Admin", "Sudo", "Staff", "Project Manager") async def add_points_to_all_users(self, ctx, points): """Add points to all users.""" + settings = self.bot.db_client.get_guild_settings(ctx.guild) self.bot.db_client.add_points_to_all_users(int(points)) - mod_log = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) + mod_log = await self.bot.fetch_channel(settings["mod_log"]) await mod_log.send(f"{points} point{('s', '')[abs(int(points)) == 1]} added to all users by {ctx.author}.") await ctx.respond(f"{points} point{('s', '')[abs(int(points)) == 1]} added to all users.") @@ -85,9 +89,10 @@ async def remove_points_from_user(self, ctx, mention, points): """Remove points from a user.""" mention = str(mention) points = int(points) + settings = self.bot.db_client.get_guild_settings(ctx.guild) user = self.bot.get_user(int(mention.split("@")[1].split(">")[0])) self.bot.db_client.remove_points_from_user(user.id, points) - mod_log = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) + mod_log = await self.bot.fetch_channel(settings["mod_log"]) await mod_log.send(f"{points} point{('s', '')[abs(points) == 1]} removed from {mention} by {ctx.author}.") await ctx.respond(f"{points} point{('s', '')[abs(points) == 1]} removed from {mention}.") @@ -96,8 +101,9 @@ async def remove_points_from_user(self, ctx, mention, points): async def remove_points_from_all_users(self, ctx, points): """Remove points from all users.""" points = int(points) + settings = self.bot.db_client.get_guild_settings(ctx.guild) self.bot.db_client.remove_points_from_all_users(points) - mod_log = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) + mod_log = await self.bot.fetch_channel(settings["mod_log"]) await mod_log.send(f"{points} point{('s', '')[abs(points) == 1]} removed from all users by {ctx.author}.") await ctx.respond(f"{points} point{('s', '')[abs(points) == 1]} removed from all users.") @@ -106,9 +112,10 @@ async def remove_points_from_all_users(self, ctx, points): async def reset_points_for_user(self, ctx, mention): """Reset points for a user.""" mention = str(mention) + settings = self.bot.db_client.get_guild_settings(ctx.guild) user = self.bot.get_user(int(mention.split("@")[1].split(">")[0])) self.bot.db_client.set_user_points(user.id, 0) - mod_log = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) + mod_log = await self.bot.fetch_channel(settings["mod_log"]) await mod_log.send(f"Points reset for {mention} by {ctx.author}.") await ctx.respond(f"Points reset for {mention}.") @@ -116,8 +123,9 @@ async def reset_points_for_user(self, ctx, mention): @commands.has_any_role("Admin", "Sudo", "Staff", "Project Manager") async def reset_points_for_all_users(self, ctx): """Reset points for all users.""" + settings = self.bot.db_client.get_guild_settings(ctx.guild) self.bot.db_client.set_all_user_points(0) - mod_log = await self.bot.fetch_channel(self.bot.server_settings.log_channel["mod_log"]) + mod_log = await self.bot.fetch_channel(settings["mod_log"]) await mod_log.send(f"Points reset for all users by {ctx.author}.") await ctx.respond("Points reset for all users.") @@ -132,17 +140,19 @@ async def get_points_for_user(self, ctx, mention): @commands.slash_command() async def leaderboard(self, ctx): """Get your points.""" + settings = self.bot.db_client.get_guild_settings(ctx.guild) def is_staff(member_obj): """ Tells us if the 'member_obj' has an admin role. """ - for role in self.bot.server_settings.admin_roles: - if self.bot.server_settings.admin_roles[role] in [y.id for y in member_obj.roles]: + # TODO: .admin_roles no longer exists, find a workaround here. + for role in settings.admin_roles: + if settings.admin_roles[role] in [y.id for y in member_obj.roles]: return True return False top10_no_staff = [] points = self.bot.db_client.get_top_10() - guild = self.bot.get_guild(self.bot.server_settings.server_info['id']) + guild = self.bot.get_guild(settings['id']) for iteration, person in enumerate(points): if len(top10_no_staff) < 10: # should only allow 10 people into the list @@ -152,8 +162,8 @@ def is_staff(member_obj): else: return - embed = embed_leaderboard(top10_no_staff, self.bot.server_settings.server_info['name'], - self.bot.server_settings.server_info['logo']) + embed = embed_leaderboard(top10_no_staff, settings.info['name'], + settings.info['logo']) await ctx.respond(embed=embed) diff --git a/src/zorak/utilities/core/README.md b/src/zorak/utilities/core/README.md index c4fb2ff2..6b0f6e26 100644 --- a/src/zorak/utilities/core/README.md +++ b/src/zorak/utilities/core/README.md @@ -12,3 +12,15 @@ Contains a function to abstract the configuration and instatiation of Python's b ### mongo.py Contains a class that implements a MongoDB client and abstracts a number of commonly used functions for ease of use. + +### channels.py +Contains the class for turning channels into channel IDs + +### info.py +Contains the class for getting information about a server. + +### log.py +Contains the class for logging channel IDs, and logging options + +### roles.py +Contains the class for roles and role IDs diff --git a/src/zorak/utilities/core/logging_utils.py b/src/zorak/utilities/core/logging_options.py similarity index 100% rename from src/zorak/utilities/core/logging_utils.py rename to src/zorak/utilities/core/logging_options.py diff --git a/src/zorak/utilities/core/mongo.py b/src/zorak/utilities/core/mongo.py index 27fa3869..6cb3bb45 100644 --- a/src/zorak/utilities/core/mongo.py +++ b/src/zorak/utilities/core/mongo.py @@ -2,6 +2,7 @@ import logging import time from typing import Dict, List +from bson.int64 import Int64 import pymongo from discord.ext.commands import Bot @@ -191,8 +192,363 @@ class CustomMongoDBClient(MongoDBClient): """A further extension ontop of the earlier MongoDB class to abstract functions to be able to more easily interact with a custom database design. This is only intended to handle a single guild, but could be extended to handle multiple guilds by adding a guild_id field + guild, but could be extended to handle multiple guilds by adding a guild_id field to the user table, or adding a new table for each guild.""" + ############### + # Settings + ############### + def initialise_settings_table(self): + """ Initializes a GuildSettings Table """ + validator = { + "validator": { + "$jsonSchema": { + "bsonType": "object", + "title": "GuildSettings Validation", + "required": ["id", "name"], + "properties": { + "id": { + "bsonType": "int", + "description": "'id' must be an int and is required" + }, + "name": { + "bsonType": "string", + "description": "'name' must be a string and is required" + }, + "website": { + "bsonType": "string", + "description": "'website' must be a string and is required" + }, + "email": { + "bsonType": "string", + "description": "'email' must be a string and is required" + }, + "logo": { + "bsonType": "string", + "description": "'logo' must be a string if the field exists" + }, + "verification_channel": { + "bsonType": "int", + "description": "'verification_channel' must be an integer" + }, + "quarantine_channel": { + "bsonType": "int", + "description": "'quarantine_channel' must be an integer if the field exists" + }, + "support_channel": { + "bsonType": "int", + "description": "'support_channel' must be an integer if the field exists" + }, + "role_channel": { + "bsonType": "int", + "description": "'role_channel' must be an integer if the field exists" + }, + "rules_channel": { + "bsonType": "int", + "description": "'rules_channel' must be an integer if the field exists" + }, + "general_channel": { + "bsonType": "int", + "description": "'general_channel' must be an integer if the field exists" + }, + "resources_channel": { + "bsonType": "int", + "description": "'resources_channel' must be an integer if the field exists" + }, + "challenges_channel": { + "bsonType": "int", + "description": "'challenges_channel' must be an integer if the field exists" + }, + "chat_log": { + "bsonType": "int", + "description": "'chat_log' must be an integer if the field exists" + }, + "join_log": { + "bsonType": "int", + "description": "'join_log' must be an integer if the field exists" + }, + "mod_log": { + "bsonType": "int", + "description": "'mod_log' must be an integer if the field exists" + }, + "server_change_log": { + "bsonType": "int", + "description": "'server_change_log' must be an integer if the field exists" + }, + "user_log": { + "bsonType": "int", + "description": "'user_log' must be an integer if the field exists" + }, + "verification_log": { + "bsonType": "int", + "description": "'verification_log' must be an integer if the field exists" + }, + "zorak_log": { + "bsonType": "int", + "description": "'zorak_log' must be an integer if the field exists" + }, + "owner_role": { + "bsonType": "int", + "description": "'owner_role' must be an integer if the field exists" + }, + "admin_role": { + "bsonType": "int", + "description": "'admin_role' must be an integer if the field exists" + }, + "staff_role": { + "bsonType": "int", + "description": "'staff_role' must be an integer if the field exists" + }, + "networking_role": { + "bsonType": "int", + "description": "'networking_role' must be an integer if the field exists" + }, + "bot_role": { + "bsonType": "int", + "description": "'bot_role' must be an integer if the field exists" + }, + "beginner_role": { + "bsonType": "int", + "description": "'beginner_role' must be an integer if the field exists" + }, + "intermediate_role": { + "bsonType": "int", + "description": "'intermediate_role' must be an integer if the field exists" + }, + "professional_role": { + "bsonType": "int", + "description": "'professional_role' must be an integer if the field exists" + }, + "north_america_role": { + "bsonType": "int", + "description": "'north_america_role' must be an integer if the field exists" + }, + "europe_role": { + "bsonType": "int", + "description": "'europe_role' must be an integer if the field exists" + }, + "asia_role": { + "bsonType": "int", + "description": "'asia_role' must be an integer if the field exists" + }, + "africa_role": { + "bsonType": "int", + "description": "'africa_role' must be an integer if the field exists" + }, + "south_america_role": { + "bsonType": "int", + "description": "'south_america_role' must be an integer if the field exists" + }, + "oceana_role": { + "bsonType": "int", + "description": "'oceana_role' must be an integer if the field exists" + }, + "naughty_role": { + "bsonType": "int", + "description": "'naughty_role' must be an integer if the field exists" + }, + "verified_role": { + "bsonType": "int", + "description": "'verified_role' must be an integer if the field exists" + } + } + } + } + } + + self.create_collection("GuildSettings", validator_schema=validator) + logger.info("GuildSettings initialised.") + + def get_guild_settings(self, guild): + settings = self.find_one("GuildSettings", {"id": Int64(guild.id)}) + if settings: + return settings + return None + + def guild_exists_in_db(self, guild): + if self.find_one("GuildSettings", {"id": Int64(guild.id)}): + return True + return False + + def add_guild_to_table(self, guild): + if not self.find_one("GuildSettings", {"id": Int64(guild.id)}): + logger.info(f" ---- Adding {guild.name} to DB") + self.insert_one("GuildSettings", { + # Guild Info + "id": guild.id + , "name": guild.name + , "logo": guild.icon.url + , "website": None + , "email": None + , "review": None + , "invite": None + , "member_count": guild.member_count + , "premium_guild": False + , "FeatureFlag1": False + , "FeatureFlag2": False + , "FeatureFlag3": False + + # Channel Info + , "verification_channel": None + , "quarantine_channel": None + , "support_channel": None + , "role_channel": None + , "rules_channel": guild.rules_channel # Default: None + , "general_channel": None + , "resources_channel": None + , "challenges_channel": None + + # Logging Channels + , "chat_log": None + , "join_log": None + , "mod_log": None + , "server_change_log": None + , "user_log": None + , "verification_log": None + , "zorak_log": None + + # Roles + , "owner_role": None + , "admin_role": None + , "staff_role": None + , "networking_role": None + , "bot_role": guild.self_role.id # Default: None + , "beginner_role": None + , "intermediate_role": None + , "professional_role": None + , "north_america_role": None + , "europe_role": None + , "asia_role": None + , "africa_role": None + , "south_america_role": None + , "oceana_role": None + , "naughty_role": None + , "verified_role": None + }) + + def update_guild_settings(self, guild, item, value): + """Update the settings of a guild.""" + try: + self.update_one( + "GuildSettings", {"id": Int64(guild.id)}, {"$set": {item: value}} + ) # The $set operator replaces the value of a field with the specified value. + + except Exception as ohfuck: + logger.critical(f"There was an issue updating {item} with {value}. Error: {ohfuck}") + + def remove_guild_from_table(self, guild): + """Remove a guild from the GuildSettings table.""" + self.delete_one("GuildSettings", {"id": guild.id}) + + + + ############### + # Reaction Roles + ############### + def initialise_reaction_roles_table(self): + """Initialise the reaction roles table.""" + validator = { + "type": "object", + "properties": { + "ReactionRoles": { + "type": "array", + "items": { + "type": "object", + "properties": { + "guildID": {"type": "integer"}, + "name": {"type": "string"}, + "single_choice": {"type": "boolean"}, + "description": {"type": "string"}, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": {"type": "string"}, + "emoji": {"type": "string"}, + "description": {"type": "string"}, + "id": {"type": "integer"} + }, + "required": ["label", "emoji", "id"] + } + } + }, + "required": ["guildID", "name", "single_choice", "description", "options"] + } + } + }, + "required": ["ReactionRoles"] + } + self.create_collection("ReactionRoles", validator_schema=validator) + logger.info("ReactionRoles initialised.") + + def add_reaction_role_to_table(self, guild, reaction_role_data): + """Add a reaction role set to the reaction roles table for the specified guild.""" + if not self.find_one("ReactionRoles", {"guildID": Int64(guild.id), "name": reaction_role_data["name"]}): + self.insert_one("ReactionRoles", reaction_role_data) + + def remove_reaction_role_from_table(self, guild, reaction_role_data): + """Remove a reaction role set from the reaction roles table for the specified guild.""" + if self.find_one("ReactionRoles", {"guildID": Int64(guild.id), "name": reaction_role_data["name"]}): + self.delete_one({"guildID": Int64(guild.id), "name": reaction_role_data["name"]}) + + def update_reaction_role_set(self, guild, reaction_role_id, updated_data): + """Update a reaction role set in the reaction roles table for the specified guild.""" + if self.find_one("ReactionRoles", {"guildID": Int64(guild.id), "name": updated_data["name"]}): + self.update_one( + "ReactionRoles" + , {"guildID": Int64(guild.id), "name": updated_data["name"]} + , {"$set": updated_data} + ) + + def see_all_reaction_role_sets(self, guild): + """See all reaction role sets in the reaction roles table for the specified guild.""" + return self.find("ReactionRoles", {"guildID": Int64(guild.id)}) + + def see_one_reaction_role_set(self, guild, name): + """See one reaction role set from the reaction roles table for the specified guild.""" + return self.find_one("ReactionRoles", {"guildID": Int64(guild.id), "name": name}) + + + ### EXAMPLE + # Assuming you have instantiated your custom MongoDB class as db + + # Define the reaction role data + # reaction_role_id = 1 # Unique identifier for the reaction role set + # reaction_role_data = { + # "name": "Color", + # "single_choice": True, + # "description": "Select your favorite color!", + # "options": [ + # { + # "label": "Red", + # "emoji": "🔴", + # "description": "The color red", + # "id": 1234567 + # }, + # { + # "label": "Blue", + # "emoji": "🔵", + # "description": "The color blue", + # "id": 1234568 + # }, + # { + # "label": "Green", + # "emoji": "🟢", + # "description": "The color green", + # "id": 1234569 + # } + # ] + # } + # + # # Add the reaction role set to the table + # db.add_reaction_role_to_table(reaction_role_id, reaction_role_data) + + + + ############### + # Users + ############### def initialise_user_table(self): """Initialise the user table. Ensures that the UserID field is unique.""" validator = { @@ -214,43 +570,6 @@ def initialise_user_table(self): self.create_collection("UserPoints", validator_schema=validator) logger.info("User table initialised.") - def initialise_dev_times_table(self): - """Initialise the devtimes table. """ - validator = { - "bsonType": "object", - "title": "Dev Times Collection Validation", - "required": ["Username", "Country", "Timezone"], - "properties": { - "Username": { - "bsonType": "string", - "description": "The Username (Display name not UID) of the user to be displayed.", - }, - "Country": { - "bsonType": "string", - "description": "The country of the user.", - }, - "Timezone": { - "bsonType": "string", - "description": " The time according to pytz.timezone, example: Asia/Kolkata.", - }, - }, - } - self.create_collection("DevTimes", validator_schema=validator) - logger.info("Dev Times table initialised.") - - def add_dev_time_to_table(self, username: str, country: str, timezone: str): - """Add a user to the user table if they are not already in it.""" - if not self.find_one("DevTimes", {"Username": username}): - self.insert_one("DevTimes", {"Username": username, "Country": country, "Timezone": timezone}) - - def remove_dev_time_from_table(self, username: str): - """Remove a user from the user table.""" - self.delete_one("DevTimes", {"Username": username}) - - def get_all_dev_times(self): - """Get all dev times from the table.""" - return self.find("DevTimes", {}) - def add_user_to_table(self, member): """Add a user to the user table if they are not already in it.""" if not self.find_one("UserPoints", {"UserID": member.id}): @@ -309,32 +628,74 @@ def get_top_10(self): top10.append(x) return top10 - # Used for the RSS_feeds cog - - def initialise_news_table(self): - """Initialise the news table.""" + # ############### + # # RSS + # ############### + # + # def initialise_news_table(self): + # """Initialise the news table.""" + # validator = { + # "bsonType": "object", + # "title": "News Collection Validation", + # "required": ["entryID"], + # "properties": { + # "entryID": { + # "bsonType": "string", + # "description": "The ID of the story.", + # } + # }, + # } + # self.create_collection("News", validator_schema=validator) + # logger.info("News table initialised.") + # + # def add_story_to_table(self, entry_id: str): + # """Add a story to the news table if they are not already in it.""" + # if not self.find_one("News", {"entryID": entry_id}): + # self.insert_one("News", {"entryID": entry_id}) + # + # def get_all_stories(self): + # """Get all stories from the news table.""" + # return self.find("News", {}) + + ############### + # DevTimes + ############### + def initialise_dev_times_table(self): + """Initialise the devtimes table. """ validator = { "bsonType": "object", - "title": "News Collection Validation", - "required": ["entryID"], + "title": "Dev Times Collection Validation", + "required": ["Username", "Country", "Timezone"], "properties": { - "entryID": { + "Username": { "bsonType": "string", - "description": "The ID of the story.", - } + "description": "The Username (Display name not UID) of the user to be displayed.", + }, + "Country": { + "bsonType": "string", + "description": "The country of the user.", + }, + "Timezone": { + "bsonType": "string", + "description": " The time according to pytz.timezone, example: Asia/Kolkata.", + }, }, } - self.create_collection("News", validator_schema=validator) - logger.info("News table initialised.") + self.create_collection("DevTimes", validator_schema=validator) + logger.info("Dev Times table initialised.") - def add_story_to_table(self, entry_id: str): - """Add a story to the news table if they are not already in it.""" - if not self.find_one("News", {"entryID": entry_id}): - self.insert_one("News", {"entryID": entry_id}) + def add_dev_time_to_table(self, username: str, country: str, timezone: str): + """Add a user to the user table if they are not already in it.""" + if not self.find_one("DevTimes", {"Username": username}): + self.insert_one("DevTimes", {"Username": username, "Country": country, "Timezone": timezone}) + + def remove_dev_time_from_table(self, username: str): + """Remove a user from the user table.""" + self.delete_one("DevTimes", {"Username": username}) - def get_all_stories(self): - """Get all stories from the news table.""" - return self.find("News", {}) + def get_all_dev_times(self): + """Get all dev times from the table.""" + return self.find("DevTimes", {}) def initialise_bot_db( @@ -345,7 +706,8 @@ def initialise_bot_db( attempts = 0 while connected is False and attempts < 5: logger.info("Connecting to database... Attempt %s of 10", str(attempts + 1)) - db_client = CustomMongoDBClient(host="mongo", port=27017) # It creates a new instance of the CustomMongoDBClient class, + db_client = CustomMongoDBClient(host="mongo", + port=27017) # It creates a new instance of the CustomMongoDBClient class, # which abstracts our database interactions. try: time.sleep(10) @@ -361,10 +723,11 @@ def initialise_bot_db( attempts += 1 if connected: logger.info("Connected to database.") + db_client.initialise_user_table() # type: ignore - # This makes the table if it doesn't exist - # and ensures the validation rules. - db_client.initialise_news_table() # type: ignore + db_client.initialise_settings_table() # type: ignore + db_client.initialise_reaction_roles_table() # type: ignore + bot.db_client = db_client # type: ignore # This adds the db_client to the bot # object so that it can be accessed elsewhere. diff --git a/src/zorak/utilities/core/server_settings.py b/src/zorak/utilities/core/server_settings.py deleted file mode 100644 index 257388da..00000000 --- a/src/zorak/utilities/core/server_settings.py +++ /dev/null @@ -1,65 +0,0 @@ -import logging -import os - -import toml - -logger = logging.getLogger(__name__) - - -class Settings: - def __init__(self, settings_path): - server_settings_path = os.path.join(settings_path, "channel_info.toml") - try: - server_settings = toml.load(server_settings_path) - - # Server - self.server_info = server_settings["server"]["info"] - - # Channels - self.channels = server_settings["channels"] - self.mod_channel = self.channels["moderation"] - self.log_channel = self.channels["log_channel"] - self.normal_channel = self.channels["normal_channel"] - - # Roles - self.user_roles = server_settings["user_roles"] - self.admin_roles = self.user_roles["admin"] - self.elevated_roles = self.user_roles["elevated"] - self.badboi_role = self.user_roles["bad"] - self.verified_role = self.user_roles["verified"] - self.fun_roles = self.user_roles["fun"] - self.employment_roles = self.user_roles["employment"] - - # RSS Feeds - self.feeds = server_settings["rss_feeds"] - self.rss_feed = self.feeds["links"] - except Exception as e: - logger.warning(f"Failed to grab server info. No file under {server_settings_path}") - self.server_info = None - self.channels = None - self.mod_channel = None - self.log_channel = None - self.normal_channel = None - self.user_roles = None - self.admin_roles = None - self.elevated_roles = None - self.badboi_role = None - self.verified_role = None - self.fun_roles = None - self.employment_roles = None - self.feeds = None - self.rss_feed = None - - reaction_role_config_path = os.path.join(settings_path, "reaction_roles.toml") - try: - self.reaction_role_data = toml.load(reaction_role_config_path) - except Exception as e: - self.reaction_role_data = None - logger.warning(f"Failed to grab reaction roles. No file under {reaction_role_config_path}") - - verification_path = os.path.join(settings_path, "verification_options.toml") - try: - self.verification_options = toml.load(verification_path) - except Exception as f: - self.verification_options = None - logger.warning(f"Failed to grab verification options. No file under {reaction_role_config_path}") diff --git a/src/zorak/utilities/core/settings.py b/src/zorak/utilities/core/settings.py new file mode 100644 index 00000000..34350954 --- /dev/null +++ b/src/zorak/utilities/core/settings.py @@ -0,0 +1,42 @@ +import logging +import os +import toml +import json + + +logger = logging.getLogger(__name__) + + +class Settings: + def __init__(self, settings_path, all_guilds): + + if all_guilds is None: + logger.warning("Settings were triggered, but bot has no guilds!") + return + + + # self.settings_path = os.path.join(settings_path, "FAKE_DB_settings.json") + self.reactions_path = os.path.join(settings_path, "reaction_roles.toml") + self.reactions_path = os.path.join(settings_path, "reaction_roles.toml") + self.verification_path = os.path.join(settings_path, "verification_options.toml") + + + # with open(self.settings_path, 'r') as f: + # server_settings = json.load(f) + # self.server = server_settings + + + # Reaction Roles + try: + self.reaction_role_data = toml.load(self.reactions_path) + except Exception as e: + self.reaction_role_data = None + logger.warning(f"Failed to grab reaction roles. No file under {self.reactions_path}") + + # Verification Options + try: + self.verification_options = toml.load(self.verification_path) + + except Exception as f: + logger.warning(f"Failed to grab verification options. No file under {self.reactions_path}") + self.verification_options = None