Skip to main content

vk_bot_api/
api.rs

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, &params).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}