mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-27 16:04:21 +00:00
fix: add filereader for dingtalk,lark (#2122)
* fix: add filereader for dingtalk * feat: add lark
This commit is contained in:
@@ -182,6 +182,88 @@ class DingTalkClient:
|
|||||||
for handler in self._message_handlers[msg_type]:
|
for handler in self._message_handlers[msg_type]:
|
||||||
await handler(event)
|
await handler(event)
|
||||||
|
|
||||||
|
async def _parse_quoted_message(self, replied_msg: dict) -> dict:
|
||||||
|
"""Parse the quoted/replied message and extract its content.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
replied_msg: The repliedMsg object from DingTalk message
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A dict containing the quoted message info with keys:
|
||||||
|
- message_id: The original message ID
|
||||||
|
- msg_type: The message type (text, file, picture, audio, etc.)
|
||||||
|
- content: The text content (if any)
|
||||||
|
- file_url: The file download URL (if file type)
|
||||||
|
- file_name: The file name (if file type)
|
||||||
|
- picture: The picture base64 (if picture type)
|
||||||
|
- audio: The audio base64 (if audio type)
|
||||||
|
"""
|
||||||
|
quote_info = {
|
||||||
|
'message_id': replied_msg.get('msgId', ''),
|
||||||
|
'msg_type': replied_msg.get('msgType', ''),
|
||||||
|
'sender_id': replied_msg.get('senderId', ''),
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_type = replied_msg.get('msgType', '')
|
||||||
|
content = replied_msg.get('content', {})
|
||||||
|
|
||||||
|
# Handle content as string (JSON) or dict
|
||||||
|
if isinstance(content, str):
|
||||||
|
try:
|
||||||
|
content = json.loads(content)
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
content = {}
|
||||||
|
|
||||||
|
if msg_type == 'text':
|
||||||
|
# Text message
|
||||||
|
if isinstance(content, dict):
|
||||||
|
quote_info['content'] = content.get('content', '')
|
||||||
|
else:
|
||||||
|
quote_info['content'] = str(content)
|
||||||
|
|
||||||
|
elif msg_type == 'file':
|
||||||
|
# File message
|
||||||
|
download_code = content.get('downloadCode')
|
||||||
|
file_name = content.get('fileName')
|
||||||
|
if download_code and file_name:
|
||||||
|
try:
|
||||||
|
quote_info['file_url'] = await self.get_file_url(download_code)
|
||||||
|
quote_info['file_name'] = file_name
|
||||||
|
except Exception as e:
|
||||||
|
if self.logger:
|
||||||
|
await self.logger.error(f'Failed to get quoted file URL: {e}')
|
||||||
|
|
||||||
|
elif msg_type == 'picture':
|
||||||
|
# Picture message
|
||||||
|
download_code = content.get('downloadCode')
|
||||||
|
if download_code:
|
||||||
|
try:
|
||||||
|
quote_info['picture'] = await self.download_image(download_code)
|
||||||
|
except Exception as e:
|
||||||
|
if self.logger:
|
||||||
|
await self.logger.error(f'Failed to download quoted image: {e}')
|
||||||
|
|
||||||
|
elif msg_type == 'audio':
|
||||||
|
# Audio message
|
||||||
|
download_code = content.get('downloadCode')
|
||||||
|
if download_code:
|
||||||
|
try:
|
||||||
|
quote_info['audio'] = await self.get_audio_url(download_code)
|
||||||
|
except Exception as e:
|
||||||
|
if self.logger:
|
||||||
|
await self.logger.error(f'Failed to get quoted audio: {e}')
|
||||||
|
|
||||||
|
elif msg_type == 'richText':
|
||||||
|
# Rich text message - extract text content
|
||||||
|
rich_text = content.get('richText', [])
|
||||||
|
texts = []
|
||||||
|
for item in rich_text:
|
||||||
|
if 'text' in item and item['text'] != '\n':
|
||||||
|
texts.append(item['text'])
|
||||||
|
quote_info['content'] = '\n'.join(texts)
|
||||||
|
|
||||||
|
return quote_info
|
||||||
|
|
||||||
async def get_message(self, incoming_message: dingtalk_stream.chatbot.ChatbotMessage):
|
async def get_message(self, incoming_message: dingtalk_stream.chatbot.ChatbotMessage):
|
||||||
try:
|
try:
|
||||||
# print(json.dumps(incoming_message.to_dict(), indent=4, ensure_ascii=False))
|
# print(json.dumps(incoming_message.to_dict(), indent=4, ensure_ascii=False))
|
||||||
@@ -193,6 +275,15 @@ class DingTalkClient:
|
|||||||
elif str(incoming_message.conversation_type) == '2':
|
elif str(incoming_message.conversation_type) == '2':
|
||||||
message_data['conversation_type'] = 'GroupMessage'
|
message_data['conversation_type'] = 'GroupMessage'
|
||||||
|
|
||||||
|
# Check for quoted/replied message
|
||||||
|
raw_data = incoming_message.to_dict()
|
||||||
|
text_data = raw_data.get('text', {})
|
||||||
|
if isinstance(text_data, dict) and text_data.get('isReplyMsg'):
|
||||||
|
replied_msg = text_data.get('repliedMsg', {})
|
||||||
|
if replied_msg:
|
||||||
|
quote_info = await self._parse_quoted_message(replied_msg)
|
||||||
|
message_data['QuotedMessage'] = quote_info
|
||||||
|
|
||||||
if incoming_message.message_type == 'richText':
|
if incoming_message.message_type == 'richText':
|
||||||
data = incoming_message.rich_text_content.to_dict()
|
data = incoming_message.rich_text_content.to_dict()
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,22 @@ class DingTalkEvent(dict):
|
|||||||
def conversation(self):
|
def conversation(self):
|
||||||
return self.get('conversation_type', '')
|
return self.get('conversation_type', '')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def quoted_message(self) -> Optional[Dict[str, Any]]:
|
||||||
|
"""Get the quoted/replied message info if this is a reply message.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A dict containing:
|
||||||
|
- message_id: The original message ID
|
||||||
|
- msg_type: The message type (text, file, picture, audio, etc.)
|
||||||
|
- content: The text content (if any)
|
||||||
|
- file_url: The file download URL (if file type)
|
||||||
|
- file_name: The file name (if file type)
|
||||||
|
- picture: The picture base64 (if picture type)
|
||||||
|
- audio: The audio base64 (if audio type)
|
||||||
|
"""
|
||||||
|
return self.get('QuotedMessage')
|
||||||
|
|
||||||
def __getattr__(self, key: str) -> Optional[Any]:
|
def __getattr__(self, key: str) -> Optional[Any]:
|
||||||
"""
|
"""
|
||||||
允许通过属性访问数据中的任意字段。
|
允许通过属性访问数据中的任意字段。
|
||||||
|
|||||||
@@ -88,6 +88,33 @@ class DingTalkMessageConverter(abstract_platform_adapter.AbstractMessageConverte
|
|||||||
else:
|
else:
|
||||||
yiri_msg_list.append(platform_message.Voice(base64=event.audio))
|
yiri_msg_list.append(platform_message.Voice(base64=event.audio))
|
||||||
|
|
||||||
|
# Handle quoted/replied message - extract content as top-level components
|
||||||
|
# so that plugins like FileReader can process them the same way as direct messages
|
||||||
|
if event.quoted_message:
|
||||||
|
quote_info = event.quoted_message
|
||||||
|
msg_type = quote_info.get('msg_type', '')
|
||||||
|
|
||||||
|
# Process quoted file - add as top-level File component (same as private chat)
|
||||||
|
if msg_type == 'file' and quote_info.get('file_url'):
|
||||||
|
file_name = quote_info.get('file_name', 'file')
|
||||||
|
yiri_msg_list.append(platform_message.File(url=quote_info['file_url'], name=file_name))
|
||||||
|
|
||||||
|
# Process quoted image - add as top-level Image component
|
||||||
|
elif msg_type == 'picture' and quote_info.get('picture'):
|
||||||
|
yiri_msg_list.append(platform_message.Image(base64=quote_info['picture']))
|
||||||
|
|
||||||
|
# Process quoted audio - add as top-level Voice component
|
||||||
|
elif msg_type == 'audio' and quote_info.get('audio'):
|
||||||
|
yiri_msg_list.append(platform_message.Voice(base64=quote_info['audio']))
|
||||||
|
|
||||||
|
# Process quoted text - add as Plain text with context prefix
|
||||||
|
elif msg_type == 'text' and quote_info.get('content'):
|
||||||
|
yiri_msg_list.append(platform_message.Plain(text=f'[引用消息] {quote_info["content"]}'))
|
||||||
|
|
||||||
|
# Process quoted rich text - add as Plain text with context prefix
|
||||||
|
elif msg_type == 'richText' and quote_info.get('content'):
|
||||||
|
yiri_msg_list.append(platform_message.Plain(text=f'[引用消息] {quote_info["content"]}'))
|
||||||
|
|
||||||
chain = platform_message.MessageChain(yiri_msg_list)
|
chain = platform_message.MessageChain(yiri_msg_list)
|
||||||
|
|
||||||
return chain
|
return chain
|
||||||
|
|||||||
@@ -709,21 +709,29 @@ class LarkEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
|||||||
message_chain = await LarkMessageConverter.target2yiri(event.event.message, api_client)
|
message_chain = await LarkMessageConverter.target2yiri(event.event.message, api_client)
|
||||||
|
|
||||||
# Check for quote/reply message
|
# Check for quote/reply message
|
||||||
|
# Extract files/images/voice from quote and add them as top-level components
|
||||||
|
# so that plugins like FileReader can process them the same way as direct messages
|
||||||
quote_message_id = LarkEventConverter._extract_quote_message_id(event.event.message)
|
quote_message_id = LarkEventConverter._extract_quote_message_id(event.event.message)
|
||||||
if quote_message_id:
|
if quote_message_id:
|
||||||
quote_chain = await LarkEventConverter._fetch_quoted_message(quote_message_id, api_client)
|
quote_chain = await LarkEventConverter._fetch_quoted_message(quote_message_id, api_client)
|
||||||
if quote_chain:
|
if quote_chain:
|
||||||
# Filter out Source component from quoted chain, keep only content
|
# Filter out Source component from quoted chain, keep only content
|
||||||
quote_origin = platform_message.MessageChain(
|
quote_components = [comp for comp in quote_chain if not isinstance(comp, platform_message.Source)]
|
||||||
[comp for comp in quote_chain if not isinstance(comp, platform_message.Source)]
|
|
||||||
)
|
# Add quoted content as top-level components instead of wrapping in Quote
|
||||||
if quote_origin:
|
for comp in quote_components:
|
||||||
message_chain.append(
|
if isinstance(comp, platform_message.File):
|
||||||
platform_message.Quote(
|
# Add file as top-level component (same as direct message)
|
||||||
message_id=quote_message_id,
|
message_chain.append(comp)
|
||||||
origin=quote_origin,
|
elif isinstance(comp, platform_message.Image):
|
||||||
)
|
# Add image as top-level component
|
||||||
)
|
message_chain.append(comp)
|
||||||
|
elif isinstance(comp, platform_message.Voice):
|
||||||
|
# Add voice as top-level component
|
||||||
|
message_chain.append(comp)
|
||||||
|
elif isinstance(comp, platform_message.Plain):
|
||||||
|
# Add text with context prefix
|
||||||
|
message_chain.append(platform_message.Plain(text=f'[引用消息] {comp.text}'))
|
||||||
|
|
||||||
if event.event.message.chat_type == 'p2p':
|
if event.event.message.chat_type == 'p2p':
|
||||||
return platform_events.FriendMessage(
|
return platform_events.FriendMessage(
|
||||||
|
|||||||
Reference in New Issue
Block a user