cwadayi commited on
Commit
43cf13e
ยท
verified ยท
1 Parent(s): 7549469

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +98 -9
app.py CHANGED
@@ -32,9 +32,9 @@ from matplotlib import font_manager as fm
32
  # 1) ๅ˜—่ฉฆ่ผ‰ๅ…ฅใ€Œๅœฐ้œ‡้ ่ญฆใ€ๅค–้ƒจๆจก็ต„๏ผ›่‹ฅไธๅญ˜ๅœจๅฐฑ็”จๅ…งๅปบๅ‚™ๆด็‰ˆๆœฌ
33
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
34
  try:
35
- from fetch_cwa_alarm_list import fetch_cwa_alarm_list # ไฝ ่‹ฅๆ”พไบ†ๅŒๅๆช”ๆกˆ๏ผŒๆœƒ็”จ้‚ฃๅ€‹
 
36
  except Exception:
37
- # ๅ…งๅปบๅ‚™ๆด๏ผš็›ดๆŽฅๅ‘ผๅซ CWA API ไธฆๆ ผๅผๅŒ–่ผธๅ‡บ
38
  CWA_ALARM_API = "https://app-2.cwa.gov.tw/api/v1/earthquake/alarm/list"
39
 
40
  def _parse_cwa_time(s: str):
@@ -54,6 +54,7 @@ except Exception:
54
  return (s, "ๆœช็Ÿฅ")
55
 
56
  def fetch_cwa_alarm_list(limit: int = 5) -> str:
 
57
  try:
58
  r = requests.get(CWA_ALARM_API, timeout=10)
59
  r.raise_for_status()
@@ -69,7 +70,6 @@ except Exception:
69
  if not items:
70
  return "โœ… ็›ฎๅ‰ๆฒ’ๆœ‰ๅœฐ้œ‡้ ่ญฆใ€‚"
71
 
72
- # ไพ originTime ๆ–ฐโ†’่ˆŠ
73
  def _key(it):
74
  s = it.get("originTime") or ""
75
  try:
@@ -136,7 +136,7 @@ except Exception:
136
  return "\n".join(lines).strip()
137
 
138
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
139
- # 2) ไธญๆ–‡ๅญ—ๅž‹๏ผˆ่‹ฅๅฎนๅ™จๆœ‰ Noto/WenQuanYi ไน‹้กžๆœƒ่‡ชๅ‹•ๅฅ—็”จ๏ผ‰
140
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
141
  for fp in [
142
  "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
@@ -154,7 +154,7 @@ for fp in [
154
  CHANNEL_ACCESS_TOKEN = os.getenv("CHANNEL_ACCESS_TOKEN")
155
  CHANNEL_SECRET = os.getenv("CHANNEL_SECRET")
156
 
157
- # ๅ…ฌ็ถฒๅ€่‡ชๅ‹•ๅตๆธฌ๏ผˆHF Spaces๏ผ‰
158
  HF_SPACE_URL = os.getenv("SPACEURL")
159
  if not HF_SPACE_URL:
160
  sid = os.getenv("SPACE_ID") # ๅฝขๅฆ‚ user/space
@@ -172,11 +172,94 @@ configuration = Configuration(access_token=CHANNEL_ACCESS_TOKEN)
172
  handler = WebhookHandler(CHANNEL_SECRET)
173
 
174
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
175
- # 4) ๅทฅๅ…ทๅ‡ฝๅผ & ่ทฏ็”ฑ
176
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
177
  @app.route("/", methods=["GET"])
178
  def home():
179
- return "LINE Bot Server is Running"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
  @app.route("/healthz")
182
  def healthz():
@@ -186,6 +269,9 @@ def healthz():
186
  def serve_static(filename):
187
  return send_from_directory(STATIC_DIR, filename)
188
 
 
 
 
189
  USGS_API_BASE_URL = "https://earthquake.usgs.gov/fdsnws/event/1/query"
190
 
191
  def _iso(dt: datetime) -> str:
@@ -247,6 +333,9 @@ def fetch_taiwan_df_this_year(min_mag=5.0) -> pd.DataFrame | str:
247
  except Exception as e:
248
  return f"โŒ ๆŸฅ่ฉขๅคฑๆ•—: {e}"
249
 
 
 
 
250
  def create_and_save_map(df: pd.DataFrame) -> str:
251
  fig, ax = plt.subplots(figsize=(9, 6), dpi=150)
252
  ax.set_xlim(118.5, 123.5)
@@ -278,7 +367,7 @@ def _base_url_for_images() -> str:
278
  return HF_SPACE_URL.rstrip("/") if HF_SPACE_URL else request.url_root.rstrip("/")
279
 
280
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
281
- # 5) LINE Webhook
282
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
283
  @app.route("/callback", methods=["POST"])
284
  def callback():
@@ -330,7 +419,7 @@ def handle_message(event):
330
  "โžก๏ธ ๅœฐ้œ‡ / quake๏ผˆๅ…จ็ƒ่ฟ‘24ๅฐๆ™‚๏ผŒๅซๆ—ฅๆœŸๆ™‚้–“๏ผ‰\n"
331
  "โžก๏ธ ่‡บ็ฃๅœฐ้œ‡ / ๅฐ็ฃๅœฐ้œ‡๏ผˆไปŠๅนดๅฐ็ฃๅ€ๅŸŸๆธ…ๅ–ฎ๏ผ‰\n"
332
  "โžก๏ธ ่‡บ็ฃๅœฐ้œ‡็•ซๅœ– / ๅฐ็ฃๅœฐ้œ‡็•ซๅœ–๏ผˆไปŠๅนดๅฐ็ฃๅ€ๅŸŸๅˆ†ไฝˆๅœ–๏ผ‰\n"
333
- "โžก๏ธ ๅœฐ้œ‡้ ่ญฆ๏ผˆCWAๆœ€ๆ–ฐ5็ญ†๏ผ‰\n"
334
  "โžก๏ธ ไฝ ๅฅฝ"
335
  )
336
  line_bot_api.reply_message_with_http_info(
 
32
  # 1) ๅ˜—่ฉฆ่ผ‰ๅ…ฅใ€Œๅœฐ้œ‡้ ่ญฆใ€ๅค–้ƒจๆจก็ต„๏ผ›่‹ฅไธๅญ˜ๅœจๅฐฑ็”จๅ…งๅปบๅ‚™ๆด็‰ˆๆœฌ
33
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
34
  try:
35
+ # ่‹ฅไฝ ๆœ‰ๅปบ็ซ‹ fetch_cwa_alarm_list.py๏ผŒๆœƒๅ„ชๅ…ˆไฝฟ็”จ
36
+ from fetch_cwa_alarm_list import fetch_cwa_alarm_list # noqa: F401
37
  except Exception:
 
38
  CWA_ALARM_API = "https://app-2.cwa.gov.tw/api/v1/earthquake/alarm/list"
39
 
40
  def _parse_cwa_time(s: str):
 
54
  return (s, "ๆœช็Ÿฅ")
55
 
56
  def fetch_cwa_alarm_list(limit: int = 5) -> str:
57
+ """CWA ๅœฐ้œ‡้ ่ญฆ๏ผˆๅ‚™ๆด็‰ˆ๏ผ‰ใ€‚"""
58
  try:
59
  r = requests.get(CWA_ALARM_API, timeout=10)
60
  r.raise_for_status()
 
70
  if not items:
71
  return "โœ… ็›ฎๅ‰ๆฒ’ๆœ‰ๅœฐ้œ‡้ ่ญฆใ€‚"
72
 
 
73
  def _key(it):
74
  s = it.get("originTime") or ""
75
  try:
 
136
  return "\n".join(lines).strip()
137
 
138
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
139
+ # 2) ไธญๆ–‡ๅญ—ๅž‹๏ผˆ่‹ฅๅฎนๅ™จๆœ‰ Noto/WenQuanYi ็ญ‰ๆœƒ่‡ชๅ‹•ๅฅ—็”จ๏ผ‰
140
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
141
  for fp in [
142
  "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
 
154
  CHANNEL_ACCESS_TOKEN = os.getenv("CHANNEL_ACCESS_TOKEN")
155
  CHANNEL_SECRET = os.getenv("CHANNEL_SECRET")
156
 
157
+ # HF Space ๅ…ฌ็ถฒไฝๅ€๏ผˆSPACEURL > SPACE_ID ๆŽจ่ซ–๏ผ‰
158
  HF_SPACE_URL = os.getenv("SPACEURL")
159
  if not HF_SPACE_URL:
160
  sid = os.getenv("SPACE_ID") # ๅฝขๅฆ‚ user/space
 
172
  handler = WebhookHandler(CHANNEL_SECRET)
173
 
174
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
175
+ # 4) ้ฆ–้ ๏ผˆ็พŽ่ง€่ชชๆ˜Ž้ ๏ผ‰
176
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
177
  @app.route("/", methods=["GET"])
178
  def home():
179
+ base = (os.getenv("SPACEURL") or request.url_root).rstrip("/")
180
+ webhook_url = f"{base}/callback"
181
+ static_hint = f"{base}/static/<ๆช”ๅ>"
182
+ channel_ok = "โœ…" if os.getenv("CHANNEL_ACCESS_TOKEN") and os.getenv("CHANNEL_SECRET") else "โš ๏ธ"
183
+ space_ok = "โœ…" if os.getenv("SPACEURL") or os.getenv("SPACE_ID") else "โ„น๏ธ"
184
+
185
+ return f"""
186
+ <!doctype html>
187
+ <html lang="zh-Hant">
188
+ <head>
189
+ <meta charset="utf-8" />
190
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
191
+ <title>ๅœฐ้œ‡้ ่ญฆ dayichen โ€“ LINE Bot Server</title>
192
+ <style>
193
+ :root {{
194
+ --bg:#0f1115; --card:#151821; --text:#e6e8ef; --muted:#9aa4b2; --accent:#23c55e; --border:rgba(255,255,255,.08);
195
+ }}
196
+ * {{ box-sizing:border-box; }}
197
+ body {{
198
+ margin:0; background:var(--bg); color:var(--text);
199
+ font:16px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Noto Sans TC","PingFang TC",sans-serif;
200
+ padding:32px 16px; display:flex; justify-content:center;
201
+ }}
202
+ .wrap {{ width:100%; max-width:980px; }}
203
+ .hero {{
204
+ background:linear-gradient(135deg,#1f2937,#0f172a);
205
+ border:1px solid var(--border); border-radius:16px; padding:28px; margin-bottom:20px;
206
+ box-shadow:0 8px 30px rgba(0,0,0,.25);
207
+ }}
208
+ .title {{ margin:0 0 6px; font-size:28px; font-weight:800; letter-spacing:.3px; }}
209
+ .subtitle {{ margin:0; color:var(--muted); }}
210
+ .grid {{ display:grid; gap:16px; grid-template-columns:repeat(auto-fit,minmax(260px,1fr)); margin-top:18px; }}
211
+ .card {{ background:#151821; border:1px solid var(--border); border-radius:14px; padding:16px 18px; }}
212
+ h3 {{ margin:0 0 8px; font-size:18px; }}
213
+ code, .mono {{ font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }}
214
+ .badge {{ display:inline-block; padding:2px 8px; border-radius:999px; background:#1f2937; border:1px solid var(--border); font-size:12px; color:#9aa4b2; }}
215
+ ul {{ padding-left:18px; margin:10px 0 0; }}
216
+ li + li {{ margin-top:6px; }}
217
+ a {{ color:#60a5fa; text-decoration:none; }}
218
+ a:hover {{ text-decoration:underline; }}
219
+ .kbd {{ padding:2px 6px; border:1px solid var(--border); border-radius:6px; background:#0b0e14; }}
220
+ .foot {{ color:#9aa4b2; font-size:13px; margin-top:18px; text-align:center; }}
221
+ </style>
222
+ </head>
223
+ <body>
224
+ <div class="wrap">
225
+ <section class="hero">
226
+ <div class="badge">็‹€ๆ…‹๏ผš<span style="color:{'#86efac' if channel_ok=='โœ…' else '#fbbf24'}">{channel_ok}</span> LINE ้‡‘้‘ฐใ€€ยทใ€€HF Space๏ผš{space_ok}</div>
227
+ <h1 class="title">ๅœฐ้œ‡้ ่ญฆ dayichen โ€“ LINE Bot</h1>
228
+ <p class="subtitle">ๆŸฅ่ฉขๅ…จ็ƒ่ฟ‘ 24 ๅฐๆ™‚ๅœฐ้œ‡ใ€ไปŠๅนดๅฐ็ฃๅœฐ้œ‡ๆธ…ๅ–ฎ่ˆ‡ๅœฐๅœ–ใ€ไธญๅคฎๆฐฃ่ฑก็ฝฒๅœฐ้œ‡้ ่ญฆใ€‚</p>
229
+ <div class="grid">
230
+ <div class="card">
231
+ <h3>๐Ÿš€ ๅฟซ้€Ÿ้–‹ๅง‹</h3>
232
+ <ul>
233
+ <li>ๅœจ LINE ๅฐ่ฉฑ่ผธๅ…ฅ <span class="kbd">/help</span> ๅ–ๅพ—ๆŒ‡ไปคๅˆ—่กจ</li>
234
+ <li>่ฉฆ่ฉฆ <span class="kbd">ๅœฐ้œ‡</span> ๆˆ– <span class="kbd">quake</span>๏ผšๅ…จ็ƒ่ฟ‘ 24 ๅฐๆ™‚ Mโ‰ฅ5.0</li>
235
+ <li><span class="kbd">่‡บ็ฃๅœฐ้œ‡</span>/<span class="kbd">ๅฐ็ฃๅœฐ้œ‡</span>๏ผšไปŠๅนดๅฐ็ฃๅ€ๅŸŸๆธ…ๅ–ฎ๏ผˆๅซๆ—ฅๆœŸๆ™‚้–“๏ผ‰</li>
236
+ <li><span class="kbd">่‡บ็ฃๅœฐ้œ‡็•ซๅœ–</span>/<span class="kbd">ๅฐ็ฃๅœฐ้œ‡็•ซๅœ–</span>๏ผšๅ›žๅ‚ณๅœฐๅœ–ๅœ–็‰‡</li>
237
+ <li><span class="kbd">ๅœฐ้œ‡้ ่ญฆ</span>๏ผšCWA ๅœฐ้œ‡้ ่ญฆ๏ผˆๆœ€ๆ–ฐ 5 ็ญ†๏ผ‰</li>
238
+ </ul>
239
+ </div>
240
+ <div class="card">
241
+ <h3>๐Ÿ› ๏ธ Webhook ่ˆ‡้œๆ…‹ๆช”</h3>
242
+ <ul>
243
+ <li>Webhook๏ผš<span class="mono"><a href="{webhook_url}">{webhook_url}</a></span></li>
244
+ <li>้œๆ…‹ๅœ–็‰‡๏ผš<span class="mono">{static_hint}</span></li>
245
+ <li>ๅฅๅบทๆชขๆŸฅ๏ผš<span class="mono"><a href="{base}/healthz">{base}/healthz</a></span></li>
246
+ </ul>
247
+ </div>
248
+ <div class="card">
249
+ <h3>โ„น๏ธ ๅ‚™่จป</h3>
250
+ <ul>
251
+ <li>ๆธ…ๅ–ฎๆ™‚้–“้กฏ็คบ๏ผšๅฐ็ฃๆ™‚้–“่ˆ‡ UTCใ€‚</li>
252
+ <li>ๅœฐๅœ–ไปฅ Matplotlib ็”ข็”Ÿๆ–ผ <span class="mono">/tmp/static</span>๏ผŒ็”ฑ <span class="mono">/static/</span> ๆไพ›ใ€‚</li>
253
+ <li>่‹ฅๅœ–็‰‡ๅœจ LINE ้กฏ็คบ็ฉบ็™ฝ๏ผŒ่ซ‹็ขบ่ชๆœฌ้ ใ€ŒWebhook ่ˆ‡้œๆ…‹ๆช”ใ€็ถฒๅ€่ƒฝๅพžๅค–็ถฒๅญ˜ๅ–ใ€‚</li>
254
+ </ul>
255
+ </div>
256
+ </div>
257
+ <p class="foot">ยฉ {datetime.now().year} dayichen ยท server: {base}</p>
258
+ </section>
259
+ </div>
260
+ </body>
261
+ </html>
262
+ """
263
 
264
  @app.route("/healthz")
265
  def healthz():
 
269
  def serve_static(filename):
270
  return send_from_directory(STATIC_DIR, filename)
271
 
272
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
273
+ # 5) ่ณ‡ๆ–™ๆŸฅ่ฉข๏ผˆUSGS๏ผ‰
274
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
275
  USGS_API_BASE_URL = "https://earthquake.usgs.gov/fdsnws/event/1/query"
276
 
277
  def _iso(dt: datetime) -> str:
 
333
  except Exception as e:
334
  return f"โŒ ๆŸฅ่ฉขๅคฑๆ•—: {e}"
335
 
336
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
337
+ # 6) ่ฃฝๅœ–
338
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
339
  def create_and_save_map(df: pd.DataFrame) -> str:
340
  fig, ax = plt.subplots(figsize=(9, 6), dpi=150)
341
  ax.set_xlim(118.5, 123.5)
 
367
  return HF_SPACE_URL.rstrip("/") if HF_SPACE_URL else request.url_root.rstrip("/")
368
 
369
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
370
+ # 7) LINE Webhook
371
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
372
  @app.route("/callback", methods=["POST"])
373
  def callback():
 
419
  "โžก๏ธ ๅœฐ้œ‡ / quake๏ผˆๅ…จ็ƒ่ฟ‘24ๅฐๆ™‚๏ผŒๅซๆ—ฅๆœŸๆ™‚้–“๏ผ‰\n"
420
  "โžก๏ธ ่‡บ็ฃๅœฐ้œ‡ / ๅฐ็ฃๅœฐ้œ‡๏ผˆไปŠๅนดๅฐ็ฃๅ€ๅŸŸๆธ…ๅ–ฎ๏ผ‰\n"
421
  "โžก๏ธ ่‡บ็ฃๅœฐ้œ‡็•ซๅœ– / ๅฐ็ฃๅœฐ้œ‡็•ซๅœ–๏ผˆไปŠๅนดๅฐ็ฃๅ€ๅŸŸๅˆ†ไฝˆๅœ–๏ผ‰\n"
422
+ "โžก๏ธ ๅœฐ้œ‡้ ่ญฆ๏ผˆCWA ๆœ€ๆ–ฐ 5 ็ญ†๏ผ‰\n"
423
  "โžก๏ธ ไฝ ๅฅฝ"
424
  )
425
  line_bot_api.reply_message_with_http_info(