Add support for timezone handling and improve terminal data fetching
- Added `pytz` dependency to `pyproject.toml` and `uv.lock`. - Introduced Taipei timezone handling in `tg_bot.py`. - Enhanced terminal data fetching to include fallback mechanisms and display for the next 12 hours.
This commit is contained in:
parent
7d90cf0564
commit
1686b40a42
135
bot/tg_bot.py
135
bot/tg_bot.py
@ -3,6 +3,7 @@ import sys
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import pytz
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from telegram import Update, BotCommand
|
||||
@ -19,6 +20,8 @@ logging.basicConfig(
|
||||
level=logging.INFO
|
||||
)
|
||||
|
||||
TAIPEI_TZ = pytz.timezone('Asia/Taipei')
|
||||
|
||||
class TelegramBot:
|
||||
def __init__(self, token: str):
|
||||
self.token = token
|
||||
@ -32,7 +35,7 @@ class TelegramBot:
|
||||
BotCommand("help", "顯示說明文字"),
|
||||
]
|
||||
await application.bot.set_my_commands(commands)
|
||||
print("Bot commands registered.")
|
||||
logging.info("Bot commands registered.")
|
||||
|
||||
async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Display help message."""
|
||||
@ -40,57 +43,115 @@ class TelegramBot:
|
||||
"<b>桃園機場航班人次預報 Bot</b>\n\n"
|
||||
"/t1 - 獲取第一航廈最新出境與過境預報\n"
|
||||
"/t2 - 獲取第二航廈最新出境與過境預報\n"
|
||||
"/help - 顯示此說明"
|
||||
"/help - 顯示此說明\n\n"
|
||||
"資料顯示為台北時間,從現在開始往後顯示最多 12 小時。"
|
||||
)
|
||||
await update.message.reply_text(help_text, parse_mode=ParseMode.HTML)
|
||||
|
||||
async def get_terminal_data(self, update: Update, terminal_key: str):
|
||||
"""Generic method to fetch and display terminal data."""
|
||||
date_str = datetime.datetime.now().strftime("%Y_%m_%d")
|
||||
def _get_file_for_date(self, target_date: datetime.date):
|
||||
"""Helper to try getting a file for a specific date (with fallback)."""
|
||||
date_str = target_date.strftime("%Y_%m_%d")
|
||||
|
||||
# Try _update first
|
||||
filename = f"{date_str}_update.json"
|
||||
url = f"https://www.taoyuan-airport.com/uploads/fos/{date_str}_update.xls"
|
||||
""
|
||||
|
||||
# Download and store
|
||||
file_path = self.downloader.download_and_store_as_json(url, filename, verify=False)
|
||||
|
||||
if not file_path or not os.path.exists(file_path):
|
||||
await update.message.reply_text("抱歉,無法獲取目前的資料。")
|
||||
return
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
# Fallback to base
|
||||
if not file_path:
|
||||
filename = f"{date_str}.json"
|
||||
url = f"https://www.taoyuan-airport.com/uploads/fos/{date_str}.xls"
|
||||
file_path = self.downloader.download_and_store_as_json(url, filename, verify=False)
|
||||
|
||||
table_data = data.get('data', {}).get(terminal_key, {})
|
||||
records = table_data.get('records', [])
|
||||
title = table_data.get('title', f"{terminal_key.upper()} 預報表")
|
||||
return file_path
|
||||
|
||||
if not records:
|
||||
await update.message.reply_text(f"找不到 {title} 的資料。")
|
||||
async def get_terminal_data(self, update: Update, terminal_key: str):
|
||||
"""Fetch and display terminal data for the next 12 hours (Taipei time)."""
|
||||
now_taipei = datetime.datetime.now(TAIPEI_TZ)
|
||||
|
||||
# Dates to check: today and tomorrow
|
||||
today = now_taipei.date()
|
||||
tomorrow = (now_taipei + datetime.timedelta(days=1)).date()
|
||||
|
||||
all_records = []
|
||||
titles = []
|
||||
|
||||
# Track which dates we successfully got data for
|
||||
available_dates = []
|
||||
|
||||
for d in [today, tomorrow]:
|
||||
file_path = self._get_file_for_date(d)
|
||||
if file_path and os.path.exists(file_path):
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
day_data = json.load(f)
|
||||
term_data = day_data.get('data', {}).get(terminal_key, {})
|
||||
day_records = term_data.get('records', [])
|
||||
if day_records:
|
||||
available_dates.append(d)
|
||||
for r in day_records:
|
||||
r['_date'] = d
|
||||
all_records.append(r)
|
||||
if term_data.get('title'):
|
||||
titles.append(term_data['title'])
|
||||
except Exception as e:
|
||||
logging.error(f"Error reading JSON for {d}: {e}")
|
||||
|
||||
if not all_records:
|
||||
await update.message.reply_text("抱歉,目前無法從桃園機場官網獲取資料。")
|
||||
return
|
||||
|
||||
# Format as a simple table
|
||||
message = f"<b>{title}</b>\n"
|
||||
# Filtering logic:
|
||||
final_list = []
|
||||
for i in range(12):
|
||||
target_dt = now_taipei + datetime.timedelta(hours=i)
|
||||
target_hour = target_dt.hour
|
||||
target_date = target_dt.date()
|
||||
|
||||
# Find the record for this target_date and hour
|
||||
for r in all_records:
|
||||
if r['_date'] == target_date:
|
||||
time_range = r.get('時間區間', '')
|
||||
# Format is "HH:00 ~ HH:59"
|
||||
try:
|
||||
record_hour = int(time_range.split(':')[0])
|
||||
if record_hour == target_hour:
|
||||
final_list.append(r)
|
||||
break
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
|
||||
if not final_list:
|
||||
await update.message.reply_text("找不到當前時段往後的預報資料。")
|
||||
return
|
||||
|
||||
# Format output
|
||||
display_title = titles[0] if titles else f"{terminal_key.upper()} 預報表"
|
||||
|
||||
message = f"<b>{display_title}</b>\n"
|
||||
message += "<pre>"
|
||||
message += f"{'時間':<15} {'出境':<6} {'過境':<6}\n"
|
||||
message += "-" * 30 + "\n"
|
||||
|
||||
now_hour = datetime.datetime.now().hour
|
||||
count = 0
|
||||
for r in records:
|
||||
time_str = r.get('時間區間', '')
|
||||
try:
|
||||
hour = int(time_str.split(':')[0])
|
||||
if hour >= now_hour:
|
||||
out_count = r.get('出境桃園', 0)
|
||||
transfer_count = r.get('到站轉機', 0)
|
||||
message += f"{time_str:<15} {out_count:<6} {transfer_count:<6}\n"
|
||||
count += 1
|
||||
if count >= 10: break
|
||||
except:
|
||||
continue
|
||||
for r in final_list:
|
||||
rec_date = r['_date']
|
||||
time_range = r.get('時間區間', '')
|
||||
# 取出起始時間 (例如 "22:00")
|
||||
start_time = time_range.split(' ~ ')[0] if ' ~ ' in time_range else time_range
|
||||
|
||||
display_time = f"{rec_date.strftime('%m/%d')} {start_time}"
|
||||
|
||||
out_count = r.get('出境桃園', 0)
|
||||
transfer_count = r.get('到站轉機', 0)
|
||||
message += f"{display_time:<15} {out_count:<6} {transfer_count:<6}\n"
|
||||
|
||||
message += "</pre>"
|
||||
|
||||
# Info about missing data if we couldn't get the full 12 hours
|
||||
if len(final_list) < 12:
|
||||
if tomorrow not in available_dates:
|
||||
message += f"\n<i>註:機場尚未發佈明日 ({tomorrow}) 的預報檔案。</i>"
|
||||
|
||||
await update.message.reply_text(message, parse_mode=ParseMode.HTML)
|
||||
|
||||
async def get_t1_data(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
@ -106,11 +167,11 @@ class TelegramBot:
|
||||
application.add_handler(CommandHandler('t2', self.get_t2_data))
|
||||
application.add_handler(CommandHandler('help', self.help_command))
|
||||
|
||||
print("Bot is running... Press Ctrl+C to stop.")
|
||||
print("Bot is running (Taipei Time Locked)... Press Ctrl+C to stop.")
|
||||
application.run_polling()
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Get token from environment variable for security
|
||||
# Get token from environment variable or .env
|
||||
load_dotenv()
|
||||
TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
|
||||
if not TOKEN:
|
||||
|
||||
@ -9,5 +9,6 @@ dependencies = [
|
||||
"pandas>=3.0.1",
|
||||
"python-dotenv>=1.2.1",
|
||||
"python-telegram-bot>=22.6",
|
||||
"pytz>=2025.2",
|
||||
"xlrd>=2.0.2",
|
||||
]
|
||||
|
||||
11
uv.lock
generated
11
uv.lock
generated
@ -222,6 +222,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/13/97/7298f0e1afe3a1ae52ff4c5af5087ed4de319ea73eb3b5c8c4dd4e76e708/python_telegram_bot-22.6-py3-none-any.whl", hash = "sha256:e598fe171c3dde2dfd0f001619ee9110eece66761a677b34719fb18934935ce0", size = 737267, upload-time = "2026-01-24T13:56:58.06Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2025.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rctp-pax-fcst"
|
||||
version = "0.1.0"
|
||||
@ -232,6 +241,7 @@ dependencies = [
|
||||
{ name = "pandas" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "python-telegram-bot" },
|
||||
{ name = "pytz" },
|
||||
{ name = "xlrd" },
|
||||
]
|
||||
|
||||
@ -242,6 +252,7 @@ requires-dist = [
|
||||
{ name = "pandas", specifier = ">=3.0.1" },
|
||||
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
||||
{ name = "python-telegram-bot", specifier = ">=22.6" },
|
||||
{ name = "pytz", specifier = ">=2025.2" },
|
||||
{ name = "xlrd", specifier = ">=2.0.2" },
|
||||
]
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user