From 66f94eaa9edf188b5fa81d3ab8c4197b953aaa88 Mon Sep 17 00:00:00 2001 From: messypy Date: Wed, 3 Jun 2026 22:04:22 +0900 Subject: [PATCH] Refine VRC OSC project architecture --- VRWT_Tool/VRC_OSC/readme.txt | 25 +- VRWT_Tool/VRC_OSC/requirements.txt | 10 + VRWT_Tool/VRC_OSC/script/test.py | 265 ------------------ VRWT_Tool/VRC_OSC/src/{bin => }/app.py | 2 +- VRWT_Tool/VRC_OSC/src/bin/__init__.py | 1 - VRWT_Tool/VRC_OSC/src/bin/vrc_osc.py | 2 +- .../actions.py} | 0 VRWT_Tool/VRC_OSC/src/main.py | 5 - .../resolve_vrchat_user.py} | 0 VRWT_Tool/VRC_OSC/src/vrc_log/log_actions.py | 2 +- 10 files changed, 37 insertions(+), 275 deletions(-) create mode 100644 VRWT_Tool/VRC_OSC/requirements.txt delete mode 100644 VRWT_Tool/VRC_OSC/script/test.py rename VRWT_Tool/VRC_OSC/src/{bin => }/app.py (98%) delete mode 100644 VRWT_Tool/VRC_OSC/src/bin/__init__.py rename VRWT_Tool/VRC_OSC/src/{discord/discord_actions.py => discord_control/actions.py} (100%) delete mode 100644 VRWT_Tool/VRC_OSC/src/main.py rename VRWT_Tool/VRC_OSC/src/{getUser.py => tools/resolve_vrchat_user.py} (100%) diff --git a/VRWT_Tool/VRC_OSC/readme.txt b/VRWT_Tool/VRC_OSC/readme.txt index 8bfd90a..ad91257 100644 --- a/VRWT_Tool/VRC_OSC/readme.txt +++ b/VRWT_Tool/VRC_OSC/readme.txt @@ -4,6 +4,12 @@ VRChat の OSC パラメータを受けて、Discord ミュート、VRChat ロ ## 起動 +依存関係を入れる。 + +```powershell +python -m pip install -r requirements.txt +``` + PowerShell から起動する。 ```powershell @@ -18,6 +24,23 @@ python src\bin\vrc_osc.py OSC は `127.0.0.1:9001` で待ち受ける。 +## 構成 + +```text +bin/ PowerShell 起動スクリプト +config/ example とローカル設定置き場 +src/app.py アプリ本体のエントリ +src/bin/ Python 起動スクリプト +src/common/ パス、設定ロード、runtime ログなどの共通基盤 +src/discord_control/ Discord ウィンドウ操作 +src/ocr/ OCR 実行 +src/osc/ OSC gateway +src/screen/ VRChat ウィンドウ検出とキャプチャ +src/tools/ 単体実行用の補助ツール +src/translate/ 翻訳 +src/vrc_log/ VRChat ログ解析と名簿照合 +``` + ## 設定ファイル 実設定は `config/config.toml` に置く。このファイルは Git に入れない。 @@ -110,7 +133,7 @@ password = "" `discord.webhook_url` は VRC ログ結果の Discord Webhook 通知に使う。 -`vrchat.username` / `vrchat.password` は `src/getUser.py` を使う場合だけ必要。 +`vrchat.username` / `vrchat.password` は `src/tools/resolve_vrchat_user.py` を使う場合だけ必要。 ## OSC パラメータ diff --git a/VRWT_Tool/VRC_OSC/requirements.txt b/VRWT_Tool/VRC_OSC/requirements.txt new file mode 100644 index 0000000..ccef526 --- /dev/null +++ b/VRWT_Tool/VRC_OSC/requirements.txt @@ -0,0 +1,10 @@ +deep-translator +paddleocr +pillow +pyautogui +pygetwindow +pykakasi +pyperclip +python-osc +rapidfuzz +vrchatapi diff --git a/VRWT_Tool/VRC_OSC/script/test.py b/VRWT_Tool/VRC_OSC/script/test.py deleted file mode 100644 index 6621205..0000000 --- a/VRWT_Tool/VRC_OSC/script/test.py +++ /dev/null @@ -1,265 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -""" -目的: -VRChat表示名とTOML guest名を fuzzy matching する - -特徴: -- 記号除去 -- 全角半角統一 -- 小文字化 -- 日本語 -> ローマ字変換 -- rapidfuzz による類似度 -- ambiguous 判定 -- テストケース付き - -前提: -pip install rapidfuzz pykakasi - -期待結果: -- 類似候補を score 順に表示 -- 危険な曖昧ケースを検知 -""" - -import re -import sys -import unicodedata -from typing import List, Tuple - -try: - from rapidfuzz import fuzz - from rapidfuzz import process -except Exception as e: - print(f"[FATAL] rapidfuzz import failed: {e}") - input("Press Enter to exit...") - sys.exit(1) - -try: - from pykakasi import kakasi -except Exception as e: - print(f"[FATAL] pykakasi import failed: {e}") - input("Press Enter to exit...") - sys.exit(1) - - -# ========================= -# 設定 -# ========================= - -AUTO_LINK_SCORE = 95 -AMBIGUOUS_DIFF = 10 - -TEST_GUESTS = [ - "okada", - "tanaka", - "yamada", - "messykenty", -] - -TEST_CURRENT_USERS = [ - "岡田", - "ikada", - "okada_", - "OKADA", - "tanaka!!!", - "田中", - "やまだ", - "messy_kenty", - "messy.kenty", - "MESSYKENTY", - "ma", - "めっしーけんてぃ", - "山田", -] - - -# ========================= -# ローマ字変換 -# ========================= - -kks = kakasi() - - -def jp_to_romaji(text: str) -> str: - try: - converted = kks.convert(text) - - result = [] - for item in converted: - result.append(item["hepburn"]) - - return "".join(result) - - except Exception as e: - print(f"[ERROR] romaji convert failed: {text} : {e}") - return text - - -# ========================= -# 正規化 -# ========================= - -def normalize_name(name: str) -> str: - """ - 正規化内容: - - 全角半角統一 - - 小文字化 - - 日本語→ローマ字 - - 記号除去 - """ - - # Unicode normalize - name = unicodedata.normalize("NFKC", name) - - # 小文字化 - name = name.lower() - - # 日本語→ローマ字 - name = jp_to_romaji(name) - - # 記号除去 - name = re.sub(r"[^a-z0-9]", "", name) - - return name.strip() - - -# ========================= -# 類似候補検索 -# ========================= - -def find_candidates( - target_guest: str, - current_users: List[str], -) -> List[Tuple[str, float]]: - - normalized_target = normalize_name(target_guest) - - normalized_map = {} - - for user in current_users: - normalized_map[user] = normalize_name(user) - - results = [] - - for original_name, normalized_name in normalized_map.items(): - - score = fuzz.ratio( - normalized_target, - normalized_name, - ) - - results.append((original_name, score)) - - results.sort(key=lambda x: x[1], reverse=True) - - return results - - -# ========================= -# 判定 -# ========================= - -def evaluate_guest_match( - guest_name: str, - current_users: List[str], -): - - print("") - print("=" * 60) - print(f"[CHECK] guest={guest_name}") - - normalized_guest = normalize_name(guest_name) - - print(f"[INFO] normalized_guest={normalized_guest}") - - candidates = find_candidates( - guest_name, - current_users, - ) - - if not candidates: - print("[ERROR] no candidates") - return - - print("") - print("[CHECK] candidate list") - - for idx, (name, score) in enumerate(candidates[:5], start=1): - print(f" {idx}. {name:<20} score={score:.2f}") - - top_name, top_score = candidates[0] - - second_score = 0 - - if len(candidates) >= 2: - second_score = candidates[1][1] - - diff = top_score - second_score - - print("") - print(f"[INFO] top_score={top_score:.2f}") - print(f"[INFO] second_score={second_score:.2f}") - print(f"[INFO] diff={diff:.2f}") - - # 自動確定 - if ( - top_score >= AUTO_LINK_SCORE - and diff >= AMBIGUOUS_DIFF - ): - print("") - print("[INFO] AUTO LINK SAFE") - print(f"[INFO] linked => {top_name}") - return - - # 曖昧 - print("") - print("[WARN] ambiguous match") - print("[WARN] auto link disabled") - - print("") - print("[ACTION] manual review required") - - -# ========================= -# main -# ========================= - -def main(): - - print("[CHECK] normalize preview") - - for user in TEST_CURRENT_USERS: - print( - f"{user:<20}" - f" => " - f"{normalize_name(user)}" - ) - - print("") - print("[CHECK] guest matching test") - - for guest in TEST_GUESTS: - evaluate_guest_match( - guest, - TEST_CURRENT_USERS, - ) - - print("") - print("[INFO] completed") - - -if __name__ == "__main__": - - print("[CHECK] start") - - answer = input( - "[ACTION] run fuzzy test? (yes/no): " - ).strip().lower() - - if answer != "yes": - print("[FATAL] cancelled") - input("Press Enter to exit...") - sys.exit(1) - - main() diff --git a/VRWT_Tool/VRC_OSC/src/bin/app.py b/VRWT_Tool/VRC_OSC/src/app.py similarity index 98% rename from VRWT_Tool/VRC_OSC/src/bin/app.py rename to VRWT_Tool/VRC_OSC/src/app.py index f17899f..6b25eba 100644 --- a/VRWT_Tool/VRC_OSC/src/bin/app.py +++ b/VRWT_Tool/VRC_OSC/src/app.py @@ -13,7 +13,7 @@ if str(SRC_DIR) not in sys.path: sys.path.insert(0, str(SRC_DIR)) from osc.gateway import VrcOscGateway -from discord.discord_actions import setDiscordMute +from discord_control.actions import setDiscordMute from ocr.ocr_actions import runOcrFromScreen from translate.translate_actions import saveTranslationText from translate.translate_actions import translateTextToJapanese diff --git a/VRWT_Tool/VRC_OSC/src/bin/__init__.py b/VRWT_Tool/VRC_OSC/src/bin/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/VRWT_Tool/VRC_OSC/src/bin/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/VRWT_Tool/VRC_OSC/src/bin/vrc_osc.py b/VRWT_Tool/VRC_OSC/src/bin/vrc_osc.py index 430ce79..bc7e369 100644 --- a/VRWT_Tool/VRC_OSC/src/bin/vrc_osc.py +++ b/VRWT_Tool/VRC_OSC/src/bin/vrc_osc.py @@ -6,7 +6,7 @@ SRC_DIR = Path(__file__).resolve().parents[1] if str(SRC_DIR) not in sys.path: sys.path.insert(0, str(SRC_DIR)) -from bin.app import main +from app import main if __name__ == "__main__": diff --git a/VRWT_Tool/VRC_OSC/src/discord/discord_actions.py b/VRWT_Tool/VRC_OSC/src/discord_control/actions.py similarity index 100% rename from VRWT_Tool/VRC_OSC/src/discord/discord_actions.py rename to VRWT_Tool/VRC_OSC/src/discord_control/actions.py diff --git a/VRWT_Tool/VRC_OSC/src/main.py b/VRWT_Tool/VRC_OSC/src/main.py deleted file mode 100644 index e51969d..0000000 --- a/VRWT_Tool/VRC_OSC/src/main.py +++ /dev/null @@ -1,5 +0,0 @@ -from bin.app import main - - -if __name__ == "__main__": - main() diff --git a/VRWT_Tool/VRC_OSC/src/getUser.py b/VRWT_Tool/VRC_OSC/src/tools/resolve_vrchat_user.py similarity index 100% rename from VRWT_Tool/VRC_OSC/src/getUser.py rename to VRWT_Tool/VRC_OSC/src/tools/resolve_vrchat_user.py diff --git a/VRWT_Tool/VRC_OSC/src/vrc_log/log_actions.py b/VRWT_Tool/VRC_OSC/src/vrc_log/log_actions.py index 62f2763..e533599 100644 --- a/VRWT_Tool/VRC_OSC/src/vrc_log/log_actions.py +++ b/VRWT_Tool/VRC_OSC/src/vrc_log/log_actions.py @@ -11,7 +11,7 @@ from pathlib import Path from common.config_loader import loadVrcLogConfig from common.config_loader import getSecretValue from common.runtime_log import appendRuntimeLog -from discord.discord_actions import setDiscordMuted +from discord_control.actions import setDiscordMuted _monitor_thread = None _monitor_stop_event = threading.Event()