async def promote(self, ctx, member: discord.Member, role: discord.Role): """ Promote member to role. """ if role >= ctx.author.top_role: await ctx.send( embed=failure("Role needs to be below you in hierarchy.")) return elif role in member.roles: await ctx.send(embed=failure( f"{member.mention} already has role {role.mention}!")) return await member.add_roles(role) await ctx.send(embed=success( f"{member.mention} is promoted to {role.mention}", ctx.me), delete_after=5) dm_embed = info(( f"You are now promoted to role **{role.name}** in our community.\n" f"`'With great power comes great responsibility'`\n" f"Be active and keep the community safe."), ctx.me, "Congratulations!") dm_embed.set_footer(text="Tortoise community") await member.send(embed=dm_embed)
async def ban(self, ctx, member: discord.Member, *, reason="Reason not stated."): """ Bans member from the guild. """ await member.ban(reason=reason) await ctx.send(embed=success(f"{member.name} successfully banned."), delete_after=5) deterrence_embed = infraction_embed(ctx, member, constants.Infraction.ban, reason) deterrence_log_channel = self.bot.get_channel( constants.deterrence_log_channel_id) await deterrence_log_channel.send(embed=deterrence_embed) dm_embed = deterrence_embed dm_embed.add_field( name="Repeal", value="If this happened by a mistake contact moderators.") await member.send(embed=dm_embed)
async def _wait_for(self, container: set, user: discord.User) -> Union[discord.Message, None]: """ Simple custom wait_for that waits for user reply for 5 minutes and has ability to cancel the wait, deal with errors and deal with containers (which mark users that are currently doing something aka event submission/bug report etc). :param container: set, container holding active user sessions by having their IDs in it. :param user: Discord user to wait reply from :return: Union[Message, None] message representing user reply, can be none representing invalid reply. """ def check(msg): return msg.guild is None and msg.author == user container.add(user.id) await user.send(embed=info( "Reply with single message, link to paste service or uploading utf-8 `.txt` file.\n" "You have 5m, type `cancel` to cancel right away.", user)) try: user_reply = await self.bot.wait_for("message", check=check, timeout=300) except TimeoutError: await user.send(embed=failure("You took too long to reply.")) container.remove(user.id) return if user_reply.content.lower() == "cancel": await user.send(embed=success("Successfully canceled.")) container.remove(user.id) return return user_reply
async def dm_members(self, ctx, role: discord.Role, *, message: str): """ DMs all member that have a certain role. Failed members are printed to log. """ members = (member for member in role.members if not member.bot) failed = [] count = 0 for member in members: dm_embed = discord.Embed(title=f"Message for role {role}", description=message, color=role.color) dm_embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon_url) try: await member.send(embed=dm_embed) except discord.Forbidden: failed.append(str(member)) else: count += 1 await ctx.send( embed=success(f"Successfully notified {count} users.", ctx.me)) if failed: logger.info(f"dm_unverified called but failed to dm: {failed}")
async def warn(self, ctx, member: discord.Member, *, reason): """ Warns a member. Reason length is maximum of 200 characters. """ if len(reason) > 200: await ctx.send( embed=failure("Please shorten the reason to 200 characters."), delete_after=3) return embed = infraction_embed(ctx, member, constants.Infraction.warning, reason) embed.add_field( name="**NOTE**", value=("If you are planning to repeat this again, " "the mods may administer punishment for the action.")) await self.deterrence_log_channel.send(f"{member.mention}", delete_after=0.5) await self.deterrence_log_channel.send(embed=embed) await self.bot.api_client.add_member_warning(ctx.author.id, member.id, reason) await ctx.send(embed=success("Warning successfully applied.", ctx.me), delete_after=5) await asyncio.sleep(5) await ctx.message.delete()
async def dm_unverified(self, ctx): """ Dms all unverified members reminder that they need to verify. Failed members are printed to log. """ unverified_role = ctx.guild.get_role(constants.unverified_role_id) unverified_members = (member for member in unverified_role.members if member.status == discord.Status.online) failed = [] count = 0 for member in unverified_members: msg = ( f"Hey {member.mention}!\n" f"You've been in our guild **{ctx.guild.name}** for some time..\n" f"We noticed you still didn't verify so please go to our channel " f"{constants.verification_url} and verify.") try: await member.send(msg) except discord.Forbidden: failed.append(str(member)) else: count += 1 await ctx.send( embed=success(f"Successfully notified {count} users.", ctx.me)) if failed: logger.info(f"dm_unverified called but failed to dm: {failed}")
async def create_bug_report(self, user: discord.User): user_reply = await self._get_user_reply(self.active_bug_reports, user) if user_reply is None: return await self.bug_report_channel.send(f"User `{user}` ID:{user.id} submitted bug report: {user_reply}") await user.send(embed=success("Bug report successfully submitted, thank you.")) self.active_bug_reports.remove(user.id)
async def manually_add_database_member(self, ctx, member: Member): if await self.bot.api_client.does_member_exist(member.id): await ctx.send(embed=warning("Member already exists, aborting..")) return logger.info(f"{ctx.author} is manually adding member {member} {member.id} to database") await self.bot.api_client.insert_new_member(member) await ctx.send(embed=success(f"Member {member} successfully added to database."))
async def create_suggestion(self, user: discord.User): user_reply = await self._get_user_reply(self.active_suggestions, user) if user_reply is None: return msg = await create_suggestion_msg(self.user_suggestions_channel, user, user_reply) await self.bot.api_client.post_suggestion(user, msg, user_reply) await user.send(embed=success("Suggestion successfully submitted, thank you.")) self.active_suggestions.remove(user.id)
async def unban(self, ctx, user: GetFetchUser, *, reason="Reason not stated."): """Unbans member from the guild.""" await ctx.guild.unban(user=user, reason=reason) await ctx.send(embed=success(f"{user} successfully unbanned."), delete_after=5)
async def clear(self, ctx, amount: int, member: discord.Member = None): """ Clears last X amount of messages. If member is passed it will clear last X messages from that member. """ def check(msg): return member is None or msg.author == member await ctx.channel.purge(limit=amount + 1, check=check) await ctx.send(embed=success(f"{amount} messages cleared."), delete_after=3)
async def create_suggestion(self, user: discord.User): user_reply = await self._get_user_reply(self.active_suggestions, user) if user_reply is None: return await self.user_suggestions_channel.send( f"User `{user}` ID:{user.id} submitted suggestion: {user_reply}") await user.send( embed=success("Suggestion successfully submitted, thank you.")) self.active_suggestions.remove(user.id)
async def deny(self, ctx, message_id: int, *, reason: str = "No reason specified"): """Deny a suggestion""" await self._suggestion_helper(ctx, message_id, reason, constants.SuggestionStatus.denied) await ctx.send(embed=success("Suggestion successfully denied."), delete_after=5)
async def delete_suggestion(self, ctx, message_id: int): """Delete a suggestion""" msg: Message = await self.user_suggestions_channel.fetch_message( message_id) if msg is not None: await msg.delete() await self.bot.api_client.delete_suggestion(message_id) await ctx.send(embed=success("Suggestion successfully deleted."), delete_after=5)
async def load(self, ctx, extension_name): """ Loads an extension. :param extension_name: cog name without suffix """ self.bot.load_extension(f"bot.cogs.{extension_name}") msg = f"{extension_name} loaded." logger.info(f"{msg} by {ctx.author.id}") await ctx.send(embed=success(msg, ctx.me))
async def mute(self, ctx, member: discord.Member, *, reason="No reason stated."): """Mutes the member.""" if self.muted_role in member.roles: await ctx.send(embed=failure("Cannot mute as member is already muted.")) return reason = f"Muting member. {reason}" await member.add_roles(self.muted_role, reason=reason) await member.remove_roles(self.verified_role, reason=reason) await ctx.send(embed=success(f"{member} successfully muted."), delete_after=5) await self.bot.api_client.add_member_warning(ctx.author.id, member.id, reason)
async def create_event_submission(self, user: discord.User): user_reply = await self._get_user_reply(self.active_event_submissions, user) if user_reply is None: return await self.code_submissions_channel.send( f"User `{user}` ID:{user.id} submitted code submission: " f"{user_reply}" ) await user.send(embed=success("Event submission successfully submitted.")) self.active_event_submissions.remove(user.id)
async def unmute(self, ctx, member: discord.Member): """Unmutes the member.""" if self.muted_role not in member.roles: await ctx.send(embed=failure("Cannot unmute as member is not muted.")) return reason = f"Unmuted by {ctx.author.id}" await member.remove_roles(self.muted_role, reason=reason) await member.add_roles(self.verified_role, reason=reason) await ctx.send(embed=success(f"{member} successfully unmuted."), delete_after=5)
async def reload(self, ctx, extension_name): """ Reloads an extension. :param extension_name: cog name without suffix """ if extension_name == Path(__file__).stem: await ctx.send(embed=failure( "This cog is protected, cannot execute operation.")) return self.bot.reload_extension(f"bot.cogs.{extension_name}") await ctx.send(embed=success(f"{extension_name} reloaded.", ctx.me))
async def create_mod_mail(self, user: discord.User): if user.id in self.pending_mod_mails: await user.send(embed=failure("You already have a pending mod mail, please be patient.")) return submission_embed = authored(f"`{user.id}` submitted for mod mail.", author=user) # Ping roles so they get notified sooner await self.mod_mail_report_channel.send("@here", delete_after=30) await self.mod_mail_report_channel.send(embed=submission_embed) self.pending_mod_mails.add(user.id) await user.send(embed=success("Mod mail was sent to admins, please wait for one of the admins to accept."))
async def on_raw_reaction_add(self, payload): if payload.channel_id == constants.react_for_roles_channel_id: guild = self.bot.get_guild(payload.guild_id) member = guild.get_member(payload.user_id) role = self.get_assignable_role(payload, guild) if member.id == self.bot.user.id: return # Ignore the bot elif role is not None: await member.add_roles(role) embed = success(f"`{role.name}` has been assigned to you in the Tortoise community.") await member.send(embed=embed)
async def ban(self, ctx, user: GetFetchUser, *, reason="Reason not stated."): """Bans member from the guild.""" await ctx.guild.ban(user=user, reason=reason) await ctx.send(embed=success(f"{user} successfully banned."), delete_after=5) deterrence_embed = infraction_embed(ctx, user, constants.Infraction.ban, reason) await self.deterrence_log_channel.send(embed=deterrence_embed) dm_embed = deterrence_embed dm_embed.add_field( name="Repeal", value="If this happened by a mistake contact moderators." ) await user.send(embed=dm_embed)
async def kick(self, ctx, member: discord.Member, *, reason="No specific reason"): """Kicks member from the guild.""" await member.kick(reason=reason) await ctx.send(embed=success(f"{member.name} successfully kicked."), delete_after=5) deterrence_embed = infraction_embed(ctx, member, constants.Infraction.kick, reason) await self.deterrence_log_channel.send(embed=deterrence_embed) dm_embed = deterrence_embed dm_embed.add_field( name="Repeal", value="If this happened by a mistake contact moderators." ) await member.send(embed=dm_embed)
async def create_mod_mail(self, user: discord.User): if user.id in self.pending_mod_mails: await user.send(embed=failure( "You already have a pending mod mail, please be patient.")) return mod_mail_report_channel = self.bot.get_channel( constants.mod_mail_report_channel_id) submission_embed = authored(f"`{user.id}` submitted for mod mail.", author=user) await mod_mail_report_channel.send(embed=submission_embed) self.pending_mod_mails.add(user.id) await user.send(embed=success( "Mod mail was sent to admins, please wait for one of the admins to accept." ))
async def unload(self, ctx, extension_name): """ Unloads an extension. :param extension_name: cog name without suffix """ if extension_name == Path(__file__).stem: await ctx.send(embed=failure("This cog is protected, cannot unload.")) return self.bot.unload_extension(f"bot.cogs.{extension_name}") msg = f"{extension_name} unloaded." logger.info(f"{msg} by {ctx.author.id}") await ctx.send(embed=success(f"{extension_name} unloaded.", ctx.me))
async def verify_member(self, member_id: str): """ Adds verified role to the member and also sends success messages. :param member_id: str member id to verify """ try: member_id = int(member_id) except ValueError: raise EndpointBadArguments() none_checks = (self.tortoise_guild, self.verified_role, self.unverified_role, self.successful_verifications_channel, self.welcome_channel) for check_none in none_checks: if check_none is None: logger.info( f"One of necessary IDs was not found {none_checks}") raise DiscordIDNotFound() member = self.tortoise_guild.get_member(member_id) if member is None: logger.critical( f"Can't verify, member is not found in guild {member} {member_id}" ) raise DiscordIDNotFound() try: await member.remove_roles(self.unverified_role) except HTTPException: logger.warning( f"Bot could't remove unverified role {self.unverified_role}") await member.add_roles(self.verified_role) await self.successful_verifications_channel.send( embed=info(f"{member} is now verified.", member.guild.me, title="") ) msg = (f"You are now verified {self.verified_emoji}\n\n" f"Make sure to read {self.welcome_channel.mention}") await member.send(embed=success(msg))
async def mute(self, ctx, member: discord.Member, *, reason="No reason stated."): """ Mutes the member. """ if self.muted_role in member.roles: await ctx.send( embed=failure("Cannot mute as member is already muted.")) return reason = "Muting member. " + reason await member.add_roles(self.muted_role, reason=reason) await member.remove_roles(self.verified_role, reason=reason) await ctx.send(embed=success(f"{member} successfully muted."), delete_after=5)
async def announce(self, ctx, *, arg): announcements_channel = self.bot.get_channel( constants.announcements_channel_id) await announcements_channel.send(arg) await ctx.send(success("Announced ✅"))
async def attend(self, ctx, user_id: int): # Time to wait for FIRST USER reply. Useful if mod attends but user is away. first_timeout = 10_800 # Flag for above variable. False means there has been no messages from the user. first_timeout_flag = False # After the user sends first reply this is the timeout we use. regular_timeout = 600 user = self.bot.get_user(user_id) mod = ctx.author # Keep a log of all messages in mod-mail log = MessageLogger(mod.id, user.id) mod_mail_report_channel = self.bot.get_channel( constants.mod_mail_report_channel_id) if user is None: await ctx.send(embed=failure( "That user cannot be found or you entered incorrect ID.")) return elif user_id not in self.pending_mod_mails: await ctx.send( embed=failure("That user is not registered for mod mail.")) return elif self.is_any_session_active(mod.id): await ctx.send(embed=failure( "You already have one of active sessions (reports/mod mail etc)." )) return self.pending_mod_mails.remove(user_id) self.active_mod_mails[user_id] = mod.id await user.send(embed=authored(( "has accepted your mod mail request.\n" "Reply here in DMs to chat with them.\n" "This mod mail will be logged, by continuing you agree to that.\n" "Type `close` to close this mod mail."), author=mod)) await mod.send( embed=success(f"You have accepted `{user}` mod mail request.\n" "Reply here in DMs to chat with them.\n" "This mod mail will be logged.\n" "Type `close` to close this mod mail.")) await ctx.send(embed=success("Mod mail initialized, check your DMs."), delete_after=10) def mod_mail_check(msg): return msg.guild is None and msg.author.id in (user_id, mod.id) _timeout = first_timeout while True: try: mail_msg = await self.bot.wait_for("message", check=mod_mail_check, timeout=_timeout) log.add_message(mail_msg) except TimeoutError: timeout_embed = failure("Mod mail closed due to inactivity.") log.add_embed(timeout_embed) await mod.send(embed=timeout_embed) await user.send(embed=timeout_embed) del self.active_mod_mails[user_id] await mod_mail_report_channel.send(file=discord.File( StringIO(str(log)), filename=log.filename)) break # Deal with dynamic timeout. if mail_msg.author == user and not first_timeout_flag: first_timeout_flag = True _timeout = regular_timeout # Deal with canceling mod mail if mail_msg.content.lower() == "close": close_embed = success( f"Mod mail successfully closed by {mail_msg.author}.") log.add_embed(close_embed) await mod.send(embed=close_embed) await user.send(embed=close_embed) del self.active_mod_mails[user_id] await mod_mail_report_channel.send(file=discord.File( StringIO(str(log)), filename=log.filename)) break # Deal with user-mod communication if mail_msg.author == user: await mod.send(mail_msg.content) elif mail_msg.author == mod: await user.send(mail_msg.content)
async def welcome(self, ctx, *, arg): channel = self.bot.get_channel(constants.welcome_channel_id) await channel.send(arg) await ctx.send(success("Added in Welcome ✅"))