FrenBot/src/discord/voices.rs
Joey Hines 335751a978
Added voice command
+ Heavily tried to the HL1 voice format
+ Clippy + fmt
2023-01-07 14:57:58 -07:00

251 lines
6.7 KiB
Rust

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<HashMap<String, PathBuf>, 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<Vec<String>, 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<Option<PathBuf>, 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::<GlobalData>().unwrap();
let voice = args.parse::<String>().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::<GlobalData>().unwrap();
let voice = args.parse::<String>().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<String> = 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::<GlobalData>().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(())
}