async def settings_welcome(self, ctx: NabCtx, *, message: str = None): """Changes the message new members receive when joining. This is initially disabled. You can use formatting to show dynamic values: - {server} -> The server's name. - {server.owner} -> The server's owner name - {server.owner.mention} -> Mention to the server's owner. - {owner} -> The name of the server owner - {owner.mention} -> Mention the server owner. - {user} -> The name of the user that joined. - {user.mention} -> Mention the user that joined. - {bot} -> The name of the bot - {bot.mention} -> Mention the bot. Be sure to change the welcome channel too.""" current_message = get_server_property(ctx.guild.id, "welcome") if message is None: await self.show_info_embed(ctx, current_message, "Any text", "message/disable") return if message.lower() == "disable": if current_message is None: await ctx.send("Welcome messages are already disabled.") return msg = await ctx.send( "Are you sure you want to disable welcome messages?") new_value = None else: try: if len(message) > 1000: await ctx.send( f"{ctx.tick(False)} This message is too long! {len(message):,}/1000 characters." ) return formatted = message.format(server=ctx.guild, bot=self.bot, owner=ctx.guild.owner, user=ctx.author) msg = await ctx.send( "Do you want to set this as the new message?\n" "*This is how your message would look if **you** joined.*", embed=discord.Embed(title="Message Preview", colour=discord.Colour.blurple(), description=formatted)) new_value = message except KeyError as e: await ctx.send(f"{ctx.tick(False)} Unknown keyword {e}.") return confirm = await ctx.react_confirm(msg, timeout=60, delete_after=True) if not confirm: await ctx.message.delete() return set_server_property(ctx.guild.id, "welcome", new_value) if new_value is None: await ctx.send( f"{ctx.tick(True)} The welcome message has been disabled.") else: await ctx.send(f"{ctx.tick(True)} Welcome message updated.")
async def settings_commandsonly(self, ctx: NabCtx, option: str = None): """Sets whether only commands are allowed in the command channel. If this is enabled, everything that is not a message will be deleted from the command channel. This allows the channel to be used exclusively for commands. If the channel is shared with other command bots, this should be off. Note that the bot needs `Manage Messages` permission to delete messages.""" def yes_no(choice: bool): return "Yes" if choice else "No" if option is None: current = get_server_property(ctx.guild.id, "commandsonly", is_int=True) if current is None: current_value = f"{yes_no(config.ask_channel_delete)} (Global default)" else: current_value = yes_no(current) await self.show_info_embed(ctx, current_value, "yes/no", "yes/no") return if option.lower() == "yes": set_server_property(ctx.guild.id, "commandsonly", True) await ctx.send( f"{ctx.tick(True)} I will delete non-commands in the command channel from now on." ) elif option.lower() == "no": set_server_property(ctx.guild.id, "commandsonly", False) await ctx.send( f"{ctx.tick(True)} I won't delete non-commands in the command channel from now on." ) else: await ctx.send("That's not a valid option, try **yes** or **no**.")
async def about(self, ctx: NabCtx): """Shows basic information about the bot.""" embed = discord.Embed(description=ctx.bot.description, colour=discord.Colour.blurple()) embed.set_author(name="NabBot", url="https://github.com/Galarzaa90/NabBot", icon_url="https://github.com/fluidicon.png") prefixes = list(config.command_prefix) if ctx.guild: prefixes = get_server_property(ctx.guild.id, "prefixes", deserialize=True, default=prefixes) prefixes_str = "\n".join(f"- `{p}`" for p in prefixes) embed.add_field(name="Prefixes", value=prefixes_str, inline=False) embed.add_field(name="Authors", value="\u2023 [Galarzaa90](https://github.com/Galarzaa90)\n" "\u2023 [Nezune](https://github.com/Nezune)") embed.add_field(name="Created", value="March 30th 2016") embed.add_field(name="Version", value=f"v{self.bot.__version__}") embed.add_field(name="Platform", value="Python " "([discord.py](https://github.com/Rapptz/discord.py/tree/rewrite))") embed.add_field(name="Servers", value=f"{len(self.bot.guilds):,}") embed.add_field(name="Users", value=f"{len(self.bot.users):,}") embed.add_field(name="Links", inline=False, value=f"[Add to your server](https://discordbots.org/bot/178966653982212096) | " f"[Support Server](https://discord.me/nabbot) | " f"[Docs](https://galarzaa90.github.io/NabBot) | " f"[Donate](https://www.paypal.com/cgi-bin/webscr?" f"cmd=_s-xclick&hosted_button_id=B33DCPZ9D3GMJ)") embed.set_footer(text=f"Uptime | {parse_uptime(self.bot.start_time, True)}") await ctx.send(embed=embed)
async def cleanup(self, ctx: NabCtx, limit: int = 50): """Cleans the channel from bot commands. If the bot has `Manage Messages` permission, it will also delete command invocation messages.""" count = 0 prefixes = get_server_property(ctx.guild.id, "prefixes", deserialize=True, default=config.command_prefix) # Also skip death and levelup messages from cleanup announce_prefix = (config.levelup_emoji, config.death_emoji, config.pvpdeath_emoji) if ctx.bot_permissions.manage_messages: def check(m: discord.Message): return (m.author == ctx.me and not m.content.startswith(announce_prefix)) or \ m.content.startswith(tuple(prefixes)) deleted = await ctx.channel.purge(limit=limit, check=check) count = len(deleted) else: with ctx.typing(): async for msg in ctx.channel.history(limit=limit): if msg.author == ctx.me: await msg.delete() count += 1 if not count: return await ctx.send("There are no messages to clean.", delete_after=10) await ctx.send(f"{ctx.tick()} Deleted {count:,} messages.", delete_after=20)
async def settings_levelschannel(self, ctx: NabCtx, channel: str = None): """Changes the channel where levelup and deaths are announced. This is were all level ups and deaths of registered characters will be announced. By default, the highest channel on the list where the bot can send messages will be used. If the assigned channel is deleted or forbidden, the top channel will be used again. If this is disabled, Announcements won't be made, but there will still be tracking. """ current_channel_id = get_server_property(ctx.guild.id, "levels_channel", is_int=True) if channel is None: current_value = self.get_current_channel(ctx, current_channel_id) await self.show_info_embed( ctx, current_value, "A channel's name or ID, or `disable`.", "channel/disable") return if channel.lower() == "disable": if current_channel_id is 0: await ctx.send( "Level and deaths announcements are already disabled.") return message = await ctx.send( f"Are you sure you want to disable the level & deaths channel?" ) new_value = 0 else: try: new_channel = await commands.TextChannelConverter().convert( ctx, channel) except commands.BadArgument: await ctx.send( "I couldn't find that channel, are you sure it exists?") return perms = new_channel.permissions_for(ctx.me) if not perms.read_messages or not perms.send_messages: await ctx.send( f"I don't have permission to use {new_channel.mention}.") return message = await ctx.send( f"Are you sure you want {new_channel.mention} as the new level & deaths channel?" ) new_value = new_channel.id confirm = await ctx.react_confirm(message, timeout=60, delete_after=True) if not confirm: await ctx.message.delete() return set_server_property(ctx.guild.id, "levels_channel", new_value) if new_value is 0: await ctx.send( f"{ctx.tick(True)} The level & deaths channel has been disabled." ) else: await ctx.send(f"{ctx.tick(True)} <#{new_value}> will now be used." )
async def settings_welcomechannel(self, ctx: NabCtx, channel: str = None): """Changes the channel where new members are welcomed. A welcome message must be set for this setting to work. If the channel becomes unavailable, private messages will be used. Note that private messages are not reliable since new users can have them disabled before joining. To disable this, you must disable welcome messages using `settings welcome`. """ current_channel_id = get_server_property(ctx.guild.id, "welcome_channel", is_int=True) if channel is None: current_value = self.get_current_channel(ctx, current_channel_id, pm_fallback=True) await self.show_info_embed( ctx, current_value, "A channel's name or ID, or `private`.", "channel/private") return if channel.lower() == "private": if current_channel_id is None: await ctx.send("Welcome messages are already private.") return message = await ctx.send( f"Are you sure you want to make welcome messages private?") new_value = None else: try: new_channel = await commands.TextChannelConverter().convert( ctx, channel) except commands.BadArgument: await ctx.send( "I couldn't find that channel, are you sure it exists?") return perms = new_channel.permissions_for(ctx.me) if not perms.read_messages or not perms.send_messages: await ctx.send( f"I don't have permission to use {new_channel.mention}.") return message = await ctx.send( f"Are you sure you want {new_channel.mention} as the new welcome channel?" ) new_value = new_channel.id confirm = await ctx.react_confirm(message, timeout=60, delete_after=True) if not confirm: await ctx.message.delete() return set_server_property(ctx.guild.id, "welcome_channel", new_value) if new_value is None: await ctx.send( f"{ctx.tick(True)} Welcome messages will be sent privately.") else: await ctx.send( f"{ctx.tick(True)} <#{new_value}> will now be used for welcome messages." )
async def on_member_join(self, member: discord.Member): """ Called when a member joins a guild (server) the bot is in.""" log.info("{0.display_name} (ID: {0.id}) joined {0.guild.name}".format( member)) # Updating member list if member.id in self.members: self.members[member.id].append(member.guild.id) else: self.members[member.id] = [member.guild.id] # No welcome message for lite servers and servers not tracking worlds if member.guild.id in config.lite_servers or tracked_worlds.get( member.guild.id) is None: return server_welcome = get_server_property("welcome", member.guild.id, "") pm = (config.welcome_pm + "\n" + server_welcome).format( user=member, server=member.guild, bot=self.user, owner=member.guild.owner) embed = discord.Embed(description="{0.mention} joined.".format(member)) icon_url = get_user_avatar(member) embed.colour = discord.Colour.green() embed.set_author(name="{0.name}#{0.discriminator}".format(member), icon_url=icon_url) embed.timestamp = dt.datetime.utcnow() # Check if user already has characters registered and announce them on log_channel # This could be because he rejoined the server or is in another server tracking the same worlds world = tracked_worlds.get(member.guild.id) if world is not None: c = userDatabase.cursor() try: c.execute( "SELECT name, vocation, ABS(level) as level, guild " "FROM chars WHERE user_id = ? and world = ?", ( member.id, world, )) results = c.fetchall() if len(results) > 0: pm += "\nYou already have these characters in {0} registered to you: {1}"\ .format(world, join_list([r["name"] for r in results], ", ", " and ")) characters = [ "\u2023 {name} - Level {level} {voc} - **{guild}**". format(**c, voc=get_voc_abb_and_emoji(c["vocation"])) for c in results ] embed.add_field(name="Registered characters", value="\n".join(characters)) finally: c.close() await self.send_log_message(member.guild, embed=embed) await member.send(pm)
async def settings_eventschannel(self, ctx: NabCtx, channel: str = None): """Changes the channel where upcoming events are announced. This is where announcements of events about to happen will be made. If the assigned channel is deleted or forbidden, the top channel will be used. If this is disabled, users that subscribed to the event will still receive notifications via PM. """ current_channel_id = get_server_property(ctx.guild.id, "events_channel", is_int=True, default=0) if channel is None: current_value = self.get_current_channel(ctx, current_channel_id) await self.show_info_embed( ctx, current_value, "A channel's name or ID, or `disable`.", "channel/disable") return if channel.lower() == "disable": if current_channel_id is 0: await ctx.send("Event announcements are already disabled.") return message = await ctx.send( f"Are you sure you want to disable events announcements?") new_value = 0 else: try: new_channel = await commands.TextChannelConverter().convert( ctx, channel) except commands.BadArgument: await ctx.send( "I couldn't find that channel, are you sure it exists?") return perms = new_channel.permissions_for(ctx.me) if not perms.read_messages or not perms.send_messages: await ctx.send( f"I don't have permission to use {new_channel.mention}.") return message = await ctx.send( f"Are you sure you want {new_channel.mention} as the new events channel?" ) new_value = new_channel.id confirm = await ctx.react_confirm(message, timeout=60, delete_after=True) if not confirm: await ctx.message.delete() return set_server_property(ctx.guild.id, "events_channel", new_value) if new_value is 0: await ctx.send( f"{ctx.tick(True)} The events channel has been disabled.") else: await ctx.send( f"{ctx.tick(True)} <#{new_value}> will now be used for events." )
def is_askchannel(self): """Checks if the current channel is the command channel""" ask_channel_id = get_server_property(self.guild.id, "ask_channel", is_int=True) ask_channel = self.guild.get_channel(ask_channel_id) if ask_channel is None: return self.channel.name == config.ask_channel_name return ask_channel == self.channel
async def on_message(self, message: discord.Message): """Called every time a message is sent on a visible channel.""" # Ignore if message is from any bot if message.author.bot: return ctx = await self.get_context(message, cls=context.NabCtx) if ctx.command is not None: await self.invoke(ctx) return # This is a PM, no further info needed if message.guild is None: return if message.content.strip() == f"<@{self.user.id}>": prefixes = list(config.command_prefix) if ctx.guild: prefixes = get_server_property(ctx.guild.id, "prefixes", deserialize=True, default=prefixes) if prefixes: prefixes_str = ", ".join(f"`{p}`" for p in prefixes) return await ctx.send( f"My command prefixes are: {prefixes_str}, and mentions. " f"To see my commands, try: `{prefixes[0]}help.`", delete_after=10) else: return await ctx.send( f"My command prefix is mentions. " f"To see my commands, try: `@{self.user.name} help.`", delete_after=10) server_delete = get_server_property(message.guild.id, "commandsonly", is_int=True) global_delete = config.ask_channel_delete if (server_delete is None and global_delete) or server_delete: if ctx.is_askchannel: try: await message.delete() except discord.Forbidden: # Bot doesn't have permission to delete message pass
async def settings_prefix(self, ctx: NabCtx, prefix: PrefixConverter = None): """Changes the command prefix for this server. The prefix are the characters that go before a command's name, in order for the bot to recognize the command. A maximum of 5 prefixes can be set per server. To remove an existing prefix, use it as a parameter. If you want to have a space at the end, such as: `nabbot help`, you have to use double quotes "nabbot ". Multiple words also require using quotes. Mentioning the bot is always a valid command and can't be changed.""" prefixes = get_server_property(ctx.guild.id, "prefixes", deserialize=True, default=list(config.command_prefix)) if prefix is None: current_value = ", ".join( f"`{p}`" for p in prefixes) if len(prefixes) > 0 else "Mentions only" await self.show_info_embed(ctx, current_value, "Any text", "prefix") return remove = False if prefix in prefixes: message = await ctx.send( f"Do you want to remove `{prefix}` as a prefix?") remove = True else: if len(prefixes) >= 5: await ctx.send("You can't have more than 5 command prefixes.") return message = await ctx.send( f"Do you want to add `{prefix}` as a prefix?") confirm = await ctx.react_confirm(message, timeout=60, delete_after=True) if not confirm: await ctx.message.delete() return if remove: prefixes.remove(prefix) await ctx.send( f"{ctx.tick(True)} The prefix `{prefix}` was removed.") else: prefixes.append(prefix) await ctx.send(f"{ctx.tick(True)} The prefix `{prefix}` was added." ) set_server_property(ctx.guild.id, "prefixes", sorted(prefixes, reverse=True), serialize=True)
def _prefix_callable(bot, msg): user_id = bot.user.id base = [f'<@!{user_id}> ', f'<@{user_id}> '] if msg.guild is None: base.extend(config.command_prefix) else: base.extend( get_server_property(msg.guild.id, "prefixes", deserialize=True, default=config.command_prefix)) base = sorted(base, reverse=True) return base
def ask_channel_name(self) -> Optional[str]: """Gets the name of the ask channel for the current server. :return: The name of the ask channel if applicable :rtype: str or None""" if self.guild is None: return None ask_channel_id = get_server_property(self.guild.id, "ask_channel", is_int=True) ask_channel = self.guild.get_channel(ask_channel_id) if ask_channel is None: return config.ask_channel_name return ask_channel.name
async def settings_minlevel(self, ctx: NabCtx, level: int = None): """Sets the minimum level for death and level up announcements. Level ups and deaths under the minimum level are still and can be seen by checking the character directly.""" current_level = get_server_property(ctx.guild.id, "announce_level", is_int=True) if level is None: if current_level is None: current_value = f"`{config.announce_threshold}` (global default)" else: current_value = f"`{current_level}`" return await self.show_info_embed(ctx, current_value, "Any number greater than 1", "level") if level < 1: return await ctx.send( f"{ctx.tick(False)} Level can't be lower than 1.") set_server_property(ctx.guild.id, "announce_level", level) await ctx.send( f"{ctx.tick()} Minimum announce level has been set to `{level}`.")
async def on_message(self, message: discord.Message): """Called every time a message is sent on a visible channel.""" # Ignore if message is from any bot if message.author.bot: return ctx = await self.get_context(message, cls=context.NabCtx) if ctx.command is not None: await self.invoke(ctx) return # This is a PM, no further info needed if message.guild is None: return server_delete = get_server_property(message.guild.id, "commandsonly", is_int=True) global_delete = config.ask_channel_delete if (server_delete is None and global_delete) or server_delete: if ctx.is_askchannel: try: await message.delete() except discord.Forbidden: # Bot doesn't have permission to delete message pass
async def on_member_join(self, member: discord.Member): """ Called when a member joins a guild (server) the bot is in.""" log.info("{0.display_name} (ID: {0.id}) joined {0.guild.name}".format( member)) # Updating member list if member.id in self.members: self.members[member.id].append(member.guild.id) else: self.members[member.id] = [member.guild.id] embed = discord.Embed(description="{0.mention} joined.".format(member), color=discord.Color.green()) embed.set_author( name="{0.name}#{0.discriminator} (ID: {0.id})".format(member), icon_url=get_user_avatar(member)) previously_registered = "" # If server is not tracking worlds, we don't check the database if member.guild.id in config.lite_servers or self.tracked_worlds.get( member.guild.id) is None: await self.send_log_message(member.guild, embed=embed) else: # Check if user already has characters registered and announce them on log_channel # This could be because he rejoined the server or is in another server tracking the same worlds world = self.tracked_worlds.get(member.guild.id) previously_registered = "" if world is not None: c = userDatabase.cursor() try: c.execute( "SELECT name, vocation, ABS(level) as level, guild " "FROM chars WHERE user_id = ? and world = ?", ( member.id, world, )) results = c.fetchall() if len(results) > 0: previously_registered = "\n\nYou already have these characters in {0} registered to you: *{1}*"\ .format(world, join_list([r["name"] for r in results], ", ", " and ")) characters = [ "\u2023 {name} - Level {level} {voc} - **{guild}**" .format(**c, voc=get_voc_abb_and_emoji(c["vocation"])) for c in results ] embed.add_field(name="Registered characters", value="\n".join(characters)) finally: c.close() self.dispatch("character_change", member.id) await self.send_log_message(member.guild, embed=embed) welcome_message = get_server_property(member.guild.id, "welcome") welcome_channel_id = get_server_property(member.guild.id, "welcome_channel", is_int=True) if welcome_message is None: return message = welcome_message.format(user=member, server=member.guild, bot=self, owner=member.guild.owner) message += previously_registered channel = member.guild.get_channel(welcome_channel_id) # If channel is not found, send via pm as fallback if channel is None: channel = member try: await channel.send(message) except discord.Forbidden: try: # If bot has no permissions to send the message on that channel, send on private message # If the channel was already a private message, don't try it again if welcome_channel_id is None: return await member.send(message) except discord.Forbidden: pass
async def settings_askchannel(self, ctx: NabCtx, channel: str = None): """Changes the channel where longer replies for commands are given. In this channel, pagination commands show more entries at once and command replies in general are longer.""" current_channel_id = get_server_property(ctx.guild.id, "ask_channel", is_int=True) current_channel = ctx.guild.get_channel(current_channel_id) if channel is None: if current_channel: perms = current_channel.permissions_for(ctx.me) else: perms = discord.Permissions() ok = False if current_channel_id is None: current_value = f"None." elif current_channel is None: current_value = "Previous channel was deleted." elif not perms.read_messages or not perms.send_messages: current_value = f"{current_channel.mention}, but I can't use the channel." else: current_value = current_channel.mention ok = True if not ok: current_value += f" By default, I'll use any channel named {config.ask_channel_name}." await self.show_info_embed(ctx, current_value, "A channel's name or id, or `none`.", "channel/none") return if channel.lower() == "none": if current_channel_id is None: await ctx.send("There's no command channel set.") return message = await ctx.send( f"Are you sure you want to delete the set command channel?") new_value = 0 else: try: new_channel = await commands.TextChannelConverter().convert( ctx, channel) except commands.BadArgument: await ctx.send( "I couldn't find that channel, are you sure it exists?") return perms = new_channel.permissions_for(ctx.me) if not perms.read_messages or not perms.send_messages: await ctx.send( f"I don't have permission to use {new_channel.mention}.") return message = await ctx.send( f"Are you sure you want {new_channel.mention} as the new commands channel?" ) new_value = new_channel.id confirm = await ctx.react_confirm(message, timeout=60, delete_after=True) if not confirm: await ctx.message.delete() return set_server_property(ctx.guild.id, "ask_channel", new_value) if new_value is 0: await ctx.send( f"{ctx.tick(True)} The command channel was deleted." f"I will still use any channel named **{config.ask_channel_name}**." ) else: await ctx.send( f"{ctx.tick(True)} <#{new_value}> will now be used as a command channel." )