async def prefix(self, ctx, *, prefix: str = None): if prefix is None: await ctx.send(Embed(f"The prefix for this server is `{ctx.prefix}`.")) return if (await ctx.message.member.guild_permissions()).administrator is False: raise commands.MissingPermissions(["administrator"]) if len(prefix) > 10: await ctx.send(ErrorEmbed("The chosen prefix is too long.")) return if prefix == self.bot.config.DEFAULT_PREFIX: prefix = None await tools.get_data(self.bot, ctx.guild.id) async with self.bot.pool.acquire() as conn: await conn.execute("UPDATE data SET prefix=$1 WHERE guild=$2", prefix, ctx.guild.id) await self.bot.state.set(f"prefix:{ctx.guild.id}", "" if prefix is None else prefix) await ctx.send( Embed( "Successfully changed the prefix to " f"`{self.bot.config.DEFAULT_PREFIX if prefix is None else prefix}`.", ) )
async def confirmation(self, ctx): data = await tools.get_user_settings(self.bot, ctx.author.id) if not data or data[0] is True: async with self.bot.pool.acquire() as conn: await conn.execute( "INSERT INTO account VALUES ($1, $2, NULL) ON CONFLICT (identifier) DO UPDATE " "SET confirmation=$2", ctx.author.id, False, ) await ctx.send( Embed( "Confirmation messages are disabled. To send messages to another server, " f"either use `{ctx.prefix}new <message>` or `{ctx.prefix}send <server ID> " "<message>`.", )) return async with self.bot.pool.acquire() as conn: await conn.execute( "UPDATE account SET confirmation=$1 WHERE identifier=$2", True, ctx.author.id, ) await ctx.send(Embed("Confirmation messages are enabled."))
async def confirmation(self, ctx): data = await tools.get_user_settings(self.bot, ctx.author.id) if not data or data[0] is True: async with self.bot.pool.acquire() as conn: if not data: await conn.execute( "INSERT INTO preference (identifier, confirmation) VALUES ($1, $2)", ctx.author.id, False, ) else: await conn.execute( "UPDATE preference SET confirmation=$1 WHERE identifier=$2", False, ctx.author.id, ) await ctx.send(embed=Embed( description= "Confirmation messages are disabled. To send messages to another server, either use " f"`{ctx.prefix}new <message>` or `{ctx.prefix}send <server ID> <message>`.", )) return async with self.bot.pool.acquire() as conn: await conn.execute( "UPDATE preference SET confirmation=$1 WHERE identifier=$2", True, ctx.author.id, ) await ctx.send(embed=Embed( description="Confirmation messages are enabled."))
async def viewsnippet(self, ctx, *, name: str = None): if name: async with self.bot.pool.acquire() as conn: res = await conn.fetchrow( "SELECT name, content FROM snippet WHERE name=$1 AND guild=$2", name.lower(), ctx.guild.id, ) if not res: await ctx.send(embed=ErrorEmbed( description="A snippet with that name was not found.")) return embed = Embed(title="Snippet") embed.add_field(name="Name", value=res[0], inline=False) embed.add_field(name="Content", value=res[1], inline=False) await ctx.send(embed=embed) return async with self.bot.pool.acquire() as conn: res = await conn.fetch( "SELECT name, content FROM snippet WHERE guild=$1", ctx.guild.id) if not res: await ctx.send(embed=Embed( description="No snippet has been added yet.")) return all_pages = [] for chunk in [res[i:i + 10] for i in range(0, len(res), 10)]: page = Embed(title="Snippets") for snippet in chunk: page.add_field( name=snippet[0], value=snippet[1][:97] + "..." if len(snippet[1]) > 100 else snippet[1], inline=False, ) page.set_footer(text="Use the reactions to flip pages.") all_pages.append(page) if len(all_pages) == 1: embed = all_pages[0] embed.set_footer(text=discord.Embed.Empty) await ctx.send(embed=embed) return await tools.create_paginator(self.bot, ctx, all_pages)
async def accessrole(self, ctx, roles: commands.Greedy[RoleConverter] = None, *, check=None): if roles is None: roles = [] if check: await ctx.send(ErrorEmbed("The role(s) are not found. Please try again.")) return if len(roles) > 10: await ctx.send( ErrorEmbed( "There can at most be 10 roles. Try using the command again but specify fewer " "roles." ) ) return msg = await ctx.send(Embed("Updating roles...")) old_data = await tools.get_data(self.bot, ctx.guild.id) async with self.bot.pool.acquire() as conn: await conn.execute( "UPDATE data SET accessrole=$1 WHERE guild=$2", [role.id for role in roles], ctx.guild.id, ) data = await tools.get_data(self.bot, ctx.guild.id) category = await ctx.guild.get_channel(data[2]) if category and roles: try: for role in old_data[3]: role = await ctx.guild.get_role(role) if role: await category.set_permissions(target=role, overwrite=None) for role, permission in (await self._get_overwrites(ctx, data[3])).items(): await category.set_permissions(target=role, overwrite=permission) except Forbidden: await msg.edit( ErrorEmbed( "The role(s) are updated successfully. The permission overwrites for the " "category failed to be changed. Update my permissions and try again or set " "the overwrites manually." ) ) return await msg.edit(Embed("The role(s) are updated successfully."))
async def ping(self, ctx): start = time.time() shard = ctx.guild.shard_id if ctx.guild else 0 msg = await ctx.send(embed=Embed(description="Checking latency...")) await msg.edit( embed=Embed( title="Pong!", description=f"Gateway latency: {round((await self.bot.statuses())[shard].latency, 2)}ms.\n" f"HTTP API latency: {round((time.time() - start) * 1000, 2)}ms.", ) )
async def sql(self, ctx, *, query: str): async with self.bot.pool.acquire() as conn: try: res = await conn.fetch(query) except Exception: await ctx.send(embed=ErrorEmbed( description=f"```py\n{traceback.format_exc()}```")) return if res: await ctx.send(embed=Embed(description=f"```{res}```")) return await ctx.send(embed=Embed(description="No results to fetch."))
async def snippetclear(self, ctx): async with self.bot.pool.acquire() as conn: await conn.execute("DELETE FROM snippet WHERE guild=$1", ctx.guild.id) await ctx.send(embed=Embed( description="All snippets were removed successfully."))
async def _send_guilds(self, ctx, guilds, title): if len(guilds) == 0: await ctx.send(embed=ErrorEmbed( description="No such guild was found.")) return all_pages = [] for chunk in [guilds[i:i + 20] for i in range(0, len(guilds), 20)]: page = Embed(title=title) for guild in chunk: if page.description == discord.Embed.Empty: page.description = guild else: page.description += f"\n{guild}" page.set_footer(text="Use the reactions to flip pages.") all_pages.append(page) if len(all_pages) == 1: embed = all_pages[0] embed.set_footer(text=discord.Embed.Empty) await ctx.send(embed=embed) return await tools.create_paginator(self.bot, ctx, all_pages)
async def stats(self, ctx): total_seconds = int((datetime.utcnow() - await self.bot.started()).total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) days, hours = divmod(hours, 24) bot_user = await self.bot.real_user() embed = Embed( "ModMail Statistics", f"Visit the bot status page [here]({self.bot.config.BASE_URI}/status) for more " "information.", ) embed.add_field("Owner", "CHamburr#2591") embed.add_field("Bot Version", self.bot.version) if days: embed.add_field("Uptime", f"{days}d {hours}h {minutes}m {seconds}s") else: embed.add_field("Uptime", f"{hours}h {minutes}m {seconds}s") embed.add_field("Clusters", self.bot.cluster_count) if ctx.guild: embed.add_field("Shards", f"{ctx.guild.shard_id + 1}/{await self.bot.shard_count()}") else: embed.add_field("Shards", f"0/{await self.bot.shard_count()}") embed.add_field("Servers", str(await self.bot.state.scard("guild_keys"))) embed.add_field("CPU Usage", f"{psutil.cpu_percent(interval=None)}%") embed.add_field("RAM Usage", f"{psutil.virtual_memory().percent}%") embed.add_field("Python Version", platform.python_version()) embed.set_thumbnail(bot_user.avatar_url) await ctx.send(embed)
async def support(self, ctx): await ctx.send( embed=Embed( title="Support Server", description="https://discord.gg/wjWJwJB", ) )
async def banserver(self, ctx, *, guild: GuildConverter): async with self.bot.pool.acquire() as conn: await conn.execute("INSERT INTO ban VALUES ($1, $2)", guild.id, 1) await self.bot.state.sadd("banned_guilds", guild.id) await ctx.send(Embed("Successfully banned that server from the bot."))
async def sharedservers(self, ctx, *, user: UserConverter): guilds = [ f"{guild.name} `{guild.id}` ({guild.member_count} members)" for guild in [ await self.bot.get_guild(int(guild)) for guild in await tools.get_user_guilds(self.bot, user) or [] ] if guild is not None ] if len(guilds) == 0: await ctx.send(ErrorEmbed("No such guild was found.")) return all_pages = [] for chunk in [guilds[i:i + 20] for i in range(0, len(guilds), 20)]: page = Embed(title="Shared Servers") for guild in chunk: if page.description == discord.Embed.Empty: page.description = guild else: page.description += f"\n{guild}" page.set_footer("Use the reactions to flip pages.") all_pages.append(page) await tools.create_paginator(self.bot, ctx, all_pages)
async def viewpremium(self, ctx): async with self.bot.pool.acquire() as conn: res = await conn.fetchrow( "SELECT guild FROM premium WHERE identifier=$1", ctx.author.id ) if not res or not res[0]: await ctx.send(Embed("You have not assigned premium to any server.")) return guilds = [] for guild_id in res[0]: guild = await self.bot.get_guild(guild_id) guilds.append(f"{guild.name if guild else 'Unknown server'} `{guild_id}`") await ctx.send(Embed("Premium Servers", "\n".join(guilds)))
async def premiumassign(self, ctx, *, guild: GuildConverter): async with self.bot.pool.acquire() as conn: res = await conn.fetchrow( "SELECT identifier FROM premium WHERE $1=any(guild)", guild.id ) if res: await ctx.send(ErrorEmbed("That server already has premium.")) return async with self.bot.pool.acquire() as conn: res = await conn.fetchrow( "SELECT array_length(guild, 1) FROM premium WHERE identifier=$1", ctx.author.id ) if res[0] and res[0] >= await tools.get_premium_slots(self.bot, ctx.author.id): await ctx.send( ErrorEmbed( "You have reached the maximum number of slots that can be assigned. Please " "upgrade your premium to increase the slots." ) ) return async with self.bot.pool.acquire() as conn: await conn.execute( "UPDATE premium SET guild=array_append(guild, $1) WHERE identifier=$2", guild.id, ctx.author.id, ) await ctx.send(Embed("That server now has premium."))
async def pingrole(self, ctx, roles: commands.Greedy[PingRoleConverter] = None): if roles is None: roles = [] role_ids = [] for role in roles: if not isinstance(role, Role): role = role.lower() role = role.replace("@", "", 1) if role == "everyone": role_ids.append(ctx.guild.id) elif role == "here": role_ids.append(-1) else: await ctx.send(ErrorEmbed("The role(s) are not found. Please try again.")) return else: role_ids.append(role.id) if len(role_ids) > 10: await ctx.send( ErrorEmbed( "There can at most be 10 roles. Try using the command again but specify fewer " "roles." ) ) return async with self.bot.pool.acquire() as conn: await conn.execute("UPDATE data SET pingrole=$1 WHERE guild=$2", role_ids, ctx.guild.id) await ctx.send(Embed("The role(s) are updated successfully."))
async def premiumstatus(self, ctx): async with self.bot.pool.acquire() as conn: res = await conn.fetchrow( "SELECT identifier FROM premium WHERE $1=any(guild)", ctx.guild.id ) await ctx.send(Embed(f"This server has premium. Offered by: <@{res[0]}>."))
async def userinfo(self, ctx, *, member: MemberConverter = None): if member is None: member = ctx.message.member roles = [role.name for role in await member.roles()] embed = Embed(title="User Information") embed.add_field(name="Name", value=str(member)) embed.add_field(name="ID", value=member.id) embed.add_field(name="Nickname", value=member.nick if member.nick else "*Not set*") embed.add_field( name="Avatar", value=f"[Link]({member.avatar_url_as(static_format='png')})") embed.add_field(name="Joined Server", value=member.joined_at.replace( microsecond=0) if member.joined_at else "Unknown") embed.add_field(name="Account Created", value=member.created_at.replace(microsecond=0)) embed.add_field(name="Roles", value=f"{len(roles)} roles" if len(", ".join(roles)) > 1000 else ", ".join(roles)) embed.set_thumbnail(url=member.avatar_url) await ctx.send(embed=embed)
async def website(self, ctx): await ctx.send( embed=Embed( title="Website", description="https://modmail.xyz", ) )
async def stats(self, ctx): total_seconds = int((datetime.utcnow() - await self.bot.started()).total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) days, hours = divmod(hours, 24) bot_user = await self.bot.real_user() embed = Embed( title=f"{bot_user.name} Statistics", description="Visit the bot status page [here](https://status.modmail.xyz) for more information.", ) embed.add_field(name="Owner", value="CHamburr#2591") embed.add_field(name="Bot Version", value=self.bot.version) if days: embed.add_field(name="Uptime", value=f"{days}d {hours}h {minutes}m {seconds}s") else: embed.add_field(name="Uptime", value=f"{hours}h {minutes}m {seconds}s") embed.add_field(name="Clusters", value=self.bot.cluster_count) if ctx.guild: embed.add_field(name="Shards", value=f"{ctx.guild.shard_id + 1}/{await self.bot.shard_count()}") else: embed.add_field(name="Shards", value=f"0/{await self.bot.shard_count()}") embed.add_field(name="Servers", value=str(await self.bot.state.scard("guild_keys"))) embed.add_field(name="Channels", value=str(await self.bot.state.scard("channel_keys"))) embed.add_field(name="Users", value=str(await self.bot.state.scard("user_keys"))) embed.add_field(name="CPU Usage", value=f"{psutil.cpu_percent(interval=None)}%") embed.add_field(name="RAM Usage", value=f"{psutil.virtual_memory().percent}%") embed.add_field(name="Platform", value=" ".join(distro.linux_distribution()[:2])) embed.add_field(name="Python Version", value=platform.python_version()) embed.set_thumbnail(url=bot_user.avatar_url) await ctx.send(embed=embed)
async def invite(self, ctx): await ctx.send( embed=Embed( title="Invite Link", description="https://modmail.xyz/invite", ) )
async def banuser(self, ctx, *, user: UserConverter): async with self.bot.pool.acquire() as conn: await conn.execute("INSERT INTO ban VALUES ($1, $2)", user.id, 0) await self.bot.state.sadd("banned_users", user.id) await ctx.send(Embed("Successfully banned that user from the bot."))
async def serverinfo(self, ctx): guild = await self.bot.get_guild(ctx.guild.id) embed = Embed(title="Server Information") embed.add_field(name="Name", value=guild.name) embed.add_field(name="ID", value=guild.id) embed.add_field( name="Owner", value=f"<@{guild.owner_id}>" if guild.owner_id else "Unknown") embed.add_field( name="Icon", value=f"[Link]({guild.icon_url_as(static_format='png')})" if guild.icon else "*Not set*") embed.add_field(name="Server Created", value=guild.created_at.replace(microsecond=0)) embed.add_field(name="Members", value=guild.member_count) embed.add_field(name="Channels", value=str(len(await guild.channels()))) embed.add_field(name="Roles", value=str(len(await guild.roles()))) embed.add_field(name="Emojis", value=str(len(await guild.emojis()))) if guild.icon: embed.set_thumbnail(url=guild.icon_url) await ctx.send(embed=embed)
async def userinfo(self, ctx, *, member: MemberConverter = None): if member is None: member = ctx.message.member roles = [f"<@&{role}>" for role in member._roles] if len(roles) == 0: roles.append("*No roles*") embed = Embed(title="User Information") embed.add_field("Name", str(member)) embed.add_field("ID", member.id) embed.add_field("Nickname", member.nick if member.nick else "*Not set*") embed.add_field("Avatar", f"[Link]({member.avatar_url_as(static_format='png')})") embed.add_field( "Joined Server", member.joined_at.replace(microsecond=0) if member.joined_at else "Unknown", ) embed.add_field("Account Created", member.created_at.replace(microsecond=0)) embed.add_field( "Roles", f"{len(roles)} roles" if len(", ".join(roles)) > 1000 else ", ".join(roles), ) embed.set_thumbnail(member.avatar_url) await ctx.send(embed)
async def source(self, ctx): await ctx.send( embed=Embed( title="GitHub Repository", description="https://github.com/chamburr/modmail", ) )
async def bash(self, ctx, *, command: str): try: output = subprocess.check_output( command.split(), stderr=subprocess.STDOUT).decode("utf-8") await ctx.send(Embed(f"```py\n{output}\n```")) except Exception as error: await ctx.send( ErrorEmbed(f"```py\n{error.__class__.__name__}: {error}\n```"))
async def _eval(self, ctx, *, body: str): env = { "bot": self.bot, "ctx": ctx, "channel": ctx.channel, "author": ctx.author, "guild": ctx.guild, "message": ctx.message, } env.update(globals()) if body.startswith("```") and body.endswith("```"): body = "\n".join(body.split("\n")[1:-1]) body = body.strip("` \n") try: exec(f"async def func():\n{textwrap.indent(body, ' ')}", env) except Exception as e: await ctx.send(embed=ErrorEmbed( description=f"```py\n{e.__class__.__name__}: {e}\n```")) return func = env["func"] stdout = io.StringIO() try: with redirect_stdout(stdout): ret = await func() except Exception: await ctx.send(embed=ErrorEmbed( description= f"```py\n{stdout.getvalue()}{traceback.format_exc()}\n```")) return try: await ctx.message.add_reaction("✅") except discord.Forbidden: pass value = stdout.getvalue() if ret is not None: await ctx.send(embed=Embed(description=f"```py\n{value}{ret}\n```") ) elif value is not None: await ctx.send(embed=Embed(description=f"```py\n{value}\n```"))
async def setup(self, ctx): msg = await ctx.send(Embed("Setting up...")) data = await tools.get_data(self.bot, ctx.guild.id) if await ctx.guild.get_channel(data[2]): await msg.edit(ErrorEmbed("The bot has already been set up.")) return overwrites = await self._get_overwrites(ctx, data[3]) category = await ctx.guild.create_category(name="ModMail", overwrites=overwrites) logging_channel = await ctx.guild.create_text_channel(name="modmail-log", category=category) async with self.bot.pool.acquire() as conn: await conn.execute( "UPDATE data SET category=$1, logging=$2 WHERE guild=$3", category.id, logging_channel.id, ctx.guild.id, ) await msg.edit( Embed( "Premium", "Please consider purchasing premium! It is the best way you can show support to " "us. You will get access to premium features including greeting and closing " "messages, advanced logging that includes chat history, as well as the snippet " "functionality. You will also receive priority support in our server. For more " f"information, see `{ctx.prefix}premium`.", ) ) await ctx.send( Embed( "Setup", "Everything has been set up! Next up, you can give your staff access to ModMail " f"commands using `{ctx.prefix}accessrole [roles]` (by default, any user with the " f"administrator permission has full access). You can also test things out by " f"direct messaging me. Check out more information and configurations with " f"`{ctx.prefix}help`.", ) )
async def snippetremove(self, ctx, *, name: str): async with self.bot.pool.acquire() as conn: res = await conn.execute( "DELETE FROM snippet WHERE name=$1 AND guild=$2", name, ctx.guild.id) if res == "DELETE 0": await ctx.send( ErrorEmbed("A snippet with that name was not found.")) return await ctx.send(Embed("The snippet was removed successfully."))
async def closeall(self, ctx, *, reason: str = None): for channel in await ctx.guild.text_channels(): if tools.is_modmail_channel(channel): msg = copy.copy(ctx.message) msg.channel = channel new_ctx = await self.bot.get_context(msg, cls=type(ctx)) await self.close_channel(new_ctx, reason) try: await ctx.send(Embed("All channels are successfully closed.")) except discord.HTTPException: pass