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 re
import difflib
import subprocess
import tempfile
import threading
@@ -11,6 +12,7 @@ from pathlib import Path
from common.config_loader import loadVrcLogConfig
from common.config_loader import getSecretValue
from common.runtime_log import appendRuntimeLog
from common.runtime_log import appendJoinLeaveLog
from discord_control.actions import setDiscordMuted
_monitor_thread = None
@@ -20,6 +22,9 @@ _self_monitor_thread = None
_self_monitor_stop_event = threading.Event()
_last_self_state = None
REALTIME_IDLE_SECONDS = 0.2
_MONITOR_PREVIEW_WINDOW_SECONDS = 1.0
_last_monitor_preview = ""
_last_monitor_preview_at = None
JOIN_PATTERN = re.compile(
r"OnPlayerJoined\s+(.+?)\s+\(usr_[0-9a-fA-F-]+\)"
@@ -39,11 +44,6 @@ WORLD_NAME_PATTERN = re.compile(
TIME_PATTERN = re.compile(
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):
now = datetime.now().strftime("%H:%M:%S")
print(f"[{level}] {now} {message}", flush=True)
@@ -282,6 +282,45 @@ def 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):
staff_count = len(state["staff_present_set"])
member_count = len(state["member_present_set"])
@@ -295,6 +334,7 @@ def parseVrchatEvents(text, config):
staff_names = set(config["staff_names"])
self_name = config.get("self_name", "")
world_name_map = extractWorldNameMap(text)
compiled_patterns = compileLogPatterns(config.get("log_patterns", []))
state = {
"location": "",
@@ -317,8 +357,14 @@ def parseVrchatEvents(text, config):
if not line_time or line_time < start_time:
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)
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()
world_id, instance_id = splitWorldLocation(location)
@@ -331,18 +377,16 @@ def parseVrchatEvents(text, config):
state["member_present_set"].clear()
state["last_notice_key"] = None
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)
if join_match and state["world_id"]:
if join_match and state["world_id"] and line_matches:
name = join_match.group(1).strip()
join_leave_line = None
if isSelf(name, self_name):
self_state = "joined"
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
role = classifyUser(name, state["guest_name_set"], state["staff_names"])
@@ -350,23 +394,28 @@ def parseVrchatEvents(text, config):
if role == "guest":
state["guest_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)
elif role == "staff":
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:
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
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()
join_leave_line = None
if isSelf(name, self_name):
self_state = "left"
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
role = classifyUser(name, state["guest_name_set"], state["staff_names"])
@@ -374,14 +423,18 @@ def parseVrchatEvents(text, config):
if role == "guest":
state["guest_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)
elif role == "staff":
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:
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
@@ -412,39 +465,35 @@ def grepVrchatLog(pattern=None):
def collectVrchatLog(pattern=None, notify_changed=True):
config = loadVrcLogConfig()
latest_log = findLatestVrchatLog()
log("ACTION", f"parseVrchatLog file={latest_log}")
log("INFO", f'event_file={config["event_file"]}')
text = readTextSafe(latest_log)
lines, self_state = parseVrchatEvents(text, config)
has_join_leave = any("[join]" in line or "[leave]" in line for line in lines)
if has_join_leave:
lines = ["[DEBUG] 入退出イベント検出"] + lines
output_text = "\n".join(lines) if lines else "[INFO] 対象イベントなし"
output_path = None
if lines:
output_text = "\n".join(lines)
output_path = appendRuntimeLog("VRC LOG", output_text)
join_leave_lines = [line for line in lines if "[join]" in line or "[leave]" in line]
if join_leave_lines:
appendJoinLeaveLog("\n".join(join_leave_lines))
if notify_changed:
global _last_sent_output
should_send = output_text != _last_sent_output or has_join_leave
if should_send:
diff_text = buildDiffMessage(_last_sent_output, output_text)
if diff_text:
try:
ok = sendWebhook(output_text)
ok = sendWebhook(diff_text)
if ok:
log("INFO", "Discord webhook sent")
log("INFO", "Discord webhook diff sent")
_last_sent_output = output_text
else:
log("ERROR", "Discord webhook send failed")
log("ERROR", "Discord webhook diff send failed")
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:
for line in lines:
print(line, flush=True)
else:
debug("対象イベントなし")
return output_path
@@ -499,9 +548,9 @@ def _watchVrchatLogChanges(stop_event, label, on_relevant_change):
def _monitor_loop():
def on_relevant_change(text):
preview = " ".join(text.splitlines()[:3]).strip()
if preview:
debug(f"VRC log monitor change preview={preview[:180]}")
if not any(token in text for token in ("OnPlayerJoined", "OnPlayerLeft", "Entering Room", "Joining or Creating Room", "worldId=", "wrld_")):
return
collectVrchatLog(notify_changed=True)
_watchVrchatLogChanges(