aboutsummaryrefslogtreecommitdiffstats
path: root/src/commands/tts.rs
diff options
context:
space:
mode:
authorsalmonik <raisersoft@proton.me>2026-07-08 02:13:35 +0200
committersalmonik <raisersoft@proton.me>2026-07-08 02:13:35 +0200
commitcd8ed8bc5793985df2c1692072a932ceee517b95 (patch)
tree42d18a4140259b5c68b4dd05b7fcb398f78b8d7f /src/commands/tts.rs
parentMerge pull request 'Voice related fixes, alongside simplifying assistant' (#5... (diff)
downloadsenator-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.rs106
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 @@
1use poise::serenity_prelude as serenity;
2
3use crate::Data;
4use crate::error::{Error, Result};
5use crate::lang;
6
7type Context<'a> = poise::Context<'a, Data, Error>;
8
9#[derive(poise::ChoiceParameter)]
10pub 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)]
19pub 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
90pub 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
104pub fn commands() -> Vec<poise::Command<Data, Error>> {
105 vec![tts()]
106}