zynko 0.1.0

Biblioteca oficial para bots do Zynko: API, gateway e voz.
Documentation
//! Someone used one of the bot's "/" commands, or clicked a button on one of its messages.

use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

use serde_json::Value;

use crate::client::Client;
use crate::error::{Error, Result};
use crate::message::Message;
use crate::rest::{CallbackType, EditOptions, ReplyOptions};
use crate::types::*;

/// Someone used one of the bot's "/" commands, or clicked a button on one of its messages.
/// Answer within 15 minutes: [`reply`](Self::reply) posts a message (or one only they see),
/// [`update`](Self::update) changes the message whose button was clicked.
///
/// ```no_run
/// # async fn f(i: zynko::Interaction) -> zynko::Result<()> {
/// if i.command.as_deref() == Some("dado") {
///     let lados = i.option_i64("lados").unwrap_or(6);
///     i.reply(format!("🎲 {}", 1 + (lados / 2))).await?;
/// }
/// # Ok(()) }
/// ```
#[derive(Clone, Debug)]
pub struct Interaction {
    pub id: Id,
    pub token: String,
    pub kind: InteractionType,
    pub channel_id: Id,
    pub guild_id: Option<Id>,
    /// Who used it.
    pub user: User,
    pub member: Option<Member>,
    /// The command's name, for commands.
    pub command: Option<String>,
    /// What was typed for each option, by name. User, channel and role options are ids.
    pub options: HashMap<String, Value>,
    /// The clicked button's `custom_id`, for buttons.
    pub custom_id: Option<String>,
    /// The message whose button was clicked.
    pub message: Option<Message>,
    /// The event as it came.
    pub data: InteractionData,
    replied: Arc<AtomicBool>,
    client: Client,
}

impl Interaction {
    pub(crate) fn new(client: Client, data: InteractionData) -> Self {
        let options = data
            .command
            .as_ref()
            .map(|c| {
                c.options
                    .iter()
                    .map(|o| (o.name.clone(), o.value.clone()))
                    .collect()
            })
            .unwrap_or_default();
        Self {
            id: data.id.clone(),
            token: data.token.clone(),
            kind: data.kind,
            channel_id: data.channel_id.clone(),
            guild_id: data.guild_id.clone(),
            user: data.user.clone(),
            member: data.member.clone(),
            command: data.command.as_ref().map(|c| c.name.clone()),
            options,
            custom_id: data.custom_id.clone(),
            message: data
                .message
                .clone()
                .map(|m| Message::new(client.clone(), m)),
            data,
            replied: Arc::new(AtomicBool::new(false)),
            client,
        }
    }

    pub fn client(&self) -> &Client {
        &self.client
    }

    /// Whether it is a click on a button.
    pub fn is_button(&self) -> bool {
        self.kind == InteractionType::Button
    }

    /// One option's value; `None` when it was left out.
    pub fn option(&self, name: &str) -> Option<&Value> {
        self.options.get(name)
    }

    /// A text option (also user, channel and role ids).
    pub fn option_str(&self, name: &str) -> Option<&str> {
        self.option(name)?.as_str()
    }

    /// An integer option (also read from text).
    pub fn option_i64(&self, name: &str) -> Option<i64> {
        let v = self.option(name)?;
        v.as_i64()
            .or_else(|| v.as_f64().map(|f| f as i64))
            .or_else(|| v.as_str()?.trim().parse().ok())
    }

    /// A number option (also read from text).
    pub fn option_f64(&self, name: &str) -> Option<f64> {
        let v = self.option(name)?;
        v.as_f64().or_else(|| v.as_str()?.trim().parse().ok())
    }

    pub fn option_bool(&self, name: &str) -> Option<bool> {
        self.option(name)?.as_bool()
    }

    /// The channel, when the client has seen it.
    pub fn channel(&self) -> Option<Channel> {
        self.client.channel(&self.channel_id)
    }

    /// Whether it was answered already.
    pub fn replied(&self) -> bool {
        self.replied.load(Ordering::Relaxed)
    }

    /// Answers with a message in the channel, or only for that person with
    /// [`ReplyOptions::ephemeral`]. May be called again for follow-ups.
    pub async fn reply(&self, answer: impl Into<ReplyOptions>) -> Result<Message> {
        let d = self
            .client
            .rest()
            .interaction_callback(&self.id, &self.token, CallbackType::Message, answer.into())
            .await?;
        self.replied.store(true, Ordering::Relaxed);
        Ok(Message::new(self.client.clone(), d))
    }

    /// Changes the message whose button was clicked: whatever is given (text, cards, buttons).
    pub async fn update(&self, edit: impl Into<EditOptions>) -> Result<Message> {
        if !self.is_button() {
            return Err(Error::Other("update() é só para cliques em botões".into()));
        }
        let d = self
            .client
            .rest()
            .interaction_callback(&self.id, &self.token, CallbackType::Update, edit.into())
            .await?;
        self.replied.store(true, Ordering::Relaxed);
        Ok(Message::new(self.client.clone(), d))
    }
}