Skip to content

Commit 9452017

Browse files
committed
feat: dpy 2.6.3, forwarded messages, bug fixes.
1 parent 050c510 commit 9452017

File tree

5 files changed

+196
-52
lines changed

5 files changed

+196
-52
lines changed

Pipfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ typing-extensions = "==4.8.0"
1212
[packages]
1313
aiohttp = "==3.9.0"
1414
colorama = "==0.4.6"
15-
"discord.py" = {version = "==2.5.2", extras = ["speed"]}
15+
"discord.py" = {version = "==2.6.3", extras = ["speed"]}
1616
emoji = "==2.8.0"
1717
isodate = "==0.6.1"
1818
motor = "==3.3.2"

bot.py

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,15 @@
4848
)
4949
from core.thread import ThreadManager
5050
from core.time import human_timedelta
51-
from core.utils import extract_block_timestamp, normalize_alias, parse_alias, truncate, tryint, human_join
51+
from core.utils import (
52+
extract_block_timestamp,
53+
normalize_alias,
54+
parse_alias,
55+
truncate,
56+
tryint,
57+
human_join,
58+
extract_forwarded_content,
59+
)
5260

5361
logger = getLogger(__name__)
5462

@@ -169,7 +177,7 @@ def startup(self):
169177
logger.info("v%s", __version__)
170178
logger.info("Authors: kyb3r, fourjr, Taaku18")
171179
logger.line()
172-
logger.info("discord.py: v2.5.2")
180+
logger.info("discord.py: v2.6.3")
173181
logger.line()
174182

175183
async def load_extensions(self):
@@ -926,32 +934,24 @@ async def process_dm_modmail(self, message: discord.Message) -> None:
926934
logger.info("A message was blocked from %s due to disabled Modmail.", message.author)
927935
await self.add_reaction(message, blocked_emoji)
928936
return await message.channel.send(embed=embed)
929-
for snap in message.message_snapshots:
930-
author = getattr(snap, "author", None)
931-
author_name = getattr(author, "name", "Unknown") if author else "Unknown"
932-
author_avatar = getattr(author, "display_avatar", None)
933-
author_avatar_url = author_avatar.url if author_avatar else None
934-
# fix: Only use '[This is a forwarded message.]' if snap.content is actually empty
935-
content = snap.content if snap.content else "[This is a forwarded message.]"
936-
if snap.embeds:
937-
content += "\n" + "\n".join(
938-
[e.to_dict().get("description", "[embed]") for e in snap.embeds]
939-
)
940-
if snap.attachments:
941-
content += "\n" + "\n".join([a.url for a in snap.attachments])
942-
943-
class DummySnap:
944-
def __init__(self, snap, author, content):
945-
self.author = author
946-
self.content = content
947-
self.attachments = getattr(snap, "attachments", [])
948-
self.stickers = getattr(snap, "stickers", [])
949-
self.created_at = getattr(snap, "created_at", message.created_at)
950-
self.embeds = getattr(snap, "embeds", [])
951-
self.id = getattr(snap, "id", 0)
952-
953-
dummy_msg = DummySnap(snap, author, content)
954-
await thread.send(dummy_msg)
937+
# Extract forwarded content using utility function
938+
combined_content = extract_forwarded_content(message) or "[Forwarded message with no content]"
939+
940+
class ForwardedMessage:
941+
def __init__(self, original_message, forwarded_content):
942+
self.author = original_message.author
943+
self.content = forwarded_content
944+
self.attachments = []
945+
self.stickers = []
946+
self.created_at = original_message.created_at
947+
self.embeds = []
948+
self.id = original_message.id
949+
self.flags = original_message.flags
950+
self.message_snapshots = original_message.message_snapshots
951+
self.type = getattr(original_message, "type", None)
952+
953+
forwarded_msg = ForwardedMessage(message, combined_content)
954+
await thread.send(forwarded_msg)
955955
await self.add_reaction(message, sent_emoji)
956956
self.dispatch("thread_reply", thread, False, message, False, False)
957957
return
@@ -1020,7 +1020,28 @@ def __init__(self, snap, author, content):
10201020
)
10211021
await self.add_reaction(message, blocked_emoji)
10221022
return await message.channel.send(embed=embed)
1023-
await thread.send(ref_msg)
1023+
1024+
# Create a forwarded message wrapper to preserve forward info
1025+
class ForwardedMessage:
1026+
def __init__(self, original_message, ref_message):
1027+
self.author = original_message.author
1028+
# Use the utility function to extract content or fallback to ref message content
1029+
extracted_content = extract_forwarded_content(original_message)
1030+
self.content = (
1031+
extracted_content
1032+
or ref_message.content
1033+
or "[Forwarded message with no text content]"
1034+
)
1035+
self.attachments = getattr(ref_message, "attachments", [])
1036+
self.stickers = getattr(ref_message, "stickers", [])
1037+
self.created_at = original_message.created_at
1038+
self.embeds = getattr(ref_message, "embeds", [])
1039+
self.id = original_message.id
1040+
self.type = getattr(original_message, "type", None)
1041+
self.reference = original_message.reference
1042+
1043+
forwarded_msg = ForwardedMessage(message, ref_msg)
1044+
await thread.send(forwarded_msg)
10241045
await self.add_reaction(message, sent_emoji)
10251046
self.dispatch("thread_reply", thread, False, message, False, False)
10261047
return
@@ -1990,7 +2011,7 @@ def main():
19902011
sys.exit(0)
19912012

19922013
# check discord version
1993-
discord_version = "2.5.2"
2014+
discord_version = "2.6.3"
19942015
if discord.__version__ != discord_version:
19952016
logger.error(
19962017
"Dependencies are not updated, run pipenv install. discord.py version expected %s, received %s",

core/thread.py

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
DenyButton,
3434
ConfirmThreadCreationView,
3535
DummyParam,
36+
extract_forwarded_content,
3637
)
3738

3839
logger = getLogger(__name__)
@@ -1188,30 +1189,53 @@ async def send(
11881189
else:
11891190
avatar_url = author.display_avatar.url
11901191

1191-
# Extract content for blank/forwarded messages ---
1192-
content = message.content
1193-
if not content:
1194-
# Try to extract from referenced message (replies)
1195-
if hasattr(message, "reference") and message.reference is not None:
1192+
# Handle forwarded messages first
1193+
forwarded_jump_url = None
1194+
if hasattr(message, "message_snapshots") and len(message.message_snapshots) > 0:
1195+
snap = message.message_snapshots[0]
1196+
# Only show "No content" if there's truly no content (no text, attachments, embeds, or stickers)
1197+
if not snap.content and not message.attachments and not message.embeds and not message.stickers:
1198+
content = "No content"
1199+
else:
1200+
content = snap.content or ""
1201+
1202+
# Get jump_url from cached_message, fetch if not cached
1203+
if hasattr(snap, "cached_message") and snap.cached_message:
1204+
forwarded_jump_url = snap.cached_message.jump_url
1205+
elif hasattr(snap, "message") and snap.message:
1206+
# Try to fetch the original message to get the correct jump_url
11961207
try:
1197-
ref = message.reference.resolved
1198-
if ref and hasattr(ref, "content") and ref.content:
1199-
content = f"(Reply to: {ref.author}: {ref.content})"
1200-
except Exception:
1208+
original_msg = await snap.message.channel.fetch_message(snap.message.id)
1209+
forwarded_jump_url = original_msg.jump_url
1210+
except (discord.NotFound, discord.Forbidden, AttributeError):
1211+
# If we can't fetch the message, we'll proceed without the jump_url
12011212
pass
1202-
# Try to extract from first embed's description
1203-
if not content and message.embeds:
1204-
first_embed = message.embeds[0]
1205-
if hasattr(first_embed, "description") and first_embed.description:
1206-
content = first_embed.description
1207-
# Fallback: show something generic if still blank
1208-
if not content:
1209-
content = "[This is a forwarded message.]"
1210-
1211-
embed = discord.Embed(description=content)
1213+
1214+
content = f"📨 **Forwarded message:**\n{content}" if content else "📨 **Forwarded message:**"
1215+
else:
1216+
# Only show "No content" if there's truly no content (no text, attachments, embeds, or stickers)
1217+
if (
1218+
not message.content
1219+
and not message.attachments
1220+
and not message.embeds
1221+
and not message.stickers
1222+
):
1223+
content = "No content"
1224+
else:
1225+
content = message.content or ""
1226+
1227+
# Only set description if there's actual content to show
1228+
if content:
1229+
embed = discord.Embed(description=content)
1230+
else:
1231+
embed = discord.Embed()
12121232
if self.bot.config["show_timestamp"]:
12131233
embed.timestamp = message.created_at
12141234

1235+
# Add forwarded message context
1236+
if forwarded_jump_url:
1237+
embed.add_field(name="Context", value=f"- {forwarded_jump_url}", inline=True)
1238+
12151239
system_avatar_url = "https://discordapp.com/assets/f78426a064bc9dd24847519259bc42af.png"
12161240

12171241
if not note:
@@ -1399,7 +1423,11 @@ def lottie_to_png(data):
13991423
else:
14001424
embed.set_footer(text=self.bot.config["anon_tag"])
14011425
else:
1402-
embed.set_footer(text=f"Message ID: {message.id}")
1426+
# Add forwarded message indicator in footer for mods
1427+
footer_text = f"Message ID: {message.id}"
1428+
if hasattr(message, "message_snapshots") and len(message.message_snapshots) > 0:
1429+
footer_text += " • Forwarded"
1430+
embed.set_footer(text=footer_text)
14031431
embed.colour = self.bot.recipient_color
14041432

14051433
if (from_mod or note) and not thread_creation:

core/utils.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"DenyButton",
4545
"ConfirmThreadCreationView",
4646
"DummyParam",
47+
"extract_forwarded_content",
4748
]
4849

4950

@@ -608,6 +609,100 @@ def __init__(self):
608609
self.value = None
609610

610611

612+
def extract_forwarded_content(message) -> typing.Optional[str]:
613+
"""
614+
Extract forwarded message content from Discord forwarded messages.
615+
616+
Parameters
617+
----------
618+
message : discord.Message
619+
The message to extract forwarded content from.
620+
621+
Returns
622+
-------
623+
Optional[str]
624+
The extracted forwarded content, or None if not a forwarded message.
625+
"""
626+
import discord
627+
628+
try:
629+
# Handle multi-forward (message_snapshots)
630+
if hasattr(message, "flags") and getattr(message.flags, "has_snapshot", False):
631+
if hasattr(message, "message_snapshots") and message.message_snapshots:
632+
forwarded_parts = []
633+
for snap in message.message_snapshots:
634+
author = getattr(snap, "author", None)
635+
author_name = getattr(author, "name", "Unknown") if author else "Unknown"
636+
snap_content = getattr(snap, "content", "")
637+
638+
if snap_content:
639+
# Truncate very long messages to prevent spam
640+
if len(snap_content) > 500:
641+
snap_content = snap_content[:497] + "..."
642+
forwarded_parts.append(f"**{author_name}:** {snap_content}")
643+
elif getattr(snap, "embeds", None):
644+
for embed in snap.embeds:
645+
if hasattr(embed, "description") and embed.description:
646+
embed_desc = embed.description
647+
if len(embed_desc) > 300:
648+
embed_desc = embed_desc[:297] + "..."
649+
forwarded_parts.append(f"**{author_name}:** {embed_desc}")
650+
break
651+
elif getattr(snap, "attachments", None):
652+
attachment_info = ", ".join(
653+
[getattr(a, "filename", "Unknown") for a in snap.attachments[:3]]
654+
)
655+
if len(snap.attachments) > 3:
656+
attachment_info += f" (+{len(snap.attachments) - 3} more)"
657+
forwarded_parts.append(f"**{author_name}:** [Attachments: {attachment_info}]")
658+
else:
659+
forwarded_parts.append(f"**{author_name}:** [No content]")
660+
661+
if forwarded_parts:
662+
return "\n".join(forwarded_parts)
663+
664+
# Handle single-message forward
665+
elif getattr(message, "type", None) == getattr(discord.MessageType, "forward", None):
666+
ref = getattr(message, "reference", None)
667+
if (
668+
ref
669+
and hasattr(discord, "MessageReferenceType")
670+
and getattr(ref, "type", None) == getattr(discord.MessageReferenceType, "forward", None)
671+
):
672+
try:
673+
ref_msg = getattr(ref, "resolved", None)
674+
if ref_msg:
675+
ref_author = getattr(ref_msg, "author", None)
676+
ref_author_name = getattr(ref_author, "name", "Unknown") if ref_author else "Unknown"
677+
ref_content = getattr(ref_msg, "content", "")
678+
679+
if ref_content:
680+
if len(ref_content) > 500:
681+
ref_content = ref_content[:497] + "..."
682+
return f"**{ref_author_name}:** {ref_content}"
683+
elif getattr(ref_msg, "embeds", None):
684+
for embed in ref_msg.embeds:
685+
if hasattr(embed, "description") and embed.description:
686+
embed_desc = embed.description
687+
if len(embed_desc) > 300:
688+
embed_desc = embed_desc[:297] + "..."
689+
return f"**{ref_author_name}:** {embed_desc}"
690+
elif getattr(ref_msg, "attachments", None):
691+
attachment_info = ", ".join(
692+
[getattr(a, "filename", "Unknown") for a in ref_msg.attachments[:3]]
693+
)
694+
if len(ref_msg.attachments) > 3:
695+
attachment_info += f" (+{len(ref_msg.attachments) - 3} more)"
696+
return f"**{ref_author_name}:** [Attachments: {attachment_info}]"
697+
except Exception:
698+
pass
699+
except Exception:
700+
# Silently handle any unexpected errors
701+
pass
702+
703+
return None
704+
705+
611706
class DummyParam:
612707
"""
613708
A dummy parameter that can be used for MissingRequiredArgument.

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ charset-normalizer==3.3.2; python_full_version >= '3.7.0'
1414
colorama==0.4.6; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'
1515
cssselect2==0.7.0; python_version >= '3.7'
1616
defusedxml==0.7.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
17-
discord.py[speed]==2.5.2; python_full_version >= '3.8.0'
17+
discord.py[speed]==2.6.3; python_full_version >= '3.8.0'
1818
dnspython==2.4.2; python_version >= '3.8' and python_version < '4.0'
1919
emoji==2.8.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
2020
frozenlist==1.4.0; python_version >= '3.8'

0 commit comments

Comments
 (0)