Added voice command
+ Heavily tried to the HL1 voice format + Clippy + fmt
This commit is contained in:
@@ -47,6 +47,7 @@ pub struct BotConfig {
|
||||
pub bot_token: String,
|
||||
pub imgur_client_id: String,
|
||||
pub story_path: PathBuf,
|
||||
pub voice_path: PathBuf,
|
||||
|
||||
#[serde(default)]
|
||||
pub albums: Vec<AlbumConfig>,
|
||||
|
||||
+2
-1
@@ -5,8 +5,9 @@ pub mod color;
|
||||
pub mod emoji_race;
|
||||
pub mod fren_coin;
|
||||
pub mod joke;
|
||||
pub(crate) mod motivate;
|
||||
pub mod motivate;
|
||||
pub mod story;
|
||||
pub mod voices;
|
||||
|
||||
use crate::discord::fren_coin::give_coin;
|
||||
use crate::discord::joke::random;
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
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(())
|
||||
}
|
||||
@@ -12,6 +12,7 @@ use magick_rust::magick_wand_genesis;
|
||||
use serenity::framework::standard::macros::{command, group, help, hook};
|
||||
use serenity::framework::standard::StandardFramework;
|
||||
use serenity::prelude::*;
|
||||
use songbird::SerenityInit;
|
||||
use std::sync::Once;
|
||||
use structopt::StructOpt;
|
||||
|
||||
@@ -54,6 +55,7 @@ async fn main() {
|
||||
.group(&discord::fren_coin::FRENCOIN_GROUP)
|
||||
.group(&discord::emoji_race::EMOJIRACE_GROUP)
|
||||
.group(&discord::motivate::MOTIVATE_GROUP)
|
||||
.group(&discord::voices::VOICES_GROUP)
|
||||
.unrecognised_command(unrecognised_command_hook)
|
||||
.bucket("bad_apple", |b| b.delay(60 * 10))
|
||||
.await
|
||||
@@ -66,6 +68,7 @@ async fn main() {
|
||||
let intents = GatewayIntents::all();
|
||||
let mut client = Client::builder(&global_data.cfg.bot_token, intents)
|
||||
.framework(framework)
|
||||
.register_songbird()
|
||||
.type_map_insert::<GlobalData>(global_data)
|
||||
.type_map_insert::<Channel<String>>(story_channel)
|
||||
.type_map_insert::<Channel<RaceMessage>>(race_channel)
|
||||
|
||||
Reference in New Issue
Block a user