Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9d4b8e3
update: dpy, snoozing.
lorenzo132 Jul 20, 2025
3cfadc2
remove: unneeded import
lorenzo132 Jul 20, 2025
0c73817
Formatting black
lorenzo132 Jul 20, 2025
6af9bcf
fix?: internal messages on restoration
lorenzo132 Jul 20, 2025
e19968c
formatting
lorenzo132 Jul 20, 2025
edc9daa
fix: internal messages.
lorenzo132 Jul 20, 2025
dc1c4af
fix: internals
lorenzo132 Jul 20, 2025
6164559
Update thread.py
lorenzo132 Jul 20, 2025
8fb8274
fix: use same logkey after restoration
lorenzo132 Jul 20, 2025
934f958
Add files via upload
lorenzo132 Jul 20, 2025
f21b9c0
Update thread.py
lorenzo132 Jul 20, 2025
bd7c4c2
Update thread.py
lorenzo132 Jul 20, 2025
97d0e8d
Update thread.py
lorenzo132 Jul 20, 2025
1a5a2bd
fix: show who send which internal message.
lorenzo132 Aug 23, 2025
f683023
Black formatting.
lorenzo132 Aug 23, 2025
5980f6a
Update Pipfile
lorenzo132 Aug 23, 2025
19ff839
Update Pipfile.lock
lorenzo132 Aug 23, 2025
021a7bd
fix: unsnooze bug
lorenzo132 Aug 24, 2025
dfdc0e7
feat: CV2
lorenzo132 Aug 24, 2025
d47a30e
update: black
lorenzo132 Aug 24, 2025
050c510
fix: duplicates in logs, notes.
lorenzo132 Sep 3, 2025
9452017
feat: dpy 2.6.3, forwarded messages, bug fixes.
lorenzo132 Sep 8, 2025
d4fe7ff
Merge pull request #2 from LorenzoModmailDev/development
lorenzo132 Sep 8, 2025
48e9df3
Fix jump_url not being displayed
martinbndr Sep 8, 2025
69c43fc
Update pipfile for new dpy version
martinbndr Sep 8, 2025
0223250
fix: bug in note title/color
lorenzo132 Sep 8, 2025
cd7b4cc
Update pipfile for new dpy version
lorenzo132 Sep 8, 2025
7953813
Update snooze arg
martinbndr Sep 22, 2025
fbc6c01
Merge pull request #4 from LorenzoModmailDev/development
lorenzo132 Sep 22, 2025
6015098
Update Pipfile to include tomli package
lorenzo132 Sep 22, 2025
2792032
auto detect dpy version
lorenzo132 Sep 22, 2025
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
Prev Previous commit
Next Next commit
feat: dpy 2.6.3, forwarded messages, bug fixes.
  • Loading branch information
lorenzo132 committed Sep 8, 2025
commit 945201706a12867d3f17e76e8beff4bffeee90a5
2 changes: 1 addition & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ typing-extensions = "==4.8.0"
[packages]
aiohttp = "==3.9.0"
colorama = "==0.4.6"
"discord.py" = {version = "==2.5.2", extras = ["speed"]}
"discord.py" = {version = "==2.6.3", extras = ["speed"]}
emoji = "==2.8.0"
isodate = "==0.6.1"
motor = "==3.3.2"
Expand Down
81 changes: 51 additions & 30 deletions bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,15 @@
)
from core.thread import ThreadManager
from core.time import human_timedelta
from core.utils import extract_block_timestamp, normalize_alias, parse_alias, truncate, tryint, human_join
from core.utils import (
extract_block_timestamp,
normalize_alias,
parse_alias,
truncate,
tryint,
human_join,
extract_forwarded_content,
)

logger = getLogger(__name__)

Expand Down Expand Up @@ -169,7 +177,7 @@ def startup(self):
logger.info("v%s", __version__)
logger.info("Authors: kyb3r, fourjr, Taaku18")
logger.line()
logger.info("discord.py: v2.5.2")
logger.info("discord.py: v2.6.3")
logger.line()

async def load_extensions(self):
Expand Down Expand Up @@ -926,32 +934,24 @@ async def process_dm_modmail(self, message: discord.Message) -> None:
logger.info("A message was blocked from %s due to disabled Modmail.", message.author)
await self.add_reaction(message, blocked_emoji)
return await message.channel.send(embed=embed)
for snap in message.message_snapshots:
author = getattr(snap, "author", None)
author_name = getattr(author, "name", "Unknown") if author else "Unknown"
author_avatar = getattr(author, "display_avatar", None)
author_avatar_url = author_avatar.url if author_avatar else None
# fix: Only use '[This is a forwarded message.]' if snap.content is actually empty
content = snap.content if snap.content else "[This is a forwarded message.]"
if snap.embeds:
content += "\n" + "\n".join(
[e.to_dict().get("description", "[embed]") for e in snap.embeds]
)
if snap.attachments:
content += "\n" + "\n".join([a.url for a in snap.attachments])

class DummySnap:
def __init__(self, snap, author, content):
self.author = author
self.content = content
self.attachments = getattr(snap, "attachments", [])
self.stickers = getattr(snap, "stickers", [])
self.created_at = getattr(snap, "created_at", message.created_at)
self.embeds = getattr(snap, "embeds", [])
self.id = getattr(snap, "id", 0)

dummy_msg = DummySnap(snap, author, content)
await thread.send(dummy_msg)
# Extract forwarded content using utility function
combined_content = extract_forwarded_content(message) or "[Forwarded message with no content]"

class ForwardedMessage:
def __init__(self, original_message, forwarded_content):
self.author = original_message.author
self.content = forwarded_content
self.attachments = []
self.stickers = []
self.created_at = original_message.created_at
self.embeds = []
self.id = original_message.id
self.flags = original_message.flags
self.message_snapshots = original_message.message_snapshots
self.type = getattr(original_message, "type", None)

forwarded_msg = ForwardedMessage(message, combined_content)
await thread.send(forwarded_msg)
await self.add_reaction(message, sent_emoji)
self.dispatch("thread_reply", thread, False, message, False, False)
return
Expand Down Expand Up @@ -1020,7 +1020,28 @@ def __init__(self, snap, author, content):
)
await self.add_reaction(message, blocked_emoji)
return await message.channel.send(embed=embed)
await thread.send(ref_msg)

# Create a forwarded message wrapper to preserve forward info
class ForwardedMessage:
def __init__(self, original_message, ref_message):
self.author = original_message.author
# Use the utility function to extract content or fallback to ref message content
extracted_content = extract_forwarded_content(original_message)
self.content = (
extracted_content
or ref_message.content
or "[Forwarded message with no text content]"
)
self.attachments = getattr(ref_message, "attachments", [])
self.stickers = getattr(ref_message, "stickers", [])
self.created_at = original_message.created_at
self.embeds = getattr(ref_message, "embeds", [])
self.id = original_message.id
self.type = getattr(original_message, "type", None)
self.reference = original_message.reference

forwarded_msg = ForwardedMessage(message, ref_msg)
await thread.send(forwarded_msg)
await self.add_reaction(message, sent_emoji)
self.dispatch("thread_reply", thread, False, message, False, False)
return
Expand Down Expand Up @@ -1990,7 +2011,7 @@ def main():
sys.exit(0)

# check discord version
discord_version = "2.5.2"
discord_version = "2.6.3"
if discord.__version__ != discord_version:
logger.error(
"Dependencies are not updated, run pipenv install. discord.py version expected %s, received %s",
Expand Down
68 changes: 48 additions & 20 deletions core/thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
DenyButton,
ConfirmThreadCreationView,
DummyParam,
extract_forwarded_content,
)

logger = getLogger(__name__)
Expand Down Expand Up @@ -1188,30 +1189,53 @@ async def send(
else:
avatar_url = author.display_avatar.url

# Extract content for blank/forwarded messages ---
content = message.content
if not content:
# Try to extract from referenced message (replies)
if hasattr(message, "reference") and message.reference is not None:
# Handle forwarded messages first
forwarded_jump_url = None
if hasattr(message, "message_snapshots") and len(message.message_snapshots) > 0:
snap = message.message_snapshots[0]
# Only show "No content" if there's truly no content (no text, attachments, embeds, or stickers)
if not snap.content and not message.attachments and not message.embeds and not message.stickers:
content = "No content"
else:
content = snap.content or ""

# Get jump_url from cached_message, fetch if not cached
if hasattr(snap, "cached_message") and snap.cached_message:
forwarded_jump_url = snap.cached_message.jump_url
elif hasattr(snap, "message") and snap.message:
# Try to fetch the original message to get the correct jump_url
try:
ref = message.reference.resolved
if ref and hasattr(ref, "content") and ref.content:
content = f"(Reply to: {ref.author}: {ref.content})"
except Exception:
original_msg = await snap.message.channel.fetch_message(snap.message.id)
forwarded_jump_url = original_msg.jump_url
except (discord.NotFound, discord.Forbidden, AttributeError):
# If we can't fetch the message, we'll proceed without the jump_url
pass
# Try to extract from first embed's description
if not content and message.embeds:
first_embed = message.embeds[0]
if hasattr(first_embed, "description") and first_embed.description:
content = first_embed.description
# Fallback: show something generic if still blank
if not content:
content = "[This is a forwarded message.]"

embed = discord.Embed(description=content)

content = f"📨 **Forwarded message:**\n{content}" if content else "📨 **Forwarded message:**"
else:
# Only show "No content" if there's truly no content (no text, attachments, embeds, or stickers)
if (
not message.content
and not message.attachments
and not message.embeds
and not message.stickers
):
content = "No content"
else:
content = message.content or ""

# Only set description if there's actual content to show
if content:
embed = discord.Embed(description=content)
else:
embed = discord.Embed()
if self.bot.config["show_timestamp"]:
embed.timestamp = message.created_at

# Add forwarded message context
if forwarded_jump_url:
embed.add_field(name="Context", value=f"- {forwarded_jump_url}", inline=True)

system_avatar_url = "https://discordapp.com/assets/f78426a064bc9dd24847519259bc42af.png"

if not note:
Expand Down Expand Up @@ -1399,7 +1423,11 @@ def lottie_to_png(data):
else:
embed.set_footer(text=self.bot.config["anon_tag"])
else:
embed.set_footer(text=f"Message ID: {message.id}")
# Add forwarded message indicator in footer for mods
footer_text = f"Message ID: {message.id}"
if hasattr(message, "message_snapshots") and len(message.message_snapshots) > 0:
footer_text += " • Forwarded"
embed.set_footer(text=footer_text)
embed.colour = self.bot.recipient_color

if (from_mod or note) and not thread_creation:
Expand Down
95 changes: 95 additions & 0 deletions core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"DenyButton",
"ConfirmThreadCreationView",
"DummyParam",
"extract_forwarded_content",
]


Expand Down Expand Up @@ -608,6 +609,100 @@ def __init__(self):
self.value = None


def extract_forwarded_content(message) -> typing.Optional[str]:
"""
Extract forwarded message content from Discord forwarded messages.

Parameters
----------
message : discord.Message
The message to extract forwarded content from.

Returns
-------
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)

# Handle single-message forward
elif getattr(message, "type", None) == getattr(discord.MessageType, "forward", None):
ref = getattr(message, "reference", None)
if (
ref
and hasattr(discord, "MessageReferenceType")
and getattr(ref, "type", None) == getattr(discord.MessageReferenceType, "forward", None)
):
try:
ref_msg = getattr(ref, "resolved", None)
if ref_msg:
ref_author = getattr(ref_msg, "author", None)
ref_author_name = getattr(ref_author, "name", "Unknown") if ref_author else "Unknown"
ref_content = getattr(ref_msg, "content", "")

if ref_content:
if len(ref_content) > 500:
ref_content = ref_content[:497] + "..."
return f"**{ref_author_name}:** {ref_content}"
elif getattr(ref_msg, "embeds", None):
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]]
)
if len(ref_msg.attachments) > 3:
attachment_info += f" (+{len(ref_msg.attachments) - 3} more)"
return f"**{ref_author_name}:** [Attachments: {attachment_info}]"
except Exception:
pass
except Exception:
# Silently handle any unexpected errors
pass

return None


class DummyParam:
"""
A dummy parameter that can be used for MissingRequiredArgument.
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ charset-normalizer==3.3.2; python_full_version >= '3.7.0'
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'
cssselect2==0.7.0; python_version >= '3.7'
defusedxml==0.7.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
discord.py[speed]==2.5.2; python_full_version >= '3.8.0'
discord.py[speed]==2.6.3; python_full_version >= '3.8.0'
dnspython==2.4.2; python_version >= '3.8' and python_version < '4.0'
emoji==2.8.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
frozenlist==1.4.0; python_version >= '3.8'
Expand Down
Loading