use clap::Parser; use secrecy::{ExposeSecret, SecretString}; use snafu::{ResultExt, Snafu}; use std::{error::Error, time::Duration}; use tracing_subscriber::fmt::format::FmtSpan; use twilight_cache_inmemory::{DefaultInMemoryCache, ResourceType}; use twilight_gateway::{Event, EventTypeFlags, Intents, Shard, ShardId, StreamExt}; use twilight_model::{ application::{command::CommandType, interaction::InteractionData}, gateway::payload::incoming::InteractionCreate, http::interaction::{InteractionResponse, InteractionResponseType}, }; use twilight_util::builder::command::CommandBuilder; #[derive(Debug, Parser)] struct Args { #[arg(long, env)] discord_token: SecretString, } #[derive(Debug, Snafu)] enum AppError { #[snafu(whatever, display("{message}"))] Whatever { message: String, #[snafu(source(from(Box, Some)))] source: Option>, }, } #[tokio::main] #[snafu::report] async fn main() -> Result<(), AppError> { let Args { discord_token } = Args::parse(); tracing_subscriber::fmt() .pretty() .with_span_events(FmtSpan::ACTIVE) .init(); let shard_id = ShardId::ONE; let intents = Intents::empty(); let mut shard = Shard::new(shard_id, discord_token.expose_secret().into(), intents); let cache = DefaultInMemoryCache::builder() .resource_types(ResourceType::empty()) .build(); tracing::info!("info"); let client = twilight_http::Client::new(discord_token.expose_secret().into()); let current_application = client .current_user_application() .await .whatever_context("couldn't get current Discord application")?; let current_application = current_application .model() .await .whatever_context("couldn't get current Discord application")?; let application_id = current_application.id; let interaction_client = client.interaction(application_id); let create_lobby = CommandBuilder::new( "create", "Create a lobby in this channel", CommandType::ChatInput, ) .validate() .whatever_context("command wasn't correct")? .build(); let commands = vec![create_lobby]; let returned_commands = interaction_client .set_global_commands(&commands) .await .whatever_context("failed to set interaction commands")? .models() .await .whatever_context("failed to deserialize set commands")?; tracing::info!(?returned_commands); while let Some(event_res) = shard.next_event(EventTypeFlags::INTERACTION_CREATE).await { let event = match event_res { Ok(event) => event, Err(receive_message_error) => { tracing::error!(?receive_message_error); continue; } }; tracing::info!(?event); cache.update(&event); match event { Event::GatewayClose(close_frame) => { tracing::error!(?close_frame); break; } Event::InteractionCreate(interaction_create) => { let InteractionCreate(interaction) = *interaction_create; tracing::info!(?interaction); match interaction.data { None => { tracing::warn!("missing expected interaction data"); continue; } Some(InteractionData::ApplicationCommand(command_data)) => { let command_data = *command_data; match command_data.name.as_str() { "create" => { tracing::info!("hurray for creating a lobby! TODO"); let initial_response = InteractionResponse { kind: InteractionResponseType::DeferredChannelMessageWithSource, data: None, }; if let Err(error) = interaction_client .create_response( interaction.id, &interaction.token, &initial_response, ) .await { tracing::error!(?error); } tokio::time::sleep(Duration::from_secs(5)).await; match interaction_client .update_response(&interaction.token) .content(Some("hello world!")) .await { Ok(message) => match message.model().await { Ok(message) => { tracing::info!(?message); } Err(error) => { tracing::error!(?error); } }, Err(error) => { tracing::error!(?error); } } } command_name => { tracing::warn!(?command_name, "did not expect command"); } } } _ => {} } } _ => {} } } Ok(()) }