Skip to main content

chat4n6_plugin_api/
types.rs

1use chrono::{DateTime, FixedOffset, Utc};
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
6pub enum EvidenceSource {
7    Live,
8    WalPending,
9    WalHistoric,
10    Freelist,
11    FtsOnly,
12    CarvedUnalloc { confidence_pct: u8 },
13    CarvedDb,
14}
15
16impl fmt::Display for EvidenceSource {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match self {
19            Self::Live => write!(f, "LIVE"),
20            Self::WalPending => write!(f, "WAL-PENDING"),
21            Self::WalHistoric => write!(f, "WAL-HISTORIC"),
22            Self::Freelist => write!(f, "FREELIST"),
23            Self::FtsOnly => write!(f, "FTS-ONLY"),
24            Self::CarvedUnalloc { confidence_pct } => {
25                write!(f, "CARVED-UNALLOC {confidence_pct}%")
26            }
27            Self::CarvedDb => write!(f, "CARVED-DB"),
28        }
29    }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
33pub struct ForensicTimestamp {
34    pub utc: DateTime<Utc>,
35    pub local_offset_seconds: i32,
36}
37
38impl ForensicTimestamp {
39    pub fn from_millis(ms: i64, local_offset_seconds: i32) -> Self {
40        use chrono::TimeZone;
41        let utc = Utc
42            .timestamp_millis_opt(ms)
43            .single()
44            .unwrap_or(DateTime::<Utc>::UNIX_EPOCH);
45        Self {
46            utc,
47            local_offset_seconds,
48        }
49    }
50
51    pub fn utc_str(&self) -> String {
52        self.utc.format("%Y-%m-%d %H:%M:%S UTC").to_string()
53    }
54
55    pub fn local_str(&self) -> String {
56        let offset = FixedOffset::east_opt(self.local_offset_seconds)
57            .unwrap_or(FixedOffset::east_opt(0).unwrap());
58        let local = self.utc.with_timezone(&offset);
59        let total_secs = self.local_offset_seconds;
60        let sign = if total_secs >= 0 { '+' } else { '-' };
61        let abs_secs = total_secs.unsigned_abs();
62        let hours = abs_secs / 3600;
63        let mins = (abs_secs % 3600) / 60;
64        format!(
65            "{}  |  {} {}{:02}:{:02}",
66            self.utc.format("%Y-%m-%d %H:%M:%S UTC"),
67            local.format("%Y-%m-%d %H:%M:%S"),
68            sign,
69            hours,
70            mins
71        )
72    }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
76pub struct MediaRef {
77    pub file_path: String,
78    pub mime_type: String,
79    pub file_size: u64,
80    pub extracted_name: Option<String>,
81    pub thumbnail_b64: Option<String>,
82    pub duration_secs: Option<u32>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
86pub struct Reaction {
87    pub emoji: String,
88    pub reactor_jid: String,
89    pub timestamp: ForensicTimestamp,
90    pub source: EvidenceSource,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
94pub struct CallRecord {
95    pub call_id: i64,
96    pub participants: Vec<String>,
97    pub from_me: bool,
98    pub video: bool,
99    pub group_call: bool,
100    pub duration_secs: u32,
101    pub timestamp: ForensicTimestamp,
102    pub source: EvidenceSource,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
106pub enum MessageContent {
107    Text(String),
108    Media(MediaRef),
109    Location {
110        lat: f64,
111        lon: f64,
112        name: Option<String>,
113    },
114    VCard(String),
115    Deleted,
116    System(String),
117    Unknown(i32),
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
121pub struct Message {
122    pub id: i64,
123    pub chat_id: i64,
124    pub sender_jid: Option<String>,
125    pub from_me: bool,
126    pub timestamp: ForensicTimestamp,
127    pub content: MessageContent,
128    pub reactions: Vec<Reaction>,
129    pub quoted_message: Option<Box<Message>>,
130    pub source: EvidenceSource,
131    pub row_offset: u64,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
135pub struct Contact {
136    pub jid: String,
137    pub display_name: Option<String>,
138    pub phone_number: Option<String>,
139    pub source: EvidenceSource,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct Chat {
144    pub id: i64,
145    pub jid: String,
146    pub name: Option<String>,
147    pub is_group: bool,
148    pub messages: Vec<Message>,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
152pub enum WalDeltaStatus {
153    AddedInWal,
154    DeletedInWal,
155    ModifiedInWal,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct WalDelta {
160    pub table: String,
161    pub row_id: i64,
162    pub status: WalDeltaStatus,
163}
164
165#[derive(Debug, Default, Serialize, Deserialize)]
166pub struct ExtractionResult {
167    pub chats: Vec<Chat>,
168    pub contacts: Vec<Contact>,
169    pub calls: Vec<CallRecord>,
170    pub wal_deltas: Vec<WalDelta>,
171    pub timezone_offset_seconds: Option<i32>,
172    pub schema_version: u32,
173}