Skip to main content

zulip_api/
client.rs

1//! Zulip API client.
2
3use crate::api::{ApiKey, User};
4use crate::error::Error;
5use crate::result::Result;
6use crate::{
7    delete_message, edit_message, emoji_reaction, fetch_api_key, get_file_temporary_url,
8    get_message, get_message_history, get_messages, get_read_receipts, mark_all_as_read,
9    mark_stream_as_read, mark_topic_as_read, matches_narrow, register, render, report_message,
10    send_message, update_message_flags, update_message_flags_narrow, upload_file, version,
11};
12
13mod endpoint;
14mod tus;
15
16pub use endpoint::Endpoint;
17
18/// Represents the HTTP request timeout.
19pub const TIMEOUT: u64 = 5;
20
21/// Represents the main client for interacting with Zulip servers.
22#[derive(Clone, Debug)]
23pub struct Client {
24    http: reqwest::Client,
25    url: hyper::Uri,
26    user: User,
27    key: ApiKey,
28}
29
30impl Client {
31    /// Creates a new [Client] from the provided Zulip server URL.
32    pub fn new(url: hyper::Uri, user: User, key: ApiKey) -> Result<Self> {
33        Ok(Self {
34            http: Self::http_client()?,
35            url,
36            user,
37            key,
38        })
39    }
40
41    /// Builder function that sets the HTTP client.
42    pub fn with_http(self, http: reqwest::Client) -> Self {
43        Self { http, ..self }
44    }
45
46    /// Attempts to read [Client] configuration from a `zuliprc` INI file.
47    pub fn from_zuliprc(path: &str) -> Result<Self> {
48        let cfg = ini!(path);
49
50        let url = cfg["api"]["site"]
51            .as_deref()
52            .ok_or(Error::io_other("missing zuliprc `site`"))
53            .and_then(|u| u.parse().map_err(|err| Error::io_other(format!("{err}"))))?;
54
55        let user = cfg["api"]["email"]
56            .as_deref()
57            .map(User::from)
58            .ok_or(Error::io_other("missing zuliprc `email`"))?;
59
60        let key = cfg["api"]["key"]
61            .as_deref()
62            .map(ApiKey::from)
63            .ok_or(Error::io_other("missing zuliprc `key`"))?;
64
65        Self::new(url, user, key)
66    }
67
68    /// Creates a HTTP client for making requests to the Zulip API server.
69    pub fn http_client() -> Result<reqwest::Client> {
70        let builder = reqwest::Client::builder().user_agent(Self::user_agent());
71
72        #[cfg(target_family = "wasm")]
73        {
74            builder.build().map_err(Error::from)
75        }
76        #[cfg(all(not(target_family = "wasm"), feature = "dev"))]
77        {
78            builder
79                .danger_accept_invalid_certs(true)
80                .timeout(std::time::Duration::from_secs(TIMEOUT))
81                .use_rustls_tls()
82                .build()
83                .map_err(Error::from)
84        }
85        #[cfg(all(not(target_family = "wasm"), not(feature = "dev")))]
86        {
87            builder
88                .timeout(std::time::Duration::from_secs(TIMEOUT))
89                .use_rustls_tls()
90                .build()
91                .map_err(Error::from)
92        }
93    }
94
95    /// Gets the [Client]'s `UserAgent`.
96    pub fn user_agent() -> String {
97        format!("ZulipRust/{}", version())
98    }
99
100    /// Gets the `Authorization` header.
101    pub fn auth_header(&self) -> String {
102        use base64::{Base64, Encoding};
103
104        let b64 = Base64::encode_string(
105            format!("{}:{}", self.user.as_str(), self.key.as_str()).as_bytes(),
106        );
107
108        format!("Basic {b64}")
109    }
110
111    /// Loads a page from the Zulip.
112    ///
113    /// - `path`: optional path of the page to load, empty string loads the base path
114    pub async fn load_page(&self, path: &str) -> Result<String> {
115        let url = if path.is_empty() {
116            self.base_url()
117        } else {
118            format!("{}/{path}", self.base_url())
119        };
120
121        let res = self
122            .http
123            .get(url)
124            .header("Authorization", self.auth_header())
125            .send()
126            .await?;
127
128        let bytes = res.bytes().await?;
129        let body = str::from_utf8(&bytes[..])?;
130
131        log::debug!("Send message response: {body}");
132
133        Ok(body.into())
134    }
135
136    /// Sends a message to the Zulip server.
137    ///
138    /// # Example
139    ///
140    /// ```rust,no_run
141    /// use zulip_api::client::Client;
142    /// use zulip_api::send_message;
143    /// use zulip_api::message::{MessageTo, MessageType};
144    ///
145    /// # async {
146    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
147    ///
148    /// let req = send_message::Request::new()
149    ///     .with_msg_type(MessageType::Channel)
150    ///     .with_topic("test topic")
151    ///     .with_to(MessageTo::ChannelName("general".into()))
152    ///     .with_content("test message".to_string());
153    ///
154    /// let res = client.send_message(&req).await.unwrap();
155    ///
156    /// println!("Send message response: {res:?}");
157    /// # };
158    /// ```
159    pub async fn send_message(
160        &self,
161        msg: &send_message::Request,
162    ) -> Result<send_message::Response> {
163        let res = self
164            .http
165            .post(self.endpoint(Endpoint::SendMessage))
166            .header("Authorization", self.auth_header())
167            .form(msg)
168            .send()
169            .await?;
170
171        let bytes = res.bytes().await?;
172        let body = str::from_utf8(&bytes[..])?;
173
174        log::debug!("Send message response: {body}");
175
176        serde_json::from_str::<send_message::Response>(body).map_err(Error::from)
177    }
178
179    /// Gets a list of messages from the Zulip server.
180    ///
181    /// # Example
182    ///
183    /// ```rust,no_run
184    /// use zulip_api::client::Client;
185    /// use zulip_api::get_messages;
186    ///
187    /// # async {
188    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
189    ///
190    /// let req = get_messages::Request::new();
191    /// let res = client.get_messages(&req).await.unwrap();
192    ///
193    /// println!("Get messages response: {res:?}");
194    /// # };
195    /// ```
196    pub async fn get_messages(
197        &self,
198        req: &get_messages::Request,
199    ) -> Result<get_messages::Response> {
200        let res = self
201            .http
202            .get(self.endpoint(Endpoint::GetMessages))
203            .header("Authorization", self.auth_header())
204            .query(req)
205            .send()
206            .await?;
207
208        let bytes = res.bytes().await?;
209        let body = str::from_utf8(&bytes[..])?;
210
211        log::debug!("Get messages response: {body}");
212
213        serde_json::from_str::<get_messages::Response>(body).map_err(Error::from)
214    }
215
216    /// Uploads a file to the Zulip server.
217    ///
218    /// # Example
219    ///
220    /// ```rust,no_run
221    /// use zulip_api::client::Client;
222    /// use zulip_api::upload_file;
223    ///
224    /// # async {
225    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
226    ///
227    /// let req = upload_file::Request::new().with_file_path("/path/to/file.txt");
228    /// let res = client.upload_file(&req).await.unwrap();
229    ///
230    /// println!("Upload file response: {res}");
231    /// # };
232    /// ```
233    pub async fn upload_file(&self, req: &upload_file::Request) -> Result<upload_file::Response> {
234        let file_path = req.file_path();
235        let md = std::fs::metadata(file_path)?;
236        let file_len = md.len() as usize;
237        let max_file_len = upload_file::LARGE_FILE;
238
239        if file_len >= max_file_len {
240            self.tus_create_and_upload(file_path).await
241        } else {
242            log::debug!("Sending {file_path}, length: {file_len} using the normal upload");
243
244            let form = reqwest::multipart::Form::new()
245                .file("filename", file_path)
246                .await?;
247
248            let res = self
249                .http
250                .post(self.endpoint(Endpoint::UploadFile))
251                .header("Authorization", self.auth_header())
252                .multipart(form)
253                .send()
254                .await?;
255
256            let bytes = res.bytes().await?;
257            let body = str::from_utf8(&bytes[..])?;
258
259            log::debug!("Upload file response: {body}");
260
261            serde_json::from_str::<upload_file::Response>(body).map_err(Error::from)
262        }
263    }
264
265    /// Get a temporary URL for access to an [uploaded file](https://zulip.com/api/upload-file) that doesn't require authentication.
266    ///
267    /// # Example
268    ///
269    /// ```rust,no_run
270    /// use zulip_api::client::Client;
271    /// use zulip_api::{get_file_temporary_url, upload_file};
272    ///
273    /// # async {
274    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
275    ///
276    /// let req = upload_file::Request::new().with_file_path("/path/to/file.txt");
277    /// let res = client.upload_file(&req).await.unwrap();
278    ///
279    /// println!("Upload file response: {res}");
280    ///
281    /// let req = get_file_temporary_url::Request::new()
282    ///     .with_realm_id(res.realm_id().unwrap())
283    ///     .with_filename(res.filename_path().unwrap());
284    ///
285    /// let res = client.get_file_temporary_url(&req).await.unwrap();
286    ///
287    /// println!("Get file temporary URL response: {res}");
288    /// # };
289    /// ```
290    pub async fn get_file_temporary_url(
291        &self,
292        req: &get_file_temporary_url::Request,
293    ) -> Result<get_file_temporary_url::Response> {
294        let endpoint = self.endpoint(Endpoint::UploadFile);
295        let realm_id = req.realm_id();
296        let filename = req.filename();
297
298        let res = self
299            .http
300            .get(format!("{endpoint}/{realm_id}/{filename}"))
301            .header("Authorization", self.auth_header())
302            .send()
303            .await?;
304
305        let bytes = res.bytes().await?;
306        let body = str::from_utf8(&bytes[..])?;
307
308        log::debug!("Upload file response: {body}");
309
310        serde_json::from_str::<get_file_temporary_url::Response>(body).map_err(Error::from)
311    }
312
313    /// Edit a message on the Zulip server.
314    ///
315    /// # Example
316    ///
317    /// ```rust,no_run
318    /// use zulip_api::client::Client;
319    /// use zulip_api::edit_message;
320    ///
321    /// # async {
322    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
323    ///
324    /// let req = edit_message::Request::new()
325    ///     .with_message_id(1)
326    ///     .with_content("edited content");
327    ///
328    /// let res = client.edit_message(&req).await.unwrap();
329    ///
330    /// println!("Edit message response: {res:?}");
331    /// # };
332    /// ```
333    pub async fn edit_message(
334        &self,
335        req: &edit_message::Request,
336    ) -> Result<edit_message::Response> {
337        let endpoint = self.endpoint(Endpoint::EditMessage);
338        let msg_id = req.message_id();
339
340        let res = self
341            .http
342            .patch(format!("{endpoint}/{msg_id}"))
343            .header("Authorization", self.auth_header())
344            .form(req)
345            .send()
346            .await?;
347
348        let bytes = res.bytes().await?;
349        let body = str::from_utf8(&bytes[..])?;
350
351        log::debug!("Edit message response: {body}");
352
353        serde_json::from_str::<edit_message::Response>(body).map_err(Error::from)
354    }
355
356    /// Delete a message on the Zulip server.
357    ///
358    /// # Example
359    ///
360    /// ```rust,no_run
361    /// use zulip_api::client::Client;
362    /// use zulip_api::delete_message;
363    ///
364    /// # async {
365    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
366    ///
367    /// let req = delete_message::Request::new()
368    ///     .with_message_id(1);
369    ///
370    /// let res = client.delete_message(&req).await.unwrap();
371    ///
372    /// println!("Delete message response: {res:?}");
373    /// # };
374    /// ```
375    pub async fn delete_message(
376        &self,
377        req: &delete_message::Request,
378    ) -> Result<delete_message::Response> {
379        let endpoint = self.endpoint(Endpoint::DeleteMessage);
380        let msg_id = req.message_id();
381
382        let res = self
383            .http
384            .delete(format!("{endpoint}/{msg_id}"))
385            .header("Authorization", self.auth_header())
386            .send()
387            .await?;
388
389        let bytes = res.bytes().await?;
390        let body = str::from_utf8(&bytes[..])?;
391
392        log::debug!("Delete message response: {body}");
393
394        serde_json::from_str::<delete_message::Response>(body).map_err(Error::from)
395    }
396
397    /// Gets a message on the Zulip server.
398    ///
399    /// # Example
400    ///
401    /// ```rust,no_run
402    /// use zulip_api::client::Client;
403    /// use zulip_api::get_message;
404    ///
405    /// # async {
406    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
407    ///
408    /// let req = get_message::Request::new()
409    ///     .with_message_id(1);
410    ///
411    /// let res = client.get_message(&req).await.unwrap();
412    ///
413    /// println!("Get message response: {res:?}");
414    /// # };
415    /// ```
416    pub async fn get_message(&self, req: &get_message::Request) -> Result<get_message::Response> {
417        let endpoint = self.endpoint(Endpoint::GetMessage);
418        let msg_id = req.message_id();
419
420        let res = self
421            .http
422            .get(format!("{endpoint}/{msg_id}"))
423            .header("Authorization", self.auth_header())
424            .query(&req)
425            .send()
426            .await?;
427
428        let bytes = res.bytes().await?;
429        let body = str::from_utf8(&bytes[..])?;
430
431        log::debug!("Get message response: {body}");
432
433        serde_json::from_str::<get_message::Response>(body).map_err(Error::from)
434    }
435
436    /// Gets a message edit history on the Zulip server.
437    ///
438    /// # Example
439    ///
440    /// ```rust,no_run
441    /// use zulip_api::client::Client;
442    /// use zulip_api::get_message_history;
443    ///
444    /// # async {
445    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
446    ///
447    /// let req = get_message_history::Request::new()
448    ///     .with_message_id(1);
449    ///
450    /// let res = client.get_message_history(&req).await.unwrap();
451    ///
452    /// println!("Get message history response: {res:?}");
453    /// # };
454    /// ```
455    pub async fn get_message_history(
456        &self,
457        req: &get_message_history::Request,
458    ) -> Result<get_message_history::Response> {
459        let endpoint = self.endpoint(Endpoint::GetMessageHistory);
460        let msg_id = req.message_id();
461
462        let res = self
463            .http
464            .get(format!("{endpoint}/{msg_id}/history"))
465            .header("Authorization", self.auth_header())
466            .query(&req)
467            .send()
468            .await?;
469
470        let bytes = res.bytes().await?;
471        let body = str::from_utf8(&bytes[..])?;
472
473        log::debug!("Get message history response: {body}");
474
475        serde_json::from_str::<get_message_history::Response>(body).map_err(Error::from)
476    }
477
478    /// Reports a message for moderation on the Zulip server.
479    ///
480    /// # Example
481    ///
482    /// ```rust,no_run
483    /// use zulip_api::client::Client;
484    /// use zulip_api::{report_message, ReportType};
485    ///
486    /// # async {
487    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
488    ///
489    /// let req = report_message::Request::new()
490    ///     .with_message_id(1)
491    ///     .with_report_type(ReportType::Other)
492    ///     .with_description("This message insults and mocks Frodo, which is against the code of conduct.");
493    ///
494    /// let res = client.report_message(&req).await.unwrap();
495    ///
496    /// println!("Report message response: {res:?}");
497    /// # };
498    /// ```
499    pub async fn report_message(
500        &self,
501        req: &report_message::Request,
502    ) -> Result<report_message::Response> {
503        let endpoint = self.endpoint(Endpoint::ReportMessage);
504        let msg_id = req.message_id();
505
506        let mut form = reqwest::multipart::Form::new().text("report_type", req.report_type());
507
508        if let Some(description) = req.description() {
509            form = form.text("description", description.to_string());
510        }
511
512        let res = self
513            .http
514            .post(format!("{endpoint}/{msg_id}/report"))
515            .header("Authorization", self.auth_header())
516            .multipart(form)
517            .send()
518            .await?;
519
520        let bytes = res.bytes().await?;
521        let body = str::from_utf8(&bytes[..])?;
522
523        log::debug!("Get message response: {body}");
524
525        serde_json::from_str::<report_message::Response>(body).map_err(Error::from)
526    }
527
528    /// Gets narrow matches on the Zulip server.
529    ///
530    /// # Example
531    ///
532    /// ```rust,no_run
533    /// use zulip_api::client::Client;
534    /// use zulip_api::filter::{Narrow, Operand, Operator};
535    /// use zulip_api::matches_narrow;
536    ///
537    /// # async {
538    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
539    ///
540    /// let msg_ids = [1u64];
541    /// let narrow = [Narrow::new()
542    ///     .with_operator(Operator::Has)
543    ///     .with_operand(Operand::text("link"))];
544    ///
545    /// let req = matches_narrow::Request::new()
546    ///     .with_msg_ids(msg_ids.as_ref())
547    ///     .with_narrow(narrow.as_ref());
548    ///
549    /// let res = client.matches_narrow(&req).await.unwrap();
550    ///
551    /// println!("Matches narrow response: {res:?}");
552    /// # };
553    /// ```
554    pub async fn matches_narrow(
555        &self,
556        req: &matches_narrow::Request,
557    ) -> Result<matches_narrow::Response> {
558        let res = self
559            .http
560            .get(self.endpoint(Endpoint::MatchesNarrow))
561            .header("Authorization", self.auth_header())
562            .query(&[("msg_ids", serde_json::to_string(req.msg_ids())?)])
563            .query(&[("narrow", serde_json::to_string(req.narrow())?)])
564            .send()
565            .await?;
566
567        let bytes = res.bytes().await?;
568        let body = str::from_utf8(&bytes[..])?;
569
570        log::debug!("Matches narrow response: {body}");
571
572        serde_json::from_str::<matches_narrow::Response>(body).map_err(Error::from)
573    }
574
575    /// Adds an emoji reaction to a message on the Zulip server.
576    ///
577    /// # Example
578    ///
579    /// ```rust,no_run
580    /// use zulip_api::client::Client;
581    /// use zulip_api::emoji_reaction;
582    ///
583    /// # async {
584    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
585    ///
586    /// let req = emoji_reaction::Request::new()
587    ///     .with_message_id(1)
588    ///     .with_emoji_name("+1");
589    ///
590    /// let res = client.add_reaction(&req).await.unwrap();
591    ///
592    /// println!("Add emoji reaction response: {res:?}");
593    /// # };
594    /// ```
595    pub async fn add_reaction(
596        &self,
597        req: &emoji_reaction::Request,
598    ) -> Result<emoji_reaction::Response> {
599        let url = format!(
600            "{}/{}/reactions",
601            self.endpoint(Endpoint::Reaction),
602            req.message_id()
603        );
604
605        let res = self
606            .http
607            .post(url)
608            .header("Authorization", self.auth_header())
609            .form(req)
610            .send()
611            .await?;
612
613        let bytes = res.bytes().await?;
614        let body = str::from_utf8(&bytes[..])?;
615
616        log::debug!("Add emoji reaction response: {body}");
617
618        serde_json::from_str::<emoji_reaction::Response>(body).map_err(Error::from)
619    }
620
621    /// Removes an emoji reaction from a message on the Zulip server.
622    ///
623    /// # Example
624    ///
625    /// ```rust,no_run
626    /// use zulip_api::client::Client;
627    /// use zulip_api::emoji_reaction;
628    ///
629    /// # async {
630    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
631    ///
632    /// let req = emoji_reaction::Request::new()
633    ///     .with_message_id(1)
634    ///     .with_emoji_name("+1");
635    ///
636    /// let res = client.remove_reaction(&req).await.unwrap();
637    ///
638    /// println!("Remove emoji reaction response: {res:?}");
639    /// # };
640    /// ```
641    pub async fn remove_reaction(
642        &self,
643        req: &emoji_reaction::Request,
644    ) -> Result<emoji_reaction::Response> {
645        let url = format!(
646            "{}/{}/reactions",
647            self.endpoint(Endpoint::Reaction),
648            req.message_id()
649        );
650
651        let res = self
652            .http
653            .delete(url)
654            .header("Authorization", self.auth_header())
655            .form(req)
656            .send()
657            .await?;
658
659        let bytes = res.bytes().await?;
660        let body = str::from_utf8(&bytes[..])?;
661
662        log::debug!("Remove emoji reaction response: {body}");
663
664        serde_json::from_str::<emoji_reaction::Response>(body).map_err(Error::from)
665    }
666
667    /// Renders a message from the Zulip server into HTML.
668    ///
669    /// # Example
670    ///
671    /// ```rust,no_run
672    /// use zulip_api::client::Client;
673    /// use zulip_api::render;
674    ///
675    /// # async {
676    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
677    ///
678    /// let req = render::Request::new()
679    ///     .with_content("test message");
680    ///
681    /// let res = client.render(&req).await.unwrap();
682    ///
683    /// println!("Render response: {res:?}");
684    /// # };
685    /// ```
686    pub async fn render(&self, req: &render::Request) -> Result<render::Response> {
687        let res = self
688            .http
689            .post(self.endpoint(Endpoint::Render))
690            .header("Authorization", self.auth_header())
691            .form(req)
692            .send()
693            .await?;
694
695        let bytes = res.bytes().await?;
696        let body = str::from_utf8(&bytes[..])?;
697
698        log::debug!("Render response: {body}");
699
700        serde_json::from_str::<render::Response>(body).map_err(Error::from)
701    }
702
703    /// Update message flags on the Zulip server.
704    ///
705    /// # Example
706    ///
707    /// ```rust,no_run
708    /// use zulip_api::client::Client;
709    /// use zulip_api::{update_message_flags, Flag, Operation};
710    ///
711    /// # async {
712    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
713    ///
714    /// let req = update_message_flags::Request::new()
715    ///     .with_messages([1u64].as_ref())
716    ///     .with_op(Operation::Add)
717    ///     .with_flag(Flag::Read);
718    ///
719    /// let res = client.update_message_flags(&req).await.unwrap();
720    ///
721    /// println!("Update message flags response: {res}");
722    /// # };
723    /// ```
724    pub async fn update_message_flags(
725        &self,
726        req: &update_message_flags::Request,
727    ) -> Result<update_message_flags::Response> {
728        let form = reqwest::multipart::Form::new()
729            .text("messages", serde_json::to_string(req.messages())?)
730            .text("op", req.op().as_str())
731            .text("flag", req.flag().as_str());
732
733        let res = self
734            .http
735            .post(self.endpoint(Endpoint::UpdateMessageFlags))
736            .header("Authorization", self.auth_header())
737            .multipart(form)
738            .send()
739            .await?;
740
741        let bytes = res.bytes().await?;
742        let body = str::from_utf8(&bytes[..])?;
743
744        log::debug!("Update message flags response: {body}");
745
746        serde_json::from_str::<update_message_flags::Response>(body).map_err(Error::from)
747    }
748
749    /// Update message flags for a narrow on the Zulip server.
750    ///
751    /// # Example
752    ///
753    /// ```rust,no_run
754    /// use zulip_api::client::Client;
755    /// use zulip_api::{update_message_flags_narrow, Flag, Operation};
756    /// use zulip_api::filter::{Anchor, Narrow};
757    ///
758    /// # async {
759    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
760    ///
761    /// let req = update_message_flags_narrow::Request::new()
762    ///     .with_anchor(Anchor::new())
763    ///     .with_include_anchor(true)
764    ///     .with_num_before(1)
765    ///     .with_num_after(1)
766    ///     .with_narrow(vec![Narrow::new()])
767    ///     .with_op(Operation::Add)
768    ///     .with_flag(Flag::Read);
769    ///
770    /// let res = client.update_message_flags_narrow(&req).await.unwrap();
771    ///
772    /// println!("Update message flags for narrow response: {res}");
773    /// # };
774    /// ```
775    pub async fn update_message_flags_narrow(
776        &self,
777        req: &update_message_flags_narrow::Request,
778    ) -> Result<update_message_flags_narrow::Response> {
779        let mut form = reqwest::multipart::Form::new()
780            .text("anchor", serde_json::to_string(&req.anchor())?)
781            .text("num_before", format!("{}", req.num_before()))
782            .text("num_after", format!("{}", req.num_after()))
783            .text("narrow", serde_json::to_string(req.narrow())?)
784            .text("op", req.op().as_str())
785            .text("flag", req.flag().as_str());
786
787        if let Some(include_anchor) = req.include_anchor() {
788            form = form.text("include_anchor", format!("{include_anchor}"));
789        }
790
791        let res = self
792            .http
793            .post(self.endpoint(Endpoint::UpdateMessageFlagsNarrow))
794            .header("Authorization", self.auth_header())
795            .multipart(form)
796            .send()
797            .await?;
798
799        let bytes = res.bytes().await?;
800        let body = str::from_utf8(&bytes[..])?;
801
802        log::debug!("Update message flags for narrow response: {body}");
803
804        serde_json::from_str::<update_message_flags_narrow::Response>(body).map_err(Error::from)
805    }
806
807    /// Marks all messages as read on the Zulip server.
808    ///
809    /// # Example
810    ///
811    /// ```rust,no_run
812    /// use zulip_api::client::Client;
813    ///
814    /// # async {
815    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
816    ///
817    /// let res = client.mark_all_as_read().await.unwrap();
818    ///
819    /// println!("Mark all as read response: {res:?}");
820    /// # };
821    /// ```
822    pub async fn mark_all_as_read(&self) -> Result<mark_all_as_read::Response> {
823        let res = self
824            .http
825            .post(self.endpoint(Endpoint::MarkAllAsRead))
826            .header("Authorization", self.auth_header())
827            .send()
828            .await?;
829
830        let bytes = res.bytes().await?;
831        let body = str::from_utf8(&bytes[..])?;
832
833        log::debug!("Mark all as read response: {body}");
834
835        serde_json::from_str::<mark_all_as_read::Response>(body).map_err(Error::from)
836    }
837
838    /// Marks all messages in a channel as read on the Zulip server.
839    ///
840    /// # Example
841    ///
842    /// ```rust,no_run
843    /// use zulip_api::client::Client;
844    /// use zulip_api::mark_stream_as_read;
845    ///
846    /// # async {
847    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
848    ///
849    /// let req = mark_stream_as_read::Request::new().with_stream_id(1);
850    ///
851    /// let res = client.mark_stream_as_read(&req).await.unwrap();
852    ///
853    /// println!("Mark stream as read response: {res:?}");
854    /// # };
855    /// ```
856    pub async fn mark_stream_as_read(
857        &self,
858        req: &mark_stream_as_read::Request,
859    ) -> Result<mark_stream_as_read::Response> {
860        let res = self
861            .http
862            .post(self.endpoint(Endpoint::MarkStreamAsRead))
863            .header("Authorization", self.auth_header())
864            .form(req)
865            .send()
866            .await?;
867
868        let bytes = res.bytes().await?;
869        let body = str::from_utf8(&bytes[..])?;
870
871        log::debug!("Mark stream as read response: {body}");
872
873        serde_json::from_str::<mark_stream_as_read::Response>(body).map_err(Error::from)
874    }
875
876    /// Marks all messages in a topic as read on the Zulip server.
877    ///
878    /// # Example
879    ///
880    /// ```rust,no_run
881    /// use zulip_api::client::Client;
882    /// use zulip_api::mark_topic_as_read;
883    ///
884    /// # async {
885    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
886    ///
887    /// let req = mark_topic_as_read::Request::new()
888    ///     .with_stream_id(1)
889    ///     .with_topic_name("test topic");
890    ///
891    /// let res = client.mark_topic_as_read(&req).await.unwrap();
892    ///
893    /// println!("Mark topic as read response: {res:?}");
894    /// # };
895    /// ```
896    pub async fn mark_topic_as_read(
897        &self,
898        req: &mark_topic_as_read::Request,
899    ) -> Result<mark_topic_as_read::Response> {
900        let form = reqwest::multipart::Form::new()
901            .text("stream_id", format!("{}", req.stream_id()))
902            .text("topic_name", req.topic_name().to_string());
903
904        let res = self
905            .http
906            .post(self.endpoint(Endpoint::MarkTopicAsRead))
907            .header("Authorization", self.auth_header())
908            .multipart(form)
909            .send()
910            .await?;
911
912        let bytes = res.bytes().await?;
913        let body = str::from_utf8(&bytes[..])?;
914
915        log::debug!("Mark topic as read response: {body}");
916
917        serde_json::from_str::<mark_topic_as_read::Response>(body).map_err(Error::from)
918    }
919
920    /// Gets a message's read receipts on the Zulip server.
921    ///
922    /// # Example
923    ///
924    /// ```rust,no_run
925    /// use zulip_api::client::Client;
926    /// use zulip_api::get_read_receipts;
927    ///
928    /// # async {
929    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
930    ///
931    /// let req = get_read_receipts::Request::new()
932    ///     .with_message_id(1);
933    ///
934    /// let res = client.get_read_receipts(&req).await.unwrap();
935    ///
936    /// println!("Get read receipts response: {res:?}");
937    /// # };
938    /// ```
939    pub async fn get_read_receipts(
940        &self,
941        req: &get_read_receipts::Request,
942    ) -> Result<get_read_receipts::Response> {
943        let endpoint = self.endpoint(Endpoint::GetMessage);
944        let msg_id = req.message_id();
945
946        let res = self
947            .http
948            .get(format!("{endpoint}/{msg_id}/read_receipts"))
949            .header("Authorization", self.auth_header())
950            .query(&req)
951            .send()
952            .await?;
953
954        let bytes = res.bytes().await?;
955        let body = str::from_utf8(&bytes[..])?;
956
957        log::debug!("Get read receipts response: {body}");
958
959        serde_json::from_str::<get_read_receipts::Response>(body).map_err(Error::from)
960    }
961
962    /// Registers an event queue with the Zulip server.
963    ///
964    /// # Example
965    ///
966    /// ```rust,no_run
967    /// use zulip_api::client::Client;
968    /// use zulip_api::register;
969    ///
970    /// # async {
971    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
972    ///
973    /// let req = register::Request::new();
974    ///
975    /// let res = client.register(&req).await.unwrap();
976    ///
977    /// println!("Register response: {res:?}");
978    /// # };
979    /// ```
980    pub async fn register(&self, req: &register::Request) -> Result<register::Response> {
981        let res = self
982            .http
983            .post(self.endpoint(Endpoint::Register))
984            .header("Authorization", self.auth_header())
985            .json(req)
986            .send()
987            .await?;
988
989        let bytes = res.bytes().await?;
990        let body = str::from_utf8(&bytes[..])?;
991
992        log::debug!("Register response: {body}");
993
994        serde_json::from_str::<register::Response>(body).map_err(Error::from)
995    }
996
997    /// Fetches a user's API key using a username-password authentication method with the Zulip server.
998    ///
999    /// # Example
1000    ///
1001    /// ```rust,no_run
1002    /// use zulip_api::client::Client;
1003    /// use zulip_api::fetch_api_key;
1004    ///
1005    /// # async {
1006    /// let client = Client::from_zuliprc(".zuliprc").unwrap();
1007    ///
1008    /// let algo = jwt::Algorithm::HS256;
1009    /// let key = jwt::EncodingKey::from_base64_secret("MWFlMTgyZjg3NzM1MzMyYmUxNDEwYWMyM2RlMDRkNzRlOWIwNmI4Mjc2YjQwMmZmYWMzNjllNTA5ZThiN2I0Mg==").unwrap();
1010    /// let email = fetch_api_key::Email::new("user@example.dev");
1011    /// let password = fetch_api_key::Password::new("super_secret_password");
1012    /// let req = fetch_api_key::Request::create(algo, &key, &email, &password).unwrap();
1013    ///
1014    /// let res = client.fetch_api_key(&req).await.unwrap();
1015    ///
1016    /// println!("Fetch API key response: {res:?}");
1017    /// # };
1018    /// ```
1019    pub async fn fetch_api_key(
1020        &self,
1021        req: &fetch_api_key::Request,
1022    ) -> Result<fetch_api_key::Response> {
1023        let res = self
1024            .http
1025            .post(self.endpoint(Endpoint::FetchApiKey))
1026            .form(req)
1027            .send()
1028            .await?;
1029
1030        let bytes = res.bytes().await?;
1031        let body = str::from_utf8(&bytes[..])?;
1032
1033        log::debug!("Fetch API key response: {body}");
1034
1035        serde_json::from_str::<fetch_api_key::Response>(body).map_err(Error::from)
1036    }
1037
1038    /// Gets the URL for the provided [Endpoint].
1039    pub fn endpoint(&self, endpoint: Endpoint) -> String {
1040        let scheme = self.scheme();
1041        let host = self.host();
1042
1043        self.port()
1044            .map(|port| format!("{scheme}://{host}:{port}/{endpoint}"))
1045            .unwrap_or_else(|| format!("{scheme}://{host}/{endpoint}"))
1046    }
1047
1048    /// Gets the base URL for the Zulip API server.
1049    pub fn base_url(&self) -> String {
1050        let scheme = self.scheme();
1051        let host = self.host();
1052
1053        self.port()
1054            .map(|port| format!("{scheme}://{host}:{port}"))
1055            .unwrap_or_else(|| format!("{scheme}://{host}"))
1056    }
1057
1058    /// Gets the Zulip server's URL scheme.
1059    pub(crate) fn scheme(&self) -> &str {
1060        self.url.scheme_str().unwrap_or("http")
1061    }
1062
1063    /// Gets the Zulip server's URL host.
1064    pub(crate) fn host(&self) -> &str {
1065        self.url.host().unwrap_or("chat.zulip.org")
1066    }
1067
1068    /// Gets the Zulip server's URL port.
1069    pub(crate) fn port(&self) -> Option<u16> {
1070        self.url.port_u16()
1071    }
1072}