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 cleanup(self): while True: for menu in await self.redis.smembers("reaction_menus"): menu = orjson.loads(menu) if menu["end"] > int(time.time()): continue if menu["kind"] == "paginator": try: await self.http.clear_reactions( menu["channel"], menu["message"]) except discord.Forbidden: for reaction in ["⏮️", "◀️", "⏹️", "▶️", "⏭️"]: try: await self.http.remove_own_reaction( menu["channel"], menu["message"], reaction) except discord.NotFound: pass elif menu["kind"] == "confirmation": for reaction in ["✅", "🔁", "❌"]: await self.http.remove_own_reaction( menu["channel"], menu["message"], reaction) await self.http.edit_message( menu["channel"], menu["message"], embed=ErrorEmbed( description="Time out. You did not choose anything." ).to_dict(), ) elif menu["kind"] == "selection": await self.http.remove_own_reaction( menu["channel"], menu["message"], "◀") await self.http.remove_own_reaction( menu["channel"], menu["message"], "▶") for reaction in [ "1⃣", "2⃣", "3⃣", "4⃣", "5⃣", "6⃣", "7⃣", "8⃣", "9⃣", "🔟" ]: try: await self.http.remove_own_reaction( menu["channel"], menu["message"], reaction) except discord.NotFound: pass await self.http.edit_message( menu["channel"], menu["message"], embed=ErrorEmbed( description="Time out. You did not choose anything." ).to_dict(), ) await self.redis.srem("reaction_menus", orjson.dumps(menu).decode("utf-8")) await asyncio.sleep(10)
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 _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 on_message(self, message): if message.author.bot: return ctx = await self.bot.get_context(message, cls=Context) if not ctx.command: return self.bot.prom.commands.inc({"name": ctx.command.name}) if message.guild: if await tools.is_guild_banned(self.bot, message.guild): await message.guild.leave() return permissions = await message.channel.permissions_for(await ctx.guild.me()) if permissions.send_messages is False: return if permissions.embed_links is False: await message.channel.send( "The Embed Links permission is needed for basic commands to work." ) return if await tools.is_user_banned(self.bot, message.author): await ctx.send(ErrorEmbed("You are banned from the bot.")) return if ctx.prefix in [f"<@{self.bot.id}> ", f"<@!{self.bot.id}> "]: ctx.prefix = await tools.get_guild_prefix(self.bot, message.guild) await self.bot.invoke(ctx)
async def predicate(ctx): if not tools.is_modmail_channel(ctx.channel): await ctx.send(ErrorEmbed("This channel is not a ModMail channel.") ) return False return True
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 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 cleanup(self): while True: for menu_key in await self.bot.state.smembers("reaction_menu_keys"): menu = await self.bot.state.get(menu_key) if menu is None: await self.bot.state.srem("reaction_menu_keys", menu_key) continue if menu["end"] > int(time.time()): continue channel = tools.create_fake_channel(self.bot, menu_key.split(":")[1]) message = tools.create_fake_message(self.bot, channel, menu_key.split(":")[2]) emojis = [] if menu["kind"] == "paginator": try: await message.clear_reactions() except discord.Forbidden: emojis = ["⏮️", "◀️", "⏹️", "▶️", "⏭️"] except discord.HTTPException: pass elif menu["kind"] == "confirmation": emojis = ["✅", "🔁", "❌"] try: await message.edit(ErrorEmbed("Time out. You did not choose anything.")) except discord.HTTPException: emojis = [] elif menu["kind"] == "selection": emojis = ["1⃣", "2⃣", "3⃣", "4⃣", "5⃣", "6⃣", "7⃣", "8⃣", "9⃣", "🔟", "◀️", "▶️"] try: await message.edit(ErrorEmbed("Time out. You did not choose anything.")) except discord.HTTPException: emojis = [] await self.bot.state.delete(menu_key) await self.bot.state.srem("reaction_menu_keys", menu_key) for emoji in emojis: try: await message.remove_reaction(emoji, self.bot.user) except discord.HTTPException: pass await asyncio.sleep(30)
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 predicate(ctx): if (await ctx.message.member.guild_permissions()).administrator: return True for role in (await tools.get_data(ctx.bot, ctx.guild.id))[3]: if role in ctx.message.member._roles: return True await ctx.send(ErrorEmbed("You do not have access to this command.")) return False
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 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 on_message(self, message): if message.author.bot: return ctx = await self.bot.get_context(message) if not ctx.command: return self.bot.prom.commands.inc({"name": ctx.command.name}) if message.guild: if await tools.is_guild_banned(self.bot, message.guild): await message.guild.leave() return permissions = await message.channel.permissions_for(await ctx.guild.me()) if permissions.send_messages is False: return if permissions.embed_links is False: await message.channel.send( "The Embed Links permission is needed for basic commands to work." ) return if await tools.is_user_banned(self.bot, message.author): await ctx.send(embed=ErrorEmbed( description="You are banned from the bot.")) return if (ctx.command.cog_name in ["Owner", "Admin"] and ctx.author.id in self.bot.config.admins + self.bot.config.owners): embed = Embed( title=ctx.command.name.title(), description=ctx.message.content, timestamp=datetime.datetime.utcnow(), ) embed.set_author(name=f"{ctx.author} ({ctx.author.id})", icon_url=ctx.author.avatar_url) if self.bot.config.admin_channel: channel = await self.bot.get_channel( self.bot.config.admin_channel) if channel: await channel.send(embed=embed) if ctx.prefix in [f"<@{self.bot.id}> ", f"<@!{self.bot.id}> "]: ctx.prefix = await tools.get_guild_prefix(self.bot, message.guild) await self.bot.invoke(ctx)
async def snippetadd(self, ctx, name: str, *, content: str): if len(name) > 100: await ctx.send( ErrorEmbed("The snippet name cannot exceed 100 characters.")) return if len(content) > 1000: await ctx.send( ErrorEmbed( "The snippet content cannot exceed 1000 characters.")) return async with self.bot.pool.acquire() as conn: try: await conn.execute("INSERT INTO snippet VALUES ($1, $2, $3)", ctx.guild.id, name.lower(), content) except asyncpg.UniqueViolationError: await ctx.send( ErrorEmbed("A snippet with that name already exists.")) return await ctx.send(Embed("The snippet was added successfully."))
async def predicate(ctx): async with ctx.bot.pool.acquire() as conn: res = await conn.fetchrow( "SELECT category FROM data WHERE guild=$1", ctx.guild.id) if not res or not res[0]: await ctx.send( ErrorEmbed( f"Your server has not been set up yet. Use `{ctx.prefix}setup` first." )) return False return True
async def createinvite(self, ctx, *, guild: GuildConverter): try: invite = (await guild.invites())[0] except (IndexError, discord.Forbidden): try: invite = await (await guild.text_channels())[0].create_invite( max_age=120) except (IndexError, discord.Forbidden): await ctx.send( ErrorEmbed("No permissions to create an invite link.")) return await ctx.send(Embed(f"Here is the invite link: {invite.url}"))
async def unbanuser(self, ctx, *, user: UserConverter): async with self.bot.pool.acquire() as conn: res = await conn.execute( "DELETE FROM ban WHERE identifier=$1 AND category=$2", user.id, 0) if res == "DELETE 0": await ctx.send(ErrorEmbed("That user is not banned.")) return await self.bot.state.srem("banned_users", user.id) await ctx.send(Embed("Successfully unbanned that user from the bot."))
async def blacklist(self, ctx, *, member: MemberConverter): blacklist = (await tools.get_data(self.bot, ctx.guild.id))[9] if member.id in blacklist: await ctx.send(ErrorEmbed("The user is already blacklisted.")) return async with self.bot.pool.acquire() as conn: await conn.execute( "UPDATE data SET blacklist=array_append(blacklist, $1) WHERE guild=$2", member.id, ctx.guild.id, ) await ctx.send(Embed("The user is blacklisted successfully."))
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 category(self, ctx, *, name: str = "ModMail"): if len(name) > 100: await ctx.send(ErrorEmbed("The category name cannot be longer than 100 characters")) return data = await tools.get_data(self.bot, ctx.guild.id) if await ctx.guild.get_channel(data[2]): await ctx.send( ErrorEmbed( "A ModMail category already exists. Please delete that category and try again." ) ) return overwrites = await self._get_overwrites(ctx, data[3]) category = await ctx.guild.create_category(name=name, overwrites=overwrites) async with self.bot.pool.acquire() as conn: await conn.execute( "UPDATE data SET category=$1 WHERE guild=$2", category.id, ctx.guild.id ) await ctx.send(Embed("Successfully created the category."))
async def unbanserver(self, ctx, *, guild_id: int): async with self.bot.pool.acquire() as conn: res = await conn.execute( "DELETE FROM ban WHERE identifier=$1 AND category=$2", guild_id, 1) if res == "DELETE 0": await ctx.send(ErrorEmbed("That server is not banned.")) return await self.bot.state.srem("banned_guilds", guild_id) await ctx.send(Embed("Successfully unbanned that server from the bot.") )
async def givepremium(self, ctx, user: UserConverter, *, expiry: DateTimeConverter): premium = await tools.get_premium_slots(self.bot, user.id) if premium: await ctx.send(ErrorEmbed("That user already has premium.")) return async with self.bot.pool.acquire() as conn: timestamp = int( expiry.replace(tzinfo=timezone.utc).timestamp() * 1000) await conn.execute("INSERT INTO premium VALUES ($1, $2, $3)", user.id, [], timestamp) await ctx.send( Embed("Successfully assigned that user premium temporarily."))
async def asnippet(self, ctx, *, name: str): async with self.bot.pool.acquire() as conn: res = await conn.fetchrow( "SELECT content FROM snippet WHERE name=$1 AND guild=$2", name.lower(), ctx.guild.id) if not res: await ctx.send(ErrorEmbed("The snippet was not found.")) return ctx.message.content = res[0] await self.bot.cogs["ModMailEvents"].send_mail_mod(ctx.message, ctx.prefix, anon=True, snippet=True)
async def logging(self, ctx): data = await tools.get_data(self.bot, ctx.guild.id) channel = await ctx.guild.get_channel(data[4]) if channel: try: await channel.delete() except Forbidden: await ctx.send(ErrorEmbed("Missing permissions to delete the channel.")) return if data[4]: async with self.bot.pool.acquire() as conn: await conn.execute("UPDATE data SET logging=$1 WHERE guild=$2", None, ctx.guild.id) await ctx.send(Embed("ModMail logs are disabled.")) return category = await ctx.guild.get_channel(data[2]) if category is None: await ctx.send( ErrorEmbed( f"Your server does not have a ModMail category yet. Use either " f"`{ctx.prefix}setup` or `{ctx.prefix}category` to create the category first." ) ) return 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 logging=$1 WHERE guild=$2", channel.id, ctx.guild.id ) await ctx.send(Embed("The channel is created successfully."))
async def predicate(ctx): if not ctx.bot.config.MAIN_SERVER: return True async with ctx.bot.pool.acquire() as conn: res = await conn.fetchrow( "SELECT identifier FROM premium WHERE $1=any(guild)", ctx.guild.id) if not res: await ctx.send( ErrorEmbed( "This server does not have premium. Want to get premium? More information is " f"available with the `{ctx.prefix}premium` command.")) return False return True
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 predicate(ctx): async with ctx.bot.pool.acquire() as conn: res = await conn.fetchrow( "SELECT identifier FROM premium WHERE identifier=$1", ctx.author.id) if res: return True if await tools.get_premium_slots(ctx.bot, ctx.author.id) == 0: await ctx.send( ErrorEmbed( "This command requires you to be a patron. Want to become a patron? More " f"information is available with the `{ctx.prefix}premium` command." )) return False async with ctx.bot.pool.acquire() as conn: await conn.execute("INSERT INTO premium VALUES ($1, $2, NULL)", ctx.author.id, []) return True