From cd8ed8bc5793985df2c1692072a932ceee517b95 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 8 Jul 2026 02:13:35 +0200 Subject: OPEN THE RUST GATES Signed-off-by: salmonik --- src/commands/tts.rs | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/commands/tts.rs (limited to 'src/commands/tts.rs') diff --git a/src/commands/tts.rs b/src/commands/tts.rs new file mode 100644 index 0000000..ee13596 --- /dev/null +++ b/src/commands/tts.rs @@ -0,0 +1,106 @@ +use poise::serenity_prelude as serenity; + +use crate::Data; +use crate::error::{Error, Result}; +use crate::lang; + +type Context<'a> = poise::Context<'a, Data, Error>; + +#[derive(poise::ChoiceParameter)] +pub enum OutputMode { + #[name = "File"] + File, + #[name = "Voice Channel"] + Vc, +} + +/// Generate Google TTS (Polish) and output to file or voice channel +#[poise::command(slash_command)] +pub async fn tts( + ctx: Context<'_>, + #[description = "Text to synthesize"] text: String, + #[description = "Output destination (file or vc)"] output: Option, +) -> Result<()> { + ctx.defer().await?; + let lang = "pl"; + let tts_url = format!( + "https://translate.google.com/translate_tts?ie=UTF-8&q={}&tl={}&client=tw-ob", + urlencoding(&text), + lang + ); + + let output = output.unwrap_or(OutputMode::Vc); + + let audio_data = ctx + .data() + .http_client + .get(&tts_url) + .send() + .await? + .bytes() + .await?; + + match output { + OutputMode::File => { + tokio::fs::create_dir_all(crate::config::TEMP_PATH) + .await + .ok(); + let path = format!( + "{}/tts_{}.mp3", + crate::config::TEMP_PATH, + uuid::Uuid::new_v4() + ); + tokio::fs::write(&path, &audio_data).await?; + ctx.send( + poise::CreateReply::default() + .content(lang::L_TTS_SAVED) + .attachment(serenity::CreateAttachment::path(&path).await.unwrap()), + ) + .await?; + } + OutputMode::Vc => { + let guild_id = ctx.guild_id().ok_or_else(|| lang::err_not_in_guild())?; + let channel_id = ctx + .serenity_context() + .cache + .guild(guild_id) + .and_then(|g| { + g.voice_states + .get(&ctx.author().id) + .and_then(|vs| vs.channel_id) + }) + .ok_or_else(|| lang::err_not_in_voice())?; + + let call = ctx + .data() + .voice_manager + .connect(guild_id, channel_id) + .await?; + let input = crate::voice::decode_audio(&audio_data)?; + let mut call_lock = call.lock().await; + if call_lock.current_channel().is_some() { + call_lock.play_input(input); + } + ctx.say(lang::L_TTS_PLAYING).await?; + } + } + Ok(()) +} + +pub fn urlencoding(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + for &byte in s.as_bytes() { + match byte { + b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_' | b'-' | b'.' | b'~' => { + out.push(byte as char) + } + b' ' => out.push('+'), + _ => out.push_str(&format!("%{:02X}", byte)), + } + } + out +} + +pub fn commands() -> Vec> { + vec![tts()] +} -- cgit v1.2.3