|
|
|
@@ -8,6 +8,7 @@ import hashlib
|
|
|
|
|
import json
|
|
|
|
|
import logging
|
|
|
|
|
import os
|
|
|
|
|
import random
|
|
|
|
|
import re
|
|
|
|
|
import shlex
|
|
|
|
|
import signal
|
|
|
|
@@ -104,6 +105,8 @@ EXTENSION_SOCKET_ROOT = Path("/run/jwebot-extension-host")
|
|
|
|
|
MAX_EVENT_TEXT = 5_000
|
|
|
|
|
MAX_REPLY_CHARS = 900
|
|
|
|
|
MAX_CONTEXT_TURNS = 6
|
|
|
|
|
MULTI_REPLY_DELAY_MIN_SECONDS = 0.5
|
|
|
|
|
MULTI_REPLY_DELAY_MAX_SECONDS = 1.0
|
|
|
|
|
OCR_EVENT_SOURCES = frozenset({"ocr", "rapidocr-local"})
|
|
|
|
|
OCR_SEMANTIC_DUPLICATE_SECONDS = 8.0
|
|
|
|
|
OCR_RESOURCE_MUTATION_DUPLICATE_SECONDS = 75.0
|
|
|
|
@@ -117,6 +120,24 @@ OCR_RESOURCE_MUTATION_REASONS = frozenset(
|
|
|
|
|
"local_transfer",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
LOCAL_CONTEXT_EXCLUDED_REASONS = frozenset(
|
|
|
|
|
{
|
|
|
|
|
"local_feature_list",
|
|
|
|
|
"local_context_summary",
|
|
|
|
|
"local_person_directory",
|
|
|
|
|
"local_person_info",
|
|
|
|
|
"local_person_memory",
|
|
|
|
|
"local_my_memory",
|
|
|
|
|
"local_forget_memory",
|
|
|
|
|
"local_memory_help",
|
|
|
|
|
"local_local_status",
|
|
|
|
|
"local_server_status",
|
|
|
|
|
"local_jwebot_version",
|
|
|
|
|
"local_telemetry_debug",
|
|
|
|
|
"local_deepseek_balance",
|
|
|
|
|
"local_unknown",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
MAX_OUTBOX_ATTEMPTS = 3
|
|
|
|
|
DIRECT_CHAT_GENERATION_WORKERS = 2
|
|
|
|
|
DIRECT_API_SOCKET_TIMEOUT_SECONDS = 18.0
|
|
|
|
@@ -1645,21 +1666,37 @@ class Ledger:
|
|
|
|
|
with self.lock:
|
|
|
|
|
rows = self.connection.execute(
|
|
|
|
|
"""
|
|
|
|
|
SELECT question, response_text
|
|
|
|
|
SELECT question, response_text, route, route_reason
|
|
|
|
|
FROM events
|
|
|
|
|
WHERE group_fingerprint = ?
|
|
|
|
|
AND event_id != ?
|
|
|
|
|
AND route = 'chat'
|
|
|
|
|
AND status IN ('chat_completed', 'chat_delivered')
|
|
|
|
|
AND (
|
|
|
|
|
(route = 'chat' AND status IN ('chat_completed', 'chat_delivered'))
|
|
|
|
|
OR (route = 'local' AND status = 'local_completed')
|
|
|
|
|
)
|
|
|
|
|
AND response_text != ''
|
|
|
|
|
ORDER BY created_at DESC
|
|
|
|
|
LIMIT ?
|
|
|
|
|
""",
|
|
|
|
|
(group_fingerprint, exclude_event_id, limit),
|
|
|
|
|
(group_fingerprint, exclude_event_id, limit * 3),
|
|
|
|
|
).fetchall()
|
|
|
|
|
result: list[tuple[str, str]] = []
|
|
|
|
|
for row in reversed(rows):
|
|
|
|
|
result.append((str(row["question"]), str(row["response_text"])))
|
|
|
|
|
if (
|
|
|
|
|
row["route"] == "local"
|
|
|
|
|
and str(row["route_reason"]) in LOCAL_CONTEXT_EXCLUDED_REASONS
|
|
|
|
|
):
|
|
|
|
|
continue
|
|
|
|
|
response_text = str(row["response_text"])
|
|
|
|
|
parts = unpack_reply_parts(response_text)
|
|
|
|
|
result.append(
|
|
|
|
|
(
|
|
|
|
|
str(row["question"]),
|
|
|
|
|
"\n".join(parts) if parts is not None else response_text,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
if len(result) >= limit:
|
|
|
|
|
break
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
def local_game(
|
|
|
|
@@ -6749,6 +6786,53 @@ class HourlyBroadcastEngine:
|
|
|
|
|
lines.append("回答格式:/回答 A")
|
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
|
|
|
|
def start_trivia_question(
|
|
|
|
|
self,
|
|
|
|
|
*,
|
|
|
|
|
group: str,
|
|
|
|
|
local_now: datetime,
|
|
|
|
|
) -> str:
|
|
|
|
|
question = self.select_trivia_question(group=group, local_now=local_now)
|
|
|
|
|
message = self.render_trivia_message(question)
|
|
|
|
|
expires_at = (
|
|
|
|
|
local_now + timedelta(minutes=TRIVIA_ANSWER_WINDOW_MINUTES)
|
|
|
|
|
).timestamp()
|
|
|
|
|
self.ledger.save_local_game(
|
|
|
|
|
group,
|
|
|
|
|
TRIVIA_GAME_KEY,
|
|
|
|
|
{
|
|
|
|
|
"question_id": str(question["id"]),
|
|
|
|
|
"base_id": str(question.get("base_id") or question["id"]),
|
|
|
|
|
"question": str(question["question"]),
|
|
|
|
|
"options": list(question["options"]),
|
|
|
|
|
"answer": str(question["answer"]),
|
|
|
|
|
"explanation": str(question["explanation"]),
|
|
|
|
|
"rarity": int(question.get("rarity", 3)),
|
|
|
|
|
"reward": int(question.get("reward", 50)),
|
|
|
|
|
"created_at": local_now.timestamp(),
|
|
|
|
|
"expires_at": expires_at,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
self.remember_trivia_question(group, question)
|
|
|
|
|
return message
|
|
|
|
|
|
|
|
|
|
def force_trivia_message(
|
|
|
|
|
self,
|
|
|
|
|
*,
|
|
|
|
|
group: str,
|
|
|
|
|
now: datetime | None = None,
|
|
|
|
|
) -> str:
|
|
|
|
|
local_now = self.in_beijing_timezone(now)
|
|
|
|
|
active = self.ledger.local_game(group, TRIVIA_GAME_KEY)
|
|
|
|
|
if active:
|
|
|
|
|
try:
|
|
|
|
|
if float(active.get("expires_at", 0)) > local_now.timestamp():
|
|
|
|
|
return "已经有一题在进行中喵\n" + self.render_trivia_message(active)
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
|
return "冷门问答状态坏了喵"
|
|
|
|
|
self.ledger.clear_local_game(group, TRIVIA_GAME_KEY)
|
|
|
|
|
return self.start_trivia_question(group=group, local_now=local_now)
|
|
|
|
|
|
|
|
|
|
def due_trivia_broadcast(
|
|
|
|
|
self,
|
|
|
|
|
*,
|
|
|
|
@@ -6772,28 +6856,7 @@ class HourlyBroadcastEngine:
|
|
|
|
|
seed = f"{group}|{local_date}|{local_now.hour:02d}|trivia-probability"
|
|
|
|
|
if deterministic_int(seed, 0, 99) >= TRIVIA_QUIZ_PROBABILITY_PERCENT:
|
|
|
|
|
return None
|
|
|
|
|
question = self.select_trivia_question(group=group, local_now=local_now)
|
|
|
|
|
message = self.render_trivia_message(question)
|
|
|
|
|
expires_at = (
|
|
|
|
|
local_now + timedelta(minutes=TRIVIA_ANSWER_WINDOW_MINUTES)
|
|
|
|
|
).timestamp()
|
|
|
|
|
self.ledger.save_local_game(
|
|
|
|
|
group,
|
|
|
|
|
TRIVIA_GAME_KEY,
|
|
|
|
|
{
|
|
|
|
|
"question_id": str(question["id"]),
|
|
|
|
|
"base_id": str(question.get("base_id") or question["id"]),
|
|
|
|
|
"question": str(question["question"]),
|
|
|
|
|
"options": list(question["options"]),
|
|
|
|
|
"answer": str(question["answer"]),
|
|
|
|
|
"explanation": str(question["explanation"]),
|
|
|
|
|
"rarity": int(question.get("rarity", 3)),
|
|
|
|
|
"reward": int(question.get("reward", 50)),
|
|
|
|
|
"created_at": local_now.timestamp(),
|
|
|
|
|
"expires_at": expires_at,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
self.remember_trivia_question(group, question)
|
|
|
|
|
message = self.start_trivia_question(group=group, local_now=local_now)
|
|
|
|
|
return ClockBroadcast(
|
|
|
|
|
group_fingerprint=group,
|
|
|
|
|
slot_key=slot_key,
|
|
|
|
@@ -8038,6 +8101,11 @@ class EdgeApplication:
|
|
|
|
|
try:
|
|
|
|
|
command_kind = local_command_kind(str(row["question"]))
|
|
|
|
|
reply = (
|
|
|
|
|
self.clock.force_trivia_message(
|
|
|
|
|
group=str(row["group_fingerprint"])
|
|
|
|
|
)
|
|
|
|
|
if command_kind == "trivia_start"
|
|
|
|
|
else
|
|
|
|
|
self._execute_local_extension(row, event)
|
|
|
|
|
if (
|
|
|
|
|
self.config.local_extensions_enabled
|
|
|
|
@@ -8705,6 +8773,13 @@ class EdgeApplication:
|
|
|
|
|
if "missing expected group" in output:
|
|
|
|
|
raise EdgeError("missing expected group")
|
|
|
|
|
raise EdgeError("delivery helper failed")
|
|
|
|
|
if index < len(replies):
|
|
|
|
|
time.sleep(
|
|
|
|
|
random.uniform(
|
|
|
|
|
MULTI_REPLY_DELAY_MIN_SECONDS,
|
|
|
|
|
MULTI_REPLY_DELAY_MAX_SECONDS,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
self.telemetry.elapsed(
|
|
|
|
|
"edge.delivery.process",
|
|
|
|
|
started,
|
|
|
|
|