1use crate::{
4 prelude::{WalleError, WalleResult},
5 util::{PushToValueMap, TryAsMut, TryAsRef, Value, ValueMap, ValueMapExt},
6 value, value_map,
7};
8
9pub type Segments = Vec<MsgSegment>;
10
11#[derive(Debug, Clone, PartialEq)]
13pub struct MsgSegment {
14 pub ty: String,
15 pub data: ValueMap,
16}
17
18pub trait ToMsgSegment: PushToValueMap {
19 fn ty(&self) -> &'static str;
20 fn to_segment(self) -> MsgSegment
21 where
22 Self: Sized,
23 {
24 MsgSegment {
25 ty: self.ty().to_string(),
26 data: {
27 let mut map = ValueMap::new();
28 self.push_to(&mut map);
29 map
30 },
31 }
32 }
33}
34
35pub trait TryFromMsgSegment: Sized {
36 fn try_from_msg_segment_mut(segment: &mut MsgSegment) -> WalleResult<Self>;
37 fn try_from_msg_segment(mut segment: MsgSegment) -> WalleResult<Self> {
38 Self::try_from_msg_segment_mut(&mut segment)
39 }
40}
41
42impl MsgSegment {
43 pub fn alt(&self) -> String {
44 if self.ty == "text" {
45 self.data.get_downcast("text").unwrap_or_default()
46 } else if self.data.is_empty() {
47 format!("[{}]", self.ty)
48 } else {
49 let mut content = serde_json::to_string(&self.data).unwrap_or_default();
50 content.pop();
51 content.remove(0);
52 format!("[{},{}]", self.ty, content)
53 }
54 }
55 pub fn try_as_ref<'a>(&'a self) -> WalleResult<MsgSegmentRef<'a>> {
56 self._try_as_ref()
57 }
58 pub fn try_as_mut<'a>(&'a mut self) -> WalleResult<MsgSegmentMut<'a>> {
59 self._try_as_mut()
60 }
61}
62
63pub fn alt(segments: &Segments) -> String {
64 segments.iter().map(|seg| seg.alt()).collect()
65}
66
67impl From<MsgSegment> for Value {
68 fn from(segment: MsgSegment) -> Self {
69 value!({
70 "type": segment.ty,
71 "data": segment.data
72 })
73 }
74}
75
76impl TryFrom<Value> for MsgSegment {
77 type Error = WalleError;
78 fn try_from(value: Value) -> Result<Self, Self::Error> {
79 if let Value::Map(mut map) = value {
80 Ok(Self {
81 ty: map.remove_downcast("type")?,
82 data: map
83 .remove("data")
84 .ok_or_else(|| WalleError::MapMissedKey("data".to_string()))?
85 .downcast_map()?,
86 })
87 } else {
88 Err(WalleError::ValueTypeNotMatch(
89 "map".to_string(),
90 format!("{:?}", value),
91 ))
92 }
93 }
94}
95
96#[derive(Debug, Clone, PartialEq)]
98pub struct BaseSegment<T> {
99 pub segment: T,
100 pub extra: ValueMap,
101}
102
103impl<T> TryFrom<MsgSegment> for BaseSegment<T>
104where
105 T: TryFromMsgSegment,
106{
107 type Error = WalleError;
108 fn try_from(mut segment: MsgSegment) -> Result<Self, Self::Error> {
109 Ok(Self {
110 segment: T::try_from_msg_segment_mut(&mut segment)?,
111 extra: segment.data,
112 })
113 }
114}
115
116pub trait IntoMessage {
117 fn into_message(self) -> Segments;
118}
119
120impl IntoMessage for Segments {
121 fn into_message(self) -> Segments {
122 self
123 }
124}
125
126impl<T: Into<MsgSegment>> IntoMessage for T {
127 fn into_message(self) -> Segments {
128 vec![self.into()]
129 }
130}
131
132impl From<String> for MsgSegment {
133 fn from(text: String) -> Self {
134 MsgSegment {
135 ty: "text".to_string(),
136 data: value_map! { "text": text },
137 }
138 }
139}
140
141impl From<&str> for MsgSegment {
142 fn from(text: &str) -> Self {
143 MsgSegment {
144 ty: "text".to_string(),
145 data: value_map! { "text": text },
146 }
147 }
148}
149
150pub trait SegmentDeclare {
151 fn ty(&self) -> &'static str;
152 fn check(segment: &MsgSegment) -> bool;
153}
154
155use walle_macro::{
156 _PushToValueMap as PushToValueMap, _ToMsgSegment as ToMsgSegment,
157 _TryFromMsgSegment as TryFromMsgSegment, _TryFromValue as TryFromValue,
158};
159
160#[derive(
161 Debug, Clone, PartialEq, Eq, PushToValueMap, ToMsgSegment, TryFromMsgSegment, TryFromValue,
162)]
163pub struct Text {
164 pub text: String,
165}
166
167#[derive(
168 Debug, Clone, PartialEq, Eq, PushToValueMap, ToMsgSegment, TryFromMsgSegment, TryFromValue,
169)]
170pub struct Mention {
171 pub user_id: String,
172}
173
174#[derive(
175 Debug, Clone, PartialEq, Eq, PushToValueMap, ToMsgSegment, TryFromMsgSegment, TryFromValue,
176)]
177pub struct MentionAll {}
178
179#[derive(
180 Debug, Clone, PartialEq, Eq, PushToValueMap, ToMsgSegment, TryFromMsgSegment, TryFromValue,
181)]
182pub struct Image {
183 pub file_id: String,
184}
185
186#[derive(
187 Debug, Clone, PartialEq, Eq, PushToValueMap, ToMsgSegment, TryFromMsgSegment, TryFromValue,
188)]
189pub struct Voice {
190 pub file_id: String,
191}
192
193#[derive(
194 Debug, Clone, PartialEq, Eq, PushToValueMap, ToMsgSegment, TryFromMsgSegment, TryFromValue,
195)]
196pub struct Audio {
197 pub file_id: String,
198}
199
200#[derive(
201 Debug, Clone, PartialEq, Eq, PushToValueMap, ToMsgSegment, TryFromMsgSegment, TryFromValue,
202)]
203pub struct Video {
204 pub file_id: String,
205}
206
207#[derive(
208 Debug, Clone, PartialEq, Eq, PushToValueMap, ToMsgSegment, TryFromMsgSegment, TryFromValue,
209)]
210pub struct File {
211 pub file_id: String,
212}
213
214#[derive(
215 Debug, Clone, PartialEq, PushToValueMap, ToMsgSegment, TryFromMsgSegment, TryFromValue,
216)]
217pub struct Location {
218 pub latitude: f64,
219 pub longitude: f64,
220 pub title: String,
221 pub content: String,
222}
223
224#[derive(
225 Debug, Clone, PartialEq, Eq, PushToValueMap, ToMsgSegment, TryFromMsgSegment, TryFromValue,
226)]
227pub struct Reply {
228 pub message_id: String,
229 pub user_id: Option<String>,
230}
231
232pub trait MessageExt {
233 fn extract_plain_text(&self) -> String;
234 fn extract<T: TryFrom<MsgSegment>>(self) -> Vec<T>;
235}
236
237pub trait MessageRefExt {
238 fn try_as_ref<'a>(&'a self) -> WalleResult<Vec<MsgSegmentRef<'a>>>;
239 fn try_iter_text_mut<'a>(&'a self) -> WalleResult<Vec<&'a str>> {
240 Ok(self
241 .try_as_ref()?
242 .into_iter()
243 .filter_map(|seg| {
244 if let MsgSegmentRef::Text { text, .. } = seg {
245 Some(text)
246 } else {
247 None
248 }
249 })
250 .collect())
251 }
252 fn try_first_text_ref<'a>(&'a self) -> WalleResult<&'a str> {
253 let mut segs = self.try_as_ref()?;
254 if !segs.is_empty() {
255 if let MsgSegmentRef::Text { text, .. } = segs.remove(0) {
256 return Ok(text);
257 }
258 }
259 Err(WalleError::Other(
260 "first message segment is not text".to_string(),
261 ))
262 }
263 fn try_last_text_ref<'a>(&'a self) -> WalleResult<&'a str> {
264 if let Some(MsgSegmentRef::Text { text, .. }) = self.try_as_ref()?.pop() {
265 return Ok(text);
266 } else {
267 Err(WalleError::Other(
268 "last message segment is not text".to_string(),
269 ))
270 }
271 }
272}
273
274pub trait MessageMutExt {
275 fn try_as_mut<'a>(&'a mut self) -> WalleResult<Vec<MsgSegmentMut<'a>>>;
276 fn try_iter_text_mut<'a>(&'a mut self) -> WalleResult<Vec<&'a mut String>> {
277 Ok(self
278 .try_as_mut()?
279 .into_iter()
280 .filter_map(|seg| {
281 if let MsgSegmentMut::Text { text } = seg {
282 Some(text)
283 } else {
284 None
285 }
286 })
287 .collect())
288 }
289 fn try_first_text_mut<'a>(&'a mut self) -> WalleResult<&'a mut String> {
290 let mut segs = self.try_as_mut()?;
291 if !segs.is_empty() {
292 if let MsgSegmentMut::Text { text } = segs.remove(0) {
293 return Ok(text);
294 }
295 }
296 Err(WalleError::Other(
297 "first message segment is not text".to_string(),
298 ))
299 }
300 fn try_last_text_mut<'a>(&'a mut self) -> WalleResult<&'a mut String> {
301 if let Some(MsgSegmentMut::Text { text }) = self.try_as_mut()?.pop() {
302 return Ok(text);
303 } else {
304 Err(WalleError::Other(
305 "last message segment is not text".to_string(),
306 ))
307 }
308 }
309}
310
311impl MessageExt for Segments {
312 fn extract_plain_text(&self) -> String {
313 self.iter()
314 .filter_map(|segment| {
315 if segment.ty == "text" {
316 Some(segment.alt())
317 } else {
318 None
319 }
320 })
321 .collect::<Vec<_>>()
322 .join("\n")
323 }
324 fn extract<T: TryFrom<MsgSegment>>(self) -> Vec<T> {
325 self.into_iter()
326 .filter_map(|seg| T::try_from(seg).ok())
327 .collect()
328 }
329}
330
331impl MessageRefExt for Segments {
332 fn try_as_ref<'a>(&'a self) -> WalleResult<Vec<MsgSegmentRef<'a>>> {
333 self.iter().map(|seg| seg.try_as_ref()).collect()
334 }
335}
336
337impl MessageMutExt for Segments {
338 fn try_as_mut<'a>(&'a mut self) -> WalleResult<Vec<MsgSegmentMut<'a>>> {
339 self.into_iter().map(|seg| seg.try_as_mut()).collect()
340 }
341}
342
343impl MessageRefExt for Vec<Value> {
344 fn try_as_ref<'a>(&'a self) -> WalleResult<Vec<MsgSegmentRef<'a>>> {
345 self.iter().map(|v| v.try_as_ref()).collect()
346 }
347}
348
349impl MessageMutExt for Vec<Value> {
350 fn try_as_mut<'a>(&'a mut self) -> WalleResult<Vec<MsgSegmentMut<'a>>> {
351 self.into_iter().map(|v| v.try_as_mut()).collect()
352 }
353}
354
355pub enum MsgSegmentRef<'a> {
356 Text {
357 text: &'a str,
358 extra: &'a ValueMap,
359 },
360 Mention {
361 user_id: &'a str,
362 extra: &'a ValueMap,
363 },
364 MentionAll {
365 extra: &'a ValueMap,
366 },
367 Image {
368 file_id: &'a str,
369 extra: &'a ValueMap,
370 },
371 Voice {
372 file_id: &'a str,
373 extra: &'a ValueMap,
374 },
375 Audio {
376 file_id: &'a str,
377 extra: &'a ValueMap,
378 },
379 Video {
380 file_id: &'a str,
381 extra: &'a ValueMap,
382 },
383 File {
384 file_id: &'a str,
385 extra: &'a ValueMap,
386 },
387 Location {
388 latitude: &'a f64,
389 longitude: &'a f64,
390 title: &'a str,
391 content: &'a str,
392 extra: &'a ValueMap,
393 },
394 Reply {
395 message_id: &'a str,
396 user_id: &'a str,
397 extra: &'a ValueMap,
398 },
399 Other {
400 ty: &'a str,
401 extra: &'a ValueMap,
402 },
403}
404
405fn _as_ref<'a, 'b, 'c>(ty: &'a str, data: &'b ValueMap) -> WalleResult<MsgSegmentRef<'c>>
406where
407 'a: 'c,
408 'b: 'c,
409{
410 match ty {
411 "text" => Ok(MsgSegmentRef::Text {
412 text: data.try_get_as_ref("text")?,
413 extra: &data,
414 }),
415 "mention" => Ok(MsgSegmentRef::Mention {
416 user_id: data.try_get_as_ref("user_id")?,
417 extra: &data,
418 }),
419 "mention_all" => Ok(MsgSegmentRef::MentionAll { extra: &data }),
420 "image" => Ok(MsgSegmentRef::Image {
421 file_id: data.try_get_as_ref("file_id")?,
422 extra: &data,
423 }),
424 "voice" => Ok(MsgSegmentRef::Voice {
425 file_id: data.try_get_as_ref("file_id")?,
426 extra: &data,
427 }),
428 "audio" => Ok(MsgSegmentRef::Audio {
429 file_id: data.try_get_as_ref("file_id")?,
430 extra: &data,
431 }),
432 "video" => Ok(MsgSegmentRef::Video {
433 file_id: data.try_get_as_ref("file_id")?,
434 extra: &data,
435 }),
436 "file" => Ok(MsgSegmentRef::File {
437 file_id: data.try_get_as_ref("file_id")?,
438 extra: &data,
439 }),
440 "location" => Ok(MsgSegmentRef::Location {
441 latitude: data.try_get_as_ref("latitude")?,
442 longitude: data.try_get_as_ref("longitude")?,
443 title: data.try_get_as_ref("title")?,
444 content: data.try_get_as_ref("content")?,
445 extra: &data,
446 }),
447 "reply" => Ok(MsgSegmentRef::Reply {
448 message_id: data.try_get_as_ref("message_id")?,
449 user_id: data.try_get_as_ref("user_id")?,
450 extra: &data,
451 }),
452 _ => Ok(MsgSegmentRef::Other { ty, extra: &data }),
453 }
454}
455
456impl<'a> TryAsRef<'a, MsgSegmentRef<'a>> for MsgSegment {
457 fn _try_as_ref(&'a self) -> WalleResult<MsgSegmentRef<'a>> {
458 _as_ref(&self.ty, &self.data)
459 }
460}
461
462impl<'a> TryAsRef<'a, MsgSegmentRef<'a>> for Value {
463 fn _try_as_ref(&'a self) -> WalleResult<MsgSegmentRef<'a>> {
464 if let Value::Map(m) = self {
465 _as_ref(
466 m.try_get_as_ref("type")?,
467 m.try_get_as_ref::<&ValueMap>("data")?,
468 )
469 } else {
470 Err(WalleError::ValueTypeNotMatch(
471 "map".to_string(),
472 format!("{:?}", self),
473 ))
474 }
475 }
476}
477
478pub enum MsgSegmentMut<'a> {
479 Text { text: &'a mut String },
480 Mention { user_id: &'a mut String },
481 Image { file_id: &'a mut String },
482 Voice { file_id: &'a mut String },
483 Audio { file_id: &'a mut String },
484 Video { file_id: &'a mut String },
485 File { file_id: &'a mut String },
486 Other,
487}
488
489fn _as_mut<'a, 'b>(ty: &str, data: &'a mut ValueMap) -> WalleResult<MsgSegmentMut<'b>>
490where
491 'a: 'b,
492{
493 match ty {
494 "text" => Ok(MsgSegmentMut::Text {
495 text: data.try_get_as_mut("text")?,
496 }),
497 "mention" => Ok(MsgSegmentMut::Mention {
498 user_id: data.try_get_as_mut("user_id")?,
499 }),
500 "image" => Ok(MsgSegmentMut::Image {
501 file_id: data.try_get_as_mut("file_id")?,
502 }),
503 "voice" => Ok(MsgSegmentMut::Voice {
504 file_id: data.try_get_as_mut("file_id")?,
505 }),
506 "audio" => Ok(MsgSegmentMut::Audio {
507 file_id: data.try_get_as_mut("file_id")?,
508 }),
509 "video" => Ok(MsgSegmentMut::Video {
510 file_id: data.try_get_as_mut("file_id")?,
511 }),
512 "file" => Ok(MsgSegmentMut::File {
513 file_id: data.try_get_as_mut("file_id")?,
514 }),
515 _ => Ok(MsgSegmentMut::Other),
516 }
517}
518
519impl<'a> TryAsMut<'a, MsgSegmentMut<'a>> for MsgSegment {
520 fn _try_as_mut(&'a mut self) -> WalleResult<MsgSegmentMut<'a>> {
521 _as_mut(&self.ty, &mut self.data)
522 }
523}
524
525impl<'a> TryAsMut<'a, MsgSegmentMut<'a>> for Value {
526 fn _try_as_mut(&'a mut self) -> WalleResult<MsgSegmentMut<'a>> {
527 if let Value::Map(m) = self {
528 _as_mut(
529 &m.get_downcast::<String>("type")?,
530 m.try_get_as_mut("data")?,
531 )
532 } else {
533 Err(WalleError::ValueTypeNotMatch(
534 "map".to_string(),
535 format!("{:?}", self),
536 ))
537 }
538 }
539}