1use crate::attachments::{Attachment, AttachmentSource, InlineKeyboard};
2use crate::client::MaxClient;
3use crate::error::Result;
4use crate::utils;
5use serde_json::json;
6use std::time::Duration;
7
8#[derive(Debug, Clone, Default)]
17pub struct SendMessageParams {
18 pub chat_id: Option<i64>,
20 pub user_id: Option<i64>,
22 pub text: String,
24 pub format: Option<String>,
26 pub notify: Option<bool>,
28 pub disable_link_preview: Option<bool>,
30 pub reply_mid: Option<String>,
32 pub attachments: Vec<Attachment>,
34 pub max_retries: usize,
36}
37
38pub struct SendMessageParamsBuilder {
59 chat_id: Option<i64>,
60 user_id: Option<i64>,
61 text: String,
62 format: Option<String>,
63 notify: Option<bool>,
64 disable_link_preview: Option<bool>,
65 reply_mid: Option<String>,
66 attachments: Vec<Attachment>,
67 max_retries: usize,
68}
69
70impl SendMessageParamsBuilder {
71 pub fn new() -> Self {
73 Self {
74 text: String::new(),
75 chat_id: None,
76 user_id: None,
77 format: None,
78 notify: Some(true),
79 disable_link_preview: None,
80 reply_mid: None,
81 attachments: vec![],
82 max_retries: 3,
83 }
84 }
85
86 pub fn text(mut self, text: impl Into<String>) -> Self {
88 self.text = text.into();
89 self
90 }
91
92 pub fn chat_id(mut self, id: i64) -> Self {
94 self.chat_id = Some(id);
95 self
96 }
97
98 pub fn user_id(mut self, id: i64) -> Self {
100 self.user_id = Some(id);
101 self
102 }
103
104 pub fn format_markdown(mut self) -> Self {
106 self.format = Some("markdown".to_string());
107 self
108 }
109
110 pub fn format_html(mut self) -> Self {
112 self.format = Some("html".to_string());
113 self
114 }
115
116 pub fn silent(mut self) -> Self {
118 self.notify = Some(false);
119 self
120 }
121
122 pub fn disable_preview(mut self) -> Self {
124 self.disable_link_preview = Some(true);
125 self
126 }
127
128 pub fn reply_to(mut self, mid: impl Into<String>) -> Self {
134 self.reply_mid = Some(mid.into());
135 self
136 }
137
138 pub fn attachment(mut self, att: Attachment) -> Self {
140 self.attachments.push(att);
141 self
142 }
143
144 pub fn attachments(mut self, atts: Vec<Attachment>) -> Self {
146 self.attachments = atts;
147 self
148 }
149
150 pub fn max_retries(mut self, retries: usize) -> Self {
152 self.max_retries = retries;
153 self
154 }
155
156 pub fn build(self) -> SendMessageParams {
158 SendMessageParams {
159 chat_id: self.chat_id,
160 user_id: self.user_id,
161 text: self.text,
162 format: self.format,
163 notify: self.notify,
164 disable_link_preview: self.disable_link_preview,
165 reply_mid: self.reply_mid,
166 attachments: self.attachments,
167 max_retries: self.max_retries,
168 }
169 }
170}
171
172impl Default for SendMessageParamsBuilder {
173 fn default() -> Self {
174 Self::new()
175 }
176}
177
178#[derive(Debug, Clone, Default)]
184pub struct GetMessagesParams {
185 pub chat_id: i64,
187 pub marker: Option<String>,
189 pub count: Option<u32>,
191}
192
193#[derive(Debug, Clone, Default)]
199pub struct EditMessageParams {
200 pub text: Option<String>,
202 pub format: Option<String>,
204 pub attachments: Vec<Attachment>,
206 pub inline_keyboard: Option<InlineKeyboard>,
208 pub disable_link_preview: Option<bool>,
210}
211
212pub(crate) async fn prepare_attachments_json(
228 client: &MaxClient,
229 attachments: &[Attachment],
230) -> Result<serde_json::Value> {
231 let mut attachments_json = Vec::new();
232
233 for att in attachments {
234 match att {
235 Attachment::Image { source, token, url } => {
236 if let Some(url_str) = url {
238 attachments_json.push(serde_json::json!({
239 "type": "image",
240 "payload": { "url": url_str }
241 }));
242 } else {
243 let final_token = if let Some(tok) = token {
244 tok.clone()
245 } else {
246 match source {
247 AttachmentSource::LocalFile(path) => {
248 let typ = att.get_type();
249 let tok = client.upload_file(path, typ).await?;
250 tokio::time::sleep(Duration::from_millis(500)).await;
251 tok
252 }
253 AttachmentSource::Token(tok) => tok.clone(),
254 }
255 };
256 attachments_json.push(serde_json::json!({
257 "type": "image",
258 "payload": { "token": final_token }
259 }));
260 }
261 }
262 Attachment::Video { source, token, url, filename } |
263 Attachment::Audio { source, token, url, filename } |
264 Attachment::File { source, token, url, filename } => {
265 if let Some(url_str) = url {
267 let file_name = filename.as_deref().ok_or_else(|| {
268 crate::error::Error::InvalidInput("Missing filename for URL attachment".into())
269 })?;
270 let temp_path = client.download_to_temp_dir(url_str, file_name).await?;
271 let typ = att.get_type();
272 let token = client.upload_file(&temp_path, typ).await?;
273 tokio::time::sleep(Duration::from_millis(500)).await;
274 let _ = tokio::fs::remove_file(&temp_path).await;
276 let _ = tokio::fs::remove_dir(temp_path.parent().unwrap()).await;
277 attachments_json.push(serde_json::json!({
278 "type": typ,
279 "payload": { "token": token }
280 }));
281 } else {
282 let final_token = if let Some(tok) = token {
283 tok.clone()
284 } else {
285 match source {
286 AttachmentSource::LocalFile(path) => {
287 let typ = att.get_type();
288 let tok = client.upload_file(path, typ).await?;
289 tokio::time::sleep(Duration::from_millis(500)).await;
290 tok
291 }
292 AttachmentSource::Token(tok) => tok.clone(),
293 }
294 };
295 attachments_json.push(serde_json::json!({
296 "type": att.get_type(),
297 "payload": { "token": final_token }
298 }));
299 }
300 }
301 Attachment::Sticker { code } => {
302 attachments_json.push(serde_json::json!({
303 "type": "sticker",
304 "payload": { "code": code }
305 }));
306 }
307 Attachment::Contact(data) => {
308 let mut payload = serde_json::Map::new();
309 if let Some(name) = &data.name {
310 payload.insert("name".into(), name.clone().into());
311 }
312 if let Some(contact_id) = data.contact_id {
313 payload.insert("contact_id".into(), contact_id.into());
314 }
315 if let Some(vcf_info) = &data.vcf_info {
316 payload.insert("vcf_info".into(), vcf_info.clone().into());
317 }
318 if let Some(vcf_phone) = &data.vcf_phone {
319 payload.insert("vcf_phone".into(), vcf_phone.clone().into());
320 }
321 attachments_json.push(serde_json::json!({
322 "type": "contact",
323 "payload": payload
324 }));
325 }
326 Attachment::InlineKeyboard(keyboard) => {
327 let mut buttons_json = Vec::new();
328 for row in &keyboard.buttons {
329 let mut row_json = Vec::new();
330 for btn in row {
331 let mut btn_json = serde_json::Map::new();
332 btn_json.insert("type".into(), btn.r#type.clone().into());
333 btn_json.insert("text".into(), btn.text.clone().into());
334 if let Some(payload) = &btn.payload {
335 btn_json.insert("payload".into(), payload.clone().into());
336 }
337 if let Some(url) = &btn.url {
338 btn_json.insert("url".into(), url.clone().into());
339 }
340 if let Some(quick) = btn.quick {
341 btn_json.insert("quick".into(), quick.into());
342 }
343 if let Some(web_app) = &btn.web_app {
344 btn_json.insert("web_app".into(), web_app.clone().into());
345 }
346 if let Some(contact_id) = btn.contact_id {
347 btn_json.insert("contact_id".into(), contact_id.into());
348 }
349 if let Some(app_payload) = &btn.app_payload {
350 btn_json.insert("payload".into(), app_payload.clone().into());
351 }
352 if let Some(msg_text) = &btn.message_text {
353 btn_json.insert("text".into(), msg_text.clone().into());
354 }
355 row_json.push(serde_json::Value::Object(btn_json));
356 }
357 buttons_json.push(serde_json::Value::Array(row_json));
358 }
359 attachments_json.push(serde_json::json!({
360 "type": "inline_keyboard",
361 "payload": { "buttons": buttons_json }
362 }));
363 }
364 Attachment::Location { latitude, longitude } => {
365 attachments_json.push(serde_json::json!({
366 "type": "location",
367 "payload": { "latitude": latitude, "longitude": longitude }
368 }));
369 }
370 Attachment::Share(data) => {
371 let mut payload = serde_json::Map::new();
372 if let Some(url) = &data.url {
373 payload.insert("url".into(), url.clone().into());
374 }
375 if let Some(token) = &data.token {
376 payload.insert("token".into(), token.clone().into());
377 }
378 attachments_json.push(serde_json::json!({
379 "type": "share",
380 "payload": payload
381 }));
382 }
383 }
384 }
385
386 Ok(serde_json::Value::Array(attachments_json))
387}
388
389impl MaxClient {
394 pub async fn send_message(&self, params: SendMessageParams) -> Result<Vec<String>> {
410 if (params.chat_id.is_none() && params.user_id.is_none()) ||
412 (params.chat_id.is_some() && params.user_id.is_some()) {
413 return Err(crate::error::Error::InvalidInput(
414 "Exactly one of chat_id or user_id must be specified".into(),
415 ));
416 }
417
418 let mut ready_attachments = Vec::new(); let mut content_attachments = Vec::new(); let mut keyboard_attachments = Vec::new(); for att in ¶ms.attachments {
424 match att {
425 Attachment::Image { source, token, url } => {
427 let final_token = if let Some(tok) = token {
428 tok.clone()
429 } else {
430 match source {
431 AttachmentSource::LocalFile(path) => {
432 let file_type = att.get_type();
433 let tok = self.upload_file(path, file_type).await?;
434 tokio::time::sleep(Duration::from_millis(500)).await; tok
436 }
437 AttachmentSource::Token(tok) => tok.clone(),
438 }
439 };
440
441 if let Some(url_str) = url {
443 content_attachments
444 .push((att.get_type(), json!({ "payload": { "url": url_str } })));
445 } else {
446 ready_attachments.push((att.get_type(), final_token));
447 }
448 }
449 Attachment::Video { source, token, url, filename } |
450 Attachment::Audio { source, token, url, filename } |
451 Attachment::File { source, token, url, filename } => {
452 if let Some(url_str) = url {
454 let file_name = filename.as_deref().ok_or_else(|| {
455 crate::error::Error::InvalidInput("Missing filename for URL attachment".into())
456 })?;
457 let temp_path = self.download_to_temp_dir(url_str, file_name).await?;
458 let typ = att.get_type();
459 let token = self.upload_file(&temp_path, typ).await?;
460 tokio::time::sleep(Duration::from_millis(500)).await;
461 let _ = tokio::fs::remove_file(&temp_path).await;
463 let _ = tokio::fs::remove_dir(temp_path.parent().unwrap()).await;
464 ready_attachments.push((typ, token));
465 } else {
466 let final_token = if let Some(tok) = token {
467 tok.clone()
468 } else {
469 match source {
470 AttachmentSource::LocalFile(path) => {
471 let typ = att.get_type();
472 let tok = self.upload_file(path, typ).await?;
473 tokio::time::sleep(Duration::from_millis(500)).await;
474 tok
475 }
476 AttachmentSource::Token(tok) => tok.clone(),
477 }
478 };
479 ready_attachments.push((att.get_type(), final_token));
480 }
481 }
482 Attachment::Sticker { code } => {
484 let payload = json!({ "code": code });
485 content_attachments.push((att.get_type(), json!({ "payload": payload })));
486 }
487 Attachment::Contact(data) => {
489 let mut payload = serde_json::Map::new();
490 if let Some(name) = &data.name {
491 payload.insert("name".into(), name.clone().into());
492 }
493 if let Some(contact_id) = data.contact_id {
494 payload.insert("contact_id".into(), contact_id.into());
495 }
496 if let Some(vcf_info) = &data.vcf_info {
497 payload.insert("vcf_info".into(), vcf_info.clone().into());
498 }
499 if let Some(vcf_phone) = &data.vcf_phone {
500 payload.insert("vcf_phone".into(), vcf_phone.clone().into());
501 }
502 content_attachments.push((att.get_type(), json!({ "payload": payload })));
503 }
504 Attachment::InlineKeyboard(keyboard) => {
506 let mut buttons_json = Vec::new();
507 for row in &keyboard.buttons {
508 let mut row_json = Vec::new();
509 for btn in row {
510 let mut btn_json = serde_json::Map::new();
511 btn_json.insert("type".into(), btn.r#type.clone().into());
512 btn_json.insert("text".into(), btn.text.clone().into());
513 if let Some(payload) = &btn.payload {
514 btn_json.insert("payload".into(), payload.clone().into());
515 }
516 if let Some(url) = &btn.url {
517 btn_json.insert("url".into(), url.clone().into());
518 }
519 if let Some(quick) = btn.quick {
520 btn_json.insert("quick".into(), quick.into());
521 }
522 if let Some(web_app) = &btn.web_app {
523 btn_json.insert("web_app".into(), web_app.clone().into());
524 }
525 if let Some(contact_id) = btn.contact_id {
526 btn_json.insert("contact_id".into(), contact_id.into());
527 }
528 if let Some(app_payload) = &btn.app_payload {
529 btn_json.insert("payload".into(), app_payload.clone().into());
530 }
531 if let Some(msg_text) = &btn.message_text {
532 btn_json.insert("text".into(), msg_text.clone().into());
533 }
534 row_json.push(serde_json::Value::Object(btn_json));
535 }
536 buttons_json.push(serde_json::Value::Array(row_json));
537 }
538 let payload = json!({ "buttons": buttons_json });
539 keyboard_attachments.push((att.get_type(), json!({ "payload": payload })));
540 }
541 Attachment::Location { latitude, longitude } => {
543 let payload = json!({ "latitude": latitude, "longitude": longitude });
544 content_attachments.push((att.get_type(), payload));
545 }
546 Attachment::Share(data) => {
548 let mut payload = serde_json::Map::new();
549 if let Some(url) = &data.url {
550 payload.insert("url".into(), url.clone().into());
551 }
552 if let Some(token) = &data.token {
553 payload.insert("token".into(), token.clone().into());
554 }
555 content_attachments.push((att.get_type(), json!({ "payload": payload })));
556 }
557 }
558 }
559
560 let fragments = utils::split_text(¶ms.text, params.format.as_deref());
562 let fragments = if fragments.is_empty() &&
563 ready_attachments.is_empty() &&
564 content_attachments.is_empty()
565 {
566 return Err(crate::error::Error::InvalidInput(
567 "Empty message text and no attachments".into(),
568 ));
569 } else if fragments.is_empty() {
570 vec!["".to_string()]
571 } else {
572 fragments
573 };
574
575 let mut get_params = Vec::new();
577 if let Some(disable) = params.disable_link_preview {
578 get_params.push(("disable_link_preview", disable.to_string()));
579 }
580
581 let reply_link = params
583 .reply_mid
584 .as_ref()
585 .map(|mid| json!({ "type": "reply", "mid": mid }));
586
587 let mut sent_ids = Vec::new();
588
589 for (i, fragment_text) in fragments.iter().enumerate() {
590 let mut body = json!({
591 "text": fragment_text,
592 });
593 if let Some(fmt) = ¶ms.format {
594 body["format"] = json!(fmt);
595 }
596 if let Some(notify) = params.notify {
597 body["notify"] = json!(notify);
598 }
599
600 if i == 0 {
602 if !body["attachments"].is_array() {
603 body["attachments"] = serde_json::Value::Array(vec![]);
604 }
605 let attachments_array = body["attachments"].as_array_mut().unwrap();
606 for (att_type, token) in &ready_attachments {
607 attachments_array.push(json!({
608 "type": att_type,
609 "payload": { "token": token }
610 }));
611 }
612 for (att_type, payload) in &content_attachments {
613 let mut attachment = json!({ "type": att_type });
614 if let Some(p) = payload.as_object() {
615 for (k, v) in p {
616 attachment[k] = v.clone();
617 }
618 } else {
619 attachment["payload"] = payload.clone();
620 }
621 attachments_array.push(attachment);
622 }
623 if let Some(link) = &reply_link {
624 body["link"] = link.clone();
625 }
626 }
627 if i == fragments.len() - 1 && !keyboard_attachments.is_empty() {
629 if !body["attachments"].is_array() {
630 body["attachments"] = serde_json::Value::Array(vec![]);
631 }
632 let attachments_array = body["attachments"].as_array_mut().unwrap();
633 for (att_type, payload) in &keyboard_attachments {
634 let mut attachment = json!({ "type": att_type });
635 if let Some(p) = payload.as_object() {
636 for (k, v) in p {
637 attachment[k] = v.clone();
638 }
639 } else {
640 attachment["payload"] = payload.clone();
641 }
642 attachments_array.push(attachment);
643 }
644 }
645
646 match self.send_fragment_with_retry(
647 params.chat_id,
648 params.user_id,
649 body,
650 &get_params,
651 i == 0, params.max_retries,
653 ).await {
654 Ok(mid) => sent_ids.push(mid),
655 Err(e) => {
656 if !sent_ids.is_empty() {
658 let _ = self.delete_messages(&sent_ids).await;
659 }
660 return Err(e);
661 }
662 }
663 }
664
665 Ok(sent_ids)
666 }
667
668 pub async fn send_message_builder(
672 &self,
673 builder: SendMessageParamsBuilder,
674 ) -> Result<Vec<String>> {
675 self.send_message(builder.build()).await
676 }
677
678 pub async fn forward_message(
691 &self,
692 chat_id: Option<i64>,
693 user_id: Option<i64>,
694 forward_mid: &str,
695 notify: Option<bool>,
696 disable_link_preview: Option<bool>,
697 ) -> Result<String> {
698 if (chat_id.is_none() && user_id.is_none()) ||
700 (chat_id.is_some() && user_id.is_some()) {
701 return Err(crate::error::Error::InvalidInput(
702 "Exactly one of chat_id or user_id must be specified".into(),
703 ));
704 }
705
706 let mut query = Vec::new();
707 if let Some(cid) = chat_id {
708 query.push(("chat_id", cid.to_string()));
709 }
710 if let Some(uid) = user_id {
711 query.push(("user_id", uid.to_string()));
712 }
713 if let Some(disable) = disable_link_preview {
714 query.push(("disable_link_preview", disable.to_string()));
715 }
716 query.push(("message_id", forward_mid.to_string()));
718
719 let body = json!({
720 "link": {
721 "type": "forward",
722 "mid": forward_mid
723 },
724 "notify": notify.unwrap_or(true),
725 });
726
727 let resp: serde_json::Value = self.request_with_rate_limit(
728 reqwest::Method::POST,
729 "/messages",
730 &query,
731 Some(body),
732 ).await?;
733
734 let new_mid = resp["message"]["body"]["mid"]
735 .as_str()
736 .ok_or_else(|| crate::error::Error::Api {
737 code: 500,
738 message: "No message ID in forward response".into(),
739 })?;
740 Ok(new_mid.to_string())
741 }
742
743 pub async fn delete_message(&self, message_id: &str) -> Result<()> {
745 self.request_with_rate_limit::<serde_json::Value>(
746 reqwest::Method::DELETE,
747 "/messages",
748 &[("message_id", message_id.to_string())],
749 None,
750 ).await?;
751 Ok(())
752 }
753
754 pub async fn delete_messages(&self, message_ids: &[String]) -> Result<()> {
758 for id in message_ids {
759 if let Err(e) = self.delete_message(id).await {
760 eprintln!("Warning: failed to delete message {}: {}", id, e);
761 }
762 }
763 Ok(())
764 }
765
766 pub async fn get_messages(
793 &self,
794 params: GetMessagesParams,
795 ) -> Result<(Vec<crate::types::Message>, Option<String>)> {
796 let mut query = vec![("chat_id", params.chat_id.to_string())];
797 if let Some(m) = ¶ms.marker {
798 query.push(("marker", m.clone()));
799 }
800 if let Some(c) = params.count {
801 query.push(("count", c.to_string()));
802 }
803 let resp: serde_json::Value = self
804 .request_with_rate_limit(reqwest::Method::GET, "/messages", &query, None)
805 .await?;
806 let messages: Vec<crate::types::Message> =
807 serde_json::from_value(resp["messages"].clone())?;
808 let next_marker = resp
809 .get("marker")
810 .and_then(|v| v.as_str())
811 .map(String::from);
812 Ok((messages, next_marker))
813 }
814
815 pub async fn edit_message(&self, message_mid: &str, params: EditMessageParams) -> Result<()> {
839 let mut body = serde_json::Map::new();
840 if let Some(text) = params.text {
841 body.insert("text".into(), text.into());
842 }
843 if let Some(format) = params.format {
844 body.insert("format".into(), format.into());
845 }
846 if let Some(disable) = params.disable_link_preview {
847 body.insert("disable_link_preview".into(), disable.into());
848 }
849
850 let mut all_attachments = params.attachments;
852 if let Some(keyboard) = params.inline_keyboard {
853 all_attachments.push(Attachment::InlineKeyboard(keyboard));
854 }
855
856 if !all_attachments.is_empty() {
857 let attachments_json = prepare_attachments_json(self, &all_attachments).await?;
858 body.insert("attachments".into(), attachments_json);
859 }
860
861 self.request_with_rate_limit::<serde_json::Value>(
862 reqwest::Method::PUT,
863 "/messages",
864 &[("message_id", message_mid.to_string())],
865 Some(serde_json::Value::Object(body)),
866 )
867 .await?;
868 Ok(())
869 }
870
871 pub async fn edit_multimessage(
911 &self,
912 chat_id: i64,
913 old_mids: &[String],
914 new_text: &str,
915 format: Option<&str>,
916 notify: Option<bool>,
917 disable_link_preview: Option<bool>,
918 reply_mid: Option<&str>,
919 attachments: Vec<Attachment>,
920 inline_keyboard: Option<InlineKeyboard>,
921 ) -> Result<Vec<String>> {
922 let fragments = utils::split_text(new_text, format);
924 if fragments.is_empty() {
925 return Err(crate::error::Error::InvalidInput(
926 "Empty message text after splitting".into(),
927 ));
928 }
929
930 let old_count = old_mids.len();
931 let new_count = fragments.len();
932
933 let first_attachments_json = if !attachments.is_empty() {
935 Some(prepare_attachments_json(self, &attachments).await?)
936 } else {
937 None
938 };
939 let keyboard_attachment_json = if let Some(keyboard) = inline_keyboard {
940 let kb_att = Attachment::InlineKeyboard(keyboard);
941 let json_arr = prepare_attachments_json(self, &[kb_att]).await?;
942 if let serde_json::Value::Array(mut arr) = json_arr {
944 arr.pop()
945 } else {
946 None
947 }
948 } else {
949 None
950 };
951
952 let mut new_ids = Vec::with_capacity(new_count);
953
954 for i in 0..std::cmp::min(old_count, new_count) {
956 let mut body = serde_json::Map::new();
957 body.insert("text".into(), fragments[i].clone().into());
958 if let Some(fmt) = format {
959 body.insert("format".into(), fmt.into());
960 }
961 if let Some(disable) = disable_link_preview {
962 body.insert("disable_link_preview".into(), disable.into());
963 }
964
965 let mut final_attachments = Vec::new();
966 if i == 0 {
967 if let Some(ref arr) = first_attachments_json {
968 if let serde_json::Value::Array(arr_vec) = arr {
969 final_attachments.extend(arr_vec.clone());
970 }
971 }
972 }
973 if i == new_count - 1 {
974 if let Some(kb) = &keyboard_attachment_json {
975 final_attachments.push(kb.clone());
976 }
977 }
978 if !final_attachments.is_empty() {
979 body.insert("attachments".into(), serde_json::Value::Array(final_attachments));
980 }
981
982 let query = [("message_id", old_mids[i].to_string())];
983 self.request_with_rate_limit::<serde_json::Value>(
984 reqwest::Method::PUT,
985 "/messages",
986 &query,
987 Some(serde_json::Value::Object(body)),
988 )
989 .await?;
990 new_ids.push(old_mids[i].clone());
991 }
992
993 if old_count > new_count {
995 let extra_mids = &old_mids[new_count..];
996 self.delete_messages(extra_mids).await?;
997 }
998
999 if new_count > old_count {
1001 let extra_fragments = &fragments[old_count..];
1002 for (i, text) in extra_fragments.iter().enumerate() {
1003 let mut builder = SendMessageParamsBuilder::new()
1004 .text(text)
1005 .chat_id(chat_id)
1006 .max_retries(3);
1007 if let Some(fmt) = format {
1008 if fmt == "markdown" {
1009 builder = builder.format_markdown();
1010 } else if fmt == "html" {
1011 builder = builder.format_html();
1012 }
1013 }
1014 if let Some(notify_val) = notify {
1015 if !notify_val {
1016 builder = builder.silent();
1017 }
1018 }
1019 if let Some(disable) = disable_link_preview {
1020 if disable {
1021 builder = builder.disable_preview();
1022 }
1023 }
1024 if i == 0 && reply_mid.is_some() {
1026 builder = builder.reply_to(reply_mid.unwrap());
1027 }
1028 let sent_ids = self.send_message_builder(builder).await?;
1029 new_ids.extend(sent_ids);
1030 }
1031 }
1032
1033 Ok(new_ids)
1034 }
1035
1036 async fn send_fragment_with_retry(
1041 &self,
1042 chat_id: Option<i64>,
1043 user_id: Option<i64>,
1044 body: serde_json::Value,
1045 get_params: &[(&str, String)],
1046 is_first: bool,
1047 max_retries: usize,
1048 ) -> Result<String> {
1049 let mut attempt = 0;
1050 let mut delay_sec = 1; let mut rate_limit_delay_sec = 5; loop {
1054 match self
1055 .send_single_fragment(chat_id, user_id, body.clone(), get_params)
1056 .await
1057 {
1058 Ok(mid) => return Ok(mid),
1059 Err(e) => {
1060 if !is_first {
1061 return Err(e);
1063 }
1064 attempt += 1;
1065 if attempt >= max_retries {
1066 return Err(e);
1067 }
1068 let err_str = e.to_string();
1069 if err_str.contains("429") || err_str.contains("too.many.requests") {
1070 tokio::time::sleep(Duration::from_secs(rate_limit_delay_sec)).await;
1071 rate_limit_delay_sec *= 2;
1072 continue;
1073 }
1074 if err_str.contains("attachment.not.ready") || err_str.contains("not.processed") {
1075 tokio::time::sleep(Duration::from_secs(delay_sec)).await;
1076 delay_sec *= 2;
1077 continue;
1078 }
1079 return Err(e);
1080 }
1081 }
1082 }
1083 }
1084
1085 async fn send_single_fragment(
1086 &self,
1087 chat_id: Option<i64>,
1088 user_id: Option<i64>,
1089 body: serde_json::Value,
1090 get_params: &[(&str, String)],
1091 ) -> Result<String> {
1092 let mut query = get_params.to_vec();
1093 if let Some(cid) = chat_id {
1094 query.push(("chat_id", cid.to_string()));
1095 }
1096 if let Some(uid) = user_id {
1097 query.push(("user_id", uid.to_string()));
1098 }
1099
1100 let resp: serde_json::Value = self.request_with_rate_limit(
1101 reqwest::Method::POST,
1102 "/messages",
1103 &query,
1104 Some(body),
1105 ).await?;
1106
1107 let mid = resp["message"]["body"]["mid"]
1108 .as_str()
1109 .ok_or_else(|| crate::error::Error::Api {
1110 code: 500,
1111 message: "No message ID in response (expected message.body.mid)".into(),
1112 })?;
1113 Ok(mid.to_string())
1114 }
1115}