1use crate::error::{VkError, VkResponseExt, VkResult};
2use crate::keyboard::Keyboard;
3use serde::Deserialize;
4use serde_json::Value;
5use std::collections::HashMap;
6use std::time::Duration;
7use tokio::time::sleep;
8
9#[cfg(feature = "reqwest")]
10use reqwest::Client;
11
12#[cfg(feature = "uuid")]
13use uuid::Uuid;
14
15#[derive(Debug, Clone)]
16pub struct VkApiConfig {
17 pub version: String,
18 pub timeout: u64,
19 pub max_retries: u32,
20 pub retry_delay: u64,
21 pub enable_logging: bool,
22 pub endpoint: String,
23}
24
25impl Default for VkApiConfig {
26 fn default() -> Self {
27 Self {
28 version: "5.199".to_string(),
29 timeout: 30,
30 max_retries: 3,
31 retry_delay: 1000,
32 enable_logging: false,
33 endpoint: "https://api.vk.com/method/".to_string(),
34 }
35 }
36}
37
38#[derive(Debug, Default)]
39pub struct VkApiBuilder {
40 token: Option<String>,
41 config: VkApiConfig,
42}
43
44impl VkApiBuilder {
45 pub fn new() -> Self {
46 Self::default()
47 }
48
49 pub fn token<T: Into<String>>(mut self, token: T) -> Self {
50 self.token = Some(token.into());
51 self
52 }
53
54 pub fn version<T: Into<String>>(mut self, version: T) -> Self {
55 self.config.version = version.into();
56 self
57 }
58
59 pub fn timeout(mut self, timeout: u64) -> Self {
60 self.config.timeout = timeout;
61 self
62 }
63
64 pub fn max_retries(mut self, max_retries: u32) -> Self {
65 self.config.max_retries = max_retries;
66 self
67 }
68
69 pub fn retry_delay(mut self, retry_delay: u64) -> Self {
70 self.config.retry_delay = retry_delay;
71 self
72 }
73
74 pub fn enable_logging(mut self, enable: bool) -> Self {
75 self.config.enable_logging = enable;
76 self
77 }
78
79 pub fn endpoint<T: Into<String>>(mut self, endpoint: T) -> Self {
80 self.config.endpoint = endpoint.into();
81 self
82 }
83
84 pub fn build(self) -> VkResult<VkApi> {
85 let token = self
86 .token
87 .ok_or_else(|| VkError::ConfigError("Token is required".to_string()))?;
88
89 #[cfg(feature = "reqwest")]
90 let client = Client::builder()
91 .timeout(Duration::from_secs(self.config.timeout))
92 .pool_max_idle_per_host(10)
93 .tcp_keepalive(Duration::from_secs(60))
94 .build()
95 .map_err(|e| VkError::NetworkError(e.to_string()))?;
96
97 Ok(VkApi {
98 #[cfg(feature = "reqwest")]
99 client,
100 token,
101 config: self.config,
102 })
103 }
104}
105
106#[derive(Debug, Clone)]
107pub struct VkApi {
108 #[cfg(feature = "reqwest")]
109 client: Client,
110 token: String,
111 config: VkApiConfig,
112}
113
114impl VkApi {
115 pub fn new<T: Into<String>>(token: T) -> VkResult<Self> {
116 VkApiBuilder::new().token(token).build()
117 }
118
119 pub fn builder() -> VkApiBuilder {
120 VkApiBuilder::new()
121 }
122
123 pub fn token(&self) -> &str {
124 &self.token
125 }
126
127 pub fn version(&self) -> &str {
128 &self.config.version
129 }
130
131 #[cfg(feature = "reqwest")]
132 async fn call_method_with_retry(
133 &self,
134 method: &str,
135 mut params: HashMap<String, String>,
136 ) -> VkResult<Value> {
137 let mut retries = 0;
138
139 params.insert("access_token".to_string(), self.token.clone());
140 params.insert("v".to_string(), self.config.version.clone());
141
142 loop {
143 match self.call_method_once(method, ¶ms).await {
144 Ok(response) => return Ok(response),
145 Err(e) => {
146 retries += 1;
147
148 if retries >= self.config.max_retries || !self.should_retry(&e) {
149 return Err(e);
150 }
151
152 let delay_ms = self.config.retry_delay * 2u64.pow(retries - 1);
153 sleep(Duration::from_millis(delay_ms)).await;
154 }
155 }
156 }
157 }
158
159 #[cfg(feature = "reqwest")]
160 fn should_retry(&self, error: &VkError) -> bool {
161 match error {
162 VkError::RateLimit => true,
163 VkError::NetworkError(_) => true,
164 VkError::Timeout(_) => true,
165 VkError::HttpError(e) => e
166 .status()
167 .map(|status| status.is_server_error() || status.as_u16() == 429)
168 .unwrap_or(false),
169 VkError::ApiError { code, .. } => {
170 matches!(*code, 6 | 9 | 10 | 14)
171 }
172 _ => false,
173 }
174 }
175
176 #[cfg(feature = "reqwest")]
177 async fn call_method_once(
178 &self,
179 method: &str,
180 params: &HashMap<String, String>,
181 ) -> VkResult<Value> {
182 let url = format!("{}{}", self.config.endpoint, method);
183
184 if self.config.enable_logging {
185 crate::vk_log!("API call: {} with {} params", method, params.len());
186 }
187
188 let response = self
189 .client
190 .post(&url)
191 .form(params)
192 .send()
193 .await
194 .map_err(|e| {
195 if e.is_timeout() {
196 VkError::Timeout(e.to_string())
197 } else {
198 VkError::HttpError(e)
199 }
200 })?;
201
202 self.handle_response(response).await
203 }
204
205 #[cfg(feature = "reqwest")]
206 async fn handle_response(&self, response: reqwest::Response) -> VkResult<Value> {
207 let status = response.status();
208
209 if status == 429 {
210 return Err(VkError::RateLimit);
211 }
212
213 if !status.is_success() {
214 return Err(VkError::HttpError(
215 response.error_for_status().err().unwrap(),
216 ));
217 }
218
219 let json_response: Value = response.json().await?;
220
221 if VkResponseExt::has_error(&json_response) {
222 return VkResponseExt::extract_error(json_response);
223 }
224
225 Ok(json_response)
226 }
227
228 #[cfg(feature = "reqwest")]
229 pub async fn call_method(
230 &self,
231 method: &str,
232 params: HashMap<String, String>,
233 ) -> VkResult<Value> {
234 self.call_method_with_retry(method, params).await
235 }
236
237 #[cfg(not(feature = "reqwest"))]
238 pub async fn call_method(
239 &self,
240 _method: &str,
241 _params: HashMap<String, String>,
242 ) -> VkResult<Value> {
243 Err(VkError::ConfigError(
244 "reqwest feature is required for API calls".to_string(),
245 ))
246 }
247
248 #[allow(clippy::too_many_arguments)]
249 pub async fn messages_send(
250 &self,
251 peer_id: i64,
252 message: &str,
253 keyboard: Option<&Keyboard>,
254 attachment: Option<&str>,
255 sticker_id: Option<i64>,
256 reply_to: Option<i64>,
257 forward_messages: Option<&[i64]>,
258 disable_mentions: bool,
259 dont_parse_links: bool,
260 random_id: Option<i64>,
261 ) -> VkResult<i64> {
262 let mut params = HashMap::new();
263 params.insert("peer_id".to_string(), peer_id.to_string());
264 params.insert("message".to_string(), message.to_string());
265
266 #[cfg(feature = "uuid")]
267 let random_id = random_id.unwrap_or_else(|| {
268 let uuid = Uuid::new_v4();
269 (uuid.as_u128() & 0x7FFFFFFFFFFFFFFF) as i64
270 });
271
272 #[cfg(not(feature = "uuid"))]
273 let random_id = random_id.unwrap_or(0);
274
275 params.insert("random_id".to_string(), random_id.to_string());
276
277 if let Some(keyboard) = keyboard {
278 params.insert("keyboard".to_string(), keyboard.to_json_string());
279 }
280
281 if let Some(attachment) = attachment {
282 params.insert("attachment".to_string(), attachment.to_string());
283 }
284
285 if let Some(sticker_id) = sticker_id {
286 params.insert("sticker_id".to_string(), sticker_id.to_string());
287 }
288
289 if let Some(reply_to) = reply_to {
290 params.insert("reply_to".to_string(), reply_to.to_string());
291 }
292
293 if let Some(forward_messages) = forward_messages {
294 let ids: Vec<String> = forward_messages.iter().map(|id| id.to_string()).collect();
295 params.insert("forward_messages".to_string(), ids.join(","));
296 }
297
298 if disable_mentions {
299 params.insert("disable_mentions".to_string(), "1".to_string());
300 }
301
302 if dont_parse_links {
303 params.insert("dont_parse_links".to_string(), "1".to_string());
304 }
305
306 let response = self.call_method("messages.send", params).await?;
307 let message_id = response["response"].as_i64().ok_or_else(|| {
308 VkError::InvalidResponse("Expected message_id in response".to_string())
309 })?;
310
311 Ok(message_id)
312 }
313
314 #[allow(clippy::too_many_arguments)]
315 pub async fn messages_edit(
316 &self,
317 peer_id: i64,
318 message_id: i64,
319 message: &str,
320 keyboard: Option<&Keyboard>,
321 attachment: Option<&str>,
322 keep_forward_messages: bool,
323 keep_snippets: bool,
324 ) -> VkResult<bool> {
325 let mut params = HashMap::new();
326 params.insert("peer_id".to_string(), peer_id.to_string());
327 params.insert("message_id".to_string(), message_id.to_string());
328 params.insert("message".to_string(), message.to_string());
329
330 if let Some(keyboard) = keyboard {
331 params.insert("keyboard".to_string(), keyboard.to_json_string());
332 }
333
334 if let Some(attachment) = attachment {
335 params.insert("attachment".to_string(), attachment.to_string());
336 }
337
338 if keep_forward_messages {
339 params.insert("keep_forward_messages".to_string(), "1".to_string());
340 }
341
342 if keep_snippets {
343 params.insert("keep_snippets".to_string(), "1".to_string());
344 }
345
346 let response = self.call_method("messages.edit", params).await?;
347 Ok(response["response"].as_i64().unwrap_or(0) == 1)
348 }
349
350 pub async fn messages_delete(
351 &self,
352 message_ids: &[i64],
353 delete_for_all: bool,
354 spam: bool,
355 ) -> VkResult<serde_json::Map<String, Value>> {
356 let mut params = HashMap::new();
357 let ids: Vec<String> = message_ids.iter().map(|id| id.to_string()).collect();
358 params.insert("message_ids".to_string(), ids.join(","));
359
360 if delete_for_all {
361 params.insert("delete_for_all".to_string(), "1".to_string());
362 }
363
364 if spam {
365 params.insert("spam".to_string(), "1".to_string());
366 }
367
368 let response = self.call_method("messages.delete", params).await?;
369 Ok(response["response"]
370 .as_object()
371 .ok_or_else(|| VkError::InvalidResponse("Expected object in response".to_string()))?
372 .clone())
373 }
374
375 pub async fn messages_restore(&self, message_id: i64) -> VkResult<bool> {
376 let mut params = HashMap::new();
377 params.insert("message_id".to_string(), message_id.to_string());
378
379 let response = self.call_method("messages.restore", params).await?;
380 Ok(response["response"].as_i64().unwrap_or(0) == 1)
381 }
382
383 pub async fn messages_mark_as_read(
384 &self,
385 peer_id: i64,
386 start_message_id: Option<i64>,
387 ) -> VkResult<bool> {
388 let mut params = HashMap::new();
389 params.insert("peer_id".to_string(), peer_id.to_string());
390
391 if let Some(start_id) = start_message_id {
392 params.insert("start_message_id".to_string(), start_id.to_string());
393 }
394
395 let response = self.call_method("messages.markAsRead", params).await?;
396 Ok(response["response"].as_i64().unwrap_or(0) == 1)
397 }
398
399 pub async fn messages_mark_as_important(
400 &self,
401 message_ids: &[i64],
402 important: Option<i32>,
403 ) -> VkResult<Vec<i64>> {
404 let mut params = HashMap::new();
405 let ids: Vec<String> = message_ids.iter().map(|id| id.to_string()).collect();
406 params.insert("message_ids".to_string(), ids.join(","));
407
408 if let Some(important) = important {
409 params.insert("important".to_string(), important.to_string());
410 }
411
412 let response = self.call_method("messages.markAsImportant", params).await?;
413 let marked_ids: Vec<i64> = response["response"]
414 .as_array()
415 .ok_or_else(|| VkError::InvalidResponse("Expected array in response".to_string()))?
416 .iter()
417 .filter_map(|v| v.as_i64())
418 .collect();
419
420 Ok(marked_ids)
421 }
422
423 pub async fn messages_get_conversations(
424 &self,
425 offset: i32,
426 count: i32,
427 filter: Option<&str>,
428 ) -> VkResult<Value> {
429 let mut params = HashMap::new();
430 params.insert("offset".to_string(), offset.to_string());
431 params.insert("count".to_string(), count.to_string());
432
433 if let Some(filter) = filter {
434 params.insert("filter".to_string(), filter.to_string());
435 }
436
437 self.call_method("messages.getConversations", params).await
438 }
439
440 pub async fn messages_get_conversation_members(
441 &self,
442 peer_id: i64,
443 fields: Option<&str>,
444 ) -> VkResult<Value> {
445 let mut params = HashMap::new();
446 params.insert("peer_id".to_string(), peer_id.to_string());
447
448 if let Some(fields) = fields {
449 params.insert("fields".to_string(), fields.to_string());
450 }
451
452 self.call_method("messages.getConversationMembers", params)
453 .await
454 }
455
456 pub async fn messages_get_history(
457 &self,
458 peer_id: i64,
459 offset: i32,
460 count: i32,
461 start_message_id: Option<i64>,
462 rev: bool,
463 ) -> VkResult<Value> {
464 let mut params = HashMap::new();
465 params.insert("peer_id".to_string(), peer_id.to_string());
466 params.insert("offset".to_string(), offset.to_string());
467 params.insert("count".to_string(), count.to_string());
468
469 if let Some(start_id) = start_message_id {
470 params.insert("start_message_id".to_string(), start_id.to_string());
471 }
472
473 if rev {
474 params.insert("rev".to_string(), "1".to_string());
475 }
476
477 self.call_method("messages.getHistory", params).await
478 }
479
480 pub async fn messages_get_by_id(
481 &self,
482 message_ids: &[i64],
483 preview_length: i32,
484 extended: bool,
485 ) -> VkResult<Value> {
486 let mut params = HashMap::new();
487 let ids: Vec<String> = message_ids.iter().map(|id| id.to_string()).collect();
488 params.insert("message_ids".to_string(), ids.join(","));
489 params.insert("preview_length".to_string(), preview_length.to_string());
490
491 if extended {
492 params.insert("extended".to_string(), "1".to_string());
493 }
494
495 self.call_method("messages.getById", params).await
496 }
497
498 pub async fn messages_search(
499 &self,
500 query: &str,
501 peer_id: Option<i64>,
502 date: Option<i64>,
503 count: i32,
504 ) -> VkResult<Value> {
505 let mut params = HashMap::new();
506 params.insert("q".to_string(), query.to_string());
507 params.insert("count".to_string(), count.to_string());
508
509 if let Some(peer_id) = peer_id {
510 params.insert("peer_id".to_string(), peer_id.to_string());
511 }
512
513 if let Some(date) = date {
514 params.insert("date".to_string(), date.to_string());
515 }
516
517 self.call_method("messages.search", params).await
518 }
519
520 pub async fn messages_search_conversations(
521 &self,
522 query: &str,
523 count: i32,
524 extended: bool,
525 fields: Option<&str>,
526 ) -> VkResult<Value> {
527 let mut params = HashMap::new();
528 params.insert("q".to_string(), query.to_string());
529 params.insert("count".to_string(), count.to_string());
530
531 if extended {
532 params.insert("extended".to_string(), "1".to_string());
533 }
534
535 if let Some(fields) = fields {
536 params.insert("fields".to_string(), fields.to_string());
537 }
538
539 self.call_method("messages.searchConversations", params)
540 .await
541 }
542
543 pub async fn messages_get_attachments(
544 &self,
545 peer_id: i64,
546 media_type: &str,
547 start_from: Option<&str>,
548 count: i32,
549 ) -> VkResult<Value> {
550 let mut params = HashMap::new();
551 params.insert("peer_id".to_string(), peer_id.to_string());
552 params.insert("media_type".to_string(), media_type.to_string());
553 params.insert("count".to_string(), count.to_string());
554
555 if let Some(start_from) = start_from {
556 params.insert("start_from".to_string(), start_from.to_string());
557 }
558
559 self.call_method("messages.getAttachments", params).await
560 }
561
562 pub async fn messages_get_invite_link(&self, peer_id: i64, reset: bool) -> VkResult<String> {
563 let mut params = HashMap::new();
564 params.insert("peer_id".to_string(), peer_id.to_string());
565
566 if reset {
567 params.insert("reset".to_string(), "1".to_string());
568 }
569
570 let response = self.call_method("messages.getInviteLink", params).await?;
571 let link = response["response"]["link"]
572 .as_str()
573 .ok_or_else(|| VkError::MissingField("link".to_string()))?
574 .to_string();
575
576 Ok(link)
577 }
578
579 pub async fn messages_remove_chat_user(
580 &self,
581 chat_id: i64,
582 user_id: i64,
583 member_id: Option<i64>,
584 ) -> VkResult<bool> {
585 let mut params = HashMap::new();
586 params.insert("chat_id".to_string(), chat_id.to_string());
587 params.insert("user_id".to_string(), user_id.to_string());
588
589 if let Some(member_id) = member_id {
590 params.insert("member_id".to_string(), member_id.to_string());
591 }
592
593 let response = self.call_method("messages.removeChatUser", params).await?;
594 Ok(response["response"].as_i64().unwrap_or(0) == 1)
595 }
596
597 pub async fn messages_add_chat_user(&self, chat_id: i64, user_id: i64) -> VkResult<bool> {
598 let mut params = HashMap::new();
599 params.insert("chat_id".to_string(), chat_id.to_string());
600 params.insert("user_id".to_string(), user_id.to_string());
601
602 let response = self.call_method("messages.addChatUser", params).await?;
603 Ok(response["response"].as_i64().unwrap_or(0) == 1)
604 }
605
606 pub async fn messages_create_chat(
607 &self,
608 user_ids: &[i64],
609 title: Option<&str>,
610 ) -> VkResult<i64> {
611 let mut params = HashMap::new();
612 let ids: Vec<String> = user_ids.iter().map(|id| id.to_string()).collect();
613 params.insert("user_ids".to_string(), ids.join(","));
614
615 if let Some(title) = title {
616 params.insert("title".to_string(), title.to_string());
617 }
618
619 let response = self.call_method("messages.createChat", params).await?;
620 let chat_id = response["response"]
621 .as_i64()
622 .ok_or_else(|| VkError::InvalidResponse("Expected chat_id in response".to_string()))?;
623
624 Ok(chat_id)
625 }
626
627 pub async fn messages_set_activity(
628 &self,
629 peer_id: i64,
630 user_id: Option<i64>,
631 activity_type: &str,
632 ) -> VkResult<bool> {
633 let mut params = HashMap::new();
634 params.insert("peer_id".to_string(), peer_id.to_string());
635 params.insert("type".to_string(), activity_type.to_string());
636
637 if let Some(user_id) = user_id {
638 params.insert("user_id".to_string(), user_id.to_string());
639 }
640
641 let response = self.call_method("messages.setActivity", params).await?;
642 Ok(response["response"].as_i64().unwrap_or(0) == 1)
643 }
644
645 pub async fn messages_send_message_event_answer(
646 &self,
647 event_id: &str,
648 user_id: i64,
649 peer_id: i64,
650 event_data: Option<&str>,
651 ) -> VkResult<bool> {
652 let mut params = HashMap::new();
653 params.insert("event_id".to_string(), event_id.to_string());
654 params.insert("user_id".to_string(), user_id.to_string());
655 params.insert("peer_id".to_string(), peer_id.to_string());
656
657 if let Some(event_data) = event_data {
658 params.insert("event_data".to_string(), event_data.to_string());
659 }
660
661 let response = self
662 .call_method("messages.sendMessageEventAnswer", params)
663 .await?;
664 Ok(response["response"].as_i64().unwrap_or(0) == 1)
665 }
666
667 pub async fn users_get(
668 &self,
669 user_ids: &[i64],
670 fields: Option<&str>,
671 name_case: Option<&str>,
672 ) -> VkResult<Value> {
673 let mut params = HashMap::new();
674 let ids: Vec<String> = user_ids.iter().map(|id| id.to_string()).collect();
675 params.insert("user_ids".to_string(), ids.join(","));
676
677 if let Some(fields) = fields {
678 params.insert("fields".to_string(), fields.to_string());
679 }
680
681 if let Some(name_case) = name_case {
682 params.insert("name_case".to_string(), name_case.to_string());
683 }
684
685 self.call_method("users.get", params).await
686 }
687
688 pub async fn groups_get_long_poll_server(&self, group_id: i64) -> VkResult<LongPollServer> {
689 let mut params = HashMap::new();
690 params.insert("group_id".to_string(), group_id.to_string());
691
692 let response = self.call_method("groups.getLongPollServer", params).await?;
693
694 let server = response["response"]["server"]
695 .as_str()
696 .ok_or_else(|| VkError::MissingField("server".to_string()))?
697 .to_string();
698
699 let key = response["response"]["key"]
700 .as_str()
701 .ok_or_else(|| VkError::MissingField("key".to_string()))?
702 .to_string();
703
704 let ts = response["response"]["ts"]
705 .as_str()
706 .ok_or_else(|| VkError::MissingField("ts".to_string()))?
707 .to_string();
708
709 Ok(LongPollServer { server, key, ts })
710 }
711
712 pub async fn groups_get_by_id(
713 &self,
714 group_ids: &[i64],
715 fields: Option<&str>,
716 ) -> VkResult<Value> {
717 let mut params = HashMap::new();
718 let ids: Vec<String> = group_ids.iter().map(|id| id.to_string()).collect();
719 params.insert("group_ids".to_string(), ids.join(","));
720
721 if let Some(fields) = fields {
722 params.insert("fields".to_string(), fields.to_string());
723 }
724
725 self.call_method("groups.getById", params).await
726 }
727
728 pub async fn send_message(&self, peer_id: i64, message: &str) -> VkResult<i64> {
729 self.messages_send(
730 peer_id, message, None, None, None, None, None, false, false, None,
731 )
732 .await
733 }
734
735 pub async fn send_message_with_keyboard(
736 &self,
737 peer_id: i64,
738 message: &str,
739 keyboard: &Keyboard,
740 ) -> VkResult<i64> {
741 self.messages_send(
742 peer_id,
743 message,
744 Some(keyboard),
745 None,
746 None,
747 None,
748 None,
749 false,
750 false,
751 None,
752 )
753 .await
754 }
755
756 pub async fn send_reply(&self, peer_id: i64, message: &str, reply_to: i64) -> VkResult<i64> {
757 self.messages_send(
758 peer_id,
759 message,
760 None,
761 None,
762 None,
763 Some(reply_to),
764 None,
765 false,
766 false,
767 None,
768 )
769 .await
770 }
771
772 pub async fn photos_get_messages_upload_server(&self, peer_id: i64) -> VkResult<UploadServer> {
776 let mut params = HashMap::new();
777 params.insert("peer_id".to_string(), peer_id.to_string());
778
779 let response = self
780 .call_method("photos.getMessagesUploadServer", params)
781 .await?;
782
783 let upload_url = response["response"]["upload_url"]
784 .as_str()
785 .ok_or_else(|| VkError::MissingField("upload_url".to_string()))?
786 .to_string();
787
788 Ok(UploadServer { upload_url })
789 }
790
791 pub async fn photos_save_messages_photo(
793 &self,
794 photo: &str,
795 server: i64,
796 hash: &str,
797 ) -> VkResult<Vec<SavedPhoto>> {
798 let mut params = HashMap::new();
799 params.insert("photo".to_string(), photo.to_string());
800 params.insert("server".to_string(), server.to_string());
801 params.insert("hash".to_string(), hash.to_string());
802
803 let response = self.call_method("photos.saveMessagesPhoto", params).await?;
804
805 let photos: Vec<SavedPhoto> =
806 serde_json::from_value(response["response"].clone()).map_err(VkError::JsonError)?;
807
808 Ok(photos)
809 }
810
811 pub async fn docs_get_upload_server(
813 &self,
814 peer_id: Option<i64>,
815 doc_type: Option<&str>,
816 ) -> VkResult<UploadServer> {
817 let mut params = HashMap::new();
818
819 if let Some(peer_id) = peer_id {
820 params.insert("peer_id".to_string(), peer_id.to_string());
821 }
822
823 if let Some(doc_type) = doc_type {
824 params.insert("type".to_string(), doc_type.to_string());
825 }
826
827 let response = self.call_method("docs.getUploadServer", params).await?;
828
829 let upload_url = response["response"]["upload_url"]
830 .as_str()
831 .ok_or_else(|| VkError::MissingField("upload_url".to_string()))?
832 .to_string();
833
834 Ok(UploadServer { upload_url })
835 }
836
837 pub async fn docs_get_messages_upload_server(
839 &self,
840 peer_id: i64,
841 doc_type: Option<&str>,
842 ) -> VkResult<UploadServer> {
843 let mut params = HashMap::new();
844 params.insert("peer_id".to_string(), peer_id.to_string());
845
846 if let Some(doc_type) = doc_type {
847 params.insert("type".to_string(), doc_type.to_string());
848 }
849
850 let response = self
851 .call_method("docs.getMessagesUploadServer", params)
852 .await?;
853
854 let upload_url = response["response"]["upload_url"]
855 .as_str()
856 .ok_or_else(|| VkError::MissingField("upload_url".to_string()))?
857 .to_string();
858
859 Ok(UploadServer { upload_url })
860 }
861
862 pub async fn docs_save(
864 &self,
865 file: &str,
866 title: Option<&str>,
867 tags: Option<&str>,
868 ) -> VkResult<SavedDocument> {
869 let mut params = HashMap::new();
870 params.insert("file".to_string(), file.to_string());
871
872 if let Some(title) = title {
873 params.insert("title".to_string(), title.to_string());
874 }
875
876 if let Some(tags) = tags {
877 params.insert("tags".to_string(), tags.to_string());
878 }
879
880 let response = self.call_method("docs.save", params).await?;
881
882 crate::vk_log!("docs.save response: {}", response);
883
884 let doc_response: DocSaveResponse =
885 serde_json::from_value(response["response"].clone()).map_err(VkError::JsonError)?;
886
887 crate::vk_log!(
888 "Parsed doc response: type={:?}, has_doc={}, has_audio_msg={}",
889 doc_response.doc_type,
890 doc_response.doc.is_some(),
891 doc_response.audio_message.is_some()
892 );
893
894 Ok(SavedDocument {
895 doc: doc_response.doc,
896 audio_message: doc_response.audio_message,
897 })
898 }
899
900 #[cfg(feature = "reqwest")]
904 pub async fn upload_file(
905 &self,
906 upload_url: &str,
907 file_data: Vec<u8>,
908 filename: &str,
909 ) -> VkResult<UploadResponse> {
910 let content_type = Self::detect_content_type(filename);
912
913 let mut part = reqwest::multipart::Part::bytes(file_data).file_name(filename.to_string());
914
915 if let Some(ct) = content_type {
916 part = part
917 .mime_str(&ct)
918 .map_err(|e| VkError::InternalError(format!("Invalid MIME type: {}", e)))?;
919 }
920
921 let form = reqwest::multipart::Form::new().part("file", part);
922
923 crate::vk_log!("Uploading file '{}' to {}", filename, upload_url);
924
925 let response = self
926 .client
927 .post(upload_url)
928 .multipart(form)
929 .send()
930 .await
931 .map_err(|e| VkError::NetworkError(format!("Upload request failed: {}", e)))?;
932
933 let status = response.status();
934
935 let response_text = response
937 .text()
938 .await
939 .map_err(|e| VkError::NetworkError(format!("Failed to read response: {}", e)))?;
940
941 crate::vk_log!("Upload response (status: {}): {}", status, response_text);
942
943 if !status.is_success() {
944 return Err(VkError::NetworkError(format!(
945 "Upload failed with status {}: {}",
946 status, response_text
947 )));
948 }
949
950 let upload_response: UploadResponse =
952 serde_json::from_str(&response_text).map_err(|e| {
953 VkError::InvalidResponse(format!(
954 "Failed to parse upload response: {}. Raw response: {}",
955 e, response_text
956 ))
957 })?;
958
959 Ok(upload_response)
960 }
961
962 fn detect_content_type(filename: &str) -> Option<String> {
964 let ext = filename.split('.').next_back()?.to_lowercase();
965 match ext.as_str() {
966 "jpg" | "jpeg" => Some("image/jpeg".to_string()),
967 "png" => Some("image/png".to_string()),
968 "gif" => Some("image/gif".to_string()),
969 "pdf" => Some("application/pdf".to_string()),
970 "txt" => Some("text/plain".to_string()),
971 "ogg" => Some("audio/ogg".to_string()),
972 "mp3" => Some("audio/mpeg".to_string()),
973 "mp4" => Some("video/mp4".to_string()),
974 _ => None,
975 }
976 }
977
978 pub async fn send_photo(
980 &self,
981 peer_id: i64,
982 photo_data: Vec<u8>,
983 filename: &str,
984 caption: Option<&str>,
985 ) -> VkResult<i64> {
986 let upload_server = self.photos_get_messages_upload_server(peer_id).await?;
988
989 let upload_response = self
991 .upload_file(&upload_server.upload_url, photo_data, filename)
992 .await?;
993
994 if upload_response.server == 0
996 || upload_response.photo.is_empty()
997 || upload_response.hash.is_empty()
998 {
999 return Err(VkError::InvalidResponse(
1000 "Upload response missing photo data. Server, photo, or hash is empty".to_string(),
1001 ));
1002 }
1003
1004 crate::vk_log!(
1005 "Photo upload successful: server={}, photo length={}",
1006 upload_response.server,
1007 upload_response.photo.len()
1008 );
1009
1010 let saved_photos = self
1012 .photos_save_messages_photo(
1013 &upload_response.photo,
1014 upload_response.server,
1015 &upload_response.hash,
1016 )
1017 .await?;
1018
1019 let attachment = saved_photos
1021 .first()
1022 .map(|p| format!("photo{}_{}", p.owner_id, p.id))
1023 .ok_or_else(|| VkError::InvalidResponse("No photo saved".to_string()))?;
1024
1025 let message = caption.unwrap_or("");
1027 self.messages_send(
1028 peer_id,
1029 message,
1030 None,
1031 Some(&attachment),
1032 None,
1033 None,
1034 None,
1035 false,
1036 false,
1037 None,
1038 )
1039 .await
1040 }
1041
1042 pub async fn send_document(
1044 &self,
1045 peer_id: i64,
1046 file_data: Vec<u8>,
1047 filename: &str,
1048 title: Option<&str>,
1049 caption: Option<&str>,
1050 ) -> VkResult<i64> {
1051 let upload_server = self.docs_get_messages_upload_server(peer_id, None).await?;
1053
1054 let upload_response = self
1056 .upload_file(&upload_server.upload_url, file_data, filename)
1057 .await?;
1058
1059 if upload_response.file.is_empty() {
1061 return Err(VkError::InvalidResponse(
1062 "Upload response missing file data".to_string(),
1063 ));
1064 }
1065
1066 crate::vk_log!(
1067 "Document upload successful: file field length={}",
1068 upload_response.file.len()
1069 );
1070
1071 let saved_doc = self
1073 .docs_save(&upload_response.file, title.or(Some(filename)), None)
1074 .await?;
1075
1076 let doc = saved_doc
1078 .doc
1079 .ok_or_else(|| VkError::InvalidResponse("No document saved".to_string()))?;
1080 let attachment = format!("doc{}_{}", doc.owner_id, doc.id);
1081
1082 let message = caption.unwrap_or("");
1084 self.messages_send(
1085 peer_id,
1086 message,
1087 None,
1088 Some(&attachment),
1089 None,
1090 None,
1091 None,
1092 false,
1093 false,
1094 None,
1095 )
1096 .await
1097 }
1098
1099 pub async fn send_voice_message(
1101 &self,
1102 peer_id: i64,
1103 audio_data: Vec<u8>,
1104 filename: &str,
1105 ) -> VkResult<i64> {
1106 let upload_server = self
1108 .docs_get_messages_upload_server(peer_id, Some("audio_message"))
1109 .await?;
1110
1111 let upload_response = self
1113 .upload_file(&upload_server.upload_url, audio_data, filename)
1114 .await?;
1115
1116 if upload_response.file.is_empty() {
1118 return Err(VkError::InvalidResponse(
1119 "Upload response missing file data".to_string(),
1120 ));
1121 }
1122
1123 crate::vk_log!(
1124 "Voice message upload successful: file field length={}",
1125 upload_response.file.len()
1126 );
1127
1128 let saved_doc = self
1130 .docs_save(&upload_response.file, Some(filename), None)
1131 .await?;
1132
1133 if saved_doc.audio_message.is_none() {
1135 return Err(VkError::InvalidResponse(
1136 "Audio message was not processed".to_string(),
1137 ));
1138 }
1139
1140 let doc = saved_doc
1142 .doc
1143 .ok_or_else(|| VkError::InvalidResponse("No document saved".to_string()))?;
1144 let attachment = format!("doc{}_{}", doc.owner_id, doc.id);
1145
1146 self.messages_send(
1148 peer_id,
1149 "",
1150 None,
1151 Some(&attachment),
1152 None,
1153 None,
1154 None,
1155 false,
1156 false,
1157 None,
1158 )
1159 .await
1160 }
1161
1162 #[cfg(feature = "reqwest")]
1166 pub async fn download_file(&self, url: &str) -> VkResult<DownloadedFile> {
1167 let response = self
1168 .client
1169 .get(url)
1170 .send()
1171 .await
1172 .map_err(|e| VkError::NetworkError(e.to_string()))?;
1173
1174 if !response.status().is_success() {
1175 return Err(VkError::NetworkError(format!(
1176 "Download failed: {}",
1177 response.status()
1178 )));
1179 }
1180
1181 let content_type = response
1182 .headers()
1183 .get("content-type")
1184 .and_then(|v| v.to_str().ok())
1185 .map(|s| s.to_string());
1186
1187 let data = response
1188 .bytes()
1189 .await
1190 .map_err(|e| VkError::NetworkError(e.to_string()))?;
1191
1192 Ok(DownloadedFile {
1193 data: data.to_vec(),
1194 content_type,
1195 })
1196 }
1197
1198 pub async fn download_photo(&self, photo: &crate::models::Photo) -> VkResult<DownloadedFile> {
1200 let size = photo
1202 .sizes
1203 .iter()
1204 .max_by_key(|s| s.width * s.height)
1205 .ok_or_else(|| VkError::InvalidResponse("Photo has no sizes".to_string()))?;
1206
1207 self.download_file(&size.url).await
1208 }
1209
1210 pub async fn download_document(
1212 &self,
1213 doc: &crate::models::Document,
1214 ) -> VkResult<DownloadedFile> {
1215 self.download_file(&doc.url).await
1216 }
1217
1218 pub async fn download_audio(&self, audio: &crate::models::Audio) -> VkResult<DownloadedFile> {
1220 let url = audio
1221 .url
1222 .as_ref()
1223 .ok_or_else(|| VkError::InvalidResponse("Audio has no URL".to_string()))?;
1224
1225 self.download_file(url).await
1226 }
1227
1228 pub async fn download_video_thumbnail(
1230 &self,
1231 video: &crate::models::Video,
1232 ) -> VkResult<DownloadedFile> {
1233 let image = video
1234 .image
1235 .first()
1236 .ok_or_else(|| VkError::InvalidResponse("Video has no thumbnails".to_string()))?;
1237
1238 self.download_file(&image.url).await
1239 }
1240
1241 pub async fn download_sticker(
1243 &self,
1244 sticker: &crate::models::Sticker,
1245 ) -> VkResult<DownloadedFile> {
1246 let image = sticker
1247 .images
1248 .first()
1249 .ok_or_else(|| VkError::InvalidResponse("Sticker has no images".to_string()))?;
1250
1251 self.download_file(&image.url).await
1252 }
1253
1254 pub async fn download_audio_message(
1256 &self,
1257 audio_msg: &crate::models::AudioMessage,
1258 ) -> VkResult<DownloadedFile> {
1259 self.download_file(&audio_msg.link_mp3).await
1261 }
1262
1263 pub async fn forward_attachment<A: AsRef<str>>(
1265 &self,
1266 peer_id: i64,
1267 attachment: A,
1268 caption: Option<&str>,
1269 ) -> VkResult<i64> {
1270 self.messages_send(
1271 peer_id,
1272 caption.unwrap_or(""),
1273 None,
1274 Some(attachment.as_ref()),
1275 None,
1276 None,
1277 None,
1278 false,
1279 false,
1280 None,
1281 )
1282 .await
1283 }
1284}
1285
1286#[derive(Debug, Clone)]
1288pub struct LongPollServer {
1289 pub server: String,
1290 pub key: String,
1291 pub ts: String,
1292}
1293
1294#[derive(Debug, Clone)]
1296pub struct UploadServer {
1297 pub upload_url: String,
1298}
1299
1300#[derive(Debug, Clone, Deserialize)]
1306pub struct UploadResponse {
1307 #[serde(default)]
1308 pub server: i64,
1309 #[serde(default)]
1310 pub photo: String,
1311 #[serde(default)]
1312 pub hash: String,
1313 #[serde(default)]
1314 pub file: String,
1315}
1316
1317#[derive(Debug, Clone, Deserialize)]
1319pub struct SavedPhoto {
1320 pub id: i64,
1321 pub owner_id: i64,
1322 #[serde(default)]
1323 pub access_key: Option<String>,
1324 #[serde(default)]
1325 pub sizes: Vec<PhotoSizeInfo>,
1326}
1327
1328#[derive(Debug, Clone, Deserialize)]
1330pub struct PhotoSizeInfo {
1331 #[serde(rename = "type")]
1332 pub size_type: String,
1333 pub url: String,
1334 pub width: i32,
1335 pub height: i32,
1336}
1337
1338#[derive(Debug, Clone, Deserialize)]
1340pub struct DocInfo {
1341 pub id: i64,
1342 pub owner_id: i64,
1343 pub title: String,
1344 pub size: i64,
1345 pub ext: String,
1346 pub url: String,
1347 pub date: i64,
1348 #[serde(rename = "type")]
1349 pub doc_type: i32,
1350}
1351
1352#[derive(Debug, Clone, Deserialize)]
1354pub struct AudioMessageInfo {
1355 pub duration: i32,
1356 #[serde(default)]
1357 pub waveform: Vec<i32>,
1358 pub link_ogg: String,
1359 pub link_mp3: String,
1360}
1361
1362#[derive(Debug, Clone)]
1364pub struct SavedDocument {
1365 pub doc: Option<DocInfo>,
1366 pub audio_message: Option<AudioMessageInfo>,
1367}
1368
1369#[derive(Debug, Clone, Deserialize)]
1371#[allow(dead_code)]
1372struct DocSaveResponse {
1373 #[serde(rename = "type")]
1374 pub doc_type: Option<String>,
1375 pub doc: Option<DocInfo>,
1376 #[serde(rename = "audio_message")]
1377 pub audio_message: Option<AudioMessageInfo>,
1378}
1379
1380#[derive(Debug, Clone)]
1382pub struct DownloadedFile {
1383 pub data: Vec<u8>,
1384 pub content_type: Option<String>,
1385}