1use serde::{Deserialize, Deserializer, Serialize};
2use serde_json::Value;
3
4use crate::card::CardId;
5use crate::media::ResourceDescriptor;
6
7mod post;
8mod sender;
9mod streaming;
10
11pub use post::{PostContent, PostContentBuilder, PostDocument, PostElement, PostStyle};
12pub use sender::{MessageBuilder, MessageReplyBuilder, MessageSender, MessageSenderOptions};
13pub use streaming::{
14 ContinuingMarkdownStream, MarkdownStream, MarkdownStreamBuilder, MarkdownStreamPage,
15 ThrottledMarkdownStream,
16};
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct MessageId(pub String);
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(tag = "type", content = "id", rename_all = "snake_case")]
23pub enum Recipient {
24 Chat(String),
25 User(String),
26}
27
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38#[serde(tag = "type", rename_all = "snake_case")]
39#[non_exhaustive]
40pub enum MessageContent {
41 Text { text: String },
42 Post { post: PostContent },
43 Card { card: Value },
44 CardReference { card_id: CardId },
45 Custom { msg_type: String, content: Value },
46}
47
48#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
49#[serde(rename_all = "snake_case")]
50pub enum MessageChatType {
51 P2p,
52 Group,
53 #[default]
54 Unknown,
55}
56
57#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
58#[serde(rename_all = "snake_case")]
59pub enum MessageSenderType {
60 User,
61 Bot,
62 #[default]
63 Unknown,
64}
65
66#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
67pub struct MessageSenderInfo {
68 #[serde(default)]
69 pub open_id: String,
70 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub user_id: Option<String>,
72 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub union_id: Option<String>,
74 #[serde(default)]
75 pub sender_type: MessageSenderType,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
79pub struct MessageMention {
80 #[serde(default)]
81 pub key: String,
82 #[serde(default)]
83 pub open_id: String,
84 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub user_id: Option<String>,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
87 pub union_id: Option<String>,
88 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub name: Option<String>,
90 #[serde(default)]
91 pub mentioned_type: MessageSenderType,
92}
93
94#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95pub struct NormalizedMessage {
96 pub message_id: String,
97 pub chat_id: String,
98 #[serde(default)]
99 pub chat_type: MessageChatType,
100 pub sender_id: String,
101 #[serde(default)]
102 pub sender: MessageSenderInfo,
103 #[serde(default)]
104 pub message_type: String,
105 #[serde(default)]
106 pub text: String,
107 #[serde(default, skip_serializing_if = "String::is_empty")]
108 pub raw_content: String,
109 #[serde(default, skip_serializing_if = "Option::is_none")]
110 pub content: Option<Value>,
111 #[serde(default, skip_serializing_if = "Option::is_none")]
112 pub root_id: Option<String>,
113 #[serde(default, skip_serializing_if = "Option::is_none")]
114 pub parent_id: Option<String>,
115 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub thread_id: Option<String>,
117 #[serde(default, deserialize_with = "deserialize_mentions")]
118 pub mentions: Vec<MessageMention>,
119 #[serde(default, skip_serializing_if = "Vec::is_empty")]
120 pub resources: Vec<ResourceDescriptor>,
121 #[serde(default)]
122 pub raw: Value,
123}
124
125impl NormalizedMessage {
126 pub fn mentions_bot(&self, bot_open_id: &str) -> bool {
127 if bot_open_id.is_empty() {
128 return false;
129 }
130
131 self.mentions
132 .iter()
133 .any(|mention| mention.open_id == bot_open_id)
134 }
135}
136
137fn deserialize_mentions<'de, D>(deserializer: D) -> Result<Vec<MessageMention>, D::Error>
138where
139 D: Deserializer<'de>,
140{
141 #[derive(Deserialize)]
142 #[serde(untagged)]
143 enum MentionValue {
144 Structured(MessageMention),
145 LegacyOpenId(String),
146 }
147
148 Vec::<MentionValue>::deserialize(deserializer).map(|mentions| {
149 mentions
150 .into_iter()
151 .map(|mention| match mention {
152 MentionValue::Structured(mention) => mention,
153 MentionValue::LegacyOpenId(open_id) => MessageMention {
154 key: String::new(),
155 open_id,
156 user_id: None,
157 union_id: None,
158 name: None,
159 mentioned_type: MessageSenderType::Unknown,
160 },
161 })
162 .collect()
163 })
164}
165
166#[cfg(test)]
167mod tests {
168 use serde_json::json;
169
170 use super::*;
171
172 #[test]
173 fn deserializes_older_normalized_message_json_with_defaults() {
174 let value = json!({
175 "message_id": "om_1",
176 "chat_id": "oc_1",
177 "sender_id": "ou_sender",
178 "text": "hello",
179 "mentions": ["ou_bot"]
180 });
181
182 let message: NormalizedMessage = serde_json::from_value(value).expect("message");
183
184 assert_eq!(message.chat_type, MessageChatType::Unknown);
185 assert_eq!(message.sender, MessageSenderInfo::default());
186 assert_eq!(message.message_type, "");
187 assert_eq!(message.raw_content, "");
188 assert_eq!(message.content, None);
189 assert_eq!(message.mentions.len(), 1);
190 assert!(message.resources.is_empty());
191 assert_eq!(message.mentions[0].open_id, "ou_bot");
192 assert_eq!(
193 message.mentions[0].mentioned_type,
194 MessageSenderType::Unknown
195 );
196 assert!(message.mentions_bot("ou_bot"));
197 }
198
199 #[test]
200 fn serializes_absent_mention_name_without_null_field() {
201 let mention = MessageMention {
202 key: "@_user_1".to_owned(),
203 open_id: "ou_bot".to_owned(),
204 user_id: None,
205 union_id: None,
206 name: None,
207 mentioned_type: MessageSenderType::Unknown,
208 };
209
210 let value = serde_json::to_value(mention).expect("mention");
211
212 assert_eq!(value["open_id"], "ou_bot");
213 assert!(value.get("user_id").is_none());
214 assert!(value.get("union_id").is_none());
215 assert!(value.get("name").is_none());
216 }
217
218 #[test]
219 fn mentions_bot_ignores_empty_bot_open_id() {
220 let message = NormalizedMessage {
221 message_id: "om_1".to_owned(),
222 chat_id: "oc_1".to_owned(),
223 chat_type: MessageChatType::Group,
224 sender_id: "ou_sender".to_owned(),
225 sender: MessageSenderInfo::default(),
226 message_type: "text".to_owned(),
227 text: "hello".to_owned(),
228 raw_content: String::new(),
229 content: None,
230 root_id: None,
231 parent_id: None,
232 thread_id: None,
233 mentions: vec![MessageMention {
234 key: String::new(),
235 open_id: String::new(),
236 user_id: None,
237 union_id: None,
238 name: None,
239 mentioned_type: MessageSenderType::Unknown,
240 }],
241 resources: Vec::new(),
242 raw: Value::Null,
243 };
244
245 assert!(!message.mentions_bot(""));
246 }
247}