Repository: python-discord/bot

Issue:
Showing what changed from `on_message_edit`
Right now, for each message edited, both before & after edited message are posted as embed, separately, in full `.content`

Some suggestions to make this better:

1. Merge both embeds and use a jump link to the after edited message
    - Since the message is not yet deleted, it is still in the server, so getting the full content of the message is just a *click away*.
2. Show what changed
    - It is more valuable to know what changed after the edit rather than the full content before and after, since the message was not deleted *yet*.

### Some examples:

Scragly's suggestion - clean, compact, using hyperlink:
![Scragly's][example 1]

Example only using `difflib`:
![Shirayuki's][example 2]

Example implementing Scragly's suggestion:
![Scragly's idea][example 3]

Example of longer messages:
![long edit][example 4]

[example 1]: https://cdn.discordapp.com/attachments/305126844661760000/638557082874019850/unknown.png
[example 2]: https://cdn.discordapp.com/attachments/305126844661760000/638557938100994069/unknown.png
[example 3]: 
https://user-images.githubusercontent.com/19921765/67757307-2411be80-fa6e-11e9-99a3-73f129122c70.png
[example 4]: 
https://user-images.githubusercontent.com/19921765/67757354-3d1a6f80-fa6e-11e9-8064-e44d91b7c0df.png




Discussion:

Resolving patch:
diff --git a/bot/cogs/moderation/modlog.py b/bot/cogs/moderation/modlog.py
--- a/bot/cogs/moderation/modlog.py
+++ b/bot/cogs/moderation/modlog.py
@@ -1,4 +1,6 @@
 import asyncio
+import difflib
+import itertools
 import logging
 import typing as t
 from datetime import datetime
@@ -618,80 +620,81 @@ async def on_raw_message_delete(self, event: discord.RawMessageDeleteEvent) -> N
         )
 
     @Cog.listener()
-    async def on_message_edit(self, before: discord.Message, after: discord.Message) -> None:
+    async def on_message_edit(self, msg_before: discord.Message, msg_after: discord.Message) -> None:
         """Log message edit event to message change log."""
         if (
-            not before.guild
-            or before.guild.id != GuildConstant.id
-            or before.channel.id in GuildConstant.ignored
-            or before.author.bot
+            not msg_before.guild
+            or msg_before.guild.id != GuildConstant.id
+            or msg_before.channel.id in GuildConstant.ignored
+            or msg_before.author.bot
         ):
             return
 
-        self._cached_edits.append(before.id)
+        self._cached_edits.append(msg_before.id)
 
-        if before.content == after.content:
+        if msg_before.content == msg_after.content:
             return
 
-        author = before.author
-        channel = before.channel
+        author = msg_before.author
+        channel = msg_before.channel
+        channel_name = f"{channel.category}/#{channel.name}" if channel.category else f"#{channel.name}"
 
-        if channel.category:
-            before_response = (
-                f"**Author:** {author} (`{author.id}`)\n"
-                f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n"
-                f"**Message ID:** `{before.id}`\n"
-                "\n"
-                f"{before.clean_content}"
-            )
-
-            after_response = (
-                f"**Author:** {author} (`{author.id}`)\n"
-                f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n"
-                f"**Message ID:** `{before.id}`\n"
-                "\n"
-                f"{after.clean_content}"
-            )
-        else:
-            before_response = (
-                f"**Author:** {author} (`{author.id}`)\n"
-                f"**Channel:** #{channel.name} (`{channel.id}`)\n"
-                f"**Message ID:** `{before.id}`\n"
-                "\n"
-                f"{before.clean_content}"
-            )
+        # Getting the difference per words and group them by type - add, remove, same
+        # Note that this is intended grouping without sorting
+        diff = difflib.ndiff(msg_before.clean_content.split(), msg_after.clean_content.split())
+        diff_groups = tuple(
+            (diff_type, tuple(s[2:] for s in diff_words))
+            for diff_type, diff_words in itertools.groupby(diff, key=lambda s: s[0])
+        )
 
-            after_response = (
-                f"**Author:** {author} (`{author.id}`)\n"
-                f"**Channel:** #{channel.name} (`{channel.id}`)\n"
-                f"**Message ID:** `{before.id}`\n"
-                "\n"
-                f"{after.clean_content}"
-            )
+        content_before: t.List[str] = []
+        content_after: t.List[str] = []
+
+        for index, (diff_type, words) in enumerate(diff_groups):
+            sub = ' '.join(words)
+            if diff_type == '-':
+                content_before.append(f"[{sub}](http://o.hi)")
+            elif diff_type == '+':
+                content_after.append(f"[{sub}](http://o.hi)")
+            elif diff_type == ' ':
+                if len(words) > 2:
+                    sub = (
+                        f"{words[0] if index > 0 else ''}"
+                        " ... "
+                        f"{words[-1] if index < len(diff_groups) - 1 else ''}"
+                    )
+                content_before.append(sub)
+                content_after.append(sub)
+
+        response = (
+            f"**Author:** {author} (`{author.id}`)\n"
+            f"**Channel:** {channel_name} (`{channel.id}`)\n"
+            f"**Message ID:** `{msg_before.id}`\n"
+            "\n"
+            f"**Before**:\n{' '.join(content_before)}\n"
+            f"**After**:\n{' '.join(content_after)}\n"
+            "\n"
+            f"[Jump to message]({msg_after.jump_url})"
+        )
 
-        if before.edited_at:
+        if msg_before.edited_at:
             # Message was previously edited, to assist with self-bot detection, use the edited_at
             # datetime as the baseline and create a human-readable delta between this edit event
             # and the last time the message was edited
-            timestamp = before.edited_at
-            delta = humanize_delta(relativedelta(after.edited_at, before.edited_at))
+            timestamp = msg_before.edited_at
+            delta = humanize_delta(relativedelta(msg_after.edited_at, msg_before.edited_at))
             footer = f"Last edited {delta} ago"
         else:
             # Message was not previously edited, use the created_at datetime as the baseline, no
             # delta calculation needed
-            timestamp = before.created_at
+            timestamp = msg_before.created_at
             footer = None
 
         await self.send_log_message(
-            Icons.message_edit, Colour.blurple(), "Message edited (Before)", before_response,
+            Icons.message_edit, Colour.blurple(), "Message edited", response,
             channel_id=Channels.message_log, timestamp_override=timestamp, footer=footer
         )
 
-        await self.send_log_message(
-            Icons.message_edit, Colour.blurple(), "Message edited (After)", after_response,
-            channel_id=Channels.message_log, timestamp_override=after.edited_at
-        )
-
     @Cog.listener()
     async def on_raw_message_edit(self, event: discord.RawMessageUpdateEvent) -> None:
         """Log raw message edit event to message change log."""
@@ -718,39 +721,23 @@ async def on_raw_message_edit(self, event: discord.RawMessageUpdateEvent) -> Non
 
         author = message.author
         channel = message.channel
+        channel_name = f"{channel.category}/#{channel.name}" if channel.category else f"#{channel.name}"
+
+        before_response = (
+            f"**Author:** {author} (`{author.id}`)\n"
+            f"**Channel:** {channel_name} (`{channel.id}`)\n"
+            f"**Message ID:** `{message.id}`\n"
+            "\n"
+            "This message was not cached, so the message content cannot be displayed."
+        )
 
-        if channel.category:
-            before_response = (
-                f"**Author:** {author} (`{author.id}`)\n"
-                f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n"
-                f"**Message ID:** `{message.id}`\n"
-                "\n"
-                "This message was not cached, so the message content cannot be displayed."
-            )
-
-            after_response = (
-                f"**Author:** {author} (`{author.id}`)\n"
-                f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n"
-                f"**Message ID:** `{message.id}`\n"
-                "\n"
-                f"{message.clean_content}"
-            )
-        else:
-            before_response = (
-                f"**Author:** {author} (`{author.id}`)\n"
-                f"**Channel:** #{channel.name} (`{channel.id}`)\n"
-                f"**Message ID:** `{message.id}`\n"
-                "\n"
-                "This message was not cached, so the message content cannot be displayed."
-            )
-
-            after_response = (
-                f"**Author:** {author} (`{author.id}`)\n"
-                f"**Channel:** #{channel.name} (`{channel.id}`)\n"
-                f"**Message ID:** `{message.id}`\n"
-                "\n"
-                f"{message.clean_content}"
-            )
+        after_response = (
+            f"**Author:** {author} (`{author.id}`)\n"
+            f"**Channel:** {channel_name} (`{channel.id}`)\n"
+            f"**Message ID:** `{message.id}`\n"
+            "\n"
+            f"{message.clean_content}"
+        )
 
         await self.send_log_message(
             Icons.message_edit, Colour.blurple(), "Message edited (Before)",


Test patch: