1use crate::error::{VkError, VkResponseExt, VkResult};
2use crate::keyboard::Keyboard;
3use serde_json::Value;
4use std::collections::HashMap;
5use std::time::Duration;
6use tokio::time::sleep;
7
8#[cfg(feature = "reqwest")]
9use reqwest::Client;
10
11#[cfg(feature = "uuid")]
12use uuid::Uuid;
13
14#[derive(Debug, Clone)]
15pub struct VkApiConfig {
16 pub version: String,
17 pub timeout: u64,
18 pub max_retries: u32,
19 pub retry_delay: u64,
20 pub enable_logging: bool,
21 pub endpoint: String,
22}
23
24impl Default for VkApiConfig {
25 fn default() -> Self {
26 Self {
27 version: "5.199".to_string(),
28 timeout: 30,
29 max_retries: 3,
30 retry_delay: 1000,
31 enable_logging: false,
32 endpoint: "https://api.vk.com/method/".to_string(),
33 }
34 }
35}
36
37#[derive(Debug, Default)]
38pub struct VkApiBuilder {
39 token: Option<String>,
40 config: VkApiConfig,
41}
42
43impl VkApiBuilder {
44 pub fn new() -> Self {
45 Self::default()
46 }
47
48 pub fn token<T: Into<String>>(mut self, token: T) -> Self {
49 self.token = Some(token.into());
50 self
51 }
52
53 pub fn version<T: Into<String>>(mut self, version: T) -> Self {
54 self.config.version = version.into();
55 self
56 }
57
58 pub fn timeout(mut self, timeout: u64) -> Self {
59 self.config.timeout = timeout;
60 self
61 }
62
63 pub fn max_retries(mut self, max_retries: u32) -> Self {
64 self.config.max_retries = max_retries;
65 self
66 }
67
68 pub fn retry_delay(mut self, retry_delay: u64) -> Self {
69 self.config.retry_delay = retry_delay;
70 self
71 }
72
73 pub fn enable_logging(mut self, enable: bool) -> Self {
74 self.config.enable_logging = enable;
75 self
76 }
77
78 pub fn endpoint<T: Into<String>>(mut self, endpoint: T) -> Self {
79 self.config.endpoint = endpoint.into();
80 self
81 }
82
83 pub fn build(self) -> VkResult<VkApi> {
84 let token = self
85 .token
86 .ok_or_else(|| VkError::ConfigError("Token is required".to_string()))?;
87
88 #[cfg(feature = "reqwest")]
89 let client = Client::builder()
90 .timeout(Duration::from_secs(self.config.timeout))
91 .pool_max_idle_per_host(10)
92 .tcp_keepalive(Duration::from_secs(60))
93 .build()
94 .map_err(|e| VkError::NetworkError(e.to_string()))?;
95
96 Ok(VkApi {
97 #[cfg(feature = "reqwest")]
98 client,
99 token,
100 config: self.config,
101 })
102 }
103}
104
105#[derive(Debug, Clone)]
106pub struct VkApi {
107 #[cfg(feature = "reqwest")]
108 client: Client,
109 token: String,
110 config: VkApiConfig,
111}
112
113impl VkApi {
114 pub fn new<T: Into<String>>(token: T) -> VkResult<Self> {
115 VkApiBuilder::new().token(token).build()
116 }
117
118 pub fn builder() -> VkApiBuilder {
119 VkApiBuilder::new()
120 }
121
122 pub fn token(&self) -> &str {
123 &self.token
124 }
125
126 pub fn version(&self) -> &str {
127 &self.config.version
128 }
129
130 #[cfg(feature = "reqwest")]
131 async fn call_method_with_retry(
132 &self,
133 method: &str,
134 mut params: HashMap<String, String>,
135 ) -> VkResult<Value> {
136 let mut retries = 0;
137
138 params.insert("access_token".to_string(), self.token.clone());
139 params.insert("v".to_string(), self.config.version.clone());
140
141 loop {
142 match self.call_method_once(method, ¶ms).await {
143 Ok(response) => return Ok(response),
144 Err(e) => {
145 retries += 1;
146
147 if retries >= self.config.max_retries || !self.should_retry(&e) {
148 return Err(e);
149 }
150
151 let delay_ms = self.config.retry_delay * 2u64.pow(retries - 1);
152 sleep(Duration::from_millis(delay_ms)).await;
153 }
154 }
155 }
156 }
157
158 #[cfg(feature = "reqwest")]
159 fn should_retry(&self, error: &VkError) -> bool {
160 match error {
161 VkError::RateLimit => true,
162 VkError::NetworkError(_) => true,
163 VkError::Timeout(_) => true,
164 VkError::HttpError(e) => e
165 .status()
166 .map(|status| status.is_server_error() || status.as_u16() == 429)
167 .unwrap_or(false),
168 VkError::ApiError { code, .. } => {
169 matches!(*code, 6 | 9 | 10 | 14)
170 }
171 _ => false,
172 }
173 }
174
175 #[cfg(feature = "reqwest")]
176 async fn call_method_once(
177 &self,
178 method: &str,
179 params: &HashMap<String, String>,
180 ) -> VkResult<Value> {
181 let url = format!("{}{}", self.config.endpoint, method);
182
183 if self.config.enable_logging {
184 crate::vk_log!("API call: {} with {} params", method, params.len());
185 }
186
187 let response = self
188 .client
189 .post(&url)
190 .form(params)
191 .send()
192 .await
193 .map_err(|e| {
194 if e.is_timeout() {
195 VkError::Timeout(e.to_string())
196 } else {
197 VkError::HttpError(e)
198 }
199 })?;
200
201 self.handle_response(response).await
202 }
203
204 #[cfg(feature = "reqwest")]
205 async fn handle_response(&self, response: reqwest::Response) -> VkResult<Value> {
206 let status = response.status();
207
208 if status == 429 {
209 return Err(VkError::RateLimit);
210 }
211
212 if !status.is_success() {
213 return Err(VkError::HttpError(
214 response.error_for_status().err().unwrap(),
215 ));
216 }
217
218 let json_response: Value = response.json().await?;
219
220 if VkResponseExt::has_error(&json_response) {
221 return VkResponseExt::extract_error(json_response);
222 }
223
224 Ok(json_response)
225 }
226
227 #[cfg(feature = "reqwest")]
228 pub async fn call_method(
229 &self,
230 method: &str,
231 params: HashMap<String, String>,
232 ) -> VkResult<Value> {
233 self.call_method_with_retry(method, params).await
234 }
235
236 #[cfg(not(feature = "reqwest"))]
237 pub async fn call_method(
238 &self,
239 _method: &str,
240 _params: HashMap<String, String>,
241 ) -> VkResult<Value> {
242 Err(VkError::ConfigError(
243 "reqwest feature is required for API calls".to_string(),
244 ))
245 }
246
247 #[allow(clippy::too_many_arguments)]
248 pub async fn messages_send(
249 &self,
250 peer_id: i64,
251 message: &str,
252 keyboard: Option<&Keyboard>,
253 attachment: Option<&str>,
254 sticker_id: Option<i64>,
255 reply_to: Option<i64>,
256 forward_messages: Option<&[i64]>,
257 disable_mentions: bool,
258 dont_parse_links: bool,
259 random_id: Option<i64>,
260 ) -> VkResult<i64> {
261 let mut params = HashMap::new();
262 params.insert("peer_id".to_string(), peer_id.to_string());
263 params.insert("message".to_string(), message.to_string());
264
265 #[cfg(feature = "uuid")]
266 let random_id = random_id.unwrap_or_else(|| {
267 let uuid = Uuid::new_v4();
268 (uuid.as_u128() & 0x7FFFFFFFFFFFFFFF) as i64
269 });
270
271 #[cfg(not(feature = "uuid"))]
272 let random_id = random_id.unwrap_or(0);
273
274 params.insert("random_id".to_string(), random_id.to_string());
275
276 if let Some(keyboard) = keyboard {
277 params.insert("keyboard".to_string(), keyboard.to_json_string());
278 }
279
280 if let Some(attachment) = attachment {
281 params.insert("attachment".to_string(), attachment.to_string());
282 }
283
284 if let Some(sticker_id) = sticker_id {
285 params.insert("sticker_id".to_string(), sticker_id.to_string());
286 }
287
288 if let Some(reply_to) = reply_to {
289 params.insert("reply_to".to_string(), reply_to.to_string());
290 }
291
292 if let Some(forward_messages) = forward_messages {
293 let ids: Vec<String> = forward_messages.iter().map(|id| id.to_string()).collect();
294 params.insert("forward_messages".to_string(), ids.join(","));
295 }
296
297 if disable_mentions {
298 params.insert("disable_mentions".to_string(), "1".to_string());
299 }
300
301 if dont_parse_links {
302 params.insert("dont_parse_links".to_string(), "1".to_string());
303 }
304
305 let response = self.call_method("messages.send", params).await?;
306 let message_id = response["response"].as_i64().ok_or_else(|| {
307 VkError::InvalidResponse("Expected message_id in response".to_string())
308 })?;
309
310 Ok(message_id)
311 }
312
313 #[allow(clippy::too_many_arguments)]
314 pub async fn messages_edit(
315 &self,
316 peer_id: i64,
317 message_id: i64,
318 message: &str,
319 keyboard: Option<&Keyboard>,
320 attachment: Option<&str>,
321 keep_forward_messages: bool,
322 keep_snippets: bool,
323 ) -> VkResult<bool> {
324 let mut params = HashMap::new();
325 params.insert("peer_id".to_string(), peer_id.to_string());
326 params.insert("message_id".to_string(), message_id.to_string());
327 params.insert("message".to_string(), message.to_string());
328
329 if let Some(keyboard) = keyboard {
330 params.insert("keyboard".to_string(), keyboard.to_json_string());
331 }
332
333 if let Some(attachment) = attachment {
334 params.insert("attachment".to_string(), attachment.to_string());
335 }
336
337 if keep_forward_messages {
338 params.insert("keep_forward_messages".to_string(), "1".to_string());
339 }
340
341 if keep_snippets {
342 params.insert("keep_snippets".to_string(), "1".to_string());
343 }
344
345 let response = self.call_method("messages.edit", params).await?;
346 Ok(response["response"].as_i64().unwrap_or(0) == 1)
347 }
348
349 pub async fn messages_delete(
350 &self,
351 message_ids: &[i64],
352 delete_for_all: bool,
353 spam: bool,
354 ) -> VkResult<serde_json::Map<String, Value>> {
355 let mut params = HashMap::new();
356 let ids: Vec<String> = message_ids.iter().map(|id| id.to_string()).collect();
357 params.insert("message_ids".to_string(), ids.join(","));
358
359 if delete_for_all {
360 params.insert("delete_for_all".to_string(), "1".to_string());
361 }
362
363 if spam {
364 params.insert("spam".to_string(), "1".to_string());
365 }
366
367 let response = self.call_method("messages.delete", params).await?;
368 Ok(response["response"]
369 .as_object()
370 .ok_or_else(|| VkError::InvalidResponse("Expected object in response".to_string()))?
371 .clone())
372 }
373
374 pub async fn messages_restore(&self, message_id: i64) -> VkResult<bool> {
375 let mut params = HashMap::new();
376 params.insert("message_id".to_string(), message_id.to_string());
377
378 let response = self.call_method("messages.restore", params).await?;
379 Ok(response["response"].as_i64().unwrap_or(0) == 1)
380 }
381
382 pub async fn messages_mark_as_read(
383 &self,
384 peer_id: i64,
385 start_message_id: Option<i64>,
386 ) -> VkResult<bool> {
387 let mut params = HashMap::new();
388 params.insert("peer_id".to_string(), peer_id.to_string());
389
390 if let Some(start_id) = start_message_id {
391 params.insert("start_message_id".to_string(), start_id.to_string());
392 }
393
394 let response = self.call_method("messages.markAsRead", params).await?;
395 Ok(response["response"].as_i64().unwrap_or(0) == 1)
396 }
397
398 pub async fn messages_mark_as_important(
399 &self,
400 message_ids: &[i64],
401 important: Option<i32>,
402 ) -> VkResult<Vec<i64>> {
403 let mut params = HashMap::new();
404 let ids: Vec<String> = message_ids.iter().map(|id| id.to_string()).collect();
405 params.insert("message_ids".to_string(), ids.join(","));
406
407 if let Some(important) = important {
408 params.insert("important".to_string(), important.to_string());
409 }
410
411 let response = self.call_method("messages.markAsImportant", params).await?;
412 let marked_ids: Vec<i64> = response["response"]
413 .as_array()
414 .ok_or_else(|| VkError::InvalidResponse("Expected array in response".to_string()))?
415 .iter()
416 .filter_map(|v| v.as_i64())
417 .collect();
418
419 Ok(marked_ids)
420 }
421
422 pub async fn messages_get_conversations(
423 &self,
424 offset: i32,
425 count: i32,
426 filter: Option<&str>,
427 ) -> VkResult<Value> {
428 let mut params = HashMap::new();
429 params.insert("offset".to_string(), offset.to_string());
430 params.insert("count".to_string(), count.to_string());
431
432 if let Some(filter) = filter {
433 params.insert("filter".to_string(), filter.to_string());
434 }
435
436 self.call_method("messages.getConversations", params).await
437 }
438
439 pub async fn messages_get_conversation_members(
440 &self,
441 peer_id: i64,
442 fields: Option<&str>,
443 ) -> VkResult<Value> {
444 let mut params = HashMap::new();
445 params.insert("peer_id".to_string(), peer_id.to_string());
446
447 if let Some(fields) = fields {
448 params.insert("fields".to_string(), fields.to_string());
449 }
450
451 self.call_method("messages.getConversationMembers", params)
452 .await
453 }
454
455 pub async fn messages_get_history(
456 &self,
457 peer_id: i64,
458 offset: i32,
459 count: i32,
460 start_message_id: Option<i64>,
461 rev: bool,
462 ) -> VkResult<Value> {
463 let mut params = HashMap::new();
464 params.insert("peer_id".to_string(), peer_id.to_string());
465 params.insert("offset".to_string(), offset.to_string());
466 params.insert("count".to_string(), count.to_string());
467
468 if let Some(start_id) = start_message_id {
469 params.insert("start_message_id".to_string(), start_id.to_string());
470 }
471
472 if rev {
473 params.insert("rev".to_string(), "1".to_string());
474 }
475
476 self.call_method("messages.getHistory", params).await
477 }
478
479 pub async fn messages_get_by_id(
480 &self,
481 message_ids: &[i64],
482 preview_length: i32,
483 extended: bool,
484 ) -> VkResult<Value> {
485 let mut params = HashMap::new();
486 let ids: Vec<String> = message_ids.iter().map(|id| id.to_string()).collect();
487 params.insert("message_ids".to_string(), ids.join(","));
488 params.insert("preview_length".to_string(), preview_length.to_string());
489
490 if extended {
491 params.insert("extended".to_string(), "1".to_string());
492 }
493
494 self.call_method("messages.getById", params).await
495 }
496
497 pub async fn messages_search(
498 &self,
499 query: &str,
500 peer_id: Option<i64>,
501 date: Option<i64>,
502 count: i32,
503 ) -> VkResult<Value> {
504 let mut params = HashMap::new();
505 params.insert("q".to_string(), query.to_string());
506 params.insert("count".to_string(), count.to_string());
507
508 if let Some(peer_id) = peer_id {
509 params.insert("peer_id".to_string(), peer_id.to_string());
510 }
511
512 if let Some(date) = date {
513 params.insert("date".to_string(), date.to_string());
514 }
515
516 self.call_method("messages.search", params).await
517 }
518
519 pub async fn messages_search_conversations(
520 &self,
521 query: &str,
522 count: i32,
523 extended: bool,
524 fields: Option<&str>,
525 ) -> VkResult<Value> {
526 let mut params = HashMap::new();
527 params.insert("q".to_string(), query.to_string());
528 params.insert("count".to_string(), count.to_string());
529
530 if extended {
531 params.insert("extended".to_string(), "1".to_string());
532 }
533
534 if let Some(fields) = fields {
535 params.insert("fields".to_string(), fields.to_string());
536 }
537
538 self.call_method("messages.searchConversations", params)
539 .await
540 }
541
542 pub async fn messages_get_attachments(
543 &self,
544 peer_id: i64,
545 media_type: &str,
546 start_from: Option<&str>,
547 count: i32,
548 ) -> VkResult<Value> {
549 let mut params = HashMap::new();
550 params.insert("peer_id".to_string(), peer_id.to_string());
551 params.insert("media_type".to_string(), media_type.to_string());
552 params.insert("count".to_string(), count.to_string());
553
554 if let Some(start_from) = start_from {
555 params.insert("start_from".to_string(), start_from.to_string());
556 }
557
558 self.call_method("messages.getAttachments", params).await
559 }
560
561 pub async fn messages_get_invite_link(&self, peer_id: i64, reset: bool) -> VkResult<String> {
562 let mut params = HashMap::new();
563 params.insert("peer_id".to_string(), peer_id.to_string());
564
565 if reset {
566 params.insert("reset".to_string(), "1".to_string());
567 }
568
569 let response = self.call_method("messages.getInviteLink", params).await?;
570 let link = response["response"]["link"]
571 .as_str()
572 .ok_or_else(|| VkError::MissingField("link".to_string()))?
573 .to_string();
574
575 Ok(link)
576 }
577
578 pub async fn messages_remove_chat_user(
579 &self,
580 chat_id: i64,
581 user_id: i64,
582 member_id: Option<i64>,
583 ) -> VkResult<bool> {
584 let mut params = HashMap::new();
585 params.insert("chat_id".to_string(), chat_id.to_string());
586 params.insert("user_id".to_string(), user_id.to_string());
587
588 if let Some(member_id) = member_id {
589 params.insert("member_id".to_string(), member_id.to_string());
590 }
591
592 let response = self.call_method("messages.removeChatUser", params).await?;
593 Ok(response["response"].as_i64().unwrap_or(0) == 1)
594 }
595
596 pub async fn messages_add_chat_user(&self, chat_id: i64, user_id: i64) -> VkResult<bool> {
597 let mut params = HashMap::new();
598 params.insert("chat_id".to_string(), chat_id.to_string());
599 params.insert("user_id".to_string(), user_id.to_string());
600
601 let response = self.call_method("messages.addChatUser", params).await?;
602 Ok(response["response"].as_i64().unwrap_or(0) == 1)
603 }
604
605 pub async fn messages_create_chat(
606 &self,
607 user_ids: &[i64],
608 title: Option<&str>,
609 ) -> VkResult<i64> {
610 let mut params = HashMap::new();
611 let ids: Vec<String> = user_ids.iter().map(|id| id.to_string()).collect();
612 params.insert("user_ids".to_string(), ids.join(","));
613
614 if let Some(title) = title {
615 params.insert("title".to_string(), title.to_string());
616 }
617
618 let response = self.call_method("messages.createChat", params).await?;
619 let chat_id = response["response"]
620 .as_i64()
621 .ok_or_else(|| VkError::InvalidResponse("Expected chat_id in response".to_string()))?;
622
623 Ok(chat_id)
624 }
625
626 pub async fn messages_set_activity(
627 &self,
628 peer_id: i64,
629 user_id: Option<i64>,
630 activity_type: &str,
631 ) -> VkResult<bool> {
632 let mut params = HashMap::new();
633 params.insert("peer_id".to_string(), peer_id.to_string());
634 params.insert("type".to_string(), activity_type.to_string());
635
636 if let Some(user_id) = user_id {
637 params.insert("user_id".to_string(), user_id.to_string());
638 }
639
640 let response = self.call_method("messages.setActivity", params).await?;
641 Ok(response["response"].as_i64().unwrap_or(0) == 1)
642 }
643
644 pub async fn messages_send_message_event_answer(
645 &self,
646 event_id: &str,
647 user_id: i64,
648 peer_id: i64,
649 event_data: Option<&str>,
650 ) -> VkResult<bool> {
651 let mut params = HashMap::new();
652 params.insert("event_id".to_string(), event_id.to_string());
653 params.insert("user_id".to_string(), user_id.to_string());
654 params.insert("peer_id".to_string(), peer_id.to_string());
655
656 if let Some(event_data) = event_data {
657 params.insert("event_data".to_string(), event_data.to_string());
658 }
659
660 let response = self
661 .call_method("messages.sendMessageEventAnswer", params)
662 .await?;
663 Ok(response["response"].as_i64().unwrap_or(0) == 1)
664 }
665
666 pub async fn users_get(
667 &self,
668 user_ids: &[i64],
669 fields: Option<&str>,
670 name_case: Option<&str>,
671 ) -> VkResult<Value> {
672 let mut params = HashMap::new();
673 let ids: Vec<String> = user_ids.iter().map(|id| id.to_string()).collect();
674 params.insert("user_ids".to_string(), ids.join(","));
675
676 if let Some(fields) = fields {
677 params.insert("fields".to_string(), fields.to_string());
678 }
679
680 if let Some(name_case) = name_case {
681 params.insert("name_case".to_string(), name_case.to_string());
682 }
683
684 self.call_method("users.get", params).await
685 }
686
687 pub async fn groups_get_long_poll_server(&self, group_id: i64) -> VkResult<LongPollServer> {
688 let mut params = HashMap::new();
689 params.insert("group_id".to_string(), group_id.to_string());
690
691 let response = self.call_method("groups.getLongPollServer", params).await?;
692
693 let server = response["response"]["server"]
694 .as_str()
695 .ok_or_else(|| VkError::MissingField("server".to_string()))?
696 .to_string();
697
698 let key = response["response"]["key"]
699 .as_str()
700 .ok_or_else(|| VkError::MissingField("key".to_string()))?
701 .to_string();
702
703 let ts = response["response"]["ts"]
704 .as_str()
705 .ok_or_else(|| VkError::MissingField("ts".to_string()))?
706 .to_string();
707
708 Ok(LongPollServer { server, key, ts })
709 }
710
711 pub async fn groups_get_by_id(
712 &self,
713 group_ids: &[i64],
714 fields: Option<&str>,
715 ) -> VkResult<Value> {
716 let mut params = HashMap::new();
717 let ids: Vec<String> = group_ids.iter().map(|id| id.to_string()).collect();
718 params.insert("group_ids".to_string(), ids.join(","));
719
720 if let Some(fields) = fields {
721 params.insert("fields".to_string(), fields.to_string());
722 }
723
724 self.call_method("groups.getById", params).await
725 }
726
727 pub async fn send_message(&self, peer_id: i64, message: &str) -> VkResult<i64> {
728 self.messages_send(
729 peer_id, message, None, None, None, None, None, false, false, None,
730 )
731 .await
732 }
733
734 pub async fn send_message_with_keyboard(
735 &self,
736 peer_id: i64,
737 message: &str,
738 keyboard: &Keyboard,
739 ) -> VkResult<i64> {
740 self.messages_send(
741 peer_id,
742 message,
743 Some(keyboard),
744 None,
745 None,
746 None,
747 None,
748 false,
749 false,
750 None,
751 )
752 .await
753 }
754
755 pub async fn send_reply(&self, peer_id: i64, message: &str, reply_to: i64) -> VkResult<i64> {
756 self.messages_send(
757 peer_id,
758 message,
759 None,
760 None,
761 None,
762 Some(reply_to),
763 None,
764 false,
765 false,
766 None,
767 )
768 .await
769 }
770}
771
772#[derive(Debug, Clone)]
773pub struct LongPollServer {
774 pub server: String,
775 pub key: String,
776 pub ts: String,
777}