agnes-the-ai-analyst/webapp/telegram_service.py
Petr c56905d34f Initial commit: OSS data distribution platform
Open-source AI data analyst platform extracted from internal repo.
Includes data sync engine, Keboola adapter, Flask web portal,
server deployment scripts, and configuration templates.
2026-03-08 23:31:28 +01:00

110 lines
2.9 KiB
Python

"""
Telegram service for the webapp.
Reads/writes shared JSON files in /data/notifications/ to manage
user-to-Telegram mappings and verify codes.
"""
import json
import logging
import os
import time
logger = logging.getLogger(__name__)
NOTIFICATIONS_DIR = "/data/notifications"
TELEGRAM_USERS_FILE = os.path.join(NOTIFICATIONS_DIR, "telegram_users.json")
PENDING_CODES_FILE = os.path.join(NOTIFICATIONS_DIR, "pending_codes.json")
CODE_TTL_SECONDS = 600 # 10 minutes
def _read_json(path: str) -> dict:
"""Read a JSON file, return empty dict if not found or invalid."""
try:
with open(path, "r") as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return {}
def _write_json(path: str, data: dict) -> None:
"""Write JSON data to file."""
import tempfile
dir_path = os.path.dirname(path)
os.makedirs(dir_path, exist_ok=True)
fd, tmp_path = tempfile.mkstemp(dir=dir_path, suffix=".tmp")
try:
with os.fdopen(fd, "w") as f:
json.dump(data, f, indent=2)
os.chmod(tmp_path, 0o660) # group-readable for data-ops
os.replace(tmp_path, path)
except Exception:
os.unlink(tmp_path)
raise
def get_telegram_status(username: str) -> dict:
"""Get Telegram link status for a user."""
users = _read_json(TELEGRAM_USERS_FILE)
entry = users.get(username)
if entry:
return {
"linked": True,
"linked_at": entry.get("linked_at", ""),
}
return {"linked": False}
def link_telegram(username: str, code: str) -> tuple[bool, str]:
"""Verify a code and link the user's Telegram account.
Returns (success, message).
"""
codes = _read_json(PENDING_CODES_FILE)
# Cleanup expired codes
now = time.time()
codes = {
c: data
for c, data in codes.items()
if now - data.get("created_at", 0) < CODE_TTL_SECONDS
}
data = codes.get(code)
if data is None:
return False, "Invalid or expired verification code."
chat_id = data["chat_id"]
# Consume the code
del codes[code]
_write_json(PENDING_CODES_FILE, codes)
# Link user
users = _read_json(TELEGRAM_USERS_FILE)
users[username] = {
"chat_id": chat_id,
"linked_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
}
_write_json(TELEGRAM_USERS_FILE, users)
logger.info(f"Linked user '{username}' to Telegram chat_id {chat_id}")
return True, "Telegram linked successfully."
def unlink_telegram(username: str) -> tuple[bool, str]:
"""Unlink Telegram from a user account.
Returns (success, message).
"""
users = _read_json(TELEGRAM_USERS_FILE)
if username not in users:
return False, "Telegram is not linked."
del users[username]
_write_json(TELEGRAM_USERS_FILE, users)
logger.info(f"Unlinked Telegram for user '{username}'")
return True, "Telegram unlinked."