Skip to content

Commit 2c1e6ce

Browse files
authored
Merge branch 'development' into users/lorenzo/forwarded-msg-logviewer
2 parents e9c1bfc + 040195d commit 2c1e6ce

File tree

3 files changed

+116
-101
lines changed

3 files changed

+116
-101
lines changed

bot.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1915,6 +1915,8 @@ async def on_message_delete(self, message):
19151915
"DM message not found.",
19161916
"Malformed thread message.",
19171917
"Thread message not found.",
1918+
"Linked DM message not found.",
1919+
"Thread message is an internal message, not a note.",
19181920
}:
19191921
logger.debug("Failed to find linked message to delete: %s", e)
19201922
embed = discord.Embed(description="Failed to delete message.", color=self.error_color)

cogs/modmail.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1724,11 +1724,11 @@ async def edit(self, ctx, message_id: Optional[int] = None, *, message: str):
17241724

17251725
try:
17261726
await thread.edit_message(message_id, message)
1727-
except ValueError:
1727+
except ValueError as e:
17281728
return await ctx.send(
17291729
embed=discord.Embed(
17301730
title="Failed",
1731-
description="Cannot find a message to edit. Plain messages are not supported.",
1731+
description=str(e),
17321732
color=self.bot.error_color,
17331733
)
17341734
)
@@ -2274,7 +2274,7 @@ async def delete(self, ctx, message_id: int = None):
22742274
return await ctx.send(
22752275
embed=discord.Embed(
22762276
title="Failed",
2277-
description="Cannot find a message to delete. Plain messages are not supported.",
2277+
description=str(e),
22782278
color=self.bot.error_color,
22792279
)
22802280
)

core/thread.py

Lines changed: 111 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,117 +1338,118 @@ async def find_linked_messages(
13381338
message1: discord.Message = None,
13391339
note: bool = True,
13401340
) -> typing.Tuple[discord.Message, typing.List[typing.Optional[discord.Message]]]:
1341-
if message1 is not None:
1342-
if note:
1343-
# For notes, don't require author.url; rely on footer/author.name markers
1344-
if not message1.embeds or message1.author != self.bot.user:
1345-
logger.warning(
1346-
f"Malformed note for deletion: embeds={bool(message1.embeds)}, author={message1.author}"
1347-
)
1348-
raise ValueError("Malformed note message.")
1341+
if message1 is None:
1342+
if message_id is not None:
1343+
try:
1344+
message1 = await self.channel.fetch_message(message_id)
1345+
except discord.NotFound:
1346+
logger.warning(f"Message ID {message_id} not found in channel history.")
1347+
raise ValueError("Thread message not found.")
13491348
else:
1350-
if (
1351-
not message1.embeds
1352-
or not message1.embeds[0].author.url
1353-
or message1.author != self.bot.user
1354-
):
1355-
logger.debug(
1356-
f"Malformed thread message for deletion: embeds={bool(message1.embeds)}, author_url={getattr(message1.embeds[0], 'author', None) and message1.embeds[0].author.url}, author={message1.author}"
1357-
)
1358-
# Keep original error string to avoid extra failure embeds in on_message_delete
1359-
raise ValueError("Malformed thread message.")
1349+
# No ID provided - find last message sent by bot
1350+
async for msg in self.channel.history():
1351+
if msg.author != self.bot.user:
1352+
continue
1353+
if not msg.embeds:
1354+
continue
13601355

1361-
elif message_id is not None:
1362-
try:
1363-
message1 = await self.channel.fetch_message(message_id)
1364-
except discord.NotFound:
1365-
logger.warning(f"Message ID {message_id} not found in channel history.")
1366-
raise ValueError("Thread message not found.")
1356+
is_valid_candidate = False
1357+
if (
1358+
msg.embeds[0].footer
1359+
and msg.embeds[0].footer.text
1360+
and msg.embeds[0].footer.text.startswith("[PLAIN]")
1361+
):
1362+
is_valid_candidate = True
1363+
elif msg.embeds[0].author.url and msg.embeds[0].author.url.split("#")[-1].isdigit():
1364+
is_valid_candidate = True
1365+
1366+
if is_valid_candidate:
1367+
message1 = msg
1368+
break
13671369

1368-
if note:
1369-
# Try to treat as note/persistent note first
1370-
if message1.embeds and message1.author == self.bot.user:
1371-
footer_text = (message1.embeds[0].footer and message1.embeds[0].footer.text) or ""
1372-
author_name = getattr(message1.embeds[0].author, "name", "") or ""
1373-
is_note = (
1374-
"internal note" in footer_text.lower()
1375-
or "persistent internal note" in footer_text.lower()
1376-
or author_name.startswith("📝 Note")
1377-
or author_name.startswith("📝 Persistent Note")
1378-
)
1379-
if is_note:
1380-
# Notes have no linked DM counterpart; keep None sentinel
1381-
return message1, None
1382-
# else: fall through to relay checks below
1383-
1384-
# Non-note path (regular relayed messages): require author.url and colors
1385-
if not (
1386-
message1.embeds
1387-
and message1.embeds[0].author.url
1388-
and message1.embeds[0].color
1389-
and message1.author == self.bot.user
1390-
):
1391-
logger.warning(
1392-
f"Message {message_id} is not a valid modmail relay message. embeds={bool(message1.embeds)}, author_url={getattr(message1.embeds[0], 'author', None) and message1.embeds[0].author.url}, color={getattr(message1.embeds[0], 'color', None)}, author={message1.author}"
1393-
)
1394-
raise ValueError("Thread message not found.")
1370+
if message1 is None:
1371+
raise ValueError("No editable thread message found.")
1372+
1373+
is_note = False
1374+
if message1.embeds and message1.author == self.bot.user:
1375+
footer_text = (message1.embeds[0].footer and message1.embeds[0].footer.text) or ""
1376+
author_name = getattr(message1.embeds[0].author, "name", "") or ""
1377+
is_note = (
1378+
"internal note" in footer_text.lower()
1379+
or "persistent internal note" in footer_text.lower()
1380+
or author_name.startswith("📝 Note")
1381+
or author_name.startswith("📝 Persistent Note")
1382+
)
13951383

1396-
if message1.embeds[0].footer and "Internal Message" in message1.embeds[0].footer.text:
1397-
if not note:
1398-
logger.warning(
1399-
f"Message {message_id} is an internal message, but note deletion not requested."
1400-
)
1401-
raise ValueError("Thread message is an internal message, not a note.")
1402-
# Internal bot-only message treated similarly; keep None sentinel
1403-
return message1, None
1384+
if note and is_note:
1385+
return message1, None
14041386

1405-
if message1.embeds[0].color.value != self.bot.mod_color and not (
1406-
either_direction and message1.embeds[0].color.value == self.bot.recipient_color
1407-
):
1408-
logger.warning("Message color does not match mod/recipient colors.")
1409-
raise ValueError("Thread message not found.")
1410-
else:
1411-
async for message1 in self.channel.history():
1412-
if (
1413-
message1.embeds
1414-
and message1.embeds[0].author.url
1415-
and message1.embeds[0].color
1416-
and (
1417-
message1.embeds[0].color.value == self.bot.mod_color
1418-
or (either_direction and message1.embeds[0].color.value == self.bot.recipient_color)
1419-
)
1420-
and message1.embeds[0].author.url.split("#")[-1].isdigit()
1421-
and message1.author == self.bot.user
1422-
):
1423-
break
1424-
else:
1387+
if not note and is_note:
1388+
raise ValueError("Thread message is an internal message, not a note.")
1389+
1390+
if is_note:
1391+
return message1, None
1392+
1393+
is_plain = False
1394+
if message1.embeds and message1.embeds[0].footer and message1.embeds[0].footer.text:
1395+
if message1.embeds[0].footer.text.startswith("[PLAIN]"):
1396+
is_plain = True
1397+
1398+
if not is_plain:
1399+
# Relaxed mod_color check: only ensure author is bot and has url (which implies it's a relay)
1400+
# We rely on author.url existing for Joint ID
1401+
if not (message1.embeds and message1.embeds[0].author.url and message1.author == self.bot.user):
14251402
raise ValueError("Thread message not found.")
14261403

1427-
try:
1428-
joint_id = int(message1.embeds[0].author.url.split("#")[-1])
1429-
except ValueError:
1430-
raise ValueError("Malformed thread message.")
1404+
try:
1405+
joint_id = int(message1.embeds[0].author.url.split("#")[-1])
1406+
except (ValueError, AttributeError, IndexError):
1407+
raise ValueError("Malformed thread message.")
1408+
else:
1409+
joint_id = None
1410+
mod_tag = message1.embeds[0].footer.text.replace("[PLAIN]", "", 1).strip()
1411+
author_name = message1.embeds[0].author.name
1412+
desc = message1.embeds[0].description or ""
1413+
prefix = f"**{mod_tag} " if mod_tag else "**"
1414+
plain_content_expected = f"{prefix}{author_name}:** {desc}"
1415+
creation_time = message1.created_at
14311416

14321417
messages = [message1]
1433-
for user in self.recipients:
1434-
async for msg in user.history():
1435-
if either_direction:
1436-
if msg.id == joint_id:
1437-
return message1, msg
14381418

1439-
if not (msg.embeds and msg.embeds[0].author.url):
1440-
continue
1441-
try:
1442-
if int(msg.embeds[0].author.url.split("#")[-1]) == joint_id:
1419+
if is_plain:
1420+
for user in self.recipients:
1421+
async for msg in user.history(limit=50, around=creation_time):
1422+
if abs((msg.created_at - creation_time).total_seconds()) > 15:
1423+
continue
1424+
if msg.author != self.bot.user:
1425+
continue
1426+
if msg.embeds:
1427+
continue
1428+
1429+
if msg.content == plain_content_expected:
14431430
messages.append(msg)
14441431
break
1445-
except ValueError:
1446-
continue
1432+
else:
1433+
for user in self.recipients:
1434+
async for msg in user.history():
1435+
if either_direction:
1436+
if msg.id == joint_id:
1437+
messages.append(msg)
1438+
break
1439+
1440+
if not (msg.embeds and msg.embeds[0].author.url):
1441+
continue
1442+
try:
1443+
if int(msg.embeds[0].author.url.split("#")[-1]) == joint_id:
1444+
messages.append(msg)
1445+
break
1446+
except (ValueError, IndexError, AttributeError):
1447+
continue
14471448

14481449
if len(messages) > 1:
14491450
return messages
14501451

1451-
raise ValueError("DM message not found.")
1452+
raise ValueError("Linked DM message not found.")
14521453

14531454
async def edit_message(self, message_id: typing.Optional[int], message: str) -> None:
14541455
try:
@@ -1460,6 +1461,10 @@ async def edit_message(self, message_id: typing.Optional[int], message: str) ->
14601461
embed1 = message1.embeds[0]
14611462
embed1.description = message
14621463

1464+
is_plain = False
1465+
if embed1.footer and embed1.footer.text and embed1.footer.text.startswith("[PLAIN]"):
1466+
is_plain = True
1467+
14631468
tasks = [
14641469
self.bot.api.edit_message(message1.id, message),
14651470
message1.edit(embed=embed1),
@@ -1469,9 +1474,17 @@ async def edit_message(self, message_id: typing.Optional[int], message: str) ->
14691474
else:
14701475
for m2 in message2:
14711476
if m2 is not None:
1472-
embed2 = m2.embeds[0]
1473-
embed2.description = message
1474-
tasks += [m2.edit(embed=embed2)]
1477+
if is_plain:
1478+
# Reconstruct the plain message format to preserve matching capability
1479+
mod_tag = embed1.footer.text.replace("[PLAIN]", "", 1).strip()
1480+
author_name = embed1.author.name
1481+
prefix = f"**{mod_tag} " if mod_tag else "**"
1482+
new_content = f"{prefix}{author_name}:** {message}"
1483+
tasks += [m2.edit(content=new_content)]
1484+
else:
1485+
embed2 = m2.embeds[0]
1486+
embed2.description = message
1487+
tasks += [m2.edit(embed=embed2)]
14751488

14761489
await asyncio.gather(*tasks)
14771490

0 commit comments

Comments
 (0)