zynko 0.1.0

Biblioteca oficial para bots do Zynko: API, gateway e voz.
Documentation
//! "/" commands, a card and buttons: `/dado` rolls a die and shows the result in a card with
//! a "Rolar de novo" button; `/ping` answers only the person who used it.
//!
//!   ZYNKO_TOKEN=bot_... cargo run --example dado

use std::time::{SystemTime, UNIX_EPOCH};

use zynko::{
    async_trait, Button, Card, Client, Command, CommandOption, EditOptions, EventHandler,
    HandlerResult, Intents, Interaction, OptionType, ReplyOptions, User,
};

const LAVENDER: u32 = 0xa594f9;

/// A number from 1 to `sides` (no `rand` needed for a die).
fn roll(sides: i64) -> i64 {
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.subsec_nanos())
        .unwrap_or(0) as i64;
    1 + nanos % sides.max(1)
}

/// The result card, with a button that rolls the same die again.
fn result(sides: i64, who: &str) -> (Card, Vec<Button>) {
    let card = Card::new()
        .author(format!("{who} rolou um d{sides}"))
        .title(format!("🎲 {}", roll(sides)))
        .color(LAVENDER);
    (
        card,
        vec![Button::primary("Rolar de novo", format!("dado:{sides}")).emoji("🎲")],
    )
}

struct Bot;

#[async_trait]
impl EventHandler for Bot {
    async fn ready(&self, ctx: Client, user: User) -> HandlerResult {
        ctx.set_commands(&[
            Command::new("dado", "Rola um dado").option(
                CommandOption::new("lados", "Quantos lados", OptionType::Integer)
                    .min(2.0)
                    .max(100.0),
            ),
            Command::new("ping", "Responde pong"),
        ])
        .await?;
        println!("{} pronto", user.display_name);
        Ok(())
    }

    async fn interaction(&self, _ctx: Client, i: Interaction) -> HandlerResult {
        let who = i
            .member
            .as_ref()
            .and_then(|m| m.nick.clone())
            .unwrap_or_else(|| i.user.display_name.clone());
        if i.is_button() {
            // "dado:20" → roll a d20 again, in the same message.
            if let Some(sides) = i
                .custom_id
                .as_deref()
                .and_then(|c| c.strip_prefix("dado:"))
                .and_then(|s| s.parse().ok())
            {
                let (card, row) = result(sides, &who);
                i.update(EditOptions::new().cards(vec![card]).components(vec![row]))
                    .await?;
            }
            return Ok(());
        }
        match i.command.as_deref() {
            Some("dado") => {
                let (card, row) = result(i.option_i64("lados").unwrap_or(6), &who);
                i.reply(ReplyOptions::default().card(card).row(row)).await?;
            }
            Some("ping") => {
                i.reply(ReplyOptions::new("pong").ephemeral()).await?;
            }
            _ => {
                i.reply(ReplyOptions::new("Não conheço esse comando.").ephemeral())
                    .await?;
            }
        }
        Ok(())
    }
}

#[tokio::main]
async fn main() -> zynko::Result<()> {
    let token = std::env::var("ZYNKO_TOKEN").expect("defina ZYNKO_TOKEN");
    let mut builder = Client::builder(token)
        .intents(Intents::GUILDS)
        .event_handler(Bot);
    if let Ok(api) = std::env::var("ZYNKO_API") {
        builder = builder.api(api);
    }
    builder.build()?.start().await
}