async def _delete_team(self, ctx: SlashContext, team_role: discord.Role, confirmation: str = None): """Delete a team. After selecting a team, the user will be asked to repeat the command with its role ID to ensure they're aware of the action they're performing and of its consequences. """ guild = data_manager.get_guild(ctx.guild) team = checks.is_role_tied_to_team(guild, team_role) if confirmation is not None and int(confirmation) == team_role.id: await guild.del_team(guild.get_team(team_role)) embed = discord.Embed( title= f'{constants.Emojis.DELETE.value} __{team_role}__ and all its data' f' was deleted.', color=constants.Colors.ERROR.value) else: embed = discord.Embed( title=f'{constants.Emojis.WARNING.value} Are you sure' f' you want to delete __{team.role}__?', description= f'This action is irreversible and will erase all your data.' f'\nTo confirm, use `/delete_team {team_role} {team_role.id}`.', color=constants.Colors.ERROR.value) await ctx.send(embed=embed)
async def _new_control_role(self, ctx: SlashContext, name: str): """Create a new control role.""" role = await discord.Guild.create_role(ctx.guild, name=name, color=models.ControlRole.DEFAULT_COLOR) guild = data_manager.get_guild(ctx.guild) control_role = models.ControlRole(guild, role) control_role.perms = await Configuration.configure_perms(self.bot, ctx, name)
async def _change_locale(self, ctx, locale: str): """Change the guild locale.""" guild = data_manager.get_guild(ctx.guild) guild.locale = locale await ctx.send(embed=discord.Embed( title=f'{constants.Emojis.LOCALE.value} Server locale set: __{locale}__', description='Note: this will only change how date and time are formatted.', color=constants.Colors.DEFAULT.value))
async def _set_channel(self, ctx, channel: discord.TextChannel): """Set the guild target channel.""" if isinstance(channel, discord.CategoryChannel): return guild = data_manager.get_guild(ctx.guild) guild.target_channel = channel await ctx.send(embed=discord.Embed( title=f'{constants.Emojis.CONFIG.value} Channel set: __{guild.target_channel.name}__', color=constants.Colors.DEFAULT.value))
async def _change_tz(self, ctx, offset: float): """Change the guild locale.""" offset = float(offset) guild = data_manager.get_guild(ctx.guild) guild.tz = timezone(timedelta(hours=offset)) await ctx.send(embed=discord.Embed( title=constants.Emojis.TIMEZONE.value + ' Server timezone set: __UTC {offset}__' .format(offset=f'+{offset}' if offset >= 0 else offset), description='Note: will not adapt to daylight savings and etc. automatically.', color=constants.Colors.DEFAULT.value))
async def _do_receive_announcements(self, ctx: SlashContext, receive: bool): """Set whether the guild wants to receive official announcements or not.""" guild = data_manager.get_guild(ctx.guild) guild.receive_announcements = receive title = (f'{constants.Emojis.CONFIG.value} This server will now' + ('' if receive else ' not') + ' receive announcements.') embed = discord.Embed( title=title, color=constants.Colors.DEFAULT.value) await ctx.send(embed=embed)
async def _new_task(self, ctx: SlashContext, content: str, due_date: str, due_time: str = None, tags: str = None): """Create a new task.""" guild = data_manager.get_guild(ctx.guild) checks.does_user_have_permission(guild, ctx.author, 'create/edit tasks') if due_time: # Parse due time. due_time = datetime.strptime(due_time.upper(), TIME_FORMATS[guild.locale]).time() else: # Use default value. due_time = datetime.strptime(Tasks.DEFAULT_DUE_TIME, '%H:%M').time() due_date = dt_utils.string_to_date(due_date, guild.tz, DATE_FORMATS[guild.locale] + '/%Y') due_datetime = datetime.combine(due_date, due_time).replace(tzinfo=guild.tz) checks.has_datetime_passed(due_datetime, guild.tz) # Parse tags. team = await guild.get_user_team(self.bot, ctx) tags = team.parse_tags(tags) if tags is not None else [] # Create the task and add it to the team. new_task = models.Task(content, tags, due_datetime) team.add_task(new_task) # Send confirmation. embed = discord.Embed( title=constants.Emojis.CREATE.value + ' New task due by' ' __{due_date}__ __{due_time}__ created:'.format( due_date=dt_utils.date_to_relative_name( new_task.due_datetime.date(), guild.tz, guild.locale), due_time=new_task.due_datetime.strftime( TIME_FORMATS[guild.locale])), description=f'{new_task.content}\n', color=team.role.color).set_footer(text=team.role.name.upper()) if tags: embed.add_field(name=f'{constants.Emojis.TAGS.value} Tags:', value=f'{iter_utils.format_iter(new_task.tags)}') await ctx.send(embed=embed)
async def _control_roles(self, ctx: SlashContext): """Show all control roles in a guild and their permissions.""" guild = data_manager.get_guild(ctx.guild) # Using helper functions, format a dictionary of roles and their formatted permissions. desc = iter_utils.format_dict({f'**{i.role}**': iter_utils.format_dict(i.perms) + '\n' for i in guild.control_roles}) desc += ('\nGrant these roles to apply users their permissions.' '\nYou can delete them through Server Settings like any other role.') await ctx.send(embed=discord.Embed( title=f'{constants.Emojis.CONFIG.value} Control roles in __{ctx.guild}__:', description=f'{desc}', color=constants.Colors.DEFAULT.value))
async def _delete_tasks(self, ctx: SlashContext, date: str, indexes: str = None): """Delete tasks by their due date and index.""" # Get the necessary information and check it. guild = data_manager.get_guild(ctx.guild) checks.does_user_have_permission(guild, ctx.author, 'delete tasks') date = dt_utils.string_to_date(date, guild.tz, DATE_FORMATS[guild.locale] + '/%Y') checks.has_date_passed(date, guild.tz) team = await guild.get_user_team(self.bot, ctx) tasks = checks.are_there_tasks_due_on_date(team, date) if not indexes: # Show every task due on the date, sorted by index. embed = discord.Embed( title= f'{constants.Emojis.DELETE.value} Which tasks do you wish to delete?', description='Use `/delete_tasks {} [indexes]`.\n'.format( dt_utils.format_date(date, guild.tz, guild.locale)), color=team.role.color) embed.description += models.Team.format_tasks_due_on_date( tasks, guild.locale) else: # Delete the tasks. indexes = [int(i) for i in indexes.split(';')] tasks_selected = [] for index in indexes: tasks_selected.append(tasks[index - 1]) team.del_task(tasks_selected[-1]) embed = discord.Embed( title=constants.Emojis.DELETE.value + ' __{}__ task(s) due on' ' __{}__ was/were deleted:'.format( len(tasks_selected), dt_utils.format_date(date, guild.tz, guild.locale)), description=iter_utils.format_iter( list(i.content for i in tasks_selected)), color=team.role.color) embed.set_footer(text=team.role.name.upper()) await ctx.send(embed=embed)
async def _teams(self, ctx: SlashContext): """Show every team in the guild.""" guild = data_manager.get_guild(ctx.guild) sorted_teams = sorted(guild.teams, key=lambda x: x.role.name) desc = '' for team in sorted_teams: desc += '• ' + ('**(Joined)** ' if team.role in ctx.author.roles else '') + f'{team.role}\n' desc += '\nJoin or leave a team with `/team`.' embed = discord.Embed( title=f'{constants.Emojis.TEAMS.value} Teams in __{ctx.guild}__:', description=desc, color=constants.Colors.DEFAULT.value) await ctx.send(embed=embed)
async def _team(self, ctx: SlashContext, team_role: discord.Role): """Join or leave a team.""" guild = data_manager.get_guild(ctx.guild) checks.does_user_have_permission(guild, ctx.author, 'join/leave teams') team = checks.is_role_tied_to_team(guild, team_role) if team in guild.get_user_teams(ctx.author): await ctx.author.remove_roles(team.role) title = f'{constants.Emojis.LEAVE.value} __{ctx.author}__ has left __{team.role}__.' desc = f'Rejoin with `/team {team.role}`.' else: await ctx.author.add_roles(team.role) title = f'{constants.Emojis.JOIN.value} __{ctx.author}__ has joined __{team.role}__.' desc = f'Leave it with `/team {team.role}`.' embed = discord.Embed(title=title, description=desc, color=team.role.color) await ctx.send(embed=embed)
async def _new_team(self, ctx: SlashContext, name: str): """Create a new team in the guild.""" guild = data_manager.get_guild(ctx.guild) checks.does_user_have_permission(guild, ctx.author, 'create teams') role = await discord.Guild.create_role( ctx.guild, name=name, color=constants.Colors.DEFAULT.value) role.position = 0 team = models.Team(role=role) guild.add_team(team) embed = discord.Embed( title= f'{constants.Emojis.TEAMS.value} New team created: \"__{role}__\"', description=f'Join the team with `/team`.' f'\n\nUsers who are granted the {role.mention} role' f' will automatically join it.', color=constants.Colors.DEFAULT.value) await ctx.send(embed=embed)
async def _edit_notifications(self, ctx: SlashContext, batch: bool, early: bool, early_time: int, exact: bool): """Edit when the bot should send task notifications to a team.""" guild = data_manager.get_guild(ctx.guild) team = await guild.get_user_team(self.bot, ctx) team.notify.update({ 'batch': batch, 'early': early, 'early_time': early_time, 'exact': exact }) embed = discord.Embed( title=f'{constants.Emojis.CONFIG.value} Notification settings' f' updated for __{team.role}__:', description=f'Note: this will not affect tasks already created.' f'\n{iter_utils.format_dict(team.notify)}', color=team.role.color).set_footer(text=team.role.name.upper()) await ctx.send(embed=embed)
async def on_guild_join(self, disc_guild_obj: discord.Guild): """Send greetings when entering a guild.""" print('>> {time}: Ivone has entered {guild}'.format( time=datetime.strftime(datetime.now(), '%H:%M'), guild=disc_guild_obj)) guild = data_manager.get_guild(disc_guild_obj) await guild.target_channel.send(embed=discord.Embed( title=f':grinning: Hello, __{guild.disc_guild_obj.name}__!', description= '• **Ivone** is a task management bot for teams using Discord.' ' Be it at school, work or anywhere else, Ivone keeps you organized.' '\n• To start, create a team with `/new_team`.' '\n• For more information, use `/help`.' '\n• Problems? Suggestions? Use `/feedback`!', color=constants.Colors.DEFAULT.value)) # Warn the guild about their current (probably default) timezone and locale. example_date = dt.date(year=1970, month=12, day=1) example_time = dt.time(hour=12, minute=0) tz_offset = guild.tz.utcoffset(None).total_seconds() / 3600 await guild.target_channel.send(embed=discord.Embed( title= f'{constants.Emojis.WARNING.value} Warning: check timezone and locale', description= f'Right now, this server\'s locale is set to {guild.locale}.' f' That means midday of december 1st will be formatted as' f' {example_date.strftime(DATE_FORMATS[guild.locale])}' f' {example_time.strftime(TIME_FORMATS[guild.locale])},' f' for example.' f'\nAlso, the timezone is set to' f' UTC {"+" if tz_offset > 0 else ""}{tz_offset}.' f'\nTo change these settings, use `/change_timezone`' f' and `/change_locale`.', color=constants.Colors.ERROR.value))
async def _edit_task(self, ctx: SlashContext, date: str, task_index: int = None, attribute: str = None, new_value: str = None): """Edit a single attribute in a task.""" # Get the necessary information and check it. guild = data_manager.get_guild(ctx.guild) checks.does_user_have_permission(guild, ctx.author, 'create/edit tasks') date = dt_utils.string_to_date(date, guild.tz, DATE_FORMATS[guild.locale] + '/%Y') checks.has_date_passed(date, guild.tz) team = await guild.get_user_team(self.bot, ctx) tasks = checks.are_there_tasks_due_on_date(team, date) if not task_index: # Show every task due on the selected date. embed = discord.Embed( title= f'{constants.Emojis.EDIT.value} Which task do you wish to edit?', description='Use `/edit_task {} [index]`.\n'.format( dt_utils.format_date(date, guild.tz, guild.locale)), color=team.role.color) embed.description += models.Team.format_tasks_due_on_date( tasks, guild.locale) else: # Show every attribute of the selected task. task = tasks[int(task_index) - 1] if not attribute: embed = discord.Embed( title= f'{constants.Emojis.EDIT.value} Which attribute of this task' f' do you wish to edit?', description= ('Use `/edit_task {date} {task_index} [attribute] [new value]`.' '\n\n{task}').format(date=dt_utils.format_date( date, guild.tz, guild.locale), task_index=task_index, task=task.to_formatted_string()), color=team.role.color) else: # Edit the task. if attribute == 'content': task.content = new_value elif attribute == 'due date': new_due_date = dt_utils.string_to_date( new_value, guild.tz, DATE_FORMATS[guild.locale] + '/%Y') new_due_datetime = task.due_datetime.replace( day=new_due_date.day, month=new_due_date.month, year=new_due_date.year) # In case the task was edited to be due today and had a # due time now already in the past, change it to 23:59. try: checks.has_datetime_passed(new_due_datetime, guild.tz) except checks.DateHasAlreadyPassedError: new_due_datetime = new_due_datetime.replace(hour=23, minute=59) checks.has_datetime_passed(new_due_datetime, guild.tz) task.due_datetime = new_due_datetime elif attribute == 'due time': new_due_time = datetime.strptime( new_value, TIME_FORMATS[guild.locale]) new_due_datetime = task.due_datetime.replace( hour=new_due_time.hour, minute=new_due_time.minute) checks.has_datetime_passed(new_due_datetime, guild.tz) task.due_datetime = new_due_datetime elif attribute == 'tags': task.tags = team.parse_tags(new_value) embed = discord.Embed( title= f'{constants.Emojis.EDIT.value} Task edited successfully:', description=f'{task.to_formatted_string()}', color=team.role.color) embed.set_footer(text=team.role.name.upper()) await ctx.send(embed=embed)
async def devtest(self, ctx: Context): """Edit this whenever a command for testing something is needed.""" guild = data_manager.get_guild(ctx.guild) await guild.announce('target_channel') await ctx.send(Development.CMD_EXECUTED)
async def on_guild_role_delete(self, role: discord.Role): """Delete data that relied on a role that no longer exists.""" guild = data_manager.get_guild(role.guild) if team := guild.get_team(role): guild.teams.remove(team)
async def _edit_control_role(self, ctx: SlashContext, role: discord.Role): """Edit what users with a control role have permission to do.""" guild = data_manager.get_guild(ctx.guild) control_role = checks.is_role_tied_to_control_role(guild, role) control_role.perms = await Configuration.configure_perms(self.bot, ctx, role.name)