Ver Fonte

Journal : dire pourquoi un bot qui mentionne Rika est ignoré

Satoko mentionne Rika (pastille @Rika vers son vrai compte) sans réponse
et sans aucune ligne de journal : le message s'arrête au filtre des bots
de on_message, qui teste seulement la liste des mentions transmise par
Discord. Pour trancher entre une mention absente de cette liste (mention
sans notification) et la limite de réponses aux bots, une ligne INFO dit
maintenant, pour chaque message de bot dont le texte contient <@id> du
bot, si la mention a été transmise et si le message est ignoré.
Le comportement ne change pas.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Johnounet há 17 horas atrás
pai
commit
8909baefaf
3 ficheiros alterados com 70 adições e 9 exclusões
  1. 2 0
      CHANGELOG.md
  2. 22 9
      bot/discord_bot.py
  3. 46 0
      tests/test_bot_streaks.py

+ 2 - 0
CHANGELOG.md

@@ -22,6 +22,8 @@ Les versions les plus récentes sont en haut. Chaque entrée commence par un ré
 
 - **Un octet abîmé ne déforme plus tous les accents d'un fichier texte.** Dans un `.txt`, `.log` ou `.csv` en UTF-8, un seul octet invalide faisait lire tout le fichier en Windows-1252 (« Café »). Seul cet octet est maintenant relu ainsi, si le fichier est surtout en UTF-8 ; les fichiers « ANSI » restent bien lus.
 
+- **Le journal dit pourquoi un bot qui mentionne Rika est ignoré.** Quand un autre bot écrit @Rika, une ligne indique si Discord a bien transmis la mention et si le message est pris en compte ou ignoré (mention absente, ou limite de réponses aux bots d'affilée).
+
 ---
 
 ## Version 5.12

+ 22 - 9
bot/discord_bot.py

@@ -357,15 +357,28 @@ class ChatBot(commands.Bot):
         # ou réponse à l'un de ses messages. Deux bots peuvent ainsi discuter ensemble, mais pas sans fin.
         if not message.author.bot:
             self._bot_streaks.human_spoke(message.channel.id)
-        elif self.user not in message.mentions and not await self.replies_to_me(message):
-            return
-        elif (rank := self._bot_streaks.count(message.channel.id, message.created_at)) > MAX_BOT_REPLIES:
-            if rank == MAX_BOT_REPLIES + 1:
-                logger.warning("#%s : %s réponses à des bots d'affilée (dernier : %s). Le bot ne leur répond plus "
-                               "jusqu'au prochain message d'un membre, ou après %s minutes sans message de bot.",
-                               message.channel, MAX_BOT_REPLIES, message.author,
-                               BOT_STREAK_RESET.seconds // 60)
-            return
+        else:
+            listed = self.user in message.mentions
+            addressed = listed or await self.replies_to_me(message)
+            # Une mention dans le texte (<@id>) devrait figurer dans la liste des mentions fournie par Discord :
+            # le journal dit si c'est le cas, pour comprendre pourquoi un bot qui mentionne le bot est ignoré.
+            in_text = self.user is not None and self.user.id in message.raw_mentions
+            if in_text:
+                logger.info("%s mentionne le bot dans #%s (message %s) : %s.", message.author, message.channel,
+                            message.id, "mention transmise par Discord" if listed else
+                            "mention absente de la liste de Discord" + ("" if addressed else ", message ignoré"))
+            if not addressed:
+                return
+            if (rank := self._bot_streaks.count(message.channel.id, message.created_at)) > MAX_BOT_REPLIES:
+                if rank == MAX_BOT_REPLIES + 1:
+                    logger.warning("#%s : %s réponses à des bots d'affilée (dernier : %s). Le bot ne leur répond "
+                                   "plus jusqu'au prochain message d'un membre, ou après %s minutes sans message de "
+                                   "bot.", message.channel, MAX_BOT_REPLIES, message.author,
+                                   BOT_STREAK_RESET.seconds // 60)
+                elif in_text:
+                    logger.info("Message %s ignoré : limite de réponses à des bots d'affilée (rang %s).",
+                                message.id, rank)
+                return
 
         in_main_channel = message.channel.id == self.settings.channel_id
         # Mention directe uniquement (@everyone et @here ne comptent pas).

+ 46 - 0
tests/test_bot_streaks.py

@@ -48,6 +48,7 @@ class OnMessageTest(unittest.IsolatedAsyncioTestCase):
         message.channel.id = CHANNEL_ID
         message.author.bot = from_bot
         message.mentions = [self.bot.user]  # « Répondre » avec ping
+        message.raw_mentions = []  # pas de <@id> dans le texte
         await ChatBot.on_message(self.bot, message)
 
     async def test_bot_loop_stopped(self):
@@ -74,5 +75,50 @@ class OnMessageTest(unittest.IsolatedAsyncioTestCase):
         self.assertEqual(self.bot.handle_main_channel.await_count, MAX_BOT_REPLIES + 1)
 
 
+class MentionLogTest(unittest.IsolatedAsyncioTestCase):
+    """Journal de diagnostic : un bot qui écrit <@id> du bot, avec ou sans mention transmise par Discord."""
+
+    def setUp(self):
+        self.bot = mock.Mock()
+        self.bot.settings.channel_id = CHANNEL_ID
+        self.bot.home_guild_id.return_value = HOME
+        self.bot._bot_streaks = BotStreaks()
+        self.bot.handle_main_channel = mock.AsyncMock()
+        self.bot.register_members = mock.AsyncMock()
+        self.bot.replies_to_me = mock.AsyncMock(return_value=False)
+
+    async def receive(self, listed: bool) -> None:
+        message = mock.Mock(guild=mock.Mock(id=HOME), webhook_id=None, type=discord.MessageType.default,
+                            created_at=START)
+        message.channel.id = CHANNEL_ID
+        message.author.bot = True
+        message.mentions = [self.bot.user] if listed else []
+        message.raw_mentions = [self.bot.user.id]
+        await ChatBot.on_message(self.bot, message)
+
+    async def test_mention_missing_from_discord_list(self):
+        with self.assertLogs("chatbot.discord", "INFO") as logs:
+            await self.receive(listed=False)
+        self.assertIn("mention absente de la liste de Discord, message ignoré", logs.output[0])
+        self.bot.handle_main_channel.assert_not_awaited()
+
+    async def test_mention_listed(self):
+        with self.assertLogs("chatbot.discord", "INFO") as logs:
+            await self.receive(listed=True)
+        self.assertIn("mention transmise par Discord", logs.output[0])
+        self.bot.handle_main_channel.assert_awaited_once()
+
+    async def test_no_log_without_mention_in_text(self):
+        # « Répondre » à un message du bot, sans <@id> dans le texte : rien de plus dans le journal.
+        message = mock.Mock(guild=mock.Mock(id=HOME), webhook_id=None, type=discord.MessageType.reply,
+                            created_at=START, mentions=[], raw_mentions=[])
+        message.channel.id = CHANNEL_ID
+        message.author.bot = True
+        self.bot.replies_to_me.return_value = True
+        with self.assertNoLogs("chatbot.discord", "INFO"):
+            await ChatBot.on_message(self.bot, message)
+        self.bot.handle_main_channel.assert_awaited_once()
+
+
 if __name__ == "__main__":
     unittest.main()