Spaces:
Sleeping
Sleeping
| import threading | |
| import time | |
| import requests | |
| import json | |
| import random | |
| import logging | |
| import sys | |
| import traceback | |
| from flask import Flask, jsonify, make_response | |
| from bs4 import BeautifulSoup | |
| from FunPayAPI.account import Account | |
| from FunPayAPI.common import enums | |
| try: | |
| from FunPayAPI.updater.runner import Runner | |
| except ImportError: | |
| from FunPayAPI.account import Runner | |
| # ========================================== | |
| # 0. DEBUGGING INFRASTRUCTURE | |
| # ========================================== | |
| LOG_BUFFER = [] | |
| class BufferHandler(logging.Handler): | |
| def emit(self, record): | |
| try: | |
| msg = self.format(record) | |
| LOG_BUFFER.append(f"{time.strftime('%H:%M:%S', time.localtime())} - {msg}") | |
| if len(LOG_BUFFER) > 200: | |
| LOG_BUFFER.pop(0) | |
| except Exception: | |
| pass | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger("FunPayBot") | |
| logger.addHandler(BufferHandler()) | |
| # ========================================== | |
| # 1. KEEP-ALIVE SERVER (Hugging Face) | |
| # ========================================== | |
| app = Flask(__name__) | |
| def home(): | |
| lines = "\n".join(LOG_BUFFER) | |
| # Return HTML for better display | |
| return f""" | |
| <h1>🤖 Bot Status: Running</h1> | |
| <p>Go to <a href="/logs">/logs</a> to see full logs.</p> | |
| <p>Go to <a href="/test_tg">/test_tg</a> to force test Telegram.</p> | |
| <h3>Last 10 Logs:</h3> | |
| <pre>{lines[-1000:]}</pre> | |
| """ | |
| def get_logs(): | |
| return "<pre>" + "\n".join(LOG_BUFFER) + "</pre>" | |
| def test_tg(): | |
| try: | |
| url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage" | |
| payload = {"chat_id": TELEGRAM_CHAT_ID, "text": "TEST MESSAGE FROM WEB. If you see this, connectivity works."} | |
| r = requests.post(url, json=payload, timeout=5) | |
| return f"OK: {r.status_code} {r.text}" | |
| except Exception as e: | |
| return f"ERROR: {e}" | |
| def run_web(): | |
| logger.info("Keep-Alive Web Server started on port 7860") | |
| app.run(host='0.0.0.0', port=7860) | |
| t = threading.Thread(target=run_web) | |
| t.start() | |
| # ========================================== | |
| # 2. PROXY MONKEY PATCH (Cloudflare) | |
| # ========================================== | |
| PROXY_URL = "https://damp-cell-4702.newnout6.workers.dev/" | |
| original_request = requests.Session.request | |
| def proxy_request(self, method, url, *args, **kwargs): | |
| if "funpay.com" in url and PROXY_URL not in url: | |
| target = url | |
| valid_cookies = self.cookies.get_dict(domain=".funpay.com") | |
| valid_cookies.update(self.cookies.get_dict(domain="funpay.com")) | |
| kw_cookies = kwargs.get('cookies') | |
| if kw_cookies: valid_cookies.update(kw_cookies) | |
| headers = kwargs.get('headers', {}) or {} | |
| headers = headers.copy() | |
| found_cookie_key = None | |
| for k in list(headers.keys()): | |
| if k.lower() == 'cookie': | |
| found_cookie_key = k | |
| raw_c = headers[k] | |
| for part in raw_c.split(';'): | |
| if '=' in part: | |
| kv = part.strip().split('=', 1) | |
| if len(kv) == 2: | |
| valid_cookies[kv[0]] = kv[1] | |
| del headers[k] | |
| cookie_header = "; ".join([f"{k}={v}" for k, v in valid_cookies.items()]) | |
| if cookie_header: headers['Cookie'] = cookie_header | |
| if 'User-Agent' not in headers: | |
| headers['User-Agent'] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" | |
| # Ensure RUB/RU locale | |
| if 'cy' not in valid_cookies: valid_cookies['cy'] = 'rub' | |
| if 'locale' not in valid_cookies: valid_cookies['locale'] = 'ru' | |
| params = kwargs.get('params', {}) or {} | |
| params = params.copy() | |
| params['url'] = target | |
| kwargs['headers'] = headers | |
| kwargs['params'] = params | |
| kwargs['cookies'] = {} | |
| return original_request(self, method, PROXY_URL, *args, **kwargs) | |
| return original_request(self, method, url, *args, **kwargs) | |
| requests.Session.request = proxy_request | |
| requests.request = proxy_request | |
| # ========================================== | |
| # 3. BOT LOGIC | |
| # ========================================== | |
| TELEGRAM_BOT_TOKEN = "8203118488:AAGC1EQavBt0u9suYQ32qt4owkU_VuubWG0" | |
| TELEGRAM_CHAT_ID = "1517760699" | |
| API_URL = "http://shrvld789.pythonanywhere.com" | |
| # Default Key | |
| GOLDEN_KEY = "dummy_key_waiting_for_update" | |
| DUMP_CATEGORY_URL = "https://funpay.com/lots/1355/" | |
| MY_USERNAME = "" # Will be filled after login | |
| def send_telegram_notification(message): | |
| try: | |
| url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage" | |
| payload = {"chat_id": TELEGRAM_CHAT_ID, "text": message, "parse_mode": "HTML"} | |
| logger.info(f"Adding to TG queue: {message[:20]}...") | |
| requests.post(url, json=payload, timeout=10) | |
| except Exception as e: | |
| logger.error(f"Failed to send Telegram notification: {e}") | |
| def get_golden_key_from_api(): | |
| try: | |
| url = f"{API_URL}/api/config" | |
| resp = requests.get(url, timeout=10) | |
| if resp.status_code == 200: | |
| config = resp.json() | |
| key = config.get('golden_key') | |
| if key and len(key) > 10: | |
| return key | |
| except Exception as e: | |
| logger.error(f"Failed to fetch key from API: {e}") | |
| return None | |
| def get_product_from_api(quantity, buyer_name, price): | |
| try: | |
| url = f"{API_URL}/api/sell" | |
| payload = {"quantity": quantity, "customer_name": buyer_name, "price": price} | |
| response = requests.post(url, json=payload, timeout=20) | |
| if response.status_code == 200: | |
| data = response.json() | |
| if data.get('success'): | |
| return "\n".join(data.get('accounts', [])) | |
| return None | |
| except Exception as e: | |
| logger.error(f"Error requesting product from API: {e}") | |
| return None | |
| def send_heartbeat(): | |
| """Periodically sends a heartbeat to the Admin Bot""" | |
| while True: | |
| try: | |
| requests.post(f"{API_URL}/api/heartbeat", timeout=5) | |
| except Exception as e: | |
| logger.error(f"Heartbeat failed: {e}") | |
| time.sleep(60) | |
| def dump_price_check(): | |
| """Monitors competitor prices and suggests updates""" | |
| global MY_USERNAME | |
| logger.info("Initializing Price Monitor...") | |
| # Wait for username with attempts | |
| attempts = 0 | |
| while not MY_USERNAME: | |
| attempts += 1 | |
| if attempts % 10 == 0: | |
| logger.warning("Still waiting for MY_USERNAME...") | |
| time.sleep(5) | |
| logger.info(f"Starting Price Monitor for {MY_USERNAME}...") | |
| while True: | |
| try: | |
| wait_time = random.randint(300, 420) | |
| logger.info(f"Checking prices... Next check in {wait_time}s") | |
| # Manually fetch page | |
| session = requests.Session() | |
| headers = { | |
| "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", | |
| "Accept-Language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7" | |
| } | |
| resp = session.get(DUMP_CATEGORY_URL, headers=headers, cookies={'cy': 'rub', 'locale': 'ru'}, timeout=20) | |
| if resp.status_code == 200: | |
| soup = BeautifulSoup(resp.text, "html.parser") | |
| offers = soup.find_all("a", class_="tc-item") | |
| online_competitor_prices = [] | |
| my_current_price = None | |
| for offer in offers: | |
| # Filter 1: Check Online Status | |
| if offer.get("data-online") != "1": | |
| continue | |
| # Filter 2: Check Subscription (Must include "с подпиской") | |
| subscription = offer.get("data-f-subscription", "").lower() | |
| if "подписк" not in subscription or "без" in subscription: | |
| continue | |
| # Filter 3: Check Keywords in Description | |
| desc_div = offer.find("div", class_="tc-desc-text") | |
| desc = desc_div.get_text(strip=True).lower() if desc_div else "" | |
| # Filter: Reviews | |
| rating_div = offer.find("span", class_="rating-mini-count") | |
| if rating_div: | |
| try: | |
| reviews = int(rating_div.get_text(strip=True).replace(' ', '')) | |
| if reviews > 100: continue | |
| except: pass | |
| # Filter: Shared | |
| if "общий" in desc or "shared" in desc or "обще" in desc: continue | |
| # Filter: Plus/Business | |
| has_plus = "plus" in desc or "плюс" in desc | |
| has_business = "business" in desc or "бизнес" in desc | |
| has_month = "month" in desc or "месяц" in desc or "30" in desc | |
| if not ((has_plus or has_business) and has_month): | |
| continue | |
| # Get Price | |
| price_div = offer.find("div", class_="tc-price") | |
| if not price_div: continue | |
| raw_s = price_div.get("data-s") | |
| if not raw_s: continue | |
| price = float(raw_s) | |
| user_div = offer.find("div", class_="media-user-name") | |
| seller = user_div.get_text(strip=True) if user_div else "Unknown" | |
| if seller == MY_USERNAME: | |
| if my_current_price is None or price < my_current_price: | |
| my_current_price = price | |
| else: | |
| online_competitor_prices.append(price) | |
| logger.info(f"Price Check Done. My Price: {my_current_price}, Min Competitor: {min(online_competitor_prices) if online_competitor_prices else 'None'}") | |
| if online_competitor_prices and my_current_price: | |
| min_competitor = min(online_competitor_prices) | |
| target_price = round(max(1.0, min_competitor - 0.01), 2) | |
| if my_current_price > target_price: | |
| send_telegram_notification( | |
| f"📉 <b>Конкурент дешевле!</b>\n\n" | |
| f"👤 Мин. конкурент: {min_competitor:.2f} ₽\n" | |
| f"💰 Твоя цена: {my_current_price:.2f} ₽\n" | |
| f"👉 <b>Рекомендую: {target_price:.2f} ₽</b>" | |
| ) | |
| elif not my_current_price: | |
| logger.warning(f"User {MY_USERNAME} not found on page!") | |
| except Exception as e: | |
| logger.error(f"Dump check failed: {e}") | |
| time.sleep(wait_time) | |
| def main(): | |
| global GOLDEN_KEY, MY_USERNAME | |
| logger.info("Starting FunPay Bot Loop on Hugging Face (DEBUG MODE)...") | |
| # Start Aux Threads | |
| t_heartbeat = threading.Thread(target=send_heartbeat, daemon=True) | |
| t_heartbeat.start() | |
| t_dump = threading.Thread(target=dump_price_check, daemon=True) | |
| t_dump.start() | |
| remote_key = get_golden_key_from_api() | |
| if remote_key: | |
| logger.info(f"✅ Key fetched from API: {remote_key[:5]}...") | |
| GOLDEN_KEY = remote_key | |
| else: | |
| logger.error("❌ Failed to fetch key from API!") | |
| while True: | |
| try: | |
| try: | |
| logger.info(f"Attempting login with key ending in ...{GOLDEN_KEY[-5:]}") | |
| acc = Account(GOLDEN_KEY).get() | |
| runner = Runner(acc) | |
| MY_USERNAME = acc.username | |
| logger.info(f"Logged in successfully: {acc.username}") | |
| send_telegram_notification(f"🤖 <b>FunPay Bot Started!</b>\nUser: {acc.username}\nMonitoring: Active (Smart Filter)") | |
| except Exception as e: | |
| logger.error(f"Login failed: {e}") | |
| logger.error(traceback.format_exc()) # Print full traceback | |
| time.sleep(5) | |
| new_key = get_golden_key_from_api() | |
| if new_key and new_key != GOLDEN_KEY: | |
| GOLDEN_KEY = new_key | |
| continue | |
| send_telegram_notification(f"⚠️ <b>Login Failed!</b>\n{e}") | |
| time.sleep(60) | |
| continue | |
| for event in runner.listen(requests_delay=4): | |
| if event.type == enums.EventTypes.NEW_ORDER: | |
| order = event.order | |
| logger.info(f"New Order Event: {order.id}") | |
| send_telegram_notification(f"💰 Новый заказ!\n{order.buyer_username} - {order.sum} руб.") | |
| product = get_product_from_api(1, order.buyer_username, order.sum) | |
| if product: | |
| try: | |
| chat = acc.get_chat_by_name(order.buyer_username, True) | |
| acc.send_message(chat.id, f"Спасибо за покупку!\n\n{product}") | |
| send_telegram_notification(f"✅ Выдан товар для {order.id}") | |
| except Exception as e: | |
| logger.error(f"Chat error: {e}") | |
| send_telegram_notification(f"⚠️ Ошибка чата: {e}") | |
| else: | |
| logger.error(f"Product not found for order {order.id}") | |
| send_telegram_notification(f"❌ Ошибка выдачи для {order.id}") | |
| except Exception as e: | |
| logger.error(f"Main Loop error: {e}") | |
| logger.error(traceback.format_exc()) | |
| time.sleep(30) | |
| if __name__ == '__main__': | |
| main() | |