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.
110 lines
2.9 KiB
Python
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."
|