vk-bot-api 1.0.0

Asynchronous VK Bot API library for Rust
Documentation
use crate::api::VkApi;
use crate::error::VkResult;
use crate::keyboard::Keyboard;
use crate::models::Event;
use async_trait::async_trait;

#[async_trait]
pub trait MessageHandler: Send + Sync {
    async fn handle(&self, event: &Event, api: &VkApi) -> VkResult<()>;
}

#[derive(Debug, Clone, Default)]
pub struct DefaultMessageHandler;

#[async_trait]
impl MessageHandler for DefaultMessageHandler {
    async fn handle(&self, event: &Event, api: &VkApi) -> VkResult<()> {
        match event {
            Event::MessageNew(message) => {
                let _ = api
                    .messages_mark_as_read(message.peer_id, Some(message.id))
                    .await;

                if message.text.starts_with('/') {
                    self.handle_command(message, api).await?;
                } else {
                    self.handle_text(message, api).await?;
                }

                if !message.attachments.is_empty() {
                    self.handle_attachments(message, api).await?;
                }

                if !message.fwd_messages.is_empty() {
                    self.handle_forwarded(message, api).await?;
                }
            }

            Event::MessageEdit(message) => {
                self.handle_edit(message, api).await?;
            }

            Event::MessageEvent(event) => {
                self.handle_message_event(event, api).await?;
            }

            Event::MessageTypingState(state) => {
                self.handle_typing(state, api).await?;
            }

            Event::MessageAllow(allow) => {
                self.handle_message_allow(allow, api).await?;
            }

            _ => {}
        }

        Ok(())
    }
}

impl DefaultMessageHandler {
    async fn handle_command(&self, message: &crate::models::Message, api: &VkApi) -> VkResult<()> {
        let command = message.text.split_whitespace().next().unwrap_or("");

        match command {
            "/start" | "/help" => {
                let keyboard = Keyboard::create_menu();
                api.messages_send(
                    message.peer_id,
                    "👋 Welcome! I'm a VK bot built with Rust.\n\nCommands:\n/start - Welcome message\n/help - Show help\n/menu - Show menu\n/info - Bot info",
                    Some(&keyboard),
                    None,
                    None,
                    None,
                    None,
                    false,
                    false,
                    None,
                ).await?;
            }

            "/menu" => {
                let keyboard = Keyboard::create_menu();
                api.messages_send(
                    message.peer_id,
                    "📱 Menu:",
                    Some(&keyboard),
                    None,
                    None,
                    None,
                    None,
                    false,
                    false,
                    None,
                )
                .await?;
            }

            "/info" => {
                api.messages_send(
                    message.peer_id,
                    "🤖 Bot built with vk-bot-api\n🦀 Powered by Rust\n⚡ Async/await\n🔧 Full VK API support",
                    None,
                    None,
                    None,
                    None,
                    None,
                    false,
                    false,
                    None,
                ).await?;
            }

            _ => {
                api.messages_send(
                    message.peer_id,
                    "❌ Unknown command. Use /help",
                    None,
                    None,
                    None,
                    None,
                    None,
                    false,
                    false,
                    None,
                )
                .await?;
            }
        }

        Ok(())
    }

    async fn handle_text(&self, message: &crate::models::Message, api: &VkApi) -> VkResult<()> {
        let response = format!("You said: {}", message.text);

        api.messages_send(
            message.peer_id,
            &response,
            None,
            None,
            None,
            Some(message.id),
            None,
            false,
            false,
            None,
        )
        .await?;

        Ok(())
    }

    async fn handle_attachments(
        &self,
        message: &crate::models::Message,
        api: &VkApi,
    ) -> VkResult<()> {
        let mut types = Vec::new();

        for attachment in &message.attachments {
            match attachment.attachment_type.as_str() {
                "photo" => types.push("📷 Photo"),
                "video" => types.push("🎥 Video"),
                "audio" => types.push("🎵 Audio"),
                "doc" => types.push("📎 Document"),
                "sticker" => types.push("😊 Sticker"),
                _ => types.push("📎 Attachment"),
            }
        }

        if !types.is_empty() {
            let text = format!("Received attachments: {}", types.join(", "));

            api.messages_send(
                message.peer_id,
                &text,
                None,
                None,
                None,
                Some(message.id),
                None,
                false,
                false,
                None,
            )
            .await?;
        }

        Ok(())
    }

    async fn handle_forwarded(
        &self,
        message: &crate::models::Message,
        api: &VkApi,
    ) -> VkResult<()> {
        let count = message.fwd_messages.len();

        api.messages_send(
            message.peer_id,
            &format!("Forwarded messages: {}", count),
            None,
            None,
            None,
            Some(message.id),
            None,
            false,
            false,
            None,
        )
        .await?;

        Ok(())
    }

    async fn handle_edit(&self, message: &crate::models::Message, api: &VkApi) -> VkResult<()> {
        api.messages_send(
            message.peer_id,
            "Message was edited",
            None,
            None,
            None,
            None,
            None,
            true,
            false,
            None,
        )
        .await?;

        Ok(())
    }

    async fn handle_message_event(
        &self,
        event: &crate::models::MessageEvent,
        api: &VkApi,
    ) -> VkResult<()> {
        let _ = api
            .messages_send_message_event_answer(
                &event.event_id,
                event.user_id,
                event.peer_id,
                Some(r#"{"type": "show_snackbar", "text": "Done!"}"#),
            )
            .await;

        if let Some(payload) = &event.payload
            && let Some(command) = payload.get("command").and_then(|c| c.as_str())
            && command == "help"
        {
            api.messages_send(
                event.peer_id,
                "Help: Use /commands for available commands",
                None,
                None,
                None,
                None,
                None,
                false,
                false,
                None,
            )
            .await?;
        }

        Ok(())
    }

    async fn handle_typing(
        &self,
        _state: &crate::models::MessageTypingState,
        _api: &VkApi,
    ) -> VkResult<()> {
        Ok(())
    }

    async fn handle_message_allow(
        &self,
        allow: &crate::models::MessageAllow,
        api: &VkApi,
    ) -> VkResult<()> {
        api.messages_send(
            allow.user_id,
            "Thanks for allowing messages!",
            None,
            None,
            None,
            None,
            None,
            false,
            false,
            None,
        )
        .await?;

        Ok(())
    }
}

#[derive(Debug, Clone)]
pub struct AdminHandler {
    admin_ids: Vec<i64>,
}

impl AdminHandler {
    pub fn new(admin_ids: Vec<i64>) -> Self {
        Self { admin_ids }
    }

    pub fn is_admin(&self, user_id: i64) -> bool {
        self.admin_ids.contains(&user_id)
    }
}

#[async_trait]
impl MessageHandler for AdminHandler {
    async fn handle(&self, event: &Event, api: &VkApi) -> VkResult<()> {
        if let Event::MessageNew(message) = event
            && self.is_admin(message.from_id)
        {
            self.handle_admin_command(message, api).await?;
        }

        Ok(())
    }
}

impl AdminHandler {
    async fn handle_admin_command(
        &self,
        message: &crate::models::Message,
        api: &VkApi,
    ) -> VkResult<()> {
        if message.text.starts_with("/admin") {
            let keyboard = Keyboard::create_admin_menu();

            api.messages_send(
                message.peer_id,
                "🔧 Admin panel:",
                Some(&keyboard),
                None,
                None,
                None,
                None,
                false,
                false,
                None,
            )
            .await?;
        }

        Ok(())
    }
}