Skip to main content

cortexfs_channels/platform/
feishu.rs

1use serde_json::{Value, json};
2
3use super::{ChannelCodec, OutboundRequest, object, participant, scalar, string};
4use crate::{
5    ChannelError, ChannelId, ConversationId, InboundMessage, MessageBody, MessageTarget,
6    OutboundMessage,
7};
8
9/// Feishu/Lark event and `im.v1.message.create` codec.
10#[derive(Clone, Copy, Debug, Default)]
11pub struct FeishuCodec;
12
13impl ChannelCodec for FeishuCodec {
14    fn channel(&self) -> ChannelId {
15        ChannelId::from_static("feishu")
16    }
17
18    fn decode(&self, payload: &str) -> Result<Option<InboundMessage>, ChannelError> {
19        let root = object(payload)?;
20        let event = root.get("event").unwrap_or(&root);
21        let message = event
22            .get("message")
23            .ok_or_else(|| ChannelError::Protocol("feishu message is missing".to_owned()))?;
24        let body = message
25            .get("content")
26            .and_then(Value::as_str)
27            .ok_or_else(|| {
28                ChannelError::Protocol("feishu message.content is missing".to_owned())
29            })?;
30        let body: Value = serde_json::from_str(body)
31            .map_err(|error| ChannelError::Protocol(format!("invalid feishu content: {error}")))?;
32        let sender = event.get("sender").and_then(|value| value.get("sender_id"));
33        let sender_id = sender
34            .and_then(|value| value.get("open_id").or_else(|| value.get("user_id")))
35            .map(|value| scalar(Some(value), "sender.sender_id"))
36            .transpose()?
37            .unwrap_or_else(|| "unknown".to_owned());
38        let conversation = ConversationId::new(string(message.get("chat_id"), "message.chat_id")?)?;
39        Ok(Some(InboundMessage {
40            id: string(message.get("message_id"), "message.message_id")?,
41            target: MessageTarget {
42                channel: self.channel(),
43                conversation,
44                thread: message
45                    .get("root_id")
46                    .map(|value| string(Some(value), "message.root_id"))
47                    .transpose()?,
48                reply_to: None,
49            },
50            sender: participant(None, sender_id),
51            body: MessageBody::text(string(body.get("text"), "content.text")?)?,
52            timestamp_ms: None,
53            metadata: std::collections::BTreeMap::new(),
54        }))
55    }
56
57    fn encode(&self, message: &OutboundMessage) -> Result<OutboundRequest, ChannelError> {
58        message.body.validate()?;
59        if !message.body.attachments.is_empty() {
60            return Err(ChannelError::Unsupported(
61                "feishu media attachments".to_owned(),
62            ));
63        }
64        let body = json!({
65            "receive_id": message.target.conversation.as_str(),
66            "msg_type": "text",
67            "content": json!({ "text": message.body.text }).to_string(),
68        });
69        Ok(OutboundRequest {
70            method: "POST".to_owned(),
71            path: "im/v1/messages".to_owned(),
72            content_type: "application/json".to_owned(),
73            body: body.to_string(),
74            headers: std::collections::BTreeMap::new(),
75        })
76    }
77}