diff --git a/Cargo.toml b/Cargo.toml index f77f8fc..0afcb13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ alkana-rs = "0.1.0" whatlang = "0.16.3" isolang = { version = "2.3.0", features = ["list_languages"] } os_info = "3.7.0" +async-recursion = "1.0.5" # Windows Only [target.'cfg(windows)'.dependencies] diff --git a/conf.example-command.toml b/conf.example-command.toml new file mode 100644 index 0000000..c25c8fb --- /dev/null +++ b/conf.example-command.toml @@ -0,0 +1,83 @@ +# 音声から「かっこよくそれっぽいコマンド」を演出します。(必須ではありませんが心躍る方は参考にして下さい。) +[[processors]] +feature = "modify" +channel_from = "user" +channel_to = "system" +regex_files = ["regex.pre-command.txt"] + +# コマンドプロセッサーの使用例です。 +[[processors]] +feature = "command" +channel_from = "system" +# コマンド実行に対する応答を表示したり音声合成させたい場合は送信先チャンネルを設定します。 +channel_to = "ai" +# 応答メッセージをカスタマイズしたい場合は設定します。設定しない場合はデフォルトの応答メッセージが使用されます。 +response_mod = [ + [ + "disable", + "システムコマンドにより{A}モジュールは無効化された。", + ], + [ + "enable", + "システムコマンドにより{A}モジュールは有効化された。", + ], + [ + "reload", + "システムコマンドにより{A}モジュールは再読み込みされた。", + ], + [ + "set", + "了解した。セット{A}の実行を試みる。", + ], + [ + "set:error", + "セット{A}の実行中にエラーが発生している。エラーログを確認するといい。", + ], + [ + "_", + "コマンドまたは何かが違うようだ。", + ], +] + + # セットコマンド( /set <セット名> )を使用したい場合に設定します。 + [[processors.set]] + # /set で呼び出す の部分を設定します。お好みのセット名を設定できます。 + name = "init" + # この init セットが実行される際、直前に追加で実行したいセット群があれば設定します。 + pre = [] + # この init セットが実行される際、直後に追加で実行したいセット群があれば設定します。 + post = ["clear", "preparing"] + + # /set init コマンドで送出したいチャンネルとコンテントを設定します。こちらは1つめです。 + [[processors.set.channel_contents]] + channel = "title" + content = "Dr.USAGIとKal'tsit Pseudo先生ののんびりゲームお楽しみ配信" + # 1つのコマンドで複数のチャンネルとコンテントへ送出できます。こちらは2つめです。 + [[processors.set.channel_contents]] + channel = "description" + content = "配信支援アプリ Virtual Avatar Connect 一般公開中です👀 https://usagi.github.io/virtual-avatar-connect/" + + # /set clear コマンド + [[processors.set]] + name = "clear" + [[processors.set.channel_contents]] + channel = "user" + content = "" + [[processors.set.channel_contents]] + channel = "ai" + content = "" + + # /set preparing コマンド + [[processors.set]] + name = "preparing" + [[processors.set.channel_contents]] + channel = "brb" + content = "只今、配信準備中です。 now preparing for streaming." + [[processors.set.channel_contents]] + channel = "scene" + content = "overlay brb" + +# OS-TTS で ai チャンネルに出力されたコンテントを音声合成して読み上げます。 +[[processors]] +feature = "OS-TTS" +channel_from = "ai" diff --git a/regex.pre-command.txt b/regex.pre-command.txt index 5e40474..a0b0179 100644 --- a/regex.pre-command.txt +++ b/regex.pre-command.txt @@ -1,5 +1,7 @@ -/quit ^\s*システムコマンド\s*シャットダウン -/reload ^\s*システムコマンド\s*リロード -/disable ^\s*システムコマンド\s*ディゼ[イー]ブル -/enable ^\s*システムコマンド\s*イネ[イー]ブル -/? ^\s*システムコマンド +/quit ^\s*システムコマンド\s*シャットダウン.* +/reload ^\s*システムコマンド\s*リロード.* +/disable ^\s*システムコマンド\s*ディゼ[イー]ブル.* +/enable ^\s*システムコマンド\s*イネ[イー]ブル.* +/set init ^\s*システムコマンド\s*セット\s*(イニット|イニシャライズ).* +/set clear ^\s*システムコマンド\s*セット\s*クリア.* +/? ^\s*システムコマンド.* diff --git a/src/conf/processor_conf.rs b/src/conf/processor_conf.rs index e97467b..31dda75 100644 --- a/src/conf/processor_conf.rs +++ b/src/conf/processor_conf.rs @@ -3,6 +3,23 @@ use serde::{Deserialize, Serialize}; pub type SharedProcessorConf = Arc>; +#[derive(Debug, Deserialize, Serialize, Clone, Default)] +pub struct ContentWithChannel { + pub channel: String, + pub content: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone, Default)] +pub struct CommandSet { + pub name: String, + #[serde(default)] + pub pre: Vec, + #[serde(default)] + pub post: Vec, + #[serde(default)] + pub channel_contents: Vec, +} + #[derive(Debug, Deserialize, Serialize, Clone, Default)] pub struct ProcessorConf { // Common @@ -22,6 +39,8 @@ pub struct ProcessorConf { pub through_if_not_command: Option, #[serde(default)] pub response_mod: Vec>, + #[serde(default)] + pub set: Vec, // screenshot pub title: Option, diff --git a/src/processor/command.rs b/src/processor/command.rs index 1397c93..c1091d8 100644 --- a/src/processor/command.rs +++ b/src/processor/command.rs @@ -1,4 +1,5 @@ use super::{CompletedAnd, Processor}; +use crate::conf::CommandSet; use crate::{ChannelDatum, ProcessorConf, ProcessorKind, SharedChannelData, SharedProcessorConf, SharedState}; use anyhow::{bail, Result}; use async_trait::async_trait; @@ -82,8 +83,23 @@ impl Processor for Command { ) .await; }, + "set" if args.len() >= 1 => { + log::info!("set がコマンドされセット名 {:?} の実行が試行されます。", args[0]); + response1(conf.clone(), self.state.clone(), "set", "セット {A} の実行を試みます。", args[0].clone()).await; + if let Err(e) = activate_command_set(args[0], &conf.set, self.state.clone()).await { + log::error!("セットの実行中にエラーが発生しました: {:?}", e); + response1( + conf, + self.state.clone(), + "set:error", + "セット {A} の実行中にエラーが発生しました。", + args[0].clone(), + ) + .await; + } + }, _ => { - log::info!("コマンドまたは何かが違うようです。"); + log::warn!("コマンドまたは何かが違うようです: command = {:?} args = {:?}", command, args); response0(conf, self.state.clone(), "_", "コマンドまたは何かが違うようです。").await; }, } @@ -173,3 +189,33 @@ async fn response1(conf: ProcessorConf, state: SharedState, command: &str, defau let state = state.read().await; state.push_channel_datum(cd).await; } + +#[async_recursion::async_recursion] +async fn activate_command_set(set_name: &str, command_sets: &Vec, state: SharedState) -> Result<()> { + // find + let command = command_sets + .iter() + .find(|&c| c.name == set_name) + .ok_or_else(|| anyhow::anyhow!("セット名 {:?} が見つかりませんでした。", set_name))?; + + for pre in &command.pre { + if let Err(e) = activate_command_set(&pre, command_sets, state.clone()).await { + log::error!("pre 処理でエラーが発生しました: {:?}", e); + } + } + + for cc in &command.channel_contents { + // log::warn!("channel = {:?} に content = {:?} を送信します。", cc.channel, cc.content) + let cd = ChannelDatum::new(cc.channel.clone(), cc.content.clone()).with_flag(ChannelDatum::FLAG_IS_FINAL); + let state = state.read().await; + state.push_channel_datum(cd).await; + } + + for post in &command.post { + if let Err(e) = activate_command_set(&post, command_sets, state.clone()).await { + log::error!("post 処理でエラーが発生しました: {:?}", e); + } + } + + Ok(()) +}