Move app settings into config files
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user