Restrict VRC log output to user events
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user