feat: broadcast消息事件块化——stdout hook从逐行碎片改为缓冲聚合(静默2s/满25行/atexit切块),相邻重复行去重,单连接写入(老莫:每条消息详情=摘要无信息量)
This commit is contained in:
@@ -13,7 +13,7 @@
|
|||||||
from messenger import send
|
from messenger import send
|
||||||
send(title="MoFin持仓异动", content="...") # 自动按当前脚本/job路由
|
send(title="MoFin持仓异动", content="...") # 自动按当前脚本/job路由
|
||||||
"""
|
"""
|
||||||
import os, sys, json, sqlite3
|
import os, sys, time, json, sqlite3
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
DB = "/home/hmo/MoFin/data/mofin.db"
|
DB = "/home/hmo/MoFin/data/mofin.db"
|
||||||
@@ -129,31 +129,50 @@ def send_action_recommendation(title, content, source=None):
|
|||||||
|
|
||||||
|
|
||||||
class _MessengerStdout:
|
class _MessengerStdout:
|
||||||
|
# 2026-08-24 事件块聚合:逐行碎片→一块一条消息(老莫:broadcast每条消息详情=摘要,无信息量)
|
||||||
|
# 块边界:静默>2秒 或 满25行;atexit强制flush。xmpp实时输出不缓冲(hermes靠stdout推送)。
|
||||||
|
MAX_BLOCK_LINES = 25
|
||||||
|
MAX_IDLE_SEC = 2.0
|
||||||
|
|
||||||
def __init__(self, orig, script):
|
def __init__(self, orig, script):
|
||||||
self.orig = orig
|
self.orig = orig
|
||||||
self.script = script
|
self.script = script
|
||||||
self.channel = get_channel(script)
|
self.channel = get_channel(script)
|
||||||
|
self._buf = []
|
||||||
|
self._last = 0.0
|
||||||
|
import atexit
|
||||||
|
atexit.register(self._flush_buf)
|
||||||
|
|
||||||
def write(self, s):
|
def write(self, s):
|
||||||
if self.channel in ("xmpp", "both"):
|
if self.channel in ("xmpp", "both"):
|
||||||
# xmpp 通道: 输出给 hermes(推xmpp) + 写入 broadcast 表
|
# xmpp 通道: 实时输出给 hermes(推xmpp)——不缓冲
|
||||||
self.orig.write(s); self.orig.flush()
|
self.orig.write(s); self.orig.flush()
|
||||||
_write_broadcast_from_stdout(s, self.script)
|
now = time.time()
|
||||||
else:
|
if self._buf and now - self._last > self.MAX_IDLE_SEC:
|
||||||
# broadcast 通道: 不输出 stdout(不推 xmpp), 只写入 broadcast 表
|
self._flush_buf() # 静默超2秒: 上一个事件块结束
|
||||||
_write_broadcast_from_stdout(s, self.script)
|
self._buf.append(s)
|
||||||
|
self._last = now
|
||||||
|
if len(self._buf) >= self.MAX_BLOCK_LINES:
|
||||||
|
self._flush_buf()
|
||||||
return len(s)
|
return len(s)
|
||||||
|
|
||||||
|
def _flush_buf(self):
|
||||||
|
if self._buf:
|
||||||
|
text = "".join(self._buf)
|
||||||
|
self._buf = []
|
||||||
|
_flush_broadcast_block(text.split("\n"), self.script)
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
if self.channel in ("xmpp", "both"):
|
if self.channel in ("xmpp", "both"):
|
||||||
self.orig.flush()
|
self.orig.flush()
|
||||||
|
|
||||||
|
|
||||||
def _write_broadcast_from_stdout(text, script):
|
def _flush_broadcast_block(lines, script):
|
||||||
"""把 stdout 内容直接写入 broadcast_messages(不经 send, 避免 xmpp 递归)"""
|
"""把一批 stdout 行聚合成一条 broadcast 消息(2026-08-24 事件块化)。
|
||||||
if not text or not text.strip():
|
过滤噪音前缀 + 相邻重复行去重(根治逐行碎片+双写重复)+ 单连接一次写入。"""
|
||||||
return
|
|
||||||
t = text.strip()
|
|
||||||
skip_prefix = ("[DB]", "[SYNC", "[guard", "[regime] ", "⏱", "📊 ", "📥 ", "💾 ", "🔄 ", "[SILENT]")
|
skip_prefix = ("[DB]", "[SYNC", "[guard", "[regime] ", "⏱", "📊 ", "📥 ", "💾 ", "🔄 ", "[SILENT]")
|
||||||
for line in t.split("\n"):
|
out = []
|
||||||
|
for line in lines:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line or len(line) < 3:
|
if not line or len(line) < 3:
|
||||||
continue
|
continue
|
||||||
@@ -161,16 +180,29 @@ def _write_broadcast_from_stdout(text, script):
|
|||||||
continue
|
continue
|
||||||
if line.startswith("【") and "】" in line and "MoFin·" in line:
|
if line.startswith("【") and "】" in line and "MoFin·" in line:
|
||||||
continue # 跳过已格式化的 messenger 输出, 防递归
|
continue # 跳过已格式化的 messenger 输出, 防递归
|
||||||
try:
|
if out and out[-1] == line:
|
||||||
category = _classify(_job_title(script), line)
|
continue # 相邻重复去重
|
||||||
conn = sqlite3.connect(DB, timeout=30)
|
out.append(line)
|
||||||
conn.execute(
|
if not out:
|
||||||
"INSERT INTO broadcast_messages (ts, category, title, content, source) VALUES (?,?,?,?,?)",
|
return
|
||||||
(datetime.now().isoformat(), category, _job_title(script), line, script))
|
content = "\n".join(out)
|
||||||
conn.commit()
|
try:
|
||||||
conn.close()
|
category = _classify(_job_title(script), content)
|
||||||
except Exception:
|
conn = sqlite3.connect(DB, timeout=30)
|
||||||
pass
|
conn.execute(
|
||||||
|
"INSERT INTO broadcast_messages (ts, category, title, content, source) VALUES (?,?,?,?,?)",
|
||||||
|
(datetime.now().isoformat(), category, _job_title(script), content, script))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _write_broadcast_from_stdout(text, script):
|
||||||
|
"""把 stdout 内容直接写入 broadcast_messages(不经 send, 避免 xmpp 递归)"""
|
||||||
|
if not text or not text.strip():
|
||||||
|
return
|
||||||
|
_flush_broadcast_block(text.strip().split("\n"), script)
|
||||||
|
|
||||||
|
|
||||||
def _job_title(script):
|
def _job_title(script):
|
||||||
|
|||||||
Reference in New Issue
Block a user