Example #1
0
    def infraction_to_string(self, infraction: utils.Infraction) -> str:
        """Convert the infraction object to a string representation."""
        actor_id = infraction["actor"]
        guild = self.bot.get_guild(constants.Guild.id)
        actor = guild.get_member(actor_id)
        active = infraction["active"]
        user_id = infraction["user"]
        hidden = infraction["hidden"]
        created = time.format_infraction(infraction["inserted_at"])
        if infraction["expires_at"] is None:
            expires = "*Permanent*"
        else:
            expires = time.format_infraction(infraction["expires_at"])

        lines = textwrap.dedent(f"""
            {"**===============**" if active else "==============="}
            Status: {"__**Active**__" if active else "Inactive"}
            User: {self.bot.get_user(user_id)} (`{user_id}`)
            Type: **{infraction["type"]}**
            Shadow: {hidden}
            Reason: {infraction["reason"] or "*None*"}
            Created: {created}
            Expires: {expires}
            Actor: {actor.mention if actor else actor_id}
            ID: `{infraction["id"]}`
            {"**===============**" if active else "==============="}
        """)

        return lines.strip()
Example #2
0
    def _nomination_to_string(self, nomination_object: dict) -> str:
        """Creates a string representation of a nomination."""
        guild = self.bot.get_guild(Guild.id)
        entries = []
        for site_entry in nomination_object["entries"]:
            actor_id = site_entry["actor"]
            actor = guild.get_member(actor_id)

            reason = site_entry["reason"] or "*None*"
            created = time.format_infraction(site_entry["inserted_at"])
            entries.append(
                f"Actor: {actor.mention if actor else actor_id}\nCreated: {created}\nReason: {reason}"
            )

        entries_string = "\n\n".join(entries)

        active = nomination_object["active"]

        start_date = time.format_infraction(nomination_object["inserted_at"])
        if active:
            lines = textwrap.dedent(
                f"""
                ===============
                Status: **Active**
                Date: {start_date}
                Nomination ID: `{nomination_object["id"]}`

                {entries_string}
                ===============
                """
            )
        else:
            end_date = time.format_infraction(nomination_object["ended_at"])
            lines = textwrap.dedent(
                f"""
                ===============
                Status: Inactive
                Date: {start_date}
                Nomination ID: `{nomination_object["id"]}`

                {entries_string}

                End date: {end_date}
                Unwatch reason: {nomination_object["end_reason"]}
                ===============
                """
            )

        return lines.strip()
Example #3
0
    def _nomination_to_string(self, nomination_object: dict) -> str:
        """Creates a string representation of a nomination."""
        guild = self.bot.get_guild(Guild.id)

        actor_id = nomination_object["actor"]
        actor = guild.get_member(actor_id)

        active = nomination_object["active"]
        log.debug(active)
        log.debug(type(nomination_object["inserted_at"]))

        start_date = time.format_infraction(nomination_object["inserted_at"])
        if active:
            lines = textwrap.dedent(
                f"""
                ===============
                Status: **Active**
                Date: {start_date}
                Actor: {actor.mention if actor else actor_id}
                Reason: {textwrap.shorten(nomination_object["reason"], width=200, placeholder="...")}
                Nomination ID: `{nomination_object["id"]}`
                ===============
                """
            )
        else:
            end_date = time.format_infraction(nomination_object["ended_at"])
            lines = textwrap.dedent(
                f"""
                ===============
                Status: Inactive
                Date: {start_date}
                Actor: {actor.mention if actor else actor_id}
                Reason: {textwrap.shorten(nomination_object["reason"], width=200, placeholder="...")}

                End date: {end_date}
                Unwatch reason: {textwrap.shorten(nomination_object["end_reason"], width=200, placeholder="...")}
                Nomination ID: `{nomination_object["id"]}`
                ===============
                """
            )

        return lines.strip()
Example #4
0
    def infraction_to_string(self, infraction: t.Dict[str, t.Any]) -> str:
        """Convert the infraction object to a string representation."""
        active = infraction["active"]
        user = infraction["user"]
        expires_at = infraction["expires_at"]
        created = time.format_infraction(infraction["inserted_at"])

        # Format the user string.
        if user_obj := self.bot.get_user(user["id"]):
            # The user is in the cache.
            user_str = messages.format_user(user_obj)
Example #5
0
    async def on_member_join(self, member: Member) -> None:
        """
        This event will trigger when someone (re)joins the server.

        At this point we will look up the user in our database and check whether they are in
        superstar-prison. If so, we will change their name back to the forced nickname.
        """
        active_superstarifies = await self.bot.api_client.get(
            'bot/infractions',
            params={
                'active': 'true',
                'type': 'superstar',
                'user__id': member.id
            })

        if active_superstarifies:
            [infraction] = active_superstarifies
            forced_nick = self.get_nick(infraction['id'], member.id)
            await member.edit(nick=forced_nick)
            end_timestamp_human = format_infraction(infraction['expires_at'])

            try:
                await member.send(
                    "You have left and rejoined the **Python Discord** server, effectively resetting "
                    f"your nickname from **{forced_nick}** to **{member.name}**, "
                    "but as you are currently in superstar-prison, you do not have permission to do so. "
                    "Therefore your nickname was automatically changed back. You will be allowed to "
                    "change your nickname again at the following time:\n\n"
                    f"**{end_timestamp_human}**.")
            except Forbidden:
                log.warning(
                    "The user left and rejoined the server while in superstar-prison. "
                    "This led to the bot trying to DM the user to let them know their name was restored, "
                    "but the user had either blocked the bot or disabled DMs, so it was not possible "
                    "to DM them, and a discord.errors.Forbidden error was incurred."
                )

            # Log to the mod_log channel
            log.trace(
                "Logging to the #mod-log channel. This could fail because of channel permissions."
            )
            mod_log_message = (
                f"**{member.name}#{member.discriminator}** (`{member.id}`)\n\n"
                f"Superstarified member potentially tried to escape the prison.\n"
                f"Restored enforced nickname: `{forced_nick}`\n"
                f"Superstardom ends: **{end_timestamp_human}**")
            await self.modlog.send_log_message(
                icon_url=constants.Icons.user_update,
                colour=Colour.gold(),
                title="Superstar member rejoined server",
                text=mod_log_message,
                thumbnail=member.avatar_url_as(static_format="png"))
Example #6
0
    async def on_member_update(self, before: Member, after: Member) -> None:
        """Revert nickname edits if the user has an active superstarify infraction."""
        if before.display_name == after.display_name:
            return  # User didn't change their nickname. Abort!

        log.trace(
            f"{before} ({before.display_name}) is trying to change their nickname to "
            f"{after.display_name}. Checking if the user is in superstar-prison..."
        )

        active_superstarifies = await self.bot.api_client.get(
            "bot/infractions",
            params={
                "active": "true",
                "type": "superstar",
                "user__id": str(before.id)
            })

        if not active_superstarifies:
            log.trace(f"{before} has no active superstar infractions.")
            return

        infraction = active_superstarifies[0]
        forced_nick = self.get_nick(infraction["id"], before.id)
        if after.display_name == forced_nick:
            return  # Nick change was triggered by this event. Ignore.

        log.info(
            f"{after.display_name} ({after.id}) tried to escape superstar prison. "
            f"Changing the nick back to {before.display_name}.")
        await after.edit(
            nick=forced_nick,
            reason=
            f"Superstarified member tried to escape the prison: {infraction['id']}"
        )

        notified = await utils.notify_infraction(
            user=after,
            infr_type="Superstarify",
            expires_at=format_infraction(infraction["expires_at"]),
            reason=
            ("You have tried to change your nickname on the **Python Discord** server "
             f"from **{before.display_name}** to **{after.display_name}**, but as you "
             "are currently in superstar-prison, you do not have permission to do so."
             ),
            icon_url=utils.INFRACTION_ICONS["superstar"][0])

        if not notified:
            log.info(
                "Failed to DM user about why they cannot change their nickname."
            )
Example #7
0
    async def on_member_update(self, before: Member, after: Member) -> None:
        """
        This event will trigger when someone changes their name.

        At this point we will look up the user in our database and check whether they are allowed to
        change their names, or if they are in superstar-prison. If they are not allowed, we will
        change it back.
        """
        if before.display_name == after.display_name:
            return  # User didn't change their nickname. Abort!

        log.trace(
            f"{before.display_name} is trying to change their nickname to {after.display_name}. "
            "Checking if the user is in superstar-prison...")

        active_superstarifies = await self.bot.api_client.get(
            'bot/infractions',
            params={
                'active': 'true',
                'type': 'superstar',
                'user__id': str(before.id)
            })

        if active_superstarifies:
            [infraction] = active_superstarifies
            forced_nick = self.get_nick(infraction['id'], before.id)
            if after.display_name == forced_nick:
                return  # Nick change was triggered by this event. Ignore.

            log.info(f"{after.display_name} is currently in superstar-prison. "
                     f"Changing the nick back to {before.display_name}.")
            await after.edit(nick=forced_nick)
            end_timestamp_human = format_infraction(infraction['expires_at'])

            try:
                await after.send(
                    "You have tried to change your nickname on the **Python Discord** server "
                    f"from **{before.display_name}** to **{after.display_name}**, but as you "
                    "are currently in superstar-prison, you do not have permission to do so. "
                    "You will be allowed to change your nickname again at the following time:\n\n"
                    f"**{end_timestamp_human}**.")
            except Forbidden:
                log.warning(
                    "The user tried to change their nickname while in superstar-prison. "
                    "This led to the bot trying to DM the user to let them know they cannot do that, "
                    "but the user had either blocked the bot or disabled DMs, so it was not possible "
                    "to DM them, and a discord.errors.Forbidden error was incurred."
                )
Example #8
0
    async def shadow_tempmute(self,
                              ctx: Context,
                              user: Member,
                              duration: Duration,
                              *,
                              reason: str = None) -> None:
        """
        Create a temporary mute infraction for a user with the provided reason.

        Duration strings are parsed per: http://strftime.org/

        This does not send the user a notification.
        """
        expiration = duration

        if await already_has_active_infraction(ctx=ctx, user=user,
                                               type="mute"):
            return

        infraction = await post_infraction(ctx,
                                           user,
                                           type="mute",
                                           reason=reason,
                                           expires_at=expiration,
                                           hidden=True)
        if infraction is None:
            return

        self.mod_log.ignore(Event.member_update, user.id)
        await user.add_roles(self._muted_role, reason=reason)

        infraction_expiration = format_infraction(infraction["expires_at"])
        self.schedule_task(ctx.bot.loop, infraction["id"], infraction)
        await ctx.send(
            f":ok_hand: muted {user.mention} until {infraction_expiration}.")

        await self.mod_log.send_log_message(
            icon_url=Icons.user_mute,
            colour=Colour(Colours.soft_red),
            title="Member temporarily muted",
            thumbnail=user.avatar_url_as(static_format="png"),
            text=textwrap.dedent(f"""
                Member: {user.mention} (`{user.id}`)
                Actor: {ctx.message.author}
                Reason: {reason}
                Expires: {infraction_expiration}
            """),
            footer=f"ID {infraction['id']}")
Example #9
0
    async def superstarify(
        self,
        ctx: Context,
        member: Member,
        duration: Expiry,
        *,
        reason: str = None,
    ) -> None:
        """
        Temporarily force a random superstar name (like Taylor Swift) to be the user's nickname.

        A unit of time should be appended to the duration.
        Units (∗case-sensitive):
        \u2003`y` - years
        \u2003`m` - months∗
        \u2003`w` - weeks
        \u2003`d` - days
        \u2003`h` - hours
        \u2003`M` - minutes∗
        \u2003`s` - seconds

        Alternatively, an ISO 8601 timestamp can be provided for the duration.

        An optional reason can be provided. If no reason is given, the original name will be shown
        in a generated reason.
        """
        if await _utils.get_active_infraction(ctx, member, "superstar"):
            return

        # Post the infraction to the API
        old_nick = member.display_name
        reason = reason or f"old nick: {old_nick}"
        infraction = await _utils.post_infraction(ctx,
                                                  member,
                                                  "superstar",
                                                  reason,
                                                  duration,
                                                  active=True)
        id_ = infraction["id"]

        forced_nick = self.get_nick(id_, member.id)
        expiry_str = format_infraction(infraction["expires_at"])

        # Apply the infraction
        async def action() -> None:
            log.debug(f"Changing nickname of {member} to {forced_nick}.")
            self.mod_log.ignore(constants.Event.member_update, member.id)
            await member.edit(nick=forced_nick, reason=reason)

        old_nick = escape_markdown(old_nick)
        forced_nick = escape_markdown(forced_nick)

        superstar_reason = f"Your nickname didn't comply with our [nickname policy]({NICKNAME_POLICY_URL})."
        nickname_info = textwrap.dedent(f"""
            Old nickname: `{old_nick}`
            New nickname: `{forced_nick}`
        """).strip()

        successful = await self.apply_infraction(ctx,
                                                 infraction,
                                                 member,
                                                 action(),
                                                 user_reason=superstar_reason,
                                                 additional_info=nickname_info)

        # Send an embed with the infraction information to the invoking context if
        # superstar was successful.
        if successful:
            log.trace(f"Sending superstar #{id_} embed.")
            embed = Embed(
                title="Congratulations!",
                colour=constants.Colours.soft_orange,
                description=
                (f"Your previous nickname, **{old_nick}**, "
                 f"was so bad that we have decided to change it. "
                 f"Your new nickname will be **{forced_nick}**.\n\n"
                 f"You will be unable to change your nickname until **{expiry_str}**.\n\n"
                 "If you're confused by this, please read our "
                 f"[official nickname policy]({NICKNAME_POLICY_URL})."))
            await ctx.send(embed=embed)
Example #10
0
    async def superstarify(
        self,
        ctx: Context,
        member: Member,
        duration: t.Optional[Expiry],
        *,
        reason: str = '',
    ) -> None:
        """
        Temporarily force a random superstar name (like Taylor Swift) to be the user's nickname.

        A unit of time should be appended to the duration.
        Units (∗case-sensitive):
        \u2003`y` - years
        \u2003`m` - months∗
        \u2003`w` - weeks
        \u2003`d` - days
        \u2003`h` - hours
        \u2003`M` - minutes∗
        \u2003`s` - seconds

        Alternatively, an ISO 8601 timestamp can be provided for the duration.

        An optional reason can be provided, which would be added to a message stating their old nickname
        and linking to the nickname policy.
        """
        if await _utils.get_active_infraction(ctx, member, "superstar"):
            return

        # Set to default duration if none was provided.
        duration = duration or await Duration().convert(
            ctx, SUPERSTARIFY_DEFAULT_DURATION)

        # Post the infraction to the API
        old_nick = member.display_name
        infraction_reason = f'Old nickname: {old_nick}. {reason}'
        infraction = await _utils.post_infraction(ctx,
                                                  member,
                                                  "superstar",
                                                  infraction_reason,
                                                  duration,
                                                  active=True)
        id_ = infraction["id"]

        forced_nick = self.get_nick(id_, member.id)
        expiry_str = format_infraction(infraction["expires_at"])

        # Apply the infraction
        async def action() -> None:
            log.debug(f"Changing nickname of {member} to {forced_nick}.")
            self.mod_log.ignore(constants.Event.member_update, member.id)
            await member.edit(nick=forced_nick, reason=reason)

        old_nick = escape_markdown(old_nick)
        forced_nick = escape_markdown(forced_nick)

        nickname_info = textwrap.dedent(f"""
            Old nickname: `{old_nick}`
            New nickname: `{forced_nick}`
        """).strip()

        user_message = (
            f"Your previous nickname, **{old_nick}**, "
            f"was so bad that we have decided to change it. "
            f"Your new nickname will be **{forced_nick}**.\n\n"
            "{reason}"
            f"You will be unable to change your nickname until **{expiry_str}**. "
            "If you're confused by this, please read our "
            f"[official nickname policy]({NICKNAME_POLICY_URL}).").format

        successful = await self.apply_infraction(
            ctx,
            infraction,
            member,
            action(),
            user_reason=user_message(
                reason=f'**Additional details:** {reason}\n\n'
                if reason else ''),
            additional_info=nickname_info)

        # Send an embed with to the invoking context if superstar was successful.
        if successful:
            log.trace(f"Sending superstar #{id_} embed.")
            embed = Embed(title="Superstarified!",
                          colour=constants.Colours.soft_orange,
                          description=user_message(reason=''))
            await ctx.send(embed=embed)
Example #11
0
    async def shadow_tempban(self,
                             ctx: Context,
                             user: UserTypes,
                             duration: Duration,
                             *,
                             reason: str = None) -> None:
        """
        Create a temporary ban infraction for a user with the provided reason.

        Duration strings are parsed per: http://strftime.org/

        This does not send the user a notification.
        """
        expiration = duration

        if not await self.respect_role_hierarchy(ctx, user, 'shadowtempban'):
            # Ensure ctx author has a higher top role than the target user
            # Warning is sent to ctx by the helper method
            return

        if await already_has_active_infraction(ctx=ctx, user=user, type="ban"):
            return

        infraction = await post_infraction(ctx,
                                           user,
                                           type="ban",
                                           reason=reason,
                                           expires_at=expiration,
                                           hidden=True)
        if infraction is None:
            return

        self.mod_log.ignore(Event.member_ban, user.id)
        self.mod_log.ignore(Event.member_remove, user.id)

        try:
            await ctx.guild.ban(user, reason=reason, delete_message_days=0)
            action_result = True
        except Forbidden:
            action_result = False

        infraction_expiration = format_infraction(infraction["expires_at"])
        self.schedule_task(ctx.bot.loop, infraction["id"], infraction)
        await ctx.send(
            f":ok_hand: banned {user.mention} until {infraction_expiration}.")

        title = "Member temporarily banned"
        if action_result:
            log_content = None
        else:
            log_content = ctx.author.mention
            title += " (Failed)"

        # Send a log message to the mod log
        await self.mod_log.send_log_message(
            icon_url=Icons.user_ban,
            colour=Colour(Colours.soft_red),
            thumbnail=user.avatar_url_as(static_format="png"),
            title=title,
            text=textwrap.dedent(f"""
                Member: {user.mention} (`{user.id}`)
                Actor: {ctx.message.author}
                Reason: {reason}
                Expires: {infraction_expiration}
            """),
            content=log_content,
            footer=f"ID {infraction['id']}")
Example #12
0
    async def tempban(self,
                      ctx: Context,
                      user: UserTypes,
                      duration: Duration,
                      *,
                      reason: str = None) -> None:
        """
        Create a temporary ban infraction for a user with the provided expiration and reason.

        Duration strings are parsed per: http://strftime.org/
        """
        expiration = duration

        if not await self.respect_role_hierarchy(ctx, user, 'tempban'):
            # Ensure ctx author has a higher top role than the target user
            # Warning is sent to ctx by the helper method
            return

        if await already_has_active_infraction(ctx=ctx, user=user, type="ban"):
            return

        infraction = await post_infraction(ctx,
                                           user,
                                           type="ban",
                                           reason=reason,
                                           expires_at=expiration)
        if infraction is None:
            return

        notified = await self.notify_infraction(user=user,
                                                infr_type="Ban",
                                                expires_at=expiration,
                                                reason=reason)

        self.mod_log.ignore(Event.member_ban, user.id)
        self.mod_log.ignore(Event.member_remove, user.id)

        try:
            await ctx.guild.ban(user, reason=reason, delete_message_days=0)
            action_result = True
        except Forbidden:
            action_result = False

        infraction_expiration = format_infraction(infraction["expires_at"])

        self.schedule_task(ctx.bot.loop, infraction["id"], infraction)

        dm_result = ":incoming_envelope: " if notified else ""
        action = f"{dm_result}:ok_hand: banned {user.mention} until {infraction_expiration}"
        await ctx.send(f"{action}.")

        dm_status = "Sent" if notified else "**Failed**"
        log_content = None if all(
            (notified, action_result)) else ctx.author.mention
        title = "Member temporarily banned"
        if not action_result:
            title += " (Failed)"

        await self.mod_log.send_log_message(
            icon_url=Icons.user_ban,
            colour=Colour(Colours.soft_red),
            thumbnail=user.avatar_url_as(static_format="png"),
            title=title,
            text=textwrap.dedent(f"""
                Member: {user.mention} (`{user.id}`)
                Actor: {ctx.message.author}
                DM: {dm_status}
                Reason: {reason}
                Expires: {infraction_expiration}
            """),
            content=log_content,
            footer=f"ID {infraction['id']}")
Example #13
0
    async def tempmute(self,
                       ctx: Context,
                       user: Member,
                       duration: Duration,
                       *,
                       reason: str = None) -> None:
        """
        Create a temporary mute infraction for a user with the provided expiration and reason.

        Duration strings are parsed per: http://strftime.org/
        """
        expiration = duration

        if await already_has_active_infraction(ctx=ctx, user=user,
                                               type="mute"):
            return

        infraction = await post_infraction(ctx,
                                           user,
                                           type="mute",
                                           reason=reason,
                                           expires_at=expiration)
        if infraction is None:
            return

        self.mod_log.ignore(Event.member_update, user.id)
        await user.add_roles(self._muted_role, reason=reason)

        notified = await self.notify_infraction(user=user,
                                                infr_type="Mute",
                                                expires_at=expiration,
                                                reason=reason)

        infraction_expiration = format_infraction(infraction["expires_at"])

        self.schedule_task(ctx.bot.loop, infraction["id"], infraction)

        dm_result = ":incoming_envelope: " if notified else ""
        action = f"{dm_result}:ok_hand: muted {user.mention} until {infraction_expiration}"
        await ctx.send(f"{action}.")

        if notified:
            dm_status = "Sent"
            log_content = None
        else:
            dm_status = "**Failed**"
            log_content = ctx.author.mention

        await self.mod_log.send_log_message(
            icon_url=Icons.user_mute,
            colour=Colour(Colours.soft_red),
            title="Member temporarily muted",
            thumbnail=user.avatar_url_as(static_format="png"),
            text=textwrap.dedent(f"""
                Member: {user.mention} (`{user.id}`)
                Actor: {ctx.message.author}
                DM: {dm_status}
                Reason: {reason}
                Expires: {infraction_expiration}
            """),
            content=log_content,
            footer=f"ID {infraction['id']}")
Example #14
0
 def test_format_infraction(self):
     """Testing format_infraction."""
     self.assertEqual(time.format_infraction('2019-12-12T00:01:00Z'),
                      '2019-12-12 00:01')
Example #15
0
 def test_format_infraction(self):
     """Testing format_infraction."""
     self.assertEqual(time.format_infraction('2019-12-12T00:01:00Z'), '<t:1576108860:f>')
Example #16
0
    async def superstarify(
        self,
        ctx: Context,
        member: Member,
        duration: Expiry,
        *,
        reason: str = None,
    ) -> None:
        """
        Temporarily force a random superstar name (like Taylor Swift) to be the user's nickname.

        A unit of time should be appended to the duration.
        Units (∗case-sensitive):
        \u2003`y` - years
        \u2003`m` - months∗
        \u2003`w` - weeks
        \u2003`d` - days
        \u2003`h` - hours
        \u2003`M` - minutes∗
        \u2003`s` - seconds

        Alternatively, an ISO 8601 timestamp can be provided for the duration.

        An optional reason can be provided. If no reason is given, the original name will be shown
        in a generated reason.
        """
        if await utils.has_active_infraction(ctx, member, "superstar"):
            return

        # Post the infraction to the API
        reason = reason or f"old nick: {member.display_name}"
        infraction = await utils.post_infraction(ctx,
                                                 member,
                                                 "superstar",
                                                 reason,
                                                 duration,
                                                 active=True)
        id_ = infraction["id"]

        old_nick = member.display_name
        forced_nick = self.get_nick(id_, member.id)
        expiry_str = format_infraction(infraction["expires_at"])

        # Apply the infraction and schedule the expiration task.
        log.debug(f"Changing nickname of {member} to {forced_nick}.")
        self.mod_log.ignore(constants.Event.member_update, member.id)
        await member.edit(nick=forced_nick, reason=reason)
        self.schedule_task(id_, infraction)

        # Send a DM to the user to notify them of their new infraction.
        await utils.notify_infraction(
            user=member,
            infr_type="Superstarify",
            expires_at=expiry_str,
            icon_url=utils.INFRACTION_ICONS["superstar"][0],
            reason=
            f"Your nickname didn't comply with our [nickname policy]({NICKNAME_POLICY_URL})."
        )

        # Send an embed with the infraction information to the invoking context.
        log.trace(f"Sending superstar #{id_} embed.")
        embed = Embed(
            title="Congratulations!",
            colour=constants.Colours.soft_orange,
            description=
            (f"Your previous nickname, **{old_nick}**, "
             f"was so bad that we have decided to change it. "
             f"Your new nickname will be **{forced_nick}**.\n\n"
             f"You will be unable to change your nickname until **{expiry_str}**.\n\n"
             "If you're confused by this, please read our "
             f"[official nickname policy]({NICKNAME_POLICY_URL})."))
        await ctx.send(embed=embed)

        # Log to the mod log channel.
        log.trace(f"Sending apply mod log for superstar #{id_}.")
        await self.mod_log.send_log_message(
            icon_url=utils.INFRACTION_ICONS["superstar"][0],
            colour=Colour.gold(),
            title="Member achieved superstardom",
            thumbnail=member.avatar_url_as(static_format="png"),
            text=textwrap.dedent(f"""
                Member: {member.mention} (`{member.id}`)
                Actor: {ctx.message.author}
                Reason: {reason}
                Expires: {expiry_str}
                Old nickname: `{old_nick}`
                New nickname: `{forced_nick}`
            """),
            footer=f"ID {id_}")
Example #17
0
    async def apply_infraction(
            self,
            ctx: Context,
            infraction: utils.Infraction,
            user: MemberObject,
            action_coro: t.Optional[t.Awaitable] = None) -> None:
        """Apply an infraction to the user, log the infraction, and optionally notify the user."""
        infr_type = infraction["type"]
        icon = utils.INFRACTION_ICONS[infr_type][0]
        reason = infraction["reason"]
        expiry = infraction["expires_at"]

        if expiry:
            expiry = time.format_infraction(expiry)

        # Default values for the confirmation message and mod log.
        confirm_msg = f":ok_hand: applied"

        # Specifying an expiry for a note or warning makes no sense.
        if infr_type in ("note", "warning"):
            expiry_msg = ""
        else:
            expiry_msg = f" until {expiry}" if expiry else " permanently"

        dm_result = ""
        dm_log_text = ""
        expiry_log_text = f"Expires: {expiry}" if expiry else ""
        log_title = "applied"
        log_content = None

        # DM the user about the infraction if it's not a shadow/hidden infraction.
        if not infraction["hidden"]:
            # Sometimes user is a discord.Object; make it a proper user.
            await self.bot.fetch_user(user.id)

            # Accordingly display whether the user was successfully notified via DM.
            if await utils.notify_infraction(user, infr_type, expiry, reason,
                                             icon):
                dm_result = ":incoming_envelope: "
                dm_log_text = "\nDM: Sent"
            else:
                dm_log_text = "\nDM: **Failed**"
                log_content = ctx.author.mention

        if infraction["actor"] == self.bot.user.id:
            end_msg = f" (reason: {infraction['reason']})"
        elif ctx.channel.id not in STAFF_CHANNELS:
            end_msg = ''
        else:
            infractions = await self.bot.api_client.get(
                "bot/infractions", params={"user__id": str(user.id)})
            total = len(infractions)
            end_msg = f" ({total} infraction{ngettext('', 's', total)} total)"

        # Execute the necessary actions to apply the infraction on Discord.
        if action_coro:
            try:
                await action_coro
                if expiry:
                    # Schedule the expiration of the infraction.
                    self.schedule_task(ctx.bot.loop, infraction["id"],
                                       infraction)
            except discord.Forbidden:
                # Accordingly display that applying the infraction failed.
                confirm_msg = f":x: failed to apply"
                expiry_msg = ""
                log_content = ctx.author.mention
                log_title = "failed to apply"

        # Send a confirmation message to the invoking context.
        await ctx.send(
            f"{dm_result}{confirm_msg} **{infr_type}** to {user.mention}{expiry_msg}{end_msg}."
        )

        # Send a log message to the mod log.
        await self.mod_log.send_log_message(
            icon_url=icon,
            colour=Colours.soft_red,
            title=f"Infraction {log_title}: {infr_type}",
            thumbnail=user.avatar_url_as(static_format="png"),
            text=textwrap.dedent(f"""
                Member: {user.mention} (`{user.id}`)
                Actor: {ctx.message.author}{dm_log_text}
                Reason: {reason}
                {expiry_log_text}
            """),
            content=log_content,
            footer=f"ID {infraction['id']}")
Example #18
0
    async def superstarify(self,
                           ctx: Context,
                           member: Member,
                           expiration: Duration,
                           reason: str = None) -> None:
        """
        Force a random superstar name (like Taylor Swift) to be the user's nickname for a specified duration.

        An optional reason can be provided.

        If no reason is given, the original name will be shown in a generated reason.
        """
        if await utils.has_active_infraction(ctx, member, "superstar"):
            return

        reason = reason or ('old nick: ' + member.display_name)
        infraction = await utils.post_infraction(ctx,
                                                 member,
                                                 'superstar',
                                                 reason,
                                                 expires_at=expiration)
        forced_nick = self.get_nick(infraction['id'], member.id)
        expiry_str = format_infraction(infraction["expires_at"])

        embed = Embed()
        embed.title = "Congratulations!"
        embed.description = (
            f"Your previous nickname, **{member.display_name}**, was so bad that we have decided to change it. "
            f"Your new nickname will be **{forced_nick}**.\n\n"
            f"You will be unable to change your nickname until \n**{expiry_str}**.\n\n"
            "If you're confused by this, please read our "
            f"[official nickname policy]({NICKNAME_POLICY_URL}).")

        # Log to the mod_log channel
        log.trace(
            "Logging to the #mod-log channel. This could fail because of channel permissions."
        )
        mod_log_message = (
            f"**{member.name}#{member.discriminator}** (`{member.id}`)\n\n"
            f"Superstarified by **{ctx.author.name}**\n"
            f"Old nickname: `{member.display_name}`\n"
            f"New nickname: `{forced_nick}`\n"
            f"Superstardom ends: **{expiry_str}**")
        await self.modlog.send_log_message(
            icon_url=constants.Icons.user_update,
            colour=Colour.gold(),
            title="Member Achieved Superstardom",
            text=mod_log_message,
            thumbnail=member.avatar_url_as(static_format="png"))

        await utils.notify_infraction(
            user=member,
            infr_type="Superstarify",
            expires_at=expiry_str,
            reason=
            f"Your nickname didn't comply with our [nickname policy]({NICKNAME_POLICY_URL})."
        )

        # Change the nick and return the embed
        log.trace("Changing the users nickname and sending the embed.")
        await member.edit(nick=forced_nick)
        await ctx.send(embed=embed)