diff --git a/config/JSON/strings.json b/config/JSON/strings.json index e99ea1d..45340e5 100644 --- a/config/JSON/strings.json +++ b/config/JSON/strings.json @@ -17,6 +17,21 @@ "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:", diff --git a/lib/constants.py b/lib/constants.py index 57e00d4..3e07748 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -85,6 +85,7 @@ class Constants: # birthdays BIRTHDAY_MESSAGES = JsonCache.read_json("birthday")["birthday_messages"] BIRTHDAY_MONTHS = JsonCache.read_json("birthday")["months"] + BIRTHDAY_GIF_URL = "https://media1.tenor.com/m/NXvU9jbBUGMAAAAC/fireworks.gif" CONST = Constants() 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, + ), + )