safe_vk/
engine.rs

1use crate::{responses::Ctx, traits::Method, Methods};
2use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc};
3
4pub struct SafeVkBot {
5    methods: Arc<Methods>,
6    commands: HashMap<
7        String,
8        Box<dyn Fn(Arc<Methods>) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
9    >,
10    watching: Vec<Box<dyn Fn(Arc<Methods>) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>>>>,
11}
12
13impl SafeVkBot {
14    /// Creates a new instance for `SafeVkBot`
15    pub fn create(token: &str) -> Self {
16        SafeVkBot {
17            methods: Arc::new(Methods::new(token.to_string())),
18            commands: HashMap::new(),
19            watching: Vec::new(),
20        }
21    }
22
23    /// Creates a new command that bot will listen
24    pub fn command<F, Fut>(mut self, trigger: &str, handler: F) -> Self
25    where
26        F: Fn(Arc<Methods>) -> Fut + Send + Sync + 'static,
27        Fut: Future<Output = ()> + Send + 'static,
28    {
29        self.commands.insert(
30            trigger.to_string(),
31            Box::new(move |ctx| Box::pin(handler(ctx))),
32        );
33        self
34    }
35
36    /// Callback for each new request
37    pub fn watch<F, Fut>(mut self, handler: F) -> Self
38    where
39        F: Fn(Arc<Methods>) -> Fut + Send + Sync + 'static,
40        Fut: Future<Output = ()> + Send + 'static,
41    {
42        self.watching
43            .push(Box::new(move |ctx| Box::pin(handler(ctx))));
44        self
45    }
46
47    /// Starts a new long poll session
48    /// For more info: https://dev.vk.com/en/api/bots-long-poll/getting-started
49    pub async fn start_polling(&self, group_id: u32) {
50        let (s, mut r) = tokio::sync::mpsc::channel(10);
51        let methods = self.methods.clone();
52
53        tokio::spawn(async move {
54            loop {
55                let response = methods.long_poll(group_id).await;
56                let update = methods
57                    .connect(&response.server, response.key, response.ts, 25)
58                    .await;
59
60                s.send(update).await.expect("unable to send");
61            }
62        });
63
64        while let Some(update) = r.recv().await {
65            self.update_state(update).await;
66        }
67    }
68
69    ///
70    async fn update_state(&self, update: Ctx) {
71        let mut state = self.write_state().await;
72
73        for watch in &self.watching {
74            tokio::spawn(watch(self.methods.clone()));
75        }
76
77        if let Some(command) = self.parse_command(&update) {
78            if let Some(handler) = self.commands.get(&command) {
79                tokio::spawn(handler(self.methods.clone()));
80            }
81        }
82
83        *state = update;
84    }
85
86    async fn write_state(&self) -> tokio::sync::RwLockWriteGuard<'_, Ctx> {
87        self.methods.context.write().await
88    }
89
90    /// Parses a command
91    #[inline]
92    fn parse_command(&self, update: &Ctx) -> Option<String> {
93        update.updates.iter().find_map(|v| {
94            v.object.message.as_ref().and_then(|msg| {
95                msg.text
96                    .to_lowercase()
97                    .split_whitespace()
98                    .next()
99                    .map(|command| command.to_string())
100            })
101        })
102    }
103}