import os import random import time import threading import re import cloudscraper import requests from bs4 import BeautifulSoup from colorama import init, Fore init(autoreset=True) # ================= KONFIGURASI ================= BASE_URL = "https://firefaucet.win" DASHBOARD_URL = f"{BASE_URL}/" FAUCET_URL = f"{BASE_URL}/faucet/" PAYOUT_URL = f"{BASE_URL}/internal-api/payout/" BALANCE_URL = f"{BASE_URL}/balance/" WITHDRAW_INFO_URL = f"{BASE_URL}/internal-api/withdraw/get-withdrawal-info/" WITHDRAW_URL = f"{BASE_URL}/api/withdraw/" # <--- PERBAIKAN: endpoint withdraw LEVELS_URL = f"{BASE_URL}/levels" # Environment variables SOLVER_URLS = os.getenv("SOLVER_URLS", "https://gadisk820-testapi.hf.space").split(',') SOLVER_KEY = os.getenv("SOLVER_KEY", "00000000000000000000#0000000000000000000#000000000000000000#") PROXY_URL = os.getenv("PROXY_URL", None) LTC_ADDRESS = os.getenv("LTC_ADDRESS", "ltc1qrthepxg7yxs5d8v94k6y8p74q0qxvv7vk5qe7m") # Alamat LTC tujuan withdraw HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Referer": DASHBOARD_URL } COOKIES = { "session": "ae530762-a411-49c1-9b86-00741a937b60.l40aeEK2wwSTMN2ifOUFUIi7KVw", "refer_by" : "1022644" } # ================= SOLVER TURNSTILE ================= class TurnstileSolver: def __init__(self, email=""): self.email = email self.solver_url = random.choice(SOLVER_URLS).strip() self.sitekey = "0x4AAAAAAAEUvFih09RuyAna" def solve(self, domain): domain_with_proto = f"https://{domain}" if not domain.startswith('http') else domain headers = {"Content-Type": "application/json", "key": SOLVER_KEY} data = {"type": "turnstile", "domain": domain_with_proto, "siteKey": self.sitekey} for attempt in range(1, 6): try: resp = requests.post(f"{self.solver_url}/solve", headers=headers, json=data, timeout=30) result = resp.json() if "taskId" not in result: print(Fore.RED + f"❌ Tidak ada taskId (percobaan {attempt})", flush=True) continue task_id = result["taskId"] for _ in range(30): time.sleep(5) poll = requests.post(f"{self.solver_url}/solve", headers=headers, json={"taskId": task_id}, timeout=30) poll_res = poll.json() if poll_res.get("status") == "done": token = poll_res.get("token") or poll_res.get("solution", {}).get("token") if token: print(Fore.GREEN + "✅ Token Turnstile diterima", flush=True) return token elif poll_res.get("status") == "error": print(Fore.RED + f"⚠️ Error solver: {poll_res}", flush=True) break print(Fore.YELLOW + "⏳ Timeout polling, coba solver lain?", flush=True) except Exception as e: print(Fore.RED + f"⚠️ Solver error: {e}", flush=True) time.sleep(2 ** attempt) return None # ================= BOT UTAMA ================= class FaucetBot: def __init__(self, cookies, email="user@example.com"): self.session = cloudscraper.create_scraper() self.session.cookies.update(cookies) self.email = email self.solver = TurnstileSolver(email) self.running = True self.proxy_url = PROXY_URL self.using_proxy = False if self.proxy_url: self.proxy_session = cloudscraper.create_scraper() self.proxy_session.cookies.update(cookies) proxies = {"http": self.proxy_url, "https": self.proxy_url} self.proxy_session.proxies.update(proxies) else: self.proxy_session = None def get_csrf_token(self, use_proxy=False): session = self.proxy_session if use_proxy and self.proxy_session else self.session try: resp = session.get(DASHBOARD_URL, headers=HEADERS, timeout=30) resp.raise_for_status() soup = BeautifulSoup(resp.text, "html.parser") token_input = soup.find("input", {"name": "csrf_token"}) if token_input: return token_input['value'] else: print(Fore.RED + "⚠️ CSRF token tidak ditemukan", flush=True) return None except Exception as e: print(Fore.RED + f"⚠️ Gagal ambil CSRF: {e}", flush=True) return None def check_cooldown(self, html): match = re.search(r'(?:after|in)\s+(\d+)\s*(minute|minutes|hour|hours)', html, re.IGNORECASE) if match: value = int(match.group(1)) unit = match.group(2).lower() seconds = value * 3600 if 'hour' in unit else value * 60 print(Fore.YELLOW + f"⏳ Cooldown {value} {unit}, menunggu {seconds} detik...", flush=True) time.sleep(seconds) return True if re.search(r'please come back after|try again later|cooldown', html, re.IGNORECASE): default_wait = 30 * 60 print(Fore.YELLOW + f"⏳ Cooldown terdeteksi (tanpa durasi), menunggu {default_wait//60} menit default...", flush=True) time.sleep(default_wait) return True return False def get_balance(self, use_proxy=False): session = self.proxy_session if use_proxy and self.proxy_session else self.session try: resp = session.get(BALANCE_URL, headers=HEADERS, timeout=30) if resp.status_code != 200: return None soup = BeautifulSoup(resp.text, "html.parser") usd_elem = soup.find("span", id="ltc-usd-balance") ltc_elem = soup.find("b", id="ltc-balance") usd = usd_elem.text.strip() if usd_elem else "N/A" ltc = ltc_elem.text.strip() if ltc_elem else "N/A" return usd, ltc except Exception as e: print(Fore.RED + f"⚠️ Gagal ambil balance: {e}", flush=True) return None # ========== FUNGSI WITHDRAW ========== def get_withdrawal_info(self, coin="ltc", use_proxy=False): session = self.proxy_session if use_proxy and self.proxy_session else self.session params = {"coin": coin} try: resp = session.get(WITHDRAW_INFO_URL, headers=HEADERS, params=params, timeout=30) resp.raise_for_status() return resp.json() except Exception as e: print(Fore.RED + f"⚠️ Gagal ambil info withdrawal: {e}", flush=True) return None def withdraw(self, coin="ltc", address=None, amount=None, use_proxy=False): """Lakukan withdraw dengan amount USD ke address tertentu (network mainnet)""" if not address: address = LTC_ADDRESS if not address: print(Fore.RED + "❌ Alamat LTC tidak ditemukan. Set LTC_ADDRESS di environment variable.", flush=True) return False # Ambil info withdrawal info = self.get_withdrawal_info(coin, use_proxy=use_proxy) if not info: return False user_balance_usd = info['user']['balance_usd'] if user_balance_usd < 4.0: print(Fore.YELLOW + f"⏳ Saldo belum mencukupi untuk withdraw: ${user_balance_usd:.2f} < $4", flush=True) return False daily_limit = info['user']['daily_withdrawal_limit'] max_possible = min(user_balance_usd, daily_limit['max'] - daily_limit['used']) if max_possible < 4.0: print(Fore.YELLOW + f"⏳ Maksimum withdraw ${max_possible:.2f} < $4", flush=True) return False if amount is None: amount = max_possible else: amount = min(amount, max_possible) # Dapatkan CSRF token csrf = self.get_csrf_token(use_proxy=use_proxy) if not csrf: return False # Data POST sesuai form (network mainnet untuk LTC) data = { "csrf_token": csrf, "coin": coin, "network": "mainnet", # <--- PENTING: field network "address": address, "amount": str(amount) } headers_post = HEADERS.copy() headers_post.update({ "Content-Type": "application/x-www-form-urlencoded", "X-Requested-With": "XMLHttpRequest" }) session = self.proxy_session if use_proxy and self.proxy_session else self.session try: resp = session.post(WITHDRAW_URL, headers=headers_post, data=data, timeout=30) if resp.status_code == 200: result = resp.json() if result.get('success'): print(Fore.GREEN + f"✅ Withdraw sukses! ID: {result.get('withdrawal_id')}", flush=True) return True else: print(Fore.RED + f"❌ Withdraw gagal: {result.get('message')}", flush=True) return False else: print(Fore.RED + f"❌ Withdraw error status: {resp.status_code}", flush=True) return False except Exception as e: print(Fore.RED + f"⚠️ Gagal POST withdraw: {e}", flush=True) return False # ========== FUNGSI LEVEL ========== def check_and_claim_levels(self, use_proxy=False): """Cek level tiap jam dan klaim jika tersedia""" session = self.proxy_session if use_proxy and self.proxy_session else self.session try: resp = session.get(LEVELS_URL, headers=HEADERS) resp.raise_for_status() soup = BeautifulSoup(resp.text, "html.parser") collect_buttons = [a['href'] for a in soup.select("a.collect-btn") if 'disabled' not in a.get('class', [])] if not collect_buttons: print(Fore.YELLOW + "⚠️ Tidak ada level yang tersedia untuk diklaim saat ini.") return for href in collect_buttons: url = href if href.startswith("http") else BASE_URL + href print(Fore.MAGENTA + f"🟢 Mengklaim level: {url}") claim_resp = session.get(url, headers=HEADERS) if claim_resp.status_code == 200: print(Fore.GREEN + "✅ Claim level berhasil!") else: print(Fore.RED + f"⚠️ Claim gagal, status code: {claim_resp.status_code}") except Exception as e: print(Fore.RED + "⚠️ Error saat cek atau klaim level:", e) # ========== LOOP UTAMA ========== def claim_faucet(self, use_proxy=False): session = self.proxy_session if use_proxy and self.proxy_session else self.session csrf = self.get_csrf_token(use_proxy=use_proxy) if not csrf: return False token = self.solver.solve("firefaucet.win") if not token: print(Fore.RED + "❌ Gagal mendapatkan token Turnstile", flush=True) return False data = { "csrf_token": csrf, "selected-captcha": "turnstile", "cf-turnstile-response": token } headers_post = HEADERS.copy() headers_post.update({ "Content-Type": "application/x-www-form-urlencoded", "X-Requested-With": "XMLHttpRequest" }) try: resp = session.post(FAUCET_URL, headers=headers_post, data=data, timeout=30, allow_redirects=True) html = resp.text print(Fore.CYAN + f"📍 Response URL: {resp.url}", flush=True) print(Fore.CYAN + f"📊 Status code: {resp.status_code}", flush=True) if resp.history: print(Fore.YELLOW + "🔄 Redirect history:", [r.url for r in resp.history], flush=True) except Exception as e: print(Fore.RED + f"⚠️ Gagal POST claim: {e}", flush=True) return False if "Proxy Detected" in html or "