Restrict VRC log output to user events

This commit is contained in:
every_holiday
2026-06-09 01:43:22 +09:00
parent 15a8b4397e
commit cc424dca56

View File

@@ -1,5 +1,6 @@
import os import os
import re import re
import difflib
import subprocess import subprocess
import tempfile import tempfile
import threading import threading
@@ -11,6 +12,7 @@ from pathlib import Path
from common.config_loader import loadVrcLogConfig from common.config_loader import loadVrcLogConfig
from common.config_loader import getSecretValue from common.config_loader import getSecretValue
from common.runtime_log import appendRuntimeLog from common.runtime_log import appendRuntimeLog
from common.runtime_log import appendJoinLeaveLog
from discord_control.actions import setDiscordMuted from discord_control.actions import setDiscordMuted
_monitor_thread = None _monitor_thread = None
@@ -20,6 +22,9 @@ _self_monitor_thread = None
_self_monitor_stop_event = threading.Event() _self_monitor_stop_event = threading.Event()
_last_self_state = None _last_self_state = None
REALTIME_IDLE_SECONDS = 0.2 REALTIME_IDLE_SECONDS = 0.2
_MONITOR_PREVIEW_WINDOW_SECONDS = 1.0
_last_monitor_preview = ""
_last_monitor_preview_at = None
JOIN_PATTERN = re.compile( JOIN_PATTERN = re.compile(
r"OnPlayerJoined\s+(.+?)\s+\(usr_[0-9a-fA-F-]+\)" r"OnPlayerJoined\s+(.+?)\s+\(usr_[0-9a-fA-F-]+\)"
@@ -39,11 +44,6 @@ WORLD_NAME_PATTERN = re.compile(
TIME_PATTERN = re.compile( TIME_PATTERN = re.compile(
r"(\d{4}\.\d{2}\.\d{2}\s+\d{2}:\d{2}:\d{2})" r"(\d{4}\.\d{2}\.\d{2}\s+\d{2}:\d{2}:\d{2})"
) )
RELEVANT_LOG_PATTERN = re.compile(
r"OnPlayerJoined|OnPlayerLeft|Entering Room|Joining or Creating Room|worldId=|wrld_"
)
def log(level, message): def log(level, message):
now = datetime.now().strftime("%H:%M:%S") now = datetime.now().strftime("%H:%M:%S")
print(f"[{level}] {now} {message}", flush=True) print(f"[{level}] {now} {message}", flush=True)
@@ -282,6 +282,45 @@ def debug(message):
log("DEBUG", message) log("DEBUG", message)
def buildDiffMessage(previous_text, current_text):
previous_lines = (previous_text or "").splitlines()
current_lines = (current_text or "").splitlines()
if previous_text == current_text:
return ""
diff_lines = list(
difflib.unified_diff(
previous_lines,
current_lines,
fromfile="before",
tofile="after",
lineterm="",
)
)
if not diff_lines:
return ""
return "\n".join(diff_lines)
def compileLogPatterns(patterns):
compiled = []
for pattern in patterns or []:
try:
compiled.append(re.compile(pattern))
except re.error as e:
log("ERROR", f"invalid vrc_log pattern={pattern} detail={e}")
return compiled
def matchesAnyPattern(text, compiled_patterns):
if not compiled_patterns:
return True
return any(pattern.search(text) for pattern in compiled_patterns)
def formatJoinLeaveLine(line_time, action, name, state, guest_names): def formatJoinLeaveLine(line_time, action, name, state, guest_names):
staff_count = len(state["staff_present_set"]) staff_count = len(state["staff_present_set"])
member_count = len(state["member_present_set"]) member_count = len(state["member_present_set"])
@@ -295,6 +334,7 @@ def parseVrchatEvents(text, config):
staff_names = set(config["staff_names"]) staff_names = set(config["staff_names"])
self_name = config.get("self_name", "") self_name = config.get("self_name", "")
world_name_map = extractWorldNameMap(text) world_name_map = extractWorldNameMap(text)
compiled_patterns = compileLogPatterns(config.get("log_patterns", []))
state = { state = {
"location": "", "location": "",
@@ -317,8 +357,14 @@ def parseVrchatEvents(text, config):
if not line_time or line_time < start_time: if not line_time or line_time < start_time:
continue continue
line_matches = matchesAnyPattern(raw_line, compiled_patterns)
room_match = ENTERING_ROOM_PATTERN.search(raw_line)
world_name_match = WORLD_NAME_PATTERN.search(raw_line)
world_match = WORLD_PATTERN.search(raw_line) world_match = WORLD_PATTERN.search(raw_line)
if world_match: should_update_world = bool(room_match or world_name_match)
if should_update_world and world_match:
location = world_match.group(1).strip() location = world_match.group(1).strip()
world_id, instance_id = splitWorldLocation(location) world_id, instance_id = splitWorldLocation(location)
@@ -331,18 +377,16 @@ def parseVrchatEvents(text, config):
state["member_present_set"].clear() state["member_present_set"].clear()
state["last_notice_key"] = None state["last_notice_key"] = None
output_lines = [] output_lines = []
world_label = formatWorldLabel(world_id, world_name_map)
if world_label != "不明":
output_lines.append(f"ワールド入室: {world_label}")
debug(f"world changed world_id={world_id} label={world_label}")
join_match = JOIN_PATTERN.search(raw_line) join_match = JOIN_PATTERN.search(raw_line)
if join_match and state["world_id"]: if join_match and state["world_id"] and line_matches:
name = join_match.group(1).strip() name = join_match.group(1).strip()
join_leave_line = None
if isSelf(name, self_name): if isSelf(name, self_name):
self_state = "joined" self_state = "joined"
state["member_present_set"].add(name) state["member_present_set"].add(name)
output_lines.append(formatJoinLeaveLine(line_time, "join", name, state, guest_names)) join_leave_line = formatJoinLeaveLine(line_time, "join", name, state, guest_names)
output_lines.append(join_leave_line)
continue continue
role = classifyUser(name, state["guest_name_set"], state["staff_names"]) role = classifyUser(name, state["guest_name_set"], state["staff_names"])
@@ -350,23 +394,28 @@ def parseVrchatEvents(text, config):
if role == "guest": if role == "guest":
state["guest_present_set"].add(name) state["guest_present_set"].add(name)
state["member_present_set"].add(name) state["member_present_set"].add(name)
output_lines.append(formatJoinLeaveLine(line_time, "join", name, state, guest_names)) join_leave_line = formatJoinLeaveLine(line_time, "join", name, state, guest_names)
output_lines.append(join_leave_line)
appendNoticeIfNeeded(output_lines, state) appendNoticeIfNeeded(output_lines, state)
elif role == "staff": elif role == "staff":
state["staff_present_set"].add(name) state["staff_present_set"].add(name)
output_lines.append(formatJoinLeaveLine(line_time, "join", name, state, guest_names)) join_leave_line = formatJoinLeaveLine(line_time, "join", name, state, guest_names)
output_lines.append(join_leave_line)
else: else:
state["member_present_set"].add(name) state["member_present_set"].add(name)
output_lines.append(formatJoinLeaveLine(line_time, "join", name, state, guest_names)) join_leave_line = formatJoinLeaveLine(line_time, "join", name, state, guest_names)
output_lines.append(join_leave_line)
continue continue
left_match = LEFT_PATTERN.search(raw_line) left_match = LEFT_PATTERN.search(raw_line)
if left_match and state["world_id"]: if left_match and state["world_id"] and line_matches:
name = left_match.group(1).strip() name = left_match.group(1).strip()
join_leave_line = None
if isSelf(name, self_name): if isSelf(name, self_name):
self_state = "left" self_state = "left"
state["member_present_set"].discard(name) state["member_present_set"].discard(name)
output_lines.append(formatJoinLeaveLine(line_time, "leave", name, state, guest_names)) join_leave_line = formatJoinLeaveLine(line_time, "leave", name, state, guest_names)
output_lines.append(join_leave_line)
continue continue
role = classifyUser(name, state["guest_name_set"], state["staff_names"]) role = classifyUser(name, state["guest_name_set"], state["staff_names"])
@@ -374,14 +423,18 @@ def parseVrchatEvents(text, config):
if role == "guest": if role == "guest":
state["guest_present_set"].discard(name) state["guest_present_set"].discard(name)
state["member_present_set"].discard(name) state["member_present_set"].discard(name)
output_lines.append(formatJoinLeaveLine(line_time, "leave", name, state, guest_names)) join_leave_line = formatJoinLeaveLine(line_time, "leave", name, state, guest_names)
output_lines.append(join_leave_line)
appendNoticeIfNeeded(output_lines, state) appendNoticeIfNeeded(output_lines, state)
elif role == "staff": elif role == "staff":
state["staff_present_set"].discard(name) state["staff_present_set"].discard(name)
output_lines.append(formatJoinLeaveLine(line_time, "leave", name, state, guest_names)) join_leave_line = formatJoinLeaveLine(line_time, "leave", name, state, guest_names)
output_lines.append(join_leave_line)
else: else:
state["member_present_set"].discard(name) state["member_present_set"].discard(name)
output_lines.append(formatJoinLeaveLine(line_time, "leave", name, state, guest_names)) join_leave_line = formatJoinLeaveLine(line_time, "leave", name, state, guest_names)
output_lines.append(join_leave_line)
continue
return output_lines, self_state return output_lines, self_state
@@ -412,39 +465,35 @@ def grepVrchatLog(pattern=None):
def collectVrchatLog(pattern=None, notify_changed=True): def collectVrchatLog(pattern=None, notify_changed=True):
config = loadVrcLogConfig() config = loadVrcLogConfig()
latest_log = findLatestVrchatLog() latest_log = findLatestVrchatLog()
log("ACTION", f"parseVrchatLog file={latest_log}")
log("INFO", f'event_file={config["event_file"]}')
text = readTextSafe(latest_log) text = readTextSafe(latest_log)
lines, self_state = parseVrchatEvents(text, config) lines, self_state = parseVrchatEvents(text, config)
has_join_leave = any("[join]" in line or "[leave]" in line for line in lines) output_path = None
if has_join_leave: if lines:
lines = ["[DEBUG] 入退出イベント検出"] + lines output_text = "\n".join(lines)
output_path = appendRuntimeLog("VRC LOG", output_text)
output_text = "\n".join(lines) if lines else "[INFO] 対象イベントなし" join_leave_lines = [line for line in lines if "[join]" in line or "[leave]" in line]
output_path = appendRuntimeLog("VRC LOG", output_text) if join_leave_lines:
appendJoinLeaveLog("\n".join(join_leave_lines))
if notify_changed: if notify_changed:
global _last_sent_output global _last_sent_output
should_send = output_text != _last_sent_output or has_join_leave diff_text = buildDiffMessage(_last_sent_output, output_text)
if should_send: if diff_text:
try: try:
ok = sendWebhook(output_text) ok = sendWebhook(diff_text)
if ok: if ok:
log("INFO", "Discord webhook sent") log("INFO", "Discord webhook diff sent")
_last_sent_output = output_text _last_sent_output = output_text
else: else:
log("ERROR", "Discord webhook send failed") log("ERROR", "Discord webhook diff send failed")
except Exception as e: except Exception as e:
log("ERROR", f"Discord webhook failed detail={e}") log("ERROR", f"Discord webhook diff failed detail={e}")
log("INFO", f"result appended={output_path}")
if lines: if lines:
for line in lines: for line in lines:
print(line, flush=True) print(line, flush=True)
else:
debug("対象イベントなし")
return output_path return output_path
@@ -499,9 +548,9 @@ def _watchVrchatLogChanges(stop_event, label, on_relevant_change):
def _monitor_loop(): def _monitor_loop():
def on_relevant_change(text): def on_relevant_change(text):
preview = " ".join(text.splitlines()[:3]).strip() if not any(token in text for token in ("OnPlayerJoined", "OnPlayerLeft", "Entering Room", "Joining or Creating Room", "worldId=", "wrld_")):
if preview: return
debug(f"VRC log monitor change preview={preview[:180]}")
collectVrchatLog(notify_changed=True) collectVrchatLog(notify_changed=True)
_watchVrchatLogChanges( _watchVrchatLogChanges(