Add METAR/TAF support and /metar command to Telegram bot

- Introduced `MetarDownloader` class for fetching METAR and TAF data.
- Added `/metar` command to retrieve Taoyuan Airport (RCTP) weather information.
- Updated help messages and command descriptions.
- Enhanced `.gitignore` to exclude `Downloads` folder.
This commit is contained in:
TzuWei 2026-02-28 21:46:39 +09:00
parent d4df11cad3
commit ae472b5b82
Signed by: tzuwei-huang
GPG Key ID: 88A0D3DA7558EE01
3 changed files with 66 additions and 2 deletions

2
.gitignore vendored
View File

@ -106,5 +106,5 @@ Network Trash Folder
Temporary Items
.apdisk
.env
*.json
Downloads

View File

@ -4,6 +4,7 @@ import datetime
import json
import logging
import pytz
import httpx
from dotenv import load_dotenv
from telegram import Update, BotCommand
@ -12,7 +13,7 @@ from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes
# Add parent directory to path to import downloader
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from downloader import FileDownloader
from downloader import FileDownloader, MetarDownloader
# Enable logging
logging.basicConfig(
@ -26,6 +27,7 @@ class TelegramBot:
def __init__(self, token: str):
self.token = token
self.downloader = FileDownloader(download_dir="Downloads")
self.metar_downloader = MetarDownloader()
async def post_init(self, application):
"""Register commands with Telegram so they show up in the menu."""
@ -34,6 +36,7 @@ class TelegramBot:
BotCommand("t1all", "獲取第一航廈至明日結束的所有預報"),
BotCommand("t2", "獲取第二航廈未來 12 小時預報"),
BotCommand("t2all", "獲取第二航廈至明日結束的所有預報"),
BotCommand("metar", "獲取桃園機場 (RCTP) 氣象資訊"),
BotCommand("help", "顯示說明文字"),
]
await application.bot.set_my_commands(commands)
@ -47,6 +50,7 @@ class TelegramBot:
"/t1all - 獲取第一航廈至明日結束的預報\n"
"/t2 - 獲取第二航廈未來 12 小時預報\n"
"/t2all - 獲取第二航廈至明日結束的預報\n"
"/metar - 獲取桃園機場氣象資訊 (METAR/TAF)\n"
"/help - 顯示此說明\n\n"
"資料顯示為台北時間。"
)
@ -182,6 +186,31 @@ class TelegramBot:
async def get_t2_all_data(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
await self.get_terminal_data(update, "terminal_2", show_all=True)
async def get_metar(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Fetch METAR for RCTP."""
try:
# Use run_in_executor to avoid blocking the event loop with synchronous requests
import asyncio
loop = asyncio.get_running_loop()
metar, taf = await loop.run_in_executor(
None, self.metar_downloader.fetch_metar_taf, "RCTP"
)
if not metar:
await update.message.reply_text("目前無法取得 METAR 資料。")
return
message = (
f"<b>桃園機場 (RCTP) 氣象資訊</b>\n\n"
f"<b>METAR:</b>\n<pre>{metar}</pre>\n\n"
f"<b>TAF:</b>\n<pre>{taf or '無 TAF 資料'}</pre>"
)
await update.message.reply_text(message, parse_mode=ParseMode.HTML)
except Exception as e:
logging.error(f"Error fetching METAR: {e}")
await update.message.reply_text(f"擷取氣象資訊時發生錯誤: {e}")
def run(self):
application = ApplicationBuilder().token(self.token).post_init(self.post_init).build()
@ -189,6 +218,7 @@ class TelegramBot:
application.add_handler(CommandHandler('t1all', self.get_t1_all_data))
application.add_handler(CommandHandler('t2', self.get_t2_data))
application.add_handler(CommandHandler('t2all', self.get_t2_all_data))
application.add_handler(CommandHandler('metar', self.get_metar))
application.add_handler(CommandHandler('help', self.help_command))
print("Bot is running (Taipei Time Locked)... Press Ctrl+C to stop.")

View File

@ -163,6 +163,40 @@ class FileDownloader:
print(f"Error downloading {url}: {e}")
return None
class MetarDownloader:
"""
A class to fetch METAR and TAF data for a specific station.
"""
def __init__(self):
self.base_url = "https://aviationweather.gov/api/data/metar"
def fetch_metar_taf(self, station_id: str = "RCTP"):
"""
Fetches METAR and TAF for the given station ID.
Returns a tuple of (metar, taf) or (None, None) on error.
"""
url = f"{self.base_url}?ids={station_id}&taf=1&format=json"
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
if not data or not isinstance(data, list):
return None, None
station_data = data[0]
metar = station_data.get('rawOb')
taf = station_data.get('rawTaf')
return metar, taf
except Exception as e:
print(f"Error fetching METAR for {station_id}: {e}")
return None, None
if __name__ == "__main__":
# Example usage
downloader = FileDownloader()
metar_downloader = MetarDownloader()
m, t = metar_downloader.fetch_metar_taf("RCTP")
if m:
print(f"METAR: {m}")
print(f"TAF: {t}")