-
Notifications
You must be signed in to change notification settings - Fork 7
feat: birthday #192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
feat: birthday #192
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
0e91672
style: add tree sync to fetch the slash commands
antoniorodr b6fab7d
feat: create birthday feature
antoniorodr 4f7790f
refactor: change the time of the check
antoniorodr f7acddc
feat(birthday): create on_member_remove action
antoniorodr 3dfcc2f
fix: fix typo
antoniorodr e052955
feat: auto role assign when birthday
antoniorodr f54db5d
chore: update `gitignore` file to ignore ruff config
antoniorodr 5d27fa8
style: avoid aggresive linting, making ruff follow `setup.cfg` rules
antoniorodr f94141d
refactor: use try/except block to check if user exists
antoniorodr 1de21f4
refactor: move bot config to json file
antoniorodr b36f151
style: fix format
antoniorodr 21218ff
style(launcher): fix linting
antoniorodr 287802f
fix: add back tree sync
antoniorodr 84ce2f0
feat: create `check_files` function to create default config at start
antoniorodr 51cbb48
feat: create `load_config` and save it as cache
antoniorodr 0d69e91
fix: search `guild_id` between all the bot guilds to find a match
antoniorodr ddd31e1
fix: `AttributeError: 'str' object has no attribute 'get'` bug
antoniorodr 375a39c
refactor: creates configs esqueleton at start
antoniorodr 01f16fd
feat: create function to save settings in json
antoniorodr de30afa
feat: create admin commands to update config file
antoniorodr 531f15b
refactor: use `discord.Role` instead of string
antoniorodr 40d0141
refactor: use `discord.TextChannel` instead of int
antoniorodr 5dba68b
refactor: use `role.id` instead of `role.name`
antoniorodr b71a848
fix: fix a startup bug when the `birthday` folder does not exist
antoniorodr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,282 @@ | ||
| """ | ||
| Modul for bursdagsfunksjonalitet | ||
| """ | ||
|
|
||
| import json | ||
| import os | ||
| from datetime import datetime, time | ||
| from zoneinfo import ZoneInfo | ||
|
|
||
| import discord | ||
| from discord.ext import commands, tasks | ||
|
|
||
| NORWEGIAN_TIME = ZoneInfo("Europe/Oslo") | ||
| BIRTHDAY_CHECK_TIME = time(hour=8, minute=00, tzinfo=NORWEGIAN_TIME) | ||
| BIRTHDAY_ROLE_TIME = time(hour=0, minute=0, tzinfo=NORWEGIAN_TIME) | ||
|
|
||
|
|
||
| class Birthday(commands.Cog): | ||
| """ | ||
| Klasse for bursdagsfunksjonalitet | ||
| """ | ||
|
|
||
| def __init__(self, bot): | ||
| self.bot = bot | ||
| self.settings_file = bot.data_dir + "/birthday/innstilinger.json" | ||
| self.birthdays_file = bot.data_dir + "/birthday/birthdays.json" | ||
| self.settings = self.load_settings() | ||
| self.data_cache = self.load_existing_data() | ||
| self.check_todays_birthday.start() | ||
| self.remove_birthday_roles.start() | ||
|
|
||
| @discord.app_commands.command(name="bursdag", description="Legg til bursdag") | ||
| @discord.app_commands.describe(date="Bursdag i format DD.MM") | ||
| async def add_birthday(self, interaction: discord.Interaction, date: str): | ||
| """ | ||
| Funksjon som lagrer bursdag | ||
| """ | ||
| try: | ||
| day, month = date.split(".") | ||
| day = int(day) | ||
| month = int(month) | ||
|
|
||
| if not (1 <= day <= 31 and 1 <= month <= 12): | ||
| raise ValueError | ||
|
|
||
| except (ValueError, IndexError): | ||
| return await interaction.response.send_message("Feil format! Bruk `DD.MM`(f.eks. 24.12)", ephemeral=True) | ||
|
|
||
| user = interaction.user | ||
| today = datetime.now() | ||
| data = self.load_existing_data() | ||
| user_id = str(interaction.user.id) | ||
|
|
||
| try: | ||
| if user_id not in data: | ||
| data[user_id] = {"username": interaction.user.name, "birthday": date, "updated": today.isoformat()} | ||
| self.data_cache = data | ||
| self.save_data(data) | ||
| else: | ||
| last_updated_str = data[user_id].get("updated") | ||
| if last_updated_str: | ||
| last_updated = datetime.fromisoformat(last_updated_str) | ||
| if (today - last_updated).days < 180: | ||
| return await interaction.response.send_message( | ||
| "Du kan bare endre bursdagen din hver 6 måned. Prøv igjen senere.", ephemeral=True | ||
| ) | ||
| data[user_id] = {"username": interaction.user.name, "birthday": date, "updated": today.isoformat()} | ||
| self.data_cache = data | ||
| self.save_data(data) | ||
|
|
||
| await interaction.response.send_message( | ||
| f"Lagret bursdag for {user.mention}: {date} \nDette blir slettet når serveren forlates." | ||
| ) | ||
| except Exception as e: | ||
| self.bot.logger.error(f"Error in add_birthday: {e}") | ||
| await interaction.response.send_message("En feil oppstod. Prøv igjen senere.", ephemeral=True) | ||
|
|
||
| def load_existing_data(self): | ||
| data = {} | ||
| if os.path.exists(self.birthdays_file): | ||
| with open(self.birthdays_file, "r") as file: | ||
| data = json.load(file) | ||
| return data | ||
|
|
||
| def load_settings(self): | ||
| if os.path.exists(self.settings_file): | ||
| with open(self.settings_file, "r") as file: | ||
| return json.load(file) | ||
| return {} | ||
|
|
||
| def save_settings(self, setting): | ||
| os.makedirs(os.path.dirname(self.settings_file), exist_ok=True) | ||
|
|
||
| try: | ||
| with open(self.settings_file, "r") as file: | ||
| settings = json.load(file) | ||
| except (FileNotFoundError, json.JSONDecodeError): | ||
| settings = {} | ||
|
|
||
| settings.update(setting) | ||
|
|
||
| with open(self.settings_file, "w") as file: | ||
| json.dump(settings, file, indent=4) | ||
|
|
||
| def save_data(self, data): | ||
| os.makedirs(os.path.dirname(self.birthdays_file), exist_ok=True) | ||
| with open(self.birthdays_file, "w") as file: | ||
| json.dump(data, file, indent=4) | ||
|
|
||
| @commands.guild_only() | ||
| @commands.group(name="bursdag") | ||
| async def birthday_group(self, ctx): | ||
| """ | ||
| Kategori for styring av bursdagsfunksjonalitet | ||
| """ | ||
|
|
||
| if ctx.invoked_subcommand is None: | ||
| await ctx.send_help(ctx.command) | ||
|
|
||
| @commands.is_owner() | ||
| @birthday_group.group() | ||
| async def admin(self, ctx): | ||
| """ | ||
| Kategori for innstillinger | ||
| """ | ||
|
|
||
| if ctx.invoked_subcommand is None: | ||
| await ctx.send_help(ctx.command) | ||
|
|
||
| @admin.command(name="guild") | ||
| async def set_guild(self, ctx, guild_id): | ||
| """ | ||
| Kommando for å sette guild_id i innstillinger | ||
| """ | ||
| try: | ||
| self.settings["guild"] = int(guild_id) | ||
| await ctx.send(f"La til guild med guild id {guild_id} i lista") | ||
| except KeyError: | ||
| self.settings["guild"] = "" | ||
| self.settings["guild"] = int(guild_id) | ||
| except Exception: | ||
| return self.bot.logger.error(f"Failed to set guild_id: {guild_id}") | ||
| self.save_settings(self.settings) | ||
| self.load_settings() | ||
|
|
||
| @admin.command(name="channel") | ||
| async def set_channel(self, ctx, channel: discord.TextChannel): | ||
| """ | ||
| Kommando for å sette channel_id i innstillinger | ||
| """ | ||
| try: | ||
| self.settings["channel_id"] = int(channel.id) | ||
| await ctx.send(f"Satt kanal med kanal id {channel.id} som bursdagskanal") | ||
| except Exception: | ||
| return self.bot.logger.error(f"Failed to set channel_id: {channel.id}") | ||
| self.save_settings(self.settings) | ||
| self.load_settings() | ||
|
|
||
| @admin.command(name="role") | ||
| async def set_role(self, ctx, role: discord.Role): | ||
| """ | ||
| Kommando for å sette bursdagsrolle i innstillinger | ||
| """ | ||
| try: | ||
| self.settings["birthday_role_id"] = int(role.id) | ||
| await ctx.send(f"Satt rolle med id {role.id} som bursdagsrolle") | ||
| except Exception: | ||
| return self.bot.logger.error(f"Failed to set birthday_role_id: {role.id}") | ||
| self.save_settings(self.settings) | ||
| self.load_settings() | ||
|
antoniorodr marked this conversation as resolved.
|
||
|
|
||
| @tasks.loop(time=BIRTHDAY_CHECK_TIME) | ||
| async def check_todays_birthday(self): | ||
| """ | ||
| Funksjon som sjekker om noen har bursdag en gang om dagen på et bestemt tidspunkt | ||
| """ | ||
|
|
||
| today = datetime.now() | ||
| day = today.day | ||
| month = today.month | ||
|
|
||
| matches = [] | ||
|
|
||
| for user_id, data in self.data_cache.items(): | ||
| try: | ||
| b_day, b_month = map(int, data["birthday"].split(".")) | ||
|
|
||
| if b_day == day and b_month == month: | ||
| try: | ||
| guild_id = self.settings.get("guild") | ||
| guild = self.bot.get_guild(guild_id) | ||
| except AttributeError: | ||
| self.bot.logger.error("Guild not found") | ||
| return | ||
|
|
||
| member = guild.get_member(int(user_id)) | ||
|
|
||
| if member: | ||
| matches.append(member.mention) | ||
| role = discord.utils.get(guild.roles, id=self.settings.get("birthday_role_id")) | ||
| if role: | ||
| await member.add_roles(role) | ||
| else: | ||
| matches.append(data["username"]) | ||
|
|
||
| except (ValueError, KeyError): | ||
| self.bot.logger.error(f"Invalid birthday format for user {user_id}: {data.get('birthday')}") | ||
|
|
||
| if matches: | ||
| birthday_list = "\n".join([f"🎂 {name}" for name in matches]) | ||
| embed = discord.Embed( | ||
| title="🎉 Dagens bursdagsbarn! 🎉", | ||
| description=birthday_list, | ||
| color=discord.Color.gold(), | ||
| ) | ||
| embed.set_footer(text="Gratulerer med dagen!") | ||
| channel = self.bot.get_channel(self.settings.get("channel_id")) | ||
| if channel: | ||
| await channel.send(embed=embed) | ||
|
|
||
| @tasks.loop(time=BIRTHDAY_ROLE_TIME) | ||
| async def remove_birthday_roles(self): | ||
| """ | ||
| Fjerner bursdagsrollen fra alle brukere hver dag ved midnatt | ||
| """ | ||
| guild = self.bot.guilds[0] | ||
| role = discord.utils.get(guild.roles, id=self.settings.get("birthday_role_id")) | ||
| if not role: | ||
| return | ||
|
|
||
| for user_id in self.data_cache: | ||
| member = guild.get_member(int(user_id)) | ||
| if member and role in member.roles: | ||
| try: | ||
| await member.remove_roles(role) | ||
| except discord.Forbidden as e: | ||
| self.bot.logger.error(f"Failed to remove 'årsdag' role from {member.name}: {e}") | ||
|
|
||
| @commands.Cog.listener() | ||
| async def on_member_remove(self, member): | ||
| """ | ||
| Fjerner bursdagsdata når en bruker forlater serveren | ||
| """ | ||
| user_id = str(member.id) | ||
|
|
||
| if user_id in self.data_cache: | ||
| del self.data_cache[user_id] | ||
| self.save_data(self.data_cache) | ||
| self.bot.logger.debug(f"Birthday from {member.name} deleted") | ||
|
|
||
| @check_todays_birthday.before_loop | ||
| @remove_birthday_roles.before_loop | ||
| async def before_task(self): | ||
| await self.bot.wait_until_ready() | ||
|
|
||
|
|
||
| def check_files(bot): | ||
| files = [ | ||
| { | ||
| f"{bot.data_dir}/birthday/innstilinger.json": { | ||
| "guild": "", | ||
| "channel_id": "", | ||
| "birthday_role_id": "", | ||
| } | ||
| }, | ||
| {f"{bot.data_dir}/birthday/birthdays.json": {}}, | ||
| ] | ||
|
|
||
| folder = f"{bot.data_dir}/birthday" | ||
| if not os.path.exists(folder): | ||
| os.makedirs(folder) | ||
|
|
||
| for i in files: | ||
| for file, default in i.items(): | ||
| if not os.path.exists(file): | ||
| with open(file, "w", encoding="utf8") as outfile: | ||
|
antoniorodr marked this conversation as resolved.
|
||
| json.dump(default, outfile) | ||
|
|
||
|
|
||
| async def setup(bot): | ||
| check_files(bot) | ||
| await bot.add_cog(Birthday(bot)) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.