#!/usr/bin/env python3 """kanban_xmpp_bridge.py — kanban→XMPP 通知桥(小小莫专用) 背景:hermes gateway 内置 kanban dispatcher 把卡片派给 246 上的 hermes profile (zhiwei/mohe),但小小莫在 Windows(xxm@yoin.fun),dispatcher 够不着。 本脚本每 2 分钟扫一次 kanban.db,把 assignee=xiaoxiao/xxm 的新卡片以 `[Kanban] card.assigned t_xxx 标题` 格式 DM 给 xxm@yoin.fun(kanban-handler 协议)。 只桥接 xiaoxiao/xxm —— 其他 assignee 由 gateway dispatcher 负责,避免重复通知。 """ import json, os, sqlite3, time, urllib.request KANBAN_DB = "/home/hmo/.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()