Files
BSN-Chatsystem/telegram_handler.py
T

291 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""Telegram Bot webhook handler — integrated into app.py"""
def _telegram_download_file(file_id):
"""Download a file from Telegram servers. Returns local path or None."""
import requests as _r
if not TELEGRAM_TOKEN:
return None
try:
gr = _r.get("{}/getFile?file_id={}".format(TELEGRAM_API, file_id), timeout=10)
gr.raise_for_status()
gdata = gr.json()
if not gdata.get("ok"):
print("[telegram] getFile failed: {}".format(gdata), file=sys.stderr)
return None
file_path = gdata["result"]["file_path"]
ext = os.path.splitext(file_path)[1].lower() or ".bin"
safe_name = "tg_{}{}".format(file_id, ext)
dest = str(MEDIA_DIR / safe_name)
dl_url = "https://api.telegram.org/file/bot{}/{}".format(TELEGRAM_TOKEN, file_path)
fr = _r.get(dl_url, timeout=60)
fr.raise_for_status()
with open(dest, "wb") as f:
f.write(fr.content)
if ext == ".mov":
mp4_path = dest + ".mp4"
import subprocess
result = subprocess.run(
["/home/hermes/.local/bin/ffmpeg", "-y", "-i", dest,
"-c:v", "libx264", "-preset", "fast", "-crf", "23",
"-c:a", "aac", "-b:a", "128k", "-movflags", "+faststart", mp4_path],
capture_output=True, text=True, timeout=120
)
if result.returncode == 0 and os.path.exists(mp4_path):
os.remove(dest)
return mp4_path
return dest
except Exception as e:
print("[telegram download error] {}".format(e), file=sys.stderr)
return None
def _telegram_autoreply(chat_id, sender_name, is_new_user):
"""Send welcome or thank-you message via Telegram Send API."""
if not TELEGRAM_TOKEN:
return
import requests as _r
try:
if is_new_user:
text = (
"🎲 Hey {}! Cool, dass du da bist — willkommen in der ".format(sender_name) +
"brettspiel-news.de Community!\n\n"
"Schick uns einfach, was dich bewegt: 💬 Text, 🎤 Sprachnachrichten, "
"🖼️ Fotos oder 🎬 Videos — alles rund um Brettspiele. Wir schauen uns alles an!\n\n"
"✍️ Nenn uns deinen Vornamen, wenn du magst — das ist deine Einwilligung, "
"dass wir dich als Quelle nennen, falls wir deinen Beitrag verwenden. "
"Ohne Namen schreiben wir „anonyme Quelle\".\n\n"
"💡 Speicher unsere Nummer im Adressbuch, "
"damit unsere Antworten sicher ankommen.\n\n"
"🔒 „Meine Daten löschen\" genügt — und alles ist sofort und vollständig weg."
)
else:
text = "📨 Danke {}! Ist angekommen — wir kümmern uns drum.".format(sender_name)
_r.post(
"{}/sendMessage".format(TELEGRAM_API),
json={"chat_id": chat_id, "text": text},
timeout=10,
)
except Exception as e:
print("[telegram reply error] {}".format(e), file=sys.stderr)
@app.route("/telegram/webhook", methods=["GET", "POST"])
def telegram_webhook():
"""Telegram Bot Webhook. GET=health check, POST=incoming messages."""
if request.method == "GET":
if TELEGRAM_TOKEN:
import requests as _r
try:
r = _r.get("{}/getMe".format(TELEGRAM_API), timeout=5)
data = r.json()
if data.get("ok"):
return jsonify({"status": "ok", "bot": data["result"]["username"]})
except Exception:
pass
return jsonify({"status": "ok", "token_configured": True})
return jsonify({"status": "off", "reason": "TELEGRAM_TOKEN not set"}), 500
# POST: Incoming message
body = request.get_json(force=True, silent=True)
if not body or not TELEGRAM_TOKEN:
return "OK", 200
try:
msg = body.get("message", {})
if not msg:
return "OK", 200
chat = msg.get("chat", {})
sender_id = str(chat.get("id", "unknown"))
sender_name = msg.get("from", {}).get("first_name", "") or ""
last_name = msg.get("from", {}).get("last_name", "")
if last_name:
sender_name = "{} {}".format(sender_name, last_name).strip()
if not sender_name:
sender_name = sender_id
msg_type = "text"
content = ""
media_path = None
file_id = None
if "text" in msg:
msg_type = "text"
content = msg["text"]
elif "photo" in msg:
msg_type = "image"
content = msg.get("caption", "[Bild ohne Text]")
file_id = msg["photo"][-1]["file_id"]
elif "video" in msg:
msg_type = "video"
content = msg.get("caption", "[🎬 Video — Transkription läuft...]")
file_id = msg["video"]["file_id"]
elif "voice" in msg:
msg_type = "audio"
content = "[🎤 Sprachnachricht — Transkription läuft...]"
file_id = msg["voice"]["file_id"]
elif "audio" in msg:
msg_type = "audio"
content = msg.get("caption", "[🎤 Audio]")
file_id = msg["audio"]["file_id"]
elif "document" in msg:
msg_type = "document"
content = msg.get("caption", "[📎 Dokument]")
file_id = msg["document"]["file_id"]
elif "sticker" in msg:
content = "[Sticker: {}]".format(msg['sticker'].get('emoji', ''))
msg_type = "document"
elif "location" in msg:
lat = msg["location"]["latitude"]
lon = msg["location"]["longitude"]
content = "[📍 Standort: {}, {}]".format(lat, lon)
msg_type = "text"
else:
content = "[Unbekannter Nachrichtentyp]"
msg_type = "text"
if file_id:
media_path = _telegram_download_file(file_id)
# GDPR delete trigger
if msg_type == "text" and content.strip().lower() == "meine daten löschen":
_gdpr_delete_sender(get_db(), sender_id)
if TELEGRAM_TOKEN:
import requests as _r
try:
_r.post(
"{}/sendMessage".format(TELEGRAM_API),
json={"chat_id": int(sender_id),
"text": "✅ Alle deine Daten wurden vollständig und endgültig vom Server gelöscht. "
"Es befinden sich keinerlei personenbezogene Daten mehr von dir bei uns."},
timeout=10,
)
except Exception:
pass
return "OK", 200
# FAQ-Trigger: "Keine Antwort" / "Warum keine Nachricht"
faq_keywords = [
"keine antwort", "bekomme keine", "keine nachricht",
"antwort kommt nicht", "warum keine", "keine rückmeldung",
"why no reply", "not receiving", "save contact", "nummer speichern"
]
if msg_type == "text" and any(kw in content.lower() for kw in faq_keywords):
if TELEGRAM_TOKEN:
import requests as _r
try:
_r.post(
"{}/sendMessage".format(TELEGRAM_API),
json={"chat_id": int(sender_id),
"text": "💡 *Tipp:* Damit unsere Antworten bei dir ankommen, "
"speichere bitte unsere Nummer in deinem Adressbuch. "
"WhatsApp/Telegram filtert Nachrichten von unbekannten "
"Business-Accounts auf manchen Handys leider automatisch heraus.\n\n"
"Sobald du uns als Kontakt gespeichert hast, klappt's! ✅"},
timeout=10,
)
except Exception:
pass
return "OK", 200
db = get_db()
spiel_titel = None
category_override = None
if "#out" in content.lower():
category_override = "ausverkauft"
idx = content.lower().find("#out")
after = content[idx + 4:].strip()
if after:
spiel_titel = re.split(r'[.!?\n]| - | | — | ist | war | in ', after, maxsplit=1)[0].strip()[:200]
halle = extract_halle(content)
THREAD_WINDOW_MINUTES = 5
recent = db.execute(
"""SELECT id, content, media_path, halle FROM submissions
WHERE channel='telegram' AND sender_id=? AND status != 'gdpr_deleted'
ORDER BY created_at DESC LIMIT 1""",
(sender_id,),
).fetchone()
is_new_user = db.execute(
"SELECT COUNT(*) FROM submissions WHERE channel='telegram' AND sender_id=? AND status != 'gdpr_deleted'",
(sender_id,),
).fetchone()[0] == 0
thread_found = False
if recent:
age_check = db.execute(
"SELECT (strftime('%s','now') - strftime('%s',created_at)) < ? FROM submissions WHERE id=?",
(THREAD_WINDOW_MINUTES * 60, recent["id"]),
).fetchone()
if age_check and age_check[0]:
thread_found = True
if thread_found:
prefix = {
"text": "", "image": "\n[🖼️ Bild] ",
"audio": "\n[🎤 Sprachnachricht]", "video": "\n[🎬 Video] ",
"document": "\n[📎 Dokument] ",
}.get(msg_type, "\n[{}] ".format(msg_type))
new_content = (recent["content"] or "") + prefix + content
new_halle = extract_halle(new_content) or recent["halle"]
db.execute(
"UPDATE submissions SET content=?, halle=?, created_at=CURRENT_TIMESTAMP WHERE id=?",
(new_content, new_halle, recent["id"]),
)
if "#out" in content.lower():
out_title = None
idx2 = content.lower().find("#out")
after2 = content[idx2 + 4:].strip()
if after2:
out_title = re.split(r'[.!?\n]| - | | — | ist | war | in ', after2, maxsplit=1)[0].strip()[:200]
db.execute("UPDATE submissions SET category=?, spiel_titel=? WHERE id=?",
("ausverkauft", out_title, recent["id"]))
if out_title:
mark_ausverkauft_in_neuheiten(db, out_title)
if media_path:
merged = media_path
if recent["media_path"]:
merged = recent["media_path"] + "||" + media_path
db.execute("UPDATE submissions SET media_path=? WHERE id=?", (merged, recent["id"]))
else:
cat = category_override or None
db.execute(
"""INSERT INTO submissions
(channel, sender_id, sender_name, type, content, media_path, status, halle, category, spiel_titel)
VALUES (?, ?, ?, ?, ?, ?, 'new', ?, ?, ?)""",
("telegram", sender_id, sender_name, msg_type, content, media_path, halle, cat, spiel_titel),
)
if cat == "ausverkauft" and spiel_titel:
mark_ausverkauft_in_neuheiten(db, spiel_titel)
if not thread_found:
_telegram_autoreply(int(sender_id), sender_name, is_new_user)
if msg_type == "audio" and media_path:
import threading
sub_id = recent["id"] if thread_found else db.execute("SELECT last_insert_rowid()").fetchone()[0]
threading.Thread(target=_transcribe_bg, args=(DB_PATH, sub_id, media_path), daemon=True).start()
if msg_type == "video" and media_path:
import threading
sub_id = recent["id"] if thread_found else db.execute("SELECT last_insert_rowid()").fetchone()[0]
threading.Thread(target=_transcribe_video_bg, args=(DB_PATH, sub_id, media_path), daemon=True).start()
if msg_type in ("text", "image") and not thread_found and content and len(content.strip()) > 20:
import threading
sub_id = db.execute("SELECT last_insert_rowid()").fetchone()[0]
if not halle:
threading.Thread(target=auto_process_bg, args=(DB_PATH, sub_id), daemon=True).start()
db.commit()
except Exception as e:
print("[telegram webhook error] {}".format(e), file=sys.stderr)
return "OK", 200
return "OK", 200