diff options
| author | salmonik <raisersoft@proton.me> | 2026-07-08 02:13:35 +0200 |
|---|---|---|
| committer | salmonik <raisersoft@proton.me> | 2026-07-08 02:13:35 +0200 |
| commit | cd8ed8bc5793985df2c1692072a932ceee517b95 (patch) | |
| tree | 42d18a4140259b5c68b4dd05b7fcb398f78b8d7f /src/commands/tts.rs | |
| parent | Merge pull request 'Voice related fixes, alongside simplifying assistant' (#5... (diff) | |
| download | senator-sperma-cd8ed8bc5793985df2c1692072a932ceee517b95.tar.gz senator-sperma-cd8ed8bc5793985df2c1692072a932ceee517b95.zip | |
OPEN THE RUST GATES
Signed-off-by: salmonik <raisersoft@proton.me>
Diffstat (limited to '')
| -rw-r--r-- | src/commands/tts.rs | 106 |
1 files changed, 106 insertions, 0 deletions
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 @@ | |||
| 1 | use poise::serenity_prelude as serenity; | ||
| 2 | |||
| 3 | use crate::Data; | ||
| 4 | use crate::error::{Error, Result}; | ||
| 5 | use crate::lang; | ||
| 6 | |||
| 7 | type Context<'a> = poise::Context<'a, Data, Error>; | ||
| 8 | |||
| 9 | #[derive(poise::ChoiceParameter)] | ||
| 10 | pub enum OutputMode { | ||
| 11 | #[name = "File"] | ||
| 12 | File, | ||
| 13 | #[name = "Voice Channel"] | ||
| 14 | Vc, | ||
| 15 | } | ||
| 16 | |||
| 17 | /// Generate Google TTS (Polish) and output to file or voice channel | ||
| 18 | #[poise::command(slash_command)] | ||
| 19 | pub async fn tts( | ||
| 20 | ctx: Context<'_>, | ||
| 21 | #[description = "Text to synthesize"] text: String, | ||
| 22 | #[description = "Output destination (file or vc)"] output: Option<OutputMode>, | ||
| 23 | ) -> Result<()> { | ||
| 24 | ctx.defer().await?; | ||
| 25 | let lang = "pl"; | ||
| 26 | let tts_url = format!( | ||
| 27 | "https://translate.google.com/translate_tts?ie=UTF-8&q={}&tl={}&client=tw-ob", | ||
| 28 | urlencoding(&text), | ||
| 29 | lang | ||
| 30 | ); | ||
| 31 | |||
| 32 | let output = output.unwrap_or(OutputMode::Vc); | ||
| 33 | |||
| 34 | let audio_data = ctx | ||
| 35 | .data() | ||
| 36 | .http_client | ||
| 37 | .get(&tts_url) | ||
| 38 | .send() | ||
| 39 | .await? | ||
| 40 | .bytes() | ||
| 41 | .await?; | ||
| 42 | |||
| 43 | match output { | ||
| 44 | OutputMode::File => { | ||
| 45 | tokio::fs::create_dir_all(crate::config::TEMP_PATH) | ||
| 46 | .await | ||
| 47 | .ok(); | ||
| 48 | let path = format!( | ||
| 49 | "{}/tts_{}.mp3", | ||
| 50 | crate::config::TEMP_PATH, | ||
| 51 | uuid::Uuid::new_v4() | ||
| 52 | ); | ||
| 53 | tokio::fs::write(&path, &audio_data).await?; | ||
| 54 | ctx.send( | ||
| 55 | poise::CreateReply::default() | ||
| 56 | .content(lang::L_TTS_SAVED) | ||
| 57 | .attachment(serenity::CreateAttachment::path(&path).await.unwrap()), | ||
| 58 | ) | ||
| 59 | .await?; | ||
| 60 | } | ||
| 61 | OutputMode::Vc => { | ||
| 62 | let guild_id = ctx.guild_id().ok_or_else(|| lang::err_not_in_guild())?; | ||
| 63 | let channel_id = ctx | ||
| 64 | .serenity_context() | ||
| 65 | .cache | ||
| 66 | .guild(guild_id) | ||
| 67 | .and_then(|g| { | ||
| 68 | g.voice_states | ||
| 69 | .get(&ctx.author().id) | ||
| 70 | .and_then(|vs| vs.channel_id) | ||
| 71 | }) | ||
| 72 | .ok_or_else(|| lang::err_not_in_voice())?; | ||
| 73 | |||
| 74 | let call = ctx | ||
| 75 | .data() | ||
| 76 | .voice_manager | ||
| 77 | .connect(guild_id, channel_id) | ||
| 78 | .await?; | ||
| 79 | let input = crate::voice::decode_audio(&audio_data)?; | ||
| 80 | let mut call_lock = call.lock().await; | ||
| 81 | if call_lock.current_channel().is_some() { | ||
| 82 | call_lock.play_input(input); | ||
| 83 | } | ||
| 84 | ctx.say(lang::L_TTS_PLAYING).await?; | ||
| 85 | } | ||
| 86 | } | ||
| 87 | Ok(()) | ||
| 88 | } | ||
| 89 | |||
| 90 | pub fn urlencoding(s: &str) -> String { | ||
| 91 | let mut out = String::with_capacity(s.len()); | ||
| 92 | for &byte in s.as_bytes() { | ||
| 93 | match byte { | ||
| 94 | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_' | b'-' | b'.' | b'~' => { | ||
| 95 | out.push(byte as char) | ||
| 96 | } | ||
| 97 | b' ' => out.push('+'), | ||
| 98 | _ => out.push_str(&format!("%{:02X}", byte)), | ||
| 99 | } | ||
| 100 | } | ||
| 101 | out | ||
| 102 | } | ||
| 103 | |||
| 104 | pub fn commands() -> Vec<poise::Command<Data, Error>> { | ||
| 105 | vec![tts()] | ||
| 106 | } | ||
