walle_core/
segment.rs

1//! MsgSegment 相关模型定义
2
3use 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/// 标准 MsgSegment 模型
12#[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/// 泛型可扩展 MsgSegment 模型
97#[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}