Get voices working again!
This commit is contained in:
parent
c329a2f387
commit
1d5b44128b
117
Cargo.lock
generated
117
Cargo.lock
generated
@ -1002,6 +1002,12 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "extended"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
@ -1098,6 +1104,7 @@ dependencies = [
|
||||
"sha3",
|
||||
"songbird",
|
||||
"structopt",
|
||||
"symphonia",
|
||||
"tera",
|
||||
"tokio",
|
||||
"tonic",
|
||||
@ -3885,8 +3892,71 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"symphonia-bundle-flac",
|
||||
"symphonia-bundle-mp3",
|
||||
"symphonia-codec-adpcm",
|
||||
"symphonia-codec-pcm",
|
||||
"symphonia-codec-vorbis",
|
||||
"symphonia-core",
|
||||
"symphonia-format-mkv",
|
||||
"symphonia-format-ogg",
|
||||
"symphonia-format-riff",
|
||||
"symphonia-metadata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symphonia-bundle-flac"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72e34f34298a7308d4397a6c7fbf5b84c5d491231ce3dd379707ba673ab3bd97"
|
||||
dependencies = [
|
||||
"log",
|
||||
"symphonia-core",
|
||||
"symphonia-metadata",
|
||||
"symphonia-utils-xiph",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symphonia-bundle-mp3"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c01c2aae70f0f1fb096b6f0ff112a930b1fb3626178fba3ae68b09dce71706d4"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"symphonia-core",
|
||||
"symphonia-metadata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symphonia-codec-adpcm"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c94e1feac3327cd616e973d5be69ad36b3945f16b06f19c6773fc3ac0b426a0f"
|
||||
dependencies = [
|
||||
"log",
|
||||
"symphonia-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symphonia-codec-pcm"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f395a67057c2ebc5e84d7bb1be71cce1a7ba99f64e0f0f0e303a03f79116f89b"
|
||||
dependencies = [
|
||||
"log",
|
||||
"symphonia-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symphonia-codec-vorbis"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a98765fb46a0a6732b007f7e2870c2129b6f78d87db7987e6533c8f164a9f30"
|
||||
dependencies = [
|
||||
"log",
|
||||
"symphonia-core",
|
||||
"symphonia-utils-xiph",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3902,6 +3972,43 @@ dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symphonia-format-mkv"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bb43471a100f7882dc9937395bd5ebee8329298e766250b15b3875652fe3d6f"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"symphonia-core",
|
||||
"symphonia-metadata",
|
||||
"symphonia-utils-xiph",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symphonia-format-ogg"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ada3505789516bcf00fc1157c67729eded428b455c27ca370e41f4d785bfa931"
|
||||
dependencies = [
|
||||
"log",
|
||||
"symphonia-core",
|
||||
"symphonia-metadata",
|
||||
"symphonia-utils-xiph",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symphonia-format-riff"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f7be232f962f937f4b7115cbe62c330929345434c834359425e043bfd15f50"
|
||||
dependencies = [
|
||||
"extended",
|
||||
"log",
|
||||
"symphonia-core",
|
||||
"symphonia-metadata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symphonia-metadata"
|
||||
version = "0.5.4"
|
||||
@ -3914,6 +4021,16 @@ dependencies = [
|
||||
"symphonia-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symphonia-utils-xiph"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "484472580fa49991afda5f6550ece662237b00c6f562c7d9638d1b086ed010fe"
|
||||
dependencies = [
|
||||
"symphonia-core",
|
||||
"symphonia-metadata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
|
||||
@ -15,7 +15,6 @@ tera = "1.19.1"
|
||||
ndm = "0.9.10"
|
||||
regex = "1.10.2"
|
||||
magick_rust = "1.0.0"
|
||||
songbird = "0.5.0"
|
||||
json = "0.12.4"
|
||||
axum = "0.8.1"
|
||||
sha3 = "0.10.8"
|
||||
@ -35,3 +34,11 @@ log = "0.4.26"
|
||||
[dependencies.tokio]
|
||||
version = "1.35.1"
|
||||
features = ["macros", "rt-multi-thread"]
|
||||
|
||||
[dependencies.songbird]
|
||||
version = "0.5.0"
|
||||
features = ["builtin-queue"]
|
||||
|
||||
[dependencies.symphonia]
|
||||
version = "0.5"
|
||||
features = ["mp3", "wav"]
|
||||
|
||||
@ -58,14 +58,12 @@ impl BotConfig {
|
||||
#[derive(Debug)]
|
||||
pub struct BotState {
|
||||
pub accepted_nsfw: Option<UserId>,
|
||||
pub speak_lock: Mutex<()>,
|
||||
}
|
||||
|
||||
impl BotState {
|
||||
pub async fn new() -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
accepted_nsfw: None,
|
||||
speak_lock: Mutex::new(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -76,6 +74,7 @@ pub struct GlobalData {
|
||||
pub bot_state: Mutex<BotState>,
|
||||
pub db: Database,
|
||||
pub picox: AlbumManager,
|
||||
pub speak_lock: Mutex<()>,
|
||||
}
|
||||
|
||||
impl GlobalData {
|
||||
@ -100,6 +99,7 @@ impl GlobalData {
|
||||
db,
|
||||
cfg: cfg.clone(),
|
||||
picox: AlbumManager::new(cfg.picox.api_base_url, &cfg.picox.token),
|
||||
speak_lock: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ mod joke;
|
||||
mod little_fren;
|
||||
mod motivate;
|
||||
pub(crate) mod shop;
|
||||
pub(crate) mod voices;
|
||||
|
||||
use crate::config::GlobalData;
|
||||
use crate::error::Error;
|
||||
@ -24,6 +25,7 @@ use poise::serenity_prelude::{GuildId, Http, Message, ReactionType, RoleId};
|
||||
use poise::{find_command, serenity_prelude as serenity, FrameworkOptions};
|
||||
use rand::prelude::IteratorRandom;
|
||||
use rand::rng;
|
||||
use songbird::SerenityInit;
|
||||
|
||||
pub type Context<'a> = poise::Context<'a, Arc<GlobalData>, Error>;
|
||||
|
||||
@ -207,6 +209,9 @@ pub async fn run_bot(global_data: GlobalData) {
|
||||
shop::sell_item(),
|
||||
shop::shop(),
|
||||
shop::use_item(),
|
||||
voices::list_voices(),
|
||||
voices::list_words(),
|
||||
voices::say(),
|
||||
],
|
||||
event_handler: |ctx, event, framework, data| {
|
||||
Box::pin(event_handler(ctx, event, framework, data))
|
||||
@ -234,6 +239,7 @@ pub async fn run_bot(global_data: GlobalData) {
|
||||
|
||||
let mut client = serenity::ClientBuilder::new(token, intents)
|
||||
.framework(framework)
|
||||
.register_songbird()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
@ -1,23 +1,21 @@
|
||||
use crate::{command, group, GlobalData};
|
||||
use serenity::all::CreateAttachment;
|
||||
use serenity::builder::CreateMessage;
|
||||
use serenity::client::Context;
|
||||
use serenity::framework::standard::{Args, CommandResult};
|
||||
use serenity::model::channel::Message;
|
||||
use serenity::model::id::UserId;
|
||||
use serenity::model::prelude::GuildId;
|
||||
use serenity::utils::MessageBuilder;
|
||||
use crate::discord::Context;
|
||||
use crate::error::Error;
|
||||
use magick_rust::bindings::wchar_t;
|
||||
use poise::async_trait;
|
||||
use poise::serenity_prelude::all::CreateAttachment;
|
||||
use poise::serenity_prelude::builder::CreateMessage;
|
||||
use poise::serenity_prelude::model::channel::Message;
|
||||
use poise::serenity_prelude::model::id::UserId;
|
||||
use poise::serenity_prelude::model::prelude::GuildId;
|
||||
use poise::serenity_prelude::utils::MessageBuilder;
|
||||
use songbird::driver::Bitrate;
|
||||
use songbird::input;
|
||||
use songbird::input::cached::Compressed;
|
||||
use songbird::{input, Event, EventContext, EventHandler, Songbird, TrackEvent};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[group]
|
||||
#[commands(say, list_words, list_voices)]
|
||||
pub struct Voices;
|
||||
use std::sync::Arc;
|
||||
|
||||
async fn get_voice_dictionary(path: &Path) -> Result<HashMap<String, PathBuf>, tokio::io::Error> {
|
||||
let mut dir = tokio::fs::read_dir(path).await?;
|
||||
@ -85,7 +83,7 @@ pub enum VoiceError {
|
||||
VoiceNotFound(String),
|
||||
WordNotFound(String),
|
||||
NotInVoiceChannel,
|
||||
Serenity(serenity::Error),
|
||||
Serenity(poise::serenity_prelude::Error),
|
||||
}
|
||||
|
||||
impl Display for VoiceError {
|
||||
@ -99,30 +97,44 @@ impl Display for VoiceError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serenity::Error> for VoiceError {
|
||||
fn from(value: serenity::Error) -> Self {
|
||||
impl From<poise::serenity_prelude::Error> for VoiceError {
|
||||
fn from(value: poise::serenity_prelude::Error) -> Self {
|
||||
Self::Serenity(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for VoiceError {}
|
||||
|
||||
struct LeaveHandler {
|
||||
guild: GuildId,
|
||||
manager: Arc<Songbird>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl EventHandler for LeaveHandler {
|
||||
async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
|
||||
if let EventContext::Track(_track) = ctx {
|
||||
let has_handler = self.manager.get(self.guild).is_some();
|
||||
|
||||
if has_handler {
|
||||
let _ = self.manager.remove(self.guild).await.is_ok();
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn speak(
|
||||
ctx: &Context,
|
||||
ctx: Context<'_>,
|
||||
guild_id: GuildId,
|
||||
user_id: UserId,
|
||||
voice: &str,
|
||||
phrase: &str,
|
||||
) -> Result<(), VoiceError> {
|
||||
let data = ctx.data.read().await;
|
||||
let global_data = data.get::<GlobalData>().unwrap();
|
||||
let _ = ctx.data().speak_lock.lock();
|
||||
|
||||
let _ = global_data.bot_state.speak_lock.lock().await;
|
||||
|
||||
let voice_path = match find_voice(&global_data.cfg.voice_path, voice)
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
let voice_path = match find_voice(&ctx.data().cfg.voice_path, voice).await.unwrap() {
|
||||
None => return Err(VoiceError::VoiceNotFound(voice.to_string())),
|
||||
Some(voice_path) => voice_path,
|
||||
};
|
||||
@ -160,7 +172,7 @@ pub async fn speak(
|
||||
}
|
||||
|
||||
let channel_id = {
|
||||
let guild = ctx.cache.guild(guild_id).unwrap();
|
||||
let guild = ctx.cache().guild(guild_id).unwrap();
|
||||
|
||||
let channel_id = guild
|
||||
.voice_states
|
||||
@ -177,7 +189,7 @@ pub async fn speak(
|
||||
}
|
||||
};
|
||||
|
||||
let manager = songbird::get(ctx)
|
||||
let manager = songbird::get(ctx.serenity_context())
|
||||
.await
|
||||
.expect("Songbird not initialized")
|
||||
.clone();
|
||||
@ -186,8 +198,8 @@ pub async fn speak(
|
||||
|
||||
if let Ok(handler_lock) = handler_lock {
|
||||
let mut handler = handler_lock.lock().await;
|
||||
for word in sentence {
|
||||
let word_path = dict.get(&word).cloned().unwrap();
|
||||
for (ndx, word) in sentence.iter().enumerate() {
|
||||
let word_path = dict.get(word).cloned().unwrap();
|
||||
|
||||
let src = input::File::new(word_path);
|
||||
|
||||
@ -197,34 +209,44 @@ pub async fn speak(
|
||||
|
||||
let _ = audio_src.raw.spawn_loader();
|
||||
|
||||
let voice = handler.play_input(audio_src.into());
|
||||
voice.set_volume(0.5).unwrap();
|
||||
let handle = handler.enqueue_input(audio_src.into()).await;
|
||||
handle.set_volume(0.50).unwrap();
|
||||
|
||||
//tokio::time::sleep(duration).await;
|
||||
if ndx == sentence.len() - 1 {
|
||||
// add task to disconnect bot
|
||||
|
||||
handle
|
||||
.add_event(
|
||||
Event::Track(TrackEvent::End),
|
||||
LeaveHandler {
|
||||
guild: ctx.guild_id().unwrap(),
|
||||
manager: manager.clone(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
handler.leave().await.unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[only_in(guilds)]
|
||||
#[min_args(1)]
|
||||
async fn say(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let guild_id = msg.guild_id.unwrap();
|
||||
#[poise::command(prefix_command, category = "Voice", guild_only)]
|
||||
pub async fn say(
|
||||
ctx: Context<'_>,
|
||||
#[description = "Voice to use"] voice: String,
|
||||
#[description = "message to say"]
|
||||
#[rest]
|
||||
phrase: String,
|
||||
) -> Result<(), Error> {
|
||||
let guild_id = ctx.guild_id().unwrap();
|
||||
|
||||
let voice = args.parse::<String>()?;
|
||||
args.advance();
|
||||
let phrase = args.rest();
|
||||
|
||||
if let Err(err) = speak(ctx, guild_id, msg.author.id, &voice, phrase).await {
|
||||
if let Err(err) = speak(ctx, guild_id, ctx.author().id, &voice, &phrase).await {
|
||||
match err {
|
||||
VoiceError::VoiceNotFound(_)
|
||||
| VoiceError::WordNotFound(_)
|
||||
| VoiceError::NotInVoiceChannel => {
|
||||
msg.reply(&ctx.http, format!("Error: {}", err)).await?;
|
||||
ctx.reply(format!("Error: {}", err)).await?;
|
||||
}
|
||||
_ => return Err(err.into()),
|
||||
}
|
||||
@ -233,18 +255,14 @@ async fn say(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
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? {
|
||||
#[poise::command(prefix_command, category = "Voice", guild_only)]
|
||||
pub async fn list_words(
|
||||
ctx: Context<'_>,
|
||||
#[description = "Get all words for this voice"] voice: String,
|
||||
) -> Result<(), Error> {
|
||||
match find_voice(&ctx.data().cfg.voice_path, &voice).await? {
|
||||
None => {
|
||||
msg.reply(&ctx.http, format!("No voice found called '{}'", voice))
|
||||
ctx.reply(format!("No voice found called '{}'", voice))
|
||||
.await?;
|
||||
}
|
||||
Some(voice_path) => {
|
||||
@ -265,9 +283,9 @@ async fn list_words(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||
let file_data = file_data.as_bytes();
|
||||
let file_data = Cow::from(file_data);
|
||||
|
||||
msg.channel_id
|
||||
ctx.channel_id()
|
||||
.send_message(
|
||||
&ctx.http,
|
||||
&ctx,
|
||||
CreateMessage::new()
|
||||
.add_file(CreateAttachment::bytes(file_data, "words.txt".to_string())),
|
||||
)
|
||||
@ -278,20 +296,16 @@ async fn list_words(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||
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();
|
||||
|
||||
#[poise::command(prefix_command, category = "Voice", guild_only)]
|
||||
pub async fn list_voices(ctx: Context<'_>) -> Result<(), Error> {
|
||||
let mut voice_message = MessageBuilder::new();
|
||||
|
||||
for voice in get_voices(&global_data.cfg.voice_path).await? {
|
||||
for voice in get_voices(&ctx.data().cfg.voice_path).await? {
|
||||
voice_message.push("* ");
|
||||
voice_message.push_line(voice);
|
||||
}
|
||||
|
||||
msg.reply(&ctx.http, voice_message.build()).await?;
|
||||
ctx.reply(voice_message.build()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use crate::discord::voices::VoiceError;
|
||||
use crate::user;
|
||||
use magick_rust::MagickError;
|
||||
use serde::ser::StdError;
|
||||
@ -19,6 +20,7 @@ pub enum Error {
|
||||
MagickError(magick_rust::MagickError),
|
||||
CommandError(String),
|
||||
IoError(std::io::Error),
|
||||
VoiceError(VoiceError),
|
||||
}
|
||||
|
||||
impl StdError for Error {}
|
||||
@ -71,6 +73,12 @@ impl From<std::io::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VoiceError> for Error {
|
||||
fn from(value: VoiceError) -> Self {
|
||||
Self::VoiceError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
@ -86,6 +94,7 @@ impl Display for Error {
|
||||
Error::MagickError(err) => write!(f, "ImageMagick error: {}", err),
|
||||
Error::CommandError(err_msg) => write!(f, "{}", err_msg),
|
||||
Error::IoError(err) => write!(f, "IO Error: {}", err),
|
||||
Error::VoiceError(err) => write!(f, "Voice error: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user