1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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<OutputMode>,
) -> 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<poise::Command<Data, Error>> {
vec![tts()]
}
|