""" GTN RDP STORE - ADAPTED FROM ZIVPN BOT STYLE - Sistem admin kayak ZIVPN: CHAT_ID di /etc/zivpn/bot_config.sh - Role: Admin, Reseller, Member - Saldo manual via QRIS (qr.jpg) - Custom password RDP + Kelola RDP (ganti pass) - Harga 1k per RDP """ import logging, json, os, subprocess, time, asyncio, datetime, random, string, re, sys, html, paramiko, socket, shlex from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, InputFile from telegram.constants import ParseMode from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, CallbackQueryHandler, MessageHandler, filters, ConversationHandler # --- PATHS (pakai struktur sama kayak ZIVPN biar familiar) --- DIR_PATH = "/etc/gtnrdp" os.makedirs(DIR_PATH, exist_ok=True) RESELLER_DB = f"{DIR_PATH}/resellers.json" # saldo & role RDP_DB = f"{DIR_PATH}/rdp_list.json" BOT_CONFIG = f"{DIR_PATH}/bot_config.sh" QRIS_IMAGE = f"{DIR_PATH}/qris.jpg" # QR lu taro sini INCOME_DB = f"{DIR_PATH}/income_log.json" BOT_START_TIME = time.time() logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) logger = logging.getLogger(__name__) # Conversation States (SELECTING_ACTION, BUAT_IP, BUAT_SSH, BUAT_RDP_PASS, BUAT_WIN, GANTI_PASS_NEW, ADD_BAL_ID, ADD_BAL_AMOUNT, ADD_RESELLER, DEL_RESELLER, SET_PRICE) = range(11) # --- HELPERS (copy dari ZIVPN) --- def get_config_value(key): if not os.path.exists(BOT_CONFIG): return None try: with open(BOT_CONFIG, 'r') as f: content = f.read() pattern = r'^\s*(?:export\s+)?' + re.escape(key) + r'\s*=\s*(["\']?)(.*?)\1\s*$' m = re.search(pattern, content, re.MULTILINE) if m: return m.group(2).strip() except: pass return None def load_json(fp): if not os.path.exists(fp): return {} if 'reseller' in fp or 'rdp' in fp or 'income' in fp else [] try: with open(fp,'r') as f: return json.load(f) except: return {} def save_json(fp, data): with open(fp,'w') as f: json.dump(data, f, indent=4) def load_resellers(): if not os.path.exists(RESELLER_DB): return {} try: with open(RESELLER_DB,'r') as f: data=json.load(f) if isinstance(data, list): nd={} for rid in data: nd[str(rid)]={"balance":0,"role":"member"} save_json(RESELLER_DB, nd) return nd return data except: return {} def save_resellers(d): save_json(RESELLER_DB, d) def get_balance(uid): d=load_resellers() return d.get(str(uid),{}).get("balance",0) def update_balance(uid, amount): d=load_resellers() sid=str(uid) if sid not in d: d[sid]={"balance":0,"role":"member"} d[sid]["balance"]=d[sid].get("balance",0)+amount save_resellers(d) return d[sid]["balance"] def is_admin(uid): val=get_config_value("CHAT_ID") return str(uid)==str(val) def check_reseller(uid): d=load_resellers() return d.get(str(uid),{}).get("role")=="reseller" def get_price(): # Harga RDP - default 1000 cfg=load_json(f"{DIR_PATH}/settings.json") return cfg.get("rdp_price", 1000) def set_price(p): cfg=load_json(f"{DIR_PATH}/settings.json") if not isinstance(cfg, dict): cfg={} cfg["rdp_price"]=int(p) save_json(f"{DIR_PATH}/settings.json", cfg) def get_rdp_list(): return load_json(RDP_DB) def save_rdp_list(data): save_json(RDP_DB, data) def add_rdp_record(uid, ip, rdp_pass, ssh_pass, windows): data=get_rdp_list() if not isinstance(data, list): data=[] data.append({ "user_id": str(uid), "ip": ip, "rdp_pass": rdp_pass, "ssh_pass": ssh_pass, "windows": windows, "status": "installing", "created": datetime.datetime.now().strftime("%Y-%m-%d %H:%M") }) save_rdp_list(data) # === INI SCRIPT INDEPENDENT PUNYA LU - GANTI URL DISINI KALO PUNYA PRIVATE === RDP_INSTALL_URL = "https://raw.githubusercontent.com/Nizwarax/Rdp/main/reinstall.sh" # kalau punya script private, ganti jadi: RDP_INSTALL_URL = "http://sacrifice.web.id/rdp.sh" atau file lu sendiri def install_rdp_auto(ip, ssh_pass, windows, rdp_pass): try: s=paramiko.SSHClient(); s.set_missing_host_key_policy(paramiko.AutoAddPolicy()) s.connect(ip, username='root', password=ssh_pass, timeout=15, banner_timeout=15, auth_timeout=15) # fix quoting biar password ada $ " ` gak bikin shell error safe_rdp_pass = shlex.quote(rdp_pass) safe_win = shlex.quote(windows) # pakai shlex juga buat RDP_INSTALL_URL biar aman cmd = f"nohup bash -c 'curl -sSL {shlex.quote(RDP_INSTALL_URL)} -o /root/reinstall.sh || wget -qO /root/reinstall.sh {shlex.quote(RDP_INSTALL_URL)}; chmod +x /root/reinstall.sh; bash /root/reinstall.sh windows --image-name {safe_win} --password {safe_rdp_pass} --allow-ping' > /tmp/rdp.log 2>&1 &" s.exec_command(cmd); time.sleep(2); s.close() return True, "" except Exception as e: err = str(e) logger.error(f"RDP install fail {ip}: {err}") return False, err def check_rdp(ip): try: sock=socket.socket(); sock.settimeout(3) return sock.connect_ex((ip,3389))==0 except: return False def format_rupiah(v): try: return f"Rp {int(v):,.0f}".replace(",",".") except: return str(v) def get_uptime(): delta=int(time.time()-BOT_START_TIME) return f"{delta//3600}h {(delta%3600)//60}m" # --- MENU --- async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): user=update.effective_user uid=str(user.id) safe_name = f"@{user.username}" if user.username else html.escape(user.first_name) saldo=get_balance(uid) role="Admin" if is_admin(uid) else ("Reseller" if check_reseller(uid) else "Member") saldo_txt="āˆž" if is_admin(uid) else format_rupiah(saldo) price=format_rupiah(get_price()) text=( f"šŸ“¦ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”šŸ“¦\n" f" ✨ GTN RDP STORE ✨\n" f"šŸ“¦ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”šŸ“¦\n\n" f"🧭 info akun\n" f"ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”“\n" f"ā”ƒ šŸ’° saldo: {saldo_txt}\n" f"ā”ƒ šŸ‘¤ status: {role}\n" f"ā”ƒ 🌐 user: {safe_name}\n" f"ā”ƒ šŸ†” id: {uid}\n" f"ā”ƒ ā±ļø uptime: {get_uptime()}\n" f"┗━━━━━━━━━━━━━━━━━━━━━┛\n\n" f"šŸŽÆ harga\n" f"ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”“\n" f"ā”ƒ šŸ·ļø 1 RDP: {price}\n" f"ā”ƒ šŸ” custom pass: bisa\n" f"ā”ƒ ⚔ auto install: 15-20m\n" f"┗━━━━━━━━━━━━━━━━━━━━━┛\n" ) buttons=[ [InlineKeyboardButton("šŸ“¦ Buat RDP Custom Pass", callback_data='buat_rdp')], [InlineKeyboardButton("šŸ“‹ Kelola RDP Ku", callback_data='kelola_rdp'), InlineKeyboardButton("šŸ” Cek RDP", callback_data='cek_rdp')], [InlineKeyboardButton("šŸ’° Saldo Ku", callback_data='saldo'), InlineKeyboardButton("šŸ’³ Topup QRIS", callback_data='topup')], ] if is_admin(uid): buttons.append([InlineKeyboardButton("šŸ‘‘ Admin: Add Saldo", callback_data='admin_addsaldo'), InlineKeyboardButton("šŸ‘„ List Saldo", callback_data='admin_listsaldo')]) buttons.append([InlineKeyboardButton("āž• Add Reseller", callback_data='admin_addreseller'), InlineKeyboardButton("šŸ“¦ Semua RDP", callback_data='admin_allrdp')]) buttons.append([InlineKeyboardButton("šŸ·ļø Set Harga RDP", callback_data='admin_setprice')]) rm=InlineKeyboardMarkup(buttons) if update.callback_query: await update.callback_query.edit_message_text(text, reply_markup=rm, parse_mode=ParseMode.HTML) else: await update.message.reply_text(text, reply_markup=rm, parse_mode=ParseMode.HTML) return SELECTING_ACTION # --- BUAT RDP FLOW (CUSTOM PASS) --- async def buat_rdp_start(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.callback_query.answer() await update.callback_query.edit_message_text("šŸ“¦ Buat RDP Custom\n\nMasukkan IP VPS target:", parse_mode=ParseMode.HTML) return BUAT_IP async def buat_ip(update: Update, context: ContextTypes.DEFAULT_TYPE): context.user_data['ip']=update.message.text.strip() await update.message.reply_text(f"IP: {context.user_data['ip']} āœ…\n\nMasukkan Password SSH Root:", parse_mode=ParseMode.HTML) return BUAT_SSH async def buat_ssh(update: Update, context: ContextTypes.DEFAULT_TYPE): context.user_data['ssh']=update.message.text.strip() await update.message.reply_text("Masukkan Password RDP custom yang kamu mau:\nContoh: MyRdp2025!!\nKetik /default untuk pakai Rdp123@@", parse_mode=ParseMode.HTML) return BUAT_RDP_PASS async def buat_rdp_pass(update: Update, context: ContextTypes.DEFAULT_TYPE): rpass = "Rdp123@@" if update.message.text.strip()=="/default" else update.message.text.strip() context.user_data['rpass']=rpass kb=InlineKeyboardMarkup([ [InlineKeyboardButton("Server 2022", callback_data='win_2022'), InlineKeyboardButton("Server 2019", callback_data='win_2019')], [InlineKeyboardButton("Win 11 Pro", callback_data='win_11'), InlineKeyboardButton("Win 10 Pro", callback_data='win_10')] ]) await update.message.reply_text(f"Pass RDP: {rpass} āœ…\n\nPilih Windows:", reply_markup=kb, parse_mode=ParseMode.HTML) return BUAT_WIN async def buat_win(update: Update, context: ContextTypes.DEFAULT_TYPE): q=update.callback_query await q.answer() wm={"win_2022":"Windows Server 2022 SERVERDATACENTER","win_2019":"Windows Server 2019 SERVERDATACENTER","win_11":"Windows 11 Pro","win_10":"Windows 10 Pro"} win=wm[q.data] ip=context.user_data['ip']; ssh=context.user_data['ssh']; rpass=context.user_data['rpass'] uid=update.effective_user.id price=get_price() saldo=get_balance(uid) if not is_admin(uid) and saldo < price: await q.edit_message_text(f"āŒ Saldo kurang! Butuh {format_rupiah(price)}, saldo lu {format_rupiah(saldo)}\n\nTopup dulu via QRIS", parse_mode=ParseMode.HTML) return SELECTING_ACTION await q.edit_message_text(f"šŸ”„ Install RDP di {ip}...\nWindows: {win}\nPass: {rpass} (custom)\n\nSedang proses...", parse_mode=ParseMode.HTML) ok, err = install_rdp_auto(ip, ssh, win, rpass) if ok: if not is_admin(uid): update_balance(uid, -price) # log income logs=load_json(INCOME_DB) if not isinstance(logs, list): logs=[] logs.append({"timestamp":int(time.time()),"user_id":str(uid),"username":update.effective_user.username or "", "amount":price, "ip":ip}) save_json(INCOME_DB, logs) add_rdp_record(uid, ip, rpass, ssh, win) await context.bot.send_message(chat_id=update.effective_chat.id, text=f"āœ… RDP DIPROSES!\n\nIP: {ip}:3389\nUser: Administrator\nPass: {rpass} (custom)\nWin: {win}\n\nTunggu 15-20 menit\nCek: /cekrdp {ip}\nKelola: /kelolardp", parse_mode=ParseMode.HTML) else: # kasih error detail biar tau kenapa gagal await context.bot.send_message(chat_id=update.effective_chat.id, text=f"āŒ Gagal install {ip}\n\nPenyebab: {html.escape(err)[:500]}\n\nCek:\n1. IP benar? ping {ip}\n2. SSH root aktif? coba ssh root@{ip}\n3. Pass SSH salah?\n4. Port 22 keblok firewall?\n5. Password RDP ada karakter aneh?", parse_mode=ParseMode.HTML) return await start(update, context) # --- KELOLA RDP --- async def kelola_rdp(update: Update, context: ContextTypes.DEFAULT_TYPE): q=update.callback_query; await q.answer() uid=str(update.effective_user.id) data=get_rdp_list() my=[x for x in data if x.get("user_id")==uid] if not my: await q.edit_message_text("Belum ada RDP. Buat dulu /buatrdp", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("šŸ”™ Menu", callback_data='back')]])) return SELECTING_ACTION txt="šŸ“‹ RDP Kamu:\n\n" kb=[] for i, r in enumerate(my[-10:]): txt+=f"{i+1}. {r['ip']}:3389 | {r['rdp_pass']} | {r['status']}\n" kb.append([InlineKeyboardButton(f"Ganti Pass {r['ip']}", callback_data=f"gp_{r['ip']}"), InlineKeyboardButton(f"Cek {r['ip']}", callback_data=f"ck_{r['ip']}")]) kb.append([InlineKeyboardButton("šŸ”™ Menu", callback_data='back')]) await q.edit_message_text(txt, reply_markup=InlineKeyboardMarkup(kb), parse_mode=ParseMode.HTML) return SELECTING_ACTION async def cek_rdp_cb(update: Update, context: ContextTypes.DEFAULT_TYPE): q=update.callback_query; await q.answer() ip=q.data.replace('ck_','') ok=check_rdp(ip) await q.message.reply_text(f"{'🟢 AKTIF' if ok else '🟔 Installing / šŸ”“ Mati'} - {ip}:3389") async def ganti_pass_cb(update: Update, context: ContextTypes.DEFAULT_TYPE): q=update.callback_query; await q.answer() ip=q.data.replace('gp_','') context.user_data['gp_ip']=ip await q.message.reply_text(f"Masukkan password baru untuk RDP {ip}:") return GANTI_PASS_NEW async def ganti_pass_new(update: Update, context: ContextTypes.DEFAULT_TYPE): new_pass=update.message.text.strip() ip=context.user_data['gp_ip'] uid=str(update.effective_user.id) # cari ssh pass data=get_rdp_list() ssh_pass="" for r in data: if r['ip']==ip and r['user_id']==uid: ssh_pass=r['ssh_pass'] r['rdp_pass']=new_pass break save_rdp_list(data) # coba ganti via ssh jika windows sudah jadi try: s=paramiko.SSHClient(); s.set_missing_host_key_policy(paramiko.AutoAddPolicy()) s.connect(ip, username='Administrator', password=ssh_pass, timeout=10) s.exec_command(f"net user Administrator {shlex.quote(new_pass)}") s.close() await update.message.reply_text(f"āœ… Password RDP {ip} diganti jadi {new_pass}", parse_mode=ParseMode.HTML) except: # jika belum jadi windows, update db aja, nanti pas install pakai pass baru? simpan aja await update.message.reply_text(f"āœ… Password di database diganti jadi {new_pass}\nJika RDP masih installing, password baru akan aktif setelah install selesai (reinstall manual jika perlu)", parse_mode=ParseMode.HTML) return await start(update, context) # --- SALDO & TOPUP MANUAL (QR PUNYA LU) --- async def saldo_cb(update: Update, context: ContextTypes.DEFAULT_TYPE): q=update.callback_query; await q.answer() uid=str(update.effective_user.id) saldo=get_balance(uid) await q.message.reply_text(f"šŸ’° Saldo: {format_rupiah(saldo)} ({saldo//get_price()} RDP)") async def topup_cb(update: Update, context: ContextTypes.DEFAULT_TYPE): q=update.callback_query; await q.answer() admin_uname="@Nizwara" # GANTI USERNAME LU uid=update.effective_user.id txt=( "šŸ’³ TOPUP MANUAL - QRIS PUNYA LU\n\n" "1. Scan QR dibawah ini\n" "2. Transfer sesuai nominal\n" "3. Kirim bukti ke Admin: "+admin_uname+"\n" f"4. Sertakan ID: {uid}\n\n" f"Harga: {format_rupiah(get_price())} / RDP\n" "10k = 10 RDP, 50k = 50 RDP\n\n" "Admin akan add saldo manual biar gak ada auto payment mencurigakan" ) if os.path.exists(QRIS_IMAGE): await context.bot.send_photo(chat_id=update.effective_chat.id, photo=open(QRIS_IMAGE,'rb'), caption=txt, parse_mode=ParseMode.HTML) else: await context.bot.send_message(chat_id=update.effective_chat.id, text=txt + "\n\nāš ļø QR belum ada di /etc/gtnrdp/qris.jpg", parse_mode=ParseMode.HTML) # --- ADMIN: ADD SALDO (MANUAL BIAR GAK CURIGA) --- async def admin_addsaldo_start(update: Update, context: ContextTypes.DEFAULT_TYPE): q=update.callback_query; await q.answer() if not is_admin(update.effective_user.id): return SELECTING_ACTION await q.edit_message_text("šŸ’° Add Saldo Manual\n\nMasukkan ID Telegram User:", parse_mode=ParseMode.HTML) return ADD_BAL_ID async def add_bal_id(update: Update, context: ContextTypes.DEFAULT_TYPE): context.user_data['bal_id']=update.message.text.strip() await update.message.reply_text(f"Target ID: {context.user_data['bal_id']}\nMasukkan nominal (contoh 10000):", parse_mode=ParseMode.HTML) return ADD_BAL_AMOUNT async def add_bal_amount(update: Update, context: ContextTypes.DEFAULT_TYPE): try: amt=int(update.message.text.strip()) except: return ADD_BAL_AMOUNT tid=context.user_data['bal_id'] new_bal=update_balance(tid, amt) await update.message.reply_text(f"āœ… Saldo Rp {amt} ditambah ke {tid}\nSaldo sekarang: {format_rupiah(new_bal)}", parse_mode=ParseMode.HTML) try: await context.bot.send_message(chat_id=int(tid), text=f"āœ… Admin menambah saldo {format_rupiah(amt)}\nSaldo sekarang: {format_rupiah(new_bal)}") except: pass return await start(update, context) async def admin_listsaldo(update: Update, context: ContextTypes.DEFAULT_TYPE): q=update.callback_query; await q.answer() data=load_resellers() txt=f"šŸ‘„ List Saldo ({len(data)})\n\n" for uid, info in sorted(data.items(), key=lambda x: x[1].get('balance',0), reverse=True)[:20]: txt+=f"ID {uid} - {format_rupiah(info.get('balance',0))} - {info.get('role','member')}\n" await q.edit_message_text(txt, parse_mode=ParseMode.HTML, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("šŸ”™ Menu", callback_data='back')]])) async def admin_allrdp(update: Update, context: ContextTypes.DEFAULT_TYPE): q=update.callback_query; await q.answer() data=get_rdp_list() txt=f"šŸ“¦ Semua RDP ({len(data)})\n\n" for r in data[-20:]: txt+=f"User {r['user_id']} | {r['ip']}:3389 | {r['rdp_pass']} | {r['status']}\n" await q.edit_message_text(txt[:4000], parse_mode=ParseMode.HTML, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("šŸ”™ Menu", callback_data='back')]])) # --- MAIN --- def main(): token=get_config_value("BOT_TOKEN") if not token: print("BOT_TOKEN belum ada di /etc/gtnrdp/bot_config.sh") print("Buat file: echo 'BOT_TOKEN=123:ABC' > /etc/gtnrdp/bot_config.sh && echo 'CHAT_ID=ID_ADMIN_LU' >> /etc/gtnrdp/bot_config.sh") return app=ApplicationBuilder().token(token).build() conv=ConversationHandler( entry_points=[CommandHandler("start", start), CallbackQueryHandler(start, pattern='^back$')], states={ SELECTING_ACTION: [ CallbackQueryHandler(buat_rdp_start, pattern='^buat_rdp$'), CallbackQueryHandler(kelola_rdp, pattern='^kelola_rdp$'), CallbackQueryHandler(cek_rdp_cb, pattern='^ck_'), CallbackQueryHandler(ganti_pass_cb, pattern='^gp_'), CallbackQueryHandler(saldo_cb, pattern='^saldo$'), CallbackQueryHandler(topup_cb, pattern='^topup$'), CallbackQueryHandler(admin_addsaldo_start, pattern='^admin_addsaldo$'), CallbackQueryHandler(admin_listsaldo, pattern='^admin_listsaldo$'), CallbackQueryHandler(admin_allrdp, pattern='^admin_allrdp$'), CallbackQueryHandler(start, pattern='^back$'), ], BUAT_IP: [MessageHandler(filters.TEXT & ~filters.COMMAND, buat_ip)], BUAT_SSH: [MessageHandler(filters.TEXT & ~filters.COMMAND, buat_ssh)], BUAT_RDP_PASS: [ MessageHandler(filters.TEXT, buat_rdp_pass), # TEXT aja, biar /default ke-detect (sebelumnya ~COMMAND bikin /default gak masuk) CommandHandler("default", buat_rdp_pass) ], BUAT_WIN: [CallbackQueryHandler(buat_win, pattern='^win_')], GANTI_PASS_NEW: [MessageHandler(filters.TEXT & ~filters.COMMAND, ganti_pass_new)], ADD_BAL_ID: [MessageHandler(filters.TEXT & ~filters.COMMAND, add_bal_id)], ADD_BAL_AMOUNT: [MessageHandler(filters.TEXT & ~filters.COMMAND, add_bal_amount)], }, fallbacks=[CommandHandler("start", start)] ) app.add_handler(conv) print("GTN RDP Bot ZIVPN Style Running - Manual QR Topup - Custom Pass") app.run_polling() if __name__=='__main__': main()