diff --git a/Luminara.py b/Luminara.py index ef0a50e..3e2f121 100644 --- a/Luminara.py +++ b/Luminara.py @@ -11,6 +11,7 @@ import services.config_service import services.help_service from lib.constants import CONST from services.blacklist_service import BlacklistUserService +from lib.exceptions.LumiExceptions import Blacklisted from db.database import run_migrations # Remove the default logger configuration @@ -42,7 +43,9 @@ client = Client.LumiBot( @client.check async def blacklist_check(ctx): - return not BlacklistUserService.is_user_blacklisted(ctx.author.id) + if BlacklistUserService.is_user_blacklisted(ctx.author.id): + raise Blacklisted + return True def load_modules(): diff --git a/config/JSON/strings.json b/config/JSON/strings.json index f9cbeb7..94e83b4 100644 --- a/config/JSON/strings.json +++ b/config/JSON/strings.json @@ -1,5 +1,39 @@ { + "lumi_exception_generic": "An error occurred.", + "lumi_exception_blacklisted": "User is blacklisted", + "admin_award_description": "awarded **${0}** to {1}.", + "admin_award_title": "Awarded Currency", + "admin_blacklist_author": "User Blacklisted", + "admin_blacklist_description": "user `{0}` has been blacklisted from Luminara.", + "admin_blacklist_footer": "There is no process to reinstate a blacklisted user. Appeals are not considered.", + "admin_sql_inject_description": "```sql\n{0}\n```", + "admin_sql_inject_error_description": "```sql\n{0}\n```\n```\n{1}\n```", + "admin_sql_inject_error_title": "SQL Query Error", + "admin_sql_inject_title": "SQL Query Executed", + "admin_sql_select_description": "```sql\nSELECT {0}\n```\n```\n{1}\n```", + "admin_sql_select_error_description": "```sql\nSELECT {0}\n```\n```\n{1}\n```", + "admin_sql_select_error_title": "SQL Select Query Error", + "admin_sql_select_title": "SQL Select Query", + "admin_sync_description": "command tree synced successfully.", + "admin_sync_error_description": "An error occurred while syncing: {0}", + "admin_sync_error_title": "Sync Error", + "admin_sync_title": "Sync Successful", "bet_limit": "❌ | **{0}** you cannot place any bets above **${1}**.", + "birthday_check_started": "Daily birthday check started.", + "birthday_check_skipped": "Birthday announcements in guild with ID {0} skipped: no birthday channel.", + "birthday_check_success": "Birthday announcement Success! user/guild/chan ID: {0}/{1}/{2}", + "birthday_check_error": "Birthday announcement skipped processing user/guild {0}/{1} | {2}", + "birthday_check_finished": "Daily birthday check finished. {0} birthdays processed. {1} birthdays failed.", + "birthday_add_invalid_date": "The date you entered is invalid.", + "birthday_add_success_author": "Birthday Set", + "birthday_add_success_description": "your birthday has been set to **{0} {1}**.", + "birthday_delete_success_author": "Birthday Deleted", + "birthday_delete_success_description": "your birthday has been deleted from this server.", + "birthday_upcoming_author": "Upcoming Birthdays!", + "birthday_upcoming_description_line": "🎂 {0} - {1}", + "birthday_upcoming_no_birthdays_author": "No Upcoming Birthdays", + "birthday_upcoming_no_birthdays": "there are no upcoming birthdays in this server.", + "birthday_leap_year": "February 29", "case_case_field": "Case:", "case_case_field_value": "`{0}`", "case_duration_field": "Duration:", @@ -97,6 +131,7 @@ "error_bot_missing_permissions_description": "Lumi lacks the required permissions to run this command.", "error_command_cooldown_author": "Command Cooldown", "error_command_cooldown_description": "try again in **{0:02d}:{1:02d}**.", + "error_command_not_found": "No command called \"{0}\" found.", "error_image_url_invalid": "invalid image URL.", "error_invalid_duration": "Invalid duration: {0}", "error_invalid_duration_author": "Invalid Duration", @@ -150,12 +185,19 @@ "mod_banned_user": "user with ID `{0}` has been banned.", "mod_dm_not_sent": "Failed to notify them in DM", "mod_dm_sent": "notified them in DM", + "mod_kick_dm": "**{0}** you have been kicked from `{1}`.\n\n**Reason:** `{2}`", + "mod_kicked_author": "User Kicked", + "mod_kicked_user": "user `{0}` has been kicked.", "mod_no_reason": "No reason provided.", "mod_not_banned": "user with ID `{0}` is not banned.", "mod_not_banned_author": "User Not Banned", "mod_not_timed_out": "user `{0}` is not timed out.", "mod_not_timed_out_author": "User Not Timed Out", "mod_reason": "Moderator: {0} | Reason: {1}", + "mod_softban_dm": "**{0}** you have been softbanned from `{1}`.\n\n**Reason:** `{2}`", + "mod_softban_unban_reason": "Softban by {0}", + "mod_softbanned_author": "User Softbanned", + "mod_softbanned_user": "user `{0}` has been softbanned.", "mod_timed_out_author": "User Timed Out", "mod_timed_out_user": "user `{0}` has been timed out.", "mod_timeout_dm": "**{0}** you have been timed out in `{1}` for `{2}`.\n\n**Reason:** `{3}`", @@ -166,13 +208,6 @@ "mod_warn_dm": "**{0}** you have been warned in `{1}`.\n\n**Reason:** `{2}`", "mod_warned_author": "User Warned", "mod_warned_user": "user `{0}` has been warned.", - "mod_kicked_author": "User Kicked", - "mod_kicked_user": "user `{0}` has been kicked.", - "mod_kick_dm": "**{0}** you have been kicked from `{1}`.\n\n**Reason:** `{2}`", - "mod_softbanned_author": "User Softbanned", - "mod_softban_dm": "**{0}** you have been softbanned from `{1}`.\n\n**Reason:** `{2}`", - "mod_softbanned_user": "user `{0}` has been softbanned.", - "mod_softban_unban_reason": "Softban by {0}", "ping_author": "I'm online!", "ping_footer": "Latency: {0}ms", "ping_pong": "Pong!", @@ -194,6 +229,5 @@ "xp_lb_field_value": "level: **{0}**\nxp: `{1}/{2}`", "xp_level": "Level {0}", "xp_progress": "Progress to next level", - "xp_server_rank": "Server Rank: #{0}", - "error_command_not_found": "No command called \"{0}\" found." + "xp_server_rank": "Server Rank: #{0}" } diff --git a/handlers/error_handler.py b/handlers/error_handler.py index 5b0a821..8833371 100644 --- a/handlers/error_handler.py +++ b/handlers/error_handler.py @@ -11,7 +11,7 @@ from lib.exceptions import LumiExceptions async def on_command_error(ctx, error): - if isinstance(error, commands.CommandNotFound): + if isinstance(error, (commands.CommandNotFound, LumiExceptions.Blacklisted)): return author_text = None diff --git a/lib/constants.py b/lib/constants.py index d314fd2..3e07748 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -85,9 +85,7 @@ class Constants: # birthdays BIRTHDAY_MESSAGES = JsonCache.read_json("birthday")["birthday_messages"] BIRTHDAY_MONTHS = JsonCache.read_json("birthday")["months"] - - -CONST = Constants() + BIRTHDAY_GIF_URL = "https://media1.tenor.com/m/NXvU9jbBUGMAAAAC/fireworks.gif" CONST = Constants() diff --git a/lib/embed_builder.py b/lib/embed_builder.py index ad9bfc6..1470daa 100644 --- a/lib/embed_builder.py +++ b/lib/embed_builder.py @@ -23,6 +23,7 @@ class EmbedBuilder: timestamp=None, hide_author=False, hide_author_icon=False, + hide_timestamp=False, ): if not hide_author: if not author_text: @@ -50,7 +51,8 @@ class EmbedBuilder: url=author_url, ) embed.set_footer(text=footer_text, icon_url=footer_icon_url) - embed.timestamp = timestamp or datetime.datetime.now() + if not hide_timestamp: + embed.timestamp = timestamp or datetime.datetime.now() if image_url: embed.set_image(url=image_url) @@ -74,6 +76,7 @@ class EmbedBuilder: timestamp=None, hide_author=False, hide_author_icon=False, + hide_timestamp=False, ): return EmbedBuilder.create_embed( ctx, @@ -91,6 +94,7 @@ class EmbedBuilder: timestamp=timestamp, hide_author=hide_author, hide_author_icon=hide_author_icon, + hide_timestamp=hide_timestamp, ) @staticmethod @@ -108,6 +112,7 @@ class EmbedBuilder: timestamp=None, hide_author=False, hide_author_icon=False, + hide_timestamp=False, ): return EmbedBuilder.create_embed( ctx, @@ -125,6 +130,7 @@ class EmbedBuilder: timestamp=timestamp, hide_author=hide_author, hide_author_icon=hide_author_icon, + hide_timestamp=hide_timestamp, ) @staticmethod @@ -142,6 +148,7 @@ class EmbedBuilder: timestamp=None, hide_author=False, hide_author_icon=False, + hide_timestamp=False, ): return EmbedBuilder.create_embed( ctx, @@ -159,6 +166,7 @@ class EmbedBuilder: timestamp=timestamp, hide_author=hide_author, hide_author_icon=hide_author_icon, + hide_timestamp=hide_timestamp, ) @staticmethod @@ -176,6 +184,7 @@ class EmbedBuilder: timestamp=None, hide_author=False, hide_author_icon=False, + hide_timestamp=False, ): return EmbedBuilder.create_embed( ctx, @@ -193,4 +202,5 @@ class EmbedBuilder: timestamp=timestamp, hide_author=hide_author, hide_author_icon=hide_author_icon, + hide_timestamp=hide_timestamp, ) diff --git a/lib/exceptions/LumiExceptions.py b/lib/exceptions/LumiExceptions.py index 6fe96c6..cb6bfe0 100644 --- a/lib/exceptions/LumiExceptions.py +++ b/lib/exceptions/LumiExceptions.py @@ -1,4 +1,5 @@ from discord.ext import commands +from lib.constants import CONST class BirthdaysDisabled(commands.CheckFailure): @@ -14,6 +15,16 @@ class LumiException(commands.CommandError): A generic exception to raise for quick error handling. """ - def __init__(self, message="An error occurred."): + def __init__(self, message=CONST.STRINGS["lumi_exception_generic"]): + self.message = message + super().__init__(message) + + +class Blacklisted(commands.CommandError): + """ + Raised when a user is blacklisted. + """ + + def __init__(self, message=CONST.STRINGS["lumi_exception_blacklisted"]): self.message = message super().__init__(message) diff --git a/modules/admin/award.py b/modules/admin/award.py index 6f6c23d..979afab 100644 --- a/modules/admin/award.py +++ b/modules/admin/award.py @@ -1,5 +1,6 @@ import discord - +from lib.constants import CONST +from lib.embed_builder import EmbedBuilder from services.currency_service import Currency @@ -9,9 +10,13 @@ async def cmd(ctx, user: discord.User, amount: int): curr.add_balance(amount) curr.push() - embed = discord.Embed( - color=discord.Color.green(), - description=f"Awarded **${Currency.format(amount)}** to {user.name}.", + embed = EmbedBuilder.create_success_embed( + ctx, + author_text=CONST.STRINGS["admin_award_title"], + description=CONST.STRINGS["admin_award_description"].format( + Currency.format(amount), + user.name, + ), ) await ctx.respond(embed=embed) diff --git a/modules/admin/blacklist.py b/modules/admin/blacklist.py index e3c3537..7333491 100644 --- a/modules/admin/blacklist.py +++ b/modules/admin/blacklist.py @@ -1,13 +1,9 @@ from typing import Optional import discord - -from config.parser import JsonCache +from lib.constants import CONST from services.blacklist_service import BlacklistUserService - -resources = JsonCache.read_json("art") -exclaim_icon = resources["icons"]["exclaim"] -hammer_icon = resources["icons"]["hammer"] +from lib.embed_builder import EmbedBuilder async def blacklist_user( @@ -15,24 +11,15 @@ async def blacklist_user( user: discord.User, reason: Optional[str] = None, ) -> None: - """ - Blacklists a user with an optional reason. - - Args: - user_id (int): The ID of the user to blacklist. - reason (str, optional): The reason for blacklisting the user. Defaults to "No reason was given". - """ blacklist_service = BlacklistUserService(user.id) blacklist_service.add_to_blacklist(reason) - embed = discord.Embed( - description=f"User `{user.name}` has been blacklisted from Luminara.", - color=discord.Color.red(), - ) - embed.set_author(name="User Blacklisted", icon_url=hammer_icon) - embed.set_footer( - text="There is no process to reinstate a blacklisted user. Appeals are not considered.", - icon_url=exclaim_icon, + embed = EmbedBuilder.create_success_embed( + ctx, + author_text=CONST.STRINGS["admin_blacklist_author"], + description=CONST.STRINGS["admin_blacklist_description"].format(user.name), + footer_text=CONST.STRINGS["admin_blacklist_footer"], + hide_timestamp=True, ) await ctx.send(embed=embed) diff --git a/modules/admin/sql.py b/modules/admin/sql.py index f24f295..f1f179a 100644 --- a/modules/admin/sql.py +++ b/modules/admin/sql.py @@ -1,4 +1,7 @@ -import sqlite3 +import mysql.connector +from lib.constants import CONST +from lib.embed_builder import EmbedBuilder +from lib.formatter import shorten from db import database @@ -9,21 +12,49 @@ async def select_cmd(ctx, query: str): try: results = database.select_query(f"SELECT {query}") - except sqlite3.Error as error: - results = error + embed = EmbedBuilder.create_success_embed( + ctx, + author_text=CONST.STRINGS["admin_sql_select_title"], + description=CONST.STRINGS["admin_sql_select_description"].format( + shorten(query, 200), + shorten(str(results), 200), + ), + show_name=False, + ) + except mysql.connector.Error as error: + embed = EmbedBuilder.create_error_embed( + ctx, + author_text=CONST.STRINGS["admin_sql_select_error_title"], + description=CONST.STRINGS["admin_sql_select_error_description"].format( + shorten(query, 200), + shorten(str(error), 200), + ), + show_name=False, + ) - return await ctx.respond( - content=f"```SELECT {query}```\n```{results}```", - ephemeral=True, - ) + return await ctx.respond(embed=embed, ephemeral=True) async def inject_cmd(ctx, query: str): try: database.execute_query(query) - await ctx.respond(content=f"That worked!\n```{query}```", ephemeral=True) - except sqlite3.Error as error: - await ctx.respond( - content=f"Query:\n```{query}```\nError message:\n```{error}```", - ephemeral=True, + embed = EmbedBuilder.create_success_embed( + ctx, + author_text=CONST.STRINGS["admin_sql_inject_title"], + description=CONST.STRINGS["admin_sql_inject_description"].format( + shorten(query, 200), + ), + show_name=False, ) + except mysql.connector.Error as error: + embed = EmbedBuilder.create_error_embed( + ctx, + author_text=CONST.STRINGS["admin_sql_inject_error_title"], + description=CONST.STRINGS["admin_sql_inject_error_description"].format( + shorten(query, 200), + shorten(str(error), 200), + ), + show_name=False, + ) + + await ctx.respond(embed=embed, ephemeral=True) diff --git a/modules/admin/sync.py b/modules/admin/sync.py index 3436ba2..0b722fd 100644 --- a/modules/admin/sync.py +++ b/modules/admin/sync.py @@ -1,6 +1,7 @@ import discord from lib.embed_builder import EmbedBuilder from lib.exceptions.LumiExceptions import LumiException +from lib.constants import CONST async def sync_commands(client, ctx): @@ -8,9 +9,11 @@ async def sync_commands(client, ctx): await client.sync_commands() embed = EmbedBuilder.create_success_embed( ctx, - author_text="Sync Successful", - description="command tree synced successfully.", + author_text=CONST.STRINGS["admin_sync_title"], + description=CONST.STRINGS["admin_sync_description"], ) await ctx.send(embed=embed) except discord.HTTPException as e: - raise LumiException(f"An error occurred while syncing: {e}") from e + raise LumiException( + CONST.STRINGS["admin_sync_error_description"].format(e), + ) from e diff --git a/modules/birthdays/__init__.py b/modules/birthdays/__init__.py index c4e95cb..6ea0c0f 100644 --- a/modules/birthdays/__init__.py +++ b/modules/birthdays/__init__.py @@ -1,16 +1,12 @@ -import asyncio -import random +import datetime +import pytz import discord from discord.commands import SlashCommandGroup from discord.ext import commands, tasks -from loguru import logger - -from lib import checks, time +from lib import checks from lib.constants import CONST -from modules.birthdays import birthday -from services.birthday_service import Birthday -from services.config_service import GuildConfig +from modules.birthdays import birthday, daily_check class Birthdays(commands.Cog): @@ -18,9 +14,6 @@ class Birthdays(commands.Cog): self.client = client self.daily_birthday_check.start() - """ - birthday module - slash command only - """ birthday = SlashCommandGroup( name="birthday", description="Birthday commands.", @@ -43,47 +36,9 @@ class Birthdays(commands.Cog): async def upcoming_birthdays(self, ctx): await birthday.upcoming(ctx) - @tasks.loop(hours=23, minutes=55) + @tasks.loop(time=datetime.time(hour=12, minute=0, tzinfo=pytz.UTC)) # 12 PM UTC async def daily_birthday_check(self): - wait_time = time.seconds_until(7, 0) - logger.debug( - f"Waiting until 7 AM Eastern for the daily birthday check: {round(wait_time)}s left.", - ) - await asyncio.sleep(wait_time) - - embed = discord.Embed(color=discord.Color.embed_background()) - embed.set_image(url="https://media1.tenor.com/m/NXvU9jbBUGMAAAAC/fireworks.gif") - - for user_id, guild_id in Birthday.get_birthdays_today(): - try: - guild = await self.client.fetch_guild(guild_id) - member = await guild.fetch_member(user_id) - guild_config = GuildConfig(guild.id) - - if not guild_config.birthday_channel_id: - logger.debug( - f"Birthday announcements in guild with ID {guild.id} skipped: no birthday channel.", - ) - return - - message = random.choice(CONST.BIRTHDAY_MESSAGES) - embed.description = message.format(member.name) - channel = await self.client.get_or_fetch_channel( - guild, - guild_config.birthday_channel_id, - ) - await channel.send(embed=embed, content=member.mention) - logger.debug( - f"Birthday announcement Success! user/guild/chan ID: {member.id}/{guild.id}/{channel.id}", - ) - - except Exception as e: - logger.warning( - f"Birthday announcement skipped processing user/guild {user_id}/{guild_id} | {e}", - ) - - # wait one second to avoid rate limits - await asyncio.sleep(1) + await daily_check.daily_birthday_check(self.client) def setup(client): diff --git a/modules/birthdays/birthday.py b/modules/birthdays/birthday.py index 29e3782..d27b4c9 100644 --- a/modules/birthdays/birthday.py +++ b/modules/birthdays/birthday.py @@ -4,7 +4,8 @@ import datetime import discord from discord.ext import commands -from lib.embeds.info import BdayInfo +from lib.embed_builder import EmbedBuilder +from lib.constants import CONST from services.birthday_service import Birthday @@ -13,59 +14,75 @@ async def add(ctx, month, month_index, day): leap_year = 2020 max_days = calendar.monthrange(leap_year, month_index)[1] - if not (1 <= day <= max_days): - raise commands.BadArgument("the date you entered is invalid.") + if not 1 <= day <= max_days: + raise commands.BadArgument(CONST.STRINGS["birthday_add_invalid_date"]) - date_str = f"{leap_year}-{month_index:02d}-{day:02d}" - date_obj = datetime.datetime.strptime(date_str, "%Y-%m-%d") + date_obj = datetime.datetime(leap_year, month_index, day) birthday = Birthday(ctx.author.id, ctx.guild.id) birthday.set(date_obj) - await ctx.respond(embed=BdayInfo.set_month(ctx, month, day)) + embed = EmbedBuilder.create_success_embed( + ctx, + author_text=CONST.STRINGS["birthday_add_success_author"], + description=CONST.STRINGS["birthday_add_success_description"].format( + month, + day, + ), + show_name=True, + ) + await ctx.respond(embed=embed) async def delete(ctx): """Delete a user's birthday in a specific server.""" - birthday = Birthday(ctx.author.id, ctx.guild.id) - birthday.delete() - await ctx.respond(embed=BdayInfo.delete(ctx)) + Birthday(ctx.author.id, ctx.guild.id).delete() + + embed = EmbedBuilder.create_success_embed( + ctx, + author_text=CONST.STRINGS["birthday_delete_success_author"], + description=CONST.STRINGS["birthday_delete_success_description"], + show_name=True, + ) + await ctx.respond(embed=embed) async def upcoming(ctx): """Get the upcoming birthdays for a specific server.""" upcoming_birthdays = Birthday.get_upcoming_birthdays(ctx.guild.id) - icon = ctx.guild.icon if ctx.guild.icon else "https://i.imgur.com/79XfsbS.png" - embed = discord.Embed( - color=discord.Color.embed_background(), + if not upcoming_birthdays: + embed = EmbedBuilder.create_warning_embed( + ctx, + author_text=CONST.STRINGS["birthday_upcoming_no_birthdays_author"], + description=CONST.STRINGS["birthday_upcoming_no_birthdays"], + show_name=True, + ) + await ctx.respond(embed=embed) + return + + embed = EmbedBuilder.create_success_embed( + ctx, + author_text=CONST.STRINGS["birthday_upcoming_author"], + description="", + show_name=False, ) - embed.set_author(name="Upcoming Birthdays!", icon_url=icon) - embed.set_thumbnail(url="https://i.imgur.com/79XfsbS.png") + embed.set_thumbnail(url=CONST.LUMI_LOGO_TRANSPARENT) - found_birthdays = 0 - for user_id, birthday in upcoming_birthdays: + birthday_lines = [] + for user_id, birthday in upcoming_birthdays[:10]: try: member = await ctx.guild.fetch_member(user_id) - name = member.name - except discord.HTTPException: - continue # skip user if not in guild - - try: birthday_date = datetime.datetime.strptime(birthday, "%m-%d") formatted_birthday = birthday_date.strftime("%B %-d") - except ValueError: - # leap year error - formatted_birthday = "February 29" - - embed.add_field( - name=f"{name}", - value=f"🎂 {formatted_birthday}", - inline=False, - ) - - found_birthdays += 1 - if found_birthdays >= 5: - break + birthday_lines.append( + CONST.STRINGS["birthday_upcoming_description_line"].format( + member.name, + formatted_birthday, + ), + ) + except (discord.HTTPException, ValueError): + continue + embed.description = "\n".join(birthday_lines) await ctx.respond(embed=embed) diff --git a/modules/birthdays/daily_check.py b/modules/birthdays/daily_check.py new file mode 100644 index 0000000..1754827 --- /dev/null +++ b/modules/birthdays/daily_check.py @@ -0,0 +1,63 @@ +import asyncio +import random +from loguru import logger +from lib.constants import CONST +from services.birthday_service import Birthday +from services.config_service import GuildConfig +from lib.embed_builder import EmbedBuilder + + +async def daily_birthday_check(client): + logger.info(CONST.STRINGS["birthday_check_started"]) + birthdays_today = Birthday.get_birthdays_today() + processed_birthdays = 0 + failed_birthdays = 0 + + if birthdays_today: + for user_id, guild_id in birthdays_today: + try: + guild = await client.fetch_guild(guild_id) + member = await guild.fetch_member(user_id) + guild_config = GuildConfig(guild.id) + + if not guild_config.birthday_channel_id: + logger.debug( + CONST.STRINGS["birthday_check_skipped"].format(guild.id), + ) + continue + + message = random.choice(CONST.BIRTHDAY_MESSAGES) + embed = EmbedBuilder.create_success_embed( + None, + author_text="Happy Birthday!", + description=message.format(member.name), + show_name=False, + ) + embed.set_image(url=CONST.BIRTHDAY_GIF_URL) + + channel = await guild.fetch_channel(guild_config.birthday_channel_id) + await channel.send(embed=embed, content=member.mention) + logger.debug( + CONST.STRINGS["birthday_check_success"].format( + member.id, + guild.id, + channel.id, + ), + ) + processed_birthdays += 1 + + except Exception as e: + logger.warning( + CONST.STRINGS["birthday_check_error"].format(user_id, guild_id, e), + ) + failed_birthdays += 1 + + # wait one second to avoid rate limits + await asyncio.sleep(1) + + logger.info( + CONST.STRINGS["birthday_check_finished"].format( + processed_birthdays, + failed_birthdays, + ), + ) diff --git a/modules/moderation/__init__.py b/modules/moderation/__init__.py index a9d5f57..730d70a 100644 --- a/modules/moderation/__init__.py +++ b/modules/moderation/__init__.py @@ -1,8 +1,8 @@ import discord -from discord.ext.commands import guild_only from discord.ext import bridge, commands +from discord.ext.commands import guild_only -from modules.moderation import ban, cases, warn, timeout, kick, softban +from modules.moderation import ban, cases, kick, softban, timeout, warn class Moderation(commands.Cog): @@ -28,25 +28,6 @@ class Moderation(commands.Cog): ): await ban.ban_user(self, ctx, target, reason) - @bridge.bridge_command( - name="unban", - aliases=["ub", "pardon"], - description="Unbans a user from the server.", - help="Unbans a user from the server, you can use ID or provide their username.", - contexts={discord.InteractionContextType.guild}, - ) - @bridge.has_permissions(ban_members=True) - @commands.bot_has_permissions(ban_members=True) - @guild_only() - async def unban_command( - self, - ctx, - target: discord.User, - *, - reason: str | None = None, - ): - await ban.unban_user(ctx, target, reason) - @bridge.bridge_command( name="case", aliases=["c"], @@ -56,11 +37,7 @@ class Moderation(commands.Cog): ) @bridge.has_permissions(view_audit_log=True) @guild_only() - async def case_command( - self, - ctx, - case_number: int, - ): + async def case_command(self, ctx, case_number: int): await cases.view_case_by_number(ctx, ctx.guild.id, case_number) @bridge.bridge_command( @@ -75,22 +52,6 @@ class Moderation(commands.Cog): async def cases_command(self, ctx): await cases.view_all_cases_in_guild(ctx, ctx.guild.id) - @bridge.bridge_command( - name="modcases", - aliases=["moderatorcases", "mc"], - description="View all cases by a specific moderator.", - help="Lists all moderation cases handled by a specific moderator in the current server.", - contexts={discord.InteractionContextType.guild}, - ) - @bridge.has_permissions(view_audit_log=True) - @guild_only() - async def moderator_cases_command( - self, - ctx, - moderator: discord.User, - ): - await cases.view_all_cases_by_mod(ctx, ctx.guild.id, moderator) - @bridge.bridge_command( name="editcase", aliases=["uc", "ec"], @@ -100,32 +61,58 @@ class Moderation(commands.Cog): ) @bridge.has_permissions(view_audit_log=True) @guild_only() - async def edit_case_command( - self, - ctx, - case_number: int, - *, - new_reason: str, - ): + async def edit_case_command(self, ctx, case_number: int, *, new_reason: str): await cases.edit_case_reason(ctx, ctx.guild.id, case_number, new_reason) @bridge.bridge_command( - name="warn", - aliases=["w"], - description="Warn a user.", - help="Warns a user in the server.", + name="kick", + aliases=["k"], + description="Kick a user from the server.", + help="Kicks a user from the server.", contexts={discord.InteractionContextType.guild}, ) @bridge.has_permissions(kick_members=True) + @commands.bot_has_permissions(kick_members=True) @guild_only() - async def warn_command( + async def kick_command( self, ctx, target: discord.Member, *, reason: str | None = None, ): - await warn.warn_user(ctx, target, reason) + await kick.kick_user(self, ctx, target, reason) + + @bridge.bridge_command( + name="modcases", + aliases=["moderatorcases", "mc"], + description="View all cases by a specific moderator.", + help="Lists all moderation cases handled by a specific moderator in the current server.", + contexts={discord.InteractionContextType.guild}, + ) + @bridge.has_permissions(view_audit_log=True) + @guild_only() + async def moderator_cases_command(self, ctx, moderator: discord.Member): + await cases.view_all_cases_by_mod(ctx, ctx.guild.id, moderator) + + @bridge.bridge_command( + name="softban", + aliases=["sb"], + description="Softban a user from the server.", + help="Softbans a user from the server (ban and immediately unban to delete messages).", + contexts={discord.InteractionContextType.guild}, + ) + @bridge.has_permissions(ban_members=True) + @commands.bot_has_permissions(ban_members=True) + @guild_only() + async def softban_command( + self, + ctx, + target: discord.Member, + *, + reason: str | None = None, + ): + await softban.softban_user(ctx, target, reason) @bridge.bridge_command( name="timeout", @@ -147,6 +134,25 @@ class Moderation(commands.Cog): ): await timeout.timeout_user(self, ctx, target, duration, reason) + @bridge.bridge_command( + name="unban", + aliases=["ub", "pardon"], + description="Unbans a user from the server.", + help="Unbans a user from the server, you can use ID or provide their username.", + contexts={discord.InteractionContextType.guild}, + ) + @bridge.has_permissions(ban_members=True) + @commands.bot_has_permissions(ban_members=True) + @guild_only() + async def unban_command( + self, + ctx, + target: discord.User, + *, + reason: str | None = None, + ): + await ban.unban_user(ctx, target, reason) + @bridge.bridge_command( name="untimeout", aliases=["removetimeout", "rto", "uto"], @@ -167,42 +173,22 @@ class Moderation(commands.Cog): await timeout.untimeout_user(ctx, target, reason) @bridge.bridge_command( - name="kick", - aliases=["k"], - description="Kick a user from the server.", - help="Kicks a user from the server.", + name="warn", + aliases=["w"], + description="Warn a user.", + help="Warns a user in the server.", contexts={discord.InteractionContextType.guild}, ) @bridge.has_permissions(kick_members=True) - @commands.bot_has_permissions(kick_members=True) @guild_only() - async def kick_command( + async def warn_command( self, ctx, target: discord.Member, *, reason: str | None = None, ): - await kick.kick_user(self, ctx, target, reason) - - @bridge.bridge_command( - name="softban", - aliases=["sb"], - description="Softban a user from the server.", - help="Softbans a user from the server (ban and immediately unban to delete messages).", - contexts={discord.InteractionContextType.guild}, - ) - @bridge.has_permissions(ban_members=True) - @commands.bot_has_permissions(ban_members=True) - @guild_only() - async def softban_command( - self, - ctx, - target: discord.Member, - *, - reason: str | None = None, - ): - await softban.softban_user(ctx, target, reason) + await warn.warn_user(ctx, target, reason) def setup(client): diff --git a/modules/moderation/cases.py b/modules/moderation/cases.py index da663c6..c0ff5bc 100644 --- a/modules/moderation/cases.py +++ b/modules/moderation/cases.py @@ -64,7 +64,7 @@ async def view_all_cases_in_guild(ctx, guild_id: int): await paginator.respond(ctx) -async def view_all_cases_by_mod(ctx, guild_id: int, moderator: discord.User): +async def view_all_cases_by_mod(ctx, guild_id: int, moderator: discord.Member): cases = case_service.fetch_cases_by_moderator(guild_id, moderator.id) if not cases: diff --git a/modules/triggers/add.py b/modules/triggers/add.py index e8e0cf5..4a34ffc 100644 --- a/modules/triggers/add.py +++ b/modules/triggers/add.py @@ -58,10 +58,10 @@ async def add_reaction( is_emoji, is_full_match, ) - await ctx.respond(embed=embed) else: embed = create_failure_embed(trigger_text, is_emoji) - await ctx.respond(embed=embed) + + await ctx.respond(embed=embed) async def check_reaction_limit( diff --git a/services/birthday_service.py b/services/birthday_service.py index 151f0e9..5ec2c66 100644 --- a/services/birthday_service.py +++ b/services/birthday_service.py @@ -38,9 +38,7 @@ class Birthday: tz = pytz.timezone("US/Eastern") today = datetime.datetime.now(tz).strftime("%m-%d") - birthdays = database.select_query(query, (today,)) - - return birthdays + return database.select_query(query, (today,)) @staticmethod def get_upcoming_birthdays(guild_id): @@ -53,10 +51,4 @@ class Birthday: data = database.select_query(query, (guild_id,)) - upcoming = [] - for row in data: - user_id = row[0] - birthday = row[1] - upcoming.append((user_id, birthday)) - - return upcoming + return [(row[0], row[1]) for row in data] diff --git a/services/help_service.py b/services/help_service.py index ad420a5..4dcd529 100644 --- a/services/help_service.py +++ b/services/help_service.py @@ -18,10 +18,10 @@ class LumiHelp(commands.HelpCommand): } def get_command_qualified_name(self, command): - return "`{}{}`".format(self.context.clean_prefix, command.qualified_name) + return f"`{self.context.clean_prefix}{command.qualified_name}`" async def send_bot_help(self, mapping): - embed = EmbedBuilder.create_info_embed( + embed = EmbedBuilder.create_success_embed( ctx=self.context, author_text="Help Command", show_name=False, @@ -53,17 +53,11 @@ class LumiHelp(commands.HelpCommand): show_name=False, ) - usage_value = "`{}{} {}`".format( - self.context.clean_prefix, - command.qualified_name, - command.signature, + usage_value = ( + f"`{self.context.clean_prefix}{command.qualified_name} {command.signature}`" ) for alias in command.aliases: - usage_value += "\n`{}{} {}`".format( - self.context.clean_prefix, - alias, - command.signature, - ) + usage_value += f"\n`{self.context.clean_prefix}{alias} {command.signature}`" embed.add_field(name="Usage", value=usage_value, inline=False) channel = self.get_destination()