feat: 회고 시스템 Phase 1 캡처 파이프라인
chat_bridge에 회고 채널 텍스트 폴링 + n8n 포워딩 추가. n8n 워크플로우(8노드): Webhook → Validate → Qwen 분류 → PostgreSQL INSERT → Chat 확인. retrospect 스키마 + 3 테이블 (entries, reviews, patterns). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""DSM Chat API Bridge — 사진 폴링 + 다운로드 서비스 (port 8091)"""
|
||||
"""DSM Chat API Bridge — 사진 폴링 + 회고 텍스트 포워딩 서비스 (port 8091)"""
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
@@ -25,10 +25,15 @@ CHAT_CHANNEL_ID = int(os.getenv("CHAT_CHANNEL_ID", "17"))
|
||||
SYNOLOGY_CHAT_WEBHOOK_URL = os.getenv("SYNOLOGY_CHAT_WEBHOOK_URL", "")
|
||||
HEIC_CONVERTER_URL = os.getenv("HEIC_CONVERTER_URL", "http://127.0.0.1:8090")
|
||||
POLL_INTERVAL = int(os.getenv("POLL_INTERVAL", "5"))
|
||||
RETROSPECT_CHANNEL_ID = int(os.getenv("RETROSPECT_CHANNEL_ID", "0"))
|
||||
RETROSPECT_CHAT_WEBHOOK_URL = os.getenv("RETROSPECT_CHAT_WEBHOOK_URL", "")
|
||||
N8N_RETROSPECT_WEBHOOK_URL = os.getenv("N8N_RETROSPECT_WEBHOOK_URL",
|
||||
"http://localhost:5678/webhook/retrospect")
|
||||
|
||||
# State
|
||||
sid: str = ""
|
||||
last_seen_post_id: int = 0
|
||||
retro_last_seen_post_id: int = 0
|
||||
pending_photos: dict[int, dict] = {} # user_id -> {post_id, create_at, filename}
|
||||
|
||||
|
||||
@@ -140,6 +145,47 @@ async def poll_channel(client: httpx.AsyncClient):
|
||||
logger.error(f"Poll error: {e}")
|
||||
|
||||
|
||||
async def forward_to_n8n(post: dict):
|
||||
payload = {
|
||||
"text": post.get("msg", ""),
|
||||
"user_id": post.get("creator_id", 0),
|
||||
"username": post.get("display_name", post.get("username", "unknown")),
|
||||
"post_id": post.get("post_id", 0),
|
||||
"timestamp": post.get("create_at", 0),
|
||||
}
|
||||
try:
|
||||
async with httpx.AsyncClient(verify=False) as client:
|
||||
resp = await client.post(N8N_RETROSPECT_WEBHOOK_URL,
|
||||
json=payload, timeout=10)
|
||||
logger.info(f"Forwarded retrospect post_id={post.get('post_id')} "
|
||||
f"to n8n: {resp.status_code}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to forward to n8n: {e}")
|
||||
|
||||
|
||||
async def poll_retrospect_channel(client: httpx.AsyncClient):
|
||||
global retro_last_seen_post_id
|
||||
if not RETROSPECT_CHANNEL_ID:
|
||||
return
|
||||
try:
|
||||
data = await api_call(client, "SYNO.Chat.Post", 8, "list",
|
||||
{"channel_id": RETROSPECT_CHANNEL_ID, "limit": 10})
|
||||
posts = extract_posts(data)
|
||||
for post in posts:
|
||||
post_id = post.get("post_id", 0)
|
||||
if post_id <= retro_last_seen_post_id:
|
||||
continue
|
||||
# 텍스트 메시지만 포워딩 (파일/시스템 메시지 제외)
|
||||
if post.get("type", "normal") == "normal" and post.get("msg", "").strip():
|
||||
await forward_to_n8n(post)
|
||||
if posts:
|
||||
max_id = max(p.get("post_id", 0) for p in posts)
|
||||
if max_id > retro_last_seen_post_id:
|
||||
retro_last_seen_post_id = max_id
|
||||
except Exception as e:
|
||||
logger.error(f"Retrospect poll error: {e}")
|
||||
|
||||
|
||||
async def polling_loop():
|
||||
async with httpx.AsyncClient(verify=False) as client:
|
||||
# Login
|
||||
@@ -155,7 +201,7 @@ async def polling_loop():
|
||||
await asyncio.sleep(5)
|
||||
|
||||
# Initialize last_seen_post_id
|
||||
global last_seen_post_id
|
||||
global last_seen_post_id, retro_last_seen_post_id
|
||||
try:
|
||||
data = await api_call(client, "SYNO.Chat.Post", 8, "list",
|
||||
{"channel_id": CHAT_CHANNEL_ID, "limit": 5})
|
||||
@@ -166,9 +212,23 @@ async def polling_loop():
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to init last_seen_post_id: {e}")
|
||||
|
||||
# Initialize retro_last_seen_post_id
|
||||
if RETROSPECT_CHANNEL_ID:
|
||||
try:
|
||||
data = await api_call(client, "SYNO.Chat.Post", 8, "list",
|
||||
{"channel_id": RETROSPECT_CHANNEL_ID, "limit": 1})
|
||||
posts = extract_posts(data)
|
||||
if posts:
|
||||
retro_last_seen_post_id = max(p.get("post_id", 0) for p in posts)
|
||||
logger.info(f"Initialized retro_last_seen_post_id={retro_last_seen_post_id}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to init retro_last_seen_post_id: {e}")
|
||||
|
||||
# Poll loop
|
||||
while True:
|
||||
await poll_channel(client)
|
||||
await asyncio.sleep(0.5) # DSM API 호출 간격
|
||||
await poll_retrospect_channel(client)
|
||||
await asyncio.sleep(POLL_INTERVAL)
|
||||
|
||||
|
||||
@@ -286,6 +346,8 @@ async def health():
|
||||
"status": "ok",
|
||||
"sid_active": bool(sid),
|
||||
"last_seen_post_id": last_seen_post_id,
|
||||
"retro_last_seen_post_id": retro_last_seen_post_id,
|
||||
"retro_channel_id": RETROSPECT_CHANNEL_ID,
|
||||
"pending_photos": {
|
||||
str(uid): info["filename"]
|
||||
for uid, info in pending_photos.items()
|
||||
|
||||
Reference in New Issue
Block a user