whatsapp_export_parser/
messages.rs

1use chrono::NaiveDateTime;
2use std::ops::Range;
3
4#[cfg(feature = "serde-1")]
5use serde::{Deserialize, Serialize};
6
7/// A single chat message.
8#[derive(Debug, Clone, PartialEq)]
9#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
10#[cfg_attr(feature = "serde-1", serde(rename_all = "kebab-case"))]
11pub struct Message {
12    /// The message [`Metadata`].
13    pub meta: Metadata,
14    /// The message's [`Body`].
15    pub body: Body,
16    /// The location of this [`Message`] in its source text.
17    pub span: Span,
18}
19
20/// [`Message`] metadata.
21#[derive(Debug, Clone, PartialEq)]
22#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
23#[cfg_attr(feature = "serde-1", serde(rename_all = "kebab-case"))]
24pub struct Metadata {
25    /// When the message was received.
26    pub timestamp: NaiveDateTime,
27    /// Who sent the message?
28    pub sender: String,
29    /// The location of the [`Metadata`] field in the source text.
30    pub span: Span,
31}
32
33/// A [`Message`]'s contents.
34#[derive(Debug, Clone, PartialEq)]
35#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
36#[cfg_attr(feature = "serde-1", serde(rename_all = "kebab-case", tag = "type"))]
37pub enum Body {
38    /// A [`DirectMessage`].
39    DirectMessage(DirectMessage),
40    /// An [`Attachment`].
41    Attachment(Attachment),
42}
43
44impl From<DirectMessage> for Body {
45    fn from(other: DirectMessage) -> Body { Body::DirectMessage(other) }
46}
47
48impl From<Attachment> for Body {
49    fn from(other: Attachment) -> Body { Body::Attachment(other) }
50}
51
52/// A message with text.
53#[derive(Debug, Clone, PartialEq)]
54#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
55#[cfg_attr(feature = "serde-1", serde(rename_all = "kebab-case"))]
56pub struct DirectMessage {
57    /// The raw body of the message.
58    pub content: String,
59    /// Where the [`DirectMessage`] occurred in its source text.
60    pub span: Span,
61}
62
63/// A link to an attachment.
64#[derive(Debug, Clone, PartialEq)]
65#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
66#[cfg_attr(feature = "serde-1", serde(rename_all = "kebab-case"))]
67pub struct Attachment {
68    /// The name of the attached file.
69    pub name: String,
70    /// Where the [`Attachment`] occurred in its source text.
71    pub span: Span,
72}
73
74/// The half-open interval representing the location of something in some text.
75#[derive(Debug, Copy, Clone, PartialEq, Eq)]
76#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
77#[cfg_attr(feature = "serde-1", serde(rename_all = "kebab-case"))]
78pub struct Span {
79    /// The index of the first character.
80    pub start: usize,
81    /// The index one after the last character.
82    pub end: usize,
83}
84
85impl Span {
86    /// Create a new [`Span`].
87    pub const fn new(start: usize, end: usize) -> Self { Span { start, end } }
88
89    /// Get the [`Span`] as a [`Range`].
90    pub const fn as_range(self) -> Range<usize> { self.start..self.end }
91
92    /// Look up the text this [`Span`] corresponds to.
93    ///
94    /// This can fail if the [`Span`] didn't come from the same string, or if
95    /// [`Span::start`] or [`Span::end`] don't lie on a UTF-8 code boundary.
96    pub fn get(self, text: &str) -> Option<&str> { text.get(self.as_range()) }
97}