@@ -532,6 +532,9 @@ async def _on_message(self, data: "P2ImMessageReceiveV1") -> None:
532532 text = extract_json_key (content_raw , "text" )
533533 if text :
534534 text_parts .append (text )
535+ elif msg_type == "post" :
536+ # Handle rich text post messages
537+ text_parts .extend (self ._extract_text_from_post (content_raw ))
535538 elif msg_type == "image" :
536539 image_key = extract_json_key (
537540 content_raw ,
@@ -695,6 +698,79 @@ def _add_reaction_sync(self, message_id: str, emoji_type: str) -> None:
695698 except Exception as e :
696699 logger .debug ("feishu reaction error: %s" , e )
697700
701+ def _extract_text_from_post (
702+ self ,
703+ content_raw : str ,
704+ ) -> List [str ]: # pylint: disable=too-many-nested-blocks
705+ """Extract text content from Feishu post message format.
706+
707+ Post format example:
708+ {
709+ "zh_cn": {
710+ "title": "...",
711+ "content": [[{"tag": "text", "text": "..."}]]
712+ }
713+ }
714+
715+ Returns list of text parts extracted from the post.
716+ """
717+ # pylint: disable=too-many-nested-blocks
718+ try :
719+ post_content = json .loads (content_raw )
720+ if not isinstance (post_content , dict ):
721+ return ["[post: invalid content format]" ]
722+
723+ post_text_parts = []
724+
725+ # Get content from any language key (zh_cn, en_us, etc.)
726+ for lang_content in post_content .values ():
727+ if not isinstance (lang_content , dict ):
728+ continue
729+ if "content" not in lang_content :
730+ continue
731+
732+ content_rows = lang_content ["content" ]
733+ if not isinstance (content_rows , list ):
734+ continue
735+
736+ for row in content_rows :
737+ if not isinstance (row , list ):
738+ continue
739+ for element in row :
740+ if not isinstance (element , dict ):
741+ continue
742+
743+ # Handle different tag types
744+ tag = element .get ("tag" )
745+ if tag == "text" :
746+ post_text_parts .append (element .get ("text" , "" ))
747+ elif tag == "md" :
748+ post_text_parts .append (element .get ("text" , "" ))
749+ elif tag == "at" :
750+ # Handle @ mentions
751+ user_name = element .get ("user_name" , "" )
752+ if user_name :
753+ post_text_parts .append (f"@{ user_name } " )
754+ # Add more tag types as needed
755+
756+ if post_text_parts :
757+ return ["\n " .join (post_text_parts )]
758+ else :
759+ return ["[post: no text content]" ]
760+
761+ except (json .JSONDecodeError , TypeError , KeyError ) as e :
762+ # Truncate content for logging to avoid excessive log length
763+ if isinstance (content_raw , str ):
764+ truncated_content = content_raw [:100 ]
765+ else :
766+ truncated_content = str (content_raw )[:100 ]
767+ logger .debug (
768+ "feishu post message parsing failed: %s, content=%s" ,
769+ e ,
770+ truncated_content ,
771+ )
772+ return ["[post: parse error]" ]
773+
698774 async def _download_image_resource (
699775 self ,
700776 message_id : str ,
@@ -1411,10 +1487,14 @@ async def send_content_parts(
14111487 media_parts : List [OutgoingContentPart ] = []
14121488 for p in parts :
14131489 t = getattr (p , "type" , None )
1414- if t == ContentType .TEXT and getattr (p , "text" , None ):
1415- text_parts .append (p .text or "" )
1416- elif t == ContentType .REFUSAL and getattr (p , "refusal" , None ):
1417- text_parts .append (p .refusal or "" )
1490+ if t == ContentType .TEXT :
1491+ text_val = getattr (p , "text" , None )
1492+ if text_val :
1493+ text_parts .append (text_val )
1494+ elif t == ContentType .REFUSAL :
1495+ refusal_val = getattr (p , "refusal" , None )
1496+ if refusal_val :
1497+ text_parts .append (refusal_val )
14181498 elif t in (
14191499 ContentType .IMAGE ,
14201500 ContentType .FILE ,
0 commit comments