Phoe2004 commited on
Commit
cc5dbfb
·
verified ·
1 Parent(s): b0e296f

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +42 -0
  2. bot.py +37 -0
  3. index.html +23 -0
app.py CHANGED
@@ -2599,6 +2599,48 @@ def api_admin_payment_slip(payment_id):
2599
  return jsonify(ok=False, msg=str(e))
2600
 
2601
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2602
  @app.route('/api/payment/kbz_qr')
2603
  def api_kbz_qr():
2604
  """
 
2599
  return jsonify(ok=False, msg=str(e))
2600
 
2601
 
2602
+ @app.route('/api/admin/broadcast', methods=['POST'])
2603
+ def api_admin_broadcast():
2604
+ """Send broadcast message to all users via Telegram bot."""
2605
+ try:
2606
+ data = request.get_json(force=True)
2607
+ caller = data.get('caller', '')
2608
+ if caller != ADMIN_U:
2609
+ return jsonify(ok=False, msg='❌ Admin only'), 403
2610
+ message = data.get('message', '').strip()
2611
+ if not message:
2612
+ return jsonify(ok=False, msg='❌ Message မထည့်ရသေးပါ')
2613
+ db = load_db()
2614
+ token = os.getenv('TELEGRAM_BOT_TOKEN', '')
2615
+ if not token:
2616
+ return jsonify(ok=False, msg='❌ BOT_TOKEN မသတ်မှတ်ရသေးပါ')
2617
+ import urllib.request as _ur, json as _json, threading as _th
2618
+ sent = 0; fail = 0
2619
+ for uname, udata in db.get('users', {}).items():
2620
+ tg_id = udata.get('tg_chat_id')
2621
+ if not tg_id:
2622
+ continue
2623
+ try:
2624
+ payload = _json.dumps({
2625
+ 'chat_id': tg_id,
2626
+ 'text': f'📢 *ကြေငြာချက်*\n\n{message}',
2627
+ 'parse_mode': 'Markdown',
2628
+ }).encode()
2629
+ req = _ur.Request(
2630
+ f'https://api.telegram.org/bot{token}/sendMessage',
2631
+ data=payload,
2632
+ headers={'Content-Type': 'application/json'})
2633
+ _ur.urlopen(req, timeout=8)
2634
+ sent += 1
2635
+ except Exception as e:
2636
+ logger.warning(f'[broadcast] {uname}: {e}')
2637
+ fail += 1
2638
+ return jsonify(ok=True, sent=sent, fail=fail,
2639
+ msg=f'✅ {sent} ယောက် ပို့ပြီး — {fail} ကျ')
2640
+ except Exception as e:
2641
+ return jsonify(ok=False, msg=str(e))
2642
+
2643
+
2644
  @app.route('/api/payment/kbz_qr')
2645
  def api_kbz_qr():
2646
  """
bot.py CHANGED
@@ -876,6 +876,41 @@ async def on_callback(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
876
  # ── TEXT HANDLER ──
877
 
878
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
879
  async def cmd_pending(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
880
  """Admin: list pending payments."""
881
  cid = update.effective_chat.id
@@ -1119,8 +1154,10 @@ def main():
1119
  )
1120
  application.add_handler(conv)
1121
  application.add_handler(CommandHandler('pending', cmd_pending))
 
1122
  # Global handler for adm_pay callbacks — works even outside conversation state
1123
  application.add_handler(CallbackQueryHandler(on_callback, pattern='^adm_pay[|]'))
 
1124
  logger.info('🤖 Recap Studio Bot အသင့်ဖြစ်ပြီ!')
1125
  application.run_polling(
1126
  drop_pending_updates=True,
 
876
  # ── TEXT HANDLER ──
877
 
878
 
879
+ async def error_handler(update: object, ctx: ContextTypes.DEFAULT_TYPE) -> None:
880
+ """Handle all telegram errors gracefully without crashing."""
881
+ import traceback
882
+ err = ctx.error
883
+ tb = ''.join(traceback.format_exception(type(err), err, err.__traceback__))
884
+ logger.warning(f'[ErrorHandler] {err}\n{tb[:400]}')
885
+
886
+ async def cmd_broadcast(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
887
+ """Admin: /broadcast <message> — send to all users with tg_chat_id."""
888
+ cid = update.effective_chat.id
889
+ s = sess(cid)
890
+ if not s.get('is_admin'):
891
+ await update.message.reply_text("❌ Admin only"); return
892
+ text = ' '.join(ctx.args) if ctx.args else ''
893
+ if not text:
894
+ await update.message.reply_text(
895
+ "📢 *Broadcast*\\n\\nUsage: `/broadcast <message>`",
896
+ parse_mode=ParseMode.MARKDOWN); return
897
+ db = load_db()
898
+ sent = 0; fail = 0
899
+ for uname, udata in db.get('users', {}).items():
900
+ tg_id = udata.get('tg_chat_id')
901
+ if not tg_id: continue
902
+ try:
903
+ await ctx.bot.send_message(
904
+ chat_id=tg_id,
905
+ text=f"📢 *ကြေငြာချက်*\\n\\n{text}",
906
+ parse_mode=ParseMode.MARKDOWN)
907
+ sent += 1
908
+ except Exception as e:
909
+ logger.warning(f'[broadcast] {uname}: {e}')
910
+ fail += 1
911
+ await update.message.reply_text(
912
+ f"✅ Broadcast ပြီးပါပြီ\\n📨 ပေးပို့: {sent} ယောက်\\n❌ မအောင်မြင်: {fail} ယောက်")
913
+
914
  async def cmd_pending(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
915
  """Admin: list pending payments."""
916
  cid = update.effective_chat.id
 
1154
  )
1155
  application.add_handler(conv)
1156
  application.add_handler(CommandHandler('pending', cmd_pending))
1157
+ application.add_handler(CommandHandler('broadcast', cmd_broadcast))
1158
  # Global handler for adm_pay callbacks — works even outside conversation state
1159
  application.add_handler(CallbackQueryHandler(on_callback, pattern='^adm_pay[|]'))
1160
+ application.add_error_handler(error_handler)
1161
  logger.info('🤖 Recap Studio Bot အသင့်ဖြစ်ပြီ!')
1162
  application.run_polling(
1163
  drop_pending_updates=True,
index.html CHANGED
@@ -1003,6 +1003,17 @@ body{background:var(--bg);color:var(--text);font-family:var(--F);min-height:100v
1003
  <div style="display:flex;gap:8px;margin-top:8px;flex-wrap:wrap">
1004
  <button class="adm-act-btn" onclick="loadUsers()"><i class="fas fa-users"></i> Users</button>
1005
  <button class="adm-act-btn" style="background:rgba(239,68,68,.1);border-color:rgba(239,68,68,.3);color:#ef4444" onclick="loadPendingPayments()"><i class="fas fa-clock"></i> Pending Payments <span id="adm-pending-count" style="background:#ef4444;color:#fff;border-radius:20px;padding:1px 7px;font-size:.62rem;margin-left:4px;display:none">0</span></button>
 
 
 
 
 
 
 
 
 
 
 
1006
  </div>
1007
  <div id="utw" style="overflow-x:auto;margin-top:8px"></div>
1008
  <!-- Pending Payments -->
@@ -1821,6 +1832,18 @@ async function qDel(u){if(!confirm('Delete '+u+'?'))return;const r=await fetch('
1821
  async function qBan(u,ban){const label=ban?'Ban':'Unban';if(!confirm(label+' '+u+'?'))return;const r=await fetch('/api/admin/ban_user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,username:u,ban})});const d=await r.json();toast(d.msg);if(d.ok)loadUsers();}
1822
 
1823
  /* ══ PENDING PAYMENTS ══ */
 
 
 
 
 
 
 
 
 
 
 
 
1824
  async function loadPendingPayments(){
1825
  const wrap=document.getElementById('adm-pending-wrap');
1826
  const list=document.getElementById('adm-pending-list');
 
1003
  <div style="display:flex;gap:8px;margin-top:8px;flex-wrap:wrap">
1004
  <button class="adm-act-btn" onclick="loadUsers()"><i class="fas fa-users"></i> Users</button>
1005
  <button class="adm-act-btn" style="background:rgba(239,68,68,.1);border-color:rgba(239,68,68,.3);color:#ef4444" onclick="loadPendingPayments()"><i class="fas fa-clock"></i> Pending Payments <span id="adm-pending-count" style="background:#ef4444;color:#fff;border-radius:20px;padding:1px 7px;font-size:.62rem;margin-left:4px;display:none">0</span></button>
1006
+ <button class="adm-act-btn" style="background:rgba(99,102,241,.1);border-color:rgba(99,102,241,.3);color:#6366f1" onclick="document.getElementById('adm-bc-wrap').style.display=document.getElementById('adm-bc-wrap').style.display==='none'?'block':'none'"><i class="fas fa-bullhorn"></i> Broadcast</button>
1007
+ </div>
1008
+ <!-- Broadcast Section -->
1009
+ <div id="adm-bc-wrap" style="display:none;margin-top:10px;background:rgba(99,102,241,.06);border:1px solid rgba(99,102,241,.2);border-radius:12px;padding:12px">
1010
+ <div style="font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#6366f1;margin-bottom:8px"><i class="fas fa-bullhorn"></i> Broadcast Message</div>
1011
+ <textarea id="bc-msg" placeholder="ကြေငြာချက် ရေးပါ…" style="width:100%;box-sizing:border-box;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.1);border-radius:8px;color:#fff;padding:10px;font-size:.82rem;font-family:var(--F);resize:vertical;min-height:80px;outline:none" rows="3"></textarea>
1012
+ <div style="display:flex;gap:8px;margin-top:8px;align-items:center">
1013
+ <button class="abtn ab-g" onclick="sendBroadcast()" style="flex:1"><i class="fas fa-paper-plane"></i> ပို့မည်</button>
1014
+ <button class="abtn" onclick="document.getElementById('bc-msg').value=''" style="background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.1);color:rgba(255,255,255,.6)"><i class="fas fa-times"></i></button>
1015
+ </div>
1016
+ <div id="bc-res" style="font-size:.72rem;margin-top:6px;color:var(--green);font-weight:600"></div>
1017
  </div>
1018
  <div id="utw" style="overflow-x:auto;margin-top:8px"></div>
1019
  <!-- Pending Payments -->
 
1832
  async function qBan(u,ban){const label=ban?'Ban':'Unban';if(!confirm(label+' '+u+'?'))return;const r=await fetch('/api/admin/ban_user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,username:u,ban})});const d=await r.json();toast(d.msg);if(d.ok)loadUsers();}
1833
 
1834
  /* ══ PENDING PAYMENTS ══ */
1835
+ async function sendBroadcast(){
1836
+ const msg=document.getElementById('bc-msg').value.trim();
1837
+ const res=document.getElementById('bc-res');
1838
+ if(!msg){res.style.color='#ef4444';res.textContent='❌ Message ထည့်ပါ';return;}
1839
+ res.style.color='rgba(255,255,255,.5)';res.textContent='📨 ပို့နေသည်…';
1840
+ try{
1841
+ const r=await fetch('/api/admin/broadcast',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,message:msg})});
1842
+ const d=await r.json();
1843
+ if(d.ok){res.style.color='var(--green)';res.textContent=d.msg;document.getElementById('bc-msg').value='';}
1844
+ else{res.style.color='#ef4444';res.textContent=d.msg||'❌ Error';}
1845
+ }catch(e){res.style.color='#ef4444';res.textContent='❌ Network error';}
1846
+ }
1847
  async function loadPendingPayments(){
1848
  const wrap=document.getElementById('adm-pending-wrap');
1849
  const list=document.getElementById('adm-pending-list');