@@ -169,7 +169,7 @@ def startup(self):
169169 logger .info ("v%s" , __version__ )
170170 logger .info ("Authors: kyb3r, fourjr, Taaku18" )
171171 logger .line ()
172- logger .info ("discord.py: v%s" , discord . __version__ )
172+ logger .info ("discord.py: v2.5.2" )
173173 logger .line ()
174174
175175 async def load_extensions (self ):
@@ -879,10 +879,172 @@ async def process_dm_modmail(self, message: discord.Message) -> None:
879879 return
880880 sent_emoji , blocked_emoji = await self .retrieve_emoji ()
881881
882+ # Handle forwarded messages (Discord forwards)
883+ # See: https://discord.com/developers/docs/resources/message#message-reference-content-attribution-forwards
884+ import discord
885+
886+ # 1. Multi-forward (message_snapshots)
887+ if hasattr (message , "flags" ) and getattr (message .flags , "has_snapshot" , False ):
888+ if hasattr (message , "message_snapshots" ) and message .message_snapshots :
889+ thread = await self .threads .find (recipient = message .author )
890+ if thread is None :
891+ delta = await self .get_thread_cooldown (message .author )
892+ if delta :
893+ await message .channel .send (
894+ embed = discord .Embed (
895+ title = self .config ["cooldown_thread_title" ],
896+ description = self .config ["cooldown_thread_response" ].format (delta = delta ),
897+ color = self .error_color ,
898+ )
899+ )
900+ return
901+ if self .config ["dm_disabled" ] in (DMDisabled .NEW_THREADS , DMDisabled .ALL_THREADS ):
902+ embed = discord .Embed (
903+ title = self .config ["disabled_new_thread_title" ],
904+ color = self .error_color ,
905+ description = self .config ["disabled_new_thread_response" ],
906+ )
907+ embed .set_footer (
908+ text = self .config ["disabled_new_thread_footer" ],
909+ icon_url = self .get_guild_icon (guild = message .guild , size = 128 ),
910+ )
911+ logger .info (
912+ "A new thread was blocked from %s due to disabled Modmail." , message .author
913+ )
914+ await self .add_reaction (message , blocked_emoji )
915+ return await message .channel .send (embed = embed )
916+ thread = await self .threads .create (message .author , message = message )
917+ else :
918+ if self .config ["dm_disabled" ] == DMDisabled .ALL_THREADS :
919+ embed = discord .Embed (
920+ title = self .config ["disabled_current_thread_title" ],
921+ color = self .error_color ,
922+ description = self .config ["disabled_current_thread_response" ],
923+ )
924+ embed .set_footer (
925+ text = self .config ["disabled_current_thread_footer" ],
926+ icon_url = self .get_guild_icon (guild = message .guild , size = 128 ),
927+ )
928+ logger .info ("A message was blocked from %s due to disabled Modmail." , message .author )
929+ await self .add_reaction (message , blocked_emoji )
930+ return await message .channel .send (embed = embed )
931+ for snap in message .message_snapshots :
932+ author = getattr (snap , "author" , None )
933+ author_name = getattr (author , "name" , "Unknown" ) if author else "Unknown"
934+ author_avatar = getattr (author , "display_avatar" , None )
935+ author_avatar_url = author_avatar .url if author_avatar else None
936+ # fix: Only use '[This is a forwarded message.]' if snap.content is actually empty
937+ content = snap .content if snap .content else "[This is a forwarded message.]"
938+ if snap .embeds :
939+ content += "\n " + "\n " .join (
940+ [e .to_dict ().get ("description" , "[embed]" ) for e in snap .embeds ]
941+ )
942+ if snap .attachments :
943+ content += "\n " + "\n " .join ([a .url for a in snap .attachments ])
944+
945+ class DummySnap :
946+ def __init__ (self , snap , author , content ):
947+ self .author = author
948+ self .content = content
949+ self .attachments = getattr (snap , "attachments" , [])
950+ self .stickers = getattr (snap , "stickers" , [])
951+ self .created_at = getattr (snap , "created_at" , message .created_at )
952+ self .embeds = getattr (snap , "embeds" , [])
953+ self .id = getattr (snap , "id" , 0 )
954+
955+ dummy_msg = DummySnap (snap , author , content )
956+ await thread .send (dummy_msg )
957+ await self .add_reaction (message , sent_emoji )
958+ self .dispatch ("thread_reply" , thread , False , message , False , False )
959+ return
960+ else :
961+ message .content = "[Forwarded message with no content]"
962+ # 2. Single-message forward (MessageType.forward)
963+ elif getattr (message , "type" , None ) == getattr (discord .MessageType , "forward" , None ):
964+ # Check for message.reference and its type
965+ ref = getattr (message , "reference" , None )
966+ if ref and getattr (ref , "type" , None ) == getattr (discord , "MessageReferenceType" , None ).forward :
967+ # Try to fetch the referenced message
968+ ref_msg = None
969+ try :
970+ if ref .resolved :
971+ ref_msg = ref .resolved
972+ elif ref .message_id and ref .channel_id :
973+ channel = self .get_channel (ref .channel_id ) or (
974+ await self .fetch_channel (ref .channel_id )
975+ )
976+ ref_msg = await channel .fetch_message (ref .message_id )
977+ except Exception :
978+ ref_msg = None
979+ if ref_msg :
980+ # Forward the referenced message as if it was sent
981+ thread = await self .threads .find (recipient = message .author )
982+ if thread is None :
983+ delta = await self .get_thread_cooldown (message .author )
984+ if delta :
985+ await message .channel .send (
986+ embed = discord .Embed (
987+ title = self .config ["cooldown_thread_title" ],
988+ description = self .config ["cooldown_thread_response" ].format (delta = delta ),
989+ color = self .error_color ,
990+ )
991+ )
992+ return
993+ if self .config ["dm_disabled" ] in (DMDisabled .NEW_THREADS , DMDisabled .ALL_THREADS ):
994+ embed = discord .Embed (
995+ title = self .config ["disabled_new_thread_title" ],
996+ color = self .error_color ,
997+ description = self .config ["disabled_new_thread_response" ],
998+ )
999+ embed .set_footer (
1000+ text = self .config ["disabled_new_thread_footer" ],
1001+ icon_url = self .get_guild_icon (guild = message .guild , size = 128 ),
1002+ )
1003+ logger .info (
1004+ "A new thread was blocked from %s due to disabled Modmail." , message .author
1005+ )
1006+ await self .add_reaction (message , blocked_emoji )
1007+ return await message .channel .send (embed = embed )
1008+ thread = await self .threads .create (message .author , message = message )
1009+ else :
1010+ if self .config ["dm_disabled" ] == DMDisabled .ALL_THREADS :
1011+ embed = discord .Embed (
1012+ title = self .config ["disabled_current_thread_title" ],
1013+ color = self .error_color ,
1014+ description = self .config ["disabled_current_thread_response" ],
1015+ )
1016+ embed .set_footer (
1017+ text = self .config ["disabled_current_thread_footer" ],
1018+ icon_url = self .get_guild_icon (guild = message .guild , size = 128 ),
1019+ )
1020+ logger .info (
1021+ "A message was blocked from %s due to disabled Modmail." , message .author
1022+ )
1023+ await self .add_reaction (message , blocked_emoji )
1024+ return await message .channel .send (embed = embed )
1025+ await thread .send (ref_msg )
1026+ await self .add_reaction (message , sent_emoji )
1027+ self .dispatch ("thread_reply" , thread , False , message , False , False )
1028+ return
1029+ else :
1030+ message .content = "[Forwarded message with no content]"
1031+
8821032 if message .type not in [discord .MessageType .default , discord .MessageType .reply ]:
8831033 return
8841034
8851035 thread = await self .threads .find (recipient = message .author )
1036+ if thread and thread .snoozed :
1037+ await thread .restore_from_snooze ()
1038+ self .threads .cache [thread .id ] = thread
1039+ # Update the DB with the new channel_id after restoration
1040+ if thread .channel :
1041+ await self .api .logs .update_one (
1042+ {"recipient.id" : str (thread .id )},
1043+ {"$set" : {"channel_id" : str (thread .channel .id )}}
1044+ )
1045+ # Re-fetch the thread object to ensure channel is valid
1046+ thread = await self .threads .find (recipient = message .author )
1047+
8861048 if thread is None :
8871049 delta = await self .get_thread_cooldown (message .author )
8881050 if delta :
@@ -1820,7 +1982,7 @@ def main():
18201982 sys .exit (0 )
18211983
18221984 # check discord version
1823- discord_version = "2.3 .2"
1985+ discord_version = "2.5 .2"
18241986 if discord .__version__ != discord_version :
18251987 logger .error (
18261988 "Dependencies are not updated, run pipenv install. discord.py version expected %s, received %s" ,
0 commit comments