This commit is contained in:
every_holiday
2026-06-22 22:29:10 +09:00
parent 072e819b0b
commit cbd8911c25
18 changed files with 621 additions and 40 deletions

View File

@@ -18,6 +18,8 @@ from discord_control.actions import setDiscordMuted
_monitor_thread = None
_monitor_stop_event = threading.Event()
_last_sent_output = None
_last_join_leave_output = ""
_monitor_vrc_state = None
_self_monitor_thread = None
_self_monitor_stop_event = threading.Event()
_last_self_state = None
@@ -39,7 +41,10 @@ ENTERING_ROOM_PATTERN = re.compile(
r"\[Behaviour\]\s+(?:Entering Room|Joining or Creating Room):\s+(.+)$"
)
WORLD_NAME_PATTERN = re.compile(
r"worldId=(wrld_[0-9a-fA-F-]+)(?::[^,}]*)?,\s*worldName=([^,}]+)"
r"worldId=(wrld_[0-9a-fA-F-]+)(?::[^,}]*)?,\s*(?:instanceId=([^,}]+),\s*)?worldName=([^,}]+)"
)
WORLD_LOCATION_PATTERN = re.compile(
r"worldId=(wrld_[0-9a-fA-F-]+)(?::[^,}]*)?,\s*instanceId=([^,}]+)"
)
TIME_PATTERN = re.compile(
r"(\d{4}\.\d{2}\.\d{2}\s+\d{2}:\d{2}:\d{2})"
@@ -77,7 +82,9 @@ param(
$Payload = @{ content = $Message } | ConvertTo-Json -Compress
$Body = [System.Text.Encoding]::UTF8.GetBytes($Payload)
$Response = Invoke-WebRequest -Uri $WebhookUrl -Method Post -ContentType 'application/json; charset=utf-8' -Body $Body -UseBasicParsing
Write-Output ("STATUS=" + $Response.StatusCode)
if ($Response.StatusCode -ne 200) {
Write-Output ("STATUS=" + $Response.StatusCode)
}
'''
ps_path = None
@@ -127,7 +134,7 @@ param(
stdout = (result.stdout or "").strip()
if stdout:
log("INFO", f"Discord webhook PowerShell stdout={stdout}")
log("DEBUG", f"Discord webhook PowerShell stdout={stdout}")
log("INFO", "Discord webhook sent via PowerShell")
return True
@@ -205,7 +212,7 @@ def extractWorldNameMap(text):
match = WORLD_NAME_PATTERN.search(raw_line)
if match:
world_id = match.group(1).strip()
world_name = match.group(2).strip()
world_name = match.group(3).strip()
if world_id and world_name:
world_name_map[world_id] = world_name
continue
@@ -322,10 +329,9 @@ def matchesAnyPattern(text, 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"])
total_count = len(state["present_set"])
guest_count = len(state["guest_names"])
return f'[{formatClock(line_time)}][{action}] {name} {member_count - staff_count}/{guest_count}'
return f'[{formatClock(line_time)}][{action}] {name} {total_count}/{guest_count}'
def parseVrchatEvents(text, config):
@@ -340,12 +346,11 @@ def parseVrchatEvents(text, config):
"location": "",
"world_id": "",
"instance_id": "",
"present_set": set(),
"guest_names": guest_names,
"guest_name_set": set(guest_names),
"staff_names": staff_names,
"guest_present_set": set(),
"staff_present_set": set(),
"member_present_set": set(),
"missing_count": config["missing_count"],
"last_notice_key": None,
}
@@ -383,9 +388,8 @@ def parseVrchatEvents(text, config):
state["location"] = location or world_id
state["world_id"] = world_id
state["instance_id"] = instance_id
state["present_set"].clear()
state["guest_present_set"].clear()
state["staff_present_set"].clear()
state["member_present_set"].clear()
state["last_notice_key"] = None
output_lines = []
world_label = formatWorldLabel(world_id, world_name_map)
@@ -398,7 +402,7 @@ def parseVrchatEvents(text, config):
join_leave_line = None
if isSelf(name, self_name):
self_state = "joined"
state["member_present_set"].add(name)
state["present_set"].add(name)
join_leave_line = formatJoinLeaveLine(line_time, "join", name, state, guest_names)
output_lines.append(join_leave_line)
continue
@@ -407,16 +411,16 @@ def parseVrchatEvents(text, config):
if role == "guest":
state["guest_present_set"].add(name)
state["member_present_set"].add(name)
state["present_set"].add(name)
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)
state["present_set"].add(name)
join_leave_line = formatJoinLeaveLine(line_time, "join", name, state, guest_names)
output_lines.append(join_leave_line)
else:
state["member_present_set"].add(name)
state["present_set"].add(name)
join_leave_line = formatJoinLeaveLine(line_time, "join", name, state, guest_names)
output_lines.append(join_leave_line)
continue
@@ -427,7 +431,7 @@ def parseVrchatEvents(text, config):
join_leave_line = None
if isSelf(name, self_name):
self_state = "left"
state["member_present_set"].discard(name)
state["present_set"].discard(name)
join_leave_line = formatJoinLeaveLine(line_time, "leave", name, state, guest_names)
output_lines.append(join_leave_line)
continue
@@ -436,16 +440,16 @@ def parseVrchatEvents(text, config):
if role == "guest":
state["guest_present_set"].discard(name)
state["member_present_set"].discard(name)
state["present_set"].discard(name)
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)
state["present_set"].discard(name)
join_leave_line = formatJoinLeaveLine(line_time, "leave", name, state, guest_names)
output_lines.append(join_leave_line)
else:
state["member_present_set"].discard(name)
state["present_set"].discard(name)
join_leave_line = formatJoinLeaveLine(line_time, "leave", name, state, guest_names)
output_lines.append(join_leave_line)
continue
@@ -453,6 +457,142 @@ def parseVrchatEvents(text, config):
return output_lines, self_state
def createVrcState(config, world_name_map=None):
guest_names = config["guest_names"]
return {
"location": "",
"world_id": "",
"instance_id": "",
"present_set": set(),
"guest_names": guest_names,
"guest_name_set": set(guest_names),
"staff_names": set(config["staff_names"]),
"guest_present_set": set(),
"missing_count": config["missing_count"],
"last_notice_key": None,
"world_name_map": world_name_map or {},
"last_join_leave_output": "",
}
def processVrcText(text, config, state):
return processVrcLines(text.splitlines(), config, state)
def processVrcLines(raw_lines, config, state):
output_lines = []
world_name_map = state.get("world_name_map") or {}
guest_names = state["guest_names"]
staff_names = state["staff_names"]
self_name = config.get("self_name", "")
compiled_patterns = compileLogPatterns(config.get("log_patterns", []))
for raw_line in raw_lines:
line_time = parseLineTime(raw_line)
if not line_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_location_match = WORLD_LOCATION_PATTERN.search(raw_line)
world_match = WORLD_PATTERN.search(raw_line)
location = ""
world_id = ""
instance_id = ""
if world_name_match:
world_id = world_name_match.group(1).strip()
instance_id = (world_name_match.group(2) or "").strip()
location = world_id
elif world_location_match:
world_id = world_location_match.group(1).strip()
instance_id = world_location_match.group(2).strip()
location = f"{world_id}:{instance_id}" if instance_id else world_id
elif room_match and world_match:
location = world_match.group(1).strip()
world_id, instance_id = splitWorldLocation(location)
elif world_match:
location = world_match.group(1).strip()
world_id, instance_id = splitWorldLocation(location)
next_location = f"{world_id}:{instance_id}" if world_id else ""
if next_location and next_location != state["location"]:
state["location"] = next_location
state["world_id"] = world_id
state["instance_id"] = instance_id
state["present_set"].clear()
state["guest_present_set"].clear()
state["last_notice_key"] = None
state["last_join_leave_output"] = ""
world_label = formatWorldLabel(world_id, world_name_map)
if world_label != "不明":
output_lines.append(f"ワールド入室: {world_label}")
join_match = JOIN_PATTERN.search(raw_line)
if join_match and line_matches:
name = join_match.group(1).strip()
if isSelf(name, self_name):
state["present_set"].add(name)
output_lines.append(
formatJoinLeaveLine(line_time, "join", name, state, guest_names)
)
continue
role = classifyUser(name, state["guest_name_set"], staff_names)
if role == "guest":
state["guest_present_set"].add(name)
state["present_set"].add(name)
output_lines.append(
formatJoinLeaveLine(line_time, "join", name, state, guest_names)
)
appendNoticeIfNeeded(output_lines, state)
elif role == "staff":
state["present_set"].add(name)
output_lines.append(
formatJoinLeaveLine(line_time, "join", name, state, guest_names)
)
else:
state["present_set"].add(name)
output_lines.append(
formatJoinLeaveLine(line_time, "join", name, state, guest_names)
)
continue
left_match = LEFT_PATTERN.search(raw_line)
if left_match and line_matches:
name = left_match.group(1).strip()
if isSelf(name, self_name):
state["present_set"].discard(name)
output_lines.append(
formatJoinLeaveLine(line_time, "leave", name, state, guest_names)
)
continue
role = classifyUser(name, state["guest_name_set"], staff_names)
if role == "guest":
state["guest_present_set"].discard(name)
state["present_set"].discard(name)
output_lines.append(
formatJoinLeaveLine(line_time, "leave", name, state, guest_names)
)
appendNoticeIfNeeded(output_lines, state)
elif role == "staff":
state["present_set"].discard(name)
output_lines.append(
formatJoinLeaveLine(line_time, "leave", name, state, guest_names)
)
else:
state["present_set"].discard(name)
output_lines.append(
formatJoinLeaveLine(line_time, "leave", name, state, guest_names)
)
continue
return output_lines, state
def extractLatestSelfLogState(text, self_name):
if not self_name:
return None
@@ -481,7 +621,9 @@ def collectVrchatLog(pattern=None, notify_changed=True):
latest_log = findLatestVrchatLog()
text = readTextSafe(latest_log)
lines, self_state = parseVrchatEvents(text, config)
world_name_map = extractWorldNameMap(text)
state = createVrcState(config, world_name_map=world_name_map)
lines, state = processVrcText(text, config, state)
output_text = "\n".join(lines) if lines else ""
output_path = None
@@ -489,13 +631,22 @@ def collectVrchatLog(pattern=None, notify_changed=True):
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))
global _last_join_leave_output
current_join_leave_output = "\n".join(join_leave_lines)
join_leave_diff = buildDiffMessage(
_last_join_leave_output,
current_join_leave_output,
)
if join_leave_diff:
appendJoinLeaveLog(join_leave_diff)
_last_join_leave_output = current_join_leave_output
if notify_changed:
global _last_sent_output
diff_text = buildDiffMessage(_last_sent_output, output_text)
if diff_text:
try:
appendRuntimeLog("VRC LOG DIFF", diff_text)
ok = sendWebhook(diff_text)
if ok:
log("INFO", "Discord webhook diff sent")
@@ -512,6 +663,16 @@ def collectVrchatLog(pattern=None, notify_changed=True):
return output_path
def initializeVrcMonitorState():
global _monitor_vrc_state
config = loadVrcLogConfig()
latest_log = findLatestVrchatLog()
text = readTextSafe(latest_log)
world_name_map = extractWorldNameMap(text)
_monitor_vrc_state = createVrcState(config, world_name_map=world_name_map)
return _monitor_vrc_state
def _readNewText(path, offset):
for encoding in ("utf-8", "utf-8-sig", "cp932", "shift_jis"):
try:
@@ -561,11 +722,54 @@ def _watchVrchatLogChanges(stop_event, label, on_relevant_change):
def _monitor_loop():
global _monitor_vrc_state
if _monitor_vrc_state is None:
try:
initializeVrcMonitorState()
except Exception as e:
log("ERROR", f"VRC log monitor init failed detail={e}")
def on_relevant_change(text):
global _monitor_vrc_state
global _last_sent_output
global _last_join_leave_output
if not any(token in text for token in ("OnPlayerJoined", "OnPlayerLeft", "Entering Room", "Joining or Creating Room", "worldId=", "wrld_")):
return
collectVrchatLog(notify_changed=True)
try:
config = loadVrcLogConfig()
if _monitor_vrc_state is None:
initializeVrcMonitorState()
lines, state = processVrcText(text, config, _monitor_vrc_state)
_monitor_vrc_state = state
output_text = "\n".join(lines) if lines else ""
if not output_text:
return
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:
current_join_leave_output = "\n".join(join_leave_lines)
if current_join_leave_output != _last_join_leave_output:
join_leave_diff = buildDiffMessage(_last_join_leave_output, current_join_leave_output)
if join_leave_diff:
appendJoinLeaveLog(join_leave_diff)
_last_join_leave_output = current_join_leave_output
global _last_sent_output
diff_text = buildDiffMessage(_last_sent_output, output_text)
if diff_text:
appendRuntimeLog("VRC LOG DIFF", diff_text)
ok = sendWebhook(diff_text)
if ok:
log("INFO", "Discord webhook diff sent")
_last_sent_output = output_text
else:
log("ERROR", "Discord webhook diff send failed")
for line in lines:
print(line, flush=True)
except Exception as e:
log("ERROR", f"VRC log monitor chunk failed detail={e}")
_watchVrchatLogChanges(
_monitor_stop_event,