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: ®ister::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}