| """ |
| recap_tg_bot.py — Recap Studio Telegram Bot |
| Uses requests library (urllib SSL fails on HuggingFace) |
| |
| Env vars: |
| TELEGRAM_BOT_TOKEN — @BotFather |
| RECAP_WEBAPP_URL — https://recap.psonline.shop |
| ADMIN_TELEGRAM_CHAT_ID — numeric Telegram ID |
| ADMIN_USERNAME — backend admin username |
| """ |
|
|
| import os, json, time, logging, requests |
|
|
| logging.basicConfig(level=logging.INFO, |
| format='%(asctime)s [%(levelname)s] %(message)s') |
| log = logging.getLogger(__name__) |
|
|
| BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN', '') |
| WEBAPP_URL = os.getenv('RECAP_WEBAPP_URL', 'https://recap.psonline.shop') |
| ADMIN_ID = os.getenv('ADMIN_TELEGRAM_CHAT_ID', '') |
| ADMIN_U = os.getenv('ADMIN_USERNAME', '') |
| API_BASE = f'https://api.telegram.org/bot{BOT_TOKEN}' |
|
|
| if not BOT_TOKEN: |
| raise RuntimeError('TELEGRAM_BOT_TOKEN not set!') |
|
|
|
|
| |
| def _tg(method, **kw): |
| try: |
| r = requests.post(f'{API_BASE}/{method}', json=kw, timeout=30) |
| data = r.json() |
| if not data.get('ok'): |
| log.warning('[BOT] %s not ok: %s', method, data.get('description', '')) |
| return data |
| except Exception as e: |
| log.error('[BOT] %s error: %s', method, e) |
| return {'ok': False} |
|
|
| def send_msg(chat_id, text, markup=None, parse_mode='HTML'): |
| kw = {'chat_id': chat_id, 'text': text, 'parse_mode': parse_mode, |
| 'disable_web_page_preview': True} |
| if markup: |
| kw['reply_markup'] = markup |
| r = _tg('sendMessage', **kw) |
| return r.get('result', {}).get('message_id') |
|
|
| def edit_msg(chat_id, msg_id, text, parse_mode='HTML'): |
| _tg('editMessageText', chat_id=chat_id, message_id=msg_id, |
| text=text, parse_mode=parse_mode) |
|
|
|
|
| |
| def inline_kb(): |
| return {'inline_keyboard': [[ |
| {'text': '🎬 Recap Studio ဖွင့်မည်', 'web_app': {'url': WEBAPP_URL}} |
| ]]} |
|
|
| def reply_kb(): |
| return { |
| 'keyboard': [[{'text': '🎬 Recap Studio', 'web_app': {'url': WEBAPP_URL}}]], |
| 'resize_keyboard': True, 'persistent': True, |
| } |
|
|
|
|
| |
| def on_start(msg): |
| chat_id = msg['chat']['id'] |
| fname = msg['from'].get('first_name', 'User') |
| send_msg(chat_id, |
| f'👋 မင်္ဂလာပါ <b>{fname}</b>!\n\n' |
| '🎬 <b>Recap Studio</b> မှ ကြိုဆိုပါသည်။\n\n' |
| 'AI ဖြင့် မြန်မာဘာသာ Movie Recap ဗီဒီယိုများ ' |
| 'အလိုအလျောက် ထုတ်လုပ်ပေးသော app ဖြစ်သည်။\n\n' |
| '⬇️ Button နှိပ်၍ ဖွင့်ပါ။', |
| markup=reply_kb() |
| ) |
| log.info('/start chat=%s user=%s', chat_id, msg['from'].get('username', fname)) |
|
|
| def on_help(msg): |
| send_msg(msg['chat']['id'], |
| '<b>📖 Recap Studio — သုံးနည်း</b>\n\n' |
| '1️⃣ /start နှိပ်ပါ\n' |
| '2️⃣ <b>🎬 Recap Studio</b> button နှိပ်ပါ\n' |
| '3️⃣ Video URL ထည့်ပါ\n' |
| '4️⃣ Settings ချိန်ညှိပြီး <b>Auto Process</b> နှိပ်ပါ\n' |
| '5️⃣ App ပိတ်၍ progress + video ဤ chat ထဲ ရောက်မည်\n\n' |
| '<b>💰 Coins:</b>\n' |
| '• Process တစ်ခု = 1 Coin\n' |
| '• App ထဲ Buy Coins နှိပ်ဝယ်ပါ\n\n' |
| '<b>Commands:</b>\n' |
| '/start — App ဖွင့်မည်\n' |
| '/coins — Coin လက်ကျန် စစ်မည်\n' |
| '/help — ဤ message', |
| markup=inline_kb() |
| ) |
|
|
| def on_coins(msg): |
| chat_id = msg['chat']['id'] |
| tg_id = str(msg['from']['id']) |
| try: |
| r = requests.get(f'{WEBAPP_URL}/api/coins_by_tgid', |
| params={'tg_id': tg_id}, timeout=10) |
| d = r.json() |
| except Exception: |
| d = {'ok': False} |
|
|
| if d.get('ok'): |
| send_msg(chat_id, |
| f"🪙 <b>{d.get('username','')}</b>\n\n" |
| f"လက်ကျန် Coins: <b>{d.get('coins', 0)}</b>\n\n" |
| 'Coins ဝယ်ရန် App ဖွင့်ပါ 👇', |
| markup=inline_kb() |
| ) |
| else: |
| send_msg(chat_id, |
| '❌ Account မတွေ့ပါ။\n/start နှိပ်၍ app ကနေ login ဝင်ပါ။', |
| markup=inline_kb() |
| ) |
|
|
| def on_broadcast(msg): |
| chat_id = msg['chat']['id'] |
| if str(chat_id) != str(ADMIN_ID): |
| send_msg(chat_id, '❌ Admin only.') |
| return |
| parts = (msg.get('text') or '').split(None, 1) |
| if len(parts) < 2: |
| send_msg(chat_id, '❌ Usage: /broadcast <message>') |
| return |
| try: |
| r = requests.get(f'{WEBAPP_URL}/api/admin/tg_users', |
| params={'caller': ADMIN_U}, timeout=15) |
| tg_ids = r.json().get('tg_ids', []) |
| except Exception: |
| tg_ids = [] |
| sent = 0 |
| for tid in tg_ids: |
| if send_msg(tid, f'📢 <b>Recap Studio</b>\n\n{parts[1]}'): |
| sent += 1 |
| time.sleep(0.05) |
| send_msg(chat_id, f'✅ Sent to {sent}/{len(tg_ids)} users.') |
|
|
| def on_unknown(msg): |
| send_msg(msg['chat']['id'], |
| '🎬 Recap Studio ဖွင့်ရန်:', |
| markup=inline_kb()) |
|
|
|
|
| |
| def dispatch(update): |
| msg = update.get('message') |
| if not msg: |
| return |
| text = (msg.get('text') or '').strip() |
| if not text: |
| return |
| cmd = text.split()[0].split('@')[0].lower() if text.startswith('/') else '' |
| log.info('MSG chat=%s cmd=%r', msg['chat']['id'], cmd or text[:20]) |
|
|
| if cmd == '/start': on_start(msg) |
| elif cmd == '/help': on_help(msg) |
| elif cmd == '/coins': on_coins(msg) |
| elif cmd == '/broadcast': on_broadcast(msg) |
| else: on_unknown(msg) |
|
|
|
|
| |
| def clear_webhook(): |
| for attempt in range(1, 4): |
| try: |
| r = requests.post(f'{API_BASE}/deleteWebhook', |
| json={'drop_pending_updates': True}, timeout=15) |
| if r.json().get('ok'): |
| log.info('Webhook cleared (attempt %d)', attempt) |
| return |
| log.warning('deleteWebhook not ok: %s', r.json()) |
| except Exception as e: |
| log.warning('deleteWebhook attempt %d failed: %s', attempt, e) |
| time.sleep(3) |
| log.error('Could not clear webhook — polling may conflict') |
|
|
|
|
| |
| def run_polling(): |
| log.info('Recap Studio Bot — clearing webhook...') |
| clear_webhook() |
| time.sleep(2) |
| log.info('Recap Studio Bot — polling started') |
|
|
| offset = 0 |
| while True: |
| try: |
| r = requests.get( |
| f'{API_BASE}/getUpdates', |
| params={'offset': offset, 'timeout': 25, |
| 'allowed_updates': ['message']}, |
| timeout=30 |
| ) |
| data = r.json() |
|
|
| if not data.get('ok'): |
| desc = data.get('description', '') |
| log.warning('getUpdates not ok: %s', desc) |
| time.sleep(15 if 'conflict' in desc.lower() else 5) |
| continue |
|
|
| for upd in data.get('result', []): |
| offset = upd['update_id'] + 1 |
| try: |
| dispatch(upd) |
| except Exception as e: |
| log.error('dispatch error: %s', e) |
|
|
| except requests.exceptions.ReadTimeout: |
| pass |
| except requests.exceptions.ConnectionError as e: |
| log.error('Connection error: %s — retry 10s', e) |
| time.sleep(10) |
| except Exception as e: |
| log.error('Polling error: %s', e) |
| time.sleep(5) |
|
|
|
|
| if __name__ == '__main__': |
| run_polling() |
|
|