# Outvoke [![licensed CC0 1.0](https://img.shields.io/badge/licensed-CC0%201.0-44B987.svg?logo=unlicense&logoColor=white)](https://creativecommons.org/publicdomain/zero/1.0/deed.ja) Outvokeはライブラリであると同時にフレームワークです。そして同時に内部DSLです。 If you need English Edition of this README, see [README.md](README.md) ## 注意 作者であるcleemy desu wayoは、Linuxでしか動作確認していません。 最初の公式リリースはバージョン0.1になる予定です。おそらく2026年リリースです。バージョン0.1はコンセプトを示すことが優先なので、速度面は期待しないでください。 2025年夏時点で利用可能となっているバージョン 0.0.99.x については、実験的なものです。 バージョン 0.1 がリリースされるまで、予告なく仕様変更されたり、突然機能が削除されたりする可能性があります。 ## システム要件 - Ruby 3.0 or later ## 概要 Outvokeを使用すれば、流れていくデータストリームをフックするようなコードが容易に書けます。 Outvokeは内部DSLなので、Rubyの機能はほぼすべて利用可能です。 OutvokeはRubyの内部DSLであり、基本的にはRubyでコードを書きたい人のためのものです。ただし、Rubyの言語的詳細に立ち入らなくても実用的な活用ができるような場面があるかもしれません。 Outvokeに部分的に「DSL内DSL」とでもいえるような箇所があったとしても、基本的にOutvokeが独自に構文解析などを行っているわけではありません。Outvokeを利用したコードにおいて、Rubyっぽく見えるコードは単にRubyのコードです。内部DSLとしての性質のおかげでRubyっぽく見えない場面があったりするかもしれませんが、内部DSLとはそういうものです。 Outvokeはライブラリとしても単体のコマンドとしても機能します。このREADMEには、単体のコマンドとして利用した時のサンプルコード(ワンライナー)がたくさん掲載されています。 設計思想に関することはこのREADMEには書かれていません。 2025年6月公開の日本語で書かれた以下の記事が、2025年7月時点では作者自身による唯一のまとまった解説となっていますが、機能についてはごく一部しか触れていません。 - Outvokeのことを、なんと説明すればいいだろう|cleemy desu wayo(2025-06-28) https://note.com/cleemy/n/n01ae9d0583a7 バージョン 0.1 がリリースされるまでは、Outvoke本体([outvoke.rb](outvoke.rb))のコード(コメント行を除く)やテストコードやサンプルコードを書く際には、AIを使用せずに書く予定です。ただし、このREADMEのようなものやコード中のコメントを含めて、日本語の文章を英語に翻訳する時にはAIを使用しています。 リポジトリ内の [samples/](samples/) 以下にあるものは、あくまでもサンプルコードです。 ## セットアップ カレントディレクトリに [outvoke.rb](https://gitlab.com/cleemy-desu-wayo/outvoke/-/raw/main/outvoke.rb) を置き、実行権限を与えてください。 以下のように `-e` オプションを使用して起動してください。 ``` $ ./outvoke.rb -e hooksec ``` 1秒に1回新しい出力があるなら、とりあえず起動はできているということになります。 終了は Ctrl + C で行います。 ## オプション |オプション |詳細| |-------------------------------------------|---| |-h, --help |オプション一覧を出力(optparseが提供する機能)| |-v, --version |バージョン情報を出力(`-q` と組み合わせるとショート版)| |-e CODE |ワンライナーでの利用| |-r LIBRARY |Rubyのライブラリをロード| |-q, --quiet |クワイエットモード| |-w, --wait INTERVAL |Outvokeのメインループのウェイト(デフォルトは `0.5`)| |-p, --preset,
--output-mode OUTPUT_MODE |"err" 以外のすべてのデータソースの出力プリセットを上書き指定| |-V, --var VAR:VALUE |例えば `--var=str:hello` と指定した場合、`OUTVOKE_VAR["str"]` を `"hello"` にセット| -V/--var の仕様は [version 0.0.99.20250816.2](https://gitlab.com/cleemy-desu-wayo/outvoke/-/blob/f3be1cfcc21e80e5ef9f271b1140ddfd3899a5b3/outvoke.rb) (f3be1cfc)で変わったことに注意してください。 環境変数とバッティングした場合、オプションが優先されます。 ## 環境変数 |変数名 |詳細| |-----------------------|---| |OUTVOKE_OPT |Outvokeのデフォルトのオプション| |OUTVOKE_QUIET_MODE |クワイエットモード(値が `1` ならオン)| |OUTVOKE_WAIT |Outvokeのメインループのウェイト(デフォルトは `0.5`)| |OUTVOKE_OUTPUT_MODE |"err" 以外のすべてのデータソースの出力プリセットを上書き指定| |OUTVOKE_VAR_* |例えば環境変数 `OUTVOKE_VAR_STR` の値が `"hello"` なら、Rubyのコードでの `OUTVOKE_VAR["str"]` が `"hello"` にセットされる| これらはコマンドとしてOutvokeを起動した時にのみ反映されます。 OUTVOKE_VAR_* の仕様は [version 0.0.99.20250816.2](https://gitlab.com/cleemy-desu-wayo/outvoke/-/blob/f3be1cfcc21e80e5ef9f271b1140ddfd3899a5b3/outvoke.rb) (f3be1cfc)で変わったことに注意してください。 OutvokeはRubyの内部DSLなので、`RUBYOPT` のようなものも反映されます。ただし、Outvokeのオプションを `RUBYOPT` に書いても無意味です。そういう目的には、`OUTVOKE_OPT` を使用してください。 ## データソース Outvokeにおけるデータソースとは、入力ストリームを抽象化したものです。必ずしも大元のデータが実際に存在しているとは限りませんが、そういう詳細を気にせずに共通のアプローチを通じて簡単にアクセスできるようにするのがデータソースです。 |データソース名|詳細| |--------------|---| |io |任意のIOオブジェクトを格納可能| |stdin |標準入力を受け取る| |lo |ループバック(外部からの受信はしない)| |err |ループバック、エラー情報集約用(外部からの受信はしない)| |every-sec |1秒に1回、日時の情報を文字列で受け取る| |osc-001 |OSCメッセージの受信(デフォルトのポートは `9001`)| |vrchat-001 |VRChatクライアントのログファイルを監視| 他にも `web-001` や `auxin:fifo:1` などがありますが、これらは実験的なものなので注意。 以下のようなワンライナーの場合、"stdin" の部分がデータソースです。 ``` $ seq 1 40 | ./outvoke.rb -e 'hook("stdin", /2/) { "matched data: #{e}" }' ``` 前述の表はビルトイン(組み込み)のデータソース一覧です。`OutvokeDataSource` のサブクラスを定義することにより、ニーズに合わせた独自のデータソースをご自身でご用意していただくことも可能です。 VRChat関連でビルトインのデータソース "osc-001" を利用しようとすると、[osc-ruby](https://github.com/aberant/osc-ruby) 1.1.5 が OSC Type Tag String の "T" (true値)や "F" (false値)をサポートしていないことがネックになるかもしれません。そういう場合、[このモンキーパッチ](https://gitlab.com/cleemy-desu-wayo/outvoke/-/snippets/4875923) が助けになるかもしれません。ちなみに [OSC 1.0](https://opensoundcontrol.stanford.edu/spec-1_0.html) 的には Type Tag "T" や "F" は「nonstandard」の扱いです。 ## 出力プリセット Outvokeにおける各データソースは、`hook` によって登録された `Proc` が返した値(`hook_result`)を処理するための `post_procs` を持っています。この `post_procs` はただの配列で、`<<` によって手動で `Proc` を登録することもできますが、ビルトイン(組み込み)で便利なプリセットがあらかじめ用意されています。それが出力プリセット(output preset)です。 |プリセット名 |詳細| |------------|---| |terminal |多くのデータソースでのデフォルト。標準出力に以下のような形式で出力:
`[2025-08-12 04:45:48 +0900] hook_result body`| |warn |データソース "err" でのデフォルト。`Kernel.#warn` を使用して以下のような形式で出力:
`[2025-08-12 04:45:48 +0900] hook_result body`| |nop |何もしない(no operation)| |jsonl |標準出力にJSONL形式で出力| |yaml |標準出力にYAMLストリームで出力| |osc |OSCメッセージの送信(デフォルトのポートは `9000`)| 以下のようなワンライナーの場合、データソース `"every-sec"` の出力プリセットを `"jsonl"` に設定しています。 ``` $ ./outvoke.rb -e '$outvoke["every-sec"].preset = "jsonl" ; hook("every-sec") { {"sec" => e.sec} }' ``` `$outvoke.preset = ` のように代入することによって、"err" 以外のすべてのデータソースの出力プリセットを一斉に変更することもできます([version 0.0.99.20250817.4](https://gitlab.com/cleemy-desu-wayo/outvoke/-/blob/7f55b93f2a477c373aface25b02f42286c0d9ed7/outvoke.rb) より)。 ## 設定ファイル カレントディレクトリに `outvoke.conf.rb` というファイルがあると、Outvokeはまずそのファイルをインクルードします。 例えば `outvoke.conf.rb` に以下のような行があれば、VRChatのログファイルを探すディレクトリを指定することができます。 ``` $outvoke["vrchat-001"].log_dir = "#{Dir.home}/.steam/debian-installation/steamapps/compatdata/438100/pfx/drive_c/users/steamuser/AppData/LocalLow/VRChat/VRChat" ``` [version 0.0.99.20241125](https://gitlab.com/cleemy-desu-wayo/outvoke/-/blob/a111518f7c74bbf39d304189f108a089458c276e/outvoke.rb) およびそれ以前では、以下のように指定する必要がありました。 ``` $outvoke.ds["vrchat-001"].log_dir = "..." ``` もっと古いバージョンでは、以下のように指定する必要がありました。 ``` $outvoke.sources["vrchat-001"].log_dir = "..." ``` ## 基本的な使い方 引数なしでOutvoke本体を起動すると、カレントディレクトリの `main.rb` を探してインクルードします。 以下のようにファイル名を指定して実行すれば、カレントディレクトリの `test.rb` を探してインクルードします。 ``` $ ./outvoke.rb test.rb ``` `-e` オプションによってワンライナーを書くこともできます。 話を分かりやすくするためにこのセクションでは `main.rb` に書いていくものとします。 この `main.rb` の先頭で `using OutvokeDSL` としておくと、内部DSLのモードになります。内部DSLのモードでは、設定ファイルを書くような気軽さで記述できます。 `main.rb` はRubyのスクリプトとして解釈されますので、複雑なプログラムを書くこともできます。 以下は `main.rb` の例です。 ``` using OutvokeDSL hook 'every-sec', /22:08:00/ ``` `'every-sec'` の箇所はデータソースの名前の指定です。`/22:08:00/` の箇所は、条件の指定です。 Outvokeを実行したあと放置し、22時8分になると以下のような行が出力されるはずです。 ``` [2024-11-03 22:08:00 +0900] 2024-11-03 22:08:00 +0900 ``` ご自身の環境に合わせて、`/22:08:00/` の部分を変えてみてください。 3行目の `hook` のある行については、以下のように書くのと同じです。 ``` hook 'every-sec', /22:08:00/ do |e| e.body end ``` 別の例を示します。 ``` using OutvokeDSL hook 'every-sec', /05:00:00/ do spawn 'play', '-q', '-t', 'wav', '-v', '0.6', 'ring.wav', 'repeat', '50' end ``` 午前5時になるとカレントディレクトリの `ring.wav` を `play` コマンドによって再生します。 複数のデータソースを扱う例を以下に示します。 ``` using OutvokeDSL hook 'every-sec', /..:..:.[02468]/ do |e| "every-sec: #{e.body}" end hook 'stdin' do |e| "stdin: #{e.body}" end ``` 上記の `main.rb` を用意した上で、以下のように実行します。 ``` $ { sleep 5 ; echo aaa ; sleep 5 ; echo bbb ; sleep 5 ; echo ccc ; } | ./outvoke.rb ``` すると、以下のような出力になるはずです。 ``` # starting Outvoke 0.1 (version 0.0.99.20240928) ---- 2024-11-03 16:06:22 +0900 # ---- # loading ./main.rb ... # ---- [2024-11-03 16:06:22 +0900] [outvoke-system] vrchat-001: a new log file was found. [2024-11-03 16:06:22 +0900] [outvoke-system] vrchat-001: first time log check has done. [2024-11-03 16:06:24 +0900] every-sec: 2024-11-03 16:06:24 +0900 [2024-11-03 16:06:26 +0900] every-sec: 2024-11-03 16:06:26 +0900 [2024-11-03 16:06:27 +0900] stdin: aaa [2024-11-03 16:06:28 +0900] every-sec: 2024-11-03 16:06:28 +0900 [2024-11-03 16:06:30 +0900] every-sec: 2024-11-03 16:06:30 +0900 [2024-11-03 16:06:32 +0900] stdin: bbb [2024-11-03 16:06:32 +0900] every-sec: 2024-11-03 16:06:32 +0900 [2024-11-03 16:06:34 +0900] every-sec: 2024-11-03 16:06:34 +0900 [2024-11-03 16:06:36 +0900] every-sec: 2024-11-03 16:06:36 +0900 [2024-11-03 16:06:37 +0900] stdin: ccc [2024-11-03 16:06:38 +0900] every-sec: 2024-11-03 16:06:38 +0900 [2024-11-03 16:06:40 +0900] every-sec: 2024-11-03 16:06:40 +0900 [2024-11-03 16:06:42 +0900] every-sec: 2024-11-03 16:06:42 +0900 [2024-11-03 16:06:44 +0900] every-sec: 2024-11-03 16:06:44 +0900 [2024-11-03 16:06:46 +0900] every-sec: 2024-11-03 16:06:46 +0900 [2024-11-03 16:06:48 +0900] every-sec: 2024-11-03 16:06:48 +0900 ``` この例では、入力ストリームが複数ある `grep` のようなものと考えると、イメージしやすいと思います。 ちなみに、`-q` オプションによって `grep` のように機能させることは可能です。 ``` $ seq 10 20 | grep 2 12 20 $ seq 10 20 | ./outvoke.rb -q -e 'hook "stdin", /2/' 12 20 ``` ## VRChatでの利用 Outvokeでは、`every-sec` や `stdin` 以外にも、データソース `vrchat-001` がビルトイン(組み込み)で用意されています。 データソース `vrchat-001` では、VRChatクライアントが出力するログファイルを監視し、入力ストリームであるかのように取り扱います。このデータソースはVRChatクライアントが出力するログファイルを見ているだけなので、普通に使用するだけならチート行為には該当しません。 VRChatがインストールされていて、かつVRChatが起動されてから長い時間が経過しているような場合、Outvoke を起動してから `first time log check has done.` の行が出るまでに長い時間がかかる場合があります。VRChatを再起動すれば、この時間は短縮できます。 以下のサンプルもご覧ください。 - [samples/vrchat_join_log2.rb](samples/vrchat_join_log2.rb) (入退室履歴) - [samples/vrchat_join_log3.rb](samples/vrchat_join_log3.rb) (入退室履歴、ユーザーIDも記録) - [samples/2024/vrchat_waittest1.rb](samples/2024/vrchat_waittest1.rb) (pickupとループのウェイトの変更) - [samples/2024/vrchat_waittest2.rb](samples/2024/vrchat_waittest2.rb) (pickupとループのウェイトの変更) - [samples/2024/vrchat_get_log_file_name.rb](samples/2024/vrchat_get_log_file_name.rb) (e.status の使い方) - [samples/2024/vrchat_multiple_ds1.rb](samples/2024/vrchat_multiple_ds1.rb) (複数のデータソースを扱う) - [samples/2024/vrchat_multiple_ds2.rb](samples/2024/vrchat_multiple_ds2.rb) (独自のデータソースを書く) - [samples/2024/vrchat_loputs.rb](samples/2024/vrchat_loputs.rb) (lo および loputs の基本的な使い方) - [samples/2024/webrick_vrchat_instanceinfo.rb](samples/2024/webrick_vrchat_instanceinfo.rb) (今いるインスタンスの情報をJSONで配信) - [samples/2024/webrick_vrchat_instanceinfo.html](samples/2024/webrick_vrchat_instanceinfo.html) (SPA、動かすまでの手順については[こちら](https://cleemy-desu-wayo.gitlab.io/ov/docs/about_webrick_vrchat_instanceinfo.html)) Outvokeを動作させている最中の動画や画像 - [samples/2025/vrchat_osc_send2.rb](samples/2025/vrchat_osc_send2.rb) およびいくつかのOSC送信ワンライナーを試す動画: https://x.com/metanagi/status/1944847364961001921 - [samples/2024/vrchat_multiple_ds2.rb](samples/2024/vrchat_multiple_ds2.rb) を動作させている動画: https://x.com/metanagi/status/1802512101111689368 - [samples/2024/webrick_vrchat_instanceinfo.html](samples/2024/webrick_vrchat_instanceinfo.html) を動作させている時のスクショ: https://cleemy-desu-wayo.gitlab.io/ov/docs/about_webrick_vrchat_instanceinfo_img001.png いくつかの解説やサンプルについては、以下で見つかるかもしれません。 - https://x.com/hashtag/outvoke - [cdwact-2023-12](2023年12月活動報告) https://note.com/cleemy/n/nf9ea83c0c5e3 - [cdwact-2024-01](2024年1月活動報告) https://note.com/cleemy/n/n4ceff128e355 - [cdwact-2024-04](2024年4月活動報告) https://note.com/cleemy/n/nf7cce0493fc0 (以下執筆中) ## ワンライナーのサンプル集 ワンライナーの場合も、基本的にRubyの機能はすべて使えます。 このREADMEで提示するサンプルでは、音を鳴らすものがあります。実行する前に、以下の準備が必要です。 1. カレントディレクトリに [outvoke.rb](outvoke.rb) を用意 2. カレントディレクトリに `maoudamashii-se-system47.wav` を用意 ( https://maou.audio/se_system47/ から wavファイルをダウンロード) 3. play コマンドによって音が鳴るかどうか確認 ``` $ play maoudamashii-se-system47.wav ``` 4. 以下のような内容の `outvoke.conf.rb` をカレントディレクトリに用意 ``` def ring(vol = "0.05") spawn "play", "-v", vol.to_s, "-q", "maoudamashii-se-system47.wav", :err=>"/dev/null" nil end ``` 5. 以下のワンライナーで1秒に1回音が鳴るかどうか確認 ``` $ ./outvoke.rb -e 'hook "every-sec", /./ do ring ; end' ``` [version 0.0.99.20240818](https://gitlab.com/cleemy-desu-wayo/outvoke/-/blob/c3a57d9660e80617bfe2b28099c6f8ad22bb0cfe/outvoke.rb) およびそれ以降では、以下のような書き方も可能: ``` $ ./outvoke.rb -e 'hook "every-sec" do ring ; end' ``` あるいは以下のように書くことも可能: ``` $ ./outvoke.rb -e 'hook("every-sec"){ring}' ``` [version 0.0.99.20241110](https://gitlab.com/cleemy-desu-wayo/outvoke/-/blob/c61c4312f72d9e745a3dfb8704783dda2021a854/outvoke.rb) およびそれ以降では、以下のように書くことも可能: ``` $ ./outvoke.rb -e 'hooksec{ring}' ``` ### これらのワンライナーにおける注意点 ここに掲載したワンライナーでは、`_1` (numbered parameter) を多用しています。 ``` $ ./outvoke.rb -e 'hookvr(/pickup object/i){puts _1.body}' ``` 上記は、以下と同じです。 ``` $ ./outvoke.rb -e 'hookvr(/pickup object/i){|e| puts e.body}' ``` 2024年11月現在、`hook "vrchat-001"` は `hookvr` と書くのと同じですが、あえて `hookvr` は使用していません。 ### 少し複雑な目覚まし時計ワンライナー さて、ここからワンライナーの例を多数紹介していきます。 AM 5:00 から AM 05:10 までの間、10秒に3回音を鳴らす: ``` $ ./outvoke.rb -e 'hook("every-sec", / 05:0[0-9]:[0-5][036]/){ring}' ``` AM 05:00 から AM 05:55 までの間、5分に1回動画を再生: ``` $ ./outvoke.rb -e 'hook("every-sec", / 05:[0-5][05]:00/) {spawn "mpv", "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "--really-quiet", :err=>"/dev/null"}' ``` 22:48になったら「蛍の光」のループ再生を開始、23:05に終了: ``` $ ./outvoke.rb -e 'hook("every-sec", / 22:48:00/) {spawn "mpv", "https://www.youtube.com/watch?v=OgYWssWn7uQ", "--really-quiet", "--loop", :err=>"/dev/null"} ; hook("every-sec", / 23:05:00/) {spawn "pkill", "mpv", :err=>"/dev/null"}' ``` 「蛍の光」ループ再生、終了時刻を指定するのではなく17分後に終了するバージョン: ``` $ ./outvoke.rb -e 'hook("every-sec", / 22:48:00/) {spawn "mpv", "https://www.youtube.com/watch?v=OgYWssWn7uQ", "--really-quiet", "--loop", :err=>"/dev/null" ; sleep 60 * 17 ; spawn "pkill", "mpv", :err=>"/dev/null"}' ``` これらの例では、`pkill mpv` によりmpvはすべて終了されることに注意してください。 `Kernel.#spawn` はPIDを返します。PIDを記憶しておいて、ピンポイントで終了することも可能です: ``` $ ./outvoke.rb -e 'mpv_pid = nil; hook("every-sec", / 22:48:00/) {mpv_pid = spawn "mpv", "https://www.youtube.com/watch?v=OgYWssWn7uQ", "--really-quiet", "--loop", :err=>"/dev/null"} ; hook("every-sec", / 23:05:00/) {spawn "kill", mpv_pid.to_s, :err=>"/dev/null" if mpv_pid}' ``` 「蛍の光」ループ再生、死活監視バージョン: ``` $ ./outvoke.rb -e 'is_music_on = false; hook("every-sec", / 22:48:00/) {is_music_on = true} ; hook("every-sec") {next if (not is_music_on) || IO.popen( "ps aux | grep [O]gYWssWn7uQ").to_a.length != 0 ; spawn "mpv", "https://www.youtube.com/watch?v=O" + "gYWssWn7uQ", "--really-quiet", "--loop", :err=>"/dev/null" ; sleep 0.5 } ; hook("every-sec", / 23:05:00/) {is_music_on = false; spawn "pkill", "mpv", :err=>"/dev/null"}' ``` 上記の例では、22:48になったらフラグを立てるだけで、あとは1秒おきに死活監視してmpvを起動します。間違ってmpvを終了してしまっても、すぐにまた再生が開始されます。 ワンライナーの欠点は、ソースコード全体が `ps aux` の出力に含まれてしまうことです。`ps aux` によるプロセスの死活監視には泥臭い工夫が必要になるかもしれません。これぐらい複雑な場合、ワンライナーではなくファイルを用意したほうがいいでしょう。 これに似たことをするサンプル [samples/2024/loputs_hotaru_no_hikari.rb](samples/2024/loputs_hotaru_no_hikari.rb) もご覧ください。これは `Process.#detach` や `Process.#kill` を使用しています。 さて、ここで曜日について考えてみます。 AM 05:00 ちょうどに音を鳴らす、ただし月曜から金曜の間だけ: ``` $ ./outvoke.rb -e 'hook("every-sec", / 05:00:00/){ring if (1..5).include?(Time.now.wday)}' ``` `Time.now` の代わりに、`_1.status.now` も使用可能: ``` $ ./outvoke.rb -e 'hook("every-sec", / 05:00:00/){ring if (1..5).include?(_1.status.now.wday)}' ``` この例ではどちらでもかまいませんが、`Time.now` は実行するたびに現在時刻を取得しようとするため、日付が次の日になる可能性があることに注意する必要があります。 Rubyの一般的な話題として、include? と曜日は相性が良いといえます。例えば月・水・金のいずれかに該当するかどうかは以下のように書けます。 ``` [1,3,5].include?(Time.now.wday) ``` ### シンプルなVRChat関連ワンライナー ここからは、VRChatに関連したサンプルを紹介していきます。 誰かがjoinしてきたら音を鳴らす: ``` $ ./outvoke.rb -e 'hook("vrchat-001", /onplayerjoincomplete/i){ring}' ``` `/onplayerjoined/i` は推奨できません。 何かオブジェクトをpickupした時に音を鳴らす: ``` $ ./outvoke.rb -e 'hook("vrchat-001", /pickup object/i){ring}' ``` 動画関連のログをとりあえず全部表示: ``` $ ./outvoke.rb -e 'hook "vrchat-001", /(resolv|video)/i' ``` "resolv" と書いておけば、"resolve" と "resolving" の両方にマッチします。 TopazChat関連のログをとりあえず全部表示: ``` $ ./outvoke.rb -e 'hook "vrchat-001", /(rtsp|topaz)/i' ``` ### 少し複雑なVRChat関連ワンライナー 誰かがjoinしてきたら音を鳴らす、ただし自分がjoinしてから90秒間は音を鳴らさない: ``` $ ./outvoke.rb -e 'hook("vrchat-001", /onplayerjoincomplete/i) {ring if _1.status.elapsed > 90}' ``` `_1.status.elapsed` で、自分がjoinしてからの経過時間が取得できます。 これにより、人が大量にいるインスタンスに自分がjoinした直後に、不必要に音が鳴るのを防ぐことができます。 音が鳴らない時も文字列の出力は欲しいという場合: ``` $ ./outvoke.rb -e 'hook("vrchat-001", /onplayerjoincomplete/i) {ring if _1.status.elapsed > 90 ; _1.body}' ``` 出力したいのが `_1.body` なら、実際は `true` でOK: ``` $ ./outvoke.rb -e 'hook("vrchat-001", /onplayerjoincomplete/i) {ring if _1.status.elapsed > 90 ; true}' ``` `true` ではなく `1` などにするのは推奨できません。Outvokeの仕様変更によって将来的に違う動作になる可能性があります。(※コミット 04be13b4 で実際にそうなりました) ワールド内で動画再生が開始されるたびに、mpvでその動画を再生: ``` $ ./outvoke.rb -e 'hook("vrchat-001", /video playback.*resolve url..(https[-_.a-zA-Z0-9&=?%:\/]*)/i) {spawn "mpv", _1.m[1], "--really-quiet", :err=>"/dev/null"; _1.m[1]}' ``` ワールド内の動画プレイヤーでエラーが起こると、リトライのせいで複数回mpvが起動する場合があります。 `Kernel.#spawn` に渡す引数を動的に変更する場合、セキュリティ上のリスクに注意を払う必要があります。OSのコマンドライン全体を単一の文字列として渡すのは避けてください。 ``` $ ruby -e 's = "aaa;date" ; spawn "echo", s' aaa;date $ ruby -e 's = "aaa;date" ; spawn "echo #{s}"' aaa 2024年 8月 24日 土曜日 18:29:43 JST $ ruby -e 's = "aaa\";date;#" ; spawn "echo \"#{s}\""' aaa 2024年 8月 24日 土曜日 18:29:48 JST ``` ワールド内で動画再生が開始されるたびに、URLとタイトルを出力: ``` $ ./outvoke.rb -e 'hookcc("vrchat-001", /video playback.*resolve url..(https[-_.a-zA-Z0-9&=?%:\/]*)/i) {title = IO.popen(["./yt-dlp_linux", "-q", "--get-title", _1.m[1], :err=>"/dev/null"]).each_line.first; loputs "#{_1.m[1]} -- #{title}"} ; hooklo' ``` `hookcc` やマルチスレッドについては、[samples/2024/vrchat_loputs.rb](samples/2024/vrchat_loputs.rb) および [samples/2024/vrchat_loputs_hookcc.rb](samples/2024/vrchat_loputs_hookcc.rb) をご覧ください。 `yt-dlp_linux` というのは、yt-dlp のLinux版バイナリです。https://github.com/yt-dlp/yt-dlp/releases/ から最新版を入手可能です。 yt-dlp によるタイトル取得にどれくらいの時間がかかっているかを知りたい時は、以下のようにブロックの冒頭でも `loputs` を実行するといいかもしれません: ``` $ ./outvoke.rb -e 'hookcc("vrchat-001", /video playback.*resolve url..(https[-_.a-zA-Z0-9&=?%:\/]*)/i) {loputs "#{_1.m[1]} -- hookcc start" ; title = IO.popen(["./yt-dlp_linux", "-q", "--get-title", _1.m[1], :err=>"/dev/null"]).each_line.first; loputs "#{_1.m[1]} -- #{title}"} ; hooklo' ``` `IO.popen` に渡す引数を動的に変更するのもセキュリティリスクがあります。OSのコマンドライン全体を単一の文字列として渡すのは避けてください。また、セキュリティ上の理由により、Kernel.#` や `%x` を使うのは慎重になったほうがいいでしょう。 (まだまだサンプルを追加予定) ## teeコマンドについて Outvokeと直接関係はありませんが、ターミナルに文字列を出力しつつそれを記録に残したいという場合、teeコマンドと組み合わせると便利です。 ``` $ ./outvoke.rb vrchat_join_log3.rb | tee join_history_$(date "+%Y-%m-%d_%H-%M-%S").txt ``` もちろんワンライナーのモードの時でも使えます。 ``` $ ./outvoke.rb -e 'hookcc("vrchat-001", /video playback.*resolve url..(https[-_.a-zA-Z0-9&=?%:\/]*)/i) {title = IO.popen(["./yt-dlp_linux", "-q", "--get-title", _1.m[1], :err=>"/dev/null"]).each_line.first; loputs "#{_1.m[1]} -- #{title}"} ; hooklo' | tee video_history_$(date "+%Y-%m-%d_%H-%M-%S").txt ``` ## mpvのオプションについて Outvokeからmpvを起動する上で、知っておくと便利なmpvのオプションは例えば以下です。 - `--fullscreen` (全画面) - `--geometry=` (ウィンドウ位置、左上なら --geometry=0:0) - `--loop` (ループ再生) - `--no-audio` (映像のみ) - `--no-video` (音のみ) - `--no-keepaspect` (縦横比を維持しない) - `--no-osc` (GUI無効、このOSCは「On Screen Controller」であって「OpenSound Control」ではない) - `--really-quiet` (余計な出力を抑制) - `--volume=` (音量指定、100が100%) mpvである必要はなく、VLC media player やコマンドとしての `firefox`、`chromium`、`xdg-open` なども便利です。