mirror of
https://github.com/mii443/ncb-tts-r2.git
synced 2025-08-22 16:15:29 +00:00
clippy
This commit is contained in:
@ -1,56 +1,89 @@
|
||||
use serenity::{prelude::Context, model::prelude::{application_command::ApplicationCommandInteraction, InteractionApplicationCommandCallbackDataFlags}};
|
||||
use serenity::{
|
||||
model::prelude::{
|
||||
application_command::ApplicationCommandInteraction,
|
||||
InteractionApplicationCommandCallbackDataFlags,
|
||||
},
|
||||
prelude::Context,
|
||||
};
|
||||
|
||||
use crate::{tts::{voicevox::voicevox::VOICEVOX, tts_type::TTSType}, data::DatabaseClientData};
|
||||
use crate::{
|
||||
data::DatabaseClientData,
|
||||
tts::{tts_type::TTSType, voicevox::voicevox::VOICEVOX},
|
||||
};
|
||||
|
||||
pub async fn config_command(ctx: &Context, command: &ApplicationCommandInteraction) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub async fn config_command(
|
||||
ctx: &Context,
|
||||
command: &ApplicationCommandInteraction,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let data_read = ctx.data.read().await;
|
||||
|
||||
let config = {
|
||||
let database = data_read.get::<DatabaseClientData>().expect("Cannot get DatabaseClientData").clone();
|
||||
let database = data_read
|
||||
.get::<DatabaseClientData>()
|
||||
.expect("Cannot get DatabaseClientData")
|
||||
.clone();
|
||||
let mut database = database.lock().await;
|
||||
database.get_user_config_or_default(command.user.id.0).await.unwrap().unwrap()
|
||||
database
|
||||
.get_user_config_or_default(command.user.id.0)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let voicevox_speaker = config.voicevox_speaker.unwrap_or(1);
|
||||
let tts_type = config.tts_type.unwrap_or(TTSType::GCP);
|
||||
|
||||
command.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("読み上げ設定")
|
||||
.components(|c| {
|
||||
c.create_action_row(|a| {
|
||||
a.create_select_menu(|m| {
|
||||
m.custom_id("TTS_CONFIG_ENGINE")
|
||||
.options(|o| {
|
||||
o.create_option(|co| {
|
||||
co.label("Google TTS")
|
||||
.value("TTS_CONFIG_ENGINE_SELECTED_GOOGLE")
|
||||
.default_selection(tts_type == TTSType::GCP)
|
||||
}).create_option(|co| {
|
||||
co.label("VOICEVOX")
|
||||
.value("TTS_CONFIG_ENGINE_SELECTED_VOICEVOX")
|
||||
.default_selection(tts_type == TTSType::VOICEVOX)
|
||||
})
|
||||
}).placeholder("読み上げAPIを選択")
|
||||
})
|
||||
}).create_action_row(|a| {
|
||||
a.create_select_menu(|m| {
|
||||
m.custom_id("TTS_CONFIG_VOICEVOX_SPEAKER")
|
||||
.options(|o| {
|
||||
let mut o = o;
|
||||
for (name, value) in VOICEVOX::get_speakers() {
|
||||
o = o.create_option(|co| {
|
||||
co.label(name)
|
||||
.value(format!("TTS_CONFIG_VOICEVOX_SPEAKER_SELECTED_{}", value))
|
||||
.default_selection(value == voicevox_speaker)
|
||||
command
|
||||
.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("読み上げ設定")
|
||||
.components(|c| {
|
||||
c.create_action_row(|a| {
|
||||
a.create_select_menu(|m| {
|
||||
m.custom_id("TTS_CONFIG_ENGINE")
|
||||
.options(|o| {
|
||||
o.create_option(|co| {
|
||||
co.label("Google TTS")
|
||||
.value("TTS_CONFIG_ENGINE_SELECTED_GOOGLE")
|
||||
.default_selection(tts_type == TTSType::GCP)
|
||||
})
|
||||
}
|
||||
o
|
||||
}).placeholder("VOICEVOX Speakerを指定")
|
||||
.create_option(
|
||||
|co| {
|
||||
co.label("VOICEVOX")
|
||||
.value("TTS_CONFIG_ENGINE_SELECTED_VOICEVOX")
|
||||
.default_selection(
|
||||
tts_type == TTSType::VOICEVOX,
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
.placeholder("読み上げAPIを選択")
|
||||
})
|
||||
})
|
||||
.create_action_row(|a| {
|
||||
a.create_select_menu(|m| {
|
||||
m.custom_id("TTS_CONFIG_VOICEVOX_SPEAKER")
|
||||
.options(|o| {
|
||||
let mut o = o;
|
||||
for (name, value) in VOICEVOX::get_speakers() {
|
||||
o = o.create_option(|co| {
|
||||
co.label(name)
|
||||
.value(format!(
|
||||
"TTS_CONFIG_VOICEVOX_SPEAKER_SELECTED_{}",
|
||||
value
|
||||
))
|
||||
.default_selection(value == voicevox_speaker)
|
||||
})
|
||||
}
|
||||
o
|
||||
})
|
||||
.placeholder("VOICEVOX Speakerを指定")
|
||||
})
|
||||
})
|
||||
})
|
||||
}).flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
})
|
||||
})
|
||||
}).await?;
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
pub mod config;
|
||||
pub mod setup;
|
||||
pub mod stop;
|
||||
pub mod config;
|
@ -1,24 +1,39 @@
|
||||
use serenity::{prelude::Context, model::prelude::{application_command::ApplicationCommandInteraction, InteractionApplicationCommandCallbackDataFlags, UserId}};
|
||||
use serenity::{
|
||||
model::prelude::{
|
||||
application_command::ApplicationCommandInteraction,
|
||||
InteractionApplicationCommandCallbackDataFlags, UserId,
|
||||
},
|
||||
prelude::Context,
|
||||
};
|
||||
|
||||
use crate::{data::TTSData, tts::instance::TTSInstance};
|
||||
|
||||
pub async fn setup_command(ctx: &Context, command: &ApplicationCommandInteraction) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub async fn setup_command(
|
||||
ctx: &Context,
|
||||
command: &ApplicationCommandInteraction,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let None = command.guild_id {
|
||||
command.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("このコマンドはサーバーでのみ使用可能です.").flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
command
|
||||
.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("このコマンドはサーバーでのみ使用可能です.")
|
||||
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
})
|
||||
})
|
||||
}).await?;
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let guild = command.guild_id.unwrap().to_guild_cached(&ctx.cache).await;
|
||||
if let None = guild {
|
||||
command.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("ギルドキャッシュを取得できませんでした.").flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
command
|
||||
.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("ギルドキャッシュを取得できませんでした.")
|
||||
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
})
|
||||
})
|
||||
}).await?;
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
let guild = guild.unwrap();
|
||||
@ -29,40 +44,55 @@ pub async fn setup_command(ctx: &Context, command: &ApplicationCommandInteractio
|
||||
.and_then(|state| state.channel_id);
|
||||
|
||||
if let None = channel_id {
|
||||
command.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("ボイスチャンネルに参加してから実行してください.").flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
command
|
||||
.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("ボイスチャンネルに参加してから実行してください.")
|
||||
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
})
|
||||
})
|
||||
}).await?;
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let channel_id = channel_id.unwrap();
|
||||
|
||||
let manager = songbird::get(ctx).await.expect("Cannot get songbird client.").clone();
|
||||
let manager = songbird::get(ctx)
|
||||
.await
|
||||
.expect("Cannot get songbird client.")
|
||||
.clone();
|
||||
|
||||
let storage_lock = {
|
||||
let data_read = ctx.data.read().await;
|
||||
data_read.get::<TTSData>().expect("Cannot get TTSStorage").clone()
|
||||
data_read
|
||||
.get::<TTSData>()
|
||||
.expect("Cannot get TTSStorage")
|
||||
.clone()
|
||||
};
|
||||
|
||||
{
|
||||
let mut storage = storage_lock.write().await;
|
||||
if storage.contains_key(&guild.id) {
|
||||
command.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("すでにセットアップしています.").flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
command
|
||||
.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("すでにセットアップしています.")
|
||||
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
})
|
||||
})
|
||||
}).await?;
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
storage.insert(guild.id, TTSInstance {
|
||||
before_message: None,
|
||||
guild: guild.id,
|
||||
text_channel: command.channel_id,
|
||||
voice_channel: channel_id
|
||||
});
|
||||
storage.insert(
|
||||
guild.id,
|
||||
TTSInstance {
|
||||
before_message: None,
|
||||
guild: guild.id,
|
||||
text_channel: command.channel_id,
|
||||
voice_channel: channel_id,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let _handler = manager.join(guild.id.0, channel_id.0).await;
|
||||
|
@ -1,24 +1,39 @@
|
||||
use serenity::{prelude::Context, model::prelude::{application_command::ApplicationCommandInteraction, InteractionApplicationCommandCallbackDataFlags, UserId}};
|
||||
use serenity::{
|
||||
model::prelude::{
|
||||
application_command::ApplicationCommandInteraction,
|
||||
InteractionApplicationCommandCallbackDataFlags, UserId,
|
||||
},
|
||||
prelude::Context,
|
||||
};
|
||||
|
||||
use crate::data::TTSData;
|
||||
|
||||
pub async fn stop_command(ctx: &Context, command: &ApplicationCommandInteraction) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub async fn stop_command(
|
||||
ctx: &Context,
|
||||
command: &ApplicationCommandInteraction,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let None = command.guild_id {
|
||||
command.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("このコマンドはサーバーでのみ使用可能です.").flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
command
|
||||
.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("このコマンドはサーバーでのみ使用可能です.")
|
||||
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
})
|
||||
})
|
||||
}).await?;
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let guild = command.guild_id.unwrap().to_guild_cached(&ctx.cache).await;
|
||||
if let None = guild {
|
||||
command.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("ギルドキャッシュを取得できませんでした.").flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
command
|
||||
.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("ギルドキャッシュを取得できませんでした.")
|
||||
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
})
|
||||
})
|
||||
}).await?;
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
let guild = guild.unwrap();
|
||||
@ -29,29 +44,41 @@ pub async fn stop_command(ctx: &Context, command: &ApplicationCommandInteraction
|
||||
.and_then(|state| state.channel_id);
|
||||
|
||||
if let None = channel_id {
|
||||
command.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("ボイスチャンネルに参加してから実行してください.").flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
command
|
||||
.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("ボイスチャンネルに参加してから実行してください.")
|
||||
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
})
|
||||
})
|
||||
}).await?;
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let manager = songbird::get(ctx).await.expect("Cannot get songbird client.").clone();
|
||||
let manager = songbird::get(ctx)
|
||||
.await
|
||||
.expect("Cannot get songbird client.")
|
||||
.clone();
|
||||
|
||||
let storage_lock = {
|
||||
let data_read = ctx.data.read().await;
|
||||
data_read.get::<TTSData>().expect("Cannot get TTSStorage").clone()
|
||||
data_read
|
||||
.get::<TTSData>()
|
||||
.expect("Cannot get TTSStorage")
|
||||
.clone()
|
||||
};
|
||||
|
||||
{
|
||||
let mut storage = storage_lock.write().await;
|
||||
if !storage.contains_key(&guild.id) {
|
||||
command.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("すでに停止しています").flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
command
|
||||
.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("すでに停止しています")
|
||||
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
})
|
||||
})
|
||||
}).await?;
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@ -60,11 +87,11 @@ pub async fn stop_command(ctx: &Context, command: &ApplicationCommandInteraction
|
||||
|
||||
let _handler = manager.remove(guild.id.0).await;
|
||||
|
||||
command.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("停止しました")
|
||||
command
|
||||
.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| d.content("停止しました"))
|
||||
})
|
||||
}).await?;
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -6,5 +6,5 @@ pub struct Config {
|
||||
pub token: String,
|
||||
pub application_id: u64,
|
||||
pub redis_url: String,
|
||||
pub voicevox_key: String
|
||||
pub voicevox_key: String,
|
||||
}
|
13
src/data.rs
13
src/data.rs
@ -1,8 +1,15 @@
|
||||
use crate::{tts::{gcp_tts::gcp_tts::TTS, voicevox::voicevox::VOICEVOX}, database::database::Database};
|
||||
use serenity::{prelude::{TypeMapKey, RwLock}, model::id::GuildId, futures::lock::Mutex};
|
||||
use crate::{
|
||||
database::database::Database,
|
||||
tts::{gcp_tts::gcp_tts::TTS, voicevox::voicevox::VOICEVOX},
|
||||
};
|
||||
use serenity::{
|
||||
futures::lock::Mutex,
|
||||
model::id::GuildId,
|
||||
prelude::{RwLock, TypeMapKey},
|
||||
};
|
||||
|
||||
use crate::tts::instance::TTSInstance;
|
||||
use std::{sync::Arc, collections::HashMap};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
/// TTSInstance data
|
||||
pub struct TTSData;
|
||||
|
@ -1,10 +1,12 @@
|
||||
use crate::tts::{gcp_tts::structs::voice_selection_params::VoiceSelectionParams, tts_type::TTSType};
|
||||
use crate::tts::{
|
||||
gcp_tts::structs::voice_selection_params::VoiceSelectionParams, tts_type::TTSType,
|
||||
};
|
||||
|
||||
use super::user_config::UserConfig;
|
||||
use redis::Commands;
|
||||
|
||||
pub struct Database {
|
||||
pub client: redis::Client
|
||||
pub client: redis::Client,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
@ -12,22 +14,35 @@ impl Database {
|
||||
Self { client }
|
||||
}
|
||||
|
||||
pub async fn get_user_config(&mut self, user_id: u64) -> redis::RedisResult<Option<UserConfig>> {
|
||||
pub async fn get_user_config(
|
||||
&mut self,
|
||||
user_id: u64,
|
||||
) -> redis::RedisResult<Option<UserConfig>> {
|
||||
if let Ok(connection) = self.client.get_connection() {
|
||||
let config: String = connection.get(format!("discord_user:{}", user_id)).unwrap_or_default();
|
||||
let config: String = connection
|
||||
.get(format!("discord_user:{}", user_id))
|
||||
.unwrap_or_default();
|
||||
|
||||
match serde_json::from_str(&config) {
|
||||
Ok(config) => Ok(Some(config)),
|
||||
Err(_) => Ok(None)
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_user_config(&mut self, user_id: u64, config: UserConfig) -> redis::RedisResult<()> {
|
||||
pub async fn set_user_config(
|
||||
&mut self,
|
||||
user_id: u64,
|
||||
config: UserConfig,
|
||||
) -> redis::RedisResult<()> {
|
||||
let config = serde_json::to_string(&config).unwrap();
|
||||
self.client.get_connection().unwrap().set::<String, String, ()>(format!("discord_user:{}", user_id), config).unwrap();
|
||||
self.client
|
||||
.get_connection()
|
||||
.unwrap()
|
||||
.set::<String, String, ()>(format!("discord_user:{}", user_id), config)
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -35,7 +50,7 @@ impl Database {
|
||||
let voice_selection = VoiceSelectionParams {
|
||||
languageCode: String::from("ja-JP"),
|
||||
name: String::from("ja-JP-Wavenet-B"),
|
||||
ssmlGender: String::from("neutral")
|
||||
ssmlGender: String::from("neutral"),
|
||||
};
|
||||
|
||||
let voice_type = TTSType::GCP;
|
||||
@ -43,15 +58,21 @@ impl Database {
|
||||
let config = UserConfig {
|
||||
tts_type: Some(voice_type),
|
||||
gcp_tts_voice: Some(voice_selection),
|
||||
voicevox_speaker: Some(1)
|
||||
voicevox_speaker: Some(1),
|
||||
};
|
||||
|
||||
self.client.get_connection().unwrap().set(format!("discord_user:{}", user_id), serde_json::to_string(&config).unwrap())?;
|
||||
self.client.get_connection().unwrap().set(
|
||||
format!("discord_user:{}", user_id),
|
||||
serde_json::to_string(&config).unwrap(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_user_config_or_default(&mut self, user_id: u64) -> redis::RedisResult<Option<UserConfig>> {
|
||||
pub async fn get_user_config_or_default(
|
||||
&mut self,
|
||||
user_id: u64,
|
||||
) -> redis::RedisResult<Option<UserConfig>> {
|
||||
let config = self.get_user_config(user_id).await?;
|
||||
match config {
|
||||
Some(_) => Ok(config),
|
||||
|
@ -1,2 +1,2 @@
|
||||
pub mod user_config;
|
||||
pub mod database;
|
||||
pub mod user_config;
|
||||
|
@ -1,10 +1,12 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::tts::{gcp_tts::structs::voice_selection_params::VoiceSelectionParams, tts_type::TTSType};
|
||||
use crate::tts::{
|
||||
gcp_tts::structs::voice_selection_params::VoiceSelectionParams, tts_type::TTSType,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct UserConfig {
|
||||
pub tts_type: Option<TTSType>,
|
||||
pub gcp_tts_voice: Option<VoiceSelectionParams>,
|
||||
pub voicevox_speaker: Option<i64>
|
||||
pub voicevox_speaker: Option<i64>,
|
||||
}
|
||||
|
@ -1,11 +1,22 @@
|
||||
use serenity::{client::{EventHandler, Context}, async_trait, model::{gateway::Ready, interactions::Interaction, id::GuildId, channel::Message, voice::VoiceState, prelude::InteractionApplicationCommandCallbackDataFlags}};
|
||||
use crate::{events, commands::{setup::setup_command, stop::stop_command, config::config_command}, data::DatabaseClientData, tts::tts_type::TTSType};
|
||||
use crate::{
|
||||
commands::{config::config_command, setup::setup_command, stop::stop_command},
|
||||
data::DatabaseClientData,
|
||||
events,
|
||||
tts::tts_type::TTSType,
|
||||
};
|
||||
use serenity::{
|
||||
async_trait,
|
||||
client::{Context, EventHandler},
|
||||
model::{
|
||||
channel::Message, gateway::Ready, id::GuildId, interactions::Interaction,
|
||||
prelude::InteractionApplicationCommandCallbackDataFlags, voice::VoiceState,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct Handler;
|
||||
|
||||
#[async_trait]
|
||||
impl EventHandler for Handler {
|
||||
|
||||
async fn message(&self, ctx: Context, message: Message) {
|
||||
events::message_receive::message(ctx, message).await
|
||||
}
|
||||
@ -29,9 +40,16 @@ impl EventHandler for Handler {
|
||||
let data_read = ctx.data.read().await;
|
||||
|
||||
let mut config = {
|
||||
let database = data_read.get::<DatabaseClientData>().expect("Cannot get DatabaseClientData").clone();
|
||||
let database = data_read
|
||||
.get::<DatabaseClientData>()
|
||||
.expect("Cannot get DatabaseClientData")
|
||||
.clone();
|
||||
let mut database = database.lock().await;
|
||||
database.get_user_config_or_default(message_component.user.id.0).await.unwrap().unwrap()
|
||||
database
|
||||
.get_user_config_or_default(message_component.user.id.0)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let res = (*v).clone();
|
||||
@ -48,7 +66,13 @@ impl EventHandler for Handler {
|
||||
}
|
||||
_ => {
|
||||
if res.starts_with("TTS_CONFIG_VOICEVOX_SPEAKER_SELECTED_") {
|
||||
config.voicevox_speaker = Some(i64::from_str_radix(&res.replace("TTS_CONFIG_VOICEVOX_SPEAKER_SELECTED_", ""), 10).unwrap());
|
||||
config.voicevox_speaker = Some(
|
||||
i64::from_str_radix(
|
||||
&res.replace("TTS_CONFIG_VOICEVOX_SPEAKER_SELECTED_", ""),
|
||||
10,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
config_changed = true;
|
||||
voicevox_changed = true;
|
||||
}
|
||||
@ -56,11 +80,17 @@ impl EventHandler for Handler {
|
||||
}
|
||||
|
||||
if config_changed {
|
||||
let database = data_read.get::<DatabaseClientData>().expect("Cannot get DatabaseClientData").clone();
|
||||
let database = data_read
|
||||
.get::<DatabaseClientData>()
|
||||
.expect("Cannot get DatabaseClientData")
|
||||
.clone();
|
||||
let mut database = database.lock().await;
|
||||
database.set_user_config(message_component.user.id.0, config.clone()).await.unwrap();
|
||||
database
|
||||
.set_user_config(message_component.user.id.0, config.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if voicevox_changed && config.tts_type.unwrap_or(TTSType::GCP) == TTSType::GCP {
|
||||
if voicevox_changed && config.tts_type.unwrap_or(TTSType::GCP) == TTSType::GCP {
|
||||
message_component.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("設定しました\nこの音声を使うにはAPIをGoogleからVOICEVOXに変更する必要があります。")
|
||||
@ -68,12 +98,16 @@ impl EventHandler for Handler {
|
||||
})
|
||||
}).await.unwrap();
|
||||
} else {
|
||||
message_component.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("設定しました")
|
||||
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
message_component
|
||||
.create_interaction_response(&ctx.http, |f| {
|
||||
f.interaction_response_data(|d| {
|
||||
d.content("設定しました").flags(
|
||||
InteractionApplicationCommandCallbackDataFlags::EPHEMERAL,
|
||||
)
|
||||
})
|
||||
})
|
||||
}).await.unwrap();
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use serenity::{prelude::Context, model::prelude::Message};
|
||||
use serenity::{model::prelude::Message, prelude::Context};
|
||||
|
||||
use crate::data::TTSData;
|
||||
|
||||
@ -17,7 +17,10 @@ pub async fn message(ctx: Context, message: Message) {
|
||||
|
||||
let storage_lock = {
|
||||
let data_read = ctx.data.read().await;
|
||||
data_read.get::<TTSData>().expect("Cannot get TTSStorage").clone()
|
||||
data_read
|
||||
.get::<TTSData>()
|
||||
.expect("Cannot get TTSStorage")
|
||||
.clone()
|
||||
};
|
||||
|
||||
{
|
||||
|
@ -1,3 +1,3 @@
|
||||
pub mod ready;
|
||||
pub mod message_receive;
|
||||
pub mod ready;
|
||||
pub mod voice_state_update;
|
@ -1,20 +1,16 @@
|
||||
use serenity::{prelude::Context, model::prelude::{Ready, application_command::ApplicationCommand}};
|
||||
use serenity::{
|
||||
model::prelude::{application_command::ApplicationCommand, Ready},
|
||||
prelude::Context,
|
||||
};
|
||||
|
||||
pub async fn ready(ctx: Context, ready: Ready) {
|
||||
println!("{} is connected!", ready.user.name);
|
||||
|
||||
let _ = ApplicationCommand::set_global_application_commands(&ctx.http, |commands| {
|
||||
commands.create_application_command(|command| {
|
||||
command.name("stop")
|
||||
.description("Stop tts")
|
||||
});
|
||||
commands.create_application_command(|command| {
|
||||
command.name("setup")
|
||||
.description("Setup tts")
|
||||
});
|
||||
commands.create_application_command(|command| {
|
||||
command.name("config")
|
||||
.description("Config")
|
||||
})
|
||||
}).await;
|
||||
commands.create_application_command(|command| command.name("stop").description("Stop tts"));
|
||||
commands
|
||||
.create_application_command(|command| command.name("setup").description("Setup tts"));
|
||||
commands.create_application_command(|command| command.name("config").description("Config"))
|
||||
})
|
||||
.await;
|
||||
}
|
@ -1,6 +1,16 @@
|
||||
use serenity::{prelude::Context, model::{prelude::GuildId, voice::VoiceState}};
|
||||
use serenity::{
|
||||
model::{prelude::GuildId, voice::VoiceState},
|
||||
prelude::Context,
|
||||
};
|
||||
|
||||
use crate::{data::TTSData, implement::{voice_move_state::{VoiceMoveStateTrait, VoiceMoveState}, member_name::ReadName}, tts::message::AnnounceMessage};
|
||||
use crate::{
|
||||
data::TTSData,
|
||||
implement::{
|
||||
member_name::ReadName,
|
||||
voice_move_state::{VoiceMoveState, VoiceMoveStateTrait},
|
||||
},
|
||||
tts::message::AnnounceMessage,
|
||||
};
|
||||
|
||||
pub async fn voice_state_update(
|
||||
ctx: Context,
|
||||
@ -15,7 +25,10 @@ pub async fn voice_state_update(
|
||||
|
||||
let storage_lock = {
|
||||
let data_read = ctx.data.read().await;
|
||||
data_read.get::<TTSData>().expect("Cannot get TTSStorage").clone()
|
||||
data_read
|
||||
.get::<TTSData>()
|
||||
.expect("Cannot get TTSStorage")
|
||||
.clone()
|
||||
};
|
||||
|
||||
{
|
||||
@ -29,15 +42,19 @@ pub async fn voice_state_update(
|
||||
let voice_move_state = new.move_state(&old, instance.voice_channel);
|
||||
|
||||
let message: Option<String> = match voice_move_state {
|
||||
VoiceMoveState::JOIN => Some(format!("{} さんが通話に参加しました", new.member.unwrap().read_name())),
|
||||
VoiceMoveState::LEAVE => Some(format!("{} さんが通話から退出しました", new.member.unwrap().read_name())),
|
||||
VoiceMoveState::JOIN => Some(format!(
|
||||
"{} さんが通話に参加しました",
|
||||
new.member.unwrap().read_name()
|
||||
)),
|
||||
VoiceMoveState::LEAVE => Some(format!(
|
||||
"{} さんが通話から退出しました",
|
||||
new.member.unwrap().read_name()
|
||||
)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(message) = message {
|
||||
instance.read(AnnounceMessage {
|
||||
message
|
||||
}, &ctx).await;
|
||||
instance.read(AnnounceMessage { message }, &ctx).await;
|
||||
}
|
||||
|
||||
if voice_move_state == VoiceMoveState::LEAVE {
|
||||
@ -51,7 +68,10 @@ pub async fn voice_state_update(
|
||||
if del_flag {
|
||||
storage.remove(&guild_id);
|
||||
|
||||
let manager = songbird::get(&ctx).await.expect("Cannot get songbird client.").clone();
|
||||
let manager = songbird::get(&ctx)
|
||||
.await
|
||||
.expect("Cannot get songbird client.")
|
||||
.clone();
|
||||
|
||||
manager.remove(guild_id.0).await.unwrap();
|
||||
}
|
||||
|
@ -1,17 +1,19 @@
|
||||
use std::{fs::File, io::Write, env};
|
||||
use std::{env, fs::File, io::Write};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serenity::{prelude::Context, model::prelude::Message};
|
||||
use serenity::{model::prelude::Message, prelude::Context};
|
||||
|
||||
use crate::{
|
||||
data::{TTSClientData, DatabaseClientData},
|
||||
data::{DatabaseClientData, TTSClientData},
|
||||
tts::{
|
||||
gcp_tts::structs::{
|
||||
audio_config::AudioConfig, synthesis_input::SynthesisInput,
|
||||
synthesize_request::SynthesizeRequest,
|
||||
},
|
||||
instance::TTSInstance,
|
||||
message::TTSMessage,
|
||||
tts_type::TTSType,
|
||||
gcp_tts::structs::{
|
||||
audio_config::AudioConfig, synthesis_input::SynthesisInput, synthesize_request::SynthesizeRequest
|
||||
}, validator
|
||||
validator,
|
||||
},
|
||||
};
|
||||
|
||||
@ -42,7 +44,11 @@ impl TTSMessage for Message {
|
||||
};
|
||||
|
||||
if self.attachments.len() > 0 {
|
||||
res = format!("{}<break time=\"200ms\"/>{}個の添付ファイル", res, self.attachments.len());
|
||||
res = format!(
|
||||
"{}<break time=\"200ms\"/>{}個の添付ファイル",
|
||||
res,
|
||||
self.attachments.len()
|
||||
);
|
||||
}
|
||||
|
||||
instance.before_message = Some(self.clone());
|
||||
@ -54,34 +60,51 @@ impl TTSMessage for Message {
|
||||
let text = self.parse(instance, ctx).await;
|
||||
|
||||
let data_read = ctx.data.read().await;
|
||||
let storage = data_read.get::<TTSClientData>().expect("Cannot get GCP TTSClientStorage").clone();
|
||||
let storage = data_read
|
||||
.get::<TTSClientData>()
|
||||
.expect("Cannot get GCP TTSClientStorage")
|
||||
.clone();
|
||||
let mut tts = storage.lock().await;
|
||||
|
||||
let config = {
|
||||
let database = data_read.get::<DatabaseClientData>().expect("Cannot get DatabaseClientData").clone();
|
||||
let database = data_read
|
||||
.get::<DatabaseClientData>()
|
||||
.expect("Cannot get DatabaseClientData")
|
||||
.clone();
|
||||
let mut database = database.lock().await;
|
||||
database.get_user_config_or_default(self.author.id.0).await.unwrap().unwrap()
|
||||
database
|
||||
.get_user_config_or_default(self.author.id.0)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let audio = match config.tts_type.unwrap_or(TTSType::GCP) {
|
||||
TTSType::GCP => {
|
||||
tts.0.synthesize(SynthesizeRequest {
|
||||
TTSType::GCP => tts
|
||||
.0
|
||||
.synthesize(SynthesizeRequest {
|
||||
input: SynthesisInput {
|
||||
text: None,
|
||||
ssml: Some(format!("<speak>{}</speak>", text))
|
||||
ssml: Some(format!("<speak>{}</speak>", text)),
|
||||
},
|
||||
voice: config.gcp_tts_voice.unwrap(),
|
||||
audioConfig: AudioConfig {
|
||||
audioEncoding: String::from("mp3"),
|
||||
speakingRate: 1.2f32,
|
||||
pitch: 1.0f32
|
||||
}
|
||||
}).await.unwrap()
|
||||
}
|
||||
pitch: 1.0f32,
|
||||
},
|
||||
})
|
||||
.await
|
||||
.unwrap(),
|
||||
|
||||
TTSType::VOICEVOX => {
|
||||
tts.1.synthesize(text.replace("<break time=\"200ms\"/>", "、"), config.voicevox_speaker.unwrap_or(1)).await.unwrap()
|
||||
}
|
||||
TTSType::VOICEVOX => tts
|
||||
.1
|
||||
.synthesize(
|
||||
text.replace("<break time=\"200ms\"/>", "、"),
|
||||
config.voicevox_speaker.unwrap_or(1),
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
|
@ -1,3 +1,3 @@
|
||||
pub mod message;
|
||||
pub mod member_name;
|
||||
pub mod message;
|
||||
pub mod voice_move_state;
|
@ -1,4 +1,4 @@
|
||||
use serenity::model::{voice::VoiceState, prelude::ChannelId};
|
||||
use serenity::model::{prelude::ChannelId, voice::VoiceState};
|
||||
|
||||
pub trait VoiceMoveStateTrait {
|
||||
fn move_state(&self, old: &Option<VoiceState>, target_channel: ChannelId) -> VoiceMoveState;
|
||||
@ -8,7 +8,7 @@ pub trait VoiceMoveStateTrait {
|
||||
pub enum VoiceMoveState {
|
||||
JOIN,
|
||||
LEAVE,
|
||||
NONE
|
||||
NONE,
|
||||
}
|
||||
|
||||
impl VoiceMoveStateTrait for VoiceState {
|
||||
@ -17,10 +17,10 @@ impl VoiceMoveStateTrait for VoiceState {
|
||||
|
||||
if let None = old.clone() {
|
||||
return if target_channel == new.channel_id.unwrap() {
|
||||
VoiceMoveState::JOIN
|
||||
} else {
|
||||
VoiceMoveState::NONE
|
||||
}
|
||||
VoiceMoveState::JOIN
|
||||
} else {
|
||||
VoiceMoveState::NONE
|
||||
};
|
||||
}
|
||||
|
||||
let old = (*old).clone().unwrap();
|
||||
@ -46,9 +46,7 @@ impl VoiceMoveStateTrait for VoiceState {
|
||||
VoiceMoveState::NONE
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
VoiceMoveState::NONE
|
||||
}
|
||||
_ => VoiceMoveState::NONE,
|
||||
}
|
||||
}
|
||||
}
|
31
src/main.rs
31
src/main.rs
@ -1,23 +1,25 @@
|
||||
mod commands;
|
||||
mod config;
|
||||
mod event_handler;
|
||||
mod tts;
|
||||
mod implement;
|
||||
mod data;
|
||||
mod database;
|
||||
mod event_handler;
|
||||
mod events;
|
||||
mod commands;
|
||||
mod implement;
|
||||
mod tts;
|
||||
|
||||
use std::{sync::Arc, collections::HashMap};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use config::Config;
|
||||
use data::{TTSData, TTSClientData, DatabaseClientData};
|
||||
use data::{DatabaseClientData, TTSClientData, TTSData};
|
||||
use database::database::Database;
|
||||
use event_handler::Handler;
|
||||
use tts::{gcp_tts::gcp_tts::TTS, voicevox::voicevox::VOICEVOX};
|
||||
use serenity::{
|
||||
client::{Client, bridge::gateway::GatewayIntents},
|
||||
framework::StandardFramework, prelude::RwLock, futures::lock::Mutex
|
||||
client::{bridge::gateway::GatewayIntents, Client},
|
||||
framework::StandardFramework,
|
||||
futures::lock::Mutex,
|
||||
prelude::RwLock,
|
||||
};
|
||||
use tts::{gcp_tts::gcp_tts::TTS, voicevox::voicevox::VOICEVOX};
|
||||
|
||||
use songbird::SerenityInit;
|
||||
|
||||
@ -30,10 +32,7 @@ use songbird::SerenityInit;
|
||||
/// client.start().await;
|
||||
/// ```
|
||||
async fn create_client(prefix: &str, token: &str, id: u64) -> Result<Client, serenity::Error> {
|
||||
let framework = StandardFramework::new()
|
||||
.configure(|c| c
|
||||
.with_whitespace(true)
|
||||
.prefix(prefix));
|
||||
let framework = StandardFramework::new().configure(|c| c.with_whitespace(true).prefix(prefix));
|
||||
|
||||
Client::builder(token)
|
||||
.event_handler(Handler)
|
||||
@ -51,12 +50,14 @@ async fn main() {
|
||||
let config: Config = toml::from_str(&config).expect("Cannot load config file.");
|
||||
|
||||
// Create discord client
|
||||
let mut client = create_client(&config.prefix, &config.token, config.application_id).await.expect("Err creating client");
|
||||
let mut client = create_client(&config.prefix, &config.token, config.application_id)
|
||||
.await
|
||||
.expect("Err creating client");
|
||||
|
||||
// Create GCP TTS client
|
||||
let tts = match TTS::new("./credentials.json".to_string()).await {
|
||||
Ok(tts) => tts,
|
||||
Err(err) => panic!("{}", err)
|
||||
Err(err) => panic!("{}", err),
|
||||
};
|
||||
|
||||
let voicevox = VOICEVOX::new(config.voicevox_key);
|
||||
|
@ -1,21 +1,22 @@
|
||||
use gcp_auth::Token;
|
||||
use crate::tts::gcp_tts::structs::{
|
||||
synthesize_request::SynthesizeRequest,
|
||||
synthesize_response::SynthesizeResponse,
|
||||
synthesize_request::SynthesizeRequest, synthesize_response::SynthesizeResponse,
|
||||
};
|
||||
use gcp_auth::Token;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TTS {
|
||||
pub token: Token,
|
||||
pub credentials_path: String
|
||||
pub credentials_path: String,
|
||||
}
|
||||
|
||||
impl TTS {
|
||||
|
||||
pub async fn update_token(&mut self) -> Result<(), gcp_auth::Error> {
|
||||
if self.token.has_expired() {
|
||||
let authenticator = gcp_auth::from_credentials_file(self.credentials_path.clone()).await?;
|
||||
let token = authenticator.get_token(&["https://www.googleapis.com/auth/cloud-platform"]).await?;
|
||||
let authenticator =
|
||||
gcp_auth::from_credentials_file(self.credentials_path.clone()).await?;
|
||||
let token = authenticator
|
||||
.get_token(&["https://www.googleapis.com/auth/cloud-platform"])
|
||||
.await?;
|
||||
self.token = token;
|
||||
}
|
||||
|
||||
@ -24,11 +25,13 @@ impl TTS {
|
||||
|
||||
pub async fn new(credentials_path: String) -> Result<TTS, gcp_auth::Error> {
|
||||
let authenticator = gcp_auth::from_credentials_file(credentials_path.clone()).await?;
|
||||
let token = authenticator.get_token(&["https://www.googleapis.com/auth/cloud-platform"]).await?;
|
||||
let token = authenticator
|
||||
.get_token(&["https://www.googleapis.com/auth/cloud-platform"])
|
||||
.await?;
|
||||
|
||||
Ok(TTS {
|
||||
token,
|
||||
credentials_path
|
||||
credentials_path,
|
||||
})
|
||||
}
|
||||
|
||||
@ -53,19 +56,29 @@ impl TTS {
|
||||
/// }
|
||||
/// }).await.unwrap();
|
||||
/// ```
|
||||
pub async fn synthesize(&mut self, request: SynthesizeRequest) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
pub async fn synthesize(
|
||||
&mut self,
|
||||
request: SynthesizeRequest,
|
||||
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
self.update_token().await.unwrap();
|
||||
let client = reqwest::Client::new();
|
||||
match client.post("https://texttospeech.googleapis.com/v1/text:synthesize")
|
||||
match client
|
||||
.post("https://texttospeech.googleapis.com/v1/text:synthesize")
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
||||
.header(reqwest::header::AUTHORIZATION, format!("Bearer {}", self.token.as_str()))
|
||||
.header(
|
||||
reqwest::header::AUTHORIZATION,
|
||||
format!("Bearer {}", self.token.as_str()),
|
||||
)
|
||||
.body(serde_json::to_string(&request).unwrap())
|
||||
.send().await {
|
||||
Ok(ok) => {
|
||||
let response: SynthesizeResponse = serde_json::from_str(&ok.text().await.expect("")).unwrap();
|
||||
Ok(base64::decode(response.audioContent).unwrap()[..].to_vec())
|
||||
},
|
||||
Err(err) => Err(Box::new(err))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(ok) => {
|
||||
let response: SynthesizeResponse =
|
||||
serde_json::from_str(&ok.text().await.expect("")).unwrap();
|
||||
Ok(base64::decode(response.audioContent).unwrap()[..].to_vec())
|
||||
}
|
||||
Err(err) => Err(Box::new(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Example:
|
||||
/// ```rust
|
||||
@ -13,5 +13,5 @@ use serde::{Serialize, Deserialize};
|
||||
pub struct AudioConfig {
|
||||
pub audioEncoding: String,
|
||||
pub speakingRate: f32,
|
||||
pub pitch: f32
|
||||
pub pitch: f32,
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
pub mod audio_config;
|
||||
pub mod synthesis_input;
|
||||
pub mod synthesize_request;
|
||||
pub mod voice_selection_params;
|
||||
pub mod synthesize_response;
|
||||
pub mod voice_selection_params;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Example:
|
||||
/// ```rust
|
||||
@ -10,5 +10,5 @@ use serde::{Serialize, Deserialize};
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SynthesisInput {
|
||||
pub text: Option<String>,
|
||||
pub ssml: Option<String>
|
||||
pub ssml: Option<String>,
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::tts::gcp_tts::structs::{
|
||||
synthesis_input::SynthesisInput,
|
||||
audio_config::AudioConfig,
|
||||
audio_config::AudioConfig, synthesis_input::SynthesisInput,
|
||||
voice_selection_params::VoiceSelectionParams,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Example:
|
||||
/// ```rust
|
||||
|
@ -1,7 +1,7 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
pub struct SynthesizeResponse {
|
||||
pub audioContent: String
|
||||
pub audioContent: String,
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Example:
|
||||
/// ```rust
|
||||
@ -13,5 +13,5 @@ use serde::{Serialize, Deserialize};
|
||||
pub struct VoiceSelectionParams {
|
||||
pub languageCode: String,
|
||||
pub name: String,
|
||||
pub ssmlGender: String
|
||||
pub ssmlGender: String,
|
||||
}
|
@ -1,12 +1,18 @@
|
||||
use serenity::{model::{channel::Message, id::{ChannelId, GuildId}}, prelude::Context};
|
||||
use serenity::{
|
||||
model::{
|
||||
channel::Message,
|
||||
id::{ChannelId, GuildId},
|
||||
},
|
||||
prelude::Context,
|
||||
};
|
||||
|
||||
use crate::{tts::message::TTSMessage};
|
||||
use crate::tts::message::TTSMessage;
|
||||
|
||||
pub struct TTSInstance {
|
||||
pub before_message: Option<Message>,
|
||||
pub text_channel: ChannelId,
|
||||
pub voice_channel: ChannelId,
|
||||
pub guild: GuildId
|
||||
pub guild: GuildId,
|
||||
}
|
||||
|
||||
impl TTSInstance {
|
||||
@ -17,7 +23,8 @@ impl TTSInstance {
|
||||
/// instance.read(message, &ctx).await;
|
||||
/// ```
|
||||
pub async fn read<T>(&mut self, message: T, ctx: &Context)
|
||||
where T: TTSMessage
|
||||
where
|
||||
T: TTSMessage,
|
||||
{
|
||||
let path = message.synthesize(self, ctx).await;
|
||||
|
||||
@ -25,7 +32,9 @@ impl TTSInstance {
|
||||
let manager = songbird::get(&ctx).await.unwrap();
|
||||
let call = manager.get(self.guild).unwrap();
|
||||
let mut call = call.lock().await;
|
||||
let input = songbird::input::ffmpeg(path).await.expect("File not found.");
|
||||
let input = songbird::input::ffmpeg(path)
|
||||
.await
|
||||
.expect("File not found.");
|
||||
call.enqueue_source(input);
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,18 @@
|
||||
use std::{fs::File, io::Write, env};
|
||||
use std::{env, fs::File, io::Write};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serenity::prelude::Context;
|
||||
|
||||
use crate::{tts::instance::TTSInstance, data::TTSClientData};
|
||||
use crate::{data::TTSClientData, tts::instance::TTSInstance};
|
||||
|
||||
use super::gcp_tts::structs::{synthesize_request::SynthesizeRequest, synthesis_input::SynthesisInput, audio_config::AudioConfig, voice_selection_params::VoiceSelectionParams};
|
||||
use super::gcp_tts::structs::{
|
||||
audio_config::AudioConfig, synthesis_input::SynthesisInput,
|
||||
synthesize_request::SynthesizeRequest, voice_selection_params::VoiceSelectionParams,
|
||||
};
|
||||
|
||||
/// Message trait that can be used to synthesize text to speech.
|
||||
#[async_trait]
|
||||
pub trait TTSMessage {
|
||||
|
||||
/// Parse the message for synthesis.
|
||||
///
|
||||
/// Example:
|
||||
@ -36,31 +38,41 @@ pub struct AnnounceMessage {
|
||||
impl TTSMessage for AnnounceMessage {
|
||||
async fn parse(&self, instance: &mut TTSInstance, _ctx: &Context) -> String {
|
||||
instance.before_message = None;
|
||||
format!(r#"<speak>アナウンス<break time="200ms"/>{}</speak>"#, self.message)
|
||||
format!(
|
||||
r#"<speak>アナウンス<break time="200ms"/>{}</speak>"#,
|
||||
self.message
|
||||
)
|
||||
}
|
||||
|
||||
async fn synthesize(&self, instance: &mut TTSInstance, ctx: &Context) -> String {
|
||||
let text = self.parse(instance, ctx).await;
|
||||
let data_read = ctx.data.read().await;
|
||||
let storage = data_read.get::<TTSClientData>().expect("Cannot get TTSClientStorage").clone();
|
||||
let storage = data_read
|
||||
.get::<TTSClientData>()
|
||||
.expect("Cannot get TTSClientStorage")
|
||||
.clone();
|
||||
let mut storage = storage.lock().await;
|
||||
|
||||
let audio = storage.0.synthesize(SynthesizeRequest {
|
||||
input: SynthesisInput {
|
||||
text: None,
|
||||
ssml: Some(text)
|
||||
},
|
||||
voice: VoiceSelectionParams {
|
||||
languageCode: String::from("ja-JP"),
|
||||
name: String::from("ja-JP-Wavenet-B"),
|
||||
ssmlGender: String::from("neutral")
|
||||
},
|
||||
audioConfig: AudioConfig {
|
||||
audioEncoding: String::from("mp3"),
|
||||
speakingRate: 1.2f32,
|
||||
pitch: 1.0f32
|
||||
}
|
||||
}).await.unwrap();
|
||||
let audio = storage
|
||||
.0
|
||||
.synthesize(SynthesizeRequest {
|
||||
input: SynthesisInput {
|
||||
text: None,
|
||||
ssml: Some(text),
|
||||
},
|
||||
voice: VoiceSelectionParams {
|
||||
languageCode: String::from("ja-JP"),
|
||||
name: String::from("ja-JP-Wavenet-B"),
|
||||
ssmlGender: String::from("neutral"),
|
||||
},
|
||||
audioConfig: AudioConfig {
|
||||
audioEncoding: String::from("mp3"),
|
||||
speakingRate: 1.2f32,
|
||||
pitch: 1.0f32,
|
||||
},
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
pub mod gcp_tts;
|
||||
pub mod voicevox;
|
||||
pub mod instance;
|
||||
pub mod message;
|
||||
pub mod tts_type;
|
||||
pub mod instance;
|
||||
pub mod validator;
|
||||
pub mod voicevox;
|
||||
|
@ -3,5 +3,5 @@ use serde::{Deserialize, Serialize};
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum TTSType {
|
||||
GCP,
|
||||
VOICEVOX
|
||||
VOICEVOX,
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::mora::Mora;
|
||||
|
||||
@ -7,5 +7,5 @@ pub struct AccentPhrase {
|
||||
pub moras: Vec<Mora>,
|
||||
pub accent: f64,
|
||||
pub pause_mora: Option<Mora>,
|
||||
pub is_interrogative: bool
|
||||
pub is_interrogative: bool,
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::accent_phrase::AccentPhrase;
|
||||
|
||||
@ -14,5 +14,5 @@ pub struct AudioQuery {
|
||||
pub postPhonemeLength: f64,
|
||||
pub outputSamplingRate: f64,
|
||||
pub outputStereo: bool,
|
||||
pub kana: Option<String>
|
||||
pub kana: Option<String>,
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
pub mod mora;
|
||||
pub mod audio_query;
|
||||
pub mod accent_phrase;
|
||||
pub mod audio_query;
|
||||
pub mod mora;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Mora {
|
||||
@ -7,5 +7,5 @@ pub struct Mora {
|
||||
pub consonant_length: Option<f64>,
|
||||
pub vowel: String,
|
||||
pub vowel_length: f64,
|
||||
pub pitch: f64
|
||||
pub pitch: f64,
|
||||
}
|
@ -2,7 +2,7 @@ const API_URL: &str = "https://api.su-shiki.com/v2/voicevox/audio";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VOICEVOX {
|
||||
pub key: String
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
impl VOICEVOX {
|
||||
@ -33,21 +33,30 @@ impl VOICEVOX {
|
||||
}
|
||||
|
||||
pub fn new(key: String) -> Self {
|
||||
Self {
|
||||
key
|
||||
}
|
||||
Self { key }
|
||||
}
|
||||
|
||||
pub async fn synthesize(&self, text: String, speaker: i64) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
pub async fn synthesize(
|
||||
&self,
|
||||
text: String,
|
||||
speaker: i64,
|
||||
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
let client = reqwest::Client::new();
|
||||
match client.post(API_URL).query(&[("speaker", speaker.to_string()), ("text", text), ("key", self.key.clone())]).send().await {
|
||||
match client
|
||||
.post(API_URL)
|
||||
.query(&[
|
||||
("speaker", speaker.to_string()),
|
||||
("text", text),
|
||||
("key", self.key.clone()),
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
let body = response.bytes().await?;
|
||||
Ok(body.to_vec())
|
||||
}
|
||||
Err(err) => {
|
||||
Err(Box::new(err))
|
||||
}
|
||||
Err(err) => Err(Box::new(err)),
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user