feat: kanban→XMPP通知桥(assignee=xiaoxiao/xxm的卡片DM给xxm@yoin.fun

- hermes gateway dispatcher只派给246本地hermes profile, 小小莫在Windows够不着
- 每2分钟扫kanban.db, [Kanban] card.assigned格式DM, 去重状态文件
This commit is contained in:
hmo
2026-07-23 15:53:06 +08:00
parent 8de2230609
commit 9fad0cfa14
@@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""kanban_xmpp_bridge.py — kanban→XMPP 通知桥(小小莫专用)
背景:hermes gateway 内置 kanban dispatcher 把卡片派给 246 上的 hermes profile
zhiwei/mohe),但小小莫在 Windowsxxm@yoin.fun),dispatcher 够不着。
本脚本每 2 分钟扫一次 kanban.db,把 assignee=xiaoxiao/xxm 的新卡片以
`[Kanban] card.assigned t_xxx 标题` 格式 DM 给 xxm@yoin.funkanban-handler 协议)。
只桥接 xiaoxiao/xxm —— 其他 assignee 由 gateway dispatcher 负责,避免重复通知。
"""
import json, os, sqlite3, time, urllib.request
KANBAN_DB = os.path.expanduser("~/.hermes/kanban.db")
STATE = "/home/hmo/MoFin/gateway/logs/kanban_bridge_state.json"
XMPP_SEND = "http://127.0.0.1:5805/"
TARGET_JID = "xxm@yoin.fun"
MY_ASSIGNEES = ("xiaoxiao", "xxm", "小小莫")
def _load_state():
try:
return set(json.load(open(STATE)))
except Exception:
return set()
def _save_state(notified):
keep = sorted(notified)[-200:]
os.makedirs(os.path.dirname(STATE), exist_ok=True)
with open(STATE, "w") as f:
json.dump(keep, f)
def _send_xmpp(body, to=TARGET_JID):
req = urllib.request.Request(
XMPP_SEND,
data=json.dumps({"body": body, "to": to, "type": "chat"}).encode(),
headers={"Content-Type": "application/json"})
urllib.request.urlopen(req, timeout=5)
def main():
if not os.path.exists(KANBAN_DB):
return
notified = _load_state()
db = sqlite3.connect(KANBAN_DB)
rows = db.execute(
"SELECT id, title, assignee, created_by FROM tasks "
"WHERE status='ready' AND assignee IN (%s) ORDER BY created_at"
% ",".join("?" * len(MY_ASSIGNEES)), MY_ASSIGNEES).fetchall()
db.close()
sent = 0
for tid, title, assignee, creator in rows:
if tid in notified:
continue
body = f"[Kanban] card.assigned {tid} {title}(来自 {creator or '?'}"
try:
_send_xmpp(body)
notified.add(tid)
sent += 1
print(f"已通知 {tid}: {title[:40]}", flush=True)
time.sleep(1) # 防XMPP突发
except Exception as e:
print(f"通知失败 {tid}: {e}", flush=True)
if sent:
_save_state(notified)
print(f"scan done: {len(rows)} ready cards, {sent} notified", flush=True)
if __name__ == "__main__":
main()