Repository: python-discord/bot

Issue:
Users can break out of superstar prison by leaving the server!
A user who really wants to change their nickname while in superstar prison can simply leave the server, change it, and come back. We should solve this by checking new arrivals for active superstar prison sentences and enforcing them at the door.


Discussion:
Ahhhh, the oldest trick in the book. oops...
I would like to look into this issue, if you haven't already fixed it in the meantime.

Resolving patch:
diff --git a/bot/cogs/moderation.py b/bot/cogs/moderation.py
--- a/bot/cogs/moderation.py
+++ b/bot/cogs/moderation.py
@@ -29,6 +29,7 @@
     "Kick": Icons.sign_out,
     "Ban": Icons.user_ban
 }
+RULES_URL = "https://pythondiscord.com/about/rules"
 
 
 def proxy_user(user_id: str) -> Object:
@@ -1088,8 +1089,9 @@ async def notify_infraction(
         )
 
         icon_url = INFRACTION_ICONS.get(infr_type, Icons.token_removed)
-        embed.set_author(name="Infraction Information", icon_url=icon_url)
-        embed.set_footer(text=f"Please review our rules over at https://pythondiscord.com/about/rules")
+        embed.set_author(name="Infraction Information", icon_url=icon_url, url=RULES_URL)
+        embed.title = f"Please review our rules over at {RULES_URL}"
+        embed.url = RULES_URL
 
         await self.send_private_embed(user, embed)
 
diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py
--- a/bot/cogs/modlog.py
+++ b/bot/cogs/modlog.py
@@ -10,12 +10,16 @@
     CategoryChannel, Colour, Embed, File, Guild,
     Member, Message, NotFound, RawBulkMessageDeleteEvent,
     RawMessageDeleteEvent, RawMessageUpdateEvent, Role,
-    TextChannel, User, VoiceChannel)
+    TextChannel, User, VoiceChannel
+)
 from discord.abc import GuildChannel
 from discord.ext.commands import Bot
 
-from bot.constants import Channels, Colours, Emojis, Event, Icons, Keys, Roles, URLs
-from bot.constants import Guild as GuildConstant
+from bot.constants import (
+    Channels, Colours, Emojis,
+    Event, Guild as GuildConstant, Icons,
+    Keys, Roles, URLs
+)
 from bot.utils.time import humanize_delta
 
 log = logging.getLogger(__name__)
@@ -609,7 +613,12 @@ async def on_raw_message_delete(self, event: RawMessageDeleteEvent):
         )
 
     async def on_message_edit(self, before: Message, after: Message):
-        if before.guild.id != GuildConstant.id or before.channel.id in GuildConstant.ignored or before.author.bot:
+        if (
+            not before.guild
+            or before.guild.id != GuildConstant.id
+            or before.channel.id in GuildConstant.ignored
+            or before.author.bot
+        ):
             return
 
         self._cached_edits.append(before.id)
@@ -670,7 +679,12 @@ async def on_raw_message_edit(self, event: RawMessageUpdateEvent):
         except NotFound:  # Was deleted before we got the event
             return
 
-        if message.guild.id != GuildConstant.id or message.channel.id in GuildConstant.ignored or message.author.bot:
+        if (
+            not message.guild
+            or message.guild.id != GuildConstant.id
+            or message.channel.id in GuildConstant.ignored
+            or message.author.bot
+        ):
             return
 
         await asyncio.sleep(1)  # Wait here in case the normal event was fired
diff --git a/bot/cogs/superstarify.py b/bot/cogs/superstarify.py
--- a/bot/cogs/superstarify.py
+++ b/bot/cogs/superstarify.py
@@ -35,13 +35,12 @@ def moderation(self) -> Moderation:
     def modlog(self) -> ModLog:
         return self.bot.get_cog("ModLog")
 
-    async def on_member_update(self, before, after):
+    async def on_member_update(self, before: Member, after: Member):
         """
         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.
-        :return:
         """
 
         if before.display_name == after.display_name:
@@ -85,6 +84,64 @@ async def on_member_update(self, before, after):
                     "to DM them, and a discord.errors.Forbidden error was incurred."
                 )
 
+    async def on_member_join(self, member: Member):
+        """
+        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.
+        """
+
+        response = await self.bot.http_session.get(
+            URLs.site_superstarify_api,
+            headers=self.headers,
+            params={"user_id": str(member.id)}
+        )
+
+        response = await response.json()
+
+        if response and response.get("end_timestamp") and not response.get("error_code"):
+            forced_nick = response.get("forced_nick")
+            end_timestamp = response.get("end_timestamp")
+            log.debug(
+                f"{member.name} rejoined but is currently in superstar-prison. "
+                f"Changing the nick back to {forced_nick}."
+            )
+
+            await member.edit(nick=forced_nick)
+            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}**."
+                )
+            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}**"
+            )
+            await self.modlog.send_log_message(
+                icon_url=Icons.user_update,
+                colour=Colour.gold(),
+                title="Superstar member rejoined server",
+                text=mod_log_message,
+                thumbnail=member.avatar_url_as(static_format="png")
+            )
+
     @command(name='superstarify', aliases=('force_nick', 'star'))
     @with_role(Roles.admin, Roles.owner, Roles.moderator)
     async def superstarify(self, ctx: Context, member: Member, duration: str, *, forced_nick: str = None):
@@ -136,10 +193,11 @@ async def superstarify(self, ctx: Context, member: Member, duration: str, *, for
             forced_nick = response.get('forced_nick')
             end_time = response.get("end_timestamp")
             image_url = response.get("image_url")
+            old_nick = member.display_name
 
             embed.title = "Congratulations!"
             embed.description = (
-                f"Your previous nickname, **{member.display_name}**, was so bad that we have decided to change it. "
+                f"Your previous nickname, **{old_nick}**, 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**{end_time}**.\n\n"
                 "If you're confused by this, please read our "
@@ -150,9 +208,10 @@ async def superstarify(self, ctx: Context, member: Member, duration: str, *, for
             # 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"**{member.name}#{member.discriminator}** (`{member.id}`)\n\n"
                 f"Superstarified by **{ctx.author.name}**\n"
-                f"New nickname:`{forced_nick}`\n"
+                f"Old nickname: `{old_nick}`\n"
+                f"New nickname: `{forced_nick}`\n"
                 f"Superstardom ends: **{end_time}**"
             )
             await self.modlog.send_log_message(
@@ -175,7 +234,7 @@ async def superstarify(self, ctx: Context, member: Member, duration: str, *, for
             await member.edit(nick=forced_nick)
             await ctx.send(embed=embed)
 
-    @command(name='unsuperstarify', aliases=('release_nick', 'uss'))
+    @command(name='unsuperstarify', aliases=('release_nick', 'unstar'))
     @with_role(Roles.admin, Roles.owner, Roles.moderator)
     async def unsuperstarify(self, ctx: Context, member: Member):
         """


Test patch: