soundpad-bumblebee 0.3.0

On-the-fly wordmixing through Soundpad
use std::{net::SocketAddr, sync::Arc};

use axum::{
    extract::State,
    http::StatusCode,
    response::{Html, IntoResponse},
    routing::{get, post},
    Form, Router,
};
use clap::Parser;
use color_eyre::eyre::Result;
use play::play;
use serde::Deserialize;
use soundpad_remote_client::{Client, ClientBuilder, Sound};
use tracing::{info, instrument, metadata::LevelFilter};
use tracing_error::ErrorLayer;
use tracing_subscriber::{fmt::format, prelude::*};

mod play;

#[derive(Parser)]
#[command(author, version, about)]
struct Args {
    message: Vec<String>,

    #[clap(short, long, default_value = "127.0.0.1:3003")]
    address: SocketAddr,
}

#[tokio::main]
async fn main() -> Result<()> {
    let args = Args::parse();
    let registry = tracing_subscriber::registry();
    #[cfg(feature = "console")]
    let registry = registry.with(console_subscriber::spawn());

    registry
        .with(
            tracing_subscriber::fmt::layer()
                .event_format(format().pretty())
                .with_filter(LevelFilter::INFO),
        )
        .with(ErrorLayer::default())
        .init();
    color_eyre::install()?;
    info!("Starting up...");

    let client = ClientBuilder::new().connect()?;

    info!("Connected to Soundpad and ready!");

    let sounds = client.get_sound_list().await?;
    if !args.message.is_empty() {
        play(args.message, sounds, client).await?;
    } else {
        let shared_state = AppState { client, sounds };
        let app = Router::new()
            .route("/", get(show_form).post(accept_form))
            .with_state(shared_state);
        info!("Opened web interface on http://{}", args.address);
        axum::Server::bind(&args.address)
            .serve(app.into_make_service())
            .await?;
    }
    Ok(())
}

async fn show_form() -> Html<&'static str> {
    let page = include_str!("./index.html");
    Html(page)
}

#[derive(Debug, Clone)]
struct AppState {
    client: Client,
    sounds: Vec<Sound>,
}

#[derive(Deserialize, Debug)]
struct Input {
    message: String,
}

#[instrument]
async fn accept_form(State(state): State<AppState>, Form(input): Form<Input>) -> impl IntoResponse {
    info!("Got a request!");

    let message = input
        .message
        .clone()
        .split_whitespace()
        .map(|s| s.to_owned())
        .collect::<Vec<_>>();
    let sounds = state.sounds.clone();
    let client = state.client.clone();
    tokio::spawn(play(message, sounds, client));
    show_form().await
}