import asyncio import json import os from pathlib import Path import httpx import uvicorn from fastapi import FastAPI, HTTPException, Header from pydantic import BaseModel from telegram import ( InlineKeyboardButton, InlineKeyboardMarkup, Update ) from telegram.ext import ( Application, CommandHandler, CallbackQueryHandler, ContextTypes ) CONFIG_FILE = "config.json" SUBSCRIBERS_FILE = "subscribers.json" def load_config(): with open(CONFIG_FILE, "r", encoding="utf-8") as f: return json.load(f) config = load_config() TELEGRAM_TOKEN = config["telegram_token"] RENNPLAN_API = config["rennplan_api"] PUSH_API_KEY = config["push_api_key"] def load_subscribers(): if not Path(SUBSCRIBERS_FILE).exists(): return [] with open(SUBSCRIBERS_FILE, "r", encoding="utf-8") as f: return json.load(f) def save_subscribers(subscribers): with open(SUBSCRIBERS_FILE, "w", encoding="utf-8") as f: json.dump(subscribers, f, indent=2) def add_subscriber(chat_id): subscribers = load_subscribers() if chat_id not in subscribers: subscribers.append(chat_id) save_subscribers(subscribers) def remove_subscriber(chat_id): subscribers = load_subscribers() if chat_id in subscribers: subscribers.remove(chat_id) save_subscribers(subscribers) async def lauf_selected(update: Update, context: ContextTypes.DEFAULT_TYPE): query = update.callback_query await query.answer() lauf_name = query.data.split(":")[1] data = await fetch_rennplan() lauf = data["rennplan"][lauf_name] keyboard = [] for race_id in sorted( lauf.keys(), key=lambda x: int(x) ): race = lauf[race_id] keyboard.append([ InlineKeyboardButton( f"{race_id} - {race['name']} ({race['zeit']})", callback_data=f"race:{lauf_name}:{race_id}" ) ]) keyboard.append([ InlineKeyboardButton( "⬅ Zurück", callback_data="back_laeufe" ) ]) await query.edit_message_text( text=f"🏁 Rennen in {lauf_name.capitalize()}:", reply_markup=InlineKeyboardMarkup(keyboard) ) async def race_selected(update: Update, context: ContextTypes.DEFAULT_TYPE): query = update.callback_query await query.answer() _, lauf_name, race_id = query.data.split(":") data = await fetch_rennplan() race = data["rennplan"][lauf_name][race_id] keyboard = [[ InlineKeyboardButton( "⬅ Zurück", callback_data=f"lauf:{lauf_name}" ) ]] await query.edit_message_text( text=format_race(race), parse_mode="HTML", reply_markup=InlineKeyboardMarkup(keyboard) ) async def back_to_laeufe(update: Update, context: ContextTypes.DEFAULT_TYPE): query = update.callback_query await query.answer() data = await fetch_rennplan() keyboard = [] for lauf_name in sorted( data["rennplan"].keys(), key=lambda x: int(x.replace("lauf", "")) ): keyboard.append([ InlineKeyboardButton( lauf_name.capitalize(), callback_data=f"lauf:{lauf_name}" ) ]) await query.edit_message_text( text="🏁 Bitte einen Lauf auswählen:", reply_markup=InlineKeyboardMarkup(keyboard) ) def get_latest_race(data): rennplan = data["rennplan"] latest_lauf_key = sorted( rennplan.keys(), key=lambda x: int(x.replace("lauf", "")) )[-1] lauf = rennplan[latest_lauf_key] latest_race_key = sorted( lauf.keys(), key=lambda x: int(x) )[-1] return lauf[latest_race_key] def format_race(race): return ( f"🏁 {race['name']}\n" f"📋 {race['art']}\n" f"🕒 {race['zeit']}\n\n" f"❶ Bahn 1\n{race['bahn1']}\n\n" f"❷ Bahn 2\n{race['bahn2']}\n\n" f"❸ Bahn 3\n{race['bahn3']}" ) async def fetch_rennplan(): async with httpx.AsyncClient(timeout=30) as client: response = await client.get(RENNPLAN_API) response.raise_for_status() return response.json() async def fetch_latest_race(): async with httpx.AsyncClient(timeout=30) as client: response = await client.get(RENNPLAN_API) response.raise_for_status() data = response.json() return get_latest_race(data) async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE): add_subscriber(update.effective_chat.id) await update.message.reply_text( "Moin! Du bekommst ab sofort Push Benachrichtigungen über Telegram. Vielen Dank!" ) async def stop_command(update: Update, context: ContextTypes.DEFAULT_TYPE): remove_subscriber(update.effective_chat.id) await update.message.reply_text( "Auf wiedersehen!" ) async def rennplan_command(update: Update, context: ContextTypes.DEFAULT_TYPE): try: data = await fetch_rennplan() rennplan = data["rennplan"] keyboard = [] for lauf_name in sorted( rennplan.keys(), key=lambda x: int(x.replace("lauf", "")) ): keyboard.append([ InlineKeyboardButton( lauf_name.capitalize(), callback_data=f"lauf:{lauf_name}" ) ]) await update.message.reply_text( "🏁 Bitte wähle einen Lauf aus:", reply_markup=InlineKeyboardMarkup(keyboard) ) except Exception as e: await update.message.reply_text( f"Fehler beim Laden des Rennplans:\n{e}" ) async def app_command(update: Update, context: ContextTypes.DEFAULT_TYPE): keyboard = [ [ InlineKeyboardButton( text="Apps herunterladen", url="https://apps.sport-am-tankumsee.de?source=telegram" ) ] ] await update.message.reply_text( "Lade dir hier unsere nativen Apps für iOS & Android herunter:", reply_markup=InlineKeyboardMarkup(keyboard) ) telegram_app = Application.builder().token( TELEGRAM_TOKEN ).build() telegram_app.add_handler(CommandHandler("start", start_command)) telegram_app.add_handler(CommandHandler("stop", stop_command)) telegram_app.add_handler(CommandHandler("rennplan", rennplan_command)) telegram_app.add_handler(CommandHandler("app", app_command)) telegram_app.add_handler( CallbackQueryHandler( lauf_selected, pattern=r"^lauf:" ) ) telegram_app.add_handler( CallbackQueryHandler( race_selected, pattern=r"^race:" ) ) telegram_app.add_handler( CallbackQueryHandler( back_to_laeufe, pattern=r"^back_laeufe$" ) ) api = FastAPI() class PushMessage(BaseModel): message: str @api.post("/push") async def push_message( payload: PushMessage, x_api_key: str = Header(None) ): if x_api_key != PUSH_API_KEY: raise HTTPException( status_code=401, detail="Invalid API Key" ) subscribers = load_subscribers() sent = 0 for chat_id in subscribers: try: await telegram_app.bot.send_message( chat_id=chat_id, text=payload.message, parse_mode="HTML" ) sent += 1 except Exception: pass return { "success": True, "sent": sent } async def start_api(): server = uvicorn.Server( uvicorn.Config( app=api, host=config["listen_host"], port=config["listen_port"], log_level="info" ) ) await server.serve() async def main(): await telegram_app.initialize() await telegram_app.start() asyncio.create_task(start_api()) await telegram_app.updater.start_polling() while True: await asyncio.sleep(3600) if __name__ == "__main__": asyncio.run(main())