Ver código fonte

Mémoire : un fichier plus long que sa limite s'ouvre de nouveau dans « Modifier »

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Johnounet 2 dias atrás
pai
commit
3a1eeb44d9
3 arquivos alterados com 71 adições e 2 exclusões
  1. 18 2
      bot/memory_views.py
  2. 6 0
      changelog.txt
  3. 47 0
      tests/test_memory.py

+ 18 - 2
bot/memory_views.py

@@ -130,6 +130,15 @@ class MemoryCard:
     # -------------------------------------------------------------- actions
 
     async def edit(self, interaction: discord.Interaction) -> None:
+        content = self.store.read(self.target.file)
+        if len(content) > INPUT_MAX * MAX_INPUTS:  # au-delà de ce qu'une fenêtre Discord peut contenir
+            file = discord.File(io.BytesIO(content.encode("utf-8")), filename="contenu_complet.txt")
+            limit = self.store.limit(self.target.file)
+            await interaction.response.send_message(
+                f"Ce fichier fait {len(content)} caractères, trop pour la fenêtre de Discord (sa limite est de "
+                f"{limit}). Son contenu est joint : efface-le, puis recopie ce qui compte avec « Modifier », ou "
+                f"demande à l'administrateur du bot de le raccourcir sur le disque.", file=file, ephemeral=True)
+            return
         await interaction.response.send_modal(EditModal(self))
 
     async def save(self, interaction: discord.Interaction, text: str, original: str | None = None) -> None:
@@ -224,13 +233,20 @@ class EditModal(discord.ui.Modal):
             "Modifier le fichier général" if card.target.member_id is None else f"Fiche de {card.target.name}")
         super().__init__(title=title[:45], timeout=VIEW_TIMEOUT)
         self.card = card
-        sizes = input_sizes(card.store.limit(card.target.file))
-        count = len(sizes)
+        limit = card.store.limit(card.target.file)
         self.original = card.store.read(card.target.file)  # comparé à l'enregistrement (voir overwrite)
+        # Fichier plus long que sa limite (limite baissée, fichier modifié sur le disque) : les zones
+        # s'agrandissent pour le contenir en entier, à raccourcir avant d'enregistrer. Tronqué, il serait
+        # perdu en partie ; plus long que sa zone, Discord refuserait d'ouvrir la fenêtre.
+        too_long = len(self.original) > limit
+        sizes = input_sizes(max(limit, len(self.original)))
+        count = len(sizes)
         parts = split_for_inputs(self.original, sizes)
         self.inputs = []
         for i in range(count):
             label = "Contenu" if count == 1 else f"Contenu ({i + 1}/{count})"
+            if too_long and i == 0:
+                label += f" — {limit} caractères au plus"
             text_input = discord.ui.TextInput(
                 label=label, style=discord.TextStyle.paragraph, required=False,
                 max_length=sizes[i], default=parts[i] or None,

+ 6 - 0
changelog.txt

@@ -126,6 +126,12 @@ Corrections
 - Mémoire : la fenêtre « Modifier » du fichier général n'accepte plus
   que 5000 caractères (MEMORY_SERVER_LIMIT) : sa deuxième zone de texte
   laissait en écrire 8000, refusés ensuite à l'enregistrement.
+- Mémoire : un fichier plus long que sa limite (MEMORY_*_LIMIT baissée,
+  fichier modifié sur le disque) s'ouvre de nouveau dans « Modifier »,
+  en entier, avec la limite rappelée pour le raccourcir. Avant, la
+  fenêtre ne s'ouvrait pas (« Échec de l'interaction »). Au-delà de
+  20000 caractères, trop pour une fenêtre Discord, son contenu est
+  envoyé en fichier joint.
 - Mémoire : un membre ne peut plus prendre le nom Discord d'un autre
   (pseudo, nom d'utilisateur ou ancien pseudo) comme surnom dans sa
   fiche. Ce surnom est ignoré dans la liste des membres : Rika ne note

+ 47 - 0
tests/test_memory.py

@@ -579,6 +579,53 @@ class InputSizesTest(MemoryTestCase):
         self.assertEqual(split_for_inputs("", [3000]), [""])
 
 
+class TooLongFileTest(MemoryTestCase):
+    """Fichier plus long que sa limite (limite baissée, modification sur le disque) : « Modifier » s'ouvre
+    quand même, sans rien tronquer."""
+
+    def card(self) -> MemoryCard:
+        return MemoryCard(self.store, Target(f"membres/{MEMBER_ID}.md", MEMBER_ID, "Bob", is_self=True),
+                          owner_id=MEMBER_ID, bot_name="Rika")
+
+    @staticmethod
+    def interaction() -> mock.Mock:
+        interaction = mock.Mock()
+        interaction.response.send_modal = mock.AsyncMock()
+        interaction.response.send_message = mock.AsyncMock()
+        return interaction
+
+    async def test_window_holds_whole_file(self):
+        # Scénario de l'audit : fiche de 3500 caractères, limite de 3000.
+        text = "\n".join(["a" * 1999, "b" * 1500])
+        self.write(f"membres/{MEMBER_ID}.md", text)
+        interaction = self.interaction()
+        await self.card().edit(interaction)
+        window = interaction.response.send_modal.call_args.args[0]
+        self.assertEqual([text_input.max_length for text_input in window.inputs], [3500])
+        self.assertEqual(window.inputs[0].default, text)
+        self.assertIn("3000 caractères au plus", window.inputs[0].label)
+        self.assertLessEqual(len(window.inputs[0].label), 45)
+
+    async def test_saving_unshortened_refused(self):
+        self.write(f"membres/{MEMBER_ID}.md", "a" * 3500)
+        window = EditModal(self.card())
+        window.inputs[0]._value = "a" * 3500
+        interaction = self.interaction()
+        await window.on_submit(interaction)
+        self.assertIn("Trop long", interaction.response.send_message.call_args.args[0])
+        self.assertEqual(len(self.store.read(f"membres/{MEMBER_ID}.md")), 3500)
+
+    async def test_beyond_discord_window(self):
+        # Plus que 5 zones de 4000 : pas de fenêtre, le contenu complet est envoyé en fichier joint.
+        self.write(f"membres/{MEMBER_ID}.md", "a" * 25139)
+        interaction = self.interaction()
+        await self.card().edit(interaction)
+        interaction.response.send_modal.assert_not_awaited()
+        message = interaction.response.send_message.call_args
+        self.assertIn("25139 caractères", message.args[0])
+        self.assertEqual(len(message.kwargs["file"].fp.getvalue()), 25139)
+
+
 class AdminAccessTest(MemoryTestCase):
     """Les membres savent que les administrateurs voient leur fiche, et chaque consultation est tracée."""