safe_vk/
methods.rs

1use crate::{
2    parse_response,
3    traits::{Method, Request},
4    util::{Error, NdArray, Result},
5    Ctx, LongPollResponse, Members, RequestBuilder, User,
6};
7use serde::Serialize;
8use serde_json::json;
9use std::sync::Arc;
10use tokio::sync::RwLock;
11
12const VK: &'static str = "https://api.vk.com/method";
13
14#[derive(Debug, Clone)]
15pub struct Methods {
16    pub request: RequestBuilder,
17    pub context: Arc<RwLock<Ctx>>,
18}
19
20impl Method for Methods {
21    fn new(token: String) -> Self {
22        Methods {
23            request: RequestBuilder::new(token),
24            context: Arc::new(RwLock::new(Ctx::default())),
25        }
26    }
27
28    async fn keyboard<T: Serialize, N: NdArray<T>>(
29        &self,
30        message: &str,
31        one_time: bool,
32        inline: bool,
33        buttons: N,
34    ) -> Result<()> {
35        let state = self.context.read().await;
36        let dim_1 = buttons.shape().dims()[1];
37        let dim_2 = buttons.shape().dims()[0];
38
39        // Ensure that the first dimension (dim_1) is not greater than 5
40        // This is to enforce a maximum shape of 5x1 for the array
41        // Fore more info: https://dev.vk.com/ru/api/bots/development/keyboard
42        if dim_1 > 5 {
43            return Err(Error::DimOutOfRange {
44                shape: buttons.shape(),
45                dim: dim_1 as i32,
46            });
47        } else if dim_2 > 10 {
48            return Err(Error::DimOutOfRange {
49                shape: buttons.shape(),
50                dim: dim_2 as i32,
51            });
52        }
53
54        let button = json!({
55            "one_time": one_time,
56            "inline": inline,
57            "buttons": json!(buttons.slice()),
58        });
59
60        for update in &state.updates {
61            let button = serde_json::to_string(&button).unwrap();
62            self.request
63                .post(
64                    VK,
65                    "messages.send",
66                    &[
67                        ("message", message.to_string()),
68                        (
69                            "peer_id",
70                            update.object.message.as_ref().unwrap().peer_id.to_string(),
71                        ),
72                        ("keyboard", button),
73                        ("random_id", String::from("0")),
74                    ],
75                    {},
76                )
77                .await?;
78        }
79        Ok(())
80    }
81
82    async fn event_answer<
83        'de,
84        T: Serialize,
85        A: serde::de::DeserializeOwned + PartialEq + Serialize,
86    >(
87        &self,
88        event_data: T,
89        payload: A,
90    ) -> Result<Option<A>> {
91        let context = self.context().await;
92
93        for update in &context.updates {
94            if let Some(data) = &update.object.payload {
95                let deserialized_payload = serde_json::from_value::<A>(data.clone())?;
96
97                if deserialized_payload != payload {
98                    return Ok(None);
99                }
100
101                let event_data = serde_json::to_string(&event_data).unwrap();
102                // Safe unwraps here, because if response has object `payload` then VK api
103                // guarantee that it will contain `user_id` field and `peer_id`
104                let res = self
105                    .request
106                    .post(
107                        VK,
108                        "messages.sendMessageEventAnswer",
109                        &[
110                            ("event_data", event_data),
111                            ("user_id", update.object.user_id.unwrap().to_string()),
112                            (
113                                "event_id",
114                                update.object.event_id.as_ref().unwrap().to_string(),
115                            ),
116                            ("peer_id", update.object.peer_id.unwrap().to_string()),
117                        ],
118                        {},
119                    )
120                    .await?;
121
122                if let Ok(response) = parse_response!(res, crate::EventAnswer) {
123                    if response.get_status().is_ok() {
124                        return Ok(Some(payload));
125                    }
126                }
127            }
128        }
129        // Return None if updates doesn't have payload
130        Ok(None)
131    }
132
133    async fn reply(&self, message: &str) {
134        let state = self.context.read().await;
135        for update in &state.updates {
136            // Using match to extract `peer_id` from the update. According to the VK API
137            // documentation, `peer_id` is always present in `message_event` and `message_new` types
138            let peer_id = match update.update_type.as_str() {
139                "message_event" => update.object.peer_id.unwrap(),
140                "message_new" => update.object.message.as_ref().unwrap().peer_id,
141                _ => panic!("No peer_id found"),
142            };
143
144            self.request
145                .post(
146                    VK,
147                    "messages.send",
148                    &[
149                        ("message", message),
150                        ("peer_id", &peer_id.to_string()),
151                        ("random_id", "0"),
152                    ],
153                    {},
154                )
155                .await
156                .expect("Failed to send/get request from `messages.send`");
157        }
158    }
159
160    async fn long_poll(&self, group_id: u32) -> LongPollResponse {
161        let response = self
162            .request
163            .post(
164                VK,
165                "groups.getLongPollServer",
166                &[("group_id", &group_id.to_string())],
167                {},
168            )
169            .await
170            .expect("Unable to get request");
171
172        parse_response!(response, LongPollResponse)
173            .expect("Unable to parse `groups.getLongPollServer` response")
174    }
175
176    async fn connect(&self, server: &str, key: String, ts: String, wait: usize) -> Ctx {
177        let response = self
178            .request
179            .post(
180                server,
181                "",
182                &[
183                    ("act", "a_check"),
184                    ("key", &key),
185                    ("ts", &ts),
186                    ("wait", &wait.to_string()),
187                ],
188                {},
189            )
190            .await
191            .expect("Unable to get request");
192
193        parse_response!(response, Ctx).expect("Unable to parse longPoll request")
194    }
195
196    async fn get_users(&self, user_ids: &[i32]) -> Result<Vec<User>> {
197        let serialize = serde_json::to_string(&user_ids[0])?;
198        let response = self
199            .request
200            .post(VK, "users.get", &[("user_ids", &serialize)], {})
201            .await
202            .unwrap();
203        Ok(parse_response!(response, Vec<User>).expect("Unable to parse `users.get` response"))
204    }
205
206    /// This function retrieves a list of participants in the conversation
207    async fn get_members(
208        &self,
209        offset: Option<u16>,
210        count: Option<u16>,
211        extended: bool,
212    ) -> Result<Members> {
213        let context = self.context().await;
214        for update in &context.updates {
215            if let Some(message) = &update.object.message {
216                let mut params = vec![("peer_id", message.peer_id.to_string())];
217
218                if extended {
219                    params.push(("extended", String::from("1")));
220                } else {
221                    params.push(("extended", String::from("0")));
222                }
223
224                if let Some(offset_val) = offset {
225                    params.push(("offset", offset_val.to_string()));
226                }
227                if let Some(count_val) = count {
228                    params.push(("count", count_val.to_string()));
229                }
230
231                let res = self
232                    .request
233                    .post(VK, "messages.getConversationMembers", &params, {})
234                    .await?;
235
236                return Ok(parse_response!(res, Members)?);
237            }
238        }
239        Err(Error::NoContent {
240            from: "getConversationMembers",
241        })
242    }
243
244    fn custom_request(&self) -> &RequestBuilder {
245        &self.request
246    }
247
248    async fn context(&self) -> tokio::sync::RwLockReadGuard<'_, Ctx> {
249        self.context.read().await
250    }
251}