feat: Telegram QR-Code Invite-Link + WhatsApp Empfang-Fix
Telegram:
- /start <contact_id> Deep-Link Handler: verknüpft Kontakt automatisch mit chat_id
- QR-Code Endpunkt GET /api/v1/contacts/{id}/telegram-qr (PNG)
- TUI: Taste T öffnet QR-Code im Browser (HTML mit eingebettetem PNG)
- config.py + .env.example: TELEGRAM_BOT_USERNAME=mcm_bot
- qrcode[pil] zu requirements.txt hinzugefügt
WhatsApp:
- receiveTimeout 5→3s, HTTP-Timeout 8s → verhindert Polling-Overlap
This commit is contained in:
@@ -101,6 +101,16 @@ class MCMApiClient:
|
||||
async def get_contacts(self) -> list[dict[str, Any]]:
|
||||
return await self._get("/contacts")
|
||||
|
||||
async def get_telegram_qr(self, contact_id: str) -> bytes:
|
||||
"""QR-Code PNG-Bytes für einen Kontakt abrufen."""
|
||||
async with httpx.AsyncClient(timeout=10.0, follow_redirects=True) as client:
|
||||
resp = await client.get(
|
||||
self._base + f"/contacts/{contact_id}/telegram-qr",
|
||||
headers=self._headers,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.content
|
||||
|
||||
# ── Status ─────────────────────────────────────────────────────────────────
|
||||
|
||||
async def get_channel_status(self) -> dict[str, Any]:
|
||||
|
||||
@@ -24,6 +24,7 @@ class MainScreen(Screen):
|
||||
BINDINGS = [
|
||||
Binding("n", "new_message", "Neu"),
|
||||
Binding("r", "refresh", "Aktualisieren"),
|
||||
Binding("t", "telegram_qr", "Telegram QR"),
|
||||
Binding("q", "quit_app", "Beenden"),
|
||||
]
|
||||
|
||||
@@ -240,5 +241,63 @@ class MainScreen(Screen):
|
||||
if self._current_conv_id:
|
||||
await self._load_messages(self._current_conv_id)
|
||||
|
||||
def action_telegram_qr(self) -> None:
|
||||
asyncio.create_task(self._generate_telegram_qr())
|
||||
|
||||
async def _generate_telegram_qr(self) -> None:
|
||||
if not self._current_conv:
|
||||
self.notify("Bitte erst eine Konversation auswählen.", severity="warning")
|
||||
return
|
||||
conv = self._current_conv
|
||||
if conv.get("channel") != "telegram":
|
||||
self.notify("QR-Code nur für Telegram-Konversationen.", severity="warning")
|
||||
return
|
||||
contact_id = conv.get("contact_id")
|
||||
if not contact_id:
|
||||
try:
|
||||
details = await self._api.get_conversation(conv["id"])
|
||||
contact_id = (details or {}).get("contact_id")
|
||||
except Exception:
|
||||
pass
|
||||
if not contact_id:
|
||||
self.notify("Kein Kontakt zur Konversation gefunden.", severity="error")
|
||||
return
|
||||
try:
|
||||
import base64
|
||||
import tempfile
|
||||
import webbrowser
|
||||
from config import settings
|
||||
png_bytes = await self._api.get_telegram_qr(contact_id)
|
||||
bot = settings.telegram_bot_username.lstrip("@")
|
||||
invite_url = f"https://t.me/{bot}?start={contact_id}"
|
||||
b64 = base64.b64encode(png_bytes).decode()
|
||||
contact_name = conv.get("title") or contact_id
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head><meta charset="utf-8"><title>Telegram QR – {contact_name}</title>
|
||||
<style>
|
||||
body {{font-family:sans-serif;text-align:center;padding:2rem;background:#1a1a2e;color:#eee}}
|
||||
img {{width:300px;height:300px;border:8px solid #fff;border-radius:12px;margin:1rem}}
|
||||
a {{color:#64b5f6;word-break:break-all}}
|
||||
h2 {{margin-bottom:0}}
|
||||
p {{margin:.5rem 0}}
|
||||
</style></head>
|
||||
<body>
|
||||
<h2>Telegram Invite</h2>
|
||||
<p>{contact_name}</p>
|
||||
<img src="data:image/png;base64,{b64}" alt="QR-Code">
|
||||
<p><a href="{invite_url}">{invite_url}</a></p>
|
||||
<p style="font-size:.85rem;color:#aaa">Kunde scannt QR-Code → Telegram öffnet Bot → Verbindung hergestellt</p>
|
||||
</body></html>"""
|
||||
with tempfile.NamedTemporaryFile(
|
||||
delete=False, suffix=".html", mode="w", encoding="utf-8"
|
||||
) as tmp:
|
||||
tmp.write(html)
|
||||
tmp_path = tmp.name
|
||||
webbrowser.open(f"file://{tmp_path}")
|
||||
self.notify(f"QR-Code im Browser geöffnet | {invite_url}", timeout=8)
|
||||
except Exception as exc:
|
||||
self.notify(f"QR-Code Fehler: {exc}", severity="error")
|
||||
|
||||
def action_quit_app(self) -> None:
|
||||
self.app.exit()
|
||||
|
||||
Reference in New Issue
Block a user