mirror of
https://github.com/mii443/ncb-tts-r2.git
synced 2025-08-23 16:39:24 +00:00
Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
6dafc66878 | |||
1789bd7c4e | |||
0b93c23e91 | |||
7fe65bc397 | |||
51c39036c6 | |||
c52429bce0 | |||
5ca5325fbd | |||
4161acbd45 | |||
47b11262e2 | |||
b36bee8be8 | |||
6aec4e4ea7 | |||
2bf2fe05f1 | |||
f99d37ea56 | |||
3652079bab | |||
8a4de65a8a |
@ -14,6 +14,7 @@ reqwest = { version = "0.11", features = ["json"] }
|
|||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
async-trait = "0.1.57"
|
async-trait = "0.1.57"
|
||||||
redis = "*"
|
redis = "*"
|
||||||
|
regex = "1"
|
||||||
|
|
||||||
[dependencies.uuid]
|
[dependencies.uuid]
|
||||||
version = "0.8"
|
version = "0.8"
|
||||||
|
20
Dockerfile
20
Dockerfile
@ -1,16 +1,18 @@
|
|||||||
FROM ubuntu:22.04
|
FROM ubuntu:22.04
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install -y ffmpeg libssl-dev pkg-config libopus-dev wget curl gcc \
|
|
||||||
&& apt-get -y clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable
|
|
||||||
ENV PATH $PATH:/root/.cargo/bin/
|
|
||||||
RUN rustup install stable
|
|
||||||
WORKDIR /usr/src/ncb-tts-r2
|
WORKDIR /usr/src/ncb-tts-r2
|
||||||
COPY Cargo.toml .
|
COPY Cargo.toml .
|
||||||
COPY src src
|
COPY src src
|
||||||
RUN cargo build --release \
|
ENV PATH $PATH:/root/.cargo/bin/
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y ffmpeg libssl-dev pkg-config libopus-dev wget curl gcc \
|
||||||
|
&& apt-get -y clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable \
|
||||||
|
&& rustup install stable \
|
||||||
|
&& cargo build --release \
|
||||||
&& cp /usr/src/ncb-tts-r2/target/release/ncb-tts-r2 /usr/bin/ncb-tts-r2 \
|
&& cp /usr/src/ncb-tts-r2/target/release/ncb-tts-r2 /usr/bin/ncb-tts-r2 \
|
||||||
&& mkdir -p /ncb-tts-r2/audio
|
&& mkdir -p /ncb-tts-r2/audio \
|
||||||
|
&& apt-get purge -y pkg-config wget curl gcc \
|
||||||
|
&& rustup self uninstall -y
|
||||||
WORKDIR /ncb-tts-r2
|
WORKDIR /ncb-tts-r2
|
||||||
CMD ["ncb-tts-r2"]
|
CMD ["ncb-tts-r2"]
|
56
src/commands/config.rs
Normal file
56
src/commands/config.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use serenity::{prelude::Context, model::prelude::{application_command::ApplicationCommandInteraction, InteractionApplicationCommandCallbackDataFlags}};
|
||||||
|
|
||||||
|
use crate::{tts::{voicevox::voicevox::VOICEVOX, tts_type::TTSType}, data::DatabaseClientData};
|
||||||
|
|
||||||
|
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 mut database = database.lock().await;
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
o
|
||||||
|
}).placeholder("VOICEVOX Speakerを指定")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}).flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
3
src/commands/mod.rs
Normal file
3
src/commands/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod setup;
|
||||||
|
pub mod stop;
|
||||||
|
pub mod config;
|
81
src/commands/setup.rs
Normal file
81
src/commands/setup.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
use serenity::{prelude::Context, model::prelude::{application_command::ApplicationCommandInteraction, InteractionApplicationCommandCallbackDataFlags, UserId}};
|
||||||
|
|
||||||
|
use crate::{data::TTSData, tts::instance::TTSInstance};
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}).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)
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let guild = guild.unwrap();
|
||||||
|
|
||||||
|
let channel_id = guild
|
||||||
|
.voice_states
|
||||||
|
.get(&UserId(command.user.id.0))
|
||||||
|
.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)
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel_id = channel_id.unwrap();
|
||||||
|
|
||||||
|
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()
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
command.create_interaction_response(&ctx.http, |f| {
|
||||||
|
f.interaction_response_data(|d| {
|
||||||
|
d.create_embed(|e| {
|
||||||
|
e.title("読み上げ (Serenity)")
|
||||||
|
.field("クレジット", "```\n四国めたん ずんだもん\n春日部つむぎ 雨晴はう\n波音リツ 玄野武宏\n白上虎太郎 青山龍星\n冥鳴ひまり 九州そら\nモチノ・キョウコ```", false)
|
||||||
|
.field("設定コマンド", "`/config`", false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
70
src/commands/stop.rs
Normal file
70
src/commands/stop.rs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
use serenity::{prelude::Context, model::prelude::{application_command::ApplicationCommandInteraction, InteractionApplicationCommandCallbackDataFlags, UserId}};
|
||||||
|
|
||||||
|
use crate::data::TTSData;
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}).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)
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let guild = guild.unwrap();
|
||||||
|
|
||||||
|
let channel_id = guild
|
||||||
|
.voice_states
|
||||||
|
.get(&UserId(command.user.id.0))
|
||||||
|
.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)
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.remove(&guild.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _handler = manager.remove(guild.id.0).await;
|
||||||
|
|
||||||
|
command.create_interaction_response(&ctx.http, |f| {
|
||||||
|
f.interaction_response_data(|d| {
|
||||||
|
d.content("停止しました")
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -4,16 +4,16 @@ use super::user_config::UserConfig;
|
|||||||
use redis::Commands;
|
use redis::Commands;
|
||||||
|
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
pub connection: redis::Connection
|
pub client: redis::Client
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Database {
|
impl Database {
|
||||||
pub fn new(connection: redis::Connection) -> Self {
|
pub fn new(client: redis::Client) -> Self {
|
||||||
Self { connection }
|
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>> {
|
||||||
let config: String = self.connection.get(format!("discord_user:{}", user_id)).unwrap_or_default();
|
let config: String = self.client.get_connection().unwrap().get(format!("discord_user:{}", user_id)).unwrap_or_default();
|
||||||
|
|
||||||
match serde_json::from_str(&config) {
|
match serde_json::from_str(&config) {
|
||||||
Ok(config) => Ok(Some(config)),
|
Ok(config) => Ok(Some(config)),
|
||||||
@ -23,7 +23,7 @@ impl Database {
|
|||||||
|
|
||||||
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();
|
let config = serde_json::to_string(&config).unwrap();
|
||||||
self.connection.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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ impl Database {
|
|||||||
voicevox_speaker: Some(1)
|
voicevox_speaker: Some(1)
|
||||||
};
|
};
|
||||||
|
|
||||||
self.connection.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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,165 +1,83 @@
|
|||||||
use serenity::{client::{EventHandler, Context}, async_trait, model::{gateway::Ready, interactions::{Interaction, application_command::ApplicationCommandInteraction, InteractionApplicationCommandCallbackDataFlags}, id::{GuildId, UserId}, channel::Message, prelude::Member, voice::VoiceState}, framework::standard::macros::group};
|
use serenity::{client::{EventHandler, Context}, async_trait, model::{gateway::Ready, interactions::Interaction, id::GuildId, channel::Message, voice::VoiceState, prelude::InteractionApplicationCommandCallbackDataFlags}};
|
||||||
use crate::{data::TTSData, tts::{instance::TTSInstance, message::AnnounceMessage}, implement::member_name::ReadName};
|
use crate::{events, commands::{setup::setup_command, stop::stop_command, config::config_command}, data::DatabaseClientData, tts::tts_type::TTSType};
|
||||||
|
|
||||||
#[group]
|
|
||||||
struct Test;
|
|
||||||
|
|
||||||
pub struct Handler;
|
pub struct Handler;
|
||||||
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
}).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)
|
|
||||||
})
|
|
||||||
}).await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let guild = guild.unwrap();
|
|
||||||
|
|
||||||
let channel_id = guild
|
|
||||||
.voice_states
|
|
||||||
.get(&UserId(command.user.id.0))
|
|
||||||
.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)
|
|
||||||
})
|
|
||||||
}).await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let channel_id = channel_id.unwrap();
|
|
||||||
|
|
||||||
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()
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
}).await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
storage.remove(&guild.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _handler = manager.leave(guild.id.0).await;
|
|
||||||
|
|
||||||
command.create_interaction_response(&ctx.http, |f| {
|
|
||||||
f.interaction_response_data(|d| {
|
|
||||||
d.content("停止しました").flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
|
||||||
})
|
|
||||||
}).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
}).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)
|
|
||||||
})
|
|
||||||
}).await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let guild = guild.unwrap();
|
|
||||||
|
|
||||||
let channel_id = guild
|
|
||||||
.voice_states
|
|
||||||
.get(&UserId(command.user.id.0))
|
|
||||||
.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)
|
|
||||||
})
|
|
||||||
}).await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let channel_id = channel_id.unwrap();
|
|
||||||
|
|
||||||
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()
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
}).await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
command.create_interaction_response(&ctx.http, |f| {
|
|
||||||
f.interaction_response_data(|d| {
|
|
||||||
d.content("セットアップ完了").flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
|
||||||
})
|
|
||||||
}).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl EventHandler for Handler {
|
impl EventHandler for Handler {
|
||||||
|
|
||||||
|
async fn message(&self, ctx: Context, message: Message) {
|
||||||
|
events::message_receive::message(ctx, message).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ready(&self, ctx: Context, ready: Ready) {
|
||||||
|
events::ready::ready(ctx, ready).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
||||||
if let Interaction::ApplicationCommand(command) = interaction {
|
if let Interaction::ApplicationCommand(command) = interaction.clone() {
|
||||||
let name = &*command.data.name;
|
let name = &*command.data.name;
|
||||||
match name {
|
match name {
|
||||||
"setup" => setup_command(&ctx, &command).await.unwrap(),
|
"setup" => setup_command(&ctx, &command).await.unwrap(),
|
||||||
"stop" => stop_command(&ctx, &command).await.unwrap(),
|
"stop" => stop_command(&ctx, &command).await.unwrap(),
|
||||||
|
"config" => config_command(&ctx, &command).await.unwrap(),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(message_component) = interaction.message_component() {
|
||||||
|
if let Some(v) = message_component.data.values.get(0) {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
|
||||||
|
let mut config = {
|
||||||
|
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()
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = (*v).clone();
|
||||||
|
let mut config_changed = false;
|
||||||
|
let mut voicevox_changed = false;
|
||||||
|
match &*res {
|
||||||
|
"TTS_CONFIG_ENGINE_SELECTED_GOOGLE" => {
|
||||||
|
config.tts_type = Some(TTSType::GCP);
|
||||||
|
config_changed = true;
|
||||||
|
}
|
||||||
|
"TTS_CONFIG_ENGINE_SELECTED_VOICEVOX" => {
|
||||||
|
config.tts_type = Some(TTSType::VOICEVOX);
|
||||||
|
config_changed = true;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
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_changed = true;
|
||||||
|
voicevox_changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config_changed {
|
||||||
|
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();
|
||||||
|
|
||||||
|
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に変更する必要があります。")
|
||||||
|
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||||
|
})
|
||||||
|
}).await.unwrap();
|
||||||
|
} else {
|
||||||
|
message_component.create_interaction_response(&ctx.http, |f| {
|
||||||
|
f.interaction_response_data(|d| {
|
||||||
|
d.content("設定しました")
|
||||||
|
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||||
|
})
|
||||||
|
}).await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn voice_state_update(
|
async fn voice_state_update(
|
||||||
@ -169,131 +87,6 @@ impl EventHandler for Handler {
|
|||||||
old: Option<VoiceState>,
|
old: Option<VoiceState>,
|
||||||
new: VoiceState,
|
new: VoiceState,
|
||||||
) {
|
) {
|
||||||
let guild_id = guild_id.unwrap();
|
events::voice_state_update::voice_state_update(ctx, guild_id, old, new).await
|
||||||
|
|
||||||
let storage_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<TTSData>().expect("Cannot get TTSStorage").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut storage = storage_lock.write().await;
|
|
||||||
if !storage.contains_key(&guild_id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let instance = storage.get_mut(&guild_id).unwrap();
|
|
||||||
|
|
||||||
let mut message: Option<String> = None;
|
|
||||||
|
|
||||||
match old {
|
|
||||||
Some(old) => {
|
|
||||||
match (old.channel_id, new.channel_id) {
|
|
||||||
(Some(old_channel_id), Some(new_channel_id)) => {
|
|
||||||
if old_channel_id == new_channel_id {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if old_channel_id != new_channel_id {
|
|
||||||
if instance.voice_channel == new_channel_id {
|
|
||||||
message = Some(format!("{} さんが通話に参加しました", new.member.unwrap().read_name()));
|
|
||||||
}
|
|
||||||
} else if old_channel_id == instance.voice_channel && new_channel_id != instance.voice_channel {
|
|
||||||
message = Some(format!("{} さんが通話から退出しました", new.member.unwrap().read_name()));
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Some(old_channel_id), None) => {
|
|
||||||
if old_channel_id == instance.voice_channel {
|
|
||||||
message = Some(format!("{} さんが通話から退出しました", new.member.unwrap().read_name()));
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(None, Some(new_channel_id)) => {
|
|
||||||
if new_channel_id == instance.voice_channel {
|
|
||||||
message = Some(format!("{} さんが通話に参加しました", new.member.unwrap().read_name()));
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
match new.channel_id {
|
|
||||||
Some(channel_id) => {
|
|
||||||
if instance.voice_channel == channel_id {
|
|
||||||
message = Some(format!("{} さんが通話に参加しました", new.member.unwrap().read_name()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(message) = message {
|
|
||||||
instance.read(AnnounceMessage {
|
|
||||||
message
|
|
||||||
}, &ctx).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn message(&self, ctx: Context, message: Message) {
|
|
||||||
|
|
||||||
if message.author.bot {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let guild_id = message.guild(&ctx.cache).await;
|
|
||||||
|
|
||||||
if let None = guild_id {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let guild_id = guild_id.unwrap().id;
|
|
||||||
|
|
||||||
let storage_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<TTSData>().expect("Cannot get TTSStorage").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut storage = storage_lock.write().await;
|
|
||||||
if !storage.contains_key(&guild_id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let instance = storage.get_mut(&guild_id).unwrap();
|
|
||||||
|
|
||||||
if instance.text_channel.0 != message.channel_id.0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.read(message, &ctx).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn ready(&self, ctx: Context, ready: Ready) {
|
|
||||||
println!("{} is connected!", ready.user.name);
|
|
||||||
|
|
||||||
let guild_id = GuildId(660046656934248460);
|
|
||||||
|
|
||||||
let commands = GuildId::set_application_commands(&guild_id, &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")
|
|
||||||
})
|
|
||||||
}).await;
|
|
||||||
println!("{:?}", commands);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
37
src/events/message_receive.rs
Normal file
37
src/events/message_receive.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
use serenity::{prelude::Context, model::prelude::Message};
|
||||||
|
|
||||||
|
use crate::data::TTSData;
|
||||||
|
|
||||||
|
pub async fn message(ctx: Context, message: Message) {
|
||||||
|
if message.author.bot {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let guild_id = message.guild(&ctx.cache).await;
|
||||||
|
|
||||||
|
if let None = guild_id {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let guild_id = guild_id.unwrap().id;
|
||||||
|
|
||||||
|
let storage_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<TTSData>().expect("Cannot get TTSStorage").clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut storage = storage_lock.write().await;
|
||||||
|
if !storage.contains_key(&guild_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let instance = storage.get_mut(&guild_id).unwrap();
|
||||||
|
|
||||||
|
if instance.text_channel.0 != message.channel_id.0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.read(message, &ctx).await;
|
||||||
|
}
|
||||||
|
}
|
3
src/events/mod.rs
Normal file
3
src/events/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod ready;
|
||||||
|
pub mod message_receive;
|
||||||
|
pub mod voice_state_update;
|
20
src/events/ready.rs
Normal file
20
src/events/ready.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use serenity::{prelude::Context, model::prelude::{Ready, application_command::ApplicationCommand}};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
57
src/events/voice_state_update.rs
Normal file
57
src/events/voice_state_update.rs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
use serenity::{prelude::Context, model::{prelude::GuildId, voice::VoiceState}};
|
||||||
|
|
||||||
|
use crate::{data::TTSData, implement::{voice_move_state::{VoiceMoveStateTrait, VoiceMoveState}, member_name::ReadName}, tts::message::AnnounceMessage};
|
||||||
|
|
||||||
|
pub async fn voice_state_update(
|
||||||
|
ctx: Context,
|
||||||
|
guild_id: Option<GuildId>,
|
||||||
|
old: Option<VoiceState>,
|
||||||
|
new: VoiceState,
|
||||||
|
) {
|
||||||
|
let guild_id = guild_id.unwrap();
|
||||||
|
|
||||||
|
let storage_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<TTSData>().expect("Cannot get TTSStorage").clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut storage = storage_lock.write().await;
|
||||||
|
if !storage.contains_key(&guild_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let instance = storage.get_mut(&guild_id).unwrap();
|
||||||
|
|
||||||
|
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())),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(message) = message {
|
||||||
|
instance.read(AnnounceMessage {
|
||||||
|
message
|
||||||
|
}, &ctx).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if voice_move_state == VoiceMoveState::LEAVE {
|
||||||
|
let mut del_flag = false;
|
||||||
|
for channel in guild_id.channels(&ctx.http).await.unwrap() {
|
||||||
|
if channel.0 == instance.voice_channel {
|
||||||
|
del_flag = channel.1.members(&ctx.cache).await.unwrap().len() <= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if del_flag {
|
||||||
|
storage.remove(&guild_id);
|
||||||
|
|
||||||
|
let manager = songbird::get(&ctx).await.expect("Cannot get songbird client.").clone();
|
||||||
|
|
||||||
|
manager.remove(guild_id.0).await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
use std::{path::Path, fs::File, io::Write};
|
use std::{fs::File, io::Write, env};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use serenity::{prelude::Context, model::prelude::Message};
|
use serenity::{prelude::Context, model::prelude::Message};
|
||||||
@ -8,18 +8,20 @@ use crate::{
|
|||||||
tts::{
|
tts::{
|
||||||
instance::TTSInstance,
|
instance::TTSInstance,
|
||||||
message::TTSMessage,
|
message::TTSMessage,
|
||||||
|
tts_type::TTSType,
|
||||||
gcp_tts::structs::{
|
gcp_tts::structs::{
|
||||||
audio_config::AudioConfig, synthesis_input::SynthesisInput, synthesize_request::SynthesizeRequest
|
audio_config::AudioConfig, synthesis_input::SynthesisInput, synthesize_request::SynthesizeRequest
|
||||||
}, tts_type::{self, TTSType}
|
}, validator
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl TTSMessage for Message {
|
impl TTSMessage for Message {
|
||||||
async fn parse(&self, instance: &mut TTSInstance, _: &Context) -> String {
|
async fn parse(&self, instance: &mut TTSInstance, _: &Context) -> String {
|
||||||
|
let text = validator::remove_url(self.content.clone());
|
||||||
let res = if let Some(before_message) = &instance.before_message {
|
let res = if let Some(before_message) = &instance.before_message {
|
||||||
if before_message.author.id == self.author.id {
|
if before_message.author.id == self.author.id {
|
||||||
self.content.clone()
|
text.clone()
|
||||||
} else {
|
} else {
|
||||||
let member = self.member.clone();
|
let member = self.member.clone();
|
||||||
let name = if let Some(member) = member {
|
let name = if let Some(member) = member {
|
||||||
@ -27,7 +29,7 @@ impl TTSMessage for Message {
|
|||||||
} else {
|
} else {
|
||||||
self.author.name.clone()
|
self.author.name.clone()
|
||||||
};
|
};
|
||||||
format!("{} さんの発言<break time=\"200ms\"/>{}", name, self.content)
|
format!("{}さんの発言<break time=\"200ms\"/>{}", name, text)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let member = self.member.clone();
|
let member = self.member.clone();
|
||||||
@ -36,7 +38,7 @@ impl TTSMessage for Message {
|
|||||||
} else {
|
} else {
|
||||||
self.author.name.clone()
|
self.author.name.clone()
|
||||||
};
|
};
|
||||||
format!("{} さんの発言<break time=\"200ms\"/>{}", name, self.content)
|
format!("{}さんの発言<break time=\"200ms\"/>{}", name, text)
|
||||||
};
|
};
|
||||||
|
|
||||||
instance.before_message = Some(self.clone());
|
instance.before_message = Some(self.clone());
|
||||||
@ -80,8 +82,7 @@ impl TTSMessage for Message {
|
|||||||
|
|
||||||
let uuid = uuid::Uuid::new_v4().to_string();
|
let uuid = uuid::Uuid::new_v4().to_string();
|
||||||
|
|
||||||
let root = option_env!("CARGO_MANIFEST_DIR").unwrap();
|
let path = env::current_dir().unwrap();
|
||||||
let path = Path::new(root);
|
|
||||||
let file_path = path.join("audio").join(format!("{}.mp3", uuid));
|
let file_path = path.join("audio").join(format!("{}.mp3", uuid));
|
||||||
|
|
||||||
let mut file = File::create(file_path.clone()).unwrap();
|
let mut file = File::create(file_path.clone()).unwrap();
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod member_name;
|
pub mod member_name;
|
||||||
|
pub mod voice_move_state;
|
54
src/implement/voice_move_state.rs
Normal file
54
src/implement/voice_move_state.rs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
use serenity::model::{voice::VoiceState, prelude::ChannelId};
|
||||||
|
|
||||||
|
pub trait VoiceMoveStateTrait {
|
||||||
|
fn move_state(&self, old: &Option<VoiceState>, target_channel: ChannelId) -> VoiceMoveState;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum VoiceMoveState {
|
||||||
|
JOIN,
|
||||||
|
LEAVE,
|
||||||
|
NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VoiceMoveStateTrait for VoiceState {
|
||||||
|
fn move_state(&self, old: &Option<VoiceState>, target_channel: ChannelId) -> VoiceMoveState {
|
||||||
|
let new = self;
|
||||||
|
|
||||||
|
if let None = old.clone() {
|
||||||
|
return if target_channel == new.channel_id.unwrap() {
|
||||||
|
VoiceMoveState::JOIN
|
||||||
|
} else {
|
||||||
|
VoiceMoveState::NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let old = (*old).clone().unwrap();
|
||||||
|
|
||||||
|
match (old.channel_id, new.channel_id) {
|
||||||
|
(Some(old_channel_id), Some(new_channel_id)) => {
|
||||||
|
if old_channel_id == new_channel_id {
|
||||||
|
VoiceMoveState::NONE
|
||||||
|
} else if old_channel_id != new_channel_id {
|
||||||
|
if target_channel == new_channel_id {
|
||||||
|
VoiceMoveState::JOIN
|
||||||
|
} else {
|
||||||
|
VoiceMoveState::NONE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
VoiceMoveState::NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Some(old_channel_id), None) => {
|
||||||
|
if old_channel_id == target_channel {
|
||||||
|
VoiceMoveState::LEAVE
|
||||||
|
} else {
|
||||||
|
VoiceMoveState::NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
VoiceMoveState::NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
src/main.rs
19
src/main.rs
@ -1,3 +1,12 @@
|
|||||||
|
mod config;
|
||||||
|
mod event_handler;
|
||||||
|
mod tts;
|
||||||
|
mod implement;
|
||||||
|
mod data;
|
||||||
|
mod database;
|
||||||
|
mod events;
|
||||||
|
mod commands;
|
||||||
|
|
||||||
use std::{sync::Arc, collections::HashMap};
|
use std::{sync::Arc, collections::HashMap};
|
||||||
|
|
||||||
use config::Config;
|
use config::Config;
|
||||||
@ -12,13 +21,6 @@ use serenity::{
|
|||||||
|
|
||||||
use songbird::SerenityInit;
|
use songbird::SerenityInit;
|
||||||
|
|
||||||
mod config;
|
|
||||||
mod event_handler;
|
|
||||||
mod tts;
|
|
||||||
mod implement;
|
|
||||||
mod data;
|
|
||||||
mod database;
|
|
||||||
|
|
||||||
/// Create discord client
|
/// Create discord client
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
@ -61,8 +63,7 @@ async fn main() {
|
|||||||
|
|
||||||
let database_client = {
|
let database_client = {
|
||||||
let redis_client = redis::Client::open(config.redis_url).unwrap();
|
let redis_client = redis::Client::open(config.redis_url).unwrap();
|
||||||
let con = redis_client.get_connection().unwrap();
|
Database::new(redis_client)
|
||||||
Database::new(con)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create TTS storage
|
// Create TTS storage
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{path::Path, fs::File, io::Write};
|
use std::{fs::File, io::Write, env};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use serenity::prelude::Context;
|
use serenity::prelude::Context;
|
||||||
@ -34,7 +34,7 @@ pub struct AnnounceMessage {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl TTSMessage for AnnounceMessage {
|
impl TTSMessage for AnnounceMessage {
|
||||||
async fn parse(&self, instance: &mut TTSInstance, ctx: &Context) -> String {
|
async fn parse(&self, instance: &mut TTSInstance, _ctx: &Context) -> String {
|
||||||
instance.before_message = None;
|
instance.before_message = None;
|
||||||
format!(r#"<speak>アナウンス<break time="200ms"/>{}</speak>"#, self.message)
|
format!(r#"<speak>アナウンス<break time="200ms"/>{}</speak>"#, self.message)
|
||||||
}
|
}
|
||||||
@ -64,8 +64,7 @@ impl TTSMessage for AnnounceMessage {
|
|||||||
|
|
||||||
let uuid = uuid::Uuid::new_v4().to_string();
|
let uuid = uuid::Uuid::new_v4().to_string();
|
||||||
|
|
||||||
let root = option_env!("CARGO_MANIFEST_DIR").unwrap();
|
let path = env::current_dir().unwrap();
|
||||||
let path = Path::new(root);
|
|
||||||
let file_path = path.join("audio").join(format!("{}.mp3", uuid));
|
let file_path = path.join("audio").join(format!("{}.mp3", uuid));
|
||||||
|
|
||||||
let mut file = File::create(file_path.clone()).unwrap();
|
let mut file = File::create(file_path.clone()).unwrap();
|
||||||
|
@ -3,3 +3,4 @@ pub mod voicevox;
|
|||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod tts_type;
|
pub mod tts_type;
|
||||||
pub mod instance;
|
pub mod instance;
|
||||||
|
pub mod validator;
|
6
src/tts/validator.rs
Normal file
6
src/tts/validator.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
pub fn remove_url(text: String) -> String {
|
||||||
|
let url_regex = Regex::new(r"(http://|https://){1}[\w\.\-/:\#\?=\&;%\~\+]+").unwrap();
|
||||||
|
url_regex.replace_all(&text, " URL ").to_string()
|
||||||
|
}
|
@ -6,6 +6,32 @@ pub struct VOICEVOX {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VOICEVOX {
|
impl VOICEVOX {
|
||||||
|
pub fn get_speakers() -> Vec<(String, i64)> {
|
||||||
|
vec![
|
||||||
|
("四国めたん - ノーマル".to_string(), 2),
|
||||||
|
("四国めたん - あまあま".to_string(), 0),
|
||||||
|
("四国めたん - ツンツン".to_string(), 6),
|
||||||
|
("四国めたん - セクシー".to_string(), 4),
|
||||||
|
("ずんだもん - ノーマル".to_string(), 3),
|
||||||
|
("ずんだもん - あまあま".to_string(), 1),
|
||||||
|
("ずんだもん - ツンツン".to_string(), 7),
|
||||||
|
("ずんだもん - セクシー".to_string(), 5),
|
||||||
|
("春日部つむぎ - ノーマル".to_string(), 8),
|
||||||
|
("雨晴はう - ノーマル".to_string(), 10),
|
||||||
|
("波音リツ - ノーマル".to_string(), 9),
|
||||||
|
("玄野武宏 - ノーマル".to_string(), 11),
|
||||||
|
("白上虎太郎 - ノーマル".to_string(), 12),
|
||||||
|
("青山龍星 - ノーマル".to_string(), 13),
|
||||||
|
("冥鳴ひまり - ノーマル".to_string(), 14),
|
||||||
|
("九州そら - ノーマル".to_string(), 16),
|
||||||
|
("九州そら - あまあま".to_string(), 15),
|
||||||
|
("九州そら - ツンツン".to_string(), 18),
|
||||||
|
("九州そら - セクシー".to_string(), 17),
|
||||||
|
("九州そら - ささやき".to_string(), 19),
|
||||||
|
("モチノ・キョウコ - ノーマル".to_string(), 20),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(key: String) -> Self {
|
pub fn new(key: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
key
|
key
|
||||||
|
Reference in New Issue
Block a user