Загрузка данных


import discord
from discord.ext import commands
from discord import app_commands
import json
import os
import re
import time
import asyncio
import random
from collections import deque, defaultdict
from datetime import datetime, timedelta

# ================= КОНФИГ =================
TOKEN = "MTU1MTY2MDA3NDI2NTYxMjQ4OA.GCcw05.AStK2K9jc_kvHz-BwSYKrO-ZCHTxOuI6Dmgks8"
GUILD_ID = 1548045835143749822

# СУПЕР-АДМИНЫ (только они могут использовать команды модерации)
OWNER_IDS = [984844682087530626, 1462075826492407878]

# Каналы
LEADER_CHANNEL_ID = 1562802314576789585
ADMIN_CHANNEL_ID  = 1562802325729452103
MEDIA_CHANNEL_ID  = 1562802309707075627
REVIEW_CHANNEL_ID = 1563190052509458544
LOG_CHANNEL_ID = REVIEW_CHANNEL_ID
WELCOME_CHANNEL_ID = 1563190052509458544

TICKET_CATEGORY_NAME = "🎫 Тикеты"
STAFF_ROLE_NAME = "Администратор"
CURRENCY = "🪙"

# ==========================================

intents = discord.Intents.default()
intents.message_content = True
intents.members = True
intents.reactions = True

bot = commands.Bot(command_prefix="!", intents=intents)

DATA_FILE = "applications.json"
ANTI_FILE = "anti.json"
WARN_FILE = "warnings.json"
USER_STATS_FILE = "user_stats.json"
TICKET_FILE = "active_tickets.json"
ECONOMY_FILE = "economy.json"
NOTES_FILE = "notes.json"
WHITELIST_FILE = "whitelist.json"
BACKUP_FILE = "channel_backups.json"

# ---------- Настройки защиты по умолчанию ----------
ANTI_DEFAULT = {
    "anti_spam": True, "anti_link": True, "anti_raid": True, "anti_nuke": True,
    "anti_mention": True, "anti_emoji": True, "anti_caps": True,
    "nuke_hits": 2,
    "nuke_window": 30,
    "spam_max": 6,
    "spam_window": 5,
    "raid_joins": 6,
    "raid_window": 10,
}
ANTI = dict(ANTI_DEFAULT)

# ---------- Whitelist ----------
WHITELIST = {
    "users": [],   # ID пользователей
    "roles": []    # ID ролей
}

# ---------- Функции загрузки/сохранения ----------
def load_json(file, default):
    if os.path.exists(file):
        try:
            with open(file, "r", encoding="utf-8") as f: return json.load(f)
        except: pass
    return default

def save_json(file, data):
    try:
        with open(file, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2)
    except Exception as e: print(f"[SAVE] Ошибка {file}: {e}")

ANTI = {**ANTI_DEFAULT, **load_json(ANTI_FILE, {})}
WARNINGS = load_json(WARN_FILE, {})
USER_STATS = load_json(USER_STATS_FILE, {})
ACTIVE_TICKETS = load_json(TICKET_FILE, {})
ECONOMY = load_json(ECONOMY_FILE, {})
NOTES = load_json(NOTES_FILE, {})
WHITELIST = {**WHITELIST, **load_json(WHITELIST_FILE, {})}
CHANNEL_BACKUPS = load_json(BACKUP_FILE, {})

SHOP_ITEMS = {"VIP": {"price": 5000, "role": "VIP"}, "Premium": {"price": 10000, "role": "Premium"}}

def save_anti(): save_json(ANTI_FILE, ANTI)
def save_warnings(): save_json(WARN_FILE, WARNINGS)
def save_stats(): save_json(USER_STATS_FILE, USER_STATS)
def save_tickets(): save_json(TICKET_FILE, ACTIVE_TICKETS)
def save_economy(): save_json(ECONOMY_FILE, ECONOMY)
def save_notes(): save_json(NOTES_FILE, NOTES)
def save_whitelist(): save_json(WHITELIST_FILE, WHITELIST)
def save_backups(): save_json(BACKUP_FILE, CHANNEL_BACKUPS)

def get_stat(user_id, key="reputation"):
    uid = str(user_id)
    if uid not in USER_STATS: USER_STATS[uid] = {"tickets": 0, "reputation": 0, "messages": 0}
    return USER_STATS[uid].get(key, 0)

def add_stat(user_id, key, amount=1):
    uid = str(user_id)
    if uid not in USER_STATS: USER_STATS[uid] = {"tickets": 0, "reputation": 0, "messages": 0}
    USER_STATS[uid][key] = USER_STATS[uid].get(key, 0) + amount
    save_stats()

def get_coins(user_id):
    uid = str(user_id)
    if uid not in ECONOMY: ECONOMY[uid] = {"coins": 0, "last_daily": 0}
    return ECONOMY[uid].get("coins", 0)

def add_coins(user_id, amount):
    uid = str(user_id)
    if uid not in ECONOMY: ECONOMY[uid] = {"coins": 0, "last_daily": 0}
    ECONOMY[uid]["coins"] = ECONOMY[uid].get("coins", 0) + amount
    save_economy()

def find_app(app_id):
    data = load_json(DATA_FILE, {})
    for apps in data.values():
        for app in apps:
            if app.get("id") == app_id: return data, app
    return data, None

# ---------- Служебное для защиты ----------
_HITS = {}
_spam_hist = defaultdict(deque)
_join_hist = defaultdict(deque)
_raid_until = {}
_orig_verification = {}

def is_owner(user_id): return user_id in OWNER_IDS

# ВАЖНО: теперь is_admin и is_staff = только овнер
def is_admin(member): return is_owner(member.id)
def is_staff(member): return is_owner(member.id)

def in_whitelist(member: discord.Member) -> bool:
    """Проверка: пользователь или одна из его ролей в вайт-листе защиты."""
    if member.id in WHITELIST.get("users", []): return True
    user_roles = {r.id for r in getattr(member, "roles", [])}
    return bool(user_roles & set(WHITELIST.get("roles", [])))

def is_trusted(member):
    """Доверенный = овнер, владелец сервера, в вайт-листе защиты.
    ВАЖНО: админ-права больше НЕ дают иммунитет от анти-нюка — это защита от взлома."""
    if is_owner(member.id): return True
    if member.guild.owner_id == member.id: return True
    if in_whitelist(member): return True
    return False

def _hit(key, window):
    now = time.time()
    lst = _HITS.setdefault(key, [])
    lst[:] = [t for t in lst if now - t < window]
    lst.append(now)
    return len(lst)

async def log_alert(guild, title, desc, color=None):
    ch = guild.get_channel(LOG_CHANNEL_ID) or bot.get_channel(LOG_CHANNEL_ID)
    if not ch: return
    embed = discord.Embed(title=title, description=desc, color=color or discord.Color.red(), timestamp=discord.utils.utcnow())
    try: await ch.send(embed=embed)
    except: pass

async def add_warning(member, reason, moderator=None):
    uid = str(member.id)
    if uid not in WARNINGS: WARNINGS[uid] = {"count": 0, "reasons": []}
    WARNINGS[uid]["count"] += 1
    WARNINGS[uid]["reasons"].append({"reason": reason, "mod": str(moderator), "time": str(datetime.now())})
    save_warnings()
    count = WARNINGS[uid]["count"]
    await log_alert(member.guild, "⚠️ Предупреждение", f"{member.mention}\nМодератор: {moderator.mention if moderator else 'Система'}\nПричина: {reason}\nВсего: {count}/3", discord.Color.yellow())
    if count >= 3:
        try:
            await member.timeout(timedelta(minutes=60), reason="Лимит предупреждений")
            WARNINGS[uid]["count"] = 0
            WARNINGS[uid]["reasons"].clear()
            save_warnings()
            await log_alert(member.guild, "🔇 Автомут", f"{member.mention} получил мут на 60 мин.", discord.Color.orange())
        except: pass

# ================= БЭКАП КАНАЛОВ =================
def backup_channel(channel: discord.abc.GuildChannel):
    """Сохраняет данные канала для восстановления."""
    gid = str(channel.guild.id)
    CHANNEL_BACKUPS.setdefault(gid, {})
    if isinstance(channel, discord.CategoryChannel):
        data = {"type": "category", "name": channel.name, "position": channel.position}
    elif isinstance(channel, discord.TextChannel):
        data = {
            "type": "text", "name": channel.name, "topic": channel.topic,
            "position": channel.position, "nsfw": channel.nsfw, "slowmode": channel.slowmode_delay,
            "category_id": channel.category_id,
            "overwrites": [
                {"target_id": t.id, "target_type": "role" if isinstance(t, discord.Role) else "member",
                 "allow": o.pair()[0].value, "deny": o.pair()[1].value}
                for t, o in channel.overwrites.items()
            ]
        }
    elif isinstance(channel, discord.VoiceChannel):
        data = {
            "type": "voice", "name": channel.name, "bitrate": channel.bitrate,
            "user_limit": channel.user_limit, "position": channel.position,
            "category_id": channel.category_id,
            "overwrites": [
                {"target_id": t.id, "target_type": "role" if isinstance(t, discord.Role) else "member",
                 "allow": o.pair()[0].value, "deny": o.pair()[1].value}
                for t, o in channel.overwrites.items()
            ]
        }
    else:
        return
    CHANNEL_BACKUPS[gid][str(channel.id)] = data
    save_backups()

async def restore_channel(guild: discord.Guild, channel_id: int) -> bool:
    """Восстанавливает канал из бэкапа."""
    gid = str(guild.id)
    data = CHANNEL_BACKUPS.get(gid, {}).pop(str(channel_id), None)
    if not data: return False
    try:
        category = None
        if data.get("category_id"):
            category = guild.get_channel(data["category_id"])
        overwrites = {}
        for ow in data.get("overwrites", []):
            target = guild.get_role(ow["target_id"]) if ow["target_type"] == "role" else guild.get_member(ow["target_id"])
            if target:
                overwrites[target] = discord.PermissionOverwrite.from_pair(
                    discord.Permissions(ow["allow"]), discord.Permissions(ow["deny"])
                )
        if data["type"] == "category":
            await guild.create_category(data["name"], position=data.get("position", 0))
        elif data["type"] == "text":
            await guild.create_text_channel(
                data["name"], category=category, topic=data.get("topic"),
                nsfw=data.get("nsfw", False), slowmode_delay=data.get("slowmode", 0),
                overwrites=overwrites, position=data.get("position", 0)
            )
        elif data["type"] == "voice":
            await guild.create_voice_channel(
                data["name"], category=category, bitrate=data.get("bitrate", 64000),
                user_limit=data.get("user_limit", 0), overwrites=overwrites,
                position=data.get("position", 0)
            )
        save_backups()
        return True
    except Exception as e:
        print(f"[RESTORE] Ошибка: {e}")
        save_backups()
        return False

async def strip_roles_and_kick(guild: discord.Guild, member: discord.Member, reason: str):
    """Снимает все роли и кикает (для анти-нюка)."""
    try:
        # Снимаем все роли, которые бот может снять
        removable = [r for r in member.roles if r != guild.default_role and r.managed is False
                     and r < guild.me.top_role]
        if removable:
            await member.remove_roles(*removable, reason=reason)
    except Exception as e:
        print(f"[STRIP] {e}")
    try:
        await member.kick(reason=reason)
    except Exception as e:
        print(f"[KICK] {e}")

# ================= МОДАЛЬНЫЕ ОКНА =================
class NoteModal(discord.ui.Modal, title="Заметка о пользователе"):
    def __init__(self, user_id: int):
        super().__init__()
        self.user_id = str(user_id)
        self.note = discord.ui.TextInput(label="Текст заметки", style=discord.TextStyle.paragraph, required=True, max_length=500)
        self.add_item(self.note)

    async def on_submit(self, interaction: discord.Interaction):
        if self.user_id not in NOTES: NOTES[self.user_id] = []
        NOTES[self.user_id].append({
            "text": self.note.value,
            "author": str(interaction.user),
            "time": datetime.now().strftime("%Y-%m-%d %H:%M")
        })
        save_notes()
        await interaction.response.send_message("✅ Заметка добавлена (видна только модераторам).", ephemeral=True)

# ================= НАСТРОЙКА ЗАЩИТЫ =================
class AntiSettingsView(discord.ui.View):
    def __init__(self):
        super().__init__(timeout=None)
        for key in ["anti_spam", "anti_link", "anti_raid", "anti_nuke", "anti_mention", "anti_emoji", "anti_caps"]:
            label = f"{'✅' if ANTI.get(key) else '❌'} {key}"
            b = discord.ui.Button(label=label, custom_id=f"toggle_{key}", style=discord.ButtonStyle.secondary)
            b.callback = self._make_toggle(key)
            self.add_item(b)

    def _make_toggle(self, key):
        async def cb(i: discord.Interaction):
            if not is_admin(i.user): return await i.response.send_message("❌ Только овнер.", ephemeral=True)
            ANTI[key] = not ANTI[key]; save_anti()
            await i.response.edit_message(embed=anti_embed(), view=AntiSettingsView())
        return cb

def anti_embed():
    icons = {"anti_spam": "💬", "anti_link": "🔗", "anti_raid": "🛡️", "anti_nuke": "💣", "anti_mention": "📢", "anti_emoji": "😡", "anti_caps": "🔠"}
    desc = "\n".join(f"{icons.get(k, '•')} **{k}** — {'включено ✅' if v else 'выключено ❌'}" for k, v in ANTI.items() if k in icons)
    desc += f"\n\n⚙️ **Анти-Нюк:** {ANTI['nuke_hits']} удалений за {ANTI['nuke_window']} сек."
    desc += f"\n📋 **Whitelist:** {len(WHITELIST.get('users', []))} юзеров, {len(WHITELIST.get('roles', []))} ролей"
    return discord.Embed(title="⚙️ Панель защиты", description=desc, color=discord.Color.blurple())

# ================= ЗАЩИТА =================
class Protection(commands.Cog):
    def __init__(self, bot_): self.bot = bot_

    @commands.Cog.listener()
    async def on_message(self, message: discord.Message):
        if message.guild is None or message.author.bot or message.webhook_id: return
        if is_trusted(message.author): return

        add_stat(message.author.id, "messages", 1)
        content = message.content or ""

        if ANTI["anti_link"]:
            INVITE_RE = re.compile(r"discord(\.gg|\.com/invite|app\.com/invite)/[\w-]+", re.I)
            LINK_RE = re.compile(r"https?://|www\.", re.I)
            allowed = ("youtube.com", "youtu.be", "twitch.tv", "tiktok.com", "vk.com", "flowdm.fun", "discord.com/channels")
            if INVITE_RE.search(content) or (LINK_RE.search(content) and not any(d in content.lower() for d in allowed)):
                try: await message.delete()
                except: pass
                return await add_warning(message.author, "Ссылки", bot.user)

        if ANTI["anti_mention"] and len(message.mentions) >= 5:
            try: await message.delete()
            except: pass
            return await add_warning(message.author, "Массовые упоминания", bot.user)

        if ANTI["anti_emoji"] and len(re.findall(r'<a?:\w+:\d+>|[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\U0001F680-\U0001F6FF\U0001F1E0-\U0001F1FF]', content)) > 10:
            try: await message.delete()
            except: pass
            return await add_warning(message.author, "Спам эмодзи", bot.user)

        if ANTI["anti_caps"] and len(content) > 10:
            letters = [c for c in content if c.isalpha()]
            if letters and (sum(1 for c in letters if c.isupper()) / len(letters)) > 0.7:
                try: await message.delete()
                except: pass
                return await add_warning(message.author, "КАПС", bot.user)

        if ANTI["anti_spam"]:
            uid = message.author.id; now = time.time()
            dq = _spam_hist[uid]; dq.append(now)
            while dq and now - dq[0] > ANTI["spam_window"]: dq.popleft()
            if len(dq) > ANTI["spam_max"]:
                dq.clear()
                try: await message.delete()
                except: pass
                await add_warning(message.author, "Флуд", bot.user)

    @commands.Cog.listener()
    async def on_member_join(self, member: discord.Member):
        ch = member.guild.get_channel(WELCOME_CHANNEL_ID)
        if ch:
            embed = discord.Embed(title=f"👋 Добро пожаловать, {member.name}!", description=f"Ты вступил на сервер **{member.guild.name}**.", color=discord.Color.green())
            embed.set_thumbnail(url=member.display_avatar.url)
            try: await ch.send(embed=embed)
            except: pass

        if not ANTI["anti_raid"] or member.bot: return
        if in_whitelist(member): return
        g = member.guild; now = time.time()
        dq = _join_hist[g.id]; dq.append(now)
        while dq and now - dq[0] > ANTI["raid_window"]: dq.popleft()

        if _raid_until.get(g.id) is None and len(dq) >= ANTI["raid_joins"]:
            _raid_until[g.id] = discord.utils.utcnow() + timedelta(minutes=15)
            _orig_verification[g.id] = g.verification_level
            try: await g.edit(verification_level=discord.VerificationLevel.high)
            except: pass
            await log_alert(g, "🛡️ РЕЖИМ АНТИРЕЙДА", f"Включен на 15 мин. Верификация: high.", discord.Color.red())
            asyncio.create_task(self._end_raid_later(g))

        if _raid_until.get(g.id) and _raid_until[g.id] > discord.utils.utcnow():
            if (discord.utils.utcnow() - member.created_at) < timedelta(days=3):
                try: await member.kick(reason="Анти-рейд")
                except: pass

    async def _end_raid_later(self, g):
        await asyncio.sleep(15 * 60)
        if _raid_until.get(g.id) and _raid_until[g.id] <= discord.utils.utcnow():
            _raid_until.pop(g.id, None)
            orig = _orig_verification.pop(g.id, None)
            if orig:
                try: await g.edit(verification_level=orig)
                except: pass
            await log_alert(g, "🛡️ Антирейд снят", "Верификация возвращена.", discord.Color.green())

    # ---- БЭКАПЫ ----
    @commands.Cog.listener()
    async def on_guild_channel_create(self, channel):
        backup_channel(channel)

    @commands.Cog.listener()
    async def on_guild_channel_update(self, before, after):
        backup_channel(after)

    @commands.Cog.listener()
    async def on_guild_join(self, guild):
        for ch in guild.channels: backup_channel(ch)

    # ---- АНТИ-НЮК ----
    async def _find_actor(self, guild, action, target_id):
        try:
            async for entry in guild.audit_logs(limit=5, action=action):
                if entry.target and entry.target.id == target_id:
                    if (discord.utils.utcnow() - entry.created_at) <= timedelta(seconds=20):
                        return entry.user
        except: pass
        return None

    async def _handle_nuke(self, guild, actor, what, target_name):
        member = guild.get_member(actor.id)
        if not member:
            await log_alert(guild, "💣 Анти-нюк", f"**{actor}** {what} **{target_name}** (не на сервере, действия не применены).", discord.Color.red())
            return
        # ВАЖНО: анти-нюк работает на всех, кроме овнеров
        if is_owner(member.id):
            await log_alert(guild, "🛠️ Доверенный (овнер)", f"**{actor}** {what} **{target_name}**. Игнорировано.", discord.Color.blue())
            return

        hits = _hit((guild.id, actor.id, "destroy"), ANTI["nuke_window"])
        await log_alert(guild, "💣 Анти-нюк", f"**{actor}** {what} **{target_name}**.\nСрабатываний: {hits}/{ANTI['nuke_hits']}.", discord.Color.red())

        if hits >= ANTI["nuke_hits"]:
            await strip_roles_and_kick(guild, member, "Анти-нюк: массовое удаление каналов/ролей")
            await log_alert(guild, "🔨 Анти-нюк: КИК + снятие ролей", f"**{actor}** наказан.", discord.Color.dark_red())

    @commands.Cog.listener()
    async def on_guild_channel_delete(self, channel):
        if not ANTI["anti_nuke"]: return
        # Восстанавливаем ВСЕГДА, даже если это овнер (бэкапы не помешают)
        g = channel.guild
        actor = await self._find_actor(g, discord.AuditLogAction.channel_delete, channel.id)

        # Восстановление
        restored = await restore_channel(g, channel.id)

        if not actor or actor.bot:
            if restored:
                await log_alert(g, "♻️ Канал восстановлен", f"**{channel.name}** пересоздан (актор не найден).", discord.Color.green())
            return

        await self._handle_nuke(g, actor, "удалил канал", channel.name)
        if restored:
            await log_alert(g, "♻️ Канал восстановлен", f"**{channel.name}** пересоздан.", discord.Color.green())

    @commands.Cog.listener()
    async def on_guild_role_delete(self, role):
        if not ANTI["anti_nuke"]: return
        g = role.guild
        actor = await self._find_actor(g, discord.AuditLogAction.role_delete, role.id)
        if not actor or actor.bot: return
        await self._handle_nuke(g, actor, "удалил роль", role.name)

    # ---- Остальные анти-нюк хуки: баны/кики ----
    @commands.Cog.listener()
    async def on_member_ban(self, guild, user):
        if not ANTI["anti_nuke"]: return
        actor = await self._find_actor(guild, discord.AuditLogAction.ban, user.id)
        if not actor or actor.bot: return
        await self._handle_nuke(guild, actor, "забанил", str(user))

    @commands.Cog.listener()
    async def on_member_remove(self, member):
        if not ANTI["anti_nuke"]: return
        g = member.guild
        actor = await self._find_actor(g, discord.AuditLogAction.kick, member.id)
        if not actor or actor.bot: return
        await self._handle_nuke(g, actor, "кикнул", str(member))

# ================= МОДАЛЬНЫЕ ОКНА ДЛЯ ЗАЯВОК =================
class ApplicationModal(discord.ui.Modal):
    def __init__(self, role_type: str):
        super().__init__(title=f"Анкета на {role_type}")
        self.role_type = role_type
        self.age = discord.ui.TextInput(label="Ваш возраст", placeholder="18", required=True, max_length=3)
        self.experience = discord.ui.TextInput(label="Опыт", style=discord.TextStyle.paragraph, required=True, max_length=500)
        self.why_me = discord.ui.TextInput(label="Почему именно вы?", style=discord.TextStyle.paragraph, required=True, max_length=500)
        self.time_online = discord.ui.TextInput(label="Онлайн", placeholder="Сколько часов в день?", required=True, max_length=100)
        self.contact = discord.ui.TextInput(label="Контакт (Discord/Telegram)", placeholder="@nickname", required=True, max_length=100)
        for item in (self.age, self.experience, self.why_me, self.time_online, self.contact): self.add_item(item)

    async def on_submit(self, interaction: discord.Interaction):
        data = load_json(DATA_FILE, {})
        uid = str(interaction.user.id)
        data.setdefault(uid, [])
        app_id = f"{int(datetime.now().timestamp())}_{uid}"
        app = {
            "id": app_id, "role_type": self.role_type, "user_id": uid, "user_name": str(interaction.user),
            "age": self.age.value, "experience": self.experience.value, "why_me": self.why_me.value,
            "time_online": self.time_online.value, "contact": self.contact.value,
            "status": "pending", "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        }
        data[uid].append(app)
        save_json(DATA_FILE, data)

        ch = bot.get_channel(REVIEW_CHANNEL_ID)
        if ch:
            embed = discord.Embed(title=f"📋 Новая заявка: {self.role_type}", color=discord.Color.blue(), timestamp=datetime.now())
            embed.add_field(name="Кандидат", value=f"{interaction.user.mention} ({interaction.user})", inline=False)
            embed.add_field(name="ID", value=app_id, inline=False)
            embed.add_field(name="Возраст", value=self.age.value, inline=True)
            embed.add_field(name="Онлайн", value=self.time_online.value, inline=True)
            embed.add_field(name="Контакт", value=self.contact.value, inline=True)
            embed.add_field(name="Опыт", value=self.experience.value, inline=False)
            embed.add_field(name="Мотивация", value=self.why_me.value, inline=False)
            embed.set_footer(text=f"User ID: {uid}")
            await ch.send(embed=embed, view=ReviewView(app_id, uid, self.role_type))

        await interaction.response.send_message("✅ Заявка отправлена! Ожидайте.", ephemeral=True)

class ReviewView(discord.ui.View):
    def __init__(self, app_id: str, user_id: str, role_type: str):
        super().__init__(timeout=None)
        self.app_id, self.user_id, self.role_type = app_id, user_id, role_type
        b1 = discord.ui.Button(label="Принять", style=discord.ButtonStyle.success, emoji="✅", custom_id=f"rv:acc:{app_id}")
        b1.callback = self.accept
        b2 = discord.ui.Button(label="Отклонить", style=discord.ButtonStyle.danger, emoji="❌", custom_id=f"rv:rej:{app_id}")
        b2.callback = self.reject
        self.add_item(b1); self.add_item(b2)

    async def _finish(self, interaction: discord.Interaction, accepted: bool):
        if not is_staff(interaction.user):
            return await interaction.response.send_message("❌ Только овнер.", ephemeral=True)
        data, app = find_app(self.app_id)
        if app:
            app["status"] = "accepted" if accepted else "rejected"
            app["reviewed_by"] = str(interaction.user)
            save_json(DATA_FILE, data)
            if accepted: add_stat(self.user_id, "reputation", 10)
        embed = interaction.message.embeds[0]
        embed.color = discord.Color.green() if accepted else discord.Color.red()
        embed.add_field(name="Статус", value=("✅ Принято" if accepted else "❌ Отклонено") + f" ({interaction.user.mention})", inline=False)
        await interaction.response.edit_message(embed=embed, view=None)
        try:
            user = await bot.fetch_user(int(self.user_id))
            await user.send(f"🎉 Ваша заявка на **{self.role_type}** была **принята**!" if accepted else f"😔 Ваша заявка на **{self.role_type}** была **отклонена**.")
        except: pass

    async def accept(self, i): await self._finish(i, True)
    async def reject(self, i): await self._finish(i, False)

class ApplyButtonView(discord.ui.View):
    def __init__(self, role_type: str):
        super().__init__(timeout=None)
        b = discord.ui.Button(label=f"Подать заявку", style=discord.ButtonStyle.primary, emoji="📝", custom_id=f"apply_{role_type.lower()}")
        b.callback = self.open_modal
        self.role_type = role_type
        self.add_item(b)

    async def open_modal(self, i: discord.Interaction): await i.response.send_modal(ApplicationModal(self.role_type))

# ================= ТИКЕТЫ =================
class TicketPanelView(discord.ui.View):
    def __init__(self):
        super().__init__(timeout=None)
        b = discord.ui.Button(label="Создать обращение", style=discord.ButtonStyle.success, emoji="🎫", custom_id="create_ticket")
        b.callback = self.create_ticket
        self.add_item(b)

    async def create_ticket(self, interaction: discord.Interaction):
        guild = interaction.guild; uid = interaction.user.id
        for ch_id, owner_id in list(ACTIVE_TICKETS.items()):
            if owner_id == uid:
                ch = guild.get_channel(int(ch_id))
                if ch: return await interaction.response.send_message(f"❌ У вас уже есть тикет: {ch.mention}", ephemeral=True)
                else: del ACTIVE_TICKETS[ch_id]; save_tickets()

        overwrites = {
            guild.default_role: discord.PermissionOverwrite(read_messages=False),
            interaction.user: discord.PermissionOverwrite(read_messages=True, send_messages=True),
            guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True)
        }
        staff_role = discord.utils.get(guild.roles, name=STAFF_ROLE_NAME)
        if staff_role: overwrites[staff_role] = discord.PermissionOverwrite(read_messages=True, send_messages=True)

        category = discord.utils.get(guild.categories, name=TICKET_CATEGORY_NAME)
        if not category:
            try: category = await guild.create_category(TICKET_CATEGORY_NAME)
            except: pass

        try:
            channel = await guild.create_text_channel(name=f"ticket-{interaction.user.name}", category=category, overwrites=overwrites)
            ACTIVE_TICKETS[str(channel.id)] = uid
            save_tickets()
            add_stat(uid, "tickets", 1)
            embed = discord.Embed(title="🎫 Новое обращение", description=f"Привет, {interaction.user.mention}!\nОпишите вашу проблему, и персонал скоро ответит.", color=discord.Color.green())
            close_btn = discord.ui.Button(label="Закрыть тикет", style=discord.ButtonStyle.danger, emoji="🔒", custom_id="close_ticket")
            async def close_cb(i: discord.Interaction):
                if not (is_staff(i.user) or i.user.id == uid): return await i.response.send_message("❌ Вы не можете закрыть этот тикет.", ephemeral=True)
                await i.response.send_message("🔒 Тикет закрывается...", ephemeral=True)
                if str(i.channel.id) in ACTIVE_TICKETS: del ACTIVE_TICKETS[str(i.channel.id)]; save_tickets()
                await asyncio.sleep(3)
                try: await i.channel.delete(reason="Тикет закрыт")
                except: pass
            close_btn.callback = close_cb
            view = discord.ui.View(); view.add_item(close_btn)
            await channel.send(embed=embed, view=view)
            await interaction.response.send_message(f"✅ Тикет создан: {channel.mention}", ephemeral=True)
        except Exception as e:
            await interaction.response.send_message(f"❌ Ошибка: {e}", ephemeral=True)

# ================= КОМАНДЫ =================

# -------- ПАНЕЛЬ ЗАЩИТЫ --------
@bot.tree.command(name="защита", description="Панель настройки защиты (овнер)")
async def protect_cmd(i: discord.Interaction):
    if not is_admin(i.user): return await i.response.send_message("❌ Только овнер.", ephemeral=True)
    await i.response.send_message(embed=anti_embed(), view=AntiSettingsView())

# -------- НАСТРОЙКИ АНТИ-НЮКА --------
@bot.tree.command(name="антинюк", description="Настроить анти-нюк (овнер)")
@app_commands.describe(hits="Сколько удалений до кика", window="За сколько секунд")
async def antinuke_cfg(i: discord.Interaction, hits: int, window: int):
    if not is_admin(i.user): return await i.response.send_message("❌ Только овнер.", ephemeral=True)
    ANTI["nuke_hits"] = hits
    ANTI["nuke_window"] = window
    save_anti()
    await i.response.send_message(f"✅ Анти-нюк: {hits} удалений за {window} сек.", ephemeral=True)

# -------- WHITELIST --------
@bot.tree.command(name="вайтлист", description="Посмотреть whitelist защиты (овнер)")
async def whitelist_show(i: discord.Interaction):
    if not is_admin(i.user): return await i.response.send_message("❌ Только овнер.", ephemeral=True)
    users = WHITELIST.get("users", [])
    roles = WHITELIST.get("roles", [])
    embed = discord.Embed(title="📋 Whitelist защиты", color=discord.Color.blue())
    embed.add_field(name="👤 Пользователи", value=", ".join(f"<@{u}>" for u in users) or "Пусто", inline=False)
    embed.add_field(name="🎭 Роли", value=", ".join(f"<@&{r}>" for r in roles) or "Пусто", inline=False)
    await i.response.send_message(embed=embed, ephemeral=True)

@bot.tree.command(name="вайтлист_добавить_юзера", description="Добавить пользователя в whitelist (овнер)")
async def whitelist_add_user(i: discord.Interaction, user: discord.User):
    if not is_admin(i.user): return await i.response.send_message("❌ Только овнер.", ephemeral=True)
    if user.id in WHITELIST["users"]: return await i.response.send_message("❌ Уже в whitelist.", ephemeral=True)
    WHITELIST["users"].append(user.id); save_whitelist()
    await i.response.send_message(f"✅ {user.mention} добавлен в whitelist защиты (анти-спам, анти-линк, анти-рейд и т.д. не срабатывают).", ephemeral=True)

@bot.tree.command(name="вайтлист_удалить_юзера", description="Убрать пользователя из whitelist (овнер)")
async def whitelist_remove_user(i: discord.Interaction, user: discord.User):
    if not is_admin(i.user): return await i.response.send_message("❌ Только овнер.", ephemeral=True)
    if user.id not in WHITELIST["users"]: return await i.response.send_message("❌ Не в whitelist.", ephemeral=True)
    WHITELIST["users"].remove(user.id); save_whitelist()
    await i.response.send_message(f"✅ {user.mention} убран из whitelist.", ephemeral=True)

@bot.tree.command(name="вайтлист_добавить_роль", description="Добавить роль в whitelist (овнер)")
async def whitelist_add_role(i: discord.Interaction, role: discord.Role):
    if not is_admin(i.user): return await i.response.send_message("❌ Только овнер.", ephemeral=True)
    if role.id in WHITELIST["roles"]: return await i.response.send_message("❌ Уже в whitelist.", ephemeral=True)
    WHITELIST["roles"].append(role.id); save_whitelist()
    await i.response.send_message(f"✅ Роль {role.mention} добавлена в whitelist защиты.", ephemeral=True)

@bot.tree.command(name="вайтлист_удалить_роль", description="Убрать роль из whitelist (овнер)")
async def whitelist_remove_role(i: discord.Interaction, role: discord.Role):
    if not is_admin(i.user): return await i.response.send_message("❌ Только овнер.", ephemeral=True)
    if role.id not in WHITELIST["roles"]: return await i.response.send_message("❌ Не в whitelist.", ephemeral=True)
    WHITELIST["roles"].remove(role.id); save_whitelist()
    await i.response.send_message(f"✅ Роль {role.mention} убрана из whitelist.", ephemeral=True)

# -------- МОДЕРАЦИЯ (ТОЛЬКО ОВНЕР) --------
@bot.tree.command(name="заметка", description="Добавить заметку о пользователе (скрыто) (овнер)")
async def add_note(i: discord.Interaction, member: discord.Member):
    if not is_staff(i.user): return await i.response.send_message("❌ Нет прав.", ephemeral=True)
    await i.response.send_modal(NoteModal(member.id))

@bot.tree.command(name="заметки", description="Посмотреть заметки о пользователе (овнер)")
async def view_notes(i: discord.Interaction, member: discord.Member):
    if not is_staff(i.user): return await i.response.send_message("❌ Нет прав.", ephemeral=True)
    uid = str(member.id)
    if uid not in NOTES or not NOTES[uid]: return await i.response.send_message("📭 Заметок нет.", ephemeral=True)
    embed = discord.Embed(title=f"📝 Заметки о {member.name}", color=discord.Color.orange())
    for n in NOTES[uid]:
        embed.add_field(name=f"Автор: {n['author']} ({n['time']})", value=n['text'], inline=False)
    await i.response.send_message(embed=embed, ephemeral=True)

@bot.tree.command(name="мут", description="Выдать мут (овнер)")
async def mute_cmd(i: discord.Interaction, member: discord.Member, minutes: int, reason: str = "Не указана"):
    if not is_staff(i.user): return await i.response.send_message("❌ Нет прав.", ephemeral=True)
    if is_trusted(member): return await i.response.send_message("❌ Нельзя наказать этого пользователя.", ephemeral=True)
    try:
        await member.timeout(timedelta(minutes=minutes), reason=reason)
        await i.response.send_message(f"🔇 {member.mention} замучен на {minutes} мин. Причина: {reason}")
        await log_alert(i.guild, "🔇 Ручной мут", f"Модератор: {i.user.mention}\nЦель: {member.mention}\nВремя: {minutes} мин.\nПричина: {reason}", discord.Color.orange())
    except Exception as e: await i.response.send_message(f"❌ Ошибка: {e}", ephemeral=True)

@bot.tree.command(name="размут", description="Снять мут (овнер)")
async def unmute_cmd(i: discord.Interaction, member: discord.Member):
    if not is_staff(i.user): return await i.response.send_message("❌ Нет прав.", ephemeral=True)
    try:
        await member.timeout(None)
        await i.response.send_message(f"🔊 {member.mention} размучен.")
    except Exception as e: await i.response.send_message(f"❌ Ошибка: {e}", ephemeral=True)

@bot.tree.command(name="варн", description="Выдать предупреждение (овнер)")
async def warn_cmd(i: discord.Interaction, member: discord.Member, reason: str = "Не указана"):
    if not is_staff(i.user): return await i.response.send_message("❌ Нет прав.", ephemeral=True)
    if is_trusted(member): return await i.response.send_message("❌ Нельзя выдать варн этому пользователю.", ephemeral=True)
    await add_warning(member, reason, i.user)
    await i.response.send_message(f"⚠️ {member.mention} получил предупреждение. Причина: {reason}", ephemeral=True)

@bot.tree.command(name="снять_варн", description="Снять 1 варн (овнер)")
async def unwarn_cmd(i: discord.Interaction, member: discord.Member):
    if not is_staff(i.user): return await i.response.send_message("❌ Нет прав.", ephemeral=True)
    uid = str(member.id)
    if uid in WARNINGS and WARNINGS[uid]["count"] > 0:
        WARNINGS[uid]["count"] -= 1; save_warnings()
        await i.response.send_message(f"✅ Снят 1 варн с {member.mention}. Осталось: {WARNINGS[uid]['count']}", ephemeral=True)
    else:
        await i.response.send_message(f"❌ У {member.mention} нет варнов.", ephemeral=True)

@bot.tree.command(name="очистить_варны", description="Полностью очистить варны (овнер)")
async def clear_warns(i: discord.Interaction, member: discord.Member):
    if not is_staff(i.user): return await i.response.send_message("❌ Нет прав.", ephemeral=True)
    uid = str(member.id)
    if uid in WARNINGS:
        WARNINGS[uid] = {"count": 0, "reasons": []}; save_warnings()
    await i.response.send_message(f"✅ Все варны сняты с {member.mention}.", ephemeral=True)

@bot.tree.command(name="варны", description="Посмотреть варны (овнер)")
async def list_warns(i: discord.Interaction, member: discord.Member):
    uid = str(member.id)
    if uid not in WARNINGS or WARNINGS[uid]["count"] == 0: return await i.response.send_message(f"У {member.mention} нет варнов.", ephemeral=True)
    embed = discord.Embed(title=f"⚠️ Варны {member.name}", description=f"Всего: {WARNINGS[uid]['count']}/3", color=discord.Color.red())
    for w in WARNINGS[uid]["reasons"]:
        embed.add_field(name=w["reason"], value=f"Модератор: {w['mod']}\nДата: {w['time']}", inline=False)
    await i.response.send_message(embed=embed, ephemeral=True)

@bot.tree.command(name="бан", description="Забанить (овнер)")
async def ban_cmd(i: discord.Interaction, member: discord.Member, reason: str = "Не указана"):
    if not is_staff(i.user): return await i.response.send_message("❌ Нет прав.", ephemeral=True)
    if is_trusted(member): return await i.response.send_message("❌ Нельзя забанить этого пользователя.", ephemeral=True)
    try:
        await member.ban(reason=f"{reason} | {i.user}")
        await i.response.send_message(f"🔨 {member.mention} забанен. Причина: {reason}")
        await log_alert(i.guild, "🔨 Бан", f"Модератор: {i.user.mention}\nЦель: {member}\nПричина: {reason}", discord.Color.dark_red())
    except Exception as e: await i.response.send_message(f"❌ Ошибка: {e}", ephemeral=True)

@bot.tree.command(name="кик", description="Кикнуть (овнер)")
async def kick_cmd(i: discord.Interaction, member: discord.Member, reason: str = "Не указана"):
    if not is_staff(i.user): return await i.response.send_message("❌ Нет прав.", ephemeral=True)
    if is_trusted(member): return await i.response.send_message("❌ Нельзя кикнуть этого пользователя.", ephemeral=True)
    try:
        await member.kick(reason=f"{reason} | {i.user}")
        await i.response.send_message(f"👢 {member.mention} кикнут. Причина: {reason}")
    except Exception as e: await i.response.send_message(f"❌ Ошибка: {e}", ephemeral=True)

@bot.tree.command(name="разбан", description="Разбанить по ID (овнер)")
async def unban_cmd(i: discord.Interaction, user_id: str, reason: str = "Не указана"):
    if not is_staff(i.user): return await i.response.send_message("❌ Нет прав.", ephemeral=True)
    try:
        user = await bot.fetch_user(int(user_id))
        await i.guild.unban(user, reason=reason)
        await i.response.send_message(f"✅ {user} разбанен. Причина: {reason}", ephemeral=True)
    except Exception as e: await i.response.send_message(f"❌ Ошибка: {e}", ephemeral=True)

@bot.tree.command(name="выдать_роль", description="Выдать роль (овнер)")
async def give_role_cmd(i: discord.Interaction, member: discord.Member, role: discord.Role):
    if not is_staff(i.user): return await i.response.send_message("❌ Нет прав.", ephemeral=True)
    try:
        await member.add_roles(role)
        await i.response.send_message(f"✅ {member.mention} выдана роль {role.mention}.", ephemeral=True)
    except Exception as e: await i.response.send_message(f"❌ Ошибка: {e}", ephemeral=True)

@bot.tree.command(name="снять_роль", description="Снять роль (овнер)")
async def remove_role_cmd(i: discord.Interaction, member: discord.Member, role: discord.Role):
    if not is_staff(i.user): return await i.response.send_message("❌ Нет прав.", ephemeral=True)
    try:
        await member.remove_roles(role)
        await i.response.send_message(f"✅ У {member.mention} снята роль {role.mention}.", ephemeral=True)
    except Exception as e: await i.response.send_message(f"❌ Ошибка: {e}", ephemeral=True)

@bot.tree.command(name="очистить", description="Удалить сообщения (овнер)")
async def clear_cmd(i: discord.Interaction, amount: int):
    if not is_staff(i.user): return await i.response.send_message("❌ Нет прав.", ephemeral=True)
    await i.response.defer(ephemeral=True)
    deleted = await i.channel.purge(limit=amount)
    await i.followup.send(f"✅ Удалено {len(deleted)}.", ephemeral=True)

@bot.tree.command(name="сказать", description="Отправить сообщение от имени бота (овнер)")
async def say_cmd(i: discord.Interaction, text: str):
    if not is_staff(i.user): return await i.response.send_message("❌ Нет прав.", ephemeral=True)
    await i.channel.send(text)
    await i.response.send_message("✅ Отправлено.", ephemeral=True)

# -------- ПУБЛИЧНЫЕ КОМАНДЫ --------
@bot.tree.command(name="инфо", description="Информация о пользователе")
async def user_info(i: discord.Interaction, member: discord.Member):
    rep = get_stat(member.id, "reputation")
    tickets = get_stat(member.id, "tickets")
    msgs = get_stat(member.id, "messages")
    warns = WARNINGS.get(str(member.id), {}).get("count", 0)
    coins = get_coins(member.id)
    notes_count = len(NOTES.get(str(member.id), []))
    embed = discord.Embed(title=f"👤 Информация: {member.name}", color=discord.Color.blue())
    embed.set_thumbnail(url=member.display_avatar.url)
    embed.add_field(name="ID", value=member.id, inline=False)
    embed.add_field(name="Создан", value=member.created_at.strftime("%d.%m.%Y"), inline=True)
    embed.add_field(name="Зашел", value=member.joined_at.strftime("%d.%m.%Y") if member.joined_at else "Неизвестно", inline=True)
    embed.add_field(name="Репутация", value=f"⭐ {rep}", inline=True)
    embed.add_field(name="Тикеты", value=f"🎫 {tickets}", inline=True)
    embed.add_field(name="Сообщений", value=f"💬 {msgs}", inline=True)
    embed.add_field(name="Монеты", value=f"{CURRENCY} {coins}", inline=True)
    embed.add_field(name="Варны", value=f"⚠️ {warns}/3", inline=True)
    embed.add_field(name="Заметки", value=f"📝 {notes_count}", inline=True)
    await i.response.send_message(embed=embed)

@bot.tree.command(name="баланс", description="Посмотреть баланс монет")
async def balance_cmd(i: discord.Interaction):
    await i.response.send_message(f"💰 Ваш баланс: **{get_coins(i.user.id)}** {CURRENCY}", ephemeral=True)

@bot.tree.command(name="бонус", description="Ежедневная награда")
async def daily_cmd(i: discord.Interaction):
    uid = str(i.user.id)
    if uid not in ECONOMY: ECONOMY[uid] = {"coins": 0, "last_daily": 0}
    last = ECONOMY[uid].get("last_daily", 0); now = time.time()
    if now - last < 86400: return await i.response.send_message("⏳ Вы уже получали награду сегодня.", ephemeral=True)
    reward = random.randint(100, 500)
    ECONOMY[uid]["coins"] += reward; ECONOMY[uid]["last_daily"] = now
    save_economy()
    await i.response.send_message(f"🎁 Вы получили бонус: **{reward}** {CURRENCY}!", ephemeral=True)

@bot.tree.command(name="магазин", description="Магазин ролей")
async def shop_cmd(i: discord.Interaction):
    embed = discord.Embed(title="🛒 Магазин", color=discord.Color.gold())
    for name, data in SHOP_ITEMS.items():
        embed.add_field(name=name, value=f"Цена: {data['price']} {CURRENCY}\nРоль: {data['role']}", inline=False)
    embed.set_footer(text="Используйте /купить [название]")
    await i.response.send_message(embed=embed)

@bot.tree.command(name="купить", description="Купить предмет")
async def buy_cmd(i: discord.Interaction, item: str):
    if item not in SHOP_ITEMS: return await i.response.send_message("❌ Нет такого предмета.", ephemeral=True)
    price = SHOP_ITEMS[item]["price"]; coins = get_coins(i.user.id)
    if coins < price: return await i.response.send_message("❌ Недостаточно монет.", ephemeral=True)
    role = discord.utils.get(i.guild.roles, name=SHOP_ITEMS[item]["role"])
    if not role: return await i.response.send_message("❌ Роль не найдена.", ephemeral=True)
    add_coins(i.user.id, -price); await i.user.add_roles(role)
    await i.response.send_message(f"✅ Вы купили **{item}**!", ephemeral=True)

@bot.tree.command(name="опрос", description="Создать опрос")
async def poll_cmd(i: discord.Interaction, question: str, options: str):
    opts = [o.strip() for o in options.split(",") if o.strip()]
    if len(opts) < 2 or len(opts) > 4: return await i.response.send_message("❌ 2-4 варианта.", ephemeral=True)
    embed = discord.Embed(title=f"📊 {question}", description="\n".join(f"**{o}**: 0 голосов" for o in opts), color=discord.Color.green())

    class PollView(discord.ui.View):
        def __init__(self):
            super().__init__(timeout=None); self.votes = {opt: 0 for opt in opts}
            for opt in opts:
                b = discord.ui.Button(label=opt, style=discord.ButtonStyle.secondary)
                b.callback = self._make_cb(opt); self.add_item(b)
        def _make_cb(self, opt):
            async def cb(inter):
                self.votes[opt] += 1
                desc = "\n".join(f"**{k}**: {v} голосов" for k, v in self.votes.items())
                await inter.response.edit_message(embed=discord.Embed(title=f"📊 {question}", description=desc, color=discord.Color.green()), view=self)
            return cb
    await i.response.send_message(embed=embed, view=PollView())

@bot.tree.command(name="топ", description="Топ по сообщениям")
async def top_cmd(i: discord.Interaction):
    sorted_users = sorted(USER_STATS.items(), key=lambda x: x[1].get("messages", 0), reverse=True)[:10]
    desc = ""
    for idx, (uid, stats) in enumerate(sorted_users, 1):
        user = i.guild.get_member(int(uid))
        name = user.display_name if user else f"ID: {uid}"
        desc += f"{idx}. **{name}** — {stats.get('messages', 0)} сообщений\n"
    await i.response.send_message(embed=discord.Embed(title="🏆 Топ", description=desc or "Нет данных", color=discord.Color.gold()))

# -------- АДМИН-ПАНЕЛИ (ОВНЕР) --------
@bot.tree.command(name="автороль", description="Кнопка выдачи роли (овнер)")
async def autorole_cmd(i: discord.Interaction, role: discord.Role, emoji: str = "🎭"):
    if not is_admin(i.user): return await i.response.send_message("❌ Только овнер.", ephemeral=True)
    class AutoRoleView(discord.ui.View):
        def __init__(self):
            super().__init__(timeout=None)
            b = discord.ui.Button(label="Получить роль", style=discord.ButtonStyle.primary, emoji=emoji)
            b.callback = self.give_role; self.add_item(b)
        async def give_role(self, inter: discord.Interaction):
            if role in inter.user.roles:
                await inter.user.remove_roles(role); await inter.response.send_message(f"✅ Снята {role.name}.", ephemeral=True)
            else:
                await inter.user.add_roles(role); await inter.response.send_message(f"✅ Выдана {role.name}.", ephemeral=True)
    await i.channel.send(embed=discord.Embed(title="🎭 Получение роли", description=f"Нажмите, чтобы получить {role.mention}", color=discord.Color.blue()), view=AutoRoleView())
    await i.response.send_message("✅ Панель создана.", ephemeral=True)

@bot.tree.command(name="setup_panels", description="Панели заявок (овнер)")
async def setup_panels(i: discord.Interaction):
    if not is_admin(i.user): return await i.response.send_message("❌ Только овнер.", ephemeral=True)
    await i.response.defer(ephemeral=True)
    panels = [
        (LEADER_CHANNEL_ID, "Лидер", "👑", discord.Color.gold(), "Хочешь стать лидером?"),
        (ADMIN_CHANNEL_ID, "Администратор", "🛡️", discord.Color.blue(), "Хочешь помогать серверу?"),
        (MEDIA_CHANNEL_ID, "Медиа", "🎥", discord.Color.purple(), "Создаёшь контент?")
    ]
    for cid, role, emoji, color, desc in panels:
        ch = bot.get_channel(cid)
        if ch:
            try: await ch.purge(limit=10, check=lambda m: m.author == bot.user)
            except: pass
            await ch.send(embed=discord.Embed(title=f"{emoji} Заявка на роль: {role}", description=desc, color=color), view=ApplyButtonView(role))
    await i.followup.send("✅ Панели обновлены.", ephemeral=True)

@bot.tree.command(name="setup_tickets", description="Панель тикетов (овнер)")
async def setup_tickets(i: discord.Interaction):
    if not is_admin(i.user): return await i.response.send_message("❌ Только овнер.", ephemeral=True)
    embed = discord.Embed(title="🎫 Служба поддержки", description="Нажмите кнопку ниже, чтобы создать обращение.", color=discord.Color.teal())
    await i.channel.send(embed=embed, view=TicketPanelView())
    await i.response.send_message("✅ Панель тикетов установлена.", ephemeral=True)

@bot.tree.command(name="статистика", description="Статистика сервера (овнер)")
async def stats_cmd(i: discord.Interaction):
    if not is_admin(i.user): return await i.response.send_message("❌ Только овнер.", ephemeral=True)
    data = load_json(DATA_FILE, {})
    total = sum(len(a) for a in data.values())
    pend = sum(1 for a in data.values() for x in a if x["status"] == "pending")
    acc = sum(1 for a in data.values() for x in a if x["status"] == "accepted")
    embed = discord.Embed(title="📊 Статистика", color=discord.Color.gold())
    embed.add_field(name="Всего заявок", value=str(total))
    embed.add_field(name="В ожидании", value=str(pend))
    embed.add_field(name="Принято", value=str(acc))
    embed.add_field(name="Открытых тикетов", value=str(len(ACTIVE_TICKETS)))
    embed.add_field(name="Whitelist юзеров", value=str(len(WHITELIST.get("users", []))))
    embed.add_field(name="Whitelist ролей", value=str(len(WHITELIST.get("roles", []))))
    await i.response.send_message(embed=embed, ephemeral=True)

@bot.tree.command(name="пинг", description="Проверить задержку бота")
async def ping_cmd(i: discord.Interaction):
    await i.response.send_message(f"🏓 Понг! Задержка: **{round(bot.latency*1000)}ms**", ephemeral=True)

@bot.tree.command(name="создать_бэкапы", description="Сделать бэкап всех каналов (овнер)")
async def backup_all(i: discord.Interaction):
    if not is_admin(i.user): return await i.response.send_message("❌ Только овнер.", ephemeral=True)
    await i.response.defer(ephemeral=True)
    for ch in i.guild.channels: backup_channel(ch)
    await i.followup.send(f"✅ Бэкапы сохранены: {len(i.guild.channels)} каналов.", ephemeral=True)

# ================= ЗАПУСК =================
@bot.event
async def setup_hook():
    for role in ("Лидер", "Администратор", "Медиа"): bot.add_view(ApplyButtonView(role))
    bot.add_view(AntiSettingsView())
    bot.add_view(TicketPanelView())

    for ch_id in list(ACTIVE_TICKETS.keys()):
        close_btn = discord.ui.Button(label="Закрыть тикет", style=discord.ButtonStyle.danger, emoji="🔒", custom_id="close_ticket")
        async def close_cb(i: discord.Interaction):
            await i.response.send_message("🔒 Закрывается...", ephemeral=True)
            if str(i.channel.id) in ACTIVE_TICKETS: del ACTIVE_TICKETS[str(i.channel.id)]; save_tickets()
            await asyncio.sleep(3)
            try: await i.channel.delete()
            except: pass
        close_btn.callback = close_cb
        v = discord.ui.View(timeout=None); v.add_item(close_btn)
        bot.add_view(v)

    data = load_json(DATA_FILE, {})
    for apps in data.values():
        for app in apps:
            if app.get("status") == "pending":
                bot.add_view(ReviewView(app["id"], app["user_id"], app["role_type"]))

    await bot.add_cog(Protection(bot))

@bot.event
async def on_ready():
    print(f"Бот {bot.user} запущен! Владельцы: {OWNER_IDS}")
    try:
        await bot.tree.sync()
        print("Команды синхронизированы.")
    except Exception as e: print(f"Ошибка синхронизации: {e}")

if __name__ == "__main__":
    bot.run(TOKEN)