use crate::{command, group, GlobalData}; use serenity::client::Context; use serenity::framework::standard::{Args, CommandResult}; use serenity::model::channel::{AttachmentType, Message}; use serenity::utils::MessageBuilder; use songbird::driver::Bitrate; use songbird::input; use songbird::input::cached::Compressed; use std::borrow::Cow; use std::collections::HashMap; use std::path::{Path, PathBuf}; #[group] #[commands(say, list_words, list_voices)] pub struct Voices; async fn get_voice_dictionary(path: &Path) -> Result, tokio::io::Error> { let mut dir = tokio::fs::read_dir(path).await?; let mut dict = HashMap::new(); while let Some(word) = &dir.next_entry().await? { let word_name = word .path() .file_stem() .unwrap() .to_str() .unwrap() .to_string(); dict.insert(word_name, word.path()); } Ok(dict) } async fn get_voices(voice_path: &Path) -> Result, tokio::io::Error> { let mut dir = tokio::fs::read_dir(voice_path).await?; let mut voices = Vec::new(); while let Some(file) = &dir.next_entry().await? { if file.path().is_dir() { let voice_name = file .path() .file_name() .unwrap() .to_str() .unwrap() .to_string(); voices.push(voice_name) } } Ok(voices) } async fn find_voice(voice_path: &Path, name: &str) -> Result, tokio::io::Error> { let mut dir = tokio::fs::read_dir(voice_path).await?; while let Some(file) = &dir.next_entry().await? { if file.path().is_dir() { let voice_name = file .path() .file_name() .unwrap() .to_str() .unwrap() .to_string(); if voice_name.eq_ignore_ascii_case(name) { return Ok(Some(file.path())); } } } Ok(None) } #[command] #[only_in(guilds)] #[min_args(1)] async fn say(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { let guild = msg.guild(&ctx.cache).unwrap(); let guild_id = guild.id; let data = ctx.data.read().await; let global_data = data.get::().unwrap(); let voice = args.parse::().unwrap(); let voice_path = match find_voice(&global_data.cfg.voice_path, &voice).await? { None => { msg.reply(&ctx.http, format!("No voice found called '{}'", voice)) .await?; return Ok(()); } Some(voice_path) => voice_path, }; args.advance(); let dict = get_voice_dictionary(&voice_path).await?; let mut phrase = Vec::new(); while let Some(word) = &args.current() { let mut add_period = false; let mut add_comma = false; let word = if word.ends_with(',') { add_comma = true; word.replace(',', "") } else if word.ends_with('.') { add_period = true; word.replace('.', "") } else { word.to_string() }; if dict.contains_key(&word) { phrase.push(word.to_string()); } else { msg.reply( &ctx.http, format!("The word '{}' is not in the dictionary", word), ) .await?; return Ok(()); } if add_comma { phrase.push("_comma".to_string()); } if add_period { phrase.push("_period".to_string()); } args.advance(); } let channel_id = guild .voice_states .get(&msg.author.id) .and_then(|voice_state| voice_state.channel_id); let connect_to = match channel_id { Some(channel) => channel, None => { msg.reply(ctx, "You are not in a voice channel").await?; return Ok(()); } }; let manager = songbird::get(ctx) .await .expect("Songbird not initialized") .clone(); let (handler_lock, success_reader) = manager.join(guild_id, connect_to).await; success_reader?; let mut handler = handler_lock.lock().await; for word in phrase { let word_path = dict.get(&word).unwrap(); let audio_src = Compressed::new( input::ffmpeg(word_path.to_str().unwrap()) .await .expect("Bad audio link."), Bitrate::BitsPerSecond(128_000), ) .expect("Bad params on message load"); let _ = audio_src.raw.spawn_loader(); let duration = audio_src.metadata.duration.unwrap(); let voice = handler.play_source(audio_src.into()); voice.set_volume(0.5)?; tokio::time::sleep(duration).await; } handler.leave().await?; Ok(()) } #[command] #[only_in(guilds)] #[min_args(1)] async fn list_words(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let data = ctx.data.read().await; let global_data = data.get::().unwrap(); let voice = args.parse::().unwrap(); match find_voice(&global_data.cfg.voice_path, &voice).await? { None => { msg.reply(&ctx.http, format!("No voice found called '{}'", voice)) .await?; } Some(voice_path) => { let dict = get_voice_dictionary(&voice_path).await?; let mut words: Vec = dict.keys().cloned().collect(); words.sort(); let mut list_msg = MessageBuilder::new(); list_msg.push_line(format!("Here are the words for {}:", voice)); for word in words { list_msg.push("* "); list_msg.push_line(word); } let file_data = list_msg.build(); let file_data = file_data.as_bytes(); let file_data = Cow::from(file_data); msg.channel_id .send_message(&ctx.http, |m| { m.add_file(AttachmentType::Bytes { data: file_data, filename: "words.txt".to_string(), }) }) .await?; } }; Ok(()) } #[command] #[only_in(guilds)] async fn list_voices(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let data = ctx.data.read().await; let global_data = data.get::().unwrap(); let mut voice_message = MessageBuilder::new(); for voice in get_voices(&global_data.cfg.voice_path).await? { voice_message.push("* "); voice_message.push_line(voice); } msg.reply(&ctx.http, voice_message.build()).await?; Ok(()) }