-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.py
More file actions
138 lines (117 loc) · 5.1 KB
/
Copy pathserver.py
File metadata and controls
138 lines (117 loc) · 5.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
"""FastAPI server: /health, /chat, plus LINE + Facebook webhook stubs.
Run with: python main.py serve
"""
import hmac
import hashlib
import base64
import logging
import requests
from fastapi import FastAPI, Request, Header, HTTPException, BackgroundTasks
from app.config import (
LINE_CHANNEL_SECRET,
LINE_CHANNEL_TOKEN,
FB_VERIFY_TOKEN,
FB_PAGE_TOKEN,
CHAT_API_KEY,
)
from app.graph import ask
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
app = FastAPI(title="Hospital Chatbot MVP1")
@app.get("/health")
def health():
return {"ok": True}
@app.post("/chat")
async def chat(req: Request, x_api_key: str = Header("", alias="X-API-Key")):
"""JSON API: POST {"message": "..."} -> {"answer": "..."}
Protected by X-API-Key header when CHAT_API_KEY env var is set.
If CHAT_API_KEY is empty, the endpoint is open (useful for local dev).
"""
# why: prevents random scrapers from burning OpenAI budget on a public Cloud Run URL
if CHAT_API_KEY and not hmac.compare_digest(x_api_key, CHAT_API_KEY):
raise HTTPException(401, "missing or invalid X-API-Key")
body = await req.json()
msg = (body.get("message") or "").strip()
if not msg:
raise HTTPException(400, "missing 'message'")
return {"answer": ask(msg)}
# ---------- LINE ----------
def _handle_line_message(user_id: str, text: str, reply_token: str) -> None:
try:
reply = ask(text, user_id=user_id)
except Exception as e:
logger.error("ask() failed for user %s: %s", user_id, e)
reply = (
"ขออภัย ระบบเกิดข้อผิดพลาดชั่วคราว กรุณาลองใหม่อีกครั้งค่ะ\n"
"Sorry, a temporary error occurred. Please try again."
)
try:
requests.post(
"https://api.line.me/v2/bot/message/reply",
headers={"Authorization": f"Bearer {LINE_CHANNEL_TOKEN}"},
json={"replyToken": reply_token, "messages": [{"type": "text", "text": reply[:4500]}]},
timeout=10,
)
logger.info("LINE reply sent to %s: %s", user_id, reply[:80])
except Exception as e:
logger.error("LINE reply failed for user %s: %s", user_id, e)
@app.post("/webhook/line")
async def line_webhook(req: Request, background_tasks: BackgroundTasks, x_line_signature: str = Header("")):
raw = await req.body()
if LINE_CHANNEL_SECRET:
sig = base64.b64encode(
hmac.new(LINE_CHANNEL_SECRET.encode(), raw, hashlib.sha256).digest()
).decode()
logger.debug("LINE sig check | secret_len=%d | computed=%s | received=%s",
len(LINE_CHANNEL_SECRET), sig[:10], x_line_signature[:10])
if not hmac.compare_digest(sig, x_line_signature):
logger.warning("LINE bad signature | computed=%s | received=%s",
sig[:20], x_line_signature[:20])
raise HTTPException(403, "bad signature") # why: LINE requires HMAC verification
LINE_VERIFY_TOKEN = "00000000000000000000000000000000"
for ev in (await req.json()).get("events", []):
reply_token = ev.get("replyToken", "")
if reply_token == LINE_VERIFY_TOKEN:
continue # LINE "Verify" button test event — not a real message
if ev.get("type") == "message" and ev["message"]["type"] == "text":
text = (ev["message"].get("text") or "").strip()
if not text:
continue
user_id = ev.get("source", {}).get("userId", "unknown")
logger.info("LINE message from %s: %s", user_id, text)
background_tasks.add_task(_handle_line_message, user_id, text, reply_token)
return {"ok": True} # returned immediately — LINE won't retry
# ---------- Facebook Messenger ----------
@app.get("/webhook/facebook")
async def fb_verify(req: Request):
p = req.query_params
if p.get("hub.mode") == "subscribe" and p.get("hub.verify_token") == FB_VERIFY_TOKEN:
return int(p.get("hub.challenge", 0))
raise HTTPException(403)
def _send_fb_reply(sender: str, text: str) -> None:
reply = ask(text)
resp = requests.post(
"https://graph.facebook.com/v21.0/me/messages",
params={"access_token": FB_PAGE_TOKEN},
json={
"recipient": {"id": sender},
"message": {"text": reply[:1900]},
"messaging_type": "RESPONSE",
},
timeout=30,
)
if not resp.ok:
logger.error("FB send failed %s: %s", resp.status_code, resp.text)
else:
logger.info("FB reply sent to %s", sender)
@app.post("/webhook/facebook")
async def fb_webhook(req: Request, background_tasks: BackgroundTasks):
payload = await req.json()
for entry in payload.get("entry", []):
for ev in entry.get("messaging", []):
sender = ev.get("sender", {}).get("id")
text = ev.get("message", {}).get("text")
if sender and text:
logger.info("FB message from %s: %s", sender, text)
background_tasks.add_task(_send_fb_reply, sender, text)
return {"ok": True}