Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -32,9 +32,9 @@ from matplotlib import font_manager as fm
|
|
| 32 |
# 1) ๅ่ฉฆ่ผๅ
ฅใๅฐ้้ ่ญฆใๅค้จๆจก็ต๏ผ่ฅไธๅญๅจๅฐฑ็จๅ
งๅปบๅๆด็ๆฌ
|
| 33 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 34 |
try:
|
| 35 |
-
|
|
|
|
| 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 |
-
#
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
| 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(
|