initial commit
This commit is contained in:
@@ -0,0 +1,362 @@
|
||||
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"🏁 <b>{race['name']}</b>\n"
|
||||
f"📋 {race['art']}\n"
|
||||
f"🕒 {race['zeit']}\n\n"
|
||||
f"❶ <b>Bahn 1</b>\n{race['bahn1']}\n\n"
|
||||
f"❷ <b>Bahn 2</b>\n{race['bahn2']}\n\n"
|
||||
f"❸ <b>Bahn 3</b>\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())
|
||||
Reference in New Issue
Block a user