maxbot 0.1.0

Автоматизация работы с чат-ботами MAX
Documentation
// src/attachments.rs

use std::path::PathBuf;

/// Источник файлового вложения: локальный файл или уже полученный токен.
#[derive(Debug, Clone)]
pub enum AttachmentSource {
    LocalFile(PathBuf),
    Token(String),
}

/// Данные контакта для вложения `contact`.
#[derive(Debug, Clone)]
pub struct ContactData {
    pub name: Option<String>,
    pub contact_id: Option<i64>,
    pub vcf_info: Option<String>,
    pub vcf_phone: Option<String>,
}

/// Кнопка inline-клавиатуры.
#[derive(Debug, Clone)]
pub struct InlineKeyboardButton {
    pub r#type: String,          // "callback", "link", "request_geo_location", "open_app", "message"
    pub text: String,
    pub payload: Option<String>,
    pub url: Option<String>,
    pub quick: Option<bool>,
    pub web_app: Option<String>,
    pub contact_id: Option<i64>,
    pub app_payload: Option<String>,
    pub message_text: Option<String>,
}

/// Inline-клавиатура (двумерный массив кнопок).
#[derive(Debug, Clone)]
pub struct InlineKeyboard {
    pub buttons: Vec<Vec<InlineKeyboardButton>>,
}

/// Данные для вложения `share` (отсылка/превью ссылки).
#[derive(Debug, Clone)]
pub struct ShareData {
    pub url: Option<String>,
    pub token: Option<String>,
}

/// Тип вложения.
#[derive(Debug, Clone)]
pub enum Attachment {
    Image {
        source: AttachmentSource,
        token: Option<String>,
    },
    Video {
        source: AttachmentSource,
        token: Option<String>,
    },
    Audio {
        source: AttachmentSource,
        token: Option<String>,
    },
    File {
        source: AttachmentSource,
        token: Option<String>,
    },
    Sticker {
        code: String,
    },
    Contact(ContactData),
    InlineKeyboard(InlineKeyboard),
    Location {
        latitude: f64,
        longitude: f64,
    },
    Share(ShareData),
}

impl Attachment {
    /// Возвращает строковый тип вложения (как требуется API MAX).
    pub fn get_type(&self) -> &'static str {
        match self {
            Attachment::Image { .. } => "image",
            Attachment::Video { .. } => "video",
            Attachment::Audio { .. } => "audio",
            Attachment::File { .. } => "file",
            Attachment::Sticker { .. } => "sticker",
            Attachment::Contact(_) => "contact",
            Attachment::InlineKeyboard(_) => "inline_keyboard",
            Attachment::Location { .. } => "location",
            Attachment::Share(_) => "share",
        }
    }

    // -------------------------------------------------------------------------
    // Удобные конструкторы для файловых вложений
    // -------------------------------------------------------------------------

    pub fn image_local(path: impl Into<PathBuf>) -> Self {
        Attachment::Image {
            source: AttachmentSource::LocalFile(path.into()),
            token: None,
        }
    }

    pub fn image_token(token: impl Into<String>) -> Self {
        Attachment::Image {
            source: AttachmentSource::Token(token.into()),
            token: None,
        }
    }

    pub fn video_local(path: impl Into<PathBuf>) -> Self {
        Attachment::Video {
            source: AttachmentSource::LocalFile(path.into()),
            token: None,
        }
    }

    pub fn video_token(token: impl Into<String>) -> Self {
        Attachment::Video {
            source: AttachmentSource::Token(token.into()),
            token: None,
        }
    }

    pub fn audio_local(path: impl Into<PathBuf>) -> Self {
        Attachment::Audio {
            source: AttachmentSource::LocalFile(path.into()),
            token: None,
        }
    }

    pub fn audio_token(token: impl Into<String>) -> Self {
        Attachment::Audio {
            source: AttachmentSource::Token(token.into()),
            token: None,
        }
    }

    pub fn file_local(path: impl Into<PathBuf>) -> Self {
        Attachment::File {
            source: AttachmentSource::LocalFile(path.into()),
            token: None,
        }
    }

    pub fn file_token(token: impl Into<String>) -> Self {
        Attachment::File {
            source: AttachmentSource::Token(token.into()),
            token: None,
        }
    }

    // -------------------------------------------------------------------------
    // Конструкторы для других типов вложений
    // -------------------------------------------------------------------------

    pub fn sticker(code: impl Into<String>) -> Self {
        Attachment::Sticker { code: code.into() }
    }

    pub fn contact(data: ContactData) -> Self {
        Attachment::Contact(data)
    }

    pub fn inline_keyboard(keyboard: InlineKeyboard) -> Self {
        Attachment::InlineKeyboard(keyboard)
    }

    pub fn location(latitude: f64, longitude: f64) -> Self {
        Attachment::Location { latitude, longitude }
    }

    pub fn share(data: ShareData) -> Self {
        Attachment::Share(data)
    }
}

// -----------------------------------------------------------------------------
// Builder для InlineKeyboard
// -----------------------------------------------------------------------------
pub struct InlineKeyboardBuilder {
    rows: Vec<Vec<InlineKeyboardButton>>,
}

impl InlineKeyboardBuilder {
    pub fn new() -> Self {
        Self { rows: vec![] }
    }

    /// Добавляет строку кнопок (заменяет текущую последнюю строку? Нет, добавляет новую строку).
    pub fn row(mut self, buttons: Vec<InlineKeyboardButton>) -> Self {
        self.rows.push(buttons);
        self
    }

    /// Добавляет кнопку в последнюю строку; если строк нет, создаёт новую.
    pub fn button(mut self, btn: InlineKeyboardButton) -> Self {
        if self.rows.is_empty() {
            self.rows.push(vec![]);
        }
        self.rows.last_mut().unwrap().push(btn);
        self
    }

    /// Добавляет несколько кнопок в последнюю строку.
    pub fn buttons(mut self, btns: Vec<InlineKeyboardButton>) -> Self {
        if self.rows.is_empty() {
            self.rows.push(vec![]);
        }
        self.rows.last_mut().unwrap().extend(btns);
        self
    }

    pub fn build(self) -> InlineKeyboard {
        InlineKeyboard { buttons: self.rows }
    }
}

impl Default for InlineKeyboardBuilder {
    fn default() -> Self {
        Self::new()
    }
}

// -----------------------------------------------------------------------------
// Удобные конструкторы для кнопок
// -----------------------------------------------------------------------------
impl InlineKeyboardButton {
    pub fn callback(text: impl Into<String>, payload: impl Into<String>) -> Self {
        Self {
            r#type: "callback".to_string(),
            text: text.into(),
            payload: Some(payload.into()),
            url: None,
            quick: None,
            web_app: None,
            contact_id: None,
            app_payload: None,
            message_text: None,
        }
    }

    pub fn link(text: impl Into<String>, url: impl Into<String>) -> Self {
        Self {
            r#type: "link".to_string(),
            text: text.into(),
            payload: None,
            url: Some(url.into()),
            quick: None,
            web_app: None,
            contact_id: None,
            app_payload: None,
            message_text: None,
        }
    }

    pub fn request_contact(text: impl Into<String>) -> Self {
        Self {
            r#type: "request_contact".to_string(),
            text: text.into(),
            payload: None,
            url: None,
            quick: Some(true),
            web_app: None,
            contact_id: None,
            app_payload: None,
            message_text: None,
        }
    }

    pub fn request_location(text: impl Into<String>) -> Self {
        Self {
            r#type: "request_geo_location".to_string(),
            text: text.into(),
            payload: None,
            url: None,
            quick: Some(true),
            web_app: None,
            contact_id: None,
            app_payload: None,
            message_text: None,
        }
    }

    pub fn open_app(text: impl Into<String>, web_app_url: impl Into<String>, contact_id: i64, payload: Option<String>) -> Self {
        Self {
            r#type: "open_app".to_string(),
            text: text.into(),
            payload: payload,
            url: None,
            quick: None,
            web_app: Some(web_app_url.into()),
            contact_id: Some(contact_id),
            app_payload: None,
            message_text: None,
        }
    }

    pub fn message(text: impl Into<String>, message_text: impl Into<String>) -> Self {
        Self {
            r#type: "message".to_string(),
            text: text.into(),
            payload: None,
            url: None,
            quick: None,
            web_app: None,
            contact_id: None,
            app_payload: None,
            message_text: Some(message_text.into()),
        }
    }
}