1use serde::{Deserialize, Serialize};
4use chrono::{DateTime, Utc};
5use crate::{RecordReference, agent::ChannelType};
6use std::collections::{HashMap, HashSet};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Conversation {
12 pub id: ConversationId,
13 pub contact: Option<Contact>,
14 pub items: Vec<ConversationItem>,
15 pub created: i64,
16 pub last_updated: i64,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
20pub struct ConversationId {
21 pub from: String, pub account: RecordReference,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct Contact {
29 pub id: String,
30 pub name: Option<String>,
31 pub email: Option<String>,
32 pub phone: Option<String>,
33 pub avatar_url: Option<String>,
34 pub metadata: serde_json::Value,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct ConversationItem {
41 pub id: String,
42 pub channel: String, pub timestamp: i64,
44 pub voice: Option<VoiceItem>,
45 pub whatsapp: Option<WhatsAppItem>,
46 pub meta: HashSet<SessionVariable>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
52pub struct SessionVariable {
53 pub name: String,
54 pub value: String,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct VoiceItem {
61 pub id: String,
62 pub direction: String, pub origin: String,
64 pub from: String,
65 pub to: String,
66 pub call_sid: String,
67 pub call_id: String,
68 pub x_cid: Option<String>,
69 pub diversion: Option<String>,
70 pub p_asserted_id: Option<String>,
71 pub sbc_ip: Option<String>,
72 pub sbc_trunk_name: Option<String>,
73 pub account_sid: String,
74 pub application_sid: Option<String>,
75 pub timestamp: i64,
76 pub events: Vec<VoiceEvent>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct VoiceEvent {
81 #[serde(rename = "type")]
82 pub event_type: String, pub timestamp: i64,
84 pub attributes: HashMap<String, String>,
85}
86
87impl VoiceItem {
88 pub fn is_trying(&self) -> bool {
90 self.events.iter()
91 .filter(|e| e.event_type == "event")
92 .max_by_key(|e| e.timestamp)
93 .and_then(|e| e.attributes.get("sipStatus"))
94 .and_then(|s| s.parse::<i32>().ok())
95 .map(|status| status == 100)
96 .unwrap_or(false)
97 }
98
99 pub fn is_ringing(&self) -> bool {
100 self.events.iter()
101 .filter(|e| e.event_type == "event")
102 .max_by_key(|e| e.timestamp)
103 .and_then(|e| e.attributes.get("sipStatus"))
104 .and_then(|s| s.parse::<i32>().ok())
105 .map(|status| status > 100 && status < 200)
106 .unwrap_or(false)
107 }
108
109 pub fn is_connected(&self) -> bool {
110 self.events.iter()
111 .filter(|e| e.event_type == "event")
112 .max_by_key(|e| e.timestamp)
113 .and_then(|e| e.attributes.get("callStatus"))
114 .map(|status| status == "in-progress")
115 .unwrap_or(false)
116 }
117
118 pub fn is_completed(&self) -> bool {
119 self.events.iter()
120 .filter(|e| e.event_type == "event")
121 .max_by_key(|e| e.timestamp)
122 .and_then(|e| e.attributes.get("callStatus"))
123 .map(|status| status == "completed")
124 .unwrap_or(false)
125 }
126
127 pub fn is_recording(&self) -> bool {
128 self.events.iter()
129 .filter(|e| e.event_type == "recording-event")
130 .filter(|e| e.attributes.get("type").map(|t| t == "dial").unwrap_or(false))
131 .max_by_key(|e| e.timestamp)
132 .and_then(|e| e.attributes.get("status"))
133 .map(|status| status == "start")
134 .unwrap_or(false)
135 }
136
137 pub fn parked_calls(&self) -> Vec<&VoiceEvent> {
138 let mut parked_events = Vec::new();
139 let mut call_park_by_sid: HashMap<&str, Vec<&VoiceEvent>> = HashMap::new();
140
141 for event in &self.events {
143 if event.event_type == "call-park" {
144 if let Some(call_sid) = event.attributes.get("callSid") {
145 call_park_by_sid.entry(call_sid).or_insert_with(Vec::new).push(event);
146 }
147 }
148 }
149
150 for (_, events) in call_park_by_sid {
152 if let Some(latest_event) = events.iter().max_by_key(|e| e.timestamp) {
153 if latest_event.attributes.get("status").map(|s| s == "waiting").unwrap_or(false) {
154 parked_events.push(*latest_event);
155 }
156 }
157 }
158
159 parked_events
160 }
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct WhatsAppItem {
167 pub id: String,
168 pub phone_number_id: String,
169 pub request: Option<WhatsappRequest>,
170 pub response: Option<WhatsappResponse>,
171 pub contacts: Vec<WhatsappContact>,
172 pub message: Option<WhatsappMessage>,
173 pub statuses: Vec<WhatsappStatus>,
174 pub timestamp: i64,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct WhatsappRequest {
179 pub to: String,
180 pub messaging_product: String,
181 #[serde(rename = "type")]
182 pub message_type: String,
183 pub text: Option<WhatsappText>,
184 pub template: Option<WhatsappTemplate>,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct WhatsappResponse {
189 pub messaging_product: String,
190 pub contacts: Vec<WhatsappContact>,
191 pub messages: Vec<WhatsappMessageId>,
192}
193
194impl WhatsappResponse {
195 pub fn get_first_message_id(&self) -> Option<String> {
196 self.messages.first().map(|m| m.id.clone())
197 }
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct WhatsappContact {
202 pub input: String,
203 pub wa_id: String,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct WhatsappMessageId {
208 pub id: String,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct WhatsappMessage {
213 pub id: String,
214 pub from: String,
215 pub timestamp: String,
216 #[serde(rename = "type")]
217 pub message_type: String,
218 pub text: Option<WhatsappText>,
219 pub image: Option<WhatsappMedia>,
220 pub video: Option<WhatsappMedia>,
221 pub audio: Option<WhatsappMedia>,
222 pub document: Option<WhatsappDocument>,
223 pub location: Option<WhatsappLocation>,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct WhatsappText {
228 pub body: String,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct WhatsappTemplate {
233 pub name: String,
234 pub language: WhatsappLanguage,
235 pub components: Vec<serde_json::Value>,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
239pub struct WhatsappLanguage {
240 pub code: String,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct WhatsappMedia {
245 pub id: String,
246 pub mime_type: Option<String>,
247 pub sha256: Option<String>,
248 pub caption: Option<String>,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct WhatsappDocument {
253 pub id: String,
254 pub mime_type: Option<String>,
255 pub sha256: Option<String>,
256 pub filename: Option<String>,
257 pub caption: Option<String>,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct WhatsappLocation {
262 pub latitude: f64,
263 pub longitude: f64,
264 pub name: Option<String>,
265 pub address: Option<String>,
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct WhatsappStatus {
270 pub id: String,
271 pub status: String, pub timestamp: String,
273 pub recipient_id: String,
274 pub conversation: Option<WhatsappConversationContext>,
275 pub pricing: Option<WhatsappPricing>,
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize)]
279pub struct WhatsappConversationContext {
280 pub id: String,
281 pub origin: WhatsappConversationOrigin,
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct WhatsappConversationOrigin {
286 #[serde(rename = "type")]
287 pub origin_type: String,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct WhatsappPricing {
292 pub billable: bool,
293 pub pricing_model: String,
294 pub category: String,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct ConversationNotification {
301 pub conversation_id: ConversationId,
302 #[serde(rename = "type")]
303 pub notification_type: String, pub item: Option<ConversationItem>,
305 pub events: Option<Vec<VoiceEvent>>,
306 pub variables: Option<Vec<SessionVariable>>,
307 pub timestamp: i64,
308}
309
310impl ConversationNotification {
311 pub fn item(id: ConversationId, item: ConversationItem) -> Self {
312 Self {
313 conversation_id: id,
314 notification_type: "item".to_string(),
315 item: Some(item),
316 events: None,
317 variables: None,
318 timestamp: chrono::Utc::now().timestamp_millis(),
319 }
320 }
321
322 pub fn events(id: ConversationId, events: Vec<VoiceEvent>) -> Self {
323 Self {
324 conversation_id: id,
325 notification_type: "event".to_string(),
326 item: None,
327 events: Some(events),
328 variables: None,
329 timestamp: chrono::Utc::now().timestamp_millis(),
330 }
331 }
332
333 pub fn session(id: ConversationId, variables: Vec<SessionVariable>) -> Self {
334 Self {
335 conversation_id: id,
336 notification_type: "session".to_string(),
337 item: None,
338 events: None,
339 variables: Some(variables),
340 timestamp: chrono::Utc::now().timestamp_millis(),
341 }
342 }
343}
344
345impl Conversation {
348 pub fn within_whatsapp_period(&self) -> bool {
349 let twenty_four_hours_ago = chrono::Utc::now().timestamp_millis() - (24 * 60 * 60 * 1000);
350 self.items.iter()
351 .filter(|item| item.channel == "whatsapp")
352 .any(|item| item.timestamp > twenty_four_hours_ago)
353 }
354
355 pub fn last_message_phone_number_id(&self) -> Option<String> {
356 self.items.iter()
357 .filter(|item| item.channel == "whatsapp")
358 .filter_map(|item| item.whatsapp.as_ref())
359 .map(|w| w.phone_number_id.clone())
360 .next()
361 }
362
363 pub fn last_item(&self) -> Option<&ConversationItem> {
364 self.items.iter().max_by_key(|item| item.timestamp)
365 }
366
367 pub fn parked_calls(&self) -> Vec<&VoiceEvent> {
368 self.items.iter()
369 .filter_map(|item| item.voice.as_ref())
370 .flat_map(|voice| voice.parked_calls())
371 .collect()
372 }
373
374 pub fn has_parked_calls(&self) -> bool {
375 !self.parked_calls().is_empty()
376 }
377}
378