import json import os import threading import urllib.request import urllib.parse import boto3 # Cache bot token (fetched once at Lambda init) _bot_token: str | None = None _token_lock = threading.Lock() def get_bot_token() -> str: global _bot_token if _bot_token is None: with _token_lock: if _bot_token is None: sm = boto3.client('secretsmanager') _bot_token = sm.get_secret_value( SecretId=os.environ['TELEGRAM_BOT_TOKEN_SECRET_ARN'] )['SecretString'] return _bot_token def send_typing(chat_id: str) -> None: """Fire-and-forget typing action (does not raise on failure).""" try: token = get_bot_token() data = json.dumps({'chat_id': chat_id, 'action': 'typing'}).encode() req = urllib.request.Request( f'https://api.telegram.org/bot{token}/sendChatAction', data=data, headers={'Content-Type': 'application/json'}, ) urllib.request.urlopen(req, timeout=3) except Exception: pass # typing is best-effort def handler(event, context): # ── Validate Telegram webhook secret ────────────────────────────────── expected_secret = os.environ.get('TELEGRAM_WEBHOOK_SECRET', '') if expected_secret: headers = event.get('headers') or {} received = headers.get('x-telegram-bot-api-secret-token', '') if received != expected_secret: return {'statusCode': 403, 'body': 'Forbidden'} # ── Parse Telegram Update ───────────────────────────────────────────── try: body = json.loads(event.get('body', '{}')) except json.JSONDecodeError: return {'statusCode': 400, 'body': 'Bad Request'} update_id = body.get('update_id') # Support regular messages and edited messages message = body.get('message') or body.get('edited_message') if not message: # Not a message update (could be channel_post, callback_query, etc.) return {'statusCode': 200, 'body': 'ok'} chat_id = str(message.get('chat', {}).get('id', '')) text = message.get('text', '') from_user = message.get('from', {}) timestamp = message.get('date', 0) if not chat_id or not text: return {'statusCode': 200, 'body': 'ok'} # ── Send typing action (non-blocking, background thread) ────────────── t = threading.Thread(target=send_typing, args=(chat_id,)) t.daemon = True t.start() # ── Enqueue to SQS FIFO ─────────────────────────────────────────────── sqs = boto3.client('sqs') sqs.send_message( QueueUrl=os.environ['MESSAGE_QUEUE_URL'], MessageGroupId=chat_id, MessageDeduplicationId=str(update_id), MessageBody=json.dumps({ 'channel': 'telegram', 'chat_id': chat_id, 'messages': [{ 'text': text, 'from_id': str(from_user.get('id', '')), 'from_username': from_user.get('username', ''), 'from_name': f"{from_user.get('first_name', '')} {from_user.get('last_name', '')}".strip(), }], 'update_id': update_id, 'timestamp': timestamp, }), ) return {'statusCode': 200, 'body': 'ok'}