From da8a9b3c3f35cf1894ccc4185ff642d0b74bf92f Mon Sep 17 00:00:00 2001 From: messypy Date: Wed, 3 Jun 2026 21:56:45 +0900 Subject: [PATCH] Move app settings into config files --- VRWT_Tool/VRC_OSC/.env.example | 11 --- VRWT_Tool/VRC_OSC/.gitignore | 2 + VRWT_Tool/VRC_OSC/config/config.example.toml | 13 ++- VRWT_Tool/VRC_OSC/config/guests.example.txt | 3 + VRWT_Tool/VRC_OSC/config/secrets.example.toml | 6 ++ VRWT_Tool/VRC_OSC/readme.txt | 96 +++++++++++-------- VRWT_Tool/VRC_OSC/src/common/config_loader.py | 57 ++++++++++- VRWT_Tool/VRC_OSC/src/common/env_config.py | 56 ----------- VRWT_Tool/VRC_OSC/src/common/project_paths.py | 1 - VRWT_Tool/VRC_OSC/src/getUser.py | 32 ++----- VRWT_Tool/VRC_OSC/src/ocr/ocr_actions.py | 11 ++- .../VRC_OSC/src/screen/screen_actions.py | 11 ++- VRWT_Tool/VRC_OSC/src/vrc_log/log_actions.py | 26 +---- 13 files changed, 159 insertions(+), 166 deletions(-) delete mode 100644 VRWT_Tool/VRC_OSC/.env.example create mode 100644 VRWT_Tool/VRC_OSC/config/guests.example.txt create mode 100644 VRWT_Tool/VRC_OSC/config/secrets.example.toml delete mode 100644 VRWT_Tool/VRC_OSC/src/common/env_config.py diff --git a/VRWT_Tool/VRC_OSC/.env.example b/VRWT_Tool/VRC_OSC/.env.example deleted file mode 100644 index 7874620..0000000 --- a/VRWT_Tool/VRC_OSC/.env.example +++ /dev/null @@ -1,11 +0,0 @@ -DISCORD_WEBHOOK_URL= -VRCHAT_USERNAME= -VRCHAT_PASSWORD= - -OCR_CROP_LEFT=0.05 -OCR_CROP_TOP=0.35 -OCR_CROP_RIGHT=0.95 -OCR_CROP_BOTTOM=0.95 -OCR_PREPROCESS=1 -OCR_SCALE=1.5 -OCR_USE_ANGLE_CLS=0 diff --git a/VRWT_Tool/VRC_OSC/.gitignore b/VRWT_Tool/VRC_OSC/.gitignore index 11a147a..03f5e52 100644 --- a/VRWT_Tool/VRC_OSC/.gitignore +++ b/VRWT_Tool/VRC_OSC/.gitignore @@ -3,6 +3,8 @@ __pycache__/ .env config/config.toml +config/secrets.toml +config/guests.txt runtime/ .vrchat_cookie.txt src/.vrchat_cookie.txt diff --git a/VRWT_Tool/VRC_OSC/config/config.example.toml b/VRWT_Tool/VRC_OSC/config/config.example.toml index 75fc8b7..e154d9e 100644 --- a/VRWT_Tool/VRC_OSC/config/config.example.toml +++ b/VRWT_Tool/VRC_OSC/config/config.example.toml @@ -5,7 +5,18 @@ name = "" names = [] [guest] -names = [] +file = "config/guests.txt" [notice] missing_count = 0 + +[ocr.crop] +left = 0.05 +top = 0.35 +right = 0.95 +bottom = 0.95 + +[ocr] +preprocess = true +scale = 1.5 +use_angle_cls = false diff --git a/VRWT_Tool/VRC_OSC/config/guests.example.txt b/VRWT_Tool/VRC_OSC/config/guests.example.txt new file mode 100644 index 0000000..3124eb2 --- /dev/null +++ b/VRWT_Tool/VRC_OSC/config/guests.example.txt @@ -0,0 +1,3 @@ +GuestUserA +GuestUserB +GuestUserC diff --git a/VRWT_Tool/VRC_OSC/config/secrets.example.toml b/VRWT_Tool/VRC_OSC/config/secrets.example.toml new file mode 100644 index 0000000..8cc68c2 --- /dev/null +++ b/VRWT_Tool/VRC_OSC/config/secrets.example.toml @@ -0,0 +1,6 @@ +[discord] +webhook_url = "" + +[vrchat] +username = "" +password = "" diff --git a/VRWT_Tool/VRC_OSC/readme.txt b/VRWT_Tool/VRC_OSC/readme.txt index f120ef8..2603a6c 100644 --- a/VRWT_Tool/VRC_OSC/readme.txt +++ b/VRWT_Tool/VRC_OSC/readme.txt @@ -34,65 +34,83 @@ names = [ ] [guest] -names = [ - "GuestUserA", - "GuestUserB" -] +file = "config/guests.txt" [notice] missing_count = 3 + +[ocr.crop] +left = 0.05 +top = 0.35 +right = 0.95 +bottom = 0.95 + +[ocr] +preprocess = true +scale = 1.5 +use_angle_cls = false ``` `self.name` は Discord 自動ミュート判定に使う。 `staff.names` は表示対象だが guest 人数には含めない。 -`guest.names` は guest 人数の母数と在室判定に使う。 +`guest.file` は guest 名簿ファイル。guest 人数の母数と在室判定に使う。 `notice.missing_count` は「あと何人以内で全員集合か」の通知範囲。例えば guest が 23 人で `missing_count = 3` の場合、20〜22 人のときに未入室 guest を出す。 -## 秘密情報 - -`.env` に置く。このファイルは Git に入れない。 - -雛形は `.env.example`。 - -```env -DISCORD_WEBHOOK_URL= -VRCHAT_USERNAME= -VRCHAT_PASSWORD= - -OCR_CROP_LEFT=0.05 -OCR_CROP_TOP=0.35 -OCR_CROP_RIGHT=0.95 -OCR_CROP_BOTTOM=0.95 -OCR_PREPROCESS=1 -OCR_SCALE=1.5 -OCR_USE_ANGLE_CLS=0 -``` - -`DISCORD_WEBHOOK_URL` は VRC ログ結果の Discord Webhook 通知に使う。 - -`VRCHAT_USERNAME` / `VRCHAT_PASSWORD` は `src/getUser.py` を使う場合だけ必要。 - OCR 設定は速度と精度の調整用。 -`OCR_CROP_*` は VRChat ウィンドウ内の相対座標。`0.0` から `1.0` で指定する。デフォルトは画面下側の中央寄りだけを読む。 +`ocr.crop` は VRChat ウィンドウ内の相対座標。`0.0` から `1.0` で指定する。デフォルトは画面下側の中央寄りだけを読む。 フル画面 OCR に戻す場合。 -```env -OCR_CROP_LEFT=0 -OCR_CROP_TOP=0 -OCR_CROP_RIGHT=1 -OCR_CROP_BOTTOM=1 +```toml +[ocr.crop] +left = 0 +top = 0 +right = 1 +bottom = 1 ``` -`OCR_PREPROCESS=1` はグレースケール化、コントラスト補正、シャープ化、拡大を行う。 +`ocr.preprocess = true` はグレースケール化、コントラスト補正、シャープ化、拡大を行う。 -`OCR_SCALE` は前処理時の拡大率。大きいほど小さい文字に強くなるが遅くなる。 +`ocr.scale` は前処理時の拡大率。大きいほど小さい文字に強くなるが遅くなる。 -`OCR_USE_ANGLE_CLS=0` は角度分類を切って高速化する。縦書きや回転文字を読む必要がある場合だけ `1` にする。 +`ocr.use_angle_cls = false` は角度分類を切って高速化する。縦書きや回転文字を読む必要がある場合だけ `true` にする。 + +## guest 名簿 + +`config/guests.txt` に 1 行 1 名で書く。このファイルは Git に入れない。 + +雛形は `config/guests.example.txt`。 + +```text +GuestUserA +GuestUserB +GuestUserC +``` + +空行と `#` で始まる行は無視する。 + +## 秘密情報 + +秘密情報は `config/secrets.toml` に置く。このファイルは Git に入れない。 + +雛形は `config/secrets.example.toml`。 + +```toml +[discord] +webhook_url = "" + +[vrchat] +username = "" +password = "" +``` + +`discord.webhook_url` は VRC ログ結果の Discord Webhook 通知に使う。 + +`vrchat.username` / `vrchat.password` は `src/getUser.py` を使う場合だけ必要。 ## OSC パラメータ @@ -188,6 +206,8 @@ OCR text、翻訳 text、VRC ログ結果は `runtime/runtime.log` に追記す - `.env` - `config/config.toml` +- `config/secrets.toml` +- `config/guests.txt` - `runtime/` - `__pycache__/` - `*.pyc` diff --git a/VRWT_Tool/VRC_OSC/src/common/config_loader.py b/VRWT_Tool/VRC_OSC/src/common/config_loader.py index 205d1dd..364ea60 100644 --- a/VRWT_Tool/VRC_OSC/src/common/config_loader.py +++ b/VRWT_Tool/VRC_OSC/src/common/config_loader.py @@ -5,7 +5,9 @@ from common.project_paths import ROOT_DIR CONFIG_DIR = ROOT_DIR / "config" CONFIG_FILE = CONFIG_DIR / "config.toml" +SECRETS_FILE = CONFIG_DIR / "secrets.toml" DEFAULT_EVENT_FILE = CONFIG_DIR / "event.toml" +DEFAULT_GUEST_FILE = CONFIG_DIR / "guests.txt" def loadTomlFile(path): @@ -26,6 +28,47 @@ def resolveConfigPath(value, default_path): return (ROOT_DIR / candidate).resolve() +def loadNameListFile(path): + if not path.exists(): + return [] + + names = [] + + for line in path.read_text(encoding="utf-8-sig", errors="replace").splitlines(): + name = line.strip() + if not name or name.startswith("#"): + continue + names.append(name) + + return names + + +def loadSecretConfig(): + return loadTomlFile(SECRETS_FILE) + + +def getSecretValue(section_name, key, default=""): + secrets = loadSecretConfig() + section = secrets.get(section_name, {}) + return str(section.get(key, default)).strip() + + +def loadOcrConfig(): + config = loadTomlFile(CONFIG_FILE) + ocr = config.get("ocr", {}) + crop = ocr.get("crop", {}) + + return { + "crop_left": float(crop.get("left", 0.05)), + "crop_top": float(crop.get("top", 0.35)), + "crop_right": float(crop.get("right", 0.95)), + "crop_bottom": float(crop.get("bottom", 0.95)), + "preprocess": bool(ocr.get("preprocess", True)), + "scale": float(ocr.get("scale", 1.5)), + "use_angle_cls": bool(ocr.get("use_angle_cls", False)), + } + + def loadVrcLogConfig(): config = loadTomlFile(CONFIG_FILE) vrc_log = config.get("vrc_log", {}) @@ -43,7 +86,18 @@ def loadVrcLogConfig(): if not event_config: event_config = vrc_log or config - guest_names = event_config.get("guest", {}).get("names", []) + guest_section = event_config.get("guest", {}) + guest_names = guest_section.get("names", []) + guest_file_value = ( + guest_section.get("file") + or event_config.get("guest_file") + or config.get("guest_file") + ) + guest_file = resolveConfigPath(guest_file_value, DEFAULT_GUEST_FILE) + + if not guest_names: + guest_names = loadNameListFile(guest_file) + staff_names = event_config.get("staff", {}).get("names", []) if not guest_names and isinstance(event_config.get("guest_names"), list): @@ -68,6 +122,7 @@ def loadVrcLogConfig(): return { "config_file": CONFIG_FILE, "event_file": event_file, + "guest_file": guest_file, "guest_names": [str(name).strip() for name in guest_names if str(name).strip()], "staff_names": [str(name).strip() for name in staff_names if str(name).strip()], "missing_count": int(missing_count), diff --git a/VRWT_Tool/VRC_OSC/src/common/env_config.py b/VRWT_Tool/VRC_OSC/src/common/env_config.py deleted file mode 100644 index e5fb774..0000000 --- a/VRWT_Tool/VRC_OSC/src/common/env_config.py +++ /dev/null @@ -1,56 +0,0 @@ -import os - -from common.project_paths import ENV_FILE - - -def readDotEnvValue(key): - if not ENV_FILE.exists(): - return "" - - for line in ENV_FILE.read_text(encoding="utf-8-sig", errors="replace").splitlines(): - stripped = line.strip() - if not stripped or stripped.startswith("#"): - continue - if not stripped.startswith(key + "="): - continue - - value = stripped.split("=", 1)[1].strip() - return value.strip('"').strip("'") - - return "" - - -def getEnvValue(key, default=""): - value = os.environ.get(key, "").strip() - if value: - return value - - value = readDotEnvValue(key).strip() - if value: - return value - - return default - - -def getEnvBool(key, default=False): - value = getEnvValue(key, "").strip().lower() - if not value: - return default - - if value in ("1", "true", "yes", "on"): - return True - if value in ("0", "false", "no", "off"): - return False - - return default - - -def getEnvFloat(key, default=0.0): - value = getEnvValue(key, "").strip() - if not value: - return default - - try: - return float(value) - except ValueError: - return default diff --git a/VRWT_Tool/VRC_OSC/src/common/project_paths.py b/VRWT_Tool/VRC_OSC/src/common/project_paths.py index 3d89804..fa38746 100644 --- a/VRWT_Tool/VRC_OSC/src/common/project_paths.py +++ b/VRWT_Tool/VRC_OSC/src/common/project_paths.py @@ -4,4 +4,3 @@ SRC_DIR = Path(__file__).resolve().parents[1] ROOT_DIR = SRC_DIR.parent RUNTIME_DIR = ROOT_DIR / "runtime" RUNTIME_LOG_FILE = RUNTIME_DIR / "runtime.log" -ENV_FILE = ROOT_DIR / ".env" diff --git a/VRWT_Tool/VRC_OSC/src/getUser.py b/VRWT_Tool/VRC_OSC/src/getUser.py index 7254bbb..9841885 100644 --- a/VRWT_Tool/VRC_OSC/src/getUser.py +++ b/VRWT_Tool/VRC_OSC/src/getUser.py @@ -1,4 +1,3 @@ -import os import re from http.cookiejar import MozillaCookieJar @@ -9,6 +8,8 @@ from vrchatapi.exceptions import UnauthorizedException from vrchatapi.models.two_factor_auth_code import TwoFactorAuthCode from vrchatapi.models.two_factor_email_code import TwoFactorEmailCode +from common.config_loader import getSecretValue + COOKIE_FILE = ".vrchat_cookie.txt" USER_AGENT = ( @@ -17,29 +18,10 @@ USER_AGENT = ( ) -def read_dotenv_value(key): - env_path = ".env" - if not os.path.exists(env_path): - return "" - - with open(env_path, "r", encoding="utf-8-sig", errors="replace") as file: - for line in file: - stripped = line.strip() - if not stripped or stripped.startswith("#"): - continue - if not stripped.startswith(key + "="): - continue - - value = stripped.split("=", 1)[1].strip() - return value.strip('"').strip("'") - - return "" - - -def get_required_secret(key): - value = os.environ.get(key, "").strip() or read_dotenv_value(key).strip() +def get_required_secret(section, key): + value = getSecretValue(section, key) if not value: - raise RuntimeError(f"{key} is not set") + raise RuntimeError(f"{section}.{key} is not set in config/secrets.toml") return value @@ -126,8 +108,8 @@ def main(): return configuration = vrchatapi.Configuration( - username=get_required_secret("VRCHAT_USERNAME"), - password=get_required_secret("VRCHAT_PASSWORD"), + username=get_required_secret("vrchat", "username"), + password=get_required_secret("vrchat", "password"), ) with vrchatapi.ApiClient(configuration) as api_client: diff --git a/VRWT_Tool/VRC_OSC/src/ocr/ocr_actions.py b/VRWT_Tool/VRC_OSC/src/ocr/ocr_actions.py index 5edf1be..56f2a4a 100644 --- a/VRWT_Tool/VRC_OSC/src/ocr/ocr_actions.py +++ b/VRWT_Tool/VRC_OSC/src/ocr/ocr_actions.py @@ -14,8 +14,7 @@ from PIL import Image from PIL import ImageFilter from PIL import ImageOps -from common.env_config import getEnvBool -from common.env_config import getEnvFloat +from common.config_loader import loadOcrConfig from common.runtime_log import appendRuntimeLog from screen.screen_actions import captureOcrRegion @@ -32,9 +31,10 @@ def getOcrEngine(): if _ocr_engine is None: log("INFO", "PaddleOCR initializing") + config = loadOcrConfig() options = { "lang": "japan", - "use_angle_cls": getEnvBool("OCR_USE_ANGLE_CLS", False), + "use_angle_cls": config["use_angle_cls"], "show_log": False, } @@ -112,7 +112,8 @@ def printOcrResult(text): def preprocessOcrImage(image_path): - enabled = getEnvBool("OCR_PREPROCESS", True) + config = loadOcrConfig() + enabled = config["preprocess"] if not enabled: return image_path @@ -122,7 +123,7 @@ def preprocessOcrImage(image_path): image = ImageOps.autocontrast(image) image = image.filter(ImageFilter.SHARPEN) - scale = max(1.0, min(2.0, getEnvFloat("OCR_SCALE", 1.5))) + scale = max(1.0, min(2.0, config["scale"])) if scale > 1.0: width, height = image.size resampling = getattr( diff --git a/VRWT_Tool/VRC_OSC/src/screen/screen_actions.py b/VRWT_Tool/VRC_OSC/src/screen/screen_actions.py index c738094..da0cf0a 100644 --- a/VRWT_Tool/VRC_OSC/src/screen/screen_actions.py +++ b/VRWT_Tool/VRC_OSC/src/screen/screen_actions.py @@ -1,7 +1,7 @@ import pyautogui import pygetwindow as gw -from common.env_config import getEnvFloat +from common.config_loader import loadOcrConfig from common.project_paths import RUNTIME_DIR SCREENSHOT_PATH = RUNTIME_DIR / "latest_screenshot.png" @@ -100,13 +100,14 @@ def clampRatio(value): def getOcrRegion(): left, top, width, height = getVrchatRegion() + config = loadOcrConfig() # Default to the lower central area. It is much faster than OCRing the # whole VRChat window and usually contains chat/subtitle text. - crop_left = clampRatio(getEnvFloat("OCR_CROP_LEFT", 0.05)) - crop_top = clampRatio(getEnvFloat("OCR_CROP_TOP", 0.35)) - crop_right = clampRatio(getEnvFloat("OCR_CROP_RIGHT", 0.95)) - crop_bottom = clampRatio(getEnvFloat("OCR_CROP_BOTTOM", 0.95)) + crop_left = clampRatio(config["crop_left"]) + crop_top = clampRatio(config["crop_top"]) + crop_right = clampRatio(config["crop_right"]) + crop_bottom = clampRatio(config["crop_bottom"]) if crop_right <= crop_left: crop_left = 0.0 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 8c5c25d..62f2763 100644 --- a/VRWT_Tool/VRC_OSC/src/vrc_log/log_actions.py +++ b/VRWT_Tool/VRC_OSC/src/vrc_log/log_actions.py @@ -9,7 +9,7 @@ from datetime import timedelta from pathlib import Path from common.config_loader import loadVrcLogConfig -from common.project_paths import ENV_FILE +from common.config_loader import getSecretValue from common.runtime_log import appendRuntimeLog from discord.discord_actions import setDiscordMuted @@ -49,34 +49,14 @@ def log(level, message): print(f"[{level}] {now} {message}", flush=True) -def readDotEnvValue(key): - if not ENV_FILE.exists(): - return "" - - for line in ENV_FILE.read_text(encoding="utf-8-sig", errors="replace").splitlines(): - stripped = line.strip() - if not stripped or stripped.startswith("#"): - continue - if not stripped.startswith(key + "="): - continue - - value = stripped.split("=", 1)[1].strip() - return value.strip('"').strip("'") - - return "" - - def getWebhookUrl(): - webhook_url = os.environ.get("DISCORD_WEBHOOK_URL", "").strip() - if webhook_url: - return webhook_url - return readDotEnvValue("DISCORD_WEBHOOK_URL") + return getSecretValue("discord", "webhook_url") def sendWebhook(text): webhook_url = getWebhookUrl().strip() if not webhook_url: - log("ERROR", f"DISCORD_WEBHOOK_URL が未設定です env_file={ENV_FILE}") + log("ERROR", "Discord webhook URL is not set config=config/secrets.toml") return False message = str(text).strip()