Repository: python-discord/bot

Issue:
Defcon have wrong text in #mod-log when you disable it.
When you disable defcon the text message in #mod-log is `DEFCON enabled` it should say `DEFCON disabled`

It works like this in the #defcon channel.

Current message in #mod-log
![defcon-disabled](https://cdn.discordapp.com/attachments/464469101889454091/633937482798333952/unknown.png)

but a few months ago you can see this behaviour.
Message from 08.08.2019 in #mod-log
![defcon-working](https://media.discordapp.net/attachments/464469101889454091/633938493835575307/unknown.png)

code from [defcon.py](https://github.com/python-discord/bot/blob/master/bot/cogs/defcon.py)


```python
        elif change.lower() == "disabled":
            icon = Icons.defcon_disabled
            color = Colours.soft_red
            status_msg = "DEFCON enabled"
```

Someone looking into this might want to focus on the last line of this snippet.


Discussion:

Resolving patch:
diff --git a/bot/cogs/defcon.py b/bot/cogs/defcon.py
--- a/bot/cogs/defcon.py
+++ b/bot/cogs/defcon.py
@@ -1,5 +1,9 @@
+from __future__ import annotations
+
 import logging
+from collections import namedtuple
 from datetime import datetime, timedelta
+from enum import Enum
 
 from discord import Colour, Embed, Member
 from discord.ext.commands import Bot, Cog, Context, group
@@ -24,6 +28,16 @@
 BASE_CHANNEL_TOPIC = "Python Discord Defense Mechanism"
 
 
+class Action(Enum):
+    """Defcon Action."""
+
+    ActionInfo = namedtuple('LogInfoDetails', ['icon', 'color', 'template'])
+
+    ENABLED = ActionInfo(Icons.defcon_enabled, Colours.soft_green, "**Days:** {days}\n\n")
+    DISABLED = ActionInfo(Icons.defcon_disabled, Colours.soft_red, "")
+    UPDATED = ActionInfo(Icons.defcon_updated, Colour.blurple(), "**Days:** {days}\n\n")
+
+
 class Defcon(Cog):
     """Time-sensitive server defense mechanisms."""
 
@@ -46,6 +60,7 @@ async def sync_settings(self) -> None:
         """On cog load, try to synchronize DEFCON settings to the API."""
         await self.bot.wait_until_ready()
         self.channel = await self.bot.fetch_channel(Channels.defcon)
+
         try:
             response = await self.bot.api_client.get('bot/bot-settings/defcon')
             data = response['data']
@@ -107,39 +122,39 @@ async def defcon_group(self, ctx: Context) -> None:
         """Check the DEFCON status or run a subcommand."""
         await ctx.invoke(self.bot.get_command("help"), "defcon")
 
-    @defcon_group.command(name='enable', aliases=('on', 'e'))
-    @with_role(Roles.admin, Roles.owner)
-    async def enable_command(self, ctx: Context) -> None:
-        """
-        Enable DEFCON mode. Useful in a pinch, but be sure you know what you're doing!
-
-        Currently, this just adds an account age requirement. Use !defcon days <int> to set how old an account must be,
-        in days.
-        """
-        self.enabled = True
-
+    async def _defcon_action(self, ctx: Context, days: int, action: Action) -> None:
+        """Providing a structured way to do an defcon action."""
+        error = None
         try:
             await self.bot.api_client.put(
                 'bot/bot-settings/defcon',
                 json={
                     'name': 'defcon',
                     'data': {
-                        'enabled': True,
                         # TODO: retrieve old days count
-                        'days': 0
+                        'days': days,
+                        'enabled': action is not Action.DISABLED,
                     }
                 }
             )
-
-        except Exception as e:
+        except Exception as err:
             log.exception("Unable to update DEFCON settings.")
-            await ctx.send(self.build_defcon_msg("enabled", e))
-            await self.send_defcon_log("enabled", ctx.author, e)
+            error = err
+        finally:
+            await ctx.send(self.build_defcon_msg(action, error))
+            await self.send_defcon_log(action, ctx.author, error)
 
-        else:
-            await ctx.send(self.build_defcon_msg("enabled"))
-            await self.send_defcon_log("enabled", ctx.author)
+    @defcon_group.command(name='enable', aliases=('on', 'e'))
+    @with_role(Roles.admin, Roles.owner)
+    async def enable_command(self, ctx: Context) -> None:
+        """
+        Enable DEFCON mode. Useful in a pinch, but be sure you know what you're doing!
 
+        Currently, this just adds an account age requirement. Use !defcon days <int> to set how old an account must be,
+        in days.
+        """
+        self.enabled = True
+        await self._defcon_action(ctx, days=0, action=Action.ENABLED)
         await self.update_channel_topic()
 
     @defcon_group.command(name='disable', aliases=('off', 'd'))
@@ -147,26 +162,7 @@ async def enable_command(self, ctx: Context) -> None:
     async def disable_command(self, ctx: Context) -> None:
         """Disable DEFCON mode. Useful in a pinch, but be sure you know what you're doing!"""
         self.enabled = False
-
-        try:
-            await self.bot.api_client.put(
-                'bot/bot-settings/defcon',
-                json={
-                    'data': {
-                        'days': 0,
-                        'enabled': False
-                    },
-                    'name': 'defcon'
-                }
-            )
-        except Exception as e:
-            log.exception("Unable to update DEFCON settings.")
-            await ctx.send(self.build_defcon_msg("disabled", e))
-            await self.send_defcon_log("disabled", ctx.author, e)
-        else:
-            await ctx.send(self.build_defcon_msg("disabled"))
-            await self.send_defcon_log("disabled", ctx.author)
-
+        await self._defcon_action(ctx, days=0, action=Action.DISABLED)
         await self.update_channel_topic()
 
     @defcon_group.command(name='status', aliases=('s',))
@@ -186,30 +182,8 @@ async def status_command(self, ctx: Context) -> None:
     async def days_command(self, ctx: Context, days: int) -> None:
         """Set how old an account must be to join the server, in days, with DEFCON mode enabled."""
         self.days = timedelta(days=days)
-
-        try:
-            await self.bot.api_client.put(
-                'bot/bot-settings/defcon',
-                json={
-                    'data': {
-                        'days': days,
-                        'enabled': True
-                    },
-                    'name': 'defcon'
-                }
-            )
-        except Exception as e:
-            log.exception("Unable to update DEFCON settings.")
-            await ctx.send(self.build_defcon_msg("updated", e))
-            await self.send_defcon_log("updated", ctx.author, e)
-        else:
-            await ctx.send(self.build_defcon_msg("updated"))
-            await self.send_defcon_log("updated", ctx.author)
-
-        # Enable DEFCON if it's not already
-        if not self.enabled:
-            self.enabled = True
-
+        self.enabled = True
+        await self._defcon_action(ctx, days=days, action=Action.UPDATED)
         await self.update_channel_topic()
 
     async def update_channel_topic(self) -> None:
@@ -223,20 +197,16 @@ async def update_channel_topic(self) -> None:
         self.mod_log.ignore(Event.guild_channel_update, Channels.defcon)
         await self.channel.edit(topic=new_topic)
 
-    def build_defcon_msg(self, change: str, e: Exception = None) -> str:
-        """
-        Build in-channel response string for DEFCON action.
-
-        `change` string may be one of the following: ('enabled', 'disabled', 'updated')
-        """
-        if change.lower() == "enabled":
+    def build_defcon_msg(self, action: Action, e: Exception = None) -> str:
+        """Build in-channel response string for DEFCON action."""
+        if action is Action.ENABLED:
             msg = f"{Emojis.defcon_enabled} DEFCON enabled.\n\n"
-        elif change.lower() == "disabled":
+        elif action is Action.DISABLED:
             msg = f"{Emojis.defcon_disabled} DEFCON disabled.\n\n"
-        elif change.lower() == "updated":
+        elif action is Action.UPDATED:
             msg = (
-                f"{Emojis.defcon_updated} DEFCON days updated; accounts must be {self.days} "
-                "days old to join the server.\n\n"
+                f"{Emojis.defcon_updated} DEFCON days updated; accounts must be {self.days.days} "
+                f"day{'s' if self.days.days > 1 else ''} old to join the server.\n\n"
             )
 
         if e:
@@ -247,28 +217,14 @@ def build_defcon_msg(self, change: str, e: Exception = None) -> str:
 
         return msg
 
-    async def send_defcon_log(self, change: str, actor: Member, e: Exception = None) -> None:
-        """
-        Send log message for DEFCON action.
-
-        `change` string may be one of the following: ('enabled', 'disabled', 'updated')
-        """
-        log_msg = f"**Staffer:** {actor} (`{actor.id}`)\n"
-
-        if change.lower() == "enabled":
-            icon = Icons.defcon_enabled
-            color = Colours.soft_green
-            status_msg = "DEFCON enabled"
-            log_msg += f"**Days:** {self.days.days}\n\n"
-        elif change.lower() == "disabled":
-            icon = Icons.defcon_disabled
-            color = Colours.soft_red
-            status_msg = "DEFCON enabled"
-        elif change.lower() == "updated":
-            icon = Icons.defcon_updated
-            color = Colour.blurple()
-            status_msg = "DEFCON updated"
-            log_msg += f"**Days:** {self.days.days}\n\n"
+    async def send_defcon_log(self, action: Action, actor: Member, e: Exception = None) -> None:
+        """Send log message for DEFCON action."""
+        info = action.value
+        log_msg: str = (
+            f"**Staffer:** {actor.mention} {actor} (`{actor.id}`)\n"
+            f"{info.template.format(days=self.days.days)}"
+        )
+        status_msg = f"DEFCON {action.name.lower()}"
 
         if e:
             log_msg += (
@@ -276,7 +232,7 @@ async def send_defcon_log(self, change: str, actor: Member, e: Exception = None)
                 f"```py\n{e}\n```"
             )
 
-        await self.mod_log.send_log_message(icon, color, status_msg, log_msg)
+        await self.mod_log.send_log_message(info.icon, info.color, status_msg, log_msg)
 
 
 def setup(bot: Bot) -> None:


Test patch: