cwadayi commited on
Commit
2744551
·
verified ·
1 Parent(s): 3234236

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +43 -219
app.py CHANGED
@@ -1,5 +1,4 @@
1
  import os
2
- # ✅ Matplotlib config/cache 放入可寫路徑
3
  os.environ["MPLCONFIGDIR"] = "/tmp/matplotlib"
4
 
5
  import uuid
@@ -20,7 +19,7 @@ from linebot.v3.webhooks import MessageEvent, TextMessageContent
20
  import requests
21
  import pandas as pd
22
 
23
- # --- Matplotlib (headless) ---
24
  import matplotlib
25
  matplotlib.use("Agg")
26
  import matplotlib.pyplot as plt
@@ -28,7 +27,10 @@ from matplotlib.colors import Normalize
28
  import matplotlib.cm as cm
29
  from matplotlib import font_manager as fm
30
 
31
- # ✅ 嘗試載入支援中文的字型
 
 
 
32
  possible_fonts = [
33
  "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
34
  "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
@@ -39,82 +41,31 @@ for font_path in possible_fonts:
39
  if os.path.exists(font_path):
40
  font_prop = fm.FontProperties(fname=font_path)
41
  matplotlib.rcParams["font.family"] = font_prop.get_name()
42
- print(f"✅ Using CJK font: {font_prop.get_name()}")
43
  break
44
- else:
45
- print("⚠️ No CJK font found, Chinese glyphs may not display correctly.")
46
 
47
- # --- Environment Variables ---
48
  CHANNEL_ACCESS_TOKEN = os.getenv("CHANNEL_ACCESS_TOKEN")
49
  CHANNEL_SECRET = os.getenv("CHANNEL_SECRET")
50
 
51
- # HF_SPACE_URL 自動偵測(若未手動設定 SPACEURL)
52
  HF_SPACE_URL = os.getenv("SPACEURL")
53
  if not HF_SPACE_URL:
54
- space_id = os.getenv("SPACE_ID") # e.g., username/space-name
55
  if space_id and "/" in space_id:
56
- author, name = space_id.split("/", 1)
57
- HF_SPACE_URL = f"https://{author.replace('_','-')}-{name.replace('_','-')}.hf.space"
58
- print(f"🔍 Auto-detected HF_SPACE_URL = {HF_SPACE_URL}")
59
  else:
60
- HF_SPACE_URL = "" # fallback 由 request.url_root 決定
61
 
62
- # --- 可寫入的靜態目錄 ---
63
  STATIC_DIR = os.getenv("STATIC_DIR", os.path.join(tempfile.gettempdir(), "static"))
64
  os.makedirs(STATIC_DIR, exist_ok=True)
65
 
66
- # --- Flask & LINE Bot ---
67
  app = Flask(__name__)
68
  configuration = Configuration(access_token=CHANNEL_ACCESS_TOKEN)
69
  handler = WebhookHandler(CHANNEL_SECRET)
70
 
71
- # -------------------- 共用小工具 --------------------
72
- def _iso(dt: datetime) -> str:
73
- return dt.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")
74
-
75
- def _base_url_for_images() -> str:
76
- if HF_SPACE_URL:
77
- return HF_SPACE_URL.rstrip("/")
78
- return request.url_root.rstrip("/")
79
-
80
- def _fmt_dt_utc(ts_ms: int | float | None) -> str:
81
- """將毫秒或秒級 timestamp 轉成 YYYY-MM-DD HH:MM (UTC),未知回傳 '未知'"""
82
- if ts_ms is None:
83
- return "未知"
84
- try:
85
- # 多數 API 為毫秒;若是秒也能容錯
86
- if ts_ms > 1e12: # 毫秒
87
- dt = datetime.fromtimestamp(ts_ms / 1000, tz=timezone.utc)
88
- else:
89
- dt = datetime.fromtimestamp(ts_ms, tz=timezone.utc)
90
- return dt.strftime("%Y-%m-%d %H:%M")
91
- except Exception:
92
- return "未知"
93
-
94
- # -------------------- 路由 --------------------
95
  @app.route("/", methods=["GET"])
96
  def home():
97
- return """
98
- <html>
99
- <head>
100
- <title>LINE Bot Server</title>
101
- <style>
102
- body{font-family:Arial,sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;background:#f0f2f5;margin:0;}
103
- .container{max-width:720px;text-align:center;padding:40px;background:#fff;border-radius:12px;box-shadow:0 4px 16px rgba(0,0,0,.08)}
104
- h1{color:#1dcd00;margin:0 0 8px}
105
- p{color:#333;font-size:1.05rem;margin:.4rem 0}
106
- .status{font-weight:700;color:#28a745}
107
- </style>
108
- </head>
109
- <body>
110
- <div class="container">
111
- <h1>✓ LINE Bot Server is Running</h1>
112
- <p>This is the backend service for the Earthquake Alert Bot.</p>
113
- <p>The service is <span class="status">active</span> and listening for webhook events from LINE.</p>
114
- </div>
115
- </body>
116
- </html>
117
- """
118
 
119
  @app.route("/healthz")
120
  def healthz():
@@ -124,11 +75,13 @@ def healthz():
124
  def serve_static(filename):
125
  return send_from_directory(STATIC_DIR, filename)
126
 
127
- # -------------------- 資料存取:USGS --------------------
128
  USGS_API_BASE_URL = "https://earthquake.usgs.gov/fdsnws/event/1/query"
129
 
 
 
 
130
  def fetch_global_last24h_text(min_mag=5.0, limit=10) -> str:
131
- """顯示近 24 小時全球 M≥min_mag 地震,含日期。"""
132
  now_utc = datetime.now(timezone.utc)
133
  since = now_utc - timedelta(hours=24)
134
  params = {
@@ -145,7 +98,6 @@ def fetch_global_last24h_text(min_mag=5.0, limit=10) -> str:
145
  features = r.json().get("features", [])
146
  if not features:
147
  return "✅ 過去 24 小時內,全球無規模 5.0 以上的顯著地震。"
148
-
149
  lines = ["🚨 近 24 小時全球顯著地震 (M≥5.0):", "-" * 20]
150
  for f in features:
151
  p = f["properties"]
@@ -158,20 +110,14 @@ def fetch_global_last24h_text(min_mag=5.0, limit=10) -> str:
158
  return f"❌ 查詢失敗: {e}"
159
 
160
  def fetch_taiwan_df_this_year(min_mag=5.0) -> pd.DataFrame | str:
161
- """抓取今年台灣經緯度框內 M≥min_mag 地震的 DataFrame。"""
162
  now_utc = datetime.now(timezone.utc)
163
  start_of_year_utc = datetime(now_utc.year, 1, 1, tzinfo=timezone.utc)
164
  params = {
165
- "format": "geojson",
166
- "starttime": _iso(start_of_year_utc),
167
- "endtime": _iso(now_utc),
168
  "minmagnitude": float(min_mag),
169
- "minlatitude": 21,
170
- "maxlatitude": 26,
171
- "minlongitude": 119,
172
- "maxlongitude": 123,
173
- "limit": 250,
174
- "orderby": "time",
175
  }
176
  try:
177
  r = requests.get(USGS_API_BASE_URL, params=params, timeout=20)
@@ -183,120 +129,19 @@ def fetch_taiwan_df_this_year(min_mag=5.0) -> pd.DataFrame | str:
183
  for f in features:
184
  p = f["properties"]
185
  lon, lat, *_ = f["geometry"]["coordinates"]
186
- rows.append(
187
- {
188
- "latitude": lat,
189
- "longitude": lon,
190
- "magnitude": p["mag"],
191
- "place": p.get("place", ""),
192
- "time_utc": datetime.fromtimestamp(p["time"] / 1000, tz=timezone.utc),
193
- }
194
- )
195
  return pd.DataFrame(rows)
196
  except Exception as e:
197
  return f"❌ 查詢失敗: {e}"
198
 
199
- # -------------------- 資料存取:CWA 地震預警 --------------------
200
- CWA_ALARM_API = "https://app-2.cwa.gov.tw/api/v1/earthquake/alarm/list"
201
-
202
- def fetch_cwa_alarm_list(limit: int = 5) -> str:
203
- """
204
- 取得交通部中央氣象署「地震預警」清單並格式化為文字。
205
- 盡量容錯不同欄位名稱;最多輸出 limit 筆。
206
- """
207
- try:
208
- r = requests.get(CWA_ALARM_API, timeout=10)
209
- r.raise_for_status()
210
- data = r.json()
211
- except Exception as e:
212
- return f"❌ 地震預警查詢失敗:{e}"
213
-
214
- # 嘗試從常見包裝鍵取出陣列
215
- items = None
216
- for key in ("data", "records", "result", "list", "items"):
217
- if isinstance(data, dict) and isinstance(data.get(key), list):
218
- items = data.get(key)
219
- break
220
- if items is None:
221
- # 也可能根本就是陣列
222
- if isinstance(data, list):
223
- items = data
224
- else:
225
- return "⚠️ 無法解析地震預警資料(未知格式)。"
226
-
227
- if not items:
228
- return "✅ 目前沒有地震預警。"
229
-
230
- lines = ["🚨 地震預警(最新):", "-" * 20]
231
- count = 0
232
- for it in items:
233
- if count >= limit:
234
- break
235
- # 嘗試各種常見欄位名稱
236
- event_id = it.get("eventId") or it.get("eventid") or it.get("id") or "—"
237
- # 可能是 ISO 字串、毫秒或秒
238
- origin = it.get("originTime") or it.get("time") or it.get("origin_time") or it.get("earthquake_time")
239
- dt_str = "未知"
240
- if isinstance(origin, (int, float)):
241
- dt_str = _fmt_dt_utc(origin)
242
- elif isinstance(origin, str):
243
- # 直接顯示(若是 ISO 字串)
244
- try:
245
- # 嘗試 parse 並以 UTC 格式化
246
- dt = datetime.fromisoformat(origin.replace("Z", "+00:00"))
247
- dt_str = dt.astimezone(timezone.utc).strftime("%Y-%m-%d %H:%M")
248
- except Exception:
249
- dt_str = origin # 無法解析就原樣輸出
250
-
251
- mag = it.get("magnitude") or it.get("mag") or it.get("ml") or it.get("mw") or "—"
252
- depth = it.get("depth") or "—"
253
- lat = it.get("latitude") or it.get("lat") or "—"
254
- lon = it.get("longitude") or it.get("lon") or "—"
255
- place = it.get("location") or it.get("place") or it.get("region") or "—"
256
-
257
- # 預警區域/縣市(可能是陣列或字串)
258
- areas = it.get("areas") or it.get("alertAreas") or it.get("counties") or it.get("cities")
259
- if isinstance(areas, list):
260
- # 可能是字串陣列或物件陣列
261
- try:
262
- area_names = []
263
- for a in areas:
264
- if isinstance(a, str):
265
- area_names.append(a)
266
- elif isinstance(a, dict):
267
- name = a.get("name") or a.get("area") or a.get("city") or a.get("county")
268
- if name:
269
- area_names.append(str(name))
270
- areas_txt = "、".join(area_names) if area_names else "—"
271
- except Exception:
272
- areas_txt = "—"
273
- elif isinstance(areas, str):
274
- areas_txt = areas
275
- else:
276
- areas_txt = "—"
277
-
278
- lines.append(
279
- f"事件: {event_id}\n"
280
- f"日期時間: {dt_str} (UTC)\n"
281
- f"震級/深度: M{mag} / {depth} km\n"
282
- f"位置: {place}(lat {lat}, lon {lon})\n"
283
- f"預警區域: {areas_txt}"
284
- )
285
- lines.append("") # 空行
286
- count += 1
287
-
288
- if len(items) > limit:
289
- lines.append(f"... 另有 {len(items) - limit} 筆。")
290
-
291
- return "\n".join(lines).strip()
292
-
293
- # -------------------- 繪圖 --------------------
294
  def create_and_save_map(df: pd.DataFrame) -> str:
295
  fig, ax = plt.subplots(figsize=(9, 6), dpi=150)
296
- lon_min, lon_max = 118.5, 123.5
297
- lat_min, lat_max = 20.5, 26.8
298
- ax.set_xlim(lon_min, lon_max)
299
- ax.set_ylim(lat_min, lat_max)
300
  ax.set_xlabel("Longitude (°E)")
301
  ax.set_ylabel("Latitude (°N)")
302
  ax.set_title(f"今年 ({datetime.now(timezone.utc).year}) 台灣區域顯著地震 (M≥5.0) — UTC")
@@ -308,18 +153,10 @@ def create_and_save_map(df: pd.DataFrame) -> str:
308
  colors = cmap(norm(mags.values))
309
  sizes = 15 + (mags - mags.min()) * 25
310
 
311
- ax.scatter(
312
- df["longitude"].values,
313
- df["latitude"].values,
314
- s=sizes,
315
- c=colors,
316
- edgecolor="k",
317
- linewidths=0.4,
318
- alpha=0.9,
319
- )
320
 
321
- cbar = fig.colorbar(cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax, pad=0.02)
322
- cbar.set_label("Magnitude")
323
 
324
  filename = f"map_{uuid.uuid4().hex}.png"
325
  filepath = os.path.join(STATIC_DIR, filename)
@@ -328,7 +165,10 @@ def create_and_save_map(df: pd.DataFrame) -> str:
328
  plt.close(fig)
329
  return filename
330
 
331
- # -------------------- LINE Webhook --------------------
 
 
 
332
  @app.route("/callback", methods=["POST"])
333
  def callback():
334
  signature = request.headers.get("X-Line-Signature")
@@ -339,15 +179,13 @@ def callback():
339
  abort(400)
340
  return "OK"
341
 
342
- # -------------------- Message Handler --------------------
343
  @handler.add(MessageEvent, message=TextMessageContent)
344
  def handle_message(event):
345
  user_message = (event.message.text or "").strip().lower()
346
-
347
  with ApiClient(configuration) as api_client:
348
  line_bot_api = MessagingApi(api_client)
349
 
350
- # ➊ 地震預警(新指令)
351
  if "地震預警" in user_message:
352
  reply_text = fetch_cwa_alarm_list(limit=5)
353
  line_bot_api.reply_message_with_http_info(
@@ -355,7 +193,6 @@ def handle_message(event):
355
  )
356
  return
357
 
358
- # ➋ 臺灣地震畫圖
359
  if ("臺灣地震畫圖" in user_message) or ("台灣地震畫圖" in user_message):
360
  result = fetch_taiwan_df_this_year()
361
  if isinstance(result, pd.DataFrame):
@@ -365,37 +202,29 @@ def handle_message(event):
365
  reply_token=event.reply_token,
366
  messages=[
367
  TextMessage(text="🗺️ 已為您繪製今年台灣區域 M≥5.0 地震分佈圖(UTC)。"),
368
- ImageMessage(
369
- original_content_url=image_url,
370
- preview_image_url=image_url,
371
- ),
372
  ],
373
  )
374
  else:
375
- reply = ReplyMessageRequest(
376
- reply_token=event.reply_token,
377
- messages=[TextMessage(text=result)],
378
- )
379
  line_bot_api.reply_message_with_http_info(reply)
380
  return
381
 
382
- # ➌ 說明
383
  if user_message == "/help":
384
  text = (
385
  "📖 地震預警 dayichen 指令說明\n\n"
386
- "➡️ /help\n 說明:顯示此幫助訊息。\n\n"
387
- "➡️ 地震 / quake\n 說明:查詢全球最近 24 小時內,M≥5.0 的顯著地震(含日期時間)。\n\n"
388
- "➡️ 臺灣地震 / 台灣地震\n 說明:查詢今年以來台灣區�� (21–26°N, 119–123°E) M≥5.0 地震(含日期時間)。\n\n"
389
- "➡️ 臺灣地震畫圖 / 台灣地震畫圖\n 說明:繪製今年台灣區域 M≥5.0 地震分佈圖並回傳圖片。\n\n"
390
- "➡️ 地震預警\n 說明:取得中央氣象署地震預警清單(最新 5 筆)。\n\n"
391
- "➡️ 你好\n 說明:顯示歡迎訊息。"
392
  )
393
  line_bot_api.reply_message_with_http_info(
394
  ReplyMessageRequest(reply_token=event.reply_token, messages=[TextMessage(text=text)])
395
  )
396
  return
397
 
398
- # ➍ 台灣清單
399
  if ("臺灣地震" in user_message) or ("台灣地震" in user_message):
400
  result = fetch_taiwan_df_this_year()
401
  if isinstance(result, pd.DataFrame):
@@ -414,7 +243,6 @@ def handle_message(event):
414
  )
415
  return
416
 
417
- # ➎ 全球 24 小時
418
  if ("地震" in user_message) or ("quake" in user_message):
419
  reply_text = fetch_global_last24h_text()
420
  line_bot_api.reply_message_with_http_info(
@@ -422,12 +250,8 @@ def handle_message(event):
422
  )
423
  return
424
 
425
- # ➏ 打招呼
426
  if ("你好" in user_message) or ("hi" in user_message):
427
  line_bot_api.reply_message_with_http_info(
428
- ReplyMessageRequest(
429
- reply_token=event.reply_token,
430
- messages=[TextMessage(text="👋 你好!我是地震查詢機器人。\n\n輸入 /help 查看所有指令。")]
431
- )
432
  )
433
  return
 
1
  import os
 
2
  os.environ["MPLCONFIGDIR"] = "/tmp/matplotlib"
3
 
4
  import uuid
 
19
  import requests
20
  import pandas as pd
21
 
22
+ # Matplotlib headless + CJK
23
  import matplotlib
24
  matplotlib.use("Agg")
25
  import matplotlib.pyplot as plt
 
27
  import matplotlib.cm as cm
28
  from matplotlib import font_manager as fm
29
 
30
+ # ✅ 匯入你剛拆出的模組
31
+ from fetch_cwa_alarm_list import fetch_cwa_alarm_list
32
+
33
+ # 設定 CJK 字型(如有)
34
  possible_fonts = [
35
  "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
36
  "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
 
41
  if os.path.exists(font_path):
42
  font_prop = fm.FontProperties(fname=font_path)
43
  matplotlib.rcParams["font.family"] = font_prop.get_name()
 
44
  break
 
 
45
 
 
46
  CHANNEL_ACCESS_TOKEN = os.getenv("CHANNEL_ACCESS_TOKEN")
47
  CHANNEL_SECRET = os.getenv("CHANNEL_SECRET")
48
 
49
+ # 自動偵測 HF Space 公網位址
50
  HF_SPACE_URL = os.getenv("SPACEURL")
51
  if not HF_SPACE_URL:
52
+ space_id = os.getenv("SPACE_ID") # username/space-name
53
  if space_id and "/" in space_id:
54
+ a, n = space_id.split("/", 1)
55
+ HF_SPACE_URL = f"https://{a.replace('_','-')}-{n.replace('_','-')}.hf.space"
 
56
  else:
57
+ HF_SPACE_URL = ""
58
 
 
59
  STATIC_DIR = os.getenv("STATIC_DIR", os.path.join(tempfile.gettempdir(), "static"))
60
  os.makedirs(STATIC_DIR, exist_ok=True)
61
 
 
62
  app = Flask(__name__)
63
  configuration = Configuration(access_token=CHANNEL_ACCESS_TOKEN)
64
  handler = WebhookHandler(CHANNEL_SECRET)
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  @app.route("/", methods=["GET"])
67
  def home():
68
+ return "LINE Bot Server is Running"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
  @app.route("/healthz")
71
  def healthz():
 
75
  def serve_static(filename):
76
  return send_from_directory(STATIC_DIR, filename)
77
 
78
+ # ---------- USGS ----------
79
  USGS_API_BASE_URL = "https://earthquake.usgs.gov/fdsnws/event/1/query"
80
 
81
+ def _iso(dt: datetime) -> str:
82
+ return dt.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")
83
+
84
  def fetch_global_last24h_text(min_mag=5.0, limit=10) -> str:
 
85
  now_utc = datetime.now(timezone.utc)
86
  since = now_utc - timedelta(hours=24)
87
  params = {
 
98
  features = r.json().get("features", [])
99
  if not features:
100
  return "✅ 過去 24 小時內,全球無規模 5.0 以上的顯著地震。"
 
101
  lines = ["🚨 近 24 小時全球顯著地震 (M≥5.0):", "-" * 20]
102
  for f in features:
103
  p = f["properties"]
 
110
  return f"❌ 查詢失敗: {e}"
111
 
112
  def fetch_taiwan_df_this_year(min_mag=5.0) -> pd.DataFrame | str:
 
113
  now_utc = datetime.now(timezone.utc)
114
  start_of_year_utc = datetime(now_utc.year, 1, 1, tzinfo=timezone.utc)
115
  params = {
116
+ "format": "geojson", "starttime": _iso(start_of_year_utc), "endtime": _iso(now_utc),
 
 
117
  "minmagnitude": float(min_mag),
118
+ "minlatitude": 21, "maxlatitude": 26,
119
+ "minlongitude": 119, "maxlongitude": 123,
120
+ "limit": 250, "orderby": "time",
 
 
 
121
  }
122
  try:
123
  r = requests.get(USGS_API_BASE_URL, params=params, timeout=20)
 
129
  for f in features:
130
  p = f["properties"]
131
  lon, lat, *_ = f["geometry"]["coordinates"]
132
+ rows.append({
133
+ "latitude": lat, "longitude": lon, "magnitude": p["mag"],
134
+ "place": p.get("place", ""), "time_utc": datetime.fromtimestamp(p["time"]/1000, tz=timezone.utc)
135
+ })
 
 
 
 
 
136
  return pd.DataFrame(rows)
137
  except Exception as e:
138
  return f"❌ 查詢失敗: {e}"
139
 
140
+ # ---------- 圖像 ----------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  def create_and_save_map(df: pd.DataFrame) -> str:
142
  fig, ax = plt.subplots(figsize=(9, 6), dpi=150)
143
+ ax.set_xlim(118.5, 123.5)
144
+ ax.set_ylim(20.5, 26.8)
 
 
145
  ax.set_xlabel("Longitude (°E)")
146
  ax.set_ylabel("Latitude (°N)")
147
  ax.set_title(f"今年 ({datetime.now(timezone.utc).year}) 台灣區域顯著地震 (M≥5.0) — UTC")
 
153
  colors = cmap(norm(mags.values))
154
  sizes = 15 + (mags - mags.min()) * 25
155
 
156
+ ax.scatter(df["longitude"].values, df["latitude"].values,
157
+ s=sizes, c=colors, edgecolor="k", linewidths=0.4, alpha=0.9)
 
 
 
 
 
 
 
158
 
159
+ fig.colorbar(cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax, pad=0.02).set_label("Magnitude")
 
160
 
161
  filename = f"map_{uuid.uuid4().hex}.png"
162
  filepath = os.path.join(STATIC_DIR, filename)
 
165
  plt.close(fig)
166
  return filename
167
 
168
+ def _base_url_for_images() -> str:
169
+ return HF_SPACE_URL.rstrip("/") if HF_SPACE_URL else request.url_root.rstrip("/")
170
+
171
+ # ---------- LINE Webhook ----------
172
  @app.route("/callback", methods=["POST"])
173
  def callback():
174
  signature = request.headers.get("X-Line-Signature")
 
179
  abort(400)
180
  return "OK"
181
 
 
182
  @handler.add(MessageEvent, message=TextMessageContent)
183
  def handle_message(event):
184
  user_message = (event.message.text or "").strip().lower()
 
185
  with ApiClient(configuration) as api_client:
186
  line_bot_api = MessagingApi(api_client)
187
 
188
+ # 地震預警(使用外部模組)
189
  if "地震預警" in user_message:
190
  reply_text = fetch_cwa_alarm_list(limit=5)
191
  line_bot_api.reply_message_with_http_info(
 
193
  )
194
  return
195
 
 
196
  if ("臺灣地震畫圖" in user_message) or ("台灣地震畫圖" in user_message):
197
  result = fetch_taiwan_df_this_year()
198
  if isinstance(result, pd.DataFrame):
 
202
  reply_token=event.reply_token,
203
  messages=[
204
  TextMessage(text="🗺️ 已為您繪製今年台灣區域 M≥5.0 地震分佈圖(UTC)。"),
205
+ ImageMessage(original_content_url=image_url, preview_image_url=image_url),
 
 
 
206
  ],
207
  )
208
  else:
209
+ reply = ReplyMessageRequest(reply_token=event.reply_token, messages=[TextMessage(text=result)])
 
 
 
210
  line_bot_api.reply_message_with_http_info(reply)
211
  return
212
 
 
213
  if user_message == "/help":
214
  text = (
215
  "📖 地震預警 dayichen 指令說明\n\n"
216
+ "➡️ /help\n"
217
+ "➡️ 地震 / quake\n"
218
+ "➡️ 臺灣地震 / 台灣地震\n"
219
+ "➡️ 臺灣地震畫圖 / 台灣地震畫圖\n"
220
+ "➡️ 地震預警(中央氣象署最新 5 筆)\n"
221
+ "➡️ 你好"
222
  )
223
  line_bot_api.reply_message_with_http_info(
224
  ReplyMessageRequest(reply_token=event.reply_token, messages=[TextMessage(text=text)])
225
  )
226
  return
227
 
 
228
  if ("臺灣地震" in user_message) or ("台灣地震" in user_message):
229
  result = fetch_taiwan_df_this_year()
230
  if isinstance(result, pd.DataFrame):
 
243
  )
244
  return
245
 
 
246
  if ("地震" in user_message) or ("quake" in user_message):
247
  reply_text = fetch_global_last24h_text()
248
  line_bot_api.reply_message_with_http_info(
 
250
  )
251
  return
252
 
 
253
  if ("你好" in user_message) or ("hi" in user_message):
254
  line_bot_api.reply_message_with_http_info(
255
+ ReplyMessageRequest(reply_token=event.reply_token, messages=[TextMessage(text="👋 你好!輸入 /help 查看指令。")])
 
 
 
256
  )
257
  return