Example #1
0
    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."))

        try:
            await self.bot.api_client.add_member_warning(
                ctx.author.id, member.id, reason)
        except Exception as e:
            msg = "Could not apply warning, problem with API."
            logger.info(f"{msg} {e}")
            await ctx.send(embed=failure(
                f"{msg}\nInfraction member should not think he got away."))
        else:
            await self.deterrence_log_channel.send(f"{member.mention}",
                                                   delete_after=0.5)
            await self.deterrence_log_channel.send(embed=embed)
            await ctx.send(embed=success("Warning successfully applied.",
                                         ctx.me),
                           delete_after=5)
            await asyncio.sleep(5)
            await ctx.message.delete()
Example #2
0
    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)
Example #3
0
    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:
            container.remove(user.id)
            await user.send(embed=failure("You took too long to reply."))
            return

        if user_reply.content.lower() == "cancel":
            container.remove(user.id)
            await user.send(embed=success("Successfully canceled."))
            return

        return user_reply
Example #4
0
    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.HTTPException:
                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 set_defcon_trigger(self, ctx, trigger: int):
        if not 7 <= trigger <= 100:
            return await ctx.send(
                embed=failure("Please use integer from 7 to 100."))

        self.joins_per_min_trigger = trigger
        await ctx.send(embed=success(
            f"Successfully changed DEFCON trigger to {trigger} users/min."))
Example #6
0
    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)
Example #7
0
 async def ban(self,
               ctx,
               user: GetFetchUser,
               *,
               reason="Reason not stated."):
     """Bans  member from the guild."""
     await self._ban_helper(ctx, user, reason)
     await ctx.send(embed=success(f"{user} successfully banned."),
                    delete_after=10)
Example #8
0
 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)
Example #9
0
    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)
Example #10
0
    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)
Example #11
0
    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)
Example #12
0
    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 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 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))
Example #15
0
    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)
Example #16
0
    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)
Example #17
0
    async def _mass_ban_timestamp_helper(self, ctx, timestamp_start: datetime, timestamp_end: datetime, reason: str):
        members_to_ban = []

        for member in self.tortoise_guild.members:
            if member.joined_at is None:
                continue

            if timestamp_start < member.joined_at < timestamp_end:
                members_to_ban.append(member)

        if not members_to_ban:
            return await ctx.send(embed=failure("Could not find any members, aborting.."))

        members_to_ban.sort(key=lambda m: m.joined_at)

        reaction_msg = await ctx.send(
            embed=warning(
                f"This will ban {len(members_to_ban)} members, "
                f"first one being {members_to_ban[0]} and last one being {members_to_ban[-1]}.\n"
                f"Are you sure you want to continue?"
            )
        )

        confirmation = await ConfirmationMessage.create_instance(self.bot, reaction_msg, ctx.author)
        if confirmation:

            one_tenth = len(members_to_ban) // 10
            notify_interval = one_tenth if one_tenth > 50 else 50

            await ctx.send(
                embed=info(
                    f"Starting the ban process, please be patient.\n"
                    f"You will be notified for each {notify_interval} banned members.",
                    ctx.author
                )
            )
            logger.info(f"{ctx.author} is timestamp banning: {', '.join(str(member.id) for member in members_to_ban)}")

            for count, member in enumerate(members_to_ban):
                if count != 0 and count % notify_interval == 0:
                    await ctx.send(embed=info(f"Banned {count} members..", ctx.author))

                await ctx.guild.ban(member, reason=reason)

            message = f"Successfully mass banned {len(members_to_ban)} members!"
            await ctx.send(embed=success(message))
            await self.deterrence_log_channel.send(embed=authored(message, author=ctx.author))
        else:
            await ctx.send(embed=info("Aborting mass ban.", ctx.me))
Example #18
0
    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)
Example #19
0
    async def ban_timestamp(self,
                            ctx,
                            timestamp_start: DatetimeConverter,
                            timestamp_end: DatetimeConverter,
                            *,
                            reason="Mass ban with timestamp."):
        """Bans  member from the guild if he joined at specific time.

        Both arguments need to be in this specific format:
        %Y-%m-%d %H:%M

        Example:
        t.ban_timestamp "2020-09-15 13:00" "2020-10-15 13:00"

        All values need to be padded with 0.
        Timezones are not accounted for.
        """
        members_to_ban = []

        for member in self.tortoise_guild.members:
            if member.joined_at is None:
                continue

            if timestamp_start < member.joined_at < timestamp_end:
                members_to_ban.append(member)

        if not members_to_ban:
            return await ctx.send(
                embed=failure("Could not find any members, aborting.."))

        reaction_msg = await ctx.send(embed=warning(
            f"This will ban {len(members_to_ban)} members, "
            f"first one being {members_to_ban[0]} and last one being {members_to_ban[-1]}.\n"
            f"Are you sure you want to continue?"))

        confirmation = await ConfirmationMessage.create_instance(
            self.bot, reaction_msg, ctx.author)
        if confirmation:
            logger.info(
                f"{ctx.author} is timestamp banning: {', '.join(member.id for member in members_to_ban)}"
            )

            for member in members_to_ban:
                await self._ban_helper(ctx, member, reason)
            await ctx.send(embed=success(
                f"Successfully mass banned {len(members_to_ban)} members!"))
        else:
            await ctx.send(embed=info("Aborting mass ban.", ctx.me))
Example #20
0
    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 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 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, delete_after=10)

        elif payload.channel_id == constants.suggestions_channel_id:
            if payload.emoji.id == constants.suggestions_emoji_id:
                await self.bot.get_cog("TortoiseDM").on_raw_reaction_add_helper(payload)
Example #23
0
    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.new_member_role,
                       self.successful_verifications_channel,
                       self.welcome_channel)

        for check_none in none_checks:
            if check_none is None:
                logger.warning(
                    f"One of necessary IDs was not found {none_checks}")
                raise DiscordIDNotFound()

        # Attempt to fix bug with verification where sometimes member is not found in cache even if they are in guild
        tortoise_guild = self.bot.get_guild(constants.tortoise_guild_id)
        member = 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()

        await member.add_roles(self.verified_role,
                               self.new_member_role,
                               reason="Completed Oauth2 Verification")
        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 self.general_channel.send(
            member.mention,
            embed=info(f"Say hi to our newest member {member.mention}",
                       member.guild.me,
                       title=""),
            delete_after=100)
        await member.send(embed=success(msg))
Example #24
0
    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)
Example #25
0
    async def connect_(self, ctx, *, channel: discord.VoiceChannel = None):
        """Connect to voice.
        Parameters
        ------------
        channel: discord.VoiceChannel [Optional]
            The channel to connect to. If a channel is not specified, an attempt to join the voice channel you are in
            will be made.
        This command also handles moving the bot to different channels.
        Note - The channel has to have 'music' in it's name. This is to avoid spamming music in general voice chats.
        """
        if not channel:
            try:
                channel = ctx.author.voice.channel
            except AttributeError:
                raise InvalidVoiceChannel(
                    "No channel to join. Please either specify a valid channel or join one."
                )

        if "music" not in channel.name.lower():
            raise InvalidVoiceChannel(
                "Can't join channel - channel has to have 'music' in it's name."
            )

        vc = ctx.voice_client

        if vc:
            if vc.channel.id == channel.id:
                return
            try:
                await vc.move_to(channel)
            except asyncio.TimeoutError:
                raise VoiceConnectionError(
                    f"Moving to channel: <{channel}> timed out.")
        else:
            try:
                await channel.connect()
            except asyncio.TimeoutError:
                raise VoiceConnectionError(
                    f"Connecting to channel: <{channel}> timed out.")

        await ctx.send(embed=success(f"Connected to: **{channel}**", ctx.me))
Example #26
0
 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)
Example #27
0
 async def disable_defcon(self, ctx):
     self.defcon_active = False
     await ctx.send(embed=success(
         f"Successfully deactivated DEFCON.\n"
         f"Kicked user count: {self._kicked_while_defcon_was_active}"))
     self._kicked_while_defcon_was_active = 0
Example #28
0
    async def attend(self, ctx, user_id: int):
        if not any(role in ctx.author.roles
                   for role in (self.admin_role, self.moderator_role)):
            await ctx.send(embed=failure(
                "You do not have permission to use this command."))
            return

        # Time to wait for FIRST USER reply. Useful if mod attends but user is away.
        first_timeout = 21_600  # 6 hours
        # 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 = 1800  # 30 min

        user = self.bot.get_user(user_id)
        mod = ctx.author

        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

        try:
            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."))
        except discord.HTTPException:
            await ctx.send(embed=failure(
                "Mod mail failed to initialize due to mod having closed DMs."))
            return

        # Unlike failing for mods due to closed DMs this cannot fail for user since user already did interact
        # with bot in DMs as he needs to in order to even open mod-mail.
        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."),
                                       author=mod))

        await ctx.send(embed=success("Mod mail initialized, check your DMs."))
        self.pending_mod_mails.remove(user_id)
        self.active_mod_mails[user_id] = mod.id
        _timeout = first_timeout
        # Keep a log of all messages in mod-mail
        log = MessageLogger(mod.id, user.id)

        def mod_mail_check(msg):
            return msg.guild is None and msg.author.id in (user_id, mod.id)

        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 self.mod_mail_report_channel.send(file=discord.File(
                    StringIO(str(log)), filename=log.filename))
                break

            # Deal with attachments. We don't re-upload we just copy paste attachment url.
            attachments = self._get_attachments_as_urls(mail_msg)
            mail_msg.content += attachments

            if len(mail_msg.content) > 1900:
                mail_msg.content = f"{mail_msg.content[:1900]} ...truncated because it was too long."

            # 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" and mail_msg.author.id == mod.id:
                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 self.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)