Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,8 @@ def __init__(self, original_message, forwarded_content):
self.author = original_message.author
self.content = forwarded_content
self.attachments = []
for snap in getattr(original_message, "message_snapshots", []):
self.attachments.extend(getattr(snap, "attachments", []))
self.stickers = []
self.created_at = original_message.created_at
self.embeds = []
Expand Down
11 changes: 10 additions & 1 deletion core/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,9 +661,18 @@ async def append_log(
channel_id: str = "",
type_: str = "thread_message",
) -> dict:
from core.utils import extract_forwarded_content

channel_id = str(channel_id) or str(message.channel.id)
message_id = str(message_id) or str(message.id)

content = message.content or ""
if forwarded := extract_forwarded_content(message):
if content:
content += "\n" + forwarded
else:
content = forwarded

data = {
"timestamp": str(message.created_at),
"message_id": message_id,
Expand All @@ -674,7 +683,7 @@ async def append_log(
"avatar_url": message.author.display_avatar.url if message.author.display_avatar else None,
"mod": not isinstance(message.channel, DMChannel),
},
"content": message.content,
"content": content,
"type": type_,
"attachments": [
{
Expand Down
11 changes: 10 additions & 1 deletion core/thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
ConfirmThreadCreationView,
DummyParam,
extract_forwarded_content,
extract_forwarded_attachments,
)

logger = getLogger(__name__)
Expand Down Expand Up @@ -205,7 +206,10 @@ async def snooze(self, moderator=None, command_used=None, snooze_for=None):
"messages": [
{
"author_id": m.author.id,
"content": m.content,
"content": (
(m.content or "")
+ (("\n" + extract_forwarded_content(m)) if extract_forwarded_content(m) else "")
).strip(),
"attachments": [a.url for a in m.attachments],
"embeds": [e.to_dict() for e in m.embeds],
"created_at": m.created_at.isoformat(),
Expand Down Expand Up @@ -1948,6 +1952,11 @@ async def send(

ext = [(a.url, a.filename, False) for a in message.attachments]

# Add forwarded message attachments
forwarded_attachments = extract_forwarded_attachments(message)
for url, filename in forwarded_attachments:
ext.append((url, filename, False))

images = []
attachments = []
for attachment in ext:
Expand Down
126 changes: 88 additions & 38 deletions core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"ConfirmThreadCreationView",
"DummyParam",
"extract_forwarded_content",
"extract_forwarded_attachments",
]


Expand Down Expand Up @@ -640,6 +641,37 @@ def __init__(self):
self.value = None


def extract_forwarded_attachments(message) -> typing.List[typing.Tuple[str, str]]:
"""
Extract attachment URLs from forwarded messages.

Parameters
----------
message : discord.Message
The message to extract attachments from.

Returns
-------
List[Tuple[str, str]]
List of (url, filename) tuples for attachments.
"""
import discord

attachments = []
try:
if getattr(message, "message_snapshots", None):
for snap in message.message_snapshots:
if getattr(snap, "attachments", None):
for a in snap.attachments:
url = getattr(a, "url", None)
filename = getattr(a, "filename", "Unknown")
if url:
attachments.append((url.split("?")[0], filename))
except Exception:
pass
return attachments


def extract_forwarded_content(message) -> typing.Optional[str]:
"""
Extract forwarded message content from Discord forwarded messages.
Expand All @@ -658,39 +690,51 @@ def extract_forwarded_content(message) -> typing.Optional[str]:

try:
# Handle multi-forward (message_snapshots)
if hasattr(message, "flags") and getattr(message.flags, "has_snapshot", False):
if hasattr(message, "message_snapshots") and message.message_snapshots:
forwarded_parts = []
for snap in message.message_snapshots:
author = getattr(snap, "author", None)
author_name = getattr(author, "name", "Unknown") if author else "Unknown"
snap_content = getattr(snap, "content", "")

if snap_content:
# Truncate very long messages to prevent spam
if len(snap_content) > 500:
snap_content = snap_content[:497] + "..."
forwarded_parts.append(f"**{author_name}:** {snap_content}")
elif getattr(snap, "embeds", None):
for embed in snap.embeds:
if hasattr(embed, "description") and embed.description:
embed_desc = embed.description
if len(embed_desc) > 300:
embed_desc = embed_desc[:297] + "..."
forwarded_parts.append(f"**{author_name}:** {embed_desc}")
break
elif getattr(snap, "attachments", None):
attachment_info = ", ".join(
[getattr(a, "filename", "Unknown") for a in snap.attachments[:3]]
)
if len(snap.attachments) > 3:
attachment_info += f" (+{len(snap.attachments) - 3} more)"
forwarded_parts.append(f"**{author_name}:** [Attachments: {attachment_info}]")
else:
forwarded_parts.append(f"**{author_name}:** [No content]")

if forwarded_parts:
return "\n".join(forwarded_parts)
# Check directly for snapshots as flags.has_snapshot can be unreliable in some versions
if getattr(message, "message_snapshots", None):
forwarded_parts = []
for snap in message.message_snapshots:
author = getattr(snap, "author", None)
# If author is missing, we can try to rely on the container message context or just omit.
# Since we can't reliably get the original author from snapshot in this state, we focus on content.

snap_content = getattr(snap, "content", "")

formatted_part = "📨 **Forwarded Message**\n"

if snap_content:
if len(snap_content) > 500:
snap_content = snap_content[:497] + "..."
formatted_part += "\n".join([f"{line}" for line in snap_content.splitlines()]) + "\n"

if getattr(snap, "embeds", None):
for embed in snap.embeds:
if hasattr(embed, "description") and embed.description:
embed_desc = embed.description
if len(embed_desc) > 300:
embed_desc = embed_desc[:297] + "..."
formatted_part += (
"\n".join([f"> {line}" for line in embed_desc.splitlines()]) + "\n"
)
break # One embed preview is usually enough

if getattr(snap, "attachments", None):
attachment_links = []
for a in snap.attachments[:3]:
filename = getattr(a, "filename", "Unknown")
url = getattr(a, "url", None)
if url:
url = url.split("?")[0]
attachment_links.append(f"[{filename}]({url})")
else:
attachment_links.append(filename)
if len(snap.attachments) > 3:
attachment_links.append(f"(+{len(snap.attachments) - 3} more)")
formatted_part += f"📎 {', '.join(attachment_links)}\n"
forwarded_parts.append(formatted_part)

if forwarded_parts:
return "\n".join(forwarded_parts)

# Handle single-message forward
elif getattr(message, "type", None) == getattr(discord.MessageType, "forward", None):
Expand Down Expand Up @@ -719,12 +763,18 @@ def extract_forwarded_content(message) -> typing.Optional[str]:
embed_desc = embed_desc[:297] + "..."
return f"**{ref_author_name}:** {embed_desc}"
elif getattr(ref_msg, "attachments", None):
attachment_info = ", ".join(
[getattr(a, "filename", "Unknown") for a in ref_msg.attachments[:3]]
)
attachment_links = []
for a in ref_msg.attachments[:3]:
filename = getattr(a, "filename", "Unknown")
url = getattr(a, "url", None)
if url:
url = url.split("?")[0]
attachment_links.append(f"[{filename}]({url})")
else:
attachment_links.append(filename)
if len(ref_msg.attachments) > 3:
attachment_info += f" (+{len(ref_msg.attachments) - 3} more)"
return f"**{ref_author_name}:** [Attachments: {attachment_info}]"
attachment_links.append(f"(+{len(ref_msg.attachments) - 3} more)")
return f"**{ref_author_name}:** 📎 {', '.join(attachment_links)}"
except Exception as e:
# Log and continue; failing to extract a reference preview shouldn't break flow
logger.debug("Failed to extract reference preview: %s", e)
Expand Down
Loading