From eb99cb53e832c363ddc2b05149a3d406829f20a4 Mon Sep 17 00:00:00 2001
From: "2569718930@qq.com" <2569718930@qq.com>
Date: Sun, 8 Feb 2026 18:22:47 +0800
Subject: [PATCH] feat: Implement new bot listening functionality or fix an
issue in event handling.
---
bot_listener.py | 246 ++++++++++++++++++------------------------------
1 file changed, 89 insertions(+), 157 deletions(-)
diff --git a/bot_listener.py b/bot_listener.py
index 906d0fd3..8ae2ee3a 100644
--- a/bot_listener.py
+++ b/bot_listener.py
@@ -240,181 +240,113 @@ def start_bot():
return
weather_data = weather.fetch_all_sources(city_name, lat=coords["lat"], lon=coords["lon"])
-
- msg_lines = [f"📍 {city_name.title()} 天气详情"]
-
- # 立即显示城市风险档案,防止被淹没
- risk_profile = get_city_risk_profile(city_name)
- if risk_profile:
- risk_warning = format_risk_warning(risk_profile, "°F") # 默认尝试用F显示偏差
- if risk_warning:
- msg_lines.append(risk_warning)
-
- msg_lines.append(f"\n⏱️ 生成时间: {datetime.now().strftime('%H:%M:%S')}")
- msg_lines.append("═" * 20)
-
open_meteo = weather_data.get("open-meteo", {})
metar = weather_data.get("metar", {})
+ mgm = weather_data.get("mgm", {})
+
temp_unit = open_meteo.get("unit", "celsius")
temp_symbol = "°F" if temp_unit == "fahrenheit" else "°C"
-
+
+ # --- 1. 紧凑 Header (城市 + 时间 + 风险状态) ---
local_time = open_meteo.get("current", {}).get("local_time", "")
- if local_time:
- time_only = local_time.split(" ")[1] if " " in local_time else local_time
- msg_lines.append(f"🕐 当地时间: {time_only}")
-
+ time_str = local_time.split(" ")[1][:5] if " " in local_time else "N/A"
+
+ risk_profile = get_city_risk_profile(city_name)
+ risk_emoji = risk_profile.get("risk_level", "⚪") if risk_profile else "⚪"
+
+ msg_header = f"📍 {city_name.title()} ({time_str}) {risk_emoji}"
+ msg_lines = [msg_header]
+
+ # --- 2. 紧凑 风险提示 ---
+ if risk_profile:
+ bias = risk_profile.get("bias", "±0.0")
+ msg_lines.append(f"⚠️ {risk_profile.get('airport_name', '')}: {bias}{temp_symbol} | {risk_profile.get('warning', '')}")
+ # --- 3. 紧凑 预测区 ---
daily = open_meteo.get("daily", {})
- dates = daily.get("time", [])
- max_temps = daily.get("temperature_2m_max", [])
- # 获取当地“今天”的日期
- utc_offset = open_meteo.get("utc_offset", 0)
- from datetime import timedelta, timezone
- city_now = datetime.now(timezone.utc) + timedelta(seconds=utc_offset)
- city_today_str = city_now.strftime("%Y-%m-%d")
-
- msg_lines.append(f"\n📊 Open-Meteo 3天预测")
- nws = weather_data.get("nws", {})
- nws_high = nws.get("today_high")
- mgm = weather_data.get("mgm", {})
+ dates = daily.get("time", [])[:3]
+ max_temps = daily.get("temperature_2m_max", [])[:3]
+
+ nws_high = weather_data.get("nws", {}).get("today_high")
mgm_high = mgm.get("today_high")
- for i, (d, t) in enumerate(zip(dates[:3], max_temps[:3])):
- # 跳过无效数据
- if t is None:
- continue
-
- day_label = "今天" if d == city_today_str else d[5:]
- indicator = "👉 " if d == city_today_str else " "
-
- # 如果是今天且有 NWS 或 MGM 数据,显示模型对比
- comp_lines = []
- if d == city_today_str:
- if nws_high is not None:
- diff_nws = abs(t - nws_high)
- warning = " ⚠️" if diff_nws > 1 else ""
- comp_lines.append(f"{indicator}{day_label}: 最高 {t}{temp_symbol}{warning}")
- comp_lines.append(f" (NWS官方预报: {nws_high}{temp_symbol},差异 {diff_nws:.1f}°)")
- elif mgm_high is not None:
- # 安卡拉 MGM 对比
- diff_mgm = abs(t - mgm_high)
- warning = " ⚠️" if diff_mgm > 1 else ""
- comp_lines.append(f"{indicator}{day_label}: 最高 {t}{temp_symbol}{warning}")
- comp_lines.append(f" (MGM官方预报: {mgm_high}{temp_symbol},差异 {diff_mgm:.1f}°)")
-
- if comp_lines:
- msg_lines.extend(comp_lines)
- else:
- msg_lines.append(f"{indicator}{day_label}: 最高 {t}{temp_symbol}")
-
- # MGM 官方实测显示
- if mgm:
- mgm_curr = mgm.get("current", {})
- mgm_temp = mgm_curr.get("temp")
- if mgm_temp is not None:
- # 翻译风向
- wind_dir = mgm_curr.get("wind_dir")
- dir_str = ""
- if wind_dir is not None:
- dirs = ["北", "东北", "东", "东南", "南", "西南", "西", "西北"]
- dir_str = dirs[int((float(wind_dir) + 22.5) % 360 / 45)] + "风 "
-
- msg_lines.append(f"\n🏛️ MGM 官方实测 ({mgm_curr.get('station_name', 'Ankara Esenboğa')})")
- msg_lines.append(f" 🌡️ {mgm_temp}°C (体感 {mgm_curr.get('feels_like', mgm_temp)}°C)")
- msg_lines.append(f" 💧 湿度: {mgm_curr.get('humidity')}%")
- msg_lines.append(f" 🌬️ {dir_str}{wind_dir}° / {mgm_curr.get('wind_speed_ms')} m/s")
- if mgm_curr.get("rain_24h") is not None:
- msg_lines.append(f" 🌧️ 24h 降水: {mgm_curr.get('rain_24h')}mm")
-
- if mgm_curr.get("time"):
- # 处理 MGM 原始时间格式 (例如 2026-02-08 13:00)
- obs_time = mgm_curr.get("time")
- if " " in obs_time:
- obs_time = obs_time.split(" ")[1]
- msg_lines.append(f" 🕐 观测: {obs_time} (官方)")
+ # 今天对比
+ today_t = max_temps[0] if max_temps else "N/A"
+ comp_str = ""
+ if nws_high is not None:
+ comp_str = f" (NWS: {nws_high})"
+ elif mgm_high is not None:
+ comp_str = f" (MGM: {mgm_high})"
+
+ msg_lines.append(f"\n👉 今天: {today_t}{temp_symbol}{comp_str}")
+
+ # 明后天 (水平排列或极简列表)
+ if len(dates) > 1:
+ future_forecasts = []
+ for d, t in zip(dates[1:], max_temps[1:]):
+ future_forecasts.append(f"{d[5:]}: {t}{temp_symbol}")
+ msg_lines.append("📅 " + " | ".join(future_forecasts))
+ # --- 4. 核心 实测区 (合并 METAR 和 MGM) ---
+ # 基础数据优先用 METAR
+ cur_temp = metar.get("current", {}).get("temp") if metar else mgm.get("current", {}).get("temp")
+ max_p = metar.get("current", {}).get("max_temp_so_far") if metar else None
+ obs_t_str = "N/A"
if metar:
- icao = metar.get("icao", "")
- metar_temp = metar.get("current", {}).get("temp")
- wind = metar.get("current", {}).get("wind_speed_kt")
- obs = metar.get("observation_time", "")
-
- if obs:
- try:
- obs_dt = datetime.fromisoformat(obs.replace("Z", "+00:00"))
- # 如果有 Open-Meteo 的时区偏移,则转换
- utc_offset = open_meteo.get("utc_offset", 0)
- from datetime import timezone, timedelta
- local_obs_dt = obs_dt.astimezone(timezone(timedelta(seconds=utc_offset)))
- obs_str = local_obs_dt.strftime("%H:%M") + " (当地)"
- except:
- obs_str = obs[:16]
- else:
- obs_str = "N/A"
+ obs_t = metar.get("observation_time", "")
+ obs_t_str = obs_t.split(" ")[1][:5] if " " in obs_t else obs_t[:5]
+ elif mgm:
+ m_time = mgm.get("current", {}).get("time", "")
+ if "T" in m_time:
+ from datetime import datetime, timezone, timedelta
+ dt = datetime.fromisoformat(m_time.replace("Z", "+00:00"))
+ m_time = dt.astimezone(timezone(timedelta(hours=3))).strftime("%H:%M")
+ elif " " in m_time:
+ m_time = m_time.split(" ")[1][:5]
+ obs_t_str = m_time
- msg_lines.append(f"\n✈️ 机场实测 ({icao})")
- if metar_temp is not None:
- max_sofar = metar.get("current", {}).get("max_temp_so_far")
- if max_sofar is not None:
- msg_lines.append(f" 🌡️ {metar_temp}{temp_symbol} (今日最高: {max_sofar}{temp_symbol})")
- else:
- msg_lines.append(f" 🌡️ {metar_temp}{temp_symbol}")
- if wind is not None:
- try:
- wind_dir_raw = metar.get("current", {}).get("wind_dir")
- if wind_dir_raw is not None:
- wind_dir = float(wind_dir_raw)
- # 翻译风向
- dirs = ["北", "东北", "东", "东南", "南", "西南", "西", "西北"]
- dir_str = dirs[int((wind_dir + 22.5) % 360 / 45)]
- msg_lines.append(f" 💨 风力: {wind}kt ({dir_str}风 {int(wind_dir)}°)")
- else:
- msg_lines.append(f" 💨 风速: {wind}kt")
- except (TypeError, ValueError):
- msg_lines.append(f" 💨 风速: {wind}kt")
+ msg_lines.append(f"\n✈️ 实测: {cur_temp}{temp_symbol}" + (f" (最高: {max_p}{temp_symbol})" if max_p else "") + f" | {obs_t_str}")
+
+ if mgm:
+ m_c = mgm.get("current", {})
+ # 翻译风向
+ wind_dir = m_c.get("wind_dir")
+ dir_str = ""
+ if wind_dir is not None:
+ dirs = ["北", "东北", "东", "东南", "南", "西南", "西", "西北"]
+ dir_str = dirs[int((float(wind_dir) + 22.5) % 360 / 45)] + "风 "
- vis = metar.get("current", {}).get("visibility_mi")
- if vis is not None:
- msg_lines.append(f" 👁️ 能见度: {vis}mi")
+ msg_lines.append(f" 🌡️ 体感: {m_c.get('feels_like')}°C | 💧 {m_c.get('humidity')}% | 🌧️ {m_c.get('rain_24h') or 0}mm")
+ msg_lines.append(f" 🌬️ {dir_str}{wind_dir}° ({m_c.get('wind_speed_ms')} m/s)")
+
+ if metar:
+ m_c = metar.get("current", {})
+ wind = m_c.get("wind_speed_kt")
+ wind_dir = m_c.get("wind_dir")
+ vis = m_c.get("visibility_mi")
+ clouds = m_c.get("clouds", [])
- wx = metar.get("current", {}).get("wx_desc")
- if wx:
- # 常见天象翻译
- wx_map = {
- "RA": "雨", "SN": "雪", "DZ": "毛毛雨", "FG": "雾",
- "BR": "薄雾", "HZ": "霾", "TS": "雷暴", "GR": "冰雹",
- "VC": "附近", "MI": "浅", "BC": "散", "PR": "部分",
- "BL": "吹", "SH": "阵", "FZ": "冻", "-": "轻微", "+": "强烈"
- }
- translated_wx = wx
- for code, cn in wx_map.items():
- translated_wx = translated_wx.replace(code, cn)
- msg_lines.append(f" 🌧️ 天象: {translated_wx}")
-
- # 云层显示
- clouds = metar.get("current", {}).get("clouds", [])
+ cloud_desc = ""
if clouds:
- cloud_map = {
- "SKC": "晴空 (无云)", "CLR": "晴空 (无云)",
- "FEW": "少云", "SCT": "散云",
- "BKN": "多云 (有遮挡)", "OVC": "阴天 (全覆盖)",
- "VV": "垂直能见度受限"
- }
- main_cloud = clouds[-1]
- cover_code = main_cloud.get("cover", "Unknown")
- base_height = main_cloud.get("base", "")
- cover_desc = cloud_map.get(cover_code, cover_code)
- if base_height:
- msg_lines.append(f" ☁️ 云层: {cover_desc} ({base_height}ft)")
- else:
- msg_lines.append(f" ☁️ 云层: {cover_desc}")
+ c_map = {"BKN": "多云", "OVC": "阴天", "FEW": "少云", "SCT": "散云", "SKC": "晴", "CLR": "晴"}
+ main = clouds[-1]
+ cloud_desc = f"☁️ {c_map.get(main.get('cover'), main.get('cover'))}"
+
+ if not mgm:
+ msg_lines.append(f" 💨 {wind or 0}kt ({wind_dir or 0}°) | 👁️ {vis or 10}mi")
- msg_lines.append(f" 🕐 观测: {obs_str}")
-
- # 3. 添加态势分析
+ if cloud_desc:
+ msg_lines.append(f" {cloud_desc} | 👁️ {vis or 10}mi")
+
+ # --- 5. 态势分析 ---
trend_insights = analyze_weather_trend(weather_data, temp_symbol)
if trend_insights:
- msg_lines.append(trend_insights)
+ clean_insights = trend_insights.replace("💡 态势分析", "").strip()
+ if clean_insights:
+ msg_lines.append(f"\n💡 分析:")
+ for line in clean_insights.split("\n"):
+ if line.strip():
+ msg_lines.append(f"- {line.strip()}")
bot.send_message(message.chat.id, "\n".join(msg_lines), parse_mode="HTML")