Skip to content
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 @@ -206,7 +207,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 @@ -1955,6 +1959,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
131 changes: 88 additions & 43 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,35 @@ 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.
"""
attachments = []
try:
if hasattr(message, "message_snapshots") and message.message_snapshots:
for snap in message.message_snapshots:
if hasattr(snap, "attachments") and snap.attachments:
for a in snap.attachments:
url = getattr(a, "url", None)
filename = getattr(a, "filename", "Unknown")
if url:
attachments.append((url.split("?")[0], filename))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it's because of this PR, but forwarded attachments show up incorrectly formatted in the logviewer. They show up as [image](link) and don't properly embed. An image sent directly does show up properly.

Image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have been able to partialy resolve this request. Images would show as following for some reason.
image

except Exception:
pass
return attachments


def extract_forwarded_content(message) -> typing.Optional[str]:
"""
Extract forwarded message content from Discord forwarded messages.
Expand All @@ -654,46 +684,54 @@ def extract_forwarded_content(message) -> typing.Optional[str]:
Optional[str]
The extracted forwarded content, or None if not a forwarded message.
"""
import discord

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 hasattr(message, "message_snapshots") and message.message_snapshots:
forwarded_parts = []
for snap in message.message_snapshots:
# 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 hasattr(snap, "embeds") and snap.embeds:
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 hasattr(snap, "attachments") and snap.attachments:
attachment_urls = []
for a in snap.attachments[:3]:
url = getattr(a, "url", None)
if url:
# Use direct URL for proper embedding in logviewer
attachment_urls.append(url.split("?")[0])
if len(snap.attachments) > 3:
formatted_part += f"📎 (+{len(snap.attachments) - 3} more attachments)\n"
# Add URLs on separate lines for proper embedding
for url in attachment_urls:
formatted_part += f"{url}\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):
elif hasattr(message, "type") and message.type == getattr(discord.MessageType, "forward", None):
ref = getattr(message, "reference", None)
if (
ref
Expand All @@ -711,20 +749,27 @@ def extract_forwarded_content(message) -> typing.Optional[str]:
if len(ref_content) > 500:
ref_content = ref_content[:497] + "..."
return f"**{ref_author_name}:** {ref_content}"
elif getattr(ref_msg, "embeds", None):
elif hasattr(ref_msg, "embeds") and ref_msg.embeds:
for embed in ref_msg.embeds:
if hasattr(embed, "description") and embed.description:
embed_desc = embed.description
if len(embed_desc) > 300:
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]]
)
elif hasattr(ref_msg, "attachments") and ref_msg.attachments:
attachment_urls = []
for a in ref_msg.attachments[:3]:
url = getattr(a, "url", None)
if url:
# Use direct URL for proper embedding in logviewer
attachment_urls.append(url.split("?")[0])
result = f"**{ref_author_name}:** 📎\n"
if len(ref_msg.attachments) > 3:
attachment_info += f" (+{len(ref_msg.attachments) - 3} more)"
return f"**{ref_author_name}:** [Attachments: {attachment_info}]"
result += f"(+{len(ref_msg.attachments) - 3} more attachments)\n"
# Add URLs on separate lines for proper embedding
for url in attachment_urls:
result += f"{url}\n"
return result
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