async def my_lists(self, ctx: commands.Context): """ Show the lists the current user is subscribed to. :param ctx: The current context. (discord.ext.commands.Context) """ guild_data = await get_guild_data(ctx.message.guild.id) subbed_lists = [] # Error if no lists exist yet if not guild_data.notification_lists: msg = translate("no_existing_lists", await culture(ctx)) return await ctx.send(msg) # Fetch the lists the author is subscribed to for list_name, list_data in guild_data.notification_lists.items(): if ctx.author.id in list_data["users"]: subbed_lists.append(list_name) # Error if the author is not subscribed to any lists if len(subbed_lists) < 1: msg = translate("no_subscriptions_error", await culture(ctx)) return await ctx.send(msg) # Show the user his lists msg = translate("your_lists_title", await culture(ctx)) + "\n - " + "\n - ".join( sorted(subbed_lists)) await ctx.send(msg)
async def unsubscribe(self, ctx: commands.Context, list_name: str, user_id=None): """ Unsubscribes the user from the provided list :param ctx: The current context. (discord.ext.commands.Context) :param list_name: The list to unsubscribe from. (str) """ if not user_id: user_id = ctx.author.id # make sure list is lowercase list_name = list_name.lower() guild_data = await get_guild_data(ctx.message.guild.id) # Error if list does not exist if not guild_data.does_list_exist(list_name): msg = translate("list_err_does_not_exit", await culture(ctx)) return await ctx.send(msg) # Unsubscribe user and error if failed if not await guild_data.unsub_user(list_name, user_id): msg = translate("list_err_not_subscribed", await culture(ctx)).format(str(user_id), list_name) return await ctx.send(msg) # Unsubscribe successful, show result to user msg = translate("list_unsubscribed", await culture(ctx)).format(str(user_id), list_name) await ctx.send(msg)
async def command_help(ctx: commands.Context, command_name: str): """ Builds and sends the help info on the given command to the current context. :param ctx: The current context. (discord.ext.commands.Context) :param command_name: The command to request help on. (str) """ # TODO print aliases embed = discord.Embed() command = ctx.bot.get_command(str(command_name)) title = ctx.prefix + command.name help_text = translate(command.help, await culture(ctx)) if command.usage is None: title += '\n' embed.add_field(name=title, value=help_text, inline=False) return await send_embed(ctx, embed) title += '\u2000' + translate(command.usage, await culture(ctx)) + '\n' embed.add_field(name=title, value=help_text, inline=False) extra_info = translate("help_info_arguments", await culture(ctx)) embed.add_field(name=empty, value=extra_info, inline=False) await send_embed(ctx, embed)
async def show_lists(self, ctx: commands.Context): """ Show all currently existing lists for this server :param ctx: The current context. (discord.ext.commands.Context) """ guild_data = await get_guild_data(ctx.message.guild.id) # Error if no lists exist yet if not guild_data.notification_lists: msg = translate("no_existing_lists", await culture(ctx)) return await ctx.send(msg) # Init text with title text = translate("lists", await culture(ctx)) # Loop and append all lists for list_name, list_data in sorted( guild_data.notification_lists.items()): if list_data["is_custom_emoji"]: text += get_custom_emoji(ctx, int(list_data["emoji"])) else: text += list_data["emoji"] text += " - " + list_name + "\n" # Send lists to context msg = await ctx.send(text) # Add reactions for list_data in guild_data.notification_lists.values(): await msg.add_reaction( list_data["emoji"] if not list_data["is_custom_emoji"] else ctx .bot.get_emoji(int(list_data["emoji"]))) # Setup listeners # TODO make reaction time configurable timeout = 60 * 5 # 5 minutes reaction_added_task = asyncio.create_task( self.wait_for_added_reactions(ctx, msg.id, guild_data, timeout)) reaction_removed_task = asyncio.create_task( self.wait_for_removed_reactions(ctx, msg.id, guild_data, timeout)) # Listen for reactions await reaction_added_task await reaction_removed_task # Delete message await msg.delete()
async def notify(self, ctx: commands.Context, list_name: str, *, message: typing.Optional[str] = None): """ Notify all subscribers for the given list with the given message. :param ctx:The current context. (discord.ext.commands.Context) :param list_name: The name of the list to notify. (str) :param message: The message to send with the notification. (optional - str - default= None) """ guild_data = await get_guild_data(ctx.message.guild.id) # Error if list does not exist list_name = list_name.lower() if not guild_data.does_list_exist(list_name): msg = translate("list_err_does_not_exit", await culture(ctx)) return await ctx.send(msg) # Fetch users to notify users = guild_data.get_users_list(list_name) # Error if no users were found if len(users) < 1: msg = translate("list_err_empty", await culture(ctx)) return await ctx.send(msg) # build users mentioning string user_tags = [] for user_id in users: user_tags.append(f'<@{str(user_id)}>') users_str = ', '.join(user_tags) # Setup the announcement with the subject and caller message_text = translate("notifying", await culture(ctx)).format( list_name.capitalize(), ctx.message.author.id, ctx.guild.get_member(ctx.bot.user.id).display_name) # append the message if provided if message: second_line = translate("notify_message", await culture(ctx)).format(message) + '\n' message_text += second_line await ctx.send(message_text + '\n' + users_str)
async def select_random_user(self, ctx: commands.Context, channel_name: str = None): """ Selects a random user from the server, channel, or online members. :param ctx: The current context. (discord.ext.commands.Context) :param channel_name: The channel to pick from, 'online' in order to pick an online member from the server. (str) """ # Error for missing parameter if channel_name is None: msg = translate("random_user_no_channel", await culture(ctx)) return await ctx.send(msg) # Choose from online members if requested if channel_name == "online": member = pick_random_online_member(ctx) msg = translate("random_user_chosen", await culture(ctx)).format(member.id) return await ctx.send(msg) # Sanitize channel name channel_name = parse_channel(channel_name) # Retrieve channel channel = discord.utils.get(ctx.channel.guild.channels, name=channel_name) # Error if channel does not exist if channel is None: msg = translate("membercount_channel_nonexistant", await culture(ctx)) return await ctx.send(msg) # Error if channel empty if len(channel.members) < 1: msg = translate("membercount_empty_channel", await culture(ctx)).format(channel.id) return await ctx.send(msg) # Pick a random user from channel, and report back to user member = random.choice(channel.members) msg = translate("random_user_chosen", await culture(ctx)).format(member.id) await ctx.send(msg)
async def add_admin(self, ctx: commands.Context): """ Add a new bot admin. :param ctx: The current context. (discord.ext.commands.Context) """ guild_data = await get_guild_data(ctx.guild.id) # Error if not admin if not guild_data.user_is_admin(ctx.author): gif = translate("not_admin_gif", await culture(ctx)) return await ctx.send(gif) # Error if the command had no mention if not ctx.message.mentions: err = translate("mention_required", await culture(ctx)) return await ctx.send(err) # Fetch user data user_id_to_add = ctx.message.mentions[0].id user_to_add = ctx.guild.get_member(int(user_id_to_add)) user_name_to_add = user_to_add.display_name # Error if the user is a server admin (no point in giving him bot admin rights) if ctx.guild.get_member( int(user_id_to_add)).guild_permissions.administrator: err = translate("bot_admin_error_discord_admin", await culture(ctx)).format(user_name_to_add) return await ctx.send(err) # Error if the user already is a bot admin if ctx.message.mentions[0].id in guild_data.bot_admins: err = translate("bot_admin_error_already_admin", await culture(ctx)).format(user_name_to_add) return await ctx.send(err) # Actually add the user to the admins await guild_data.add_admin(user_id_to_add) # Display success msg = translate("bot_admin_add_success", await culture(ctx)).format(user_name_to_add) await ctx.send(msg)
async def set_language(self, ctx: commands.Context): """ Show the current language, and allow for updates. :param ctx: The current context. (discord.ext.commands.Context) """ # Get guild data guild_data = await get_guild_data(ctx.guild.id) # Error if not admin if not guild_data.user_is_admin(ctx.author): gif = translate("not_admin_gif", await culture(ctx)) return await ctx.send(gif) # Get current language current_culture = await culture(ctx) # Show current language msg = translate("current_language", current_culture).format( translate(current_culture, current_culture)) await ctx.send(msg) # Request new language confirmation_message = translate("pick_new_language", current_culture) confirmation_ref = await ctx.send(confirmation_message) for emoji in flags.values(): await confirmation_ref.add_reaction(emoji) # Handle user reaction try: reaction, user = await ctx.bot.wait_for( "reaction_add", check=lambda new_reaction, author: new_reaction.message.id == confirmation_ref.id and author == ctx.message.author, timeout=30.0, ) # Parse reaction new_language = None for lan in flags.keys(): if flags[lan] == reaction.emoji: new_language = lan break # Update language if found if new_language: await guild_data.update_language(new_language) await confirmation_ref.delete() msg = translate("picked_new_language", new_language).format( translate(new_language, new_language)) return await ctx.send(msg) # Handle timeout except asyncio.TimeoutError: await confirmation_ref.delete() msg = translate("snooze_lose", await culture(ctx)) return await ctx.send(msg)
async def admins_bot(self, ctx: commands.Context): """ Show a list of all bot admins. :param ctx: The current context. (discord.ext.commands.Context) """ guild_data = await get_guild_data(ctx.guild.id) # Error if no admins found if not guild_data.bot_admins: msg = translate("bot_admin_err_no_admins", await culture(ctx)) return await ctx.send(msg) # Build list message = translate("bot_admin_list_prefix", await culture(ctx)) for user_id in guild_data.bot_admins: message += f"\n- {ctx.guild.get_member(int(user_id)).display_name}" # Show list await ctx.send(message)
async def on_command_error(self, ctx: commands.Context, error: Any): """ Handles any errors thrown by the commands. :param ctx: The current context. (discord.ext.commands.Context) :param error: The current error. (Any) """ # Log the error log_error(error) # Notify user for MissingRequiredArgument errors if isinstance(error, commands.MissingRequiredArgument): command_name = ctx.message.content.split(" ")[0] msg = translate("err_missing_parameter", await culture(ctx)).format(command_name, error.param.name) return await ctx.send(msg) # Notify user with general error msg = translate("err_unrecognized_command", await culture(ctx)) await ctx.send(msg)
async def subscribe(self, ctx: commands.Context, list_name: typing.Optional[str] = None, user_id=None): """ If used with list_name, subscribes the user to that list if possible. If used without parameter it prints the existing lists, and allows users to subscribe by adding reactions. :param ctx: The current context (discord.ext.commands.Context) :param list_name: The list to subscribe to. (optional - str - default = None) """ if not user_id: user_id = ctx.author.id # Execute 'show_lists' if no parameter provided if not list_name: return await self.show_lists(ctx) # Make sure list is lowercase list_name = list_name.lower() guild_data = await get_guild_data(ctx.message.guild.id) # Error if list does not exist if not guild_data.does_list_exist(list_name): msg = translate("list_err_does_not_exit", await culture(ctx)) return await ctx.send(msg) # Subscribe user and error if failed if not await guild_data.sub_user(list_name, user_id): msg = translate("list_err_already_subscribed", await culture(ctx)).format(str(user_id), list_name) return await ctx.send(msg) # Subscription successful, show result to user msg = translate("list_subscribed", await culture(ctx)).format(str(user_id), list_name) await ctx.send(msg)
async def remove_list(self, ctx: commands.Context, list_name: str): """ Removes the given list. :param ctx: The current contest. (discord.ext.commands.Context) :param list_name: The list to be removed. (str) """ guild_data = await get_guild_data(ctx.message.guild.id) # Error if not admin if not guild_data.user_is_admin(ctx.author): gif = translate("not_admin_gif", await culture(ctx)) return await ctx.send(gif) # Make sure the list name is lowercase list_name = list_name.lower() # Error if list does not exist if not guild_data.does_list_exist(list_name): msg = translate("list_err_does_not_exit", await culture(ctx)) return await ctx.send(msg) # Ask user confirmation msg = translate("confirmation_question", await culture(ctx)) confirmation_ref = await ctx.send(msg) await confirmation_ref.add_reaction(thumbs_up) await confirmation_ref.add_reaction(thumbs_down) # Handle user reaction try: reaction, user = await ctx.bot.wait_for( "reaction_add", check=lambda emoji, author: emoji.message.id == confirmation_ref.id and author == ctx.message.author, timeout=30.0, ) # Process emoji if reaction.emoji == thumbs_up: await guild_data.remove_notification_list(list_name) msg = translate("remove_list_success", await culture(ctx)).format(list_name) await ctx.send(msg) elif reaction.emoji == thumbs_down: msg = translate("remove_list_cancel", await culture(ctx)).format(list_name) await ctx.send(msg) # Delete message await confirmation_ref.delete() # Handle Timeout except asyncio.TimeoutError: await confirmation_ref.delete() msg = translate("snooze_lose", await culture(ctx)) return await ctx.send(msg)
async def send_embed(ctx: commands.Context, embed: discord.Embed): """ Send embed to the current context :param ctx: The current context. (discord.ext.commands.Context) :param embed: the embed to send back to the current context. (discord.Embed) """ if len(embed) > 0: return await ctx.channel.send(embed=embed) # if we reach here, the embed is empty and we show an error name = empty value = translate("help_err_not_recognized", await culture(ctx)).format(ctx.prefix) embed.add_field(name=name, value=value, inline=False) await ctx.channel.send(ctx, embed)
async def build_commands_message(cog: commands.Cog, current_culture: str) -> str: """ Builds the help info for a certain cog :param cog: The requested cog. (discord.ext.commands.Cog) :param current_culture: The culture in which the help should be generated (str) :return: The help info for the given cog (str) """ message = {} for command in cog.get_commands(): if command.hidden: continue if command.brief is None: message[command.name] = translate(command.help, current_culture) else: message[command.name] = translate(command.brief, current_culture) strings = [] for name in sorted(message): strings.append("*{0}*\n \u2003 {1}\n".format(name, message[name])) return ' '.join(strings)
async def general_help(ctx: commands.Context): """ Builds and sends the general help info to the current context. :param ctx: The current context. (discord.ext.commands.Context) """ embed = discord.Embed() for cog_name in ctx.bot.cogs: content = await build_commands_message(ctx.bot.get_cog(cog_name), await culture(ctx)) if len(content) == 0: continue title = build_title(cog_name) embed.add_field(name=title, value=content, inline=False) extra_info_command = translate("help_info_command", await culture(ctx)).format(ctx.prefix) extra_info_category = translate("help_info_category", await culture(ctx)).format(ctx.prefix) extra_info = f'{extra_info_command}\n{extra_info_category}' embed.add_field(name=empty, value=extra_info, inline=False) await send_embed(ctx, embed)
async def subject_help(ctx: commands.Context, cog_name: str): """ Builds and sends the help info for the given cog to the current context. :param ctx: The current context. (discord.ext.commands.Context) :param cog_name: The subject to request help on. (str) """ embed = discord.Embed() cog = ctx.bot.get_cog(cog_name) content = await build_commands_message(cog, await culture(ctx)) # add subject title title = f'**{cog_name}**\n' embed.add_field(name=title, value=content, inline=False) # add extra info extra_info = translate("help_info_command", await culture(ctx)).format(ctx.prefix) embed.add_field(name=empty, value=extra_info, inline=False) await send_embed(ctx, embed)
async def on_member_join(self, member: discord.Member): """ This function is executed on every member_join event, and logs a message if a certain threshold is passed. :param member: The member that just joined. (discord.Member) """ # Fetch server guild = member.guild # Get member count member_count = len(guild.members) # Return if count is no multiple of threshold if member_count % member_notification_trigger != 0: return # Send message to dedicated channel channel = discord.utils.get(guild.channels, name=notification_channel_name) culture = (await get_guild_data(member.guild.id)).culture msg = translate("member_join_count", culture).format(member.guild, member_count) await channel.send(msg)
async def count(self, ctx, channel_name=None): """ Count the members in a given channel, the members in the current server, or the online members in the current server. :param ctx: The current context. (discord.ext.commands.Context) :param channel_name: The name of the channel to count, 'online' to count online members, or nothing to count the entire server. (optional - str - default = None) """ if channel_name == "online": online_member_count = count_online_members(ctx) msg = translate("membercount_online_result", await culture(ctx)).format(online_member_count) return await ctx.send(msg) if not channel_name: msg = translate("membercount_server_result", await culture(ctx)).format(len(ctx.guild.members), ctx.guild.name) return await ctx.send(msg) # Sanitize channel name channel_name = parse_channel(channel_name) # Retrieve channel channel = discord.utils.get(ctx.channel.guild.channels, name=channel_name) if channel is None: msg = translate("membercount_channel_nonexistant", await culture(ctx)) return await ctx.send(msg) if len(channel.members) < 1: msg = translate("membercount_empty_channel", await culture(ctx)).format(channel.id) return await ctx.send(msg) if len(channel.members) == 1: msg = translate("membercount_single_person", await culture(ctx)).format(channel.id) return await ctx.send(msg) msg = translate("membercount_channel_result", await culture(ctx)).format(len(channel.members), channel.id) await ctx.send(msg)
async def remove_admin(self, ctx: commands.Context): """ Remove a bot admin. :param ctx: The current context. (discord.ext.commands.Context) """ guild_data = await get_guild_data(ctx.guild.id) # Error if not admin if not guild_data.user_is_admin(ctx.author): gif = translate("not_admin_gif", await culture(ctx)) return await ctx.send(gif) # Error if the command had no mention if not ctx.message.mentions: err = translate("mention_required", await culture(ctx)) return await ctx.send(err) # Fetch user data user_id_to_remove = ctx.message.mentions[0].id user_to_remove = ctx.guild.get_member(int(user_id_to_remove)) user_name_to_remove = user_to_remove.display_name # Error if the user is not a bot admin if user_id_to_remove not in guild_data.bot_admins: err = translate("bot_admin_error_not_admin", await culture(ctx)).format(user_name_to_remove) return await ctx.send(err) # User is trying to revoke his own rights if ctx.author.id == user_id_to_remove: # Ask confirmation confirmation_text = translate("bot_admin_confirm_remove_self", await culture(ctx)) confirmation_ref = await ctx.send(confirmation_text) await confirmation_ref.add_reaction(thumbs_up) await confirmation_ref.add_reaction(thumbs_down) # Handle user reaction try: reaction, user = await ctx.bot.wait_for( "reaction_add", check=lambda new_reaction, author: new_reaction.message.id == confirmation_ref.id and author == ctx.message.author, timeout=30.0, ) # Process thumbs up if reaction.emoji == thumbs_up: await guild_data.remove_admin(user_id_to_remove) msg = translate("bot_admin_remove_success", await culture(ctx)).format(user_name_to_remove) return await ctx.send(msg) # Process thumbs down if reaction.emoji == thumbs_down: msg = translate("bot_admin_remove_cancel", await culture(ctx)).format(user_name_to_remove) return await ctx.send(msg) # If we reach here, an invalid emoji was used await confirmation_ref.delete() return # Handle timeout except asyncio.TimeoutError: await confirmation_ref.delete() msg = translate("snooze_lose", await culture(ctx)) return await ctx.send(msg) # Authorized user wants to remove another bot admin # Ask confirmation confirmation_text = translate("bot_admin_confirm_remove", await culture(ctx)).format(user_name_to_remove) confirmation_ref = await ctx.send(confirmation_text) await confirmation_ref.add_reaction(thumbs_up) await confirmation_ref.add_reaction(thumbs_down) # Handle user reaction try: reaction, user = await ctx.bot.wait_for( "reaction_add", check=lambda new_reaction, author: new_reaction.message.id == confirmation_ref.id and author == ctx.message.author, timeout=30.0, ) # Process thumbs up if reaction.emoji == thumbs_up: await guild_data.remove_admin(user_id_to_remove) msg = translate("bot_admin_remove_success", await culture(ctx)).format(user_name_to_remove) return await ctx.send(msg) # Process thumbs down elif reaction.emoji == thumbs_down: msg = translate("bot_admin_remove_cancel", await culture(ctx)).format(user_name_to_remove) return await ctx.send(msg) # If we reach here, an invalid emoji was used await confirmation_ref.delete() return # Handle timeout except asyncio.TimeoutError: await confirmation_ref.delete() msg = translate("snooze_lose", await culture(ctx)) return await ctx.send(msg)
async def add_list(self, ctx: commands.Context, list_name: str): """ Adds a new notification list with the given name. :param ctx: The current context. (discord.ext.commands.Context) :param list_name: The name to be used for the list. (str) """ guild_data = await get_guild_data(ctx.message.guild.id) # Error if not admin if not guild_data.user_is_admin(ctx.author): gif = translate("not_admin_gif", await culture(ctx)) return await ctx.send(gif) # Make sure the list name is lowercase list_name = list_name.lower() # Error if list already exists if guild_data.does_list_exist(list_name): msg = translate("list_already_exists", await culture(ctx)).format(list_name) return await ctx.send(msg) # Request emoji from user msg = await ctx.send("What emoji do you want to use for " + list_name + " ?") # Handle user reaction try: reaction, user = await ctx.bot.wait_for( "reaction_add", check=lambda emoji, author: emoji.message.id == msg.id and author == ctx.message.author, timeout=30.0, ) # Process emoji if reaction.custom_emoji: try: reaction_emoji = reaction.emoji.id emoji_to_print = get_custom_emoji(ctx, reaction_emoji) custom_emoji = True except AttributeError: msg = translate("unknown_emoji", await culture(ctx)) return await ctx.send(msg) else: reaction_emoji = reaction.emoji emoji_to_print = str(reaction_emoji) custom_emoji = False # Error if emoji is being used already on this server for data in guild_data.notification_lists.values(): if reaction_emoji == data["emoji"]: msg = translate("emoji_already_in_use", await culture(ctx)) return await ctx.send(msg) # Add list to GuildData await guild_data.add_notification_list(list_name, reaction_emoji, custom_emoji) # Show success message to user await ctx.send("The list `" + list_name + "` is saved with the emoji " + emoji_to_print) # Handle timeout except asyncio.TimeoutError: await msg.delete() msg = translate("snooze_lose", await culture(ctx)) return await ctx.send(msg)