377 lines
17 KiB
Python
377 lines
17 KiB
Python
# -*- 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
|
||
|
||
# ── Nutzer-Registrierung per Telegram ──────────────────────────
|
||
pending_codes = getattr(app, '_tg_pending_codes', {})
|
||
if msg_type == "text":
|
||
clean = content.strip().lower()
|
||
if clean == "registrieren":
|
||
import random
|
||
code = str(random.randint(100000, 999999))
|
||
pending_codes[sender_id] = code
|
||
app._tg_pending_codes = pending_codes
|
||
if TELEGRAM_TOKEN:
|
||
import requests as _r
|
||
try:
|
||
_r.post(
|
||
"{}/sendMessage".format(TELEGRAM_API),
|
||
json={"chat_id": int(sender_id),
|
||
"text": "🔐 Dein Verifikationscode: *{}*\n\n"
|
||
"Antworte einfach mit: code {}".format(code, code)},
|
||
timeout=10,
|
||
)
|
||
except Exception:
|
||
pass
|
||
return "OK", 200
|
||
if clean.startswith("code ") and len(clean) >= 10:
|
||
entered = clean[5:].strip()
|
||
if sender_id in pending_codes and pending_codes[sender_id] == entered:
|
||
del pending_codes[sender_id]
|
||
app._tg_pending_codes = pending_codes
|
||
if TELEGRAM_TOKEN:
|
||
import requests as _r
|
||
try:
|
||
_r.post(
|
||
"{}/sendMessage".format(TELEGRAM_API),
|
||
json={"chat_id": int(sender_id),
|
||
"text": "✅ Du bist verifiziert! Welche Telefonnummer hast du? "
|
||
"Antworte einfach mit: phone +49152..."},
|
||
timeout=10,
|
||
)
|
||
except Exception:
|
||
pass
|
||
else:
|
||
if TELEGRAM_TOKEN:
|
||
import requests as _r
|
||
try:
|
||
_r.post(
|
||
"{}/sendMessage".format(TELEGRAM_API),
|
||
json={"chat_id": int(sender_id),
|
||
"text": "❌ Falscher Code. Schick \"registrieren\" für einen neuen."},
|
||
timeout=10,
|
||
)
|
||
except Exception:
|
||
pass
|
||
return "OK", 200
|
||
# "phone +49..." → Telefonnummer verknüpfen
|
||
if clean.startswith("phone ") or clean.startswith("+49"):
|
||
phone = clean[6:].strip() if clean.startswith("phone ") else clean.strip()
|
||
# Normalize: remove spaces
|
||
phone = phone.replace(" ", "").replace("-", "")
|
||
existing = db.execute("SELECT id FROM users WHERE phone=?", (phone,)).fetchone()
|
||
if existing:
|
||
db.execute("UPDATE users SET name=? WHERE phone=?",
|
||
(sender_name, phone))
|
||
else:
|
||
db.execute("INSERT INTO users (phone, name, verified) VALUES (?,?,1)",
|
||
(phone, sender_name))
|
||
db.commit()
|
||
if TELEGRAM_TOKEN:
|
||
import requests as _r
|
||
try:
|
||
_r.post(
|
||
"{}/sendMessage".format(TELEGRAM_API),
|
||
json={"chat_id": int(sender_id),
|
||
"text": "✅ Deine Telefonnummer ist jetzt verknüpft! "
|
||
"Dein Profil: https://chat.datenhimmel.work/profile"},
|
||
timeout=10,
|
||
)
|
||
except Exception:
|
||
pass
|
||
return "OK", 200
|
||
|
||
# ── Cross-Channel: User ueber Telegram-ID + phone verknuepfung ─
|
||
user_row = db.execute(
|
||
"SELECT id, name FROM users WHERE phone=?",
|
||
(sender_id,), # Telegram-ID als Fallback
|
||
).fetchone()
|
||
|
||
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, user_id)
|
||
VALUES (?, ?, ?, ?, ?, ?, 'new', ?, ?, ?, ?)""",
|
||
("telegram", sender_id, sender_name, msg_type, content, media_path, halle, cat, spiel_titel,
|
||
user_row["id"] if user_row else None),
|
||
)
|
||
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
|