kyle9574 commited on
Commit
306497b
·
verified ·
1 Parent(s): 5818bb5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +835 -867
app.py CHANGED
@@ -1,868 +1,836 @@
1
- import os
2
- import sqlite3
3
- import requests
4
- import tempfile
5
- import logging
6
- from io import BytesIO
7
-
8
- from flask import Flask, request, abort, send_from_directory
9
- from PIL import Image
10
-
11
- from linebot.v3.webhook import WebhookParser, WebhookHandler
12
- from linebot.v3.webhooks import MessageEvent, TextMessageContent, ImageMessageContent
13
- from linebot.v3.messaging import MessagingApi, Configuration, ApiClient, MessagingApiBlob
14
- from linebot.v3.messaging.models import (
15
- TextMessage, ReplyMessageRequest, PushMessageRequest,
16
- FlexMessage, FlexBubble, FlexBox, FlexText, FlexButton, URIAction,
17
- QuickReply, QuickReplyItem, LocationAction, ImageMessage, DatetimePickerAction,
18
- MessageAction
19
- )
20
- from linebot.v3.exceptions import InvalidSignatureError
21
-
22
- import google.generativeai as genai
23
- import json
24
- import datetime
25
- from apscheduler.schedulers.background import BackgroundScheduler
26
- import pytz
27
-
28
- CHANNEL_SECRET = os.environ.get("YOUR_CHANNEL_SECRET")
29
- CHANNEL_ACCESS_TOKEN = os.environ.get("YOUR_CHANNEL_ACCESS_TOKEN")
30
- GOOGLE_MAP_API_KEY = os.environ.get("GOOGLE_MAP_API_KEY")
31
- GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
32
- base_url = os.environ.get("HF_SPACE_URL", "localhost")
33
-
34
- if not CHANNEL_SECRET or not CHANNEL_ACCESS_TOKEN or not GOOGLE_API_KEY:
35
- raise RuntimeError("Missing essential environment variables")
36
-
37
- app = Flask(__name__)
38
-
39
- BASE_DIR = os.path.dirname(os.path.abspath(__file__))
40
- DB_PATH = os.path.join(BASE_DIR, "linebot.db")
41
-
42
- print("目前資料庫路徑:", DB_PATH)
43
- print("資料庫檔案是否存在:", os.path.exists(DB_PATH))
44
- try:
45
- with open(DB_PATH, "ab") as f:
46
- f.write(b"")
47
- print("✅ 資料庫有寫入權限")
48
- except Exception as e:
49
- print("❌ 資料庫無法寫入:", e)
50
-
51
- static_tmp_path = "/tmp"
52
- os.makedirs(static_tmp_path, exist_ok=True)
53
-
54
- configuration = Configuration(access_token=CHANNEL_ACCESS_TOKEN)
55
- parser = WebhookParser(CHANNEL_SECRET)
56
- handler = WebhookHandler(CHANNEL_SECRET)
57
-
58
- genai.configure(api_key=GOOGLE_API_KEY)
59
- chat = genai.GenerativeModel(model_name="gemini-1.5-flash")
60
- text_system_prompt = "你是一個專業的中文藥物安全衛教AI,運行於Linebot平台,負責為台灣用戶提供用藥查詢、衛教提醒、藥品辨識與互動諮詢。所有回應必須以繁體中文呈現,語氣需保持專業、中立、清晰,嚴禁使用非正式語彙或網路用語。你的回答僅限於台灣現行合法藥品、常見用藥安全及一般衛教知識,絕不涉及診斷、處方或違法用途。遇重要藥品資訊或警語時,務必標示資料來源(如衛福部、健保署或官方藥物資料庫);無法查證時,需說明資訊有限並提醒用戶諮詢藥師。遇到模糊、非藥物相關、或疑似緊急情境(如中毒、嚴重過敏),請直接回覆:「請儘速就醫或聯絡藥師,Linebot無法提供緊急醫療協助。」回答時,優先給出簡明結論,再補充必要說明,遇複雜內容可分點陳述,藥品名稱、注意事項及用法用量需明顯標註。若用戶詢問非本功能範圍問題,請回覆:「本Linebot僅提供藥物安全與衛生教育資訊。」並簡要列舉可查詢主題(如用藥禁忌、藥物交互作用、藥品保存方式等)。所有資訊僅反映截至2025年6月之官方資料,若遇新藥、召回或重大警訊,應提醒用戶查閱衛福部或官方藥事機構。"
61
-
62
- logging.basicConfig(level=logging.INFO)
63
- app.logger.setLevel(logging.INFO)
64
-
65
- user_states = {}
66
-
67
- def init_reminders_table():
68
- conn = sqlite3.connect(DB_PATH)
69
- cursor = conn.cursor()
70
- cursor.execute("""
71
- CREATE TABLE IF NOT EXISTS reminders (
72
- id INTEGER PRIMARY KEY AUTOINCREMENT,
73
- user_id TEXT NOT NULL,
74
- medicine TEXT NOT NULL,
75
- start_date TEXT NOT NULL,
76
- end_date TEXT NOT NULL,
77
- times TEXT NOT NULL,
78
- sent INTEGER DEFAULT 0
79
- );
80
- """)
81
- cursor.execute("""
82
- CREATE TABLE IF NOT EXISTS reminders_log (
83
- id INTEGER PRIMARY KEY AUTOINCREMENT,
84
- reminder_id INTEGER,
85
- date TEXT,
86
- time TEXT
87
- );
88
- """)
89
- cursor.execute("""
90
- CREATE TABLE IF NOT EXISTS drugs (
91
- 中文品名 TEXT,
92
- 英文品名 TEXT,
93
- 適應症 TEXT
94
- );
95
- """)
96
- conn.commit()
97
- conn.close()
98
- init_reminders_table()
99
-
100
- def add_reminder(user_id, medicine, start_date, end_date, times):
101
- print("[DEBUG] add_reminder 被呼叫")
102
- print(f"[DEBUG] 嘗試寫入提醒:{user_id}, {medicine}, {start_date}, {end_date}, {times}")
103
- conn = sqlite3.connect(DB_PATH)
104
- cursor = conn.cursor()
105
- cursor.execute(
106
- "INSERT INTO reminders (user_id, medicine, start_date, end_date, times, sent) VALUES (?, ?, ?, ?, ?, 0)",
107
- (user_id, medicine, start_date, end_date, json.dumps(times))
108
- )
109
- conn.commit()
110
- cursor.execute("SELECT * FROM reminders")
111
- print("[DEBUG] reminders 資料表內容:", cursor.fetchall())
112
- conn.close()
113
- print("[DEBUG] ✅ 寫入 reminders 成功")
114
-
115
- def check_and_send_reminders():
116
- tz = pytz.timezone('Asia/Taipei')
117
- now = datetime.datetime.now(tz)
118
- today = now.strftime("%Y-%m-%d")
119
- now_time = now.strftime("%H:%M")
120
- conn = sqlite3.connect(DB_PATH)
121
- cursor = conn.cursor()
122
- cursor.execute("SELECT id, user_id, medicine, start_date, end_date, times FROM reminders")
123
- rows = cursor.fetchall()
124
- for rid, user_id, medicine, start_date, end_date, times_json in rows:
125
- if start_date <= today <= end_date:
126
- times = json.loads(times_json)
127
- for t in times:
128
- cursor.execute("SELECT COUNT(*) FROM reminders_log WHERE reminder_id=? AND date=? AND time=?", (rid, today, t))
129
- if now_time == t and cursor.fetchone()[0] == 0:
130
- print(f"[DEBUG] 發送提醒給 {user_id}:{medicine} @ {t}")
131
- with ApiClient(configuration) as api_client:
132
- messaging_api = MessagingApi(api_client)
133
- messaging_api.push_message(
134
- push_message_request=PushMessageRequest(
135
- to=user_id,
136
- messages=[TextMessage(text=f"⏰ 用藥提醒:該服用「{medicine}」囉!")]
137
- )
138
- )
139
- cursor.execute("INSERT INTO reminders_log (reminder_id, date, time) VALUES (?, ?, ?)", (rid, today, t))
140
- conn.commit()
141
- conn.close()
142
-
143
- if not hasattr(app, "reminder_scheduler_started"):
144
- scheduler = BackgroundScheduler()
145
- scheduler.add_job(check_and_send_reminders, 'interval', seconds=20)
146
- scheduler.start()
147
- app.reminder_scheduler_started = True
148
-
149
- @app.route("/images/<filename>")
150
- def serve_image(filename):
151
- return send_from_directory(static_tmp_path, filename)
152
-
153
- @app.route("/")
154
- def home():
155
- return {"message": "Line Webhook Server"}
156
-
157
- @app.route("/show_reminders")
158
- def show_reminders():
159
- conn = sqlite3.connect(DB_PATH)
160
- cursor = conn.cursor()
161
- cursor.execute("SELECT * FROM reminders")
162
- rows = cursor.fetchall()
163
- conn.close()
164
- print("[DEBUG] /show_reminders 查詢結果:", rows)
165
- return {"reminders": rows}
166
-
167
- @app.route("/callback", methods=["POST"])
168
- def callback():
169
- signature = request.headers.get("X-Line-Signature", "")
170
- body = request.get_data(as_text=True)
171
- print(f"[DEBUG] 收到 callback 請求,body={body}")
172
-
173
- try:
174
- events = parser.parse(body, signature)
175
- except InvalidSignatureError:
176
- print("[DEBUG] InvalidSignatureError")
177
- abort(400)
178
- except Exception as e:
179
- print("[DEBUG] Webhook parse error:", e)
180
- abort(400)
181
-
182
- with ApiClient(configuration) as api_client:
183
- messaging_api = MessagingApi(api_client)
184
- blob_api = MessagingApiBlob(api_client)
185
-
186
- for event in events:
187
- print(f"[DEBUG] event.type={event.type}, event={event}")
188
- # ====== 用藥提醒對話流程 ======
189
- if event.type == "message" and event.message.type == "text":
190
- user_id = event.source.user_id
191
- user_input = event.message.text.strip()
192
- print(f"[DEBUG] user_input: {user_input}, user_states: {user_states.get(user_id)}")
193
- # 修改用藥提醒選單
194
- if user_input == "修改用藥提醒":
195
- conn = sqlite3.connect(DB_PATH)
196
- cursor = conn.cursor()
197
- cursor.execute("SELECT DISTINCT medicine FROM reminders WHERE user_id=?", (user_id,))
198
- medicines = [row[0] for row in cursor.fetchall()]
199
- conn.close()
200
- if not medicines:
201
- reply_text = "你還沒有設定過任何藥物提醒。"
202
- reply_request = ReplyMessageRequest(
203
- reply_token=event.reply_token,
204
- messages=[TextMessage(text=reply_text)]
205
- )
206
- messaging_api.reply_message(reply_message_request=reply_request)
207
- return "OK"
208
- quick_reply = QuickReply(
209
- items=[QuickReplyItem(action=MessageAction(label=med, text=med)) for med in medicines]
210
- )
211
- reply_text = "請選擇你要修改的藥品:"
212
- reply_request = ReplyMessageRequest(
213
- reply_token=event.reply_token,
214
- messages=[TextMessage(text=reply_text, quick_reply=quick_reply)]
215
- )
216
- messaging_api.reply_message(reply_message_request=reply_request)
217
- user_states[user_id] = {'step': 'edit_medicine'}
218
- return "OK"
219
- elif user_input == "用藥提醒":
220
- user_states[user_id] = {'step': 'ask_medicine'}
221
- print(f"[DEBUG] 進入 ask_medicine, user_id={user_id}")
222
- reply_text = "請輸入要提醒的藥品名稱:"
223
- reply_request = ReplyMessageRequest(
224
- reply_token=event.reply_token,
225
- messages=[TextMessage(text=reply_text)]
226
- )
227
- messaging_api.reply_message(reply_message_request=reply_request)
228
- return "OK"
229
- elif user_id in user_states:
230
- state = user_states[user_id]
231
- print(f"[DEBUG] user_states[{user_id}] = {state}")
232
- if state.get('step') == 'ask_medicine':
233
- state['medicine'] = user_input
234
- state['step'] = 'ask_start'
235
- print(f"[DEBUG] 進入 ask_start, user_id={user_id}, medicine={user_input}")
236
- quick_reply = QuickReply(
237
- items=[
238
- QuickReplyItem(
239
- action=DatetimePickerAction(
240
- label="選擇開始日期",
241
- data="start_date",
242
- mode="date"
243
- )
244
- )
245
- ]
246
- )
247
- reply_text = "請選擇提醒開始日期:"
248
- reply_request = ReplyMessageRequest(
249
- reply_token=event.reply_token,
250
- messages=[TextMessage(text=reply_text, quick_reply=quick_reply)]
251
- )
252
- messaging_api.reply_message(reply_message_request=reply_request)
253
- return "OK"
254
- elif state.get('step') == 'ask_times':
255
- print(f"[DEBUG] 進入 ask_times, user_id={user_id}, state={state}")
256
- times = [t.strip() for t in user_input.split(",") if t.strip()]
257
- # 檢查每個時間格式是否為 HH:MM
258
- import re
259
- valid = True
260
- for t in times:
261
- if not re.match(r"^(?:[01]\d|2[0-3]):[0-5]\d$", t):
262
- valid = False
263
- break
264
- if not times or not valid:
265
- reply_text = "時間格式錯誤,請重新輸入(24小時制,如 08:00,12:00,18:00):"
266
- reply_request = ReplyMessageRequest(
267
- reply_token=event.reply_token,
268
- messages=[TextMessage(text=reply_text)]
269
- )
270
- messaging_api.reply_message(reply_message_request=reply_request)
271
- return "OK"
272
- # 時間格式正確才繼續
273
- add_reminder(user_id, state['medicine'], state['start_date'], state['end_date'], times)
274
- reply_text = f"已設定提醒:{state['medicine']}\n從 {state['start_date']} 到 {state['end_date']}\n每天:{', '.join(times)}"
275
- reply_request = ReplyMessageRequest(
276
- reply_token=event.reply_token,
277
- messages=[TextMessage(text=reply_text)]
278
- )
279
- messaging_api.reply_message(reply_message_request=reply_request)
280
- user_states.pop(user_id, None)
281
- print(f"[DEBUG] 完成提醒流程,user_states 移除 {user_id}")
282
- return "OK"
283
- # ====== 修改用藥提醒流程 ======
284
- elif state.get('step') == 'edit_medicine':
285
- selected_medicine = user_input
286
- conn = sqlite3.connect(DB_PATH)
287
- cursor = conn.cursor()
288
- cursor.execute(
289
- "SELECT id, start_date, end_date, times FROM reminders WHERE user_id=? AND medicine=? ORDER BY id DESC LIMIT 1",
290
- (user_id, selected_medicine)
291
- )
292
- row = cursor.fetchone()
293
- conn.close()
294
- if not row:
295
- reply_text = "查無此藥品提醒資料。"
296
- reply_request = ReplyMessageRequest(
297
- reply_token=event.reply_token,
298
- messages=[TextMessage(text=reply_text)]
299
- )
300
- messaging_api.reply_message(reply_message_request=reply_request)
301
- user_states.pop(user_id, None)
302
- return "OK"
303
- reminder_id, start_date, end_date, times_json = row
304
- times = ','.join(json.loads(times_json))
305
- reply_text = (
306
- f"你目前的提醒設定:\n"
307
- f"藥品:{selected_medicine}\n"
308
- f"開始:{start_date}\n"
309
- f"結束:{end_date}\n"
310
- f"時間:{times}\n"
311
- "請選擇要修改的欄位,或輸入 完成 結束:"
312
- )
313
- quick_reply = QuickReply(
314
- items=[
315
- QuickReplyItem(action=MessageAction(label="開始日期", text="開始日期")),
316
- QuickReplyItem(action=MessageAction(label="結束日期", text="結束日期")),
317
- QuickReplyItem(action=MessageAction(label="提醒時間", text="提醒時間")),
318
- QuickReplyItem(action=MessageAction(label="完成", text="完成")),
319
- ]
320
- )
321
- state['step'] = 'edit_field'
322
- state['reminder_id'] = reminder_id
323
- state['medicine'] = selected_medicine
324
- reply_request = ReplyMessageRequest(
325
- reply_token=event.reply_token,
326
- messages=[TextMessage(text=reply_text, quick_reply=quick_reply)]
327
- )
328
- messaging_api.reply_message(reply_message_request=reply_request)
329
- return "OK"
330
- elif state.get('step') == 'edit_field':
331
- field = user_input.strip()
332
- if field == "開始日期":
333
- state['step'] = 'edit_start_date'
334
- quick_reply = QuickReply(
335
- items=[
336
- QuickReplyItem(
337
- action=DatetimePickerAction(
338
- label="選擇開始日期",
339
- data="edit_start_date",
340
- mode="date"
341
- )
342
- )
343
- ]
344
- )
345
- reply_text = "請選擇新的開始日期:"
346
- reply_request = ReplyMessageRequest(
347
- reply_token=event.reply_token,
348
- messages=[TextMessage(text=reply_text, quick_reply=quick_reply)]
349
- )
350
- messaging_api.reply_message(reply_message_request=reply_request)
351
- return "OK"
352
- elif field == "結束日期":
353
- state['step'] = 'edit_end_date'
354
- quick_reply = QuickReply(
355
- items=[
356
- QuickReplyItem(
357
- action=DatetimePickerAction(
358
- label="選擇結束日期",
359
- data="edit_end_date",
360
- mode="date"
361
- )
362
- )
363
- ]
364
- )
365
- reply_text = "請選擇新的結束日期:"
366
- reply_request = ReplyMessageRequest(
367
- reply_token=event.reply_token,
368
- messages=[TextMessage(text=reply_text, quick_reply=quick_reply)]
369
- )
370
- messaging_api.reply_message(reply_message_request=reply_request)
371
- return "OK"
372
- elif field == "提醒時間":
373
- state['step'] = 'edit_times'
374
- reply_text = "請輸入新的提醒時間(24小時制,用逗號分隔):"
375
- reply_request = ReplyMessageRequest(
376
- reply_token=event.reply_token,
377
- messages=[TextMessage(text=reply_text)]
378
- )
379
- messaging_api.reply_message(reply_message_request=reply_request)
380
- return "OK"
381
- elif field.lower() == "完成":
382
- reply_text = "已結束修改。"
383
- user_states.pop(user_id, None)
384
- reply_request = ReplyMessageRequest(
385
- reply_token=event.reply_token,
386
- messages=[TextMessage(text=reply_text)]
387
- )
388
- messaging_api.reply_message(reply_message_request=reply_request)
389
- return "OK"
390
- else:
391
- # 再次顯示選單
392
- quick_reply = QuickReply(
393
- items=[
394
- QuickReplyItem(action=MessageAction(label="開始日期", text="開始日期")),
395
- QuickReplyItem(action=MessageAction(label="結束日期", text="結束日期")),
396
- QuickReplyItem(action=MessageAction(label="提醒時間", text="提醒時間")),
397
- QuickReplyItem(action=MessageAction(label="完成", text="完成")),
398
- ]
399
- )
400
- reply_text = "請選擇要修改的欄位,或輸入 完成 結束:"
401
- reply_request = ReplyMessageRequest(
402
- reply_token=event.reply_token,
403
- messages=[TextMessage(text=reply_text, quick_reply=quick_reply)]
404
- )
405
- messaging_api.reply_message(reply_message_request=reply_request)
406
- return "OK"
407
- elif state.get('step') == 'edit_times':
408
- import re
409
- times = [t.strip() for t in user_input.split(",") if t.strip()]
410
- valid = all(re.match(r"^(?:[01]\d|2[0-3]):[0-5]\d$", t) for t in times)
411
- if not times or not valid:
412
- reply_text = "時間格式錯誤,請重新輸入(24小時制,如 08:00,12:00,18:00):"
413
- reply_request = ReplyMessageRequest(
414
- reply_token=event.reply_token,
415
- messages=[TextMessage(text=reply_text)]
416
- )
417
- messaging_api.reply_message(reply_message_request=reply_request)
418
- return "OK"
419
- conn = sqlite3.connect(DB_PATH)
420
- cursor = conn.cursor()
421
- cursor.execute("UPDATE reminders SET times=? WHERE id=?", (json.dumps(times), state['reminder_id']))
422
- conn.commit()
423
- conn.close()
424
- reply_text = "提醒時間已更新!"
425
- # 修改完繼續顯示選單
426
- quick_reply = QuickReply(
427
- items=[
428
- QuickReplyItem(action=MessageAction(label="開始日期", text="開始日期")),
429
- QuickReplyItem(action=MessageAction(label="結束日期", text="結束日期")),
430
- QuickReplyItem(action=MessageAction(label="提醒時間", text="提醒時間")),
431
- QuickReplyItem(action=MessageAction(label="完成", text="完成")),
432
- ]
433
- )
434
- reply_text += "\n請選擇要繼續修改的欄位,或輸入 完成 結束:"
435
- reply_request = ReplyMessageRequest(
436
- reply_token=event.reply_token,
437
- messages=[TextMessage(text=reply_text, quick_reply=quick_reply)]
438
- )
439
- messaging_api.reply_message(reply_message_request=reply_request)
440
- state['step'] = 'edit_field'
441
- return "OK"
442
-
443
- # ====== 其他功能區塊(查詢藥品、AI、藥局、圖片) ======
444
- user_input = event.message.text.strip()
445
- print("[DEBUG] 進入原有功能區塊,收到訊息:", user_input)
446
-
447
- # AI 問答
448
- if user_input.startswith("AI "):
449
- prompt = "你是一個中文的AI助手,請用繁體中文回答。\n" + user_input[3:].strip()
450
- try:
451
- response = chat.generate_content(prompt)
452
- reply_text = response.text
453
- except Exception as e:
454
- logging.exception("AI 問答發生錯誤")
455
- reply_text = "⚠️ AI 回答失敗,請稍後再試"
456
- reply_request = ReplyMessageRequest(
457
- reply_token=event.reply_token,
458
- messages=[TextMessage(text=reply_text)]
459
- )
460
- messaging_api.reply_message(reply_message_request=reply_request)
461
- return "OK"
462
-
463
- # 查詢藥品
464
- elif user_input == "查詢藥��":
465
- try:
466
- conn = sqlite3.connect(DB_PATH)
467
- cursor = conn.cursor()
468
- query = """
469
- SELECT DISTINCT 中文品名, 英文品名, 適應症
470
- FROM drugs
471
- WHERE 中文品名 LIKE ? OR 英文品名 LIKE ?
472
- LIMIT 1
473
- """
474
- like_param = f'%{medicine_name}%'
475
- cursor.execute(query, (like_param, like_param))
476
- row = cursor.fetchone()
477
- conn.close()
478
- print(f"[DEBUG] 查詢 drugs 結果:{row}")
479
-
480
- if row:
481
- zh_name, en_name, indication = row
482
- # 副作用由 AI 產生
483
- prompt = (
484
- f"請只用簡短條列式(每點用-開頭,不要用*),僅列出副作用,"
485
- f"針對藥品「{zh_name}」(英文名:{en_name}),"
486
- "請用繁體中文回答,不要加任何說明、警語或強調語句。"
487
- )
488
- try:
489
- ai_resp = chat.generate_content(prompt)
490
- side_effects = ai_resp.text.strip()
491
- except Exception as e:
492
- side_effects = f"AI 回答失敗:{e}"
493
- reply_text = (
494
- f"🔹 中文品名:{zh_name}\n"
495
- f"📌 英文品名:{en_name}\n"
496
- f"📄 適應症:{indication}\n"
497
- f"⚠️ 副作用:\n{side_effects}"
498
- )
499
- else:
500
- prompt = (
501
- f"請用以下格式,幫我介紹藥品「{medicine_name}」,"
502
- "只要條列資料本身,不要加任何說明、警語或強調語句:\n"
503
- "🔹 中文品名:\n"
504
- "📌 英文品名:\n"
505
- "📄 適應症:\n"
506
- "⚠️ 副作用:\n(請用-開頭條列,不要用*)"
507
- )
508
- try:
509
- ai_resp = chat.generate_content(prompt)
510
- reply_text = ai_resp.text
511
- except Exception as e:
512
- reply_text = f"AI 回答失敗:{e}"
513
-
514
- except Exception as e:
515
- reply_text = f"⚠️ 查詢資料時發生錯誤:{str(e)}"
516
-
517
- reply_request = ReplyMessageRequest(
518
- reply_token=event.reply_token,
519
- messages=[TextMessage(text=reply_text.strip())]
520
- )
521
- messaging_api.reply_message(reply_message_request=reply_request)
522
-
523
- #圖片查詢
524
- elif user_input == "圖片查詢":
525
- try:
526
- content = blob_api.get_message_content(message_id=event.message.id)
527
- with tempfile.NamedTemporaryFile(dir=static_tmp_path, suffix=".jpg", delete=False) as tf:
528
- tf.write(content)
529
- filename = os.path.basename(tf.name)
530
- image = Image.open(tf.name)
531
-
532
- prompt = (
533
- "請根據這張圖片判斷藥品資訊,若圖片無法判斷適應症或副作用,請根據藥品名稱推測並補充,"
534
- "只要條列資料本身,不要加任何說明、警語或強調語句,也不要加**:\n"
535
- "🔹 中文品名:\n"
536
- "📌 英文品名:\n"
537
- "📄 適應症:\n"
538
- "⚠️ 副作用:\n(請用-開頭條列,不要用*)"
539
- )
540
-
541
- response = chat.generate_content([image, prompt])
542
- description = response.text
543
-
544
- reply_request = ReplyMessageRequest(
545
- reply_token=event.reply_token,
546
- messages=[TextMessage(text=description.strip())]
547
- )
548
- messaging_api.reply_message(reply_message_request=reply_request)
549
- except Exception as e:
550
- logging.exception("圖片查詢發生錯誤")
551
- reply_text = "⚠️ 圖片查詢失敗,請稍後再試"
552
- reply_request = ReplyMessageRequest(
553
- reply_token=event.reply_token,
554
- messages=[TextMessage(text=reply_text)]
555
- )
556
- messaging_api.reply_message(reply_message_request=reply_request)
557
- return "OK"
558
-
559
- # 查詢藥局
560
- elif "查詢藥局" in user_input:
561
- try:
562
- quick_reply = QuickReply(
563
- items=[QuickReplyItem(action=LocationAction(label="傳送我的位置"))]
564
- )
565
- reply_request = ReplyMessageRequest(
566
- reply_token=event.reply_token,
567
- messages=[TextMessage(text="請點選下方按鈕傳送你的位置,我才能幫你找附近藥局喔~", quick_reply=quick_reply)]
568
- )
569
- messaging_api.reply_message(reply_message_request=reply_request)
570
- except Exception as e:
571
- logging.exception("查詢藥局發生錯誤")
572
- reply_text = "⚠️ 查詢藥局失敗,請稍後再試"
573
- reply_request = ReplyMessageRequest(
574
- reply_token=event.reply_token,
575
- messages=[TextMessage(text=reply_text)]
576
- )
577
- messaging_api.reply_message(reply_message_request=reply_request)
578
- return "OK"
579
- else:
580
- try:
581
- medicine_name = user_input
582
- conn = sqlite3.connect(DB_PATH)
583
- cursor = conn.cursor()
584
- query = """
585
- SELECT DISTINCT 中文品名, 英文品名, 適應症
586
- FROM drugs
587
- WHERE 中文品名 LIKE ? OR 英文品名 LIKE ?
588
- LIMIT 1
589
- """
590
- like_param = f'%{medicine_name}%'
591
- cursor.execute(query, (like_param, like_param))
592
- row = cursor.fetchone()
593
- conn.close()
594
- print(f"[DEBUG] 查詢 drugs 結果:{row}")
595
-
596
- if row:
597
- zh_name, en_name, indication = row
598
- # 副作用由 AI 產生
599
- prompt = (
600
- f"請只用簡短條列式(每點用-開頭,不要用*),僅列出副作用,"
601
- f"針對藥品「{zh_name}」(英文名:{en_name}),"
602
- "請用繁體中文回答,不要加任何說明、警語或強調語句。"
603
- )
604
- try:
605
- ai_resp = chat.generate_content(prompt)
606
- side_effects = ai_resp.text.strip()
607
- except Exception as e:
608
- side_effects = f"AI 回答失敗:{e}"
609
- reply_text = (
610
- f"🔹 中文品名:{zh_name}\n"
611
- f"📌 英文品名:{en_name}\n"
612
- f"📄 適應症:{indication}\n"
613
- f"⚠️ 副作用:\n{side_effects}"
614
- )
615
- else:
616
- prompt = (
617
- f"請用以下格式,幫我介紹藥品「{medicine_name}」,"
618
- "只要條列資料本身,不要加任何說明、警語或強調語句:\n"
619
- "🔹 中文品名:\n"
620
- "📌 英文品名:\n"
621
- "📄 適應症:\n"
622
- "⚠️ 副作用:\n(請用-開頭條列,不要用*)"
623
- )
624
- try:
625
- ai_resp = chat.generate_content(prompt)
626
- reply_text = ai_resp.text
627
- except Exception as e:
628
- reply_text = f"AI 回答失敗:{e}"
629
-
630
- except Exception as e:
631
- reply_text = f"⚠️ 查詢資料時發生錯誤:{str(e)}"
632
-
633
- reply_request = ReplyMessageRequest(
634
- reply_token=event.reply_token,
635
- messages=[TextMessage(text=reply_text.strip())]
636
- )
637
- messaging_api.reply_message(reply_message_request=reply_request)
638
-
639
- elif event.type == "message" and event.message.type == "location":
640
- print("[DEBUG] 收到位置訊息")
641
- user_lat = event.message.latitude
642
- user_lng = event.message.longitude
643
-
644
- nearby_url = (
645
- f"https://maps.googleapis.com/maps/api/place/nearbysearch/json?"
646
- f"location={user_lat},{user_lng}&radius=1000&type=pharmacy&language=zh-TW&key={GOOGLE_MAP_API_KEY}"
647
- )
648
- nearby_res = requests.get(nearby_url).json()
649
- print(f"[DEBUG] nearby_res: {nearby_res}")
650
-
651
- if not nearby_res.get('results'):
652
- reply_request = ReplyMessageRequest(
653
- reply_token=event.reply_token,
654
- messages=[TextMessage(text="附近找不到藥局")]
655
- )
656
- messaging_api.reply_message(reply_message_request=reply_request)
657
- return "OK"
658
-
659
- bubbles = []
660
- for place in nearby_res['results'][:3]:
661
- place_id = place['place_id']
662
- name = place.get('name', '藥局名稱未知')
663
- address = place.get('vicinity', '地址不詳')
664
- location = place['geometry']['location']
665
- dest_lat, dest_lng = location['lat'], location['lng']
666
-
667
- # 取得電話
668
- details_url = (
669
- f"https://maps.googleapis.com/maps/api/place/details/json?"
670
- f"place_id={place_id}&fields=name,formatted_phone_number&key={GOOGLE_MAP_API_KEY}"
671
- )
672
- details_res = requests.get(details_url).json()
673
- phone = details_res.get('result', {}).get('formatted_phone_number', '電話不詳')
674
-
675
- # 取得距離
676
- dist_url = (
677
- f"https://maps.googleapis.com/maps/api/distancematrix/json?"
678
- f"origins={user_lat},{user_lng}&destinations={dest_lat},{dest_lng}&key={GOOGLE_MAP_API_KEY}"
679
- )
680
- dist_res = requests.get(dist_url).json()
681
- distance = dist_res['rows'][0]['elements'][0]['distance']['text']
682
-
683
- map_url = f"https://www.google.com/maps/search/?api=1&query={dest_lat},{dest_lng}"
684
-
685
- bubble = FlexBubble(
686
- body=FlexBox(
687
- layout="vertical",
688
- contents=[
689
- FlexText(text=name, weight="bold", size="lg"),
690
- FlexText(text=f"地址:{address}", size="sm", color="#555555", wrap=True),
691
- FlexText(text=f"電話:{phone}", size="sm", color="#555555"),
692
- FlexText(text=f"距離:{distance}", size="sm", color="#777777"),
693
- ],
694
- ),
695
- footer=FlexBox(
696
- layout="vertical",
697
- contents=[
698
- FlexButton(
699
- style="link",
700
- height="sm",
701
- action=URIAction(label="地圖導航", uri=map_url),
702
- )
703
- ],
704
- ),
705
- )
706
- bubbles.append(bubble)
707
-
708
- from linebot.v3.messaging.models import FlexCarousel, FlexMessage
709
-
710
- carousel = FlexCarousel(contents=bubbles)
711
- flex_message = FlexMessage(
712
- alt_text="附近藥局推薦",
713
- contents=carousel
714
- )
715
-
716
- reply_request = ReplyMessageRequest(
717
- reply_token=event.reply_token,
718
- messages=[flex_message]
719
- )
720
- messaging_api.reply_message(reply_message_request=reply_request)
721
- return "OK"
722
- elif event.type == "message" and event.message.type == "image":
723
- print("[DEBUG] 收到圖片訊息")
724
- try:
725
- content = blob_api.get_message_content(message_id=event.message.id)
726
- with tempfile.NamedTemporaryFile(dir=static_tmp_path, suffix=".jpg", delete=False) as tf:
727
- tf.write(content)
728
- filename = os.path.basename(tf.name)
729
- image = Image.open(tf.name)
730
-
731
- prompt = (
732
- "請根據這張圖片判斷藥品資訊,若圖片無法判斷適應症或副作用,請根據藥品名稱推測並補充,"
733
- "只要條列資料本身,不要加任何說明、警語或強調語句,也不要加**:\n"
734
- "🔹 中文品名:\n"
735
- "📌 英文品名:\n"
736
- "📄 適應症:\n"
737
- "⚠️ 副作用:\n(請用-開頭條列,不要用*)"
738
- )
739
-
740
- response = chat.generate_content([image, prompt])
741
- description = response.text
742
-
743
- reply_request = ReplyMessageRequest(
744
- reply_token=event.reply_token,
745
- messages=[TextMessage(text=description.strip())]
746
- )
747
- messaging_api.reply_message(reply_message_request=reply_request)
748
- except Exception as e:
749
- logging.exception("圖片處理發生錯誤")
750
- reply_text = "⚠️ 圖片處理失敗,請稍後再試"
751
- reply_request = ReplyMessageRequest(
752
- reply_token=event.reply_token,
753
- messages=[TextMessage(text=reply_text)]
754
- )
755
- messaging_api.reply_message(reply_message_request=reply_request)
756
- return "OK"
757
-
758
- elif event.type == "postback":
759
- user_id = event.source.user_id
760
- data = event.postback.data
761
- print(f"[DEBUG] postback data: {data}, user_states: {user_states.get(user_id)}")
762
- # 用藥提醒步驟分開訊息
763
- if data == "start_date":
764
- user_states[user_id]['start_date'] = event.postback.params['date']
765
- user_states[user_id]['step'] = 'ask_end'
766
- # 先回覆
767
- reply_request = ReplyMessageRequest(
768
- reply_token=event.reply_token,
769
- messages=[TextMessage(text=f"你選擇的開始日期為:{event.postback.params['date']}")]
770
- )
771
- messaging_api.reply_message(reply_message_request=reply_request)
772
- # 再推送下一步
773
- quick_reply = QuickReply(
774
- items=[
775
- QuickReplyItem(
776
- action=DatetimePickerAction(
777
- label="選擇結束日期",
778
- data="end_date",
779
- mode="date"
780
- )
781
- )
782
- ]
783
- )
784
- messaging_api.push_message(
785
- push_message_request=PushMessageRequest(
786
- to=user_id,
787
- messages=[TextMessage(text="請選擇提醒結束日期:", quick_reply=quick_reply)]
788
- )
789
- )
790
- return "OK"
791
- elif data == "end_date":
792
- user_states[user_id]['end_date'] = event.postback.params['date']
793
- user_states[user_id]['step'] = 'ask_times'
794
- reply_request = ReplyMessageRequest(
795
- reply_token=event.reply_token,
796
- messages=[TextMessage(text=f"你選擇的結束日期為:{event.postback.params['date']}")]
797
- )
798
- messaging_api.reply_message(reply_message_request=reply_request)
799
- messaging_api.push_message(
800
- push_message_request=PushMessageRequest(
801
- to=user_id,
802
- messages=[TextMessage(text="請輸入每天要提醒的時間(24小時制,可多個,用逗號分隔,如 08:00,12:00,18:00):")]
803
- )
804
- )
805
- return "OK"
806
- # 修改用藥提醒步驟分開訊息
807
- elif data == "edit_start_date":
808
- user_states[user_id]['step'] = 'edit_field'
809
- new_start = event.postback.params['date']
810
- conn = sqlite3.connect(DB_PATH)
811
- cursor = conn.cursor()
812
- cursor.execute("UPDATE reminders SET start_date=? WHERE id=?", (new_start, user_states[user_id]['reminder_id']))
813
- conn.commit()
814
- conn.close()
815
- reply_request = ReplyMessageRequest(
816
- reply_token=event.reply_token,
817
- messages=[TextMessage(text=f"開始日期已更新為:{new_start}")]
818
- )
819
- messaging_api.reply_message(reply_message_request=reply_request)
820
- quick_reply = QuickReply(
821
- items=[
822
- QuickReplyItem(action=MessageAction(label="開始日期", text="開始日期")),
823
- QuickReplyItem(action=MessageAction(label="結束日期", text="結束日期")),
824
- QuickReplyItem(action=MessageAction(label="提醒時間", text="提醒時間")),
825
- QuickReplyItem(action=MessageAction(label="完成", text="完成")),
826
- ]
827
- )
828
- messaging_api.push_message(
829
- push_message_request=PushMessageRequest(
830
- to=user_id,
831
- messages=[TextMessage(text="請選擇要繼續修改的欄位,或輸入 完成 結束:", quick_reply=quick_reply)]
832
- )
833
- )
834
- return "OK"
835
- elif data == "edit_end_date":
836
- user_states[user_id]['step'] = 'edit_field'
837
- new_end = event.postback.params['date']
838
- conn = sqlite3.connect(DB_PATH)
839
- cursor = conn.cursor()
840
- cursor.execute("UPDATE reminders SET end_date=? WHERE id=?", (new_end, user_states[user_id]['reminder_id']))
841
- conn.commit()
842
- conn.close()
843
- reply_request = ReplyMessageRequest(
844
- reply_token=event.reply_token,
845
- messages=[TextMessage(text=f"結束日期已更新為:{new_end}")]
846
- )
847
- messaging_api.reply_message(reply_message_request=reply_request)
848
- quick_reply = QuickReply(
849
- items=[
850
- QuickReplyItem(action=MessageAction(label="開始日期", text="開始日期")),
851
- QuickReplyItem(action=MessageAction(label="結束日期", text="結束日期")),
852
- QuickReplyItem(action=MessageAction(label="提醒時間", text="提醒時間")),
853
- QuickReplyItem(action=MessageAction(label="完成", text="完成")),
854
- ]
855
- )
856
- messaging_api.push_message(
857
- push_message_request=PushMessageRequest(
858
- to=user_id,
859
- messages=[TextMessage(text="請選擇要繼續修改的欄位,或輸入 完成 結束:", quick_reply=quick_reply)]
860
- )
861
- )
862
- return "OK"
863
-
864
- print("[DEBUG] callback 執行結束")
865
- return "OK"
866
-
867
- if __name__ == "__main__":
868
  app.run(host="0.0.0.0", port=7860)
 
1
+ import os
2
+ import sqlite3
3
+ import requests
4
+ import tempfile
5
+ import logging
6
+ from io import BytesIO
7
+
8
+ from flask import Flask, request, abort, send_from_directory
9
+ from PIL import Image
10
+
11
+ from linebot.v3.webhook import WebhookParser, WebhookHandler
12
+ from linebot.v3.webhooks import MessageEvent, TextMessageContent, ImageMessageContent
13
+ from linebot.v3.messaging import MessagingApi, Configuration, ApiClient, MessagingApiBlob
14
+ from linebot.v3.messaging.models import (
15
+ TextMessage, ReplyMessageRequest, PushMessageRequest,
16
+ FlexMessage, FlexBubble, FlexBox, FlexText, FlexButton, URIAction,
17
+ QuickReply, QuickReplyItem, LocationAction, ImageMessage, DatetimePickerAction,
18
+ MessageAction
19
+ )
20
+ from linebot.v3.exceptions import InvalidSignatureError
21
+
22
+ import google.generativeai as genai
23
+ import json
24
+ import datetime
25
+ from apscheduler.schedulers.background import BackgroundScheduler
26
+ import pytz
27
+
28
+ CHANNEL_SECRET = os.environ.get("YOUR_CHANNEL_SECRET")
29
+ CHANNEL_ACCESS_TOKEN = os.environ.get("YOUR_CHANNEL_ACCESS_TOKEN")
30
+ GOOGLE_MAP_API_KEY = os.environ.get("GOOGLE_MAP_API_KEY")
31
+ GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
32
+ base_url = os.environ.get("HF_SPACE_URL", "localhost")
33
+
34
+ if not CHANNEL_SECRET or not CHANNEL_ACCESS_TOKEN or not GOOGLE_API_KEY:
35
+ raise RuntimeError("Missing essential environment variables")
36
+
37
+ app = Flask(__name__)
38
+
39
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
40
+ DB_PATH = os.path.join(BASE_DIR, "linebot.db")
41
+
42
+ print("目前資料庫路徑:", DB_PATH)
43
+ print("資料庫檔案是否存在:", os.path.exists(DB_PATH))
44
+ try:
45
+ with open(DB_PATH, "ab") as f:
46
+ f.write(b"")
47
+ print("✅ 資料庫有寫入權限")
48
+ except Exception as e:
49
+ print("❌ 資料庫無法寫入:", e)
50
+
51
+ static_tmp_path = "/tmp"
52
+ os.makedirs(static_tmp_path, exist_ok=True)
53
+
54
+ configuration = Configuration(access_token=CHANNEL_ACCESS_TOKEN)
55
+ parser = WebhookParser(CHANNEL_SECRET)
56
+ handler = WebhookHandler(CHANNEL_SECRET)
57
+
58
+ genai.configure(api_key=GOOGLE_API_KEY)
59
+ chat = genai.GenerativeModel(model_name="gemini-1.5-flash")
60
+ text_system_prompt = "你是一個專業的中文藥物安全衛教AI,運行於Linebot平台,負責為台灣用戶提供用藥查詢、衛教提醒、藥品辨識與互動諮詢。所有回應必須以繁體中文呈現,語氣需保持專業、中立、清晰,嚴禁使用非正式語彙或網路用語。你的回答僅限於台灣現行合法藥品、常見用藥安全及一般衛教知識,絕不涉及診斷、處方或違法用途。遇重要藥品資訊或警語時,務必標示資料來源(如衛福部、健保署或官方藥物資料庫);無法查證時,需說明資訊有限並提醒用戶諮詢藥師。遇到模糊、非藥物相關、或疑似緊急情境(如中毒、嚴重過敏),請直接回覆:「請儘速就醫或聯絡藥師,Linebot無法提供緊急醫療協助。」回答時,優先給出簡明結論,再補充必要說明,遇複雜內容可分點陳述,藥品名稱、注意事項及用法用量需明顯標註。若用戶詢問非本功能範圍問題,請回覆:「本Linebot僅提供藥物安全與衛生教育資訊。」並簡要列舉可查詢主題(如用藥禁忌、藥物交互作用、藥品保存方式等)。所有資訊僅反映截至2025年6月之官方資料,若遇新藥、召回或重大警訊,應提醒用戶查閱衛福部或官方藥事機構。"
61
+
62
+ logging.basicConfig(level=logging.INFO)
63
+ app.logger.setLevel(logging.INFO)
64
+
65
+ user_states = {}
66
+
67
+ def init_reminders_table():
68
+ conn = sqlite3.connect(DB_PATH)
69
+ cursor = conn.cursor()
70
+ cursor.execute("""
71
+ CREATE TABLE IF NOT EXISTS reminders (
72
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
73
+ user_id TEXT NOT NULL,
74
+ medicine TEXT NOT NULL,
75
+ start_date TEXT NOT NULL,
76
+ end_date TEXT NOT NULL,
77
+ times TEXT NOT NULL,
78
+ sent INTEGER DEFAULT 0
79
+ );
80
+ """)
81
+ cursor.execute("""
82
+ CREATE TABLE IF NOT EXISTS reminders_log (
83
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
84
+ reminder_id INTEGER,
85
+ date TEXT,
86
+ time TEXT
87
+ );
88
+ """)
89
+ cursor.execute("""
90
+ CREATE TABLE IF NOT EXISTS drugs (
91
+ 中文品名 TEXT,
92
+ 英文品名 TEXT,
93
+ 適應症 TEXT
94
+ );
95
+ """)
96
+ conn.commit()
97
+ conn.close()
98
+ init_reminders_table()
99
+
100
+ def add_reminder(user_id, medicine, start_date, end_date, times):
101
+ print("[DEBUG] add_reminder 被呼叫")
102
+ print(f"[DEBUG] 嘗試寫入提醒:{user_id}, {medicine}, {start_date}, {end_date}, {times}")
103
+ conn = sqlite3.connect(DB_PATH)
104
+ cursor = conn.cursor()
105
+ cursor.execute(
106
+ "INSERT INTO reminders (user_id, medicine, start_date, end_date, times, sent) VALUES (?, ?, ?, ?, ?, 0)",
107
+ (user_id, medicine, start_date, end_date, json.dumps(times))
108
+ )
109
+ conn.commit()
110
+ cursor.execute("SELECT * FROM reminders")
111
+ print("[DEBUG] reminders 資料表內容:", cursor.fetchall())
112
+ conn.close()
113
+ print("[DEBUG] ✅ 寫入 reminders 成功")
114
+
115
+ def check_and_send_reminders():
116
+ tz = pytz.timezone('Asia/Taipei')
117
+ now = datetime.datetime.now(tz)
118
+ today = now.strftime("%Y-%m-%d")
119
+ now_time = now.strftime("%H:%M")
120
+ conn = sqlite3.connect(DB_PATH)
121
+ cursor = conn.cursor()
122
+ cursor.execute("SELECT id, user_id, medicine, start_date, end_date, times FROM reminders")
123
+ rows = cursor.fetchall()
124
+ for rid, user_id, medicine, start_date, end_date, times_json in rows:
125
+ if start_date <= today <= end_date:
126
+ times = json.loads(times_json)
127
+ for t in times:
128
+ cursor.execute("SELECT COUNT(*) FROM reminders_log WHERE reminder_id=? AND date=? AND time=?", (rid, today, t))
129
+ if now_time == t and cursor.fetchone()[0] == 0:
130
+ print(f"[DEBUG] 發送提醒給 {user_id}:{medicine} @ {t}")
131
+ with ApiClient(configuration) as api_client:
132
+ messaging_api = MessagingApi(api_client)
133
+ messaging_api.push_message(
134
+ push_message_request=PushMessageRequest(
135
+ to=user_id,
136
+ messages=[TextMessage(text=f"⏰ 用藥提醒:該服用「{medicine}」囉!")]
137
+ )
138
+ )
139
+ cursor.execute("INSERT INTO reminders_log (reminder_id, date, time) VALUES (?, ?, ?)", (rid, today, t))
140
+ conn.commit()
141
+ conn.close()
142
+
143
+ if not hasattr(app, "reminder_scheduler_started"):
144
+ scheduler = BackgroundScheduler()
145
+ scheduler.add_job(check_and_send_reminders, 'interval', seconds=20)
146
+ scheduler.start()
147
+ app.reminder_scheduler_started = True
148
+
149
+ @app.route("/images/<filename>")
150
+ def serve_image(filename):
151
+ return send_from_directory(static_tmp_path, filename)
152
+
153
+ @app.route("/")
154
+ def home():
155
+ return {"message": "Line Webhook Server"}
156
+
157
+ @app.route("/show_reminders")
158
+ def show_reminders():
159
+ conn = sqlite3.connect(DB_PATH)
160
+ cursor = conn.cursor()
161
+ cursor.execute("SELECT * FROM reminders")
162
+ rows = cursor.fetchall()
163
+ conn.close()
164
+ print("[DEBUG] /show_reminders 查詢結果:", rows)
165
+ return {"reminders": rows}
166
+
167
+ @app.route("/callback", methods=["POST"])
168
+ def callback():
169
+ signature = request.headers.get("X-Line-Signature", "")
170
+ body = request.get_data(as_text=True)
171
+ print(f"[DEBUG] 收到 callback 請求,body={body}")
172
+
173
+ try:
174
+ events = parser.parse(body, signature)
175
+ except InvalidSignatureError:
176
+ print("[DEBUG] InvalidSignatureError")
177
+ abort(400)
178
+ except Exception as e:
179
+ print("[DEBUG] Webhook parse error:", e)
180
+ abort(400)
181
+
182
+ with ApiClient(configuration) as api_client:
183
+ messaging_api = MessagingApi(api_client)
184
+ blob_api = MessagingApiBlob(api_client)
185
+
186
+ for event in events:
187
+ print(f"[DEBUG] event.type={event.type}, event={event}")
188
+ # ====== 用藥提醒對話流程 ======
189
+ if event.type == "message" and event.message.type == "text":
190
+ user_id = event.source.user_id
191
+ user_input = event.message.text.strip()
192
+ print(f"[DEBUG] user_input: {user_input}, user_states: {user_states.get(user_id)}")
193
+ # 修改用藥提醒選單
194
+ if user_input == "修改用藥提醒":
195
+ conn = sqlite3.connect(DB_PATH)
196
+ cursor = conn.cursor()
197
+ cursor.execute("SELECT DISTINCT medicine FROM reminders WHERE user_id=?", (user_id,))
198
+ medicines = [row[0] for row in cursor.fetchall()]
199
+ conn.close()
200
+ if not medicines:
201
+ reply_text = "你還沒有設定過任何藥物提醒。"
202
+ reply_request = ReplyMessageRequest(
203
+ reply_token=event.reply_token,
204
+ messages=[TextMessage(text=reply_text)]
205
+ )
206
+ messaging_api.reply_message(reply_message_request=reply_request)
207
+ return "OK"
208
+ quick_reply = QuickReply(
209
+ items=[QuickReplyItem(action=MessageAction(label=med, text=med)) for med in medicines]
210
+ )
211
+ reply_text = "請選擇你要修改的藥品:"
212
+ reply_request = ReplyMessageRequest(
213
+ reply_token=event.reply_token,
214
+ messages=[TextMessage(text=reply_text, quick_reply=quick_reply)]
215
+ )
216
+ messaging_api.reply_message(reply_message_request=reply_request)
217
+ user_states[user_id] = {'step': 'edit_medicine'}
218
+ return "OK"
219
+ elif user_input == "用藥提醒":
220
+ user_states[user_id] = {'step': 'ask_medicine'}
221
+ print(f"[DEBUG] 進入 ask_medicine, user_id={user_id}")
222
+ reply_text = "請輸入要提醒的藥品名稱:"
223
+ reply_request = ReplyMessageRequest(
224
+ reply_token=event.reply_token,
225
+ messages=[TextMessage(text=reply_text)]
226
+ )
227
+ messaging_api.reply_message(reply_message_request=reply_request)
228
+ return "OK"
229
+ elif user_id in user_states:
230
+ state = user_states[user_id]
231
+ print(f"[DEBUG] user_states[{user_id}] = {state}")
232
+ if state.get('step') == 'ask_medicine':
233
+ state['medicine'] = user_input
234
+ state['step'] = 'ask_start'
235
+ print(f"[DEBUG] 進入 ask_start, user_id={user_id}, medicine={user_input}")
236
+ quick_reply = QuickReply(
237
+ items=[
238
+ QuickReplyItem(
239
+ action=DatetimePickerAction(
240
+ label="選擇開始日期",
241
+ data="start_date",
242
+ mode="date"
243
+ )
244
+ )
245
+ ]
246
+ )
247
+ reply_text = "請選擇提醒開始日期:"
248
+ reply_request = ReplyMessageRequest(
249
+ reply_token=event.reply_token,
250
+ messages=[TextMessage(text=reply_text, quick_reply=quick_reply)]
251
+ )
252
+ messaging_api.reply_message(reply_message_request=reply_request)
253
+ return "OK"
254
+ elif state.get('step') == 'ask_times':
255
+ print(f"[DEBUG] 進入 ask_times, user_id={user_id}, state={state}")
256
+ times = [t.strip() for t in user_input.split(",") if t.strip()]
257
+ # 檢查每個時間格式是否為 HH:MM
258
+ import re
259
+ valid = True
260
+ for t in times:
261
+ if not re.match(r"^(?:[01]\d|2[0-3]):[0-5]\d$", t):
262
+ valid = False
263
+ break
264
+ if not times or not valid:
265
+ reply_text = "時間格式錯誤,請重新輸入(24小時制,如 08:00,12:00,18:00):"
266
+ reply_request = ReplyMessageRequest(
267
+ reply_token=event.reply_token,
268
+ messages=[TextMessage(text=reply_text)]
269
+ )
270
+ messaging_api.reply_message(reply_message_request=reply_request)
271
+ return "OK"
272
+ # 時間格式正確才繼續
273
+ add_reminder(user_id, state['medicine'], state['start_date'], state['end_date'], times)
274
+ reply_text = f"已設定提醒:{state['medicine']}\n從 {state['start_date']} 到 {state['end_date']}\n每天:{', '.join(times)}"
275
+ reply_request = ReplyMessageRequest(
276
+ reply_token=event.reply_token,
277
+ messages=[TextMessage(text=reply_text)]
278
+ )
279
+ messaging_api.reply_message(reply_message_request=reply_request)
280
+ user_states.pop(user_id, None)
281
+ print(f"[DEBUG] 完成提醒流程,user_states 移除 {user_id}")
282
+ return "OK"
283
+ # ====== 修改用藥提醒流程 ======
284
+ elif state.get('step') == 'edit_medicine':
285
+ selected_medicine = user_input
286
+ conn = sqlite3.connect(DB_PATH)
287
+ cursor = conn.cursor()
288
+ cursor.execute(
289
+ "SELECT id, start_date, end_date, times FROM reminders WHERE user_id=? AND medicine=? ORDER BY id DESC LIMIT 1",
290
+ (user_id, selected_medicine)
291
+ )
292
+ row = cursor.fetchone()
293
+ conn.close()
294
+ if not row:
295
+ reply_text = "查無此藥品提醒資料。"
296
+ reply_request = ReplyMessageRequest(
297
+ reply_token=event.reply_token,
298
+ messages=[TextMessage(text=reply_text)]
299
+ )
300
+ messaging_api.reply_message(reply_message_request=reply_request)
301
+ user_states.pop(user_id, None)
302
+ return "OK"
303
+ reminder_id, start_date, end_date, times_json = row
304
+ times = ','.join(json.loads(times_json))
305
+ reply_text = (
306
+ f"你目前的提醒設定:\n"
307
+ f"藥品:{selected_medicine}\n"
308
+ f"開始:{start_date}\n"
309
+ f"結束:{end_date}\n"
310
+ f"時間:{times}\n"
311
+ "請選擇要修改的欄位,或輸入 完成 結束:"
312
+ )
313
+ quick_reply = QuickReply(
314
+ items=[
315
+ QuickReplyItem(action=MessageAction(label="開始日期", text="開始日期")),
316
+ QuickReplyItem(action=MessageAction(label="結束日期", text="結束日期")),
317
+ QuickReplyItem(action=MessageAction(label="提醒時間", text="提醒時間")),
318
+ QuickReplyItem(action=MessageAction(label="完成", text="完成")),
319
+ ]
320
+ )
321
+ state['step'] = 'edit_field'
322
+ state['reminder_id'] = reminder_id
323
+ state['medicine'] = selected_medicine
324
+ reply_request = ReplyMessageRequest(
325
+ reply_token=event.reply_token,
326
+ messages=[TextMessage(text=reply_text, quick_reply=quick_reply)]
327
+ )
328
+ messaging_api.reply_message(reply_message_request=reply_request)
329
+ return "OK"
330
+ elif state.get('step') == 'edit_field':
331
+ field = user_input.strip()
332
+ if field == "開始日期":
333
+ state['step'] = 'edit_start_date'
334
+ quick_reply = QuickReply(
335
+ items=[
336
+ QuickReplyItem(
337
+ action=DatetimePickerAction(
338
+ label="選擇開始日期",
339
+ data="edit_start_date",
340
+ mode="date"
341
+ )
342
+ )
343
+ ]
344
+ )
345
+ reply_text = "請選擇新的開始日期:"
346
+ reply_request = ReplyMessageRequest(
347
+ reply_token=event.reply_token,
348
+ messages=[TextMessage(text=reply_text, quick_reply=quick_reply)]
349
+ )
350
+ messaging_api.reply_message(reply_message_request=reply_request)
351
+ return "OK"
352
+ elif field == "結束日期":
353
+ state['step'] = 'edit_end_date'
354
+ quick_reply = QuickReply(
355
+ items=[
356
+ QuickReplyItem(
357
+ action=DatetimePickerAction(
358
+ label="選擇結束日期",
359
+ data="edit_end_date",
360
+ mode="date"
361
+ )
362
+ )
363
+ ]
364
+ )
365
+ reply_text = "請選擇新的結束日期:"
366
+ reply_request = ReplyMessageRequest(
367
+ reply_token=event.reply_token,
368
+ messages=[TextMessage(text=reply_text, quick_reply=quick_reply)]
369
+ )
370
+ messaging_api.reply_message(reply_message_request=reply_request)
371
+ return "OK"
372
+ elif field == "提醒時間":
373
+ state['step'] = 'edit_times'
374
+ reply_text = "請輸入新的提醒時間(24小時制,用逗號分隔):"
375
+ reply_request = ReplyMessageRequest(
376
+ reply_token=event.reply_token,
377
+ messages=[TextMessage(text=reply_text)]
378
+ )
379
+ messaging_api.reply_message(reply_message_request=reply_request)
380
+ return "OK"
381
+ elif field.lower() == "完成":
382
+ reply_text = "已結束修改。"
383
+ user_states.pop(user_id, None)
384
+ reply_request = ReplyMessageRequest(
385
+ reply_token=event.reply_token,
386
+ messages=[TextMessage(text=reply_text)]
387
+ )
388
+ messaging_api.reply_message(reply_message_request=reply_request)
389
+ return "OK"
390
+ else:
391
+ # 再次顯示選單
392
+ quick_reply = QuickReply(
393
+ items=[
394
+ QuickReplyItem(action=MessageAction(label="開始日期", text="開始日期")),
395
+ QuickReplyItem(action=MessageAction(label="結束日期", text="結束日期")),
396
+ QuickReplyItem(action=MessageAction(label="提醒時間", text="提醒時間")),
397
+ QuickReplyItem(action=MessageAction(label="完成", text="完成")),
398
+ ]
399
+ )
400
+ reply_text = "請選擇要修改的欄位,或輸入 完成 結束:"
401
+ reply_request = ReplyMessageRequest(
402
+ reply_token=event.reply_token,
403
+ messages=[TextMessage(text=reply_text, quick_reply=quick_reply)]
404
+ )
405
+ messaging_api.reply_message(reply_message_request=reply_request)
406
+ return "OK"
407
+ elif state.get('step') == 'edit_times':
408
+ import re
409
+ times = [t.strip() for t in user_input.split(",") if t.strip()]
410
+ valid = all(re.match(r"^(?:[01]\d|2[0-3]):[0-5]\d$", t) for t in times)
411
+ if not times or not valid:
412
+ reply_text = "時間格式錯誤,請重新輸入(24小時制,如 08:00,12:00,18:00):"
413
+ reply_request = ReplyMessageRequest(
414
+ reply_token=event.reply_token,
415
+ messages=[TextMessage(text=reply_text)]
416
+ )
417
+ messaging_api.reply_message(reply_message_request=reply_request)
418
+ return "OK"
419
+ conn = sqlite3.connect(DB_PATH)
420
+ cursor = conn.cursor()
421
+ cursor.execute("UPDATE reminders SET times=? WHERE id=?", (json.dumps(times), state['reminder_id']))
422
+ conn.commit()
423
+ conn.close()
424
+ reply_text = "提醒時間已更新!"
425
+ # 修改完繼續顯示選單
426
+ quick_reply = QuickReply(
427
+ items=[
428
+ QuickReplyItem(action=MessageAction(label="開始日期", text="開始日期")),
429
+ QuickReplyItem(action=MessageAction(label="結束日期", text="結束日期")),
430
+ QuickReplyItem(action=MessageAction(label="提醒時間", text="提醒時間")),
431
+ QuickReplyItem(action=MessageAction(label="完成", text="完成")),
432
+ ]
433
+ )
434
+ reply_text += "\n請選擇要繼續修改的欄位,或輸入 完成 結束:"
435
+ reply_request = ReplyMessageRequest(
436
+ reply_token=event.reply_token,
437
+ messages=[TextMessage(text=reply_text, quick_reply=quick_reply)]
438
+ )
439
+ messaging_api.reply_message(reply_message_request=reply_request)
440
+ state['step'] = 'edit_field'
441
+ return "OK"
442
+
443
+ # ====== 其他功能區塊(查詢藥品、AI、藥局、圖片) ======
444
+ user_input = event.message.text.strip()
445
+ print("[DEBUG] 進入原有功能區塊,收到訊息:", user_input)
446
+
447
+ # AI 問答
448
+ if user_input.startswith("AI "):
449
+ prompt = "你是一個中文的AI助手,請用繁體中文回答。\n" + user_input[3:].strip()
450
+ try:
451
+ response = chat.generate_content(prompt)
452
+ reply_text = response.text
453
+ except Exception as e:
454
+ logging.exception("AI 問答發生錯誤")
455
+ reply_text = "⚠️ AI 回答失敗,請稍後再試"
456
+ reply_request = ReplyMessageRequest(
457
+ reply_token=event.reply_token,
458
+ messages=[TextMessage(text=reply_text)]
459
+ )
460
+ messaging_api.reply_message(reply_message_request=reply_request)
461
+ return "OK"
462
+
463
+ # 查詢藥品
464
+ elif user_input == "查詢藥品":
465
+ try:
466
+ # 這裡應該要有 medicine_name 的來源,通常是 user_states 或請用戶再輸入
467
+ medicine_name = user_states.get(user_id, {}).get('medicine')
468
+ if not medicine_name:
469
+ reply_text = "請輸入要查詢的藥品名稱:"
470
+ else:
471
+ medicine_name = medicine_name.strip().lower()
472
+ conn = sqlite3.connect(DB_PATH)
473
+ cursor = conn.cursor()
474
+ query = """
475
+ SELECT DISTINCT 中文品名, 英文品名, 適應症
476
+ FROM drugs
477
+ WHERE LOWER(中文品名) = ? OR LOWER(英文品名) = ?
478
+ LIMIT 1
479
+ """
480
+ cursor.execute(query, (medicine_name, medicine_name))
481
+ row = cursor.fetchone()
482
+ conn.close()
483
+ print(f"[DEBUG] 查詢 drugs 結果:{row}")
484
+
485
+ if row:
486
+ zh_name, en_name, indication = row
487
+ # 副作用由 AI 產生
488
+ prompt = (
489
+ f"請只用簡短條列式(每點用-開頭,不要用*),僅列出副作用,"
490
+ f"針對藥品「{zh_name}」(英文名:{en_name}),"
491
+ "請用繁體中文回答,不要加任何說明、警語或強調語句。"
492
+ )
493
+ try:
494
+ ai_resp = chat.generate_content(prompt)
495
+ side_effects = ai_resp.text.strip()
496
+ except Exception as e:
497
+ logging.exception("AI 產生副作用失敗")
498
+ side_effects = f"AI 回答失敗:{e}"
499
+ reply_text = (
500
+ f"🔹 中文品名:{zh_name}\n"
501
+ f"📌 英文品名:{en_name}\n"
502
+ f"📄 適應症:{indication}\n"
503
+ f"⚠️ 副作用:\n{side_effects}"
504
+ )
505
+ else:
506
+ reply_text = "未找到相關藥品,請重新輸入"
507
+ except Exception as e:
508
+ logging.exception("查詢資料時發生錯誤")
509
+ reply_text = f"⚠️ 查詢資料時發生錯誤,請稍後再試"
510
+
511
+ reply_request = ReplyMessageRequest(
512
+ reply_token=event.reply_token,
513
+ messages=[TextMessage(text=reply_text.strip())]
514
+ )
515
+ messaging_api.reply_message(reply_message_request=reply_request)
516
+
517
+ #圖片查詢
518
+ elif user_input == "圖片查詢":
519
+ reply_text = "請傳送藥品圖片:"
520
+ reply_request = ReplyMessageRequest(
521
+ reply_token=event.reply_token,
522
+ messages=[TextMessage(text=reply_text)]
523
+ )
524
+ messaging_api.reply_message(reply_message_request=reply_request)
525
+ return "OK"
526
+
527
+ # 查詢藥局
528
+ elif "查詢藥局" in user_input:
529
+ try:
530
+ quick_reply = QuickReply(
531
+ items=[QuickReplyItem(action=LocationAction(label="傳送我的位置"))]
532
+ )
533
+ reply_request = ReplyMessageRequest(
534
+ reply_token=event.reply_token,
535
+ messages=[TextMessage(text="請點選下方按鈕傳送你的位置,我才能幫你找附近藥局喔~", quick_reply=quick_reply)]
536
+ )
537
+ messaging_api.reply_message(reply_message_request=reply_request)
538
+ except Exception as e:
539
+ logging.exception("查詢藥局發生錯誤")
540
+ reply_text = "⚠️ 查詢藥局失敗,請稍後再試"
541
+ reply_request = ReplyMessageRequest(
542
+ reply_token=event.reply_token,
543
+ messages=[TextMessage(text=reply_text)]
544
+ )
545
+ messaging_api.reply_message(reply_message_request=reply_request)
546
+ return "OK"
547
+ else:
548
+ try:
549
+ medicine_name = user_input
550
+ conn = sqlite3.connect(DB_PATH)
551
+ cursor = conn.cursor()
552
+ query = """
553
+ SELECT DISTINCT 中文品名, 英文品名, 適應症
554
+ FROM drugs
555
+ WHERE 中文品名 LIKE ? OR 英文品名 LIKE ?
556
+ LIMIT 1
557
+ """
558
+ like_param = f'%{medicine_name}%'
559
+ cursor.execute(query, (like_param, like_param))
560
+ row = cursor.fetchone()
561
+ conn.close()
562
+ print(f"[DEBUG] 查詢 drugs 結果:{row}")
563
+
564
+ if row:
565
+ zh_name, en_name, indication = row
566
+ # 副作用由 AI 產生
567
+ prompt = (
568
+ f"請只用簡短條列式(每點用-開頭,不要用*),僅列出副作用,"
569
+ f"針對藥品「{zh_name}」(英文名:{en_name}),"
570
+ "請用繁體中文回答,不要加任何說明、警語或強調語句。"
571
+ )
572
+ try:
573
+ ai_resp = chat.generate_content(prompt)
574
+ side_effects = ai_resp.text.strip()
575
+ except Exception as e:
576
+ side_effects = f"AI 回答失敗:{e}"
577
+ reply_text = (
578
+ f"🔹 中文品名:{zh_name}\n"
579
+ f"📌 英文品名:{en_name}\n"
580
+ f"📄 適應症:{indication}\n"
581
+ f"⚠️ 副作用:\n{side_effects}"
582
+ )
583
+ else:
584
+ prompt = (
585
+ f"請用以下格式,幫我介紹藥品「{medicine_name}」,"
586
+ "只要條列資料本身,不要加任何說明、警語或強調語句:\n"
587
+ "🔹 中文品名:\n"
588
+ "📌 英文品名:\n"
589
+ "📄 適應症:\n"
590
+ "⚠️ 副作用:\n(請用-開頭條列,不要用*)"
591
+ )
592
+ try:
593
+ ai_resp = chat.generate_content(prompt)
594
+ reply_text = ai_resp.text
595
+ except Exception as e:
596
+ reply_text = f"AI 回答失敗:{e}"
597
+
598
+ except Exception as e:
599
+ reply_text = f"⚠️ 查詢資料時發生錯誤:{str(e)}"
600
+
601
+ reply_request = ReplyMessageRequest(
602
+ reply_token=event.reply_token,
603
+ messages=[TextMessage(text=reply_text.strip())]
604
+ )
605
+ messaging_api.reply_message(reply_message_request=reply_request)
606
+
607
+ elif event.type == "message" and event.message.type == "location":
608
+ print("[DEBUG] 收到位置訊息")
609
+ user_lat = event.message.latitude
610
+ user_lng = event.message.longitude
611
+
612
+ nearby_url = (
613
+ f"https://maps.googleapis.com/maps/api/place/nearbysearch/json?"
614
+ f"location={user_lat},{user_lng}&radius=1000&type=pharmacy&language=zh-TW&key={GOOGLE_MAP_API_KEY}"
615
+ )
616
+ nearby_res = requests.get(nearby_url).json()
617
+ print(f"[DEBUG] nearby_res: {nearby_res}")
618
+
619
+ if not nearby_res.get('results'):
620
+ reply_request = ReplyMessageRequest(
621
+ reply_token=event.reply_token,
622
+ messages=[TextMessage(text="附近找不到藥局")]
623
+ )
624
+ messaging_api.reply_message(reply_message_request=reply_request)
625
+ return "OK"
626
+
627
+ bubbles = []
628
+ for place in nearby_res['results'][:3]:
629
+ place_id = place['place_id']
630
+ name = place.get('name', '藥局名稱未知')
631
+ address = place.get('vicinity', '地址不詳')
632
+ location = place['geometry']['location']
633
+ dest_lat, dest_lng = location['lat'], location['lng']
634
+
635
+ # 取得電話
636
+ details_url = (
637
+ f"https://maps.googleapis.com/maps/api/place/details/json?"
638
+ f"place_id={place_id}&fields=name,formatted_phone_number&key={GOOGLE_MAP_API_KEY}"
639
+ )
640
+ details_res = requests.get(details_url).json()
641
+ phone = details_res.get('result', {}).get('formatted_phone_number', '電話不詳')
642
+
643
+ # 取得距離
644
+ dist_url = (
645
+ f"https://maps.googleapis.com/maps/api/distancematrix/json?"
646
+ f"origins={user_lat},{user_lng}&destinations={dest_lat},{dest_lng}&key={GOOGLE_MAP_API_KEY}"
647
+ )
648
+ dist_res = requests.get(dist_url).json()
649
+ distance = dist_res['rows'][0]['elements'][0]['distance']['text']
650
+
651
+ map_url = f"https://www.google.com/maps/search/?api=1&query={dest_lat},{dest_lng}"
652
+
653
+ bubble = FlexBubble(
654
+ body=FlexBox(
655
+ layout="vertical",
656
+ contents=[
657
+ FlexText(text=name, weight="bold", size="lg"),
658
+ FlexText(text=f"地址:{address}", size="sm", color="#555555", wrap=True),
659
+ FlexText(text=f"電話:{phone}", size="sm", color="#555555"),
660
+ FlexText(text=f"距離:{distance}", size="sm", color="#777777"),
661
+ ],
662
+ ),
663
+ footer=FlexBox(
664
+ layout="vertical",
665
+ contents=[
666
+ FlexButton(
667
+ style="link",
668
+ height="sm",
669
+ action=URIAction(label="地圖導航", uri=map_url),
670
+ )
671
+ ],
672
+ ),
673
+ )
674
+ bubbles.append(bubble)
675
+
676
+ from linebot.v3.messaging.models import FlexCarousel, FlexMessage
677
+
678
+ carousel = FlexCarousel(contents=bubbles)
679
+ flex_message = FlexMessage(
680
+ alt_text="附近藥局推薦",
681
+ contents=carousel
682
+ )
683
+
684
+ reply_request = ReplyMessageRequest(
685
+ reply_token=event.reply_token,
686
+ messages=[flex_message]
687
+ )
688
+ messaging_api.reply_message(reply_message_request=reply_request)
689
+ return "OK"
690
+ elif event.type == "message" and event.message.type == "image":
691
+ print("[DEBUG] 收到圖片訊息")
692
+ try:
693
+ content = blob_api.get_message_content(message_id=event.message.id)
694
+ with tempfile.NamedTemporaryFile(dir=static_tmp_path, suffix=".jpg", delete=False) as tf:
695
+ tf.write(content)
696
+ filename = os.path.basename(tf.name)
697
+ image = Image.open(tf.name)
698
+
699
+ prompt = (
700
+ "請根據這張圖片判斷藥品資訊,若圖片無法判斷適應症或副作用,請根據藥品名稱推測並補充,"
701
+ "只要條列資料本身,不要加任何說明、警語或強調語句,也不要加**:\n"
702
+ "🔹 中文品名:\n"
703
+ "📌 英文品名:\n"
704
+ "📄 適應症:\n"
705
+ "⚠️ 副作用:\n(請用-開頭條列,不要用*)"
706
+ )
707
+
708
+ response = chat.generate_content([image, prompt])
709
+ description = response.text
710
+
711
+ reply_request = ReplyMessageRequest(
712
+ reply_token=event.reply_token,
713
+ messages=[TextMessage(text=description.strip())]
714
+ )
715
+ messaging_api.reply_message(reply_message_request=reply_request)
716
+ except Exception as e:
717
+ logging.exception("圖片處理發生錯誤")
718
+ reply_text = "⚠️ 圖片處理失敗,請稍後再試"
719
+ reply_request = ReplyMessageRequest(
720
+ reply_token=event.reply_token,
721
+ messages=[TextMessage(text=reply_text)]
722
+ )
723
+ messaging_api.reply_message(reply_message_request=reply_request)
724
+ return "OK"
725
+
726
+ elif event.type == "postback":
727
+ user_id = event.source.user_id
728
+ data = event.postback.data
729
+ print(f"[DEBUG] postback data: {data}, user_states: {user_states.get(user_id)}")
730
+ # 用藥提醒步驟分開訊息
731
+ if data == "start_date":
732
+ user_states[user_id]['start_date'] = event.postback.params['date']
733
+ user_states[user_id]['step'] = 'ask_end'
734
+ # 先回覆
735
+ reply_request = ReplyMessageRequest(
736
+ reply_token=event.reply_token,
737
+ messages=[TextMessage(text=f"你選擇的開始日期為:{event.postback.params['date']}")]
738
+ )
739
+ messaging_api.reply_message(reply_message_request=reply_request)
740
+ # 再推送下一步
741
+ quick_reply = QuickReply(
742
+ items=[
743
+ QuickReplyItem(
744
+ action=DatetimePickerAction(
745
+ label="選擇結束日期",
746
+ data="end_date",
747
+ mode="date"
748
+ )
749
+ )
750
+ ]
751
+ )
752
+ messaging_api.push_message(
753
+ push_message_request=PushMessageRequest(
754
+ to=user_id,
755
+ messages=[TextMessage(text="請選擇提醒結束日期:", quick_reply=quick_reply)]
756
+ )
757
+ )
758
+ return "OK"
759
+ elif data == "end_date":
760
+ user_states[user_id]['end_date'] = event.postback.params['date']
761
+ user_states[user_id]['step'] = 'ask_times'
762
+ reply_request = ReplyMessageRequest(
763
+ reply_token=event.reply_token,
764
+ messages=[TextMessage(text=f"你選擇的結束日期為:{event.postback.params['date']}")]
765
+ )
766
+ messaging_api.reply_message(reply_message_request=reply_request)
767
+ messaging_api.push_message(
768
+ push_message_request=PushMessageRequest(
769
+ to=user_id,
770
+ messages=[TextMessage(text="請輸入每天要提醒的時間(24小時制,可多個,用逗號分隔,如 08:00,12:00,18:00):")]
771
+ )
772
+ )
773
+ return "OK"
774
+ # 修改用藥提醒步驟分開訊息
775
+ elif data == "edit_start_date":
776
+ user_states[user_id]['step'] = 'edit_field'
777
+ new_start = event.postback.params['date']
778
+ conn = sqlite3.connect(DB_PATH)
779
+ cursor = conn.cursor()
780
+ cursor.execute("UPDATE reminders SET start_date=? WHERE id=?", (new_start, user_states[user_id]['reminder_id']))
781
+ conn.commit()
782
+ conn.close()
783
+ reply_request = ReplyMessageRequest(
784
+ reply_token=event.reply_token,
785
+ messages=[TextMessage(text=f"開始日期已更新為:{new_start}")]
786
+ )
787
+ messaging_api.reply_message(reply_message_request=reply_request)
788
+ quick_reply = QuickReply(
789
+ items=[
790
+ QuickReplyItem(action=MessageAction(label="開始日期", text="開始日期")),
791
+ QuickReplyItem(action=MessageAction(label="結束日期", text="結束日期")),
792
+ QuickReplyItem(action=MessageAction(label="提醒時間", text="提醒時間")),
793
+ QuickReplyItem(action=MessageAction(label="完成", text="完成")),
794
+ ]
795
+ )
796
+ messaging_api.push_message(
797
+ push_message_request=PushMessageRequest(
798
+ to=user_id,
799
+ messages=[TextMessage(text="請選擇要繼續修改的欄位,或輸入 完成 結���:", quick_reply=quick_reply)]
800
+ )
801
+ )
802
+ return "OK"
803
+ elif data == "edit_end_date":
804
+ user_states[user_id]['step'] = 'edit_field'
805
+ new_end = event.postback.params['date']
806
+ conn = sqlite3.connect(DB_PATH)
807
+ cursor = conn.cursor()
808
+ cursor.execute("UPDATE reminders SET end_date=? WHERE id=?", (new_end, user_states[user_id]['reminder_id']))
809
+ conn.commit()
810
+ conn.close()
811
+ reply_request = ReplyMessageRequest(
812
+ reply_token=event.reply_token,
813
+ messages=[TextMessage(text=f"結束日期已更新為:{new_end}")]
814
+ )
815
+ messaging_api.reply_message(reply_message_request=reply_request)
816
+ quick_reply = QuickReply(
817
+ items=[
818
+ QuickReplyItem(action=MessageAction(label="開始日期", text="開始日期")),
819
+ QuickReplyItem(action=MessageAction(label="結束日期", text="結束日期")),
820
+ QuickReplyItem(action=MessageAction(label="提醒時間", text="提醒時間")),
821
+ QuickReplyItem(action=MessageAction(label="完成", text="完成")),
822
+ ]
823
+ )
824
+ messaging_api.push_message(
825
+ push_message_request=PushMessageRequest(
826
+ to=user_id,
827
+ messages=[TextMessage(text="請選擇要繼續修改的欄位,或輸入 完成 結束:", quick_reply=quick_reply)]
828
+ )
829
+ )
830
+ return "OK"
831
+
832
+ print("[DEBUG] callback 執行結束")
833
+ return "OK"
834
+
835
+ if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
836
  app.run(host="0.0.0.0", port=7860)