VRChatには、Join戦争というものがあります。そして人気イベントのインスタンスは、開いた瞬間に埋まります。
開始時刻までにグループの画面を何度も開き直し、インスタンスが出ていないか注意深く見て、出てたらすぐさま飛び込む。
ちょっとでも出遅れて満員になっていたなら、また次の機会を待つしかありません。
私も縁があって?か、そういうイベントに通うようになり、この戦争を経験するようになりました。
手持ちの武器は、自作erゆえの良さげなスペックのパソコンと、今ではお手頃な10Gbpsのインターネット回線。
それと、当日までに高めていく「Joinしたい」という熱意です。
熱意のおかげか、はたまたタイミングが良いだけか、負けることはあまりない気はします。ただ、心拍数がぶち上がるので健康には良くない。
で、一時期は毎週あるいは隔週でこの戦争に挑んでいたので、ふと思ってしまったわけです。これ、もう少し良い方法はないものか?と。
この記事は、そのふとした疑問から始まって、Windows向けのアプリを作り、右往左往の末に公開してみるところまでの話です。
今回作ったアプリケーションは「OnCue」という名前で、日本語では「ちょうどいいタイミングで」みたいなニュアンスです。
本ツールはVRChat非公式です。VRChat Inc.とは関係がなく、利用は自己責任です。
記事中の画面イメージの情報は、すべてテスト用のサンプルです。実在するイベントとの関連はありません。
「何か良い方法ないですか?」から始まった
発端は、ChatGPTに投げた一言でした。味もそっけもない「何か良い方法ないですか?」だけ。
返ってきた答えをじっくり読み進めながらも、とにかく頭の中は不安でいっぱいでした。
- そもそも、Windowsアプリを作ったことがない
- どういう基盤(フレームワーク)を使えばいいのかも分からん…
- VRChat APIなるものは知っていた。でも、果たしてそれを使ってもいいものなのか?
プログラムは、学生時分にちょこっと勉強して、最終的に頭がこんがらがって投げたような私です。
自分でコードをすべて書くような前提だったなら、当然のように見送っていることでしょう。
でも、分からないなりに私の中の何かがささやきました。
悩むなら、とにかく作れ。作ってから悩め―――
気づけばChatGPTに要望を投げて、それをもとに設計を進めてもらっていました。
こそこそ作った初期バージョン
GitHubには、誰でも見られるパブリックリポジトリと、自分だけが見られるプライベートリポジトリがあります。
自分のソースを世に出すのは、とても怖い。そもそも大っぴらに出せる類のアプリでもない。
なので、プライベートでこそこそ進めていました。
右往左往しつつ、暇なタイミングを見計らいながら2、3日ほどだったと思います。やっとのことで初期バージョンができました。
このアプリでやっていることは、ざっくりこうです。
- 登録したグループイベントの開始が近づくと、グループのインスタンスを見張る
- 見張るのは開始3分前から開始2分後まで。確認は通常15秒おき、開始30秒前からは5秒おき
- 条件に合うインスタンスが開いたら、VRChatに起動リンクを送って、そのインスタンスを開かせる
本命のコードは実質1行だけ
ここで、白状しなければいけないことがあります。
VRChatに「このインスタンスに入って」と頼んでいる本命のコードは、実質この1行です。
format!("vrchat://launch?ref=vrchat.com&id={}&attach=1", location)
要するに起動リンクの文字列を組み立てているだけです。これを叩くと普段よく見るインスタンス情報の画面が表示されます。
それだけのシンプルな挙動なのですが、公開した時点のコードの量はこうなっていました。
| 中身 | 行数 |
|---|---|
| Rust(アプリの裏側) | 7,840行 |
| 画面(TypeScript / React / CSS) | 5,557行 |
| テスト、自動チェック、各種スクリプト | 2,075行 |
| VRChatにインスタンス情報を送るコード | 1行 |
…さっきも言いましたが、大事なのは1行です。
残りの約1万5千行の膨大なコードはすべて、その1行を正しいタイミングで、正しいインスタンスに向けて送るためのお膳立て。
舞台で言えば、台詞ひとことの役者のために、劇場を建てて、照明を吊って、リハーサルを100回やったようなものです。
そして、これは単なる趣味です。正直、最近はそこまでVRChatにログインする余力がないので、ちょっと頑張りすぎでした。
初陣:「ポン」と入れて、ちょっと悔しい
テスト? なんだそれ、という感じで、いきなり本番のイベントで試しました。
とはいえ、自分が作った(作ってるのはAIですけど)アプリほど信用できないものはありません。
いつもどおりブラウザも開いておいて、人力で戦争へ挑むための準備は万全にしていました。
開始時刻。VRChatの画面に「ポン」と、インスタンスの入室画面が出ました。
半信半疑でしたが、うまいことJoinできていました。
私が手を動かすより、早かった。とても良い結果です、良い結果なんですが……少々悔しかったのも本音でした。

二戦目:「…あれ?」
前回試したのは、インスタンスがひとつ立つ系のイベントでした。
どういう挙動になるかも気になったので、後日インスタンスが複数立つイベントにも挑戦してみました。
いつものように迎えた開始時刻。あれ、何も起きない。
どのインスタンスも、候補に上がってきません。慌てて手で入ろうとしましたが、当然ながら人力でのJoinも負けました。
皆さんも、Join戦争に負けて枕を涙で濡らすのは日常茶飯事だと思います。この日の私も推しに会えなかったのでそうでした。
原因は「第1」と「第1」の微妙な違い
すぐにAIに「動かなかった」と投げて、原因を探してもらいました。
OnCueには、インスタンス名に含まれる語で本命を絞り込む機能があります。
私は条件に「第1」と書いていました。ところが、実際のインスタンス名は「第1」。全角の「1」です。
人間には同じに見えても、アプリから見れば赤の他人です。
条件に合うインスタンスがひとつもない扱いになってしまい、当の本人(OnCue)は黙って見送っていました。
直し方は、比べる前に名前の表記をそろえることでした。
use unicode_normalization::UnicodeNormalization;
/// 全角の「1」と半角の「1」、半角カナと全角カナを同じものとして扱う
fn normalize_name_for_match(value: &str) -> String {
value.nfkc().collect::<String>().to_lowercase()
}
NFKCというUnicodeの正規化で全角と半角をそろえ、小文字にもそろえてから比べます。直したのは、名前を比べる部分の数行だけ。
原因にたどり着くまでのほうが、ずっと長かったです。
失敗の報告は本当に大事
この件で身にしみたのは、フィードバックの大切さです。
「うまくいった」という報告だけでは、AIは何も気づいてくれません。「失敗した」という報告は、それ以上に大事です。
何を設定して、何が起きて、何が起きなかったのか。それを伝えると、一直線でなくともちゃんと原因までたどり着いてくれることは多いです。
報告するのが当たり前、というのはおっしゃるとおりです。でも、そこから進むものがあるんです。フィードバックは本当に大事。
インスタンスが同時に開かず、本命が開くより先に監視が終わっていた件も、同じように報告から直りました。
今は、終わり際にまだ候補のインスタンスがあれば、回数に上限を付けたうえで延長するような処理にしています。
全自動にしなかった理由
作り始めた頃の野望は、全自動でした。
インスタンスが開いたら、自分宛てに招待を飛ばして、そのままJoinまで済ませる。人間は寝ていてもいい(本当に寝てはいませんが)。
実際、初期バージョンには「起動リンクがうまくいかなければ、自動でセルフ招待(自分宛ての招待)を送る」仕組みが入っていました。
ところが、調べれば調べるほど、最後のひと押しはどう頑張っても人間の操作が必要になるということが分かってきました。
セルフ招待は、あくまで「招待の通知が届く」だけです。届いた通知から入るには、結局自分が最速で操作しないとダメです。
より深掘りしていけば、いわゆる「抜け道」を探すこともできたかもしれません。
ただ、それをやり始めると越えてはいけないラインを越えてしまうこともあると思います。
何であれ、誰もやってないことには理由がありますから、全自動でやるという点は「理想」に留めて、実際にはやっていません。
また、9月頃に他のリポジトリを触るついでにアプリを大きく作り直したときに、今後の方針もはっきりと変えています。
- 自動で送るのは、VRChatの起動リンクまで
- セルフ招待は、手動の「救済ボタン」に据えて、失敗したからといって機械的に送り直すようなことはしない
- 候補のインスタンスが複数あり、本命のインスタンスを決められないときは自動でやらずに「止める」

最後の「止める」は、地味ですが大事なことだと思っています。
知らない誰かが楽しみにしていたイベントに、何も知らない私が誤爆で入る。これは気まずすぎます。
ペコペコ謝りながら「失礼しました…」と抜けていくくらいなら、最初から入らないほうが全然いいのは当たり前です。
自動化を考えていくと、どうしても「どこまで自動でやれるか」に目が行きがちです。
でも、本当に大事なのは「どこで止めるか」でした。
無料枠が溶けて、セルフホスト沼にはまる
このアプリに限ったことではありませんが、短期間で改修を繰り返すことは多くあります。
気づけば、GitHub Actions(GitHub上でビルドやテストを自動で回してくれる仕組み)の無料枠が上限(2,000分)に達していました。
正直、枠なんて気にするほどの作業はしていないと思っていましたが、上限に達してからまずいことに気づく。
ただ、請求の上限は何かの折に0円に設定してあったので、従量課金で事故る…みたいなことにはなっていません。
そこは素直に自分を褒めておきました。
また、この時点ではプライベートリポジトリのままです。
枠のリセットを待つには日数があったので、調べてみると自宅の開発用PCをビルド係(セルフホストランナー)として登録すれば、枠の消費は関係なくなると分かりました。
ここで、私の悪い癖が出ます。ひとつ気づきがあると、すべてに展開したくなるんです。
一時期は、ほかのリポジトリまで必要かどうかの判断が曖昧なままで、セルフホスト化を進めていました(もちろんプライベートリポジトリだけが対象です)。
セルフホスト化しておけば、枠を消費しないので便利なのは確かです。
でも、推し進めすぎると、ただの環境構築で時間が溶けていきます。今はだいぶ整理しましたが、これも失敗のひとつです。
自宅サーバーの経験があると、なんでもセルフホストでやりたがる癖、たぶんありますよね。
ちなみに、公開に向けてセルフホストを畳もうとしたときにも、もう一度やらかしています。
GitHubのWindows環境に切り替えたら、プライベートのWindowsは消費が2倍で数えられるうえ、PRのたびに20分ほどのチェックが走る設定になっていました。
1日で推計約700分。月の無料枠2,000分の、3分の1強です。早めに気づけて良かったですが、気づくのが遅れていたら、無料枠とはいえ冷や汗ものでした。
GPT-サンからOpus-サンへ
ここまでずっと付き合ってくれていたのは、GPT-サンでした(ChatGPTのモデルのことです。後に出てくるAstra-サンやSolも、同じ一族です)。
GPT-サンは、言うなれば「実直でガチガチなエンジニア系」のお人です。
一方の私は、だいたいのことを「良い感じにやってもらえると…」と頼む、雰囲気重視の人間です。正直、相性はあまりよくない。
そんなとき、Opus 5.5の噂を聞きました。しばらく悩み続けた末に、Claudeを契約しました(とりあえずMax 5x)。
これが、めちゃくちゃ相性が良かった。本当に「良い感じに」やってくれます。次にどう進めるかの道筋を示すのもお上手。
とはいえ、要件をころころ変える、面倒くさい日本人(私)との対話に疲れたのか、ときどき英語で返事をしてくることが。
どうもモデル自体のクセのようですが、指摘すればすぐ日本語に戻ってくるので、あまり気にしていません。
(この記事を書いている間にも、何度か英語で返ってきました)
使用量も、GPT Plusプランが一瞬で尽きるようなAstra-サンと比べれば雲泥の差です。
十分すぎるので、ガンガン使っています。今はGPT-6.1 Solも出ていて、そちらであれば消費もさほど激しくありません。
当時はこのSolを、Claudeだけで不十分そうな気がする箇所のレビュー役として、思考レベルをLowにして役立てていました。
今のレビュー役はCodeRabbitです。パブリックリポジトリなら無料で使えるという話がXで流れてきて、「良さそうかも」と入れてみました。PRを出すと、日本語でレビューを付けてくれます。
昨日の不満が、次の日には解消されているかもしれないと思うと、やっぱりAIの進化って早いなぁと思います。
「クリエイター魂を爆発させて」
Opus-サンへのお願いは、こんなニュアンスでした。
- このリポジトリをパブリックにしたいので、懸念事項を整理して。公開できる段階まで持っていって。
- UIも野暮ったいので、君のクリエイター魂を爆発させて刷新して
公開前の大掃除
懸念事項は、思っていたよりたくさん出てきました。雰囲気だけで進めてると起こりやすいものばかりです。
- 個人のメールアドレスが、アプリの通信の名乗りと、過去の作業履歴の作者情報に入っていた
- 「VRC Event Joiner」という旧名の「VRC」は、VRChatの略称。公式ツールと誤解されかねない
- 画面のエラー表示が、
API error: ... {"verified":false}のような暗号だった - 自宅のPCをビルド係にしたままだと、パブリックにしたとき、知らない人のコードを自宅で動かす入口になりかねない
メールアドレスは、作業履歴ごと持ち込まないことで解決しました。新しいリポジトリに、まっさらな状態で公開しています。
エラー表示は、全部「次に何をすればいいか」が分かる文に直してもらいました。
2FAのコードを打ち間違えたときは、今はこのように表示されるようになっています。
認証コードが正しくないか、有効期限が切れています。
新しいコードを入力してください。
{"verified":false}と機械的に返されるよりは、だいぶ人間の言葉になりました。
アプリの名前も、このときにOnCueに変えました。
ログイン方式は、公開前の調整でしばし悩んだところです。OnCueは、実際に使うユーザーの情報でログインする仕組みです。
ただ、VRChatは外部アプリ向けのログイン手段を用意しておらず、公式には第三者ツールに認証情報を渡さないよう求めています。
そこで、他のVRChat系アプリの仕様も参考にさせていただいて、OnCueではこう線を引いています。
- パスワードは保存しない
- ログイン状態の保存は、自分で選んだときだけ(初期はオフ)
- 保存するなら、Windowsの暗号化機能(DPAPI)で暗号化する
- ログイン画面とREADMEに、非公式であることと自己責任であることを書く

UIはほぼ一発採用
UIのほうは、よく分からないけれど一発採用できそうな案が出てきました。
舞台のキューライト(出番の合図の灯り)をモチーフにした、大変エモい画面です。重すぎることもありません。
作り直す前後では、このように変化しています。
開始までの残り時間を大きく出し、監視の始まりから終わりまでを1本の線で描いています。
イベント直前かどうかを問わず、人間は基本的に細かい文字を隅から隅まで読むことはありません。
大きな数字と、光る丸。パッと目に入るのならそれで十分でした。
アイコンも、Tauri(使っているフレームワーク)の初期のままだったものが、見違えました。

地味にうれしかったのは、IssueやPRに画像を添付してくれたことです。
GPT-サンではどうにかできないかと頼み込んだ記憶がありますが、なぜか上手くいかずに諦めてしまった作業でした。
UIの相談では、画像があるかないかで話の早さがまるで違います。思わず手放しでClaudeを褒めておきました。
一番効いたのは押し忘れ対策
作り直しの途中で一番効いた改善は、見た目ではありませんでした。「開始」ボタンの押し忘れ対策です。
アプリを開いて「これを監視する」でイベントを選んでおけば、時間になったら勝手に監視が始まるようにしました。
1万5千行かけて秒単位の最適化をいろいろやったのに、最後に一番効いたのが「押し忘れ対策」です。
使っていくにつれて細かい気づきや改善したい箇所はいくらでも出てきます。押し忘れは、灯台下暗しみたいな問題でした。
三戦目:見えているのに、入らない
実は公開した後にも、また「…あれ?」がありました。
今度は名前の条件を「第2」にして、第2インスタンスを狙ったときのこと。「第2」を含むインスタンスはちゃんと立っていたのに、OnCueはそれを見つけられず、監視を延長しながら待ち続けていました。
厄介だったのは、OnCue自身は何かしらのインスタンスを見ていたことです。見えていたからこそ延長していたわけですが、画面は「会場が開くのを待っています」のまま。
なぜ待っているのかは、詳しいログを掘り返さないと分からない状態でした。
原因は、まだ分かっていません。VRChatから届いた一覧にそもそも第2がいなかったのか、それとも第2の名前の情報が欠けていたのか。今のところ、この2つまでしか絞れていません。
なので今回は、原因を当てにいく前に、OnCueが自分で説明できるようにしました。
見えているのに条件に合わず待っているときは、その理由と、見えているインスタンスの名前と状態(空きあり、満員など)を画面とログに出します。そのまま一覧から選んで入ることもできます。
ログも起動ごとに別のファイルへ分けて、直近20回分を残すようにしました。次に同じことが起きれば、どちらが原因だったのかが分かるはずです。

…と言いつつ、直してからまだ本番では試せていません。「失敗の報告は本当に大事」と書いた手前、次に何かあったら、まずは報告からです。
コードだけ置いておきます
いろいろな人に使ってもらおうとは特に思っていないので、ソースコードだけ置いています。
ビルド済みの実行ファイルも配布しません。注意書きは載せていますが、だからといって必要以上の責任は持てませんから…。
とはいえ、何か不具合に遭遇したり、もっとましな改善案があったら、気軽に教えてもらえるとうれしいところです。
おわりに
何も分からないところから始めましたが、言えることはひとつ。「作ろうと思えば、作れる」です。
Windowsアプリを作ったことがなくても、どのようなフレームワークがあるのかが分からなくても。
今はAIにどんな質問でも壁打ちできるので、ふんわりとした理解でも前に進めます。セキュリティとか気をつけるべき点はいくらでもありますが。
ただ、必要なものが2つあると思います。目的がはっきりしていること。そして、失敗してもへこたれないこと。
「OnCue」も、枕を涙で濡らした夜のたびに少しずつ良くなっていった実感があります。
とはいえ、何かをやってみること自体は、とてもやりやすい時代になりました。
悩むなら、とにかく作れ。作ってから悩め。


