import telebot import requests import time import threading import json import os import io

try: import openpyxl from openpyxl.styles import Font, PatternFill, Alignment EXCEL_AVAILABLE = True except ImportError: EXCEL_AVAILABLE = False

====== НАСТРОЙКИ ======

import os from dotenv import load_dotenv load_dotenv()

BOT_TOKEN = os.getenv("8625434679:AAG9ZV61dgqBrVc_d-fNvpd0jSAk0pduSUA") API_KEY = os.getenv("API_KEY") BASE_URL = os.getenv("BASE_URL", "") HEADERS = {"X-API-Key": API_KEY} ADMIN_IDS = [int(x) for x in os.getenv("ADMIN_IDS", "").split(",") if x.strip()] STATS_FILE = "stats.json" HISTORY_FILE = "history.json" MIRRORS_FILE = "mirrors.json" PHONES_FILE = "phones.json" CRYPTO_BOT_TOKEN = os.getenv("CRYPTO_BOT_TOKEN") CRYPTO_BOT_API = "https://pay.crypt.bot/api" CHANNEL_URL = os.getenv("CHANNEL_URL", "") CHANNEL_USERNAME = os.getenv("CHANNEL_USERNAME", "") MIRROR_AUTOPAYOUT_MIN_RUB = float(os.getenv("MIRROR_AUTOPAYOUT_MIN_RUB", "1.0"))

Цены за сервис в долларах

SERVICE_PRICES = { "Сбер ID": 0.03, "ITMS - табачка": 0.13, "Юрент": 0.03, "Яндекс [NEW]": 0.06, "Яндекс [#1]": 0.06, "Яндекс Еда": 0.08, "Яндекс Еда [#3]": 0.08, "Яндекс Еда [#1]": 0.08, "Яндекс Еда [новое]": 0.15, "Теле2": 0.03, "Вкусно и точка": 0.13, "Яндекс Пей": 0.05, "Пятерочка": 0.04, "Пятерочка [#2]": 0.04, "Магнит": 0.04, "Ростикс": 0.04, "ОЗОН": 0.09, "Whoosh": 0.0, "ITMS": 0.13, "Яндекс Еда [#2]": 0.13, }

Whoosh API (отдельный сервис)

WHOOSH_API_URL = os.getenv("WHOOSH_API_URL", "") WHOOSH_API_KEY = os.getenv("WHOOSH_API_KEY", "") WHOOSH_HEADERS = {"X-API-Key": WHOOSH_API_KEY, "Content-Type": "application/json"}

=======================

bot = telebot.TeleBot(BOT_TOKEN) user_state = {} payout_in_progress = set() # UID воркеров у которых сейчас идёт выплата mirror_bots = {} # token -> TeleBot instance mirror_user_states = {} # token -> {uid: state}

====== БАЗА НОМЕРОВ ======

def save_phone_record(uid, username, phone, bot_name, service_name, success=True): """Сохраняем запись о сданном номере""" try: if os.path.exists(PHONES_FILE): with open(PHONES_FILE, "r", encoding="utf-8") as f: phones = json.load(f) else: phones = [] phones.append({ "uid": uid, "username": username, "phone": phone, "bot": bot_name, "service": service_name, "success": success, "ts": int(time.time()), "date": time.strftime("%d.%m.%Y %H:%M") }) with open(PHONES_FILE, "w", encoding="utf-8") as f: json.dump(phones, f, ensure_ascii=False) except Exception as e: print(f"Ошибка записи номера: {e}")

====== СТАТИСТИКА ======

def load_stats(): if os.path.exists(STATS_FILE): with open(STATS_FILE, "r", encoding="utf-8") as f: return json.load(f) return {}

def save_stats(stats): with open(STATS_FILE, "w", encoding="utf-8") as f: json.dump(stats, f, ensure_ascii=False)

def add_stat(uid, username, success=True, service_name=None, earned_rub=0, mirror_token=None): stats = load_stats() key = str(uid) now_ts = int(time.time()) if key not in stats: stats[key] = {"username": username or str(uid), "success": 0, "failed": 0, "services": {}, "earned_rub": 0, "paid_rub": 0, "history_24h": []} stats[key]["username"] = username or stats[key].get("username") or str(uid) if "services" not in stats[key]: stats[key]["services"] = {} if "history_24h" not in stats[key]: stats[key]["history_24h"] = [] # запись в историю 24ч stats[key]["history_24h"].append({"ts": now_ts, "success": success, "service": service_name, "earned": earned_rub}) cutoff = now_ts - 86400 stats[key]["history_24h"] = [e for e in stats[key]["history_24h"] if e.get("ts", 0) >= cutoff] if success: stats[key]["success"] += 1 stats[key]["earned_rub"] = stats[key].get("earned_rub", 0) + earned_rub if service_name: stats[key]["services"][service_name] = stats[key]["services"].get(service_name, 0) + 1 else: stats[key]["failed"] += 1 save_stats(stats) if mirror_token: _add_mirror_worker_stat(mirror_token, uid, username, success, service_name, earned_rub)

def get_stats_24h(uid): stats = load_stats() s = stats.get(str(uid), {}) cutoff = int(time.time()) - 86400 recent = [e for e in s.get("history_24h", []) if e.get("ts", 0) >= cutoff] ok = sum(1 for e in recent if e.get("success")) fail = sum(1 for e in recent if not e.get("success")) earned = sum(e.get("earned", 0) for e in recent if e.get("success")) return ok, fail, earned

def reset_stat(uid): stats = load_stats() key = str(uid) if key in stats: stats[key]["success"] = 0 stats[key]["failed"] = 0 stats[key]["services"] = {} stats[key]["earned_rub"] = 0 save_stats(stats) return True return False

def add_payout_history(uid, username, amount_rub, amount_usdt, breakdown): history = [] if os.path.exists(HISTORY_FILE): with open(HISTORY_FILE, "r", encoding="utf-8") as f: history = json.load(f) history.append({ "uid": str(uid), "username": username, "amount_rub": amount_rub, "amount_usdt": amount_usdt, "breakdown": breakdown, "date": time.strftime("%d.%m.%Y %H:%M") }) with open(HISTORY_FILE, "w", encoding="utf-8") as f: json.dump(history, f, ensure_ascii=False)

def get_payout_history(): if os.path.exists(HISTORY_FILE): with open(HISTORY_FILE, "r", encoding="utf-8") as f: return json.load(f) return []

def notify_admins_payout(uid, username, amount_rub, amount_usdt, source="основной бот"): text = (f"💸 Запрос на вывод\n\n" f"👤 @{username} (ID: {uid})\n" f"💵 {amount_usdt} USDT (≈{amount_rub}$)\n" f"🤖 Источник: {source}\n" f"🕐 {time.strftime('%d.%m.%Y %H:%M')}") for admin_id in ADMIN_IDS: try: bot.send_message(admin_id, text) except Exception: pass

====== ЗЕРКАЛА: ХРАНИЛИЩЕ ======

def load_mirrors(): if os.path.exists(MIRRORS_FILE): with open(MIRRORS_FILE, "r", encoding="utf-8") as f: return json.load(f) return {}

def save_mirrors(mirrors): with open(MIRRORS_FILE, "w", encoding="utf-8") as f: json.dump(mirrors, f, ensure_ascii=False, indent=2)

def get_user_mirrors(uid): mirrors = load_mirrors() return {k: v for k, v in mirrors.items() if v.get("owner_uid") == str(uid)}

def get_mirror_by_token(token): return load_mirrors().get(token)

def create_mirror(owner_uid, owner_username, bot_token, bot_username, prices): mirrors = load_mirrors() mirrors[bot_token] = { "owner_uid": str(owner_uid), "owner_username": owner_username, "bot_token": bot_token, "bot_username": bot_username, "prices": prices, "created_at": time.strftime("%d.%m.%Y %H:%M"), "active": True, "workers": {}, "owner_earned_rub": 0, "owner_paid_rub": 0, "autopayout_enabled": True, "autopayout_min_rub": MIRROR_AUTOPAYOUT_MIN_RUB, } save_mirrors(mirrors) return mirrors[bot_token]

def _add_mirror_worker_stat(mirror_token, uid, username, success, service_name, earned_rub): mirrors = load_mirrors() if mirror_token not in mirrors: return key = str(uid) w = mirrors[mirror_token].setdefault("workers", {}) now_ts = int(time.time()) if key not in w: w[key] = {"username": username or str(uid), "success": 0, "failed": 0, "services": {}, "earned_rub": 0, "history_24h": [], "joined_at": time.strftime("%d.%m.%Y %H:%M")} w[key]["username"] = username or w[key].get("username") or str(uid) w[key].setdefault("history_24h", []).append( {"ts": now_ts, "success": success, "service": service_name, "earned": earned_rub}) cutoff = now_ts - 86400 w[key]["history_24h"] = [e for e in w[key]["history_24h"] if e.get("ts", 0) >= cutoff] if success: w[key]["success"] += 1 w[key]["earned_rub"] = w[key].get("earned_rub", 0) + earned_rub if service_name: w[key]["services"][service_name] = w[key]["services"].get(service_name, 0) + 1 # прибыль владельца зеркала = базовая цена - цена зеркала base_price = SERVICE_PRICES.get(service_name, 0) mirror_price = mirrors[mirror_token].get("prices", {}).get(service_name, 0) owner_profit = base_price - mirror_price if owner_profit > 0: mirrors[mirror_token]["owner_earned_rub"] = mirrors[mirror_token].get("owner_earned_rub", 0) + owner_profit else: w[key]["failed"] += 1 save_mirrors(mirrors) _check_mirror_autopayout(mirror_token)

def _check_mirror_autopayout(mirror_token): mirrors = load_mirrors() m = mirrors.get(mirror_token) if not m or not m.get("autopayout_enabled", True): return threshold = m.get("autopayout_min_rub", MIRROR_AUTOPAYOUT_MIN_RUB) balance = m.get("owner_earned_rub", 0) - m.get("owner_paid_rub", 0) if balance < threshold: return owner_uid = int(m["owner_uid"]) amount_usdt = round(balance, 2) if amount_usdt < 0.01: return result, error = create_check(amount_usdt, owner_uid) if result: check_url = result.get("bot_check_url", "") mirrors = load_mirrors() mirrors[mirror_token]["owner_paid_rub"] = mirrors[mirror_token].get("owner_paid_rub", 0) + balance save_mirrors(mirrors) try: bot.send_message(owner_uid, f"💰 Автовыплата дохода зеркала\n\n" f"🤖 @{m.get('bot_username','?')}\n" f"💵 {amount_usdt} USDT\n\n{check_url}") except Exception: pass notify_admins_payout(owner_uid, m.get("owner_username", str(owner_uid)), balance, amount_usdt, f"автовыплата зеркала @{m.get('bot_username','?')}")

def format_stats_text(s, name=None): total = s["success"] + s["failed"] lines = [] if name: lines.append(f"👤 {name}\n") lines.append(f"✅ Успешных: {s['success']}") services = s.get("services", {}) if services: sorted_services = sorted(services.items(), key=lambda x: x[1], reverse=True) for sname, count in sorted_services: lines.append(f" • {sname} — {count}") lines.append(f"❌ Неудачных: {s['failed']}") lines.append(f"📦 Всего: {total}") return "\n".join(lines)

def calc_payout(services_dict): """Считаем сумму выплаты в рублях""" total = 0 breakdown = [] for sname, count in services_dict.items(): price = SERVICE_PRICES.get(sname, 0) if price > 0 and count > 0: amount = price * count total += amount breakdown.append(f"• {sname}: {count} × {price}$ = {amount}$") return total, breakdown

====== CRYPTO BOT API ======

def get_usdt_rate(): """Цены уже в USD, USDT ≈ USD, возвращаем 1""" return 1

def get_app_balance(): """Получаем баланс приложения""" try: r = requests.get( f"{CRYPTO_BOT_API}/getBalance", headers={"Crypto-Pay-API-Token": CRYPTO_BOT_TOKEN}, timeout=10 ) if r.status_code == 200: data = r.json() if data.get("ok"): for b in data["result"]: if b["currency_code"] == "USDT": return float(b["available"]) except Exception as e: print(f"Ошибка получения баланса: {e}") return None

def create_check(amount_usdt, uid=None): """Создаём чек (без привязки к пользователю — может активировать любой)""" try: payload = { "asset": "USDT", "amount": str(round(amount_usdt, 2)), } r = requests.post( f"{CRYPTO_BOT_API}/createCheck", headers={"Crypto-Pay-API-Token": CRYPTO_BOT_TOKEN}, json=payload, timeout=10 ) if r.status_code == 200: data = r.json() if data.get("ok"): return data["result"], None return None, data.get("error", {}).get("name", "Ошибка") return None, f"HTTP {r.status_code}: {r.text[:200]}" except Exception as e: return None, str(e)

====== WHOOSH API ======

def whoosh_send_sms(phone): """Отправить SMS через Whoosh API""" try: r = requests.post( f"{WHOOSH_API_URL}/api/sms/send", headers=WHOOSH_HEADERS, json={"phone": phone}, timeout=15 ) data = r.json() if r.status_code == 200 and data.get("ok"): return True, data.get("confirm_type", "SMS") if r.status_code == 409: return False, "Номер уже сдан ранее" return False, data.get("error", f"Ошибка {r.status_code}") except Exception as e: return False, str(e)

def whoosh_submit_number(phone, code): """Сдать номер + код через Whoosh API""" try: r = requests.post( f"{WHOOSH_API_URL}/api/number/submit", headers=WHOOSH_HEADERS, json={"phone": phone, "code": code}, timeout=15 ) data = r.json() if r.status_code == 200 and data.get("ok") and data.get("valid"): return True, data.get("credited", 0) if r.status_code == 409: return False, "Номер уже сдан ранее" reason = data.get("reason", data.get("error", "Отклонён")) return False, reason except Exception as e: return False, str(e)

====== API САЙТА ======

def get_services(): services = [] try: r = requests.get(f"{BASE_URL}/worker-api/services", headers=HEADERS, timeout=10) if r.status_code == 200: services = r.json() except Exception as e: print(f"Ошибка получения сервисов: {e}") # Добавляем Whoosh как отдельный сервис services.append({"id": "whoosh_service", "name": "Whoosh"}) return services

def create_activations(phone, service_ids): try: r = requests.post( f"{BASE_URL}/worker-api/activations", headers=HEADERS, json={"phone": phone, "service_ids": service_ids}, timeout=10 ) if r.status_code == 201: return r.json(), None else: detail = r.json().get("detail", "Неизвестная ошибка") return None, detail except Exception as e: return None, str(e)

def get_activation(act_id): try: r = requests.get(f"{BASE_URL}/worker-api/activations/{act_id}", headers=HEADERS, timeout=10) if r.status_code == 200: return r.json() except: pass return None

def send_sms_code(act_id, code): try: r = requests.post( f"{BASE_URL}/worker-api/activations/{act_id}/sms", headers=HEADERS, json={"code": code}, timeout=10 ) if r.status_code == 200: return True, None else: detail = r.json().get("detail", "Неизвестная ошибка") return False, detail except Exception as e: return False, str(e)

def cancel_activation(act_id): try: requests.post(f"{BASE_URL}/worker-api/activations/{act_id}/cancel", headers=HEADERS, timeout=10) except: pass

====== КЛАВИАТУРЫ ======

def main_menu(): markup = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True) markup.add("📲 Новая активация") markup.row("📊 Моя статистика", "💸 Вывести") markup.row("🔗 Создать зеркало", "📢 Канал") markup.add("❌ Отмена") return markup

def admin_menu(): markup = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True) markup.add("📲 Новая активация") markup.row("📊 Моя статистика", "👥 Воркеры") markup.row("📈 Полная статистика", "🏆 Топ воркеров") markup.row("💸 Вывести", "📥 Экспорт CSV") markup.row("💰 Пополнить", "💼 Баланс") markup.row("📜 История выплат", "🔍 Все зеркала") markup.row("💵 Цены", "📣 Рассылка") markup.row("🔗 Создать зеркало", "📢 Канал") markup.add("❌ Отмена") return markup

def services_keyboard(services, selected_ids, custom_prices=None): markup = telebot.types.InlineKeyboardMarkup() for s in services: checked = "✅ " if s["id"] in selected_ids else "☐ " # Показываем цену рядом с названием if custom_prices: price = custom_prices.get(s['name'], 0) else: price = SERVICE_PRICES.get(s['name'], 0) markup.add(telebot.types.InlineKeyboardButton( text=f"{checked}{s['name']} — {price}$", callback_data=f"tog_{s['id']}" )) if selected_ids: markup.add(telebot.types.InlineKeyboardButton( text=f"➡️ Продолжить ({len(selected_ids)} выбрано)", callback_data="confirm_services" )) return markup

def workers_list_keyboard(): stats = load_stats() markup = telebot.types.InlineKeyboardMarkup() if not stats: return markup for uid, s in stats.items(): total = s["success"] + s["failed"] markup.add(telebot.types.InlineKeyboardButton( text=f"👤 {s['username']} | ✅{s['success']} ❌{s['failed']} 📦{total}", callback_data=f"worker_{uid}" )) return markup

def worker_detail_keyboard(uid): markup = telebot.types.InlineKeyboardMarkup() markup.add(telebot.types.InlineKeyboardButton( text="🔄 Сбросить статистику", callback_data=f"doreset_{uid}" )) markup.add(telebot.types.InlineKeyboardButton( text="◀️ Назад к списку", callback_data="back_workers" )) return markup

====== СТАРТ ======

@bot.message_handler(commands=['start']) def start(message): uid = message.from_user.id username = message.from_user.username or message.from_user.first_name or str(uid) user_state.pop(uid, None) stats = load_stats() if str(uid) not in stats: stats[str(uid)] = {"username": username, "success": 0, "failed": 0, "services": {}} save_stats(stats) else: stats[str(uid)]["username"] = username if "services" not in stats[str(uid)]: stats[str(uid)]["services"] = {} save_stats(stats) menu = admin_menu() if uid in ADMIN_IDS else main_menu() bot.send_message( message.chat.id, "👋 Привет! Я бот для регистрации через SMS.\n\nНажми кнопку ниже чтобы начать:", reply_markup=menu )

====== СТАТИСТИКА ======

@bot.message_handler(func=lambda m: m.text == "📊 Моя статистика") def my_stats(message): uid = message.from_user.id stats = load_stats() s = stats.get(str(uid), {"success": 0, "failed": 0, "services": {}, "earned_rub": 0}) ok24, fail24, earned24 = get_stats_24h(uid) rate = get_usdt_rate() total_earned = s.get("earned_rub", 0) total_success = s.get("success", 0) total_failed = s.get("failed", 0) services_text = "" services = s.get("services", {}) if services: sorted_services = sorted(services.items(), key=lambda x: x[1], reverse=True) svc_lines = [f" • {sname} — {count}" for sname, count in sorted_services] services_text = "\n" + "\n".join(svc_lines) text = (f"📊 Твоя статистика\n\n" f"📅 За 24 часа:\n" f" ✅ Успешных: {ok24}\n" f" ❌ Неудачных: {fail24}\n" f" 💵 Заработано: {earned24}$\n\n" f"📆 За всё время:\n" f" ✅ Успешных: {total_success}{services_text}\n" f" ❌ Неудачных: {total_failed}\n" f" 📦 Всего: {total_success + total_failed}\n" f" 💵 Заработано: {total_earned}$") bot.send_message(message.chat.id, text)

====== ПОЛНАЯ СТАТИСТИКА (АДМИН) ======

@bot.message_handler(func=lambda m: m.text == "📈 Полная статистика" and m.from_user.id in ADMIN_IDS) def admin_full_stats(message): stats = load_stats() mirrors = load_mirrors() rate = get_usdt_rate() cutoff = int(time.time()) - 86400 bot_total_success = 0 bot_total_failed = 0 bot_earned_alltime = 0 bot_earned_24h = 0 bot_success_24h = 0 bot_workers_total = len(stats) for uid, s in stats.items(): bot_total_success += s.get("success", 0) bot_total_failed += s.get("failed", 0) bot_earned_alltime += s.get("earned_rub", 0) for e in s.get("history_24h", []): if e.get("ts", 0) >= cutoff: if e.get("success"): bot_earned_24h += e.get("earned", 0) bot_success_24h += 1 total_mirrors = len(mirrors) total_mirror_workers = 0 mirror_earned_alltime = 0 mirror_earned_24h = 0 mirror_success_24h = 0 mirror_total_success = 0 mirror_total_failed = 0 for token, m in mirrors.items(): workers = m.get("workers", {}) total_mirror_workers += len(workers) mirror_earned_alltime += m.get("owner_earned_rub", 0) for wid, w in workers.items(): mirror_total_success += w.get("success", 0) mirror_total_failed += w.get("failed", 0) for e in w.get("history_24h", []): if e.get("ts", 0) >= cutoff: if e.get("success"): mirror_earned_24h += e.get("earned", 0) mirror_success_24h += 1 grand_earned_alltime = bot_earned_alltime + mirror_earned_alltime grand_earned_24h = bot_earned_24h + mirror_earned_24h grand_success = bot_total_success + mirror_total_success text = (f"📈 ПОЛНАЯ СТАТИСТИКА\n" f"{'═' * 25}\n\n" f"🤖 ОСНОВНОЙ БОТ\n" f" 👥 Воркеров: {bot_workers_total}\n" f" 📅 За 24ч: ✅ {bot_success_24h} | 💵 {bot_earned_24h}$\n" f" 📆 Всё время: ✅ {bot_total_success} ❌ {bot_total_failed}\n" f" 💰 Заработано: {bot_earned_alltime}$\n\n" f"🪞 ЗЕРКАЛА (всего: {total_mirrors})\n" f" 👥 Воркеров: {total_mirror_workers}\n" f" 📅 За 24ч: ✅ {mirror_success_24h} | 💵 {mirror_earned_24h}$\n" f" 📆 Всё время: ✅ {mirror_total_success} ❌ {mirror_total_failed}\n" f" 💰 Доход владельцев: {mirror_earned_alltime}$\n\n" f"{'═' * 25}\n" f"💎 ИТОГО\n" f" 📅 За 24ч: 💵 {grand_earned_24h}$\n" f" 📆 Всё время: 💵 {grand_earned_alltime}$\n" f" ✅ Всего активаций: {grand_success}") markup = telebot.types.InlineKeyboardMarkup() for token, m in mirrors.items(): bname = m.get("bot_username", "?") workers = m.get("workers", {}) total_act = sum(w.get("success", 0) for w in workers.values()) prefix = token.split(":")[0] markup.add(telebot.types.InlineKeyboardButton( f"🤖 @{bname} | 👥{len(workers)} | ✅{total_act}", callback_data=f"mstat_{prefix}")) bot.send_message(message.chat.id, text, reply_markup=markup)

@bot.callback_query_handler(func=lambda call: call.data.startswith("mstat_")) def admin_mirror_detail(call): try: bot.answer_callback_query(call.id) except Exception: pass prefix = call.data[6:] mirrors = load_mirrors() rate = get_usdt_rate() cutoff = int(time.time()) - 86400 mirror_data = None for token, m in mirrors.items(): if token.startswith(prefix): mirror_data = m break if not mirror_data: bot.send_message(call.message.chat.id, "❌ Зеркало не найдено.") return bname = mirror_data.get("bot_username", "?") owner_uid = mirror_data.get("owner_uid", "?") owner_username = mirror_data.get("owner_username", "?") workers = mirror_data.get("workers", {}) earned_alltime = mirror_data.get("owner_earned_rub", 0) paid = mirror_data.get("owner_paid_rub", 0) balance = earned_alltime - paid created = mirror_data.get("created_at", "?") ap = "ВКЛ ✅" if mirror_data.get("autopayout_enabled", True) else "ВЫКЛ ❌" threshold = mirror_data.get("autopayout_min_rub", MIRROR_AUTOPAYOUT_MIN_RUB) prices = mirror_data.get("prices", {}) prices_text = "\n".join([f" • {k}: {v}$" for k, v in prices.items() if v > 0]) total_success = 0 total_failed = 0 success_24h = 0 earned_24h = 0 worker_lines = [] for wid, w in workers.items(): w_success = w.get("success", 0) w_failed = w.get("failed", 0) w_earned = w.get("earned_rub", 0) total_success += w_success total_failed += w_failed w_ok24 = 0 w_earned24 = 0 for e in w.get("history_24h", []): if e.get("ts", 0) >= cutoff: if e.get("success"): w_ok24 += 1 w_earned24 += e.get("earned", 0) success_24h += 1 earned_24h += e.get("earned", 0) worker_lines.append( f" 👤 {w.get('username', wid)}\n" f" 24ч: ✅{w_ok24} 💵{w_earned24}$\n" f" Всего: ✅{w_success} ❌{w_failed} 💵{w_earned}$") workers_text = "\n".join(worker_lines) if worker_lines else " Нет воркеров" text = (f"🤖 ЗЕРКАЛО @{bname}\n" f"{'═' * 25}\n\n" f"👤 Владелец: @{owner_username} (ID: {owner_uid})\n" f"📅 Создано: {created}\n" f"🔔 Автовыплата: {ap} (порог {threshold}$)\n\n" f"💰 ДОХОД ВЛАДЕЛЬЦА\n" f" 📅 За 24ч: {earned_24h}$\n" f" 📆 Всё время: {earned_alltime}$\n" f" 💳 Выплачено: {paid}$\n" f" 💰 Баланс: {balance}$\n\n" f"📦 АКТИВАЦИИ\n" f" 📅 За 24ч: ✅ {success_24h}\n" f" 📆 Всё время: ✅ {total_success} ❌ {total_failed}\n\n" f"💵 ЦЕНЫ\n{prices_text}\n\n" f"👥 ВОРКЕРЫ ({len(workers)})\n{workers_text}") bot.send_message(call.message.chat.id, text)

====== ВЫПЛАТА ======

@bot.message_handler(func=lambda m: m.text == "💸 Вывести") def request_payout(message): uid = message.from_user.id

# Защита от повторного вывода
if uid in payout_in_progress:
    bot.send_message(message.chat.id, "⏳ Выплата уже в процессе. Подожди завершения.")
    return

stats = load_stats()
s = stats.get(str(uid), {"success": 0, "failed": 0, "services": {}})
services_done = s.get("services", {})

if not services_done:
    bot.send_message(message.chat.id, "💸 У тебя пока нет успешных активаций для выплаты.")
    return

total_rub, breakdown = calc_payout(services_done)

if total_rub <= 0:
    bot.send_message(message.chat.id, "💸 Сумма выплаты равна 0.")
    return

# Цены уже в долларах — используем напрямую
amount_usdt = round(total_rub, 2)

text = (
    f"💸 Расчёт выплаты:\n\n"
    + "\n".join(breakdown) +
    f"\n\n💵 К выплате: {amount_usdt} USDT"
)

markup = telebot.types.InlineKeyboardMarkup()
markup.add(telebot.types.InlineKeyboardButton(
    text=f"✅ Получить {amount_usdt} USDT",
    callback_data=f"dopayout_{total_rub}_{amount_usdt}"
))
markup.add(telebot.types.InlineKeyboardButton(
    text="❌ Отмена",
    callback_data="cancelpayout"
))

bot.send_message(message.chat.id, text, reply_markup=markup)

@bot.callback_query_handler(func=lambda call: call.data.startswith("dopayout_")) def do_payout(call): uid = call.from_user.id

try:
    bot.answer_callback_query(call.id)
except Exception:
    pass

# Защита от повторного нажатия
if uid in payout_in_progress:
    bot.edit_message_text(
        "⏳ Выплата уже в процессе. Подожди.",
        chat_id=call.message.chat.id,
        message_id=call.message.message_id)
    return

payout_in_progress.add(uid)

# Пересчитываем сумму из ТЕКУЩИХ сервисов (а не из кнопки!)
stats = load_stats()
s = stats.get(str(uid), {"success": 0, "failed": 0, "services": {}})
services_done = s.get("services", {})

if not services_done or s.get("success", 0) == 0:
    payout_in_progress.discard(uid)
    bot.edit_message_text(
        "❌ Нет активаций для выплаты.",
        chat_id=call.message.chat.id,
        message_id=call.message.message_id)
    return

total_rub, breakdown = calc_payout(services_done)
amount_usdt = round(total_rub, 2)

if amount_usdt <= 0:
    payout_in_progress.discard(uid)
    bot.edit_message_text(
        "❌ Сумма выплаты равна 0.",
        chat_id=call.message.chat.id,
        message_id=call.message.message_id)
    return

bot.edit_message_text(
    "⏳ Отправляю выплату...",
    chat_id=call.message.chat.id,
    message_id=call.message.message_id
)

result, error = create_check(amount_usdt, uid)

if result:
    check_url = result.get("bot_check_url", "")
    # Сначала сбрасываем стату, потом отправляем
    reset_stat(uid)
    payout_in_progress.discard(uid)
    bot.send_message(
        call.message.chat.id,
        f"✅ Чек создан!\n💵 {amount_usdt} USDT\n\n"
        f"Нажми на ссылку чтобы получить деньги:\n{check_url}",
        reply_markup=admin_menu() if uid in ADMIN_IDS else main_menu()
    )
    uname = s.get("username", str(uid))
    add_payout_history(uid, uname, total_rub, amount_usdt, breakdown)
    if uid not in ADMIN_IDS:
        notify_admins_payout(uid, uname, total_rub, amount_usdt, "основной бот")
else:
    payout_in_progress.discard(uid)
    bot.send_message(
        call.message.chat.id,
        f"❌ Ошибка выплаты: {error}\n\nОбратись к администратору.",
        reply_markup=admin_menu() if uid in ADMIN_IDS else main_menu()
    )

@bot.callback_query_handler(func=lambda call: call.data == "cancelpayout") def cancel_payout(call): bot.edit_message_text( "❌ Выплата отменена.", chat_id=call.message.chat.id, message_id=call.message.message_id ) try:

    bot.answer_callback_query(call.id)

except Exception:

    pass

====== АДМИН: ВОРКЕРЫ ======

@bot.message_handler(func=lambda m: m.text == "👥 Воркеры") def all_workers(message): if message.from_user.id not in ADMIN_IDS: return stats = load_stats() if not stats: bot.send_message(message.chat.id, "👥 Воркеров пока нет.") return bot.send_message( message.chat.id, "👥 Список воркеров:\nНажми на воркера чтобы открыть его кабинет.", reply_markup=workers_list_keyboard() )

@bot.callback_query_handler(func=lambda call: call.data.startswith("worker_")) def worker_detail(call): if call.from_user.id not in ADMIN_IDS: try:

        bot.answer_callback_query(call.id, "Нет доступа")

    except Exception:

        pass
    return
uid = call.data[7:]
stats = load_stats()
s = stats.get(uid)
if not s:
    try:

        bot.answer_callback_query(call.id, "Воркер не найден")

    except Exception:

        pass
    return
services_done = s.get("services", {})
total_rub, breakdown = calc_payout(services_done)
amount_usdt = round(total_rub, 2)

text = (
    f"📊 Кабинет воркера:\n\n"
    f"{format_stats_text(s, s['username'])}\n\n"
    f"💵 К выплате: {amount_usdt} USDT"
)
bot.edit_message_text(
    text,
    chat_id=call.message.chat.id,
    message_id=call.message.message_id,
    reply_markup=worker_detail_keyboard(uid)
)
try:

    bot.answer_callback_query(call.id)

except Exception:

    pass

@bot.callback_query_handler(func=lambda call: call.data.startswith("doreset_")) def do_reset(call): if call.from_user.id not in ADMIN_IDS: try:

        bot.answer_callback_query(call.id, "Нет доступа")

    except Exception:

        pass
    return
uid = call.data[8:]
stats = load_stats()
s = stats.get(uid)
if not s:
    try:

        bot.answer_callback_query(call.id, "Воркер не найден")

    except Exception:

        pass
    return
username = s["username"]
reset_stat(uid)
try:

    bot.answer_callback_query(call.id, f"✅ Статистика {username} сброшена!")

except Exception:

    pass
stats = load_stats()
s = stats.get(uid, {"username": username, "success": 0, "failed": 0, "services": {}})
text = f"📊 Кабинет воркера:\n\n{format_stats_text(s, username)}\n\n🔄 Статистика сброшена!"
bot.edit_message_text(
    text,
    chat_id=call.message.chat.id,
    message_id=call.message.message_id,
    reply_markup=worker_detail_keyboard(uid)
)

@bot.callback_query_handler(func=lambda call: call.data == "back_workers") def back_to_workers(call): if call.from_user.id not in ADMIN_IDS: try:

        bot.answer_callback_query(call.id)

    except Exception:

        pass
    return
stats = load_stats()
if not stats:
    bot.edit_message_text("👥 Воркеров пока нет.", chat_id=call.message.chat.id, message_id=call.message.message_id)
    return
bot.edit_message_text(
    "👥 Список воркеров:\nНажми на воркера чтобы открыть его кабинет.",
    chat_id=call.message.chat.id,
    message_id=call.message.message_id,
    reply_markup=workers_list_keyboard()
)
try:

    bot.answer_callback_query(call.id)

except Exception:

    pass

====== АКТИВАЦИИ ======

@bot.message_handler(func=lambda m: m.text == "📲 Новая активация") def new_activation(message): uid = message.from_user.id services = get_services() if not services: bot.send_message(message.chat.id, "❌ Не удалось получить список сервисов. Попробуй позже.") return user_state[uid] = {"step": "choose_services", "services": services, "selected_ids": []} bot.send_message( message.chat.id, "📋 Выбери сервисы (можно несколько), затем нажми Продолжить:", reply_markup=services_keyboard(services, []) )

@bot.callback_query_handler(func=lambda call: call.data.startswith("tog_")) def toggle_service(call): uid = call.from_user.id service_id = call.data[4:] state = user_state.get(uid, {}) if state.get("step") != "choose_services": try:

        bot.answer_callback_query(call.id)

    except Exception:

        pass
    return
selected = state.get("selected_ids", [])
if service_id in selected:
    selected.remove(service_id)
else:
    selected.append(service_id)
user_state[uid]["selected_ids"] = selected
bot.edit_message_reply_markup(
    chat_id=call.message.chat.id,
    message_id=call.message.message_id,
    reply_markup=services_keyboard(state["services"], selected)
)
try:

    bot.answer_callback_query(call.id)

except Exception:

    pass

@bot.callback_query_handler(func=lambda call: call.data == "confirm_services") def confirm_services(call): uid = call.from_user.id state = user_state.get(uid, {}) selected = state.get("selected_ids", []) if not selected: try:

        bot.answer_callback_query(call.id, "Выбери хотя бы один сервис!")

    except Exception:

        pass
    return
services = state["services"]
names = [s["name"] for s in services if s["id"] in selected]
user_state[uid]["step"] = "enter_phone"
bot.edit_message_text(
    f"✅ Выбрано сервисов: {len(selected)}\n" +
    "\n".join(f"• {n}" for n in names) +
    "\n\nВведи номер телефона в формате +79001234567:",
    chat_id=call.message.chat.id,
    message_id=call.message.message_id)
try:

    bot.answer_callback_query(call.id)

except Exception:

    pass

@bot.message_handler(func=lambda m: user_state.get(m.from_user.id, {}).get("step") == "enter_phone") def enter_phone(message): uid = message.from_user.id username = message.from_user.username or message.from_user.first_name or str(uid) phone = message.text.strip() clean = phone.replace("+", "").replace(" ", "").replace("-", "").replace("(", "").replace(")", "") if not clean.isdigit() or len(clean) < 10: bot.send_message(message.chat.id, "❌ Неверный формат. Введи номер, например: +79001234567") return

# Добавляем + если его нет
# Поддерживаем 7 и 8 (старый формат)
if clean.startswith("8"):
    # 8XXXXXXXXXX → 79XXXXXXXXXX → +79XXXXXXXXXX
    clean = "7" + clean[1:]

if not clean.startswith("7"):
    bot.send_message(message.chat.id, "❌ Номер должен быть для России (начинаться на 7 или 8)")
    return

formatted_phone = "+" + clean

state = user_state[uid]
selected_ids = state["selected_ids"]
services = state["services"]

msg = bot.send_message(
    message.chat.id,
    f"⏳ Создаю активации...\n📱 Номер: `{formatted_phone}`\n🔧 Сервисов: {len(selected_ids)}")

activations, error = create_activations(formatted_phone, selected_ids)

if not activations:
    bot.edit_message_text(
        f"❌ Ошибка: {error}",
        chat_id=message.chat.id,
        message_id=msg.message_id
    )
    user_state.pop(uid, None)
    return

act_map = {}
for act in activations:
    sid = act["service_id"]
    svc = next((s for s in services if s["id"] == sid), None)
    sname = svc["name"] if svc else sid
    code_len = svc.get("sms_code_length", 6) if svc else 6
    act_map[act["id"]] = {
        "name": sname,
        "status": "pending",
        "code": None,
        "sms_code_length": code_len,
        "waiting_input": False
    }

user_state[uid] = {
    "step": "waiting_sms",
    "phone": phone,
    "username": username,
    "act_map": act_map,
    "pending_input": {}
}

status_msg = bot.edit_message_text(
    build_status_text(phone, act_map),
    chat_id=message.chat.id,
    message_id=msg.message_id)

t = threading.Thread(
    target=poll_all_activations,
    args=(message.chat.id, uid, status_msg.message_id),
    daemon=True
)
t.start()

def build_status_text(phone, act_map): lines = [f"📱 Номер: {phone}\n"] for act_id, info in act_map.items(): status = info["status"] code = info["code"] waiting = info.get("waiting_input", False) if waiting: icon, label = "⌨️", f"введи код ({info['sms_code_length']} цифр)" elif status == "pending": icon, label = "⏳", "ожидание" elif status == "number_taken": icon, label = "📡", "номер взят, ожидаю СМС" elif status == "sms_sent": icon, label = "📩", f"код принят: {code}" if code else "код принят" elif status == "code_ready": icon, label = "🔄", "регистрация..." elif status == "completed": icon, label = "✅", f"готово! код: {code}" if code else "готово!" elif status == "failed": icon, label = "❌", "ошибка" elif status == "cancelled": icon, label = "🚫", "отменено" else: icon, label = "🔄", status lines.append(f"{icon} {info['name']} — {label}") return "\n".join(lines)

def poll_all_activations(chat_id, uid, status_msg_id): max_wait = 300 elapsed = 0 username = user_state.get(uid, {}).get("username", str(uid))

while elapsed < max_wait:
    state = user_state.get(uid, {})
    if state.get("step") != "waiting_sms":
        return

    act_map = state["act_map"]
    all_done = True

    for act_id in list(act_map.keys()):
        info = act_map[act_id]
        if info["status"] in ("completed", "failed", "cancelled"):
            continue
        if info.get("waiting_input"):
            all_done = False
            continue

        all_done = False
        data = get_activation(act_id)
        if not data:
            continue

        old_status = info["status"]
        new_status = data["status"]
        api_code = data.get("sms_code")

        act_map[act_id]["status"] = new_status
        if api_code:
            act_map[act_id]["code"] = api_code

        if old_status != "sms_sent" and new_status == "sms_sent":
            act_map[act_id]["waiting_input"] = True
            bot.send_message(
                chat_id,
                f"📩 {info['name']} — пришла СМС!\n\n"
                f"Введи код ({info['sms_code_length']} цифр) для *{info['name']}*:")
            state["pending_input"][act_id] = True

        if old_status != new_status:
            if new_status == "completed":
                price = SERVICE_PRICES.get(info["name"], 0)
                add_stat(uid, username, success=True, service_name=info["name"], earned_rub=price)
                save_phone_record(uid, username, state.get("phone", "?"), "основной", info["name"], success=True)
            elif new_status == "failed":
                add_stat(uid, username, success=False)
                save_phone_record(uid, username, state.get("phone", "?"), "основной", info["name"], success=False)

    phone = state["phone"]
    try:
        bot.edit_message_text(
            build_status_text(phone, act_map),
            chat_id=chat_id,
            message_id=status_msg_id)
    except:
        pass

    if all_done:
        menu = admin_menu() if uid in ADMIN_IDS else main_menu()
        bot.send_message(chat_id, "✅ Все активации завершены!", reply_markup=menu)
        user_state.pop(uid, None)
        return

    time.sleep(3)
    elapsed += 3

act_map = user_state.get(uid, {}).get("act_map", {})
for act_id, info in act_map.items():
    if info["status"] not in ("completed", "failed", "cancelled"):
        cancel_activation(act_id)
        add_stat(uid, username, success=False)

menu = admin_menu() if uid in ADMIN_IDS else main_menu()
bot.send_message(chat_id, "⏰ Время ожидания истекло. Активации отменены.", reply_markup=menu)
user_state.pop(uid, None)

====== ВВОД КОДА ======

@bot.message_handler(func=lambda m: ( user_state.get(m.from_user.id, {}).get("step") == "waiting_sms" and bool(user_state.get(m.from_user.id, {}).get("pending_input")) and m.text not in ["📲 Новая активация", "📊 Моя статистика", "👥 Воркеры", "💸 Вывести", "❌ Отмена", "🔗 Создать зеркало", "📢 Канал", "🏆 Топ воркеров", "📥 Экспорт CSV", "💰 Пополнить", "💼 Баланс", "📜 История выплат", "🔍 Все зеркала", "📈 Полная статистика", "📣 Рассылка", "💵 Цены"] )) def handle_sms_code_input(message): uid = message.from_user.id code = message.text.strip() state = user_state.get(uid, {}) act_map = state.get("act_map", {}) pending = state.get("pending_input", {})

if not code.isdigit():
    bot.send_message(message.chat.id, "❌ Код должен состоять только из цифр. Попробуй ещё раз.")
    return

target_id = None
for act_id in pending:
    if act_map.get(act_id, {}).get("waiting_input"):
        target_id = act_id
        break

if not target_id:
    return

info = act_map[target_id]
expected_len = info["sms_code_length"]

if len(code) != expected_len:
    bot.send_message(message.chat.id, f"❌ Код должен быть {expected_len} цифр. Попробуй ещё раз.")
    return

ok, error = send_sms_code(target_id, code)

if ok:
    act_map[target_id]["waiting_input"] = False
    act_map[target_id]["code"] = code
    act_map[target_id]["status"] = "code_ready"
    pending.pop(target_id, None)
    bot.send_message(message.chat.id, f"✅ Код для {info['name']} принят! Ожидаю завершения регистрации...")
else:
    bot.send_message(message.chat.id, f"❌ Ошибка отправки кода: {error}\nПопробуй ввести снова.")

====== БАЛАНС ======

@bot.message_handler(func=lambda m: m.text == "💼 Баланс" and m.from_user.id in ADMIN_IDS) def show_balance(message): balance = get_app_balance() if balance is None: bot.send_message(message.chat.id, "❌ Не удалось получить баланс.") return rate = get_usdt_rate() balance_rub = round(balance * rate, 2) bot.send_message( message.chat.id, f"💼 Баланс приложения:\n\n💵 {balance} USDT\n💰 ≈ {balance_rub}$")

====== ПОПОЛНЕНИЕ БАЛАНСА ======

@bot.message_handler(func=lambda m: m.text == "💰 Пополнить" and m.from_user.id in ADMIN_IDS) def topup_balance(message): markup = telebot.types.InlineKeyboardMarkup() amounts = [1, 2, 5, 10, 20] for a in amounts: markup.add(telebot.types.InlineKeyboardButton( text=f"💵 {a} USDT", callback_data=f"topup_{a}" )) bot.send_message(message.chat.id, "💰 Выбери сумму пополнения:", reply_markup=markup)

@bot.callback_query_handler(func=lambda call: call.data.startswith("topup_")) def do_topup(call): if call.from_user.id not in ADMIN_IDS: try:

        bot.answer_callback_query(call.id, "Нет доступа")

    except Exception:

        pass
    return
amount = call.data[6:]
try:
    r = requests.post(
        f"{CRYPTO_BOT_API}/createInvoice",
        headers={"Crypto-Pay-API-Token": CRYPTO_BOT_TOKEN},
        json={"asset": "USDT", "amount": amount},
        timeout=10
    )
    data = r.json()
    if data.get("ok"):
        url = data["result"]["bot_invoice_url"]
        markup = telebot.types.InlineKeyboardMarkup()
        markup.add(telebot.types.InlineKeyboardButton(
            text=f"💳 Оплатить {amount} USDT",
            url=url
        ))
        bot.edit_message_text(
            f"💰 Инвойс на {amount} USDT создан!\n\nНажми кнопку ниже чтобы оплатить — деньги попадут на баланс приложения.",
            chat_id=call.message.chat.id,
            message_id=call.message.message_id,
            reply_markup=markup
        )
    else:
        error = data.get("error", {}).get("name", "Ошибка")
        bot.edit_message_text(f"❌ Ошибка: {error}", chat_id=call.message.chat.id, message_id=call.message.message_id)
except Exception as e:
    bot.edit_message_text(f"❌ Ошибка: {e}", chat_id=call.message.chat.id, message_id=call.message.message_id)
try:

    bot.answer_callback_query(call.id)

except Exception:

    pass

====== ИСТОРИЯ ВЫПЛАТ ======

@bot.message_handler(func=lambda m: m.text == "📜 История выплат" and m.from_user.id in ADMIN_IDS) def payout_history(message): history = get_payout_history() if not history: bot.send_message(message.chat.id, "📜 История выплат пуста.") return # Показываем последние 20 записей lines = ["📜 История выплат:"] lines.append("") for h in reversed(history[-20:]): lines.append("👤 " + h["username"] + " | " + h["date"]) lines.append("💵 " + str(h["amount_usdt"]) + " USDT (≈" + str(h["amount_rub"]) + "$)") lines.append("") bot.send_message(message.chat.id, chr(10).join(lines))

====== ОТМЕНА ======

@bot.message_handler(commands=['cancel']) @bot.message_handler(func=lambda m: m.text == "❌ Отмена") def cancel(message): uid = message.from_user.id state = user_state.get(uid, {}) act_map = state.get("act_map", {}) for act_id, info in act_map.items(): if info["status"] not in ("completed", "failed", "cancelled"): cancel_activation(act_id) menu = admin_menu() if uid in ADMIN_IDS else main_menu() bot.send_message(message.chat.id, "✅ Отменено.", reply_markup=menu) user_state.pop(uid, None)

@bot.message_handler(func=lambda m: m.text == "🏆 Топ воркеров" and m.from_user.id in ADMIN_IDS) def top_workers(message): stats = load_stats() if not stats: bot.send_message(message.chat.id, "👥 Воркеров пока нет.") return rate = get_usdt_rate() medals = ["🥇", "🥈", "🥉"] + [f"{i}." for i in range(4, 11)] by_act = sorted(stats.items(), key=lambda x: x[1].get("success", 0), reverse=True)[:10] lines = ["🏆 Топ по активациям:\n"] for i, (uid, s) in enumerate(by_act): if s.get("success", 0) <= 0: continue lines.append(f"{medals[i]} {s.get('username', uid)} — {s.get('success', 0)} ✅") by_earn = sorted(stats.items(), key=lambda x: x[1].get("earned_rub", 0), reverse=True)[:10] lines.append("\n💵 Топ по заработку:\n") for i, (uid, s) in enumerate(by_earn): earned = s.get("earned_rub", 0) if earned <= 0: continue lines.append(f"{medals[i]} {s.get('username', uid)} — {earned}$") bot.send_message(message.chat.id, "\n".join(lines))

@bot.message_handler(func=lambda m: m.text == "📥 Экспорт CSV" and m.from_user.id in ADMIN_IDS) def export_csv(message): stats = load_stats() if not stats: bot.send_message(message.chat.id, "📊 Нет данных для экспорта.") return rate = get_usdt_rate() markup = telebot.types.InlineKeyboardMarkup() markup.add(telebot.types.InlineKeyboardButton("📄 CSV", callback_data="export_csv")) if EXCEL_AVAILABLE: markup.add(telebot.types.InlineKeyboardButton("📊 Excel (XLSX)", callback_data="export_xlsx")) bot.send_message(message.chat.id, "Выбери формат экспорта:", reply_markup=markup)

@bot.callback_query_handler(func=lambda call: call.data == "export_csv") def export_csv_file(call): try: bot.answer_callback_query(call.id) except Exception: pass stats = load_stats() rate = get_usdt_rate() lines = ["Воркер,Успешных,Неудачных,Заработано_руб,Заработано_USDT,Выведено_руб"] for uid, s in stats.items(): earned = s.get("earned_rub", 0) paid = s.get("paid_rub", 0) lines.append(f"@{s.get('username', uid)},{s.get('success', 0)},{s.get('failed', 0)}," f"{earned},{round(earned/rate, 2)},{paid}") csv_bytes = "\n".join(lines).encode("utf-8-sig") filename = f"stats_{time.strftime('%d%m%Y_%H%M')}.csv" bot.send_document(call.message.chat.id, (filename, io.BytesIO(csv_bytes), "text/csv"))

@bot.callback_query_handler(func=lambda call: call.data == "export_xlsx") def export_xlsx_file(call): try: bot.answer_callback_query(call.id) except Exception: pass if not EXCEL_AVAILABLE: bot.send_message(call.message.chat.id, "❌ Excel недоступен на сервере.") return stats = load_stats() rate = get_usdt_rate() wb = openpyxl.Workbook() ws = wb.active ws.title = "Статистика" headers = ["Воркер", "Успешных", "Неудачных", "Заработано $", "Заработано USDT", "Выведено $"] ws.append(headers) header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid") for cell in ws[1]: cell.font = Font(bold=True, color="FFFFFF") cell.fill = header_fill cell.alignment = Alignment(horizontal="center") for uid, s in stats.items(): earned = s.get("earned_rub", 0) ws.append([f"@{s.get('username', uid)}", s.get("success", 0), s.get("failed", 0), earned, round(earned/rate, 2), s.get("paid_rub", 0)]) bio = io.BytesIO() wb.save(bio) bio.seek(0) filename = f"stats_{time.strftime('%d%m%Y_%H%M')}.xlsx" bot.send_document(call.message.chat.id, (filename, bio, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))

====== КАНАЛ ======

@bot.message_handler(func=lambda m: m.text == "📢 Канал") def channel_button(message): markup = telebot.types.InlineKeyboardMarkup() markup.add(telebot.types.InlineKeyboardButton("📢 Перейти в канал", url=CHANNEL_URL)) bot.send_message(message.chat.id, "📢 Наш канал\n\nНовости, обновления и важные объявления:", reply_markup=markup)

====== УПРАВЛЕНИЕ ЦЕНАМИ (АДМИН) ======

@bot.message_handler(func=lambda m: m.text == "💵 Цены" and m.from_user.id in ADMIN_IDS) def admin_prices(message): lines = ["💵 Текущие цены основного бота:\n"] markup = telebot.types.InlineKeyboardMarkup() for sname, price in SERVICE_PRICES.items(): lines.append(f" • {sname} — {price}$") markup.add(telebot.types.InlineKeyboardButton( f"✏️ {sname} ({price}$)", callback_data=f"editprice_{sname}")) bot.send_message(message.chat.id, "\n".join(lines), reply_markup=markup)

@bot.callback_query_handler(func=lambda call: call.data.startswith("editprice_")) def edit_price_start(call): try: bot.answer_callback_query(call.id) except Exception: pass uid = call.from_user.id if uid not in ADMIN_IDS: return sname = call.data[10:] current = SERVICE_PRICES.get(sname, 0) user_state[uid] = {"step": "edit_base_price", "service": sname} bot.send_message(call.message.chat.id, f"✏️ Изменение цены: {sname}\n" f"Текущая цена: {current}$\n\n" f"Введи новую цену (в рублях):")

@bot.message_handler(func=lambda m: user_state.get(m.from_user.id, {}).get("step") == "edit_base_price") def edit_price_apply(message): uid = message.from_user.id state = user_state.get(uid, {}) sname = state.get("service", "") text = message.text.strip() if text == "❌ Отмена": user_state.pop(uid, None) bot.send_message(message.chat.id, "✅ Отменено.", reply_markup=admin_menu()) return try: new_price = float(text) if new_price < 0: bot.send_message(message.chat.id, "❌ Цена не может быть отрицательной. Попробуй снова:") return except ValueError: bot.send_message(message.chat.id, "❌ Введи число (например 0.15). Попробуй снова:") return user_state.pop(uid, None) old_price = SERVICE_PRICES.get(sname, 0) SERVICE_PRICES[sname] = new_price

# Спрашиваем уведомить ли воркеров
markup = telebot.types.InlineKeyboardMarkup()
markup.add(telebot.types.InlineKeyboardButton(
    "📣 Да, уведомить", callback_data=f"notify_yes_{sname}_{old_price}_{new_price}"))
markup.add(telebot.types.InlineKeyboardButton(
    "🔇 Нет, без уведомления", callback_data=f"notify_no"))

bot.send_message(message.chat.id,
                 f"✅ Цена обновлена!\n\n"
                 f"  {sname}: {old_price}$ → {new_price}$\n\n"
                 f"Уведомить воркеров об изменении?",
                 reply_markup=markup)

@bot.callback_query_handler(func=lambda call: call.data.startswith("notify_yes_")) def notify_workers_yes(call): try: bot.answer_callback_query(call.id) except Exception: pass uid = call.from_user.id if uid not in ADMIN_IDS: return parts = call.data[11:].rsplit("_", 2) sname = parts[0] old_price = parts[1] new_price = parts[2]

stats = load_stats()
notification = (f"💲💲💲 ИЗМЕНЕНИЕ ЦЕНЫ 💲💲💲\n\n"
                f"📌 {sname}\n"
                f"💰 Было: {old_price}$\n"
                f"💵 Стало: {new_price}$\n\n"
                f"💲 Новая цена действует прямо сейчас!")
sent = 0
for worker_uid in stats.keys():
    try:
        bot.send_message(int(worker_uid), notification)
        sent += 1
    except Exception:
        pass
bot.send_message(call.message.chat.id,
                 f"📣 Уведомление отправлено {sent} воркерам!",
                 reply_markup=admin_menu())

@bot.callback_query_handler(func=lambda call: call.data == "notify_no") def notify_workers_no(call): try: bot.answer_callback_query(call.id) except Exception: pass bot.send_message(call.message.chat.id, "🔇 Без уведомления.", reply_markup=admin_menu())

====== РАССЫЛКА (АДМИН) ======

@bot.message_handler(func=lambda m: m.text == "📣 Рассылка" and m.from_user.id in ADMIN_IDS) def admin_broadcast(message): uid = message.from_user.id # Считаем воркеров основного бота (не в зеркалах) stats = load_stats() mirrors = load_mirrors() mirror_worker_ids = set() for token, m in mirrors.items(): for wid in m.get("workers", {}).keys(): mirror_worker_ids.add(str(wid)) main_workers = [uid for uid in stats.keys() if uid not in mirror_worker_ids] user_state[uid] = {"step": "broadcast_text"} bot.send_message(message.chat.id, f"📣 Рассылка для воркеров ОСНОВНОГО бота\n\n" f"👥 Получателей: {len(main_workers)}\n\n" f"Введи текст рассылки:\n" f"(или нажми ❌ Отмена)")

@bot.message_handler(func=lambda m: user_state.get(m.from_user.id, {}).get("step") == "broadcast_text") def admin_broadcast_send(message): uid = message.from_user.id text = message.text.strip() if text == "❌ Отмена": user_state.pop(uid, None) bot.send_message(message.chat.id, "✅ Отменено.", reply_markup=admin_menu()) return user_state.pop(uid, None) stats = load_stats() mirrors = load_mirrors() mirror_worker_ids = set() for token, m in mirrors.items(): for wid in m.get("workers", {}).keys(): mirror_worker_ids.add(str(wid)) sent = 0 failed = 0 for worker_uid in stats.keys(): if worker_uid in mirror_worker_ids: continue try: bot.send_message(int(worker_uid), f"📣 Объявление от админа:\n\n{text}") sent += 1 except Exception: failed += 1 bot.send_message(message.chat.id, f"📣 Рассылка завершена!\n\n" f"✅ Доставлено: {sent}\n" f"❌ Не доставлено: {failed}", reply_markup=admin_menu())

====== СОЗДАНИЕ ЗЕРКАЛА ======

@bot.message_handler(func=lambda m: m.text == "🔗 Создать зеркало") def mirror_menu(message): uid = message.from_user.id my_mirrors = get_user_mirrors(uid) markup = telebot.types.InlineKeyboardMarkup() markup.add(telebot.types.InlineKeyboardButton("🤖 Создать зеркало", callback_data="mir_create")) if my_mirrors: markup.add(telebot.types.InlineKeyboardButton( f"📋 Мои зеркала ({len(my_mirrors)})", callback_data="mir_list")) bot.send_message(message.chat.id, "🪞 Зеркала\n\n" "Создай собственного бота-зеркало с твоими ценами. " "Воркеры регистрируются у тебя, а ты получаешь разницу в цене.\n\n" "Для создания нужен токен бота от @BotFather.", reply_markup=markup)

@bot.message_handler(func=lambda m: m.text == "🔍 Все зеркала" and m.from_user.id in ADMIN_IDS) def admin_all_mirrors(message): mirrors = load_mirrors() if not mirrors: bot.send_message(message.chat.id, "🔍 Зеркал не создано.") return chunks = [] current = "🔍 Все зеркала в системе:\n\n" for token, m in mirrors.items(): owner_uid = int(m.get("owner_uid", 0)) workers = m.get("workers", {}) total_act = sum(w.get("success", 0) for w in workers.values()) balance = round(m.get("owner_earned_rub", 0) - m.get("owner_paid_rub", 0), 2) block = (f"🤖 @{m.get('bot_username','?')}\n" f" 👤 @{m.get('owner_username', '?')} | 👥 {len(workers)}\n" f" ✅ {total_act} | 💰 {balance}$\n\n") if len(current) + len(block) > 3900: chunks.append(current) current = "" current += block if current: chunks.append(current) for chunk in chunks: bot.send_message(message.chat.id, chunk)

@bot.callback_query_handler(func=lambda call: call.data == "mir_create") def mirror_create_start(call): try: bot.answer_callback_query(call.id) except Exception: pass uid = call.from_user.id user_state[uid] = {"step": "mirror_token"} bot.send_message(call.message.chat.id, "🤖 Отправь токен нового бота (получи у @BotFather):")

@bot.message_handler(func=lambda m: user_state.get(m.from_user.id, {}).get("step") == "mirror_token") def mirror_enter_token(message): uid = message.from_user.id token = message.text.strip().replace("\n", "").replace("\r", "").replace(" ", "") try: test_bot = telebot.TeleBot(token) info = test_bot.get_me() bot_username = info.username except Exception: bot.send_message(message.chat.id, "❌ Неверный токен. Попробуй ещё раз или нажми ❌ Отмена.") return existing = load_mirrors() if token in existing: bot.send_message(message.chat.id, "❌ Это зеркало уже создано.") user_state.pop(uid, None) return services = list(SERVICE_PRICES.keys()) user_state[uid] = {"step": "mirror_prices", "token": token, "bot_username": bot_username, "prices": {}, "service_idx": 0, "services": services} first = services[0] bot.send_message(message.chat.id, f"✅ Бот @{bot_username} найден!\n\n" f"Теперь установи цены для воркеров (в долларах).\n" f"Разница между базовой ценой и твоей — твой доход.\n\n" f"💰 {first} (базовая {SERVICE_PRICES[first]}$)\n" f"Введи цену для воркеров (0 — отключить сервис):")

@bot.message_handler(func=lambda m: user_state.get(m.from_user.id, {}).get("step") == "mirror_prices") def mirror_set_price(message): uid = message.from_user.id try: price = float(message.text.strip()) except ValueError: bot.send_message(message.chat.id, "❌ Введи число.") return state = user_state[uid] services = state["services"] idx = state["service_idx"] sname = services[idx] base = SERVICE_PRICES[sname] if price < 0 or price > base: bot.send_message(message.chat.id, f"❌ Цена должна быть от 0 до {base}$.") return state["prices"][sname] = price idx += 1 state["service_idx"] = idx if idx < len(services): nxt = services[idx] bot.send_message(message.chat.id, f"💰 {nxt} (базовая {SERVICE_PRICES[nxt]}$)\n" f"Введи цену для воркеров (0 — отключить):") else: token = state["token"] bot_username = state["bot_username"] prices = state["prices"] username = message.from_user.username or message.from_user.first_name or str(uid) create_mirror(uid, username, token, bot_username, prices) price_lines = "\n".join([f"• {k}: {v}$" for k, v in prices.items()]) bot.send_message(message.chat.id, f"✅ Зеркало @{bot_username} создано!\n\n" f"Твои цены:\n{price_lines}\n\n" f"🔔 Автовыплата дохода включена (порог {MIRROR_AUTOPAYOUT_MIN_RUB}$).\n" f"Бот запускается...", reply_markup=admin_menu() if uid in ADMIN_IDS else main_menu()) user_state.pop(uid, None) threading.Thread(target=launch_mirror_bot, args=(token,), daemon=True).start()

@bot.callback_query_handler(func=lambda call: call.data == "mir_list") def mirror_list(call): try: bot.answer_callback_query(call.id) except Exception: pass uid = call.from_user.id my_mirrors = get_user_mirrors(uid) if not my_mirrors: bot.send_message(call.message.chat.id, "📋 У тебя нет зеркал.") return rate = get_usdt_rate() for token, m in my_mirrors.items(): workers = m.get("workers", {}) total_act = sum(w.get("success", 0) for w in workers.values()) balance = m.get("owner_earned_rub", 0) - m.get("owner_paid_rub", 0) prefix = token.split(":")[0] ap = "ВКЛ ✅" if m.get("autopayout_enabled", True) else "ВЫКЛ ❌" text = (f"🤖 @{m.get('bot_username', '?')}\n\n" f"👥 Воркеров: {len(workers)}\n" f"📦 Всего активаций: {total_act}\n" f"💰 Твой доход к выплате: {balance}$\n" f"💵 Всего заработано: {m.get('owner_earned_rub', 0)}$\n" f"🔔 Автовыплата: {ap} (порог {m.get('autopayout_min_rub', MIRROR_AUTOPAYOUT_MIN_RUB)}$)") markup = telebot.types.InlineKeyboardMarkup() markup.add(telebot.types.InlineKeyboardButton("👥 Воркеры зеркала", callback_data=f"mirw_{prefix}")) markup.add( telebot.types.InlineKeyboardButton( f"🔔 Автовыплата: {'ВКЛ' if m.get('autopayout_enabled', True) else 'ВЫКЛ'}", callback_data=f"mirtog_{prefix}"), telebot.types.InlineKeyboardButton( f"⚙️ Порог: {m.get('autopayout_min_rub', MIRROR_AUTOPAYOUT_MIN_RUB)}$", callback_data=f"mirthr_{prefix}") ) if balance > 0: markup.add(telebot.types.InlineKeyboardButton("💰 Вывести доход", callback_data=f"mirpay_{prefix}")) bot.send_message(call.message.chat.id, text, reply_markup=markup)

def _find_mirror_by_prefix(prefix): for token, m in load_mirrors().items(): if token.split(":")[0] == prefix: return token, m return None, None

@bot.callback_query_handler(func=lambda call: call.data.startswith("mirw_")) def mirror_workers(call): try: bot.answer_callback_query(call.id) except Exception: pass prefix = call.data[5:] token, m = _find_mirror_by_prefix(prefix) if not m: bot.send_message(call.message.chat.id, "❌ Зеркало не найдено.") return workers = m.get("workers", {}) if not workers: bot.send_message(call.message.chat.id, "👥 В зеркале пока нет воркеров.") return rate = get_usdt_rate() lines = [f"👥 Воркеры @{m.get('bot_username','?')}:\n"] for wid, w in workers.items(): cutoff = int(time.time()) - 86400 recent = [e for e in w.get("history_24h", []) if e.get("ts", 0) >= cutoff] ok24 = sum(1 for e in recent if e.get("success")) lines.append(f"👤 {w.get('username', wid)}\n" f" ✅ {w.get('success', 0)} | 24ч: {ok24} | ❌ {w.get('failed', 0)}\n" f" 💵 принёс дохода: {w.get('earned_rub', 0)}$") bot.send_message(call.message.chat.id, "\n".join(lines))

@bot.callback_query_handler(func=lambda call: call.data.startswith("mirtog_")) def mirror_toggle_autopayout(call): prefix = call.data[7:] token, m = _find_mirror_by_prefix(prefix) if not m: try: bot.answer_callback_query(call.id, "Не найдено") except Exception: pass return mirrors = load_mirrors() new_val = not mirrors[token].get("autopayout_enabled", True) mirrors[token]["autopayout_enabled"] = new_val save_mirrors(mirrors) try: bot.answer_callback_query(call.id, f"Автовыплата {'включена' if new_val else 'выключена'}") except Exception: pass

@bot.callback_query_handler(func=lambda call: call.data.startswith("mirthr_")) def mirror_set_threshold(call): try: bot.answer_callback_query(call.id) except Exception: pass prefix = call.data[7:] uid = call.from_user.id user_state[uid] = {"step": "mirror_threshold", "prefix": prefix} bot.send_message(call.message.chat.id, "⚙️ Введи новый порог автовыплаты в рублях (минимум 10):")

@bot.message_handler(func=lambda m: user_state.get(m.from_user.id, {}).get("step") == "mirror_threshold") def mirror_threshold_input(message): uid = message.from_user.id try: val = int(message.text.strip()) except ValueError: bot.send_message(message.chat.id, "❌ Введи число.") return if val < 10: bot.send_message(message.chat.id, "❌ Минимум 10$.") return prefix = user_state[uid]["prefix"] token, m = _find_mirror_by_prefix(prefix) if token: mirrors = load_mirrors() mirrors[token]["autopayout_min_rub"] = val save_mirrors(mirrors) bot.send_message(message.chat.id, f"✅ Порог автовыплаты: {val}$", reply_markup=admin_menu() if uid in ADMIN_IDS else main_menu()) user_state.pop(uid, None)

@bot.callback_query_handler(func=lambda call: call.data.startswith("mirpay_")) def mirror_payout(call): try: bot.answer_callback_query(call.id) except Exception: pass prefix = call.data[7:] token, m = _find_mirror_by_prefix(prefix) if not m: bot.send_message(call.message.chat.id, "❌ Зеркало не найдено.") return balance = m.get("owner_earned_rub", 0) - m.get("owner_paid_rub", 0) if balance <= 0: bot.send_message(call.message.chat.id, "💸 Нет дохода для вывода.") return uid = call.from_user.id amount_usdt = round(balance, 2) result, error = create_check(amount_usdt, uid) if result: check_url = result.get("bot_check_url", "") mirrors = load_mirrors() mirrors[token]["owner_paid_rub"] = mirrors[token].get("owner_paid_rub", 0) + balance save_mirrors(mirrors) bot.send_message(call.message.chat.id, f"✅ Чек на доход создан!\n💵 {amount_usdt} USDT\n\n{check_url}") notify_admins_payout(uid, m.get("owner_username", str(uid)), balance, amount_usdt, f"доход зеркала @{m.get('bot_username','?')}") else: bot.send_message(call.message.chat.id, f"❌ Ошибка: {error}")

def mmenu_mirror_owner(): """Меню для владельца зеркала""" mk = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True) mk.add("📲 Новая активация") mk.row("📊 Моя статистика", "👥 Воркеры") mk.row("💸 Вывести", "💵 Цены") mk.row("📣 Рассылка", "❌ Отмена") return mk

def mmenu_mirror_worker(): """Меню для обычного воркера""" mk = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True) mk.add("📲 Новая активация") mk.add("📊 Моя статистика") mk.add("💸 Вывести") mk.add("❌ Отмена") return mk

====== ЗАПУСК ЗЕРКАЛЬНЫХ БОТОВ ======

def launch_mirror_bot(token): try: mirror_data = get_mirror_by_token(token) if not mirror_data or not mirror_data.get("active", True): return prices = mirror_data.get("prices", {}) bot_username = mirror_data.get("bot_username", "?")

    # Автообновление: добавляем новые сервисы с базовой ценой
    updated = False
    for sname, base_price in SERVICE_PRICES.items():
        if sname not in prices:
            prices[sname] = base_price
            updated = True
    if updated:
        mirrors = load_mirrors()
        if token in mirrors:
            mirrors[token]["prices"] = prices
            save_mirrors(mirrors)
    
    mbot = telebot.TeleBot(token)
    mirror_bots[token] = mbot
    mstates = mirror_user_states.setdefault(token, {})

    @mbot.message_handler(commands=['start'])
    def m_start(message):
        uid = message.from_user.id
        username = message.from_user.username or message.from_user.first_name or str(uid)
        stats = load_stats()
        if str(uid) not in stats:
            stats[str(uid)] = {"username": username, "success": 0, "failed": 0,
                               "services": {}, "earned_rub": 0, "paid_rub": 0, "history_24h": []}
            save_stats(stats)
        # регистрируем воркера в зеркале сразу
        mirrors = load_mirrors()
        if token in mirrors:
            w = mirrors[token].setdefault("workers", {})
            if str(uid) not in w:
                w[str(uid)] = {"username": username, "success": 0, "failed": 0, "services": {},
                               "earned_rub": 0, "history_24h": [],
                               "joined_at": time.strftime("%d.%m.%Y %H:%M")}
                save_mirrors(mirrors)
        owner_uid = int(mirror_data.get("owner_uid", 0))
        menu = mmenu_mirror_owner() if uid == owner_uid else mmenu_mirror_worker()
        mbot.send_message(message.chat.id,
                          "👋 Привет! Я бот для регистрации через SMS.\n\nНажми кнопку ниже чтобы начать:",
                          reply_markup=menu)

    @mbot.message_handler(func=lambda m: m.text == "📊 Моя статистика")
    def m_stats(message):
        uid = message.from_user.id
        stats = load_stats()
        s = stats.get(str(uid), {"success": 0, "failed": 0, "services": {}, "earned_rub": 0})
        ok24, fail24, earned24 = get_stats_24h(uid)
        rate = get_usdt_rate()
        total_earned = s.get("earned_rub", 0)
        total_success = s.get("success", 0)
        total_failed = s.get("failed", 0)
        services_text = ""
        services = s.get("services", {})
        if services:
            sorted_services = sorted(services.items(), key=lambda x: x[1], reverse=True)
            svc_lines = [f"  • {sname} — {count}" for sname, count in sorted_services]
            services_text = "\n" + "\n".join(svc_lines)
        text = (f"📊 Твоя статистика\n\n"
                f"📅 За 24 часа:\n"
                f"  ✅ Успешных: {ok24}\n"
                f"  ❌ Неудачных: {fail24}\n"
                f"  💵 Заработано: {earned24}$\n\n"
                f"📆 За всё время:\n"
                f"  ✅ Успешных: {total_success}{services_text}\n"
                f"  ❌ Неудачных: {total_failed}\n"
                f"  📦 Всего: {total_success + total_failed}\n"
                f"  💵 Заработано: {total_earned}$")
        mbot.send_message(message.chat.id, text)

    @mbot.message_handler(func=lambda m: m.text == "👥 Воркеры")
    def m_workers(message):
        mirrors = load_mirrors()
        if token not in mirrors:
            mbot.send_message(message.chat.id, "❌ Зеркало не найдено.")
            return
        workers = mirrors[token].get("workers", {})
        if not workers:
            mbot.send_message(message.chat.id, "👥 В зеркале пока нет воркеров.")
            return
        rate = get_usdt_rate()
        lines = ["👥 Воркеры зеркала:\n"]
        for wid, w in workers.items():
            total = w.get("success", 0) + w.get("failed", 0)
            earned = w.get("earned_rub", 0)
            lines.append(f"👤 {w.get('username', wid)}\n"
                         f"  ✅ {w.get('success', 0)} | ❌ {w.get('failed', 0)} | 📦 {total}\n"
                         f"  💵 принёс дохода: {earned}$")
        mbot.send_message(message.chat.id, "\n".join(lines))

    @mbot.message_handler(func=lambda m: m.text == "💸 Вывести")
    def m_payout(message):
        uid = message.from_user.id
        
        # Защита от повторного вывода
        if uid in payout_in_progress:
            mbot.send_message(message.chat.id, "⏳ Выплата уже в процессе. Подожди.")
            return
        
        stats = load_stats()
        s = stats.get(str(uid), {"services": {}, "success": 0})
        services_done = s.get("services", {})
        if not services_done or s.get("success", 0) == 0:
            mbot.send_message(message.chat.id, "💸 Нет успешных активаций для выплаты.")
            return
        # считаем по ценам зеркала
        total_rub = 0
        breakdown = []
        for sname, count in services_done.items():
            p = prices.get(sname, 0)
            if p > 0 and count > 0:
                amt = round(p * count, 2)
                total_rub += amt
                breakdown.append(f"• {sname}: {count} × {p}$ = {amt}$")
        if total_rub <= 0:
            mbot.send_message(message.chat.id, "💸 Сумма выплаты равна 0.")
            return
        amount_usdt = round(total_rub, 2)
        text = (f"💸 Расчёт выплаты:\n\n" + "\n".join(breakdown) +
                f"\n\n💵 К выплате: {amount_usdt} USDT")
        markup = telebot.types.InlineKeyboardMarkup()
        markup.add(telebot.types.InlineKeyboardButton(
            f"✅ Получить {amount_usdt} USDT", callback_data=f"dopayout_{total_rub}_{amount_usdt}"))
        markup.add(telebot.types.InlineKeyboardButton("❌ Отмена", callback_data="cancelpayout"))
        mbot.send_message(message.chat.id, text, reply_markup=markup)

    @mbot.callback_query_handler(func=lambda call: call.data.startswith("dopayout_"))
    def m_dopayout(call):
        try: mbot.answer_callback_query(call.id)
        except Exception: pass
        uid = call.from_user.id
        
        # Защита от повторного нажатия
        if uid in payout_in_progress:
            mbot.edit_message_text("⏳ Выплата уже в процессе.",
                chat_id=call.message.chat.id, message_id=call.message.message_id)
            return
        
        payout_in_progress.add(uid)
        
        # Пересчитываем из текущих сервисов
        stats = load_stats()
        s = stats.get(str(uid), {"services": {}, "success": 0})
        services_done = s.get("services", {})
        
        if not services_done or s.get("success", 0) == 0:
            payout_in_progress.discard(uid)
            mbot.edit_message_text("❌ Нет активаций для выплаты.",
                chat_id=call.message.chat.id, message_id=call.message.message_id)
            return
        
        total_rub = 0
        breakdown = []
        for sname, count in services_done.items():
            p = prices.get(sname, 0)
            if p > 0 and count > 0:
                amt = round(p * count, 2)
                total_rub += amt
                breakdown.append(f"• {sname}: {count} × {p}$ = {amt}$")
        
        amount_usdt = round(total_rub, 2)
        if amount_usdt <= 0:
            payout_in_progress.discard(uid)
            mbot.edit_message_text("❌ Сумма выплаты равна 0.",
                chat_id=call.message.chat.id, message_id=call.message.message_id)
            return
        
        mbot.edit_message_text("⏳ Отправляю выплату...", chat_id=call.message.chat.id,
                               message_id=call.message.message_id)
        result, error = create_check(amount_usdt, uid)
        if result:
            check_url = result.get("bot_check_url", "")
            # Сначала сбрасываем стату
            reset_stat(uid)
            payout_in_progress.discard(uid)
            owner_uid = int(mirror_data.get("owner_uid", 0))
            menu = mmenu_mirror_owner() if uid == owner_uid else mmenu_mirror_worker()
            mbot.send_message(call.message.chat.id,
                              f"✅ Чек создан!\n💵 {amount_usdt} USDT\n\n{check_url}", reply_markup=menu)
            stats = load_stats()
            uname = stats.get(str(uid), {}).get("username", str(uid))
            add_payout_history(uid, uname, total_rub, amount_usdt, breakdown)
            notify_admins_payout(uid, uname, total_rub, amount_usdt, f"зеркало @{bot_username}")
        else:
            payout_in_progress.discard(uid)
            owner_uid = int(mirror_data.get("owner_uid", 0))
            menu = mmenu_mirror_owner() if uid == owner_uid else mmenu_mirror_worker()
            mbot.send_message(call.message.chat.id, f"❌ Ошибка выплаты: {error}", reply_markup=menu)

    @mbot.callback_query_handler(func=lambda call: call.data == "cancelpayout")
    def m_cancelpay(call):
        try: mbot.answer_callback_query(call.id)
        except Exception: pass
        try:
            mbot.edit_message_text("❌ Выплата отменена.", chat_id=call.message.chat.id,
                                   message_id=call.message.message_id)
        except Exception:
            pass

    @mbot.message_handler(func=lambda m: m.text == "📲 Новая активация")
    def m_new_activation(message):
        uid = message.from_user.id
        services = get_services()
        if not services:
            mbot.send_message(message.chat.id, "❌ Не удалось получить сервисы.")
            return
        # фильтруем только включённые в зеркале (цена > 0)
        available = [s for s in services if prices.get(s["name"], 0) > 0]
        if not available:
            mbot.send_message(message.chat.id, "❌ Нет доступных сервисов.")
            return
        mstates[uid] = {"step": "choose", "services": available, "selected_ids": []}
        mbot.send_message(message.chat.id, "📋 Выбери сервисы, затем нажми Продолжить:",
                          reply_markup=services_keyboard(available, [], custom_prices=prices))

    @mbot.callback_query_handler(func=lambda call: call.data.startswith("tog_"))
    def m_toggle(call):
        try: mbot.answer_callback_query(call.id)
        except Exception: pass
        uid = call.from_user.id
        sid = call.data[4:]
        st = mstates.get(uid, {})
        if st.get("step") != "choose":
            return
        sel = st.get("selected_ids", [])
        if sid in sel:
            sel.remove(sid)
        else:
            sel.append(sid)
        st["selected_ids"] = sel
        try:
            mbot.edit_message_reply_markup(call.message.chat.id, call.message.message_id,
                                           reply_markup=services_keyboard(st["services"], sel, custom_prices=prices))
        except Exception:
            pass

    @mbot.callback_query_handler(func=lambda call: call.data == "confirm_services")
    def m_confirm(call):
        try: mbot.answer_callback_query(call.id)
        except Exception: pass
        uid = call.from_user.id
        st = mstates.get(uid, {})
        if not st.get("selected_ids"):
            return
        st["step"] = "enter_phone"
        mbot.edit_message_text("📱 Введи номер телефона (+79001234567):",
                               chat_id=call.message.chat.id, message_id=call.message.message_id)

    @mbot.message_handler(func=lambda m: mstates.get(m.from_user.id, {}).get("step") == "enter_phone")
    def m_phone(message):
        uid = message.from_user.id
        username = message.from_user.username or message.from_user.first_name or str(uid)
        phone = message.text.strip()
        clean = phone.replace("+", "").replace(" ", "").replace("-", "").replace("(", "").replace(")", "")
        if not clean.isdigit() or len(clean) < 10:
            mbot.send_message(message.chat.id, "❌ Неверный формат номера.")
            return
        
        # Добавляем + если его нет
        # Поддерживаем 7 и 8 (старый формат)
        if clean.startswith("8"):
            # 8XXXXXXXXXX → 79XXXXXXXXXX → +79XXXXXXXXXX
            clean = "7" + clean[1:]
        
        if not clean.startswith("7"):
            mbot.send_message(message.chat.id, "❌ Номер должен быть для России (начинаться на 7 или 8)")
            return
        
        formatted_phone = "+" + clean
        
        st = mstates[uid]
        selected_ids = st["selected_ids"]
        services = st["services"]
        msg = mbot.send_message(message.chat.id, f"⏳ Создаю активации...\n📱 {formatted_phone}")
        activations, error = create_activations(formatted_phone, selected_ids)
        if not activations:
            mbot.edit_message_text(f"❌ Ошибка: {error}", chat_id=message.chat.id, message_id=msg.message_id)
            mstates.pop(uid, None)
            return
        act_map = {}
        for act in activations:
            sid = act["service_id"]
            svc = next((s for s in services if s["id"] == sid), None)
            sname = svc["name"] if svc else sid
            code_len = svc.get("sms_code_length", 6) if svc else 6
            act_map[act["id"]] = {"name": sname, "status": "pending", "code": None,
                                  "sms_code_length": code_len, "waiting_input": False}
        mstates[uid] = {"step": "waiting_sms", "phone": phone, "username": username,
                        "act_map": act_map, "pending_input": {}}
        mbot.edit_message_text(build_status_text(phone, act_map), chat_id=message.chat.id,
                               message_id=msg.message_id)
        threading.Thread(target=poll_mirror_activations,
                         args=(mbot, message.chat.id, uid, msg.message_id, mstates, token, prices, mirror_data),
                         daemon=True).start()

    @mbot.message_handler(func=lambda m: (
        mstates.get(m.from_user.id, {}).get("step") == "waiting_sms" and
        bool(mstates.get(m.from_user.id, {}).get("pending_input")) and
        m.text not in ["📲 Новая активация", "📊 Моя статистика", "💸 Вывести", "❌ Отмена", "👥 Воркеры", "📣 Рассылка", "💵 Цены"]
    ))
    def m_sms_input(message):
        uid = message.from_user.id
        code = message.text.strip()
        st = mstates.get(uid, {})
        act_map = st.get("act_map", {})
        pending = st.get("pending_input", {})
        if not code.isdigit():
            mbot.send_message(message.chat.id, "❌ Код должен состоять из цифр.")
            return
        target_id = None
        for act_id in pending:
            if act_map.get(act_id, {}).get("waiting_input"):
                target_id = act_id
                break
        if not target_id:
            return
        info = act_map[target_id]
        if len(code) != info["sms_code_length"]:
            mbot.send_message(message.chat.id, f"❌ Код должен быть {info['sms_code_length']} цифр.")
            return
        ok, error = send_sms_code(target_id, code)
        if ok:
            act_map[target_id]["waiting_input"] = False
            act_map[target_id]["code"] = code
            act_map[target_id]["status"] = "code_ready"
            pending.pop(target_id, None)
            mbot.send_message(message.chat.id, f"✅ Код для {info['name']} принят!")
        else:
            mbot.send_message(message.chat.id, f"❌ Ошибка: {error}")

    @mbot.message_handler(func=lambda m: m.text == "❌ Отмена")
    def m_cancel(message):
        uid = message.from_user.id
        st = mstates.get(uid, {})
        for act_id, info in st.get("act_map", {}).items():
            if info["status"] not in ("completed", "failed", "cancelled"):
                cancel_activation(act_id)
        mstates.pop(uid, None)
        owner_uid = int(mirror_data.get("owner_uid", 0))
        menu = mmenu_mirror_owner() if uid == owner_uid else mmenu_mirror_worker()
        mbot.send_message(message.chat.id, "✅ Отменено.", reply_markup=menu)

    @mbot.message_handler(func=lambda m: m.text == "📣 Рассылка")
    def m_broadcast(message):
        uid = message.from_user.id
        owner_uid = int(mirror_data.get("owner_uid", 0))
        if uid != owner_uid:
            return
        mstates[uid] = {"step": "mirror_broadcast"}
        mbot.send_message(message.chat.id,
                          "📣 Введи текст рассылки для воркеров этого зеркала:\n\n"
                          "(Отправь текст или нажми ❌ Отмена)")

    @mbot.message_handler(func=lambda m: mstates.get(m.from_user.id, {}).get("step") == "mirror_broadcast")
    def m_broadcast_send(message):
        uid = message.from_user.id
        owner_uid = int(mirror_data.get("owner_uid", 0))
        if uid != owner_uid:
            return
        text = message.text.strip()
        if text == "❌ Отмена":
            mstates.pop(uid, None)
            mbot.send_message(message.chat.id, "✅ Отменено.", reply_markup=mmenu_mirror_owner())
            return
        mstates.pop(uid, None)
        mirrors = load_mirrors()
        m = mirrors.get(token, {})
        workers = m.get("workers", {})
        sent = 0
        failed = 0
        for worker_uid in workers.keys():
            try:
                mbot.send_message(int(worker_uid), f"📣 Объявление:\n\n{text}")
                sent += 1
            except Exception:
                failed += 1
        mbot.send_message(message.chat.id,
                          f"📣 Рассылка завершена!\n\n"
                          f"✅ Доставлено: {sent}\n"
                          f"❌ Не доставлено: {failed}",
                          reply_markup=mmenu_mirror_owner())

    @mbot.message_handler(func=lambda m: m.text == "💵 Цены")
    def m_prices(message):
        uid = message.from_user.id
        owner_uid = int(mirror_data.get("owner_uid", 0))
        if uid != owner_uid:
            return
        mirrors = load_mirrors()
        current_prices = mirrors.get(token, {}).get("prices", prices)
        lines = ["💵 Цены твоего зеркала:\n"]
        markup = telebot.types.InlineKeyboardMarkup()
        for sname, price in current_prices.items():
            base = SERVICE_PRICES.get(sname, 0)
            status = "✅" if price > 0 else "❌ выкл"
            lines.append(f"  {status} {sname} — {price}$ (база: {base}$)")
            markup.add(telebot.types.InlineKeyboardButton(
                f"✏️ {sname} ({price}$)",
                callback_data=f"meditpr_{sname}"))
        bot_msg = "\n".join(lines)
        mbot.send_message(message.chat.id, bot_msg, reply_markup=markup)

    @mbot.callback_query_handler(func=lambda call: call.data.startswith("meditpr_"))
    def m_edit_price_start(call):
        try:
            mbot.answer_callback_query(call.id)
        except Exception:
            pass
        uid = call.from_user.id
        owner_uid = int(mirror_data.get("owner_uid", 0))
        if uid != owner_uid:
            return
        sname = call.data[8:]
        mirrors = load_mirrors()
        current_price = mirrors.get(token, {}).get("prices", {}).get(sname, 0)
        base = SERVICE_PRICES.get(sname, 0)
        mstates[uid] = {"step": "mirror_edit_price", "service": sname}
        mbot.send_message(call.message.chat.id,
                          f"✏️ Изменение цены: {sname}\n"
                          f"Текущая цена: {current_price}$\n"
                          f"Базовая цена: {base}$\n\n"
                          f"Введи новую цену (0 — выключить сервис):")

    @mbot.message_handler(func=lambda m: mstates.get(m.from_user.id, {}).get("step") == "mirror_edit_price")
    def m_edit_price_apply(message):
        uid = message.from_user.id
        owner_uid = int(mirror_data.get("owner_uid", 0))
        if uid != owner_uid:
            return
        st = mstates.get(uid, {})
        sname = st.get("service", "")
        text = message.text.strip()
        if text == "❌ Отмена":
            mstates.pop(uid, None)
            mbot.send_message(message.chat.id, "✅ Отменено.", reply_markup=mmenu_mirror_owner())
            return
        try:
            new_price = float(text)
            if new_price < 0:
                mbot.send_message(message.chat.id, "❌ Цена не может быть отрицательной.")
                return
            base = SERVICE_PRICES.get(sname, 0)
            if new_price > base:
                mbot.send_message(message.chat.id,
                    f"❌ Цена не может быть выше базовой ({base}$).\n"
                    f"Введи значение от 0 до {base}:")
                return
        except ValueError:
            mbot.send_message(message.chat.id, "❌ Введи число (например 0.10).")
            return
        mstates.pop(uid, None)
        mirrors = load_mirrors()
        if token in mirrors:
            old_price = mirrors[token].get("prices", {}).get(sname, 0)
            mirrors[token]["prices"][sname] = new_price
            prices[sname] = new_price
            save_mirrors(mirrors)
            
            # Спрашиваем уведомить ли воркеров
            markup = telebot.types.InlineKeyboardMarkup()
            markup.add(telebot.types.InlineKeyboardButton(
                "📣 Да, уведомить", callback_data=f"mnotify_yes_{sname}_{old_price}_{new_price}"))
            markup.add(telebot.types.InlineKeyboardButton(
                "🔇 Нет", callback_data=f"mnotify_no"))
            
            mbot.send_message(message.chat.id,
                              f"✅ Цена обновлена!\n\n"
                              f"  {sname}: {old_price}$ → {new_price}$\n\n"
                              f"Уведомить воркеров?",
                              reply_markup=markup)
        else:
            mbot.send_message(message.chat.id, "❌ Ошибка сохранения.", reply_markup=mmenu_mirror_owner())

    @mbot.callback_query_handler(func=lambda call: call.data.startswith("mnotify_yes_"))
    def m_notify_yes(call):
        try:
            mbot.answer_callback_query(call.id)
        except Exception:
            pass
        parts = call.data[12:].rsplit("_", 2)
        sname = parts[0]
        old_price = parts[1]
        new_price = parts[2]
        
        mirrors_data = load_mirrors()
        workers = mirrors_data.get(token, {}).get("workers", {})
        notification = (f"💲💲💲 ИЗМЕНЕНИЕ ЦЕНЫ 💲💲💲\n\n"
                        f"📌 {sname}\n"
                        f"💰 Было: {old_price}$\n"
                        f"💵 Стало: {new_price}$\n\n"
                        f"💲 Новая цена действует прямо сейчас!")
        sent = 0
        for worker_uid in workers.keys():
            try:
                mbot.send_message(int(worker_uid), notification)
                sent += 1
            except Exception:
                pass
        mbot.send_message(call.message.chat.id,
                          f"📣 Уведомление отправлено {sent} воркерам!",
                          reply_markup=mmenu_mirror_owner())

    @mbot.callback_query_handler(func=lambda call: call.data == "mnotify_no")
    def m_notify_no(call):
        try:
            mbot.answer_callback_query(call.id)
        except Exception:
            pass
        mbot.send_message(call.message.chat.id, "🔇 Без уведомления.", reply_markup=mmenu_mirror_owner())

    print(f"Зеркало @{bot_username} запущено")
    mbot.infinity_polling(skip_pending=True, timeout=20)
except Exception as e:
    print(f"Ошибка зеркала {token[:10]}: {e}")

def poll_mirror_activations(mbot, chat_id, uid, msg_id, mstates, mirror_token, prices, mirror_data): max_wait = 300 elapsed = 0 username = mstates.get(uid, {}).get("username", str(uid)) owner_uid = int(mirror_data.get("owner_uid", 0)) while elapsed < max_wait: st = mstates.get(uid, {}) if st.get("step") != "waiting_sms": return act_map = st["act_map"] all_done = True for act_id in list(act_map.keys()): info = act_map[act_id] if info["status"] in ("completed", "failed", "cancelled"): continue if info.get("waiting_input"): all_done = False continue all_done = False data = get_activation(act_id) if not data: continue old_status = info["status"] new_status = data["status"] api_code = data.get("sms_code") act_map[act_id]["status"] = new_status if api_code: act_map[act_id]["code"] = api_code if old_status != "sms_sent" and new_status == "sms_sent": act_map[act_id]["waiting_input"] = True st["pending_input"][act_id] = True try: mbot.send_message(chat_id, f"📩 {info['name']} — пришла СМС!\nВведи код " f"({info['sms_code_length']} цифр):") except Exception: pass if old_status != new_status: if new_status == "completed": price = prices.get(info["name"], SERVICE_PRICES.get(info["name"], 0)) add_stat(uid, username, success=True, service_name=info["name"], earned_rub=price, mirror_token=mirror_token) save_phone_record(uid, username, st.get("phone", "?"), f"@{mirror_data.get('bot_username','?')}", info["name"], success=True) elif new_status == "failed": add_stat(uid, username, success=False, mirror_token=mirror_token) save_phone_record(uid, username, st.get("phone", "?"), f"@{mirror_data.get('bot_username','?')}", info["name"], success=False) phone = st["phone"] try: mbot.edit_message_text(build_status_text(phone, act_map), chat_id=chat_id, message_id=msg_id) except Exception: pass if all_done: try: menu = mmenu_mirror_owner() if uid == owner_uid else mmenu_mirror_worker() mbot.send_message(chat_id, "✅ Все активации завершены!", reply_markup=menu) except Exception: pass mstates.pop(uid, None) return time.sleep(3) elapsed += 3 mstates.pop(uid, None) try: menu = mmenu_mirror_owner() if uid == owner_uid else mmenu_mirror_worker() mbot.send_message(chat_id, "⏰ Время истекло. Активации отменены.", reply_markup=menu) except Exception: pass

def launch_all_mirrors(): mirrors = load_mirrors() for token, m in mirrors.items(): if m.get("active", True): threading.Thread(target=launch_mirror_bot, args=(token,), daemon=True).start() time.sleep(1)

====== ЗАПУСК ======

print("Бот запущен...") launch_all_mirrors()

while True: try: bot.infinity_polling(skip_pending=True, timeout=20) except Exception as e: print(f"Ошибка polling: {e}") time.sleep(5)