1use reqwest::Method;
55use serde::{Deserialize, Serialize};
56
57use crate::client::Ghl;
58use crate::error::Result;
59
60#[derive(Debug, Clone, Default, Serialize, Deserialize)]
62#[serde(rename_all = "camelCase")]
63#[allow(missing_docs)] pub struct Conversation {
65 pub id: String,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
67 pub location_id: Option<String>,
68 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub contact_id: Option<String>,
70 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub full_name: Option<String>,
72 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub last_message_body: Option<String>,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub last_message_date: Option<serde_json::Value>,
76 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub last_message_type: Option<String>,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
79 pub unread_count: Option<i64>,
80 #[serde(flatten)]
82 pub extra: serde_json::Map<String, serde_json::Value>,
83}
84
85#[derive(Debug, Clone, Default, Serialize, Deserialize)]
87#[serde(rename_all = "camelCase")]
88#[allow(missing_docs)] pub struct Message {
90 pub id: String,
91 #[serde(default, skip_serializing_if = "Option::is_none")]
92 pub body: Option<String>,
93 #[serde(default, skip_serializing_if = "Option::is_none")]
95 pub message_type: Option<String>,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
98 pub direction: Option<String>,
99 #[serde(default, skip_serializing_if = "Option::is_none")]
100 pub status: Option<String>,
101 #[serde(default, skip_serializing_if = "Option::is_none")]
102 pub date_added: Option<String>,
103 #[serde(default, skip_serializing_if = "Option::is_none")]
104 pub conversation_id: Option<String>,
105 #[serde(flatten)]
107 pub extra: serde_json::Map<String, serde_json::Value>,
108}
109
110#[derive(Debug, Clone, Default, Serialize)]
112#[serde(rename_all = "camelCase")]
113#[allow(missing_docs)] pub struct SendMessage {
115 #[serde(rename = "type")]
117 pub message_type: String,
118 pub contact_id: String,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub message: Option<String>,
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub subject: Option<String>,
124 #[serde(skip_serializing_if = "Option::is_none")]
125 pub html: Option<String>,
126 #[serde(skip_serializing_if = "Vec::is_empty", default)]
127 pub attachments: Vec<String>,
128}
129
130#[derive(Debug, Clone, Deserialize)]
132#[serde(rename_all = "camelCase")]
133#[allow(missing_docs)]
134pub struct SendMessageResult {
135 #[serde(default)]
136 pub conversation_id: Option<String>,
137 #[serde(default)]
138 pub message_id: Option<String>,
139 #[serde(flatten)]
141 pub extra: serde_json::Map<String, serde_json::Value>,
142}
143
144#[derive(Deserialize)]
145struct ConversationSearch {
146 #[serde(default)]
147 conversations: Vec<Conversation>,
148 #[serde(default)]
149 total: Option<i64>,
150}
151
152#[derive(Deserialize)]
153struct MessagesEnvelope {
154 messages: MessagesInner,
155}
156
157#[derive(Deserialize)]
158struct MessagesInner {
159 #[serde(default)]
160 messages: Vec<Message>,
161 #[serde(default, rename = "nextPage")]
162 next_page: Option<bool>,
163}
164
165#[derive(Debug, Clone)]
167pub struct ConversationPage {
168 pub conversations: Vec<Conversation>,
170 pub total: Option<i64>,
172}
173
174#[derive(Debug, Clone)]
176pub struct MessagePage {
177 pub messages: Vec<Message>,
179 pub next_page: Option<bool>,
181}
182
183pub struct ConversationsService {
185 pub(crate) client: Ghl,
186}
187
188impl ConversationsService {
189 pub(crate) fn new(client: Ghl) -> Self {
190 Self { client }
191 }
192
193 pub async fn search(
195 &self,
196 location_id: &str,
197 query: Option<&str>,
198 limit: u32,
199 ) -> Result<ConversationPage> {
200 let mut params: Vec<(String, String)> = vec![
201 ("locationId".into(), location_id.to_owned()),
202 ("limit".into(), limit.clamp(1, 100).to_string()),
203 ];
204 if let Some(q) = query {
205 params.push(("query".into(), q.to_owned()));
206 }
207 let result: ConversationSearch = self
208 .client
209 .send(Method::GET, "/conversations/search", ¶ms, None::<&()>)
210 .await?;
211 Ok(ConversationPage {
212 conversations: result.conversations,
213 total: result.total,
214 })
215 }
216
217 pub async fn messages(&self, conversation_id: &str, limit: u32) -> Result<MessagePage> {
219 let envelope: MessagesEnvelope = self
220 .client
221 .send(
222 Method::GET,
223 &format!("/conversations/{conversation_id}/messages"),
224 &[("limit".into(), limit.clamp(1, 100).to_string())],
225 None::<&()>,
226 )
227 .await?;
228 Ok(MessagePage {
229 messages: envelope.messages.messages,
230 next_page: envelope.messages.next_page,
231 })
232 }
233
234 pub async fn send_message(&self, message: SendMessage) -> Result<SendMessageResult> {
236 self.client
237 .send(Method::POST, "/conversations/messages", &[], Some(&message))
238 .await
239 }
240}