1885 lines
76 KiB
Python
1885 lines
76 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
TOKEN = "Ваш токен"
|
||
|
||
import discord
|
||
from discord.ext import commands
|
||
from discord import ui, ButtonStyle, SelectOption, app_commands
|
||
import json
|
||
import asyncio
|
||
from datetime import datetime, timedelta
|
||
import socket
|
||
import struct
|
||
import re
|
||
|
||
# =====================================================
|
||
# 📁 РАБОТА С ДАННЫМИ
|
||
# =====================================================
|
||
|
||
STAFF_FILE = "staff_data.json"
|
||
REQUESTS_FILE = "requests.json"
|
||
ACTIONS_FILE = "actions.json"
|
||
CONFIG_FILE = "config.json"
|
||
BANS_FILE = "bans.json"
|
||
RANKS_FILE = "ranks.json"
|
||
PANELS_FILE = "panels.json"
|
||
WARNS_FILE = "warns.json" # Файл для хранения предупреждений
|
||
|
||
def load_json(file):
|
||
try:
|
||
with open(file, 'r') as f:
|
||
return json.load(f)
|
||
except:
|
||
return {}
|
||
|
||
def save_json(file, data):
|
||
with open(file, 'w') as f:
|
||
json.dump(data, f, indent=4)
|
||
|
||
def load_staff():
|
||
return load_json(STAFF_FILE)
|
||
|
||
def save_staff(data):
|
||
save_json(STAFF_FILE, data)
|
||
|
||
def load_requests():
|
||
return load_json(REQUESTS_FILE)
|
||
|
||
def save_requests(data):
|
||
save_json(REQUESTS_FILE, data)
|
||
|
||
def load_actions():
|
||
return load_json(ACTIONS_FILE)
|
||
|
||
def save_actions(data):
|
||
save_json(ACTIONS_FILE, data)
|
||
|
||
def load_config():
|
||
config = load_json(CONFIG_FILE)
|
||
if not config:
|
||
config = {
|
||
"rcon_host": "localhost",
|
||
"rcon_port": 25575,
|
||
"rcon_password": "",
|
||
"log_channel_id": None,
|
||
"dm_on_staff_change": True,
|
||
"ban_command": "tempban",
|
||
"warn_limit": 3, # Количество предупреждений до бана
|
||
"ban_duration": 2880 # 2 дня в минутах
|
||
}
|
||
save_json(CONFIG_FILE, config)
|
||
return config
|
||
|
||
def save_config(data):
|
||
save_json(CONFIG_FILE, data)
|
||
|
||
def load_bans():
|
||
return load_json(BANS_FILE)
|
||
|
||
def save_bans(data):
|
||
save_json(BANS_FILE, data)
|
||
|
||
def load_ranks():
|
||
ranks = load_json(RANKS_FILE)
|
||
if not ranks:
|
||
ranks = {
|
||
"owner": {"name": "👑 Владелец", "weight": 16},
|
||
"dep.owner": {"name": "⚜️ Зам. Владельца", "weight": 15},
|
||
"create": {"name": "✨ Создатель", "weight": 14},
|
||
"moder": {"name": "🛡️ Модератор", "weight": 13},
|
||
"ml.moder": {"name": "🔰 Ст. Модератор", "weight": 12},
|
||
"dragon": {"name": "🐉 Дракон", "weight": 11},
|
||
"titan": {"name": "🗿 Титан", "weight": 10},
|
||
"magic": {"name": "🔮 Магия", "weight": 9},
|
||
"legend": {"name": "⭐ Легенда", "weight": 8},
|
||
"ultra": {"name": "💎 Ультра", "weight": 7},
|
||
"hero": {"name": "🦸 Герой", "weight": 6},
|
||
"premium+": {"name": "💠 Премиум+", "weight": 5},
|
||
"premium": {"name": "💠 Премиум", "weight": 4},
|
||
"vip+": {"name": "⭐ ВИП+", "weight": 3},
|
||
"vip": {"name": "⭐ ВИП", "weight": 2},
|
||
"default": {"name": "📌 Обычный", "weight": 1}
|
||
}
|
||
save_json(RANKS_FILE, ranks)
|
||
return ranks
|
||
|
||
def save_ranks(data):
|
||
save_json(RANKS_FILE, data)
|
||
|
||
def load_panels():
|
||
return load_json(PANELS_FILE)
|
||
|
||
def save_panels(data):
|
||
save_json(PANELS_FILE, data)
|
||
|
||
def load_warns():
|
||
return load_json(WARNS_FILE)
|
||
|
||
def save_warns(data):
|
||
save_json(WARNS_FILE, data)
|
||
|
||
def add_panel(guild_id, channel_id, message_id, panel_type="main"):
|
||
panels = load_panels()
|
||
key = f"{guild_id}_{channel_id}_{message_id}"
|
||
panels[key] = {
|
||
"guild_id": guild_id,
|
||
"channel_id": channel_id,
|
||
"message_id": message_id,
|
||
"panel_type": panel_type,
|
||
"created_at": datetime.now().isoformat()
|
||
}
|
||
save_panels(panels)
|
||
|
||
def remove_panel(guild_id, channel_id, message_id):
|
||
panels = load_panels()
|
||
key = f"{guild_id}_{channel_id}_{message_id}"
|
||
if key in panels:
|
||
del panels[key]
|
||
save_panels(panels)
|
||
return True
|
||
return False
|
||
|
||
def log_action(mod_id, action, target, reason=""):
|
||
actions = load_actions()
|
||
if str(mod_id) not in actions:
|
||
actions[str(mod_id)] = []
|
||
actions[str(mod_id)].append({
|
||
"action": action,
|
||
"target": target,
|
||
"reason": reason,
|
||
"date": datetime.now().isoformat()
|
||
})
|
||
save_actions(actions)
|
||
|
||
# =====================================================
|
||
# 🎮 RCON КЛИЕНТ
|
||
# =====================================================
|
||
|
||
class RconClient:
|
||
def __init__(self, host, port, password):
|
||
self.host = host
|
||
self.port = port
|
||
self.password = password
|
||
self.socket = None
|
||
self.request_id = 0
|
||
|
||
def connect(self):
|
||
try:
|
||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
self.socket.settimeout(3)
|
||
self.socket.connect((self.host, self.port))
|
||
|
||
auth_packet = self._create_packet(3, self.password)
|
||
self.socket.send(auth_packet)
|
||
response = self.socket.recv(4096)
|
||
|
||
if response and len(response) > 8:
|
||
response_id = struct.unpack('<i', response[:4])[0]
|
||
if response_id == -1:
|
||
self.socket.close()
|
||
self.socket = None
|
||
return False
|
||
return True
|
||
return False
|
||
except Exception as e:
|
||
print(f"RCON connection error: {e}")
|
||
self.socket = None
|
||
return False
|
||
|
||
def _create_packet(self, packet_type, body):
|
||
self.request_id += 1
|
||
body_bytes = body.encode('utf-8')
|
||
packet = struct.pack('<ii', self.request_id, packet_type) + body_bytes + b'\x00\x00'
|
||
return struct.pack('<i', len(packet)) + packet
|
||
|
||
def send_command(self, command):
|
||
if not self.socket:
|
||
if not self.connect():
|
||
return None
|
||
|
||
try:
|
||
packet = self._create_packet(2, command)
|
||
self.socket.send(packet)
|
||
response = self.socket.recv(4096)
|
||
|
||
if len(response) > 8:
|
||
return response[8:-2].decode('utf-8', errors='ignore')
|
||
return None
|
||
except Exception as e:
|
||
print(f"RCON command error: {e}")
|
||
self.socket = None
|
||
return None
|
||
|
||
def close(self):
|
||
if self.socket:
|
||
try:
|
||
self.socket.close()
|
||
except:
|
||
pass
|
||
self.socket = None
|
||
|
||
rcon_client = None
|
||
|
||
def get_rcon():
|
||
global rcon_client
|
||
config = load_config()
|
||
|
||
if rcon_client is None:
|
||
rcon_client = RconClient(
|
||
config.get("rcon_host", "localhost"),
|
||
config.get("rcon_port", 25575),
|
||
config.get("rcon_password", "")
|
||
)
|
||
|
||
return rcon_client
|
||
|
||
async def execute_mc_command_async(command):
|
||
"""Асинхронная обертка для выполнения RCON команд"""
|
||
loop = asyncio.get_event_loop()
|
||
return await loop.run_in_executor(None, execute_mc_command_sync, command)
|
||
|
||
def execute_mc_command_sync(command):
|
||
"""Синхронная версия выполнения RCON команд"""
|
||
try:
|
||
rcon = get_rcon()
|
||
if not rcon.connect():
|
||
return None, "❌ Не удалось подключиться к серверу! Проверьте настройки RCON."
|
||
|
||
result = rcon.send_command(command)
|
||
rcon.close()
|
||
return result, None
|
||
except Exception as e:
|
||
return None, f"❌ Ошибка: {e}"
|
||
|
||
def parse_duration(duration_str):
|
||
if not duration_str:
|
||
return None
|
||
|
||
duration_str = duration_str.lower().strip()
|
||
|
||
if duration_str.isdigit():
|
||
return int(duration_str)
|
||
|
||
total_minutes = 0
|
||
patterns = [
|
||
(r'(\d+)w', 7 * 24 * 60),
|
||
(r'(\d+)d', 24 * 60),
|
||
(r'(\d+)h', 60),
|
||
(r'(\d+)m', 1),
|
||
]
|
||
|
||
for pattern, multiplier in patterns:
|
||
match = re.search(pattern, duration_str)
|
||
if match:
|
||
total_minutes += int(match.group(1)) * multiplier
|
||
|
||
return total_minutes if total_minutes > 0 else None
|
||
|
||
def format_duration(minutes):
|
||
if not minutes:
|
||
return "Навсегда"
|
||
|
||
if minutes < 60:
|
||
return f"{minutes} мин"
|
||
elif minutes < 24 * 60:
|
||
hours = minutes // 60
|
||
mins = minutes % 60
|
||
return f"{hours} ч {mins} мин" if mins else f"{hours} ч"
|
||
elif minutes < 7 * 24 * 60:
|
||
days = minutes // (24 * 60)
|
||
hours = (minutes % (24 * 60)) // 60
|
||
return f"{days} д {hours} ч" if hours else f"{days} д"
|
||
else:
|
||
weeks = minutes // (7 * 24 * 60)
|
||
days = (minutes % (7 * 24 * 60)) // (24 * 60)
|
||
return f"{weeks} нед {days} д" if days else f"{weeks} нед"
|
||
|
||
# =====================================================
|
||
# 🎮 БОТ
|
||
# =====================================================
|
||
|
||
intents = discord.Intents.default()
|
||
intents.message_content = True
|
||
intents.guilds = True
|
||
intents.members = True
|
||
|
||
bot = commands.Bot(command_prefix='!', intents=intents)
|
||
|
||
@bot.event
|
||
async def on_ready():
|
||
print(f'✅ Бот {bot.user} запущен!')
|
||
print(f'📡 Серверов: {len(bot.guilds)}')
|
||
print('=' * 50)
|
||
await bot.change_presence(activity=discord.Activity(
|
||
type=discord.ActivityType.watching,
|
||
name="👮 Управление персоналом | /staff"
|
||
))
|
||
|
||
await restore_panels()
|
||
|
||
try:
|
||
await bot.tree.sync()
|
||
print("✅ Слеш-команды синхронизированы!")
|
||
except Exception as e:
|
||
print(f"❌ Ошибка синхронизации: {e}")
|
||
|
||
async def restore_panels():
|
||
panels = load_panels()
|
||
if not panels:
|
||
print("📭 Нет сохраненных панелей для восстановления")
|
||
return
|
||
|
||
restored = 0
|
||
failed = 0
|
||
|
||
for key, data in panels.items():
|
||
try:
|
||
guild = bot.get_guild(data["guild_id"])
|
||
if not guild:
|
||
failed += 1
|
||
continue
|
||
|
||
channel = guild.get_channel(data["channel_id"])
|
||
if not channel:
|
||
failed += 1
|
||
continue
|
||
|
||
try:
|
||
message = await channel.fetch_message(data["message_id"])
|
||
|
||
if message.author.id == bot.user.id:
|
||
await message.edit(view=StaffMainMenu())
|
||
restored += 1
|
||
print(f"✅ Восстановлена панель в #{channel.name} ({message.id})")
|
||
else:
|
||
remove_panel(data["guild_id"], data["channel_id"], data["message_id"])
|
||
failed += 1
|
||
|
||
except discord.NotFound:
|
||
remove_panel(data["guild_id"], data["channel_id"], data["message_id"])
|
||
failed += 1
|
||
print(f"❌ Сообщение не найдено, удалено из сохраненных")
|
||
|
||
except Exception as e:
|
||
failed += 1
|
||
print(f"❌ Ошибка восстановления панели: {e}")
|
||
|
||
print(f"📊 Восстановлено панелей: {restored}, ошибок: {failed}")
|
||
|
||
# =====================================================
|
||
# 📢 ОТПРАВКА УВЕДОМЛЕНИЙ
|
||
# =====================================================
|
||
|
||
async def send_notification(interaction, title, description, color, user_id=None, dm_message=None):
|
||
config = load_config()
|
||
embed = discord.Embed(
|
||
title=title,
|
||
description=description,
|
||
color=color,
|
||
timestamp=datetime.now()
|
||
)
|
||
|
||
log_channel_id = config.get("log_channel_id")
|
||
if log_channel_id:
|
||
channel = interaction.guild.get_channel(log_channel_id)
|
||
if channel:
|
||
await channel.send(embed=embed)
|
||
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
|
||
if user_id and config.get("dm_on_staff_change", True):
|
||
try:
|
||
user = await bot.fetch_user(user_id)
|
||
if dm_message:
|
||
await user.send(dm_message)
|
||
else:
|
||
await user.send(embed=embed)
|
||
except:
|
||
pass
|
||
|
||
async def send_punishment_notification(guild, target, punishment_type, reason, duration=None, moderator=None):
|
||
config = load_config()
|
||
log_channel_id = config.get("log_channel_id")
|
||
|
||
if not log_channel_id:
|
||
return
|
||
|
||
channel = guild.get_channel(log_channel_id)
|
||
if not channel:
|
||
return
|
||
|
||
embed = discord.Embed(
|
||
title=f"⚔️ {punishment_type}",
|
||
description=f"Игрок: **{target}**",
|
||
color=discord.Color.red(),
|
||
timestamp=datetime.now()
|
||
)
|
||
|
||
embed.add_field(name="📝 Причина", value=reason, inline=False)
|
||
if duration:
|
||
embed.add_field(name="⏱️ Длительность", value=format_duration(duration), inline=True)
|
||
if moderator:
|
||
embed.add_field(name="👮 Модератор", value=moderator.mention, inline=True)
|
||
|
||
await channel.send(embed=embed)
|
||
|
||
# =====================================================
|
||
# ⚠️ СИСТЕМА ПРЕДУПРЕЖДЕНИЙ
|
||
# =====================================================
|
||
|
||
def get_warns(nickname):
|
||
"""Получить список предупреждений для игрока"""
|
||
warns = load_warns()
|
||
if nickname not in warns:
|
||
warns[nickname] = []
|
||
save_warns(warns)
|
||
return warns[nickname]
|
||
|
||
def add_warn(nickname, reason, moderator):
|
||
"""Добавить предупреждение игроку"""
|
||
warns = load_warns()
|
||
if nickname not in warns:
|
||
warns[nickname] = []
|
||
|
||
warns[nickname].append({
|
||
"reason": reason,
|
||
"moderator": moderator,
|
||
"date": datetime.now().isoformat(),
|
||
"id": len(warns[nickname]) + 1
|
||
})
|
||
save_warns(warns)
|
||
return len(warns[nickname])
|
||
|
||
def remove_warn(nickname, warn_id):
|
||
"""Удалить предупреждение у игрока"""
|
||
warns = load_warns()
|
||
if nickname not in warns:
|
||
return False
|
||
|
||
# Находим и удаляем предупреждение по ID
|
||
for i, warn in enumerate(warns[nickname]):
|
||
if warn.get("id") == warn_id:
|
||
del warns[nickname][i]
|
||
# Перенумеровываем ID
|
||
for j, w in enumerate(warns[nickname]):
|
||
w["id"] = j + 1
|
||
save_warns(warns)
|
||
return True
|
||
|
||
return False
|
||
|
||
def get_warn_count(nickname):
|
||
"""Получить количество предупреждений у игрока"""
|
||
warns = load_warns()
|
||
return len(warns.get(nickname, []))
|
||
|
||
def clear_warns(nickname):
|
||
"""Очистить все предупреждения у игрока"""
|
||
warns = load_warns()
|
||
if nickname in warns:
|
||
warns[nickname] = []
|
||
save_warns(warns)
|
||
return True
|
||
return False
|
||
|
||
# =====================================================
|
||
# 🎯 КЛАССЫ ДЛЯ МЕНЮ
|
||
# =====================================================
|
||
|
||
class StaffMainMenu(ui.View):
|
||
def __init__(self):
|
||
super().__init__(timeout=None)
|
||
|
||
async def on_timeout(self):
|
||
pass
|
||
|
||
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
||
return True
|
||
|
||
@ui.button(label="👮 Назначение/увольнение", style=ButtonStyle.primary, row=0)
|
||
async def staff_btn(self, interaction: discord.Interaction, button: ui.Button):
|
||
await interaction.response.send_message(
|
||
"👮 **Управление персоналом**\nВыберите действие:",
|
||
view=StaffManagementView(interaction.user.id)
|
||
)
|
||
|
||
@ui.button(label="⚔️ Наказания", style=ButtonStyle.danger, row=0)
|
||
async def punish_btn(self, interaction: discord.Interaction, button: ui.Button):
|
||
await interaction.response.send_message(
|
||
"⚔️ **Выдача наказания**\nВыберите тип наказания:",
|
||
view=PunishmentView(interaction.user.id)
|
||
)
|
||
|
||
@ui.button(label="👑 Привилегии", style=ButtonStyle.success, row=0)
|
||
async def lp_btn(self, interaction: discord.Interaction, button: ui.Button):
|
||
await interaction.response.send_message(
|
||
"👑 **Управление привилегиями**\nВыберите действие:",
|
||
view=LPView(interaction.user.id)
|
||
)
|
||
|
||
@ui.button(label="➕ Добавить ранг", style=ButtonStyle.success, row=0)
|
||
async def add_rank_btn(self, interaction: discord.Interaction, button: ui.Button):
|
||
modal = AddRankModal()
|
||
await interaction.response.send_modal(modal)
|
||
|
||
@ui.button(label="📝 Заявки", style=ButtonStyle.secondary, row=1)
|
||
async def requests_btn(self, interaction: discord.Interaction, button: ui.Button):
|
||
await interaction.response.send_message(
|
||
"📝 **Управление заявками**\nВыберите действие:",
|
||
view=RequestsView(interaction.user.id)
|
||
)
|
||
|
||
@ui.button(label="📢 Рассылки", style=ButtonStyle.secondary, row=1)
|
||
async def broadcast_btn(self, interaction: discord.Interaction, button: ui.Button):
|
||
modal = BroadcastModal()
|
||
await interaction.response.send_modal(modal)
|
||
|
||
@ui.button(label="📋 Журнал действий", style=ButtonStyle.primary, row=2)
|
||
async def actions_btn(self, interaction: discord.Interaction, button: ui.Button):
|
||
await interaction.response.defer()
|
||
actions = load_actions()
|
||
if not actions:
|
||
await interaction.followup.send("📭 Журнал действий пуст")
|
||
return
|
||
|
||
embed = discord.Embed(
|
||
title="📋 Журнал действий",
|
||
description="Последние действия",
|
||
color=discord.Color.blue()
|
||
)
|
||
|
||
all_actions = []
|
||
for mod_id, acts in actions.items():
|
||
for act in acts:
|
||
all_actions.append((act["date"], mod_id, act))
|
||
|
||
all_actions.sort(reverse=True)
|
||
|
||
for date, mod_id, act in all_actions[:10]:
|
||
embed.add_field(
|
||
name=f"{act['action']}",
|
||
value=f"👮 <@{mod_id}>\n🎯 {act['target']}\n📅 {date[:16]}",
|
||
inline=False
|
||
)
|
||
await interaction.followup.send(embed=embed)
|
||
|
||
@ui.button(label="🔐 Роли", style=ButtonStyle.primary, row=2)
|
||
async def roles_btn(self, interaction: discord.Interaction, button: ui.Button):
|
||
embed = discord.Embed(
|
||
title="🔐 Управление ролями",
|
||
description="Используйте команды для управления ролями",
|
||
color=discord.Color.blue()
|
||
)
|
||
embed.add_field(
|
||
name="📋 Команды",
|
||
value="`/role add <участник> <роль>` - Выдать роль\n"
|
||
"`/role remove <участник> <роль>` - Убрать роль\n"
|
||
"`/role list` - Список ролей\n"
|
||
"`/role create <название>` - Создать роль",
|
||
inline=False
|
||
)
|
||
await interaction.response.send_message(embed=embed)
|
||
|
||
@ui.button(label="📊 Статистика", style=ButtonStyle.success, row=2)
|
||
async def stats_btn(self, interaction: discord.Interaction, button: ui.Button):
|
||
await interaction.response.defer()
|
||
|
||
staff = load_staff()
|
||
actions = load_actions()
|
||
|
||
embed = discord.Embed(
|
||
title="📊 Статистика персонала",
|
||
color=discord.Color.blue()
|
||
)
|
||
|
||
embed.add_field(
|
||
name="👥 Всего сотрудников",
|
||
value=str(len(staff)),
|
||
inline=True
|
||
)
|
||
|
||
roles_stats = {}
|
||
for uid, data in staff.items():
|
||
role = data.get("role", "Неизвестно")
|
||
roles_stats[role] = roles_stats.get(role, 0) + 1
|
||
|
||
roles_text = "\n".join([f"{role}: {count}" for role, count in list(roles_stats.items())[:10]])
|
||
if len(roles_stats) > 10:
|
||
roles_text += f"\nИ ещё {len(roles_stats)-10} должностей..."
|
||
|
||
embed.add_field(
|
||
name="📋 По ролям",
|
||
value=roles_text if roles_text else "Нет данных",
|
||
inline=True
|
||
)
|
||
|
||
total_actions = sum(len(acts) for acts in actions.values())
|
||
embed.add_field(
|
||
name="📋 Всего действий",
|
||
value=str(total_actions),
|
||
inline=True
|
||
)
|
||
|
||
# Статистика предупреждений
|
||
warns = load_warns()
|
||
total_warns = sum(len(w) for w in warns.values())
|
||
embed.add_field(
|
||
name="⚠️ Всего предупреждений",
|
||
value=str(total_warns),
|
||
inline=True
|
||
)
|
||
|
||
await interaction.followup.send(embed=embed)
|
||
|
||
@ui.button(label="⚙️ Настройки RCON", style=ButtonStyle.secondary, row=3)
|
||
async def rcon_settings_btn(self, interaction: discord.Interaction, button: ui.Button):
|
||
modal = RconSettingsModal()
|
||
await interaction.response.send_modal(modal)
|
||
|
||
class RconSettingsModal(ui.Modal, title="⚙️ Настройки RCON"):
|
||
host = ui.TextInput(
|
||
label="Хост",
|
||
placeholder="IP или домен сервера",
|
||
required=True,
|
||
style=discord.TextStyle.short
|
||
)
|
||
port = ui.TextInput(
|
||
label="Порт",
|
||
placeholder="Обычно 25575",
|
||
required=True,
|
||
style=discord.TextStyle.short,
|
||
default="25575"
|
||
)
|
||
password = ui.TextInput(
|
||
label="Пароль RCON",
|
||
placeholder="Пароль от RCON",
|
||
required=True,
|
||
style=discord.TextStyle.short
|
||
)
|
||
log_channel_id = ui.TextInput(
|
||
label="ID канала для логов",
|
||
placeholder="ID канала, куда отправлять уведомления",
|
||
required=False,
|
||
style=discord.TextStyle.short
|
||
)
|
||
warn_limit = ui.TextInput(
|
||
label="Кол-во предупреждений до бана",
|
||
placeholder="По умолчанию: 3",
|
||
required=False,
|
||
style=discord.TextStyle.short,
|
||
default="3"
|
||
)
|
||
ban_duration = ui.TextInput(
|
||
label="Длительность бана после 3 предупреждений (минуты)",
|
||
placeholder="По умолчанию: 2880 (2 дня)",
|
||
required=False,
|
||
style=discord.TextStyle.short,
|
||
default="2880"
|
||
)
|
||
|
||
async def on_submit(self, interaction: discord.Interaction):
|
||
await interaction.response.defer(ephemeral=True)
|
||
|
||
config = load_config()
|
||
config["rcon_host"] = self.host.value
|
||
config["rcon_port"] = int(self.port.value)
|
||
config["rcon_password"] = self.password.value
|
||
if self.log_channel_id.value:
|
||
config["log_channel_id"] = int(self.log_channel_id.value)
|
||
if self.warn_limit.value and self.warn_limit.value.isdigit():
|
||
config["warn_limit"] = int(self.warn_limit.value)
|
||
if self.ban_duration.value and self.ban_duration.value.isdigit():
|
||
config["ban_duration"] = int(self.ban_duration.value)
|
||
|
||
save_config(config)
|
||
|
||
global rcon_client
|
||
rcon_client = None
|
||
|
||
embed = discord.Embed(
|
||
title="✅ Настройки RCON сохранены!",
|
||
description="Настройки успешно обновлены",
|
||
color=discord.Color.green()
|
||
)
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
|
||
# =====================================================
|
||
# 👑 УПРАВЛЕНИЕ ПРИВИЛЕГИЯМИ (LUCKPERMS)
|
||
# =====================================================
|
||
|
||
class LPView(ui.View):
|
||
def __init__(self, user_id):
|
||
super().__init__(timeout=120)
|
||
self.user_id = user_id
|
||
|
||
ranks_data = load_ranks()
|
||
self.ranks = [(rank_id, data["name"], data.get("weight", 0)) for rank_id, data in ranks_data.items()]
|
||
self.ranks.sort(key=lambda x: x[2], reverse=True)
|
||
|
||
self.current_page = 0
|
||
self.page_size = 15
|
||
|
||
options = []
|
||
for rank_id, name, weight in self.ranks[:self.page_size]:
|
||
emoji = name.split()[0] if name.split() else "📌"
|
||
options.append(SelectOption(
|
||
label=name[:100],
|
||
value=rank_id,
|
||
description=f"Вес: {weight}",
|
||
emoji=emoji
|
||
))
|
||
|
||
self.select = ui.Select(
|
||
placeholder="👑 Выберите привилегию...",
|
||
options=options,
|
||
row=0
|
||
)
|
||
self.select.callback = self.select_callback
|
||
self.add_item(self.select)
|
||
|
||
self.prev_btn = ui.Button(label="◀️ Назад", style=ButtonStyle.secondary, row=1, disabled=True)
|
||
self.prev_btn.callback = self.prev_callback
|
||
self.add_item(self.prev_btn)
|
||
|
||
total_pages = (len(self.ranks) + self.page_size - 1) // self.page_size
|
||
self.next_btn = ui.Button(label="Вперед ▶️", style=ButtonStyle.secondary, row=1, disabled=len(self.ranks) <= self.page_size)
|
||
self.next_btn.callback = self.next_callback
|
||
self.add_item(self.next_btn)
|
||
|
||
check_btn = ui.Button(label="🔍 Проверить привилегию", style=ButtonStyle.secondary, row=2)
|
||
check_btn.callback = self.check_callback
|
||
self.add_item(check_btn)
|
||
|
||
delete_btn = ui.Button(label="🗑️ Удалить ранг", style=ButtonStyle.danger, row=2)
|
||
delete_btn.callback = self.delete_callback
|
||
self.add_item(delete_btn)
|
||
|
||
close = ui.Button(label="❌ Закрыть", style=ButtonStyle.danger, row=2)
|
||
close.callback = self.close_callback
|
||
self.add_item(close)
|
||
|
||
def update_select(self):
|
||
ranks_data = load_ranks()
|
||
self.ranks = [(rank_id, data["name"], data.get("weight", 0)) for rank_id, data in ranks_data.items()]
|
||
self.ranks.sort(key=lambda x: x[2], reverse=True)
|
||
|
||
start = self.current_page * self.page_size
|
||
end = start + self.page_size
|
||
page_ranks = self.ranks[start:end]
|
||
|
||
options = []
|
||
for rank_id, name, weight in page_ranks:
|
||
emoji = name.split()[0] if name.split() else "📌"
|
||
options.append(SelectOption(
|
||
label=name[:100],
|
||
value=rank_id,
|
||
description=f"Вес: {weight}",
|
||
emoji=emoji
|
||
))
|
||
|
||
self.select.options = options
|
||
self.select.placeholder = f"👑 Выберите привилегию (стр. {self.current_page + 1})"
|
||
|
||
total_pages = (len(self.ranks) + self.page_size - 1) // self.page_size
|
||
self.prev_btn.disabled = self.current_page == 0
|
||
self.next_btn.disabled = self.current_page >= total_pages - 1
|
||
|
||
async def prev_callback(self, interaction: discord.Interaction):
|
||
if interaction.user.id != self.user_id:
|
||
await interaction.response.send_message("❌ Это не ваше меню!", ephemeral=True)
|
||
return
|
||
self.current_page -= 1
|
||
self.update_select()
|
||
await interaction.response.edit_message(view=self)
|
||
|
||
async def next_callback(self, interaction: discord.Interaction):
|
||
if interaction.user.id != self.user_id:
|
||
await interaction.response.send_message("❌ Это не ваше меню!", ephemeral=True)
|
||
return
|
||
self.current_page += 1
|
||
self.update_select()
|
||
await interaction.response.edit_message(view=self)
|
||
|
||
async def select_callback(self, interaction: discord.Interaction):
|
||
if interaction.user.id != self.user_id:
|
||
await interaction.response.send_message("❌ Это не ваше меню!", ephemeral=True)
|
||
return
|
||
|
||
rank = self.select.values[0]
|
||
modal = LPModal(rank)
|
||
await interaction.response.send_modal(modal)
|
||
|
||
async def check_callback(self, interaction: discord.Interaction):
|
||
if interaction.user.id != self.user_id:
|
||
await interaction.response.send_message("❌ Это не ваше меню!", ephemeral=True)
|
||
return
|
||
|
||
modal = CheckRankModal()
|
||
await interaction.response.send_modal(modal)
|
||
|
||
async def delete_callback(self, interaction: discord.Interaction):
|
||
if interaction.user.id != self.user_id:
|
||
await interaction.response.send_message("❌ Это не ваше меню!", ephemeral=True)
|
||
return
|
||
|
||
modal = DeleteRankModal()
|
||
await interaction.response.send_modal(modal)
|
||
|
||
async def close_callback(self, interaction: discord.Interaction):
|
||
if interaction.user.id != self.user_id:
|
||
await interaction.response.send_message("❌ Это не ваше меню!", ephemeral=True)
|
||
return
|
||
await interaction.message.delete()
|
||
|
||
class LPModal(ui.Modal, title="👑 Выдача привилегии"):
|
||
def __init__(self, rank):
|
||
super().__init__(title=f"👑 Выдача {rank}")
|
||
self.rank = rank
|
||
|
||
self.nickname = ui.TextInput(
|
||
label="Ник в Minecraft",
|
||
placeholder="Введите ник игрока...",
|
||
required=True,
|
||
style=discord.TextStyle.short
|
||
)
|
||
self.add_item(self.nickname)
|
||
|
||
self.reason = ui.TextInput(
|
||
label="Причина (будет отображаться в чате)",
|
||
placeholder="Введите причину выдачи...",
|
||
required=False,
|
||
style=discord.TextStyle.short,
|
||
default="Выдача привилегии"
|
||
)
|
||
self.add_item(self.reason)
|
||
|
||
self.server = ui.TextInput(
|
||
label="Сервер (опционально)",
|
||
placeholder="Оставьте пустым для глобальной",
|
||
required=False,
|
||
style=discord.TextStyle.short
|
||
)
|
||
self.add_item(self.server)
|
||
|
||
async def on_submit(self, interaction: discord.Interaction):
|
||
await interaction.response.defer(ephemeral=True)
|
||
|
||
nickname = self.nickname.value
|
||
rank = self.rank
|
||
reason = self.reason.value if self.reason.value else "Выдача привилегии"
|
||
server = self.server.value if self.server.value else "global"
|
||
|
||
ranks_data = load_ranks()
|
||
rank_name = ranks_data.get(rank, {}).get("name", rank)
|
||
|
||
if server == "global":
|
||
lp_command = f"lp user {nickname} parent set {rank}"
|
||
else:
|
||
lp_command = f"lp user {nickname} parent set {rank} server={server}"
|
||
|
||
chat_message = f"§6§l🔔 Игроку §e{nickname} §6выдана привилегия §b{rank_name} §6причина: §f{reason}"
|
||
chat_command = f"say {chat_message}"
|
||
|
||
lp_result, lp_error = await execute_mc_command_async(lp_command)
|
||
chat_result, chat_error = await execute_mc_command_async(chat_command)
|
||
|
||
embed = discord.Embed(
|
||
title="👑 Привилегия выдана!",
|
||
description=f"Игроку **{nickname}** выдана привилегия **{rank_name}**",
|
||
color=discord.Color.gold()
|
||
)
|
||
embed.add_field(name="📌 Сервер", value=server, inline=True)
|
||
embed.add_field(name="📝 Причина", value=reason, inline=True)
|
||
|
||
status = ""
|
||
if lp_error:
|
||
status += f"❌ LP: {lp_error}\n"
|
||
else:
|
||
status += f"✅ LP: Команда выполнена\n"
|
||
|
||
if chat_error:
|
||
status += f"❌ Чат: {chat_error}"
|
||
else:
|
||
status += f"✅ Чат: Сообщение отправлено"
|
||
|
||
embed.add_field(name="📡 Статус", value=status, inline=False)
|
||
embed.add_field(
|
||
name="💬 Сообщение в чате",
|
||
value=f"```{chat_message}```",
|
||
inline=False
|
||
)
|
||
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
|
||
log_action(interaction.user.id, f"выдача привилегии {rank}", nickname, f"Сервер: {server}, Причина: {reason}")
|
||
|
||
class CheckRankModal(ui.Modal, title="🔍 Проверка привилегии"):
|
||
nickname = ui.TextInput(
|
||
label="Ник в Minecraft",
|
||
placeholder="Введите ник игрока...",
|
||
required=True,
|
||
style=discord.TextStyle.short
|
||
)
|
||
|
||
async def on_submit(self, interaction: discord.Interaction):
|
||
await interaction.response.defer(ephemeral=True)
|
||
|
||
nickname = self.nickname.value
|
||
command = f"lp user {nickname} info"
|
||
result, error = await execute_mc_command_async(command)
|
||
|
||
if error:
|
||
embed = discord.Embed(
|
||
title=f"❌ Ошибка",
|
||
description=f"```{error}```",
|
||
color=discord.Color.red()
|
||
)
|
||
else:
|
||
embed = discord.Embed(
|
||
title=f"🔍 Информация о {nickname}",
|
||
description=f"```{result[:1900]}```" if result else "❌ Игрок не найден или нет данных",
|
||
color=discord.Color.blue()
|
||
)
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
|
||
class AddRankModal(ui.Modal, title="➕ Добавить новый ранг"):
|
||
rank_id = ui.TextInput(
|
||
label="ID ранга",
|
||
placeholder="Например: admin, helper, builder",
|
||
required=True,
|
||
style=discord.TextStyle.short
|
||
)
|
||
rank_name = ui.TextInput(
|
||
label="Название ранга",
|
||
placeholder="👑 Администратор",
|
||
required=True,
|
||
style=discord.TextStyle.short
|
||
)
|
||
weight = ui.TextInput(
|
||
label="Вес (чем больше, тем выше)",
|
||
placeholder="Например: 20, 30, 100",
|
||
required=True,
|
||
style=discord.TextStyle.short,
|
||
default="10"
|
||
)
|
||
|
||
async def on_submit(self, interaction: discord.Interaction):
|
||
await interaction.response.defer(ephemeral=True)
|
||
|
||
rank_id = self.rank_id.value.lower().strip()
|
||
rank_name = self.rank_name.value
|
||
weight = int(self.weight.value) if self.weight.value.isdigit() else 10
|
||
|
||
ranks = load_ranks()
|
||
|
||
if rank_id in ranks:
|
||
await interaction.followup.send(f"❌ Ранг с ID **{rank_id}** уже существует!", ephemeral=True)
|
||
return
|
||
|
||
ranks[rank_id] = {
|
||
"name": rank_name,
|
||
"weight": weight
|
||
}
|
||
save_ranks(ranks)
|
||
|
||
embed = discord.Embed(
|
||
title="✅ Новый ранг добавлен!",
|
||
description=f"Ранг **{rank_name}** (ID: {rank_id}) добавлен с весом {weight}",
|
||
color=discord.Color.green()
|
||
)
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
|
||
log_action(interaction.user.id, "добавление ранга", rank_id, f"{rank_name} (вес: {weight})")
|
||
|
||
class DeleteRankModal(ui.Modal, title="🗑️ Удалить ранг"):
|
||
rank_id = ui.TextInput(
|
||
label="ID ранга",
|
||
placeholder="Введите ID ранга для удаления",
|
||
required=True,
|
||
style=discord.TextStyle.short
|
||
)
|
||
|
||
async def on_submit(self, interaction: discord.Interaction):
|
||
await interaction.response.defer(ephemeral=True)
|
||
|
||
rank_id = self.rank_id.value.lower().strip()
|
||
ranks = load_ranks()
|
||
|
||
if rank_id not in ranks:
|
||
await interaction.followup.send(f"❌ Ранг с ID **{rank_id}** не найден!", ephemeral=True)
|
||
return
|
||
|
||
rank_name = ranks[rank_id]["name"]
|
||
del ranks[rank_id]
|
||
save_ranks(ranks)
|
||
|
||
embed = discord.Embed(
|
||
title="🗑️ Ранг удален!",
|
||
description=f"Ранг **{rank_name}** (ID: {rank_id}) удален",
|
||
color=discord.Color.red()
|
||
)
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
|
||
log_action(interaction.user.id, "удаление ранга", rank_id, rank_name)
|
||
|
||
# =====================================================
|
||
# 👮 УПРАВЛЕНИЕ ПЕРСОНАЛОМ
|
||
# =====================================================
|
||
|
||
class StaffManagementView(ui.View):
|
||
def __init__(self, user_id):
|
||
super().__init__(timeout=120)
|
||
self.user_id = user_id
|
||
|
||
options = [
|
||
SelectOption(label="📥 Назначить", value="hire", description="Назначить сотрудника", emoji="📥"),
|
||
SelectOption(label="📤 Уволить", value="fire", description="Уволить сотрудника", emoji="📤"),
|
||
SelectOption(label="📋 Список", value="list", description="Показать всех сотрудников", emoji="📋"),
|
||
]
|
||
self.select = ui.Select(placeholder="Выберите действие...", options=options, row=0)
|
||
self.select.callback = self.select_callback
|
||
self.add_item(self.select)
|
||
|
||
close = ui.Button(label="❌ Закрыть", style=ButtonStyle.danger, row=1)
|
||
close.callback = self.close_callback
|
||
self.add_item(close)
|
||
|
||
async def select_callback(self, interaction: discord.Interaction):
|
||
if interaction.user.id != self.user_id:
|
||
await interaction.response.send_message("❌ Это не ваше меню!", ephemeral=True)
|
||
return
|
||
|
||
action = self.select.values[0]
|
||
|
||
if action == "hire":
|
||
modal = HireModal()
|
||
await interaction.response.send_modal(modal)
|
||
elif action == "fire":
|
||
modal = FireModal()
|
||
await interaction.response.send_modal(modal)
|
||
elif action == "list":
|
||
await interaction.response.defer()
|
||
staff = load_staff()
|
||
if not staff:
|
||
await interaction.followup.send("📭 Нет сотрудников")
|
||
return
|
||
|
||
embed = discord.Embed(
|
||
title="👮 Список сотрудников",
|
||
color=discord.Color.blue()
|
||
)
|
||
for uid, data in list(staff.items())[:15]:
|
||
member = interaction.guild.get_member(int(uid))
|
||
name = member.mention if member else f"<@{uid}>"
|
||
embed.add_field(
|
||
name=f"📌 {data.get('role', 'Без роли')}",
|
||
value=f"{name}\n📅 {data.get('hired', 'Неизвестно')[:16]}",
|
||
inline=False
|
||
)
|
||
if len(staff) > 15:
|
||
embed.set_footer(text=f"И ещё {len(staff)-15} сотрудников...")
|
||
await interaction.followup.send(embed=embed)
|
||
|
||
async def close_callback(self, interaction: discord.Interaction):
|
||
if interaction.user.id != self.user_id:
|
||
await interaction.response.send_message("❌ Это не ваше меню!", ephemeral=True)
|
||
return
|
||
await interaction.message.delete()
|
||
|
||
class HireModal(ui.Modal, title="📥 Назначение сотрудника"):
|
||
member_id = ui.TextInput(
|
||
label="ID участника",
|
||
placeholder="Введите ID пользователя...",
|
||
required=True,
|
||
style=discord.TextStyle.short
|
||
)
|
||
role = ui.TextInput(
|
||
label="Должность",
|
||
placeholder="Введите должность...",
|
||
required=True,
|
||
style=discord.TextStyle.short
|
||
)
|
||
|
||
async def on_submit(self, interaction: discord.Interaction):
|
||
await interaction.response.defer(ephemeral=True)
|
||
|
||
try:
|
||
member = await interaction.guild.fetch_member(int(self.member_id.value))
|
||
except:
|
||
await interaction.followup.send("❌ Участник не найден!", ephemeral=True)
|
||
return
|
||
|
||
staff = load_staff()
|
||
if str(member.id) in staff:
|
||
await interaction.followup.send(f"⚠️ {member.mention} уже является сотрудником!", ephemeral=True)
|
||
return
|
||
|
||
staff[str(member.id)] = {
|
||
"role": self.role.value,
|
||
"hired": datetime.now().isoformat(),
|
||
"hired_by": interaction.user.id
|
||
}
|
||
save_staff(staff)
|
||
log_action(interaction.user.id, "назначение", member.name, self.role.value)
|
||
|
||
await send_notification(
|
||
interaction,
|
||
"✅ Сотрудник назначен!",
|
||
f"{member.mention} назначен на должность **{self.role.value}**",
|
||
discord.Color.green(),
|
||
member.id,
|
||
f"🎉 Поздравляем! Вы назначены на должность **{self.role.value}** на сервере {interaction.guild.name}!"
|
||
)
|
||
|
||
class FireModal(ui.Modal, title="📤 Увольнение сотрудника"):
|
||
member_id = ui.TextInput(
|
||
label="ID участника",
|
||
placeholder="Введите ID пользователя...",
|
||
required=True,
|
||
style=discord.TextStyle.short
|
||
)
|
||
reason = ui.TextInput(
|
||
label="Причина",
|
||
placeholder="Введите причину...",
|
||
required=False,
|
||
style=discord.TextStyle.short,
|
||
default="Не указана"
|
||
)
|
||
|
||
async def on_submit(self, interaction: discord.Interaction):
|
||
await interaction.response.defer(ephemeral=True)
|
||
|
||
try:
|
||
member = await interaction.guild.fetch_member(int(self.member_id.value))
|
||
except:
|
||
await interaction.followup.send("❌ Участник не найден!", ephemeral=True)
|
||
return
|
||
|
||
staff = load_staff()
|
||
if str(member.id) not in staff:
|
||
await interaction.followup.send(f"⚠️ {member.mention} не является сотрудником!", ephemeral=True)
|
||
return
|
||
|
||
role = staff[str(member.id)]["role"]
|
||
del staff[str(member.id)]
|
||
save_staff(staff)
|
||
log_action(interaction.user.id, "увольнение", member.name, self.reason.value)
|
||
|
||
await send_notification(
|
||
interaction,
|
||
"❌ Сотрудник уволен!",
|
||
f"{member.mention} уволен с должности **{role}**\n📝 Причина: {self.reason.value}",
|
||
discord.Color.red(),
|
||
member.id,
|
||
f"⚠️ Вы были уволены с должности **{role}** на сервере {interaction.guild.name}.\n📝 Причина: {self.reason.value}"
|
||
)
|
||
|
||
# =====================================================
|
||
# ⚔️ НАКАЗАНИЯ (С RCON)
|
||
# =====================================================
|
||
|
||
class PunishmentView(ui.View):
|
||
def __init__(self, user_id):
|
||
super().__init__(timeout=120)
|
||
self.user_id = user_id
|
||
|
||
options = [
|
||
SelectOption(label="⚠️ Предупреждение", value="warn", emoji="⚠️"),
|
||
SelectOption(label="🔽 Снять предупреждение", value="unwarn", emoji="🔽"),
|
||
SelectOption(label="📋 Список предупреждений", value="list_warns", emoji="📋"),
|
||
SelectOption(label="🔇 Мут", value="mute", emoji="🔇"),
|
||
SelectOption(label="🔊 Размут", value="unmute", emoji="🔊"),
|
||
SelectOption(label="⛔ Кик", value="kick", emoji="⛔"),
|
||
SelectOption(label="❌ Бан", value="ban", emoji="❌"),
|
||
SelectOption(label="🔓 Разбан", value="unban", emoji="🔓"),
|
||
SelectOption(label="🌐 Бан IP", value="banip", emoji="🌐"),
|
||
SelectOption(label="🌐 Разбан IP", value="unbanip", emoji="🌐"),
|
||
]
|
||
self.select = ui.Select(placeholder="Выберите наказание...", options=options, row=0)
|
||
self.select.callback = self.select_callback
|
||
self.add_item(self.select)
|
||
|
||
close = ui.Button(label="❌ Закрыть", style=ButtonStyle.danger, row=1)
|
||
close.callback = self.close_callback
|
||
self.add_item(close)
|
||
|
||
async def select_callback(self, interaction: discord.Interaction):
|
||
if interaction.user.id != self.user_id:
|
||
await interaction.response.send_message("❌ Это не ваше меню!", ephemeral=True)
|
||
return
|
||
|
||
punishment = self.select.values[0]
|
||
|
||
if punishment == "list_warns":
|
||
modal = ListWarnsModal()
|
||
await interaction.response.send_modal(modal)
|
||
else:
|
||
modal = PunishmentModal(punishment)
|
||
await interaction.response.send_modal(modal)
|
||
|
||
async def close_callback(self, interaction: discord.Interaction):
|
||
if interaction.user.id != self.user_id:
|
||
await interaction.response.send_message("❌ Это не ваше меню!", ephemeral=True)
|
||
return
|
||
await interaction.message.delete()
|
||
|
||
class ListWarnsModal(ui.Modal, title="📋 Список предупреждений"):
|
||
nickname = ui.TextInput(
|
||
label="Ник в Minecraft",
|
||
placeholder="Введите ник игрока...",
|
||
required=True,
|
||
style=discord.TextStyle.short
|
||
)
|
||
|
||
async def on_submit(self, interaction: discord.Interaction):
|
||
await interaction.response.defer(ephemeral=True)
|
||
|
||
nickname = self.nickname.value
|
||
warns = get_warns(nickname)
|
||
|
||
if not warns:
|
||
embed = discord.Embed(
|
||
title=f"📋 Предупреждения игрока {nickname}",
|
||
description="У игрока нет предупреждений ✅",
|
||
color=discord.Color.green()
|
||
)
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
return
|
||
|
||
embed = discord.Embed(
|
||
title=f"📋 Предупреждения игрока {nickname}",
|
||
description=f"Всего: **{len(warns)}** предупреждений",
|
||
color=discord.Color.orange()
|
||
)
|
||
|
||
for warn in warns:
|
||
date = warn.get("date", "")[:16]
|
||
embed.add_field(
|
||
name=f"#{warn.get('id', '?')}",
|
||
value=f"📝 {warn.get('reason', 'Без причины')}\n👮 {warn.get('moderator', 'Неизвестно')}\n📅 {date}",
|
||
inline=False
|
||
)
|
||
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
|
||
class PunishmentModal(ui.Modal, title="⚔️ Выдача наказания"):
|
||
def __init__(self, punishment_type):
|
||
super().__init__(title=f"⚔️ {self._get_title(punishment_type)}")
|
||
self.punishment_type = punishment_type
|
||
|
||
if punishment_type in ["banip", "unbanip"]:
|
||
self.target_label = "IP адрес"
|
||
self.target_placeholder = "Введите IP адрес..."
|
||
else:
|
||
self.target_label = "Ник в Minecraft"
|
||
self.target_placeholder = "Введите ник игрока..."
|
||
|
||
self.target = ui.TextInput(
|
||
label=self.target_label,
|
||
placeholder=self.target_placeholder,
|
||
required=True,
|
||
style=discord.TextStyle.short
|
||
)
|
||
self.add_item(self.target)
|
||
|
||
self.reason = ui.TextInput(
|
||
label="Причина",
|
||
placeholder="Введите причину...",
|
||
required=False,
|
||
style=discord.TextStyle.short,
|
||
default="Нарушение правил"
|
||
)
|
||
self.add_item(self.reason)
|
||
|
||
if punishment_type in ["mute", "ban", "banip"]:
|
||
self.duration = ui.TextInput(
|
||
label="Длительность (1d, 2h, 30m, 1w или число в минутах)",
|
||
placeholder="Например: 10, 60, 1d, 2h, 1w (0 - навсегда)",
|
||
required=False,
|
||
style=discord.TextStyle.short,
|
||
default="10"
|
||
)
|
||
self.add_item(self.duration)
|
||
|
||
if punishment_type == "unwarn":
|
||
self.warn_id = ui.TextInput(
|
||
label="ID предупреждения (из списка)",
|
||
placeholder="Введите ID для удаления",
|
||
required=True,
|
||
style=discord.TextStyle.short
|
||
)
|
||
self.add_item(self.warn_id)
|
||
|
||
def _get_title(self, punishment_type):
|
||
titles = {
|
||
"warn": "Предупреждение",
|
||
"unwarn": "Снять предупреждение",
|
||
"mute": "Мут",
|
||
"unmute": "Размут",
|
||
"kick": "Кик",
|
||
"ban": "Бан",
|
||
"unban": "Разбан",
|
||
"banip": "Бан по IP",
|
||
"unbanip": "Разбан по IP"
|
||
}
|
||
return titles.get(punishment_type, punishment_type)
|
||
|
||
async def on_submit(self, interaction: discord.Interaction):
|
||
await interaction.response.defer(ephemeral=True)
|
||
|
||
target = self.target.value
|
||
reason = self.reason.value if self.reason.value else "Нарушение правил"
|
||
|
||
punishment_names = {
|
||
"warn": "Предупреждение",
|
||
"unwarn": "Снятие предупреждения",
|
||
"mute": "Мут",
|
||
"unmute": "Размут",
|
||
"kick": "Кик",
|
||
"ban": "Бан",
|
||
"unban": "Разбан",
|
||
"banip": "Бан по IP",
|
||
"unbanip": "Разбан по IP"
|
||
}
|
||
|
||
duration = None
|
||
if self.punishment_type in ["mute", "ban", "banip"] and hasattr(self, 'duration'):
|
||
duration_str = self.duration.value if self.duration.value else "0"
|
||
if duration_str != "0":
|
||
duration = parse_duration(duration_str)
|
||
|
||
# ============ ПРЕДУПРЕЖДЕНИЕ ============
|
||
if self.punishment_type == "warn":
|
||
warn_count = add_warn(target, reason, interaction.user.display_name)
|
||
config = load_config()
|
||
warn_limit = config.get("warn_limit", 3)
|
||
ban_duration = config.get("ban_duration", 2880) # 2 дня
|
||
|
||
# Отправляем только ОДНО сообщение в чат
|
||
chat_message = f"§e⚠️ Игроку §6{target} §eвынесено предупреждение №{warn_count} §fпричина: §c{reason}"
|
||
await execute_mc_command_async(f"say {chat_message}")
|
||
|
||
await send_punishment_notification(
|
||
interaction.guild,
|
||
target,
|
||
f"Предупреждение #{warn_count}",
|
||
reason,
|
||
None,
|
||
interaction.user
|
||
)
|
||
|
||
embed = discord.Embed(
|
||
title="⚠️ Предупреждение выдано!",
|
||
description=f"Игроку **{target}** выдано предупреждение №{warn_count}",
|
||
color=discord.Color.gold()
|
||
)
|
||
embed.add_field(name="📝 Причина", value=reason, inline=False)
|
||
embed.add_field(name="📊 Всего предупреждений", value=f"{warn_count}/{warn_limit}", inline=True)
|
||
|
||
# Проверяем, нужно ли забанить
|
||
if warn_count >= warn_limit:
|
||
ban_reason = f"Авто-бан: {warn_limit} предупреждений"
|
||
ban_command = f"tempban {target} {ban_duration}m {ban_reason}"
|
||
ban_result, ban_error = await execute_mc_command_async(ban_command)
|
||
|
||
embed.add_field(
|
||
name="❌ Авто-бан!",
|
||
value=f"Игрок **{target}** автоматически забанен на {format_duration(ban_duration)} за {warn_limit} предупреждений",
|
||
inline=False
|
||
)
|
||
|
||
# Отправляем сообщение о бане в чат (только одно)
|
||
ban_chat = f"§c§l❌ Игрок §e{target} §cавтоматически забанен за {warn_limit} предупреждений §f(причина: {reason})"
|
||
await execute_mc_command_async(f"say {ban_chat}")
|
||
|
||
# Очищаем предупреждения после бана
|
||
clear_warns(target)
|
||
log_action(interaction.user.id, "авто-бан", target, f"{warn_limit} предупреждений")
|
||
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
log_action(interaction.user.id, "предупреждение", target, f"{reason} (№{warn_count})")
|
||
|
||
# ============ СНЯТИЕ ПРЕДУПРЕЖДЕНИЯ ============
|
||
elif self.punishment_type == "unwarn":
|
||
warn_id = int(self.warn_id.value) if self.warn_id.value.isdigit() else None
|
||
|
||
if not warn_id:
|
||
await interaction.followup.send("❌ Укажите корректный ID предупреждения!", ephemeral=True)
|
||
return
|
||
|
||
if remove_warn(target, warn_id):
|
||
# Отправляем сообщение в чат
|
||
chat_message = f"§a✅ Игроку §6{target} §aснято предупреждение #{warn_id}"
|
||
await execute_mc_command_async(f"say {chat_message}")
|
||
|
||
embed = discord.Embed(
|
||
title="✅ Предупреждение снято!",
|
||
description=f"У игрока **{target}** снято предупреждение #{warn_id}",
|
||
color=discord.Color.green()
|
||
)
|
||
embed.add_field(
|
||
name="📊 Осталось предупреждений",
|
||
value=str(get_warn_count(target)),
|
||
inline=True
|
||
)
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
log_action(interaction.user.id, "снятие предупреждения", target, f"ID: {warn_id}")
|
||
else:
|
||
await interaction.followup.send(f"❌ Предупреждение #{warn_id} у игрока **{target}** не найдено!", ephemeral=True)
|
||
|
||
# ============ ОСТАЛЬНЫЕ НАКАЗАНИЯ ============
|
||
elif self.punishment_type == "mute":
|
||
if duration:
|
||
command = f"mute {target} {duration}m {reason}"
|
||
else:
|
||
command = f"mute {target} {reason}"
|
||
|
||
chat_message = f"§c§l🔇 Игроку §e{target} §cвыдан мут §fпричина: §e{reason}"
|
||
if duration:
|
||
chat_message += f" §fдлительность: §e{format_duration(duration)}"
|
||
|
||
await execute_mc_command_async(f"say {chat_message}")
|
||
result, error = await execute_mc_command_async(command)
|
||
|
||
await send_punishment_notification(interaction.guild, target, "Мут", reason, duration, interaction.user)
|
||
|
||
embed = discord.Embed(title="✅ Мут выдан!", description=f"Игрок **{target}** замучен", color=discord.Color.red())
|
||
embed.add_field(name="📝 Причина", value=reason, inline=False)
|
||
if duration:
|
||
embed.add_field(name="⏱️ Длительность", value=format_duration(duration), inline=True)
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
log_action(interaction.user.id, "мут", target, reason)
|
||
|
||
elif self.punishment_type == "unmute":
|
||
command = f"unmute {target}"
|
||
chat_message = f"§a✅ Игрок §e{target} §aразмучен"
|
||
await execute_mc_command_async(f"say {chat_message}")
|
||
result, error = await execute_mc_command_async(command)
|
||
|
||
embed = discord.Embed(title="✅ Размут выполнен!", description=f"Игрок **{target}** размучен", color=discord.Color.green())
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
log_action(interaction.user.id, "размут", target, reason)
|
||
|
||
elif self.punishment_type == "kick":
|
||
command = f"kick {target} {reason}"
|
||
chat_message = f"§c§l⛔ Игрок §e{target} §cкикнут §fпричина: §e{reason}"
|
||
await execute_mc_command_async(f"say {chat_message}")
|
||
result, error = await execute_mc_command_async(command)
|
||
|
||
await send_punishment_notification(interaction.guild, target, "Кик", reason, None, interaction.user)
|
||
|
||
embed = discord.Embed(title="✅ Кик выполнен!", description=f"Игрок **{target}** кикнут", color=discord.Color.orange())
|
||
embed.add_field(name="📝 Причина", value=reason, inline=False)
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
log_action(interaction.user.id, "кик", target, reason)
|
||
|
||
elif self.punishment_type == "ban":
|
||
if duration:
|
||
command = f"tempban {target} {duration}m {reason}"
|
||
else:
|
||
command = f"ban {target} {reason}"
|
||
|
||
chat_message = f"§c§l❌ Игрок §e{target} §cзабанен §fпричина: §e{reason}"
|
||
if duration:
|
||
chat_message += f" §fдлительность: §e{format_duration(duration)}"
|
||
|
||
await execute_mc_command_async(f"say {chat_message}")
|
||
result, error = await execute_mc_command_async(command)
|
||
|
||
await send_punishment_notification(interaction.guild, target, "Бан", reason, duration, interaction.user)
|
||
|
||
embed = discord.Embed(title="✅ Бан выдан!", description=f"Игрок **{target}** забанен", color=discord.Color.dark_red())
|
||
embed.add_field(name="📝 Причина", value=reason, inline=False)
|
||
if duration:
|
||
embed.add_field(name="⏱️ Длительность", value=format_duration(duration), inline=True)
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
log_action(interaction.user.id, "бан", target, reason)
|
||
|
||
elif self.punishment_type == "unban":
|
||
command = f"unban {target}"
|
||
chat_message = f"§a✅ Игрок §e{target} §aразбанен"
|
||
await execute_mc_command_async(f"say {chat_message}")
|
||
result, error = await execute_mc_command_async(command)
|
||
|
||
embed = discord.Embed(title="✅ Разбан выполнен!", description=f"Игрок **{target}** разбанен", color=discord.Color.green())
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
log_action(interaction.user.id, "разбан", target, reason)
|
||
|
||
elif self.punishment_type == "banip":
|
||
if duration:
|
||
command = f"tempbanip {target} {duration}m {reason}"
|
||
else:
|
||
command = f"banip {target} {reason}"
|
||
|
||
chat_message = f"§c§l🌐 IP §e{target} §cзабанен §fпричина: §e{reason}"
|
||
if duration:
|
||
chat_message += f" §fдлительность: §e{format_duration(duration)}"
|
||
|
||
await execute_mc_command_async(f"say {chat_message}")
|
||
result, error = await execute_mc_command_async(command)
|
||
|
||
await send_punishment_notification(interaction.guild, f"{target} (IP)", "Бан по IP", reason, duration, interaction.user)
|
||
|
||
embed = discord.Embed(title="✅ Бан по IP выдан!", description=f"IP **{target}** забанен", color=discord.Color.dark_red())
|
||
embed.add_field(name="📝 Причина", value=reason, inline=False)
|
||
if duration:
|
||
embed.add_field(name="⏱️ Длительность", value=format_duration(duration), inline=True)
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
log_action(interaction.user.id, "бан по IP", target, reason)
|
||
|
||
elif self.punishment_type == "unbanip":
|
||
command = f"unbanip {target}"
|
||
chat_message = f"§a✅ IP §e{target} §aразбанен"
|
||
await execute_mc_command_async(f"say {chat_message}")
|
||
result, error = await execute_mc_command_async(command)
|
||
|
||
embed = discord.Embed(title="✅ Разбан по IP выполнен!", description=f"IP **{target}** разбанен", color=discord.Color.green())
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
log_action(interaction.user.id, "разбан по IP", target, reason)
|
||
|
||
# =====================================================
|
||
# 📝 ЗАЯВКИ
|
||
# =====================================================
|
||
|
||
class RequestsView(ui.View):
|
||
def __init__(self, user_id):
|
||
super().__init__(timeout=120)
|
||
self.user_id = user_id
|
||
|
||
options = [
|
||
SelectOption(label="📝 Создать заявку", value="create", emoji="📝"),
|
||
SelectOption(label="📋 Мои заявки", value="my", emoji="📋"),
|
||
SelectOption(label="✅ Все заявки", value="all", emoji="✅"),
|
||
]
|
||
self.select = ui.Select(placeholder="Выберите действие...", options=options, row=0)
|
||
self.select.callback = self.select_callback
|
||
self.add_item(self.select)
|
||
|
||
close = ui.Button(label="❌ Закрыть", style=ButtonStyle.danger, row=1)
|
||
close.callback = self.close_callback
|
||
self.add_item(close)
|
||
|
||
async def select_callback(self, interaction: discord.Interaction):
|
||
if interaction.user.id != self.user_id:
|
||
await interaction.response.send_message("❌ Это не ваше меню!", ephemeral=True)
|
||
return
|
||
|
||
action = self.select.values[0]
|
||
|
||
if action == "create":
|
||
modal = RequestModal()
|
||
await interaction.response.send_modal(modal)
|
||
elif action == "my":
|
||
await interaction.response.defer()
|
||
requests = load_requests()
|
||
user_requests = [r for r in requests.values() if r.get("author") == str(interaction.user.id)]
|
||
|
||
if not user_requests:
|
||
await interaction.followup.send("📭 У вас нет заявок")
|
||
return
|
||
|
||
embed = discord.Embed(
|
||
title="📋 Мои заявки",
|
||
color=discord.Color.blue()
|
||
)
|
||
for req in user_requests[-5:]:
|
||
embed.add_field(
|
||
name=f"#{req.get('id', '?')} - {req.get('status', 'Неизвестно')}",
|
||
value=f"📝 {req.get('text', '')[:50]}...\n📅 {req.get('date', '')[:16]}",
|
||
inline=False
|
||
)
|
||
await interaction.followup.send(embed=embed)
|
||
elif action == "all":
|
||
await interaction.response.defer()
|
||
requests = load_requests()
|
||
if not requests:
|
||
await interaction.followup.send("📭 Нет заявок")
|
||
return
|
||
|
||
embed = discord.Embed(
|
||
title="✅ Все заявки",
|
||
color=discord.Color.blue()
|
||
)
|
||
for req_id, req in list(requests.items())[-10:]:
|
||
embed.add_field(
|
||
name=f"#{req_id} - {req.get('status', 'Неизвестно')}",
|
||
value=f"👤 <@{req.get('author', '?')}>\n📝 {req.get('text', '')[:50]}...",
|
||
inline=False
|
||
)
|
||
await interaction.followup.send(embed=embed)
|
||
|
||
async def close_callback(self, interaction: discord.Interaction):
|
||
if interaction.user.id != self.user_id:
|
||
await interaction.response.send_message("❌ Это не ваше меню!", ephemeral=True)
|
||
return
|
||
await interaction.message.delete()
|
||
|
||
class RequestModal(ui.Modal, title="📝 Создание заявки"):
|
||
title = ui.TextInput(
|
||
label="Тема",
|
||
placeholder="Кратко опишите тему...",
|
||
required=True,
|
||
style=discord.TextStyle.short
|
||
)
|
||
text = ui.TextInput(
|
||
label="Текст заявки",
|
||
placeholder="Подробно опишите вашу заявку...",
|
||
required=True,
|
||
style=discord.TextStyle.paragraph
|
||
)
|
||
|
||
async def on_submit(self, interaction: discord.Interaction):
|
||
await interaction.response.defer(ephemeral=True)
|
||
|
||
requests = load_requests()
|
||
req_id = str(len(requests) + 1)
|
||
|
||
requests[req_id] = {
|
||
"id": req_id,
|
||
"title": self.title.value,
|
||
"text": self.text.value,
|
||
"author": str(interaction.user.id),
|
||
"status": "Новая",
|
||
"date": datetime.now().isoformat()
|
||
}
|
||
save_requests(requests)
|
||
|
||
embed = discord.Embed(
|
||
title=f"📝 Новая заявка #{req_id}",
|
||
description=self.text.value,
|
||
color=discord.Color.blue()
|
||
)
|
||
embed.add_field(name="📌 Тема", value=self.title.value, inline=False)
|
||
embed.add_field(name="👤 Автор", value=interaction.user.mention, inline=True)
|
||
embed.add_field(name="📅 Дата", value=datetime.now().strftime("%d.%m.%Y %H:%M"), inline=True)
|
||
|
||
for channel in interaction.guild.text_channels:
|
||
if "заявк" in channel.name.lower() or "request" in channel.name.lower():
|
||
await channel.send(embed=embed)
|
||
break
|
||
|
||
await interaction.followup.send(f"✅ Заявка #{req_id} создана!", ephemeral=True)
|
||
|
||
# =====================================================
|
||
# 📢 РАССЫЛКИ
|
||
# =====================================================
|
||
|
||
class BroadcastModal(ui.Modal, title="📢 Отправить рассылку"):
|
||
channel_id = ui.TextInput(
|
||
label="ID канала",
|
||
placeholder="Введите ID канала...",
|
||
required=True,
|
||
style=discord.TextStyle.short
|
||
)
|
||
message = ui.TextInput(
|
||
label="Сообщение",
|
||
placeholder="Введите текст рассылки...",
|
||
required=True,
|
||
style=discord.TextStyle.paragraph
|
||
)
|
||
|
||
async def on_submit(self, interaction: discord.Interaction):
|
||
await interaction.response.defer(ephemeral=True)
|
||
|
||
try:
|
||
channel = interaction.guild.get_channel(int(self.channel_id.value))
|
||
if not channel:
|
||
await interaction.followup.send("❌ Канал не найден!", ephemeral=True)
|
||
return
|
||
|
||
embed = discord.Embed(
|
||
title="📢 Важное сообщение",
|
||
description=self.message.value,
|
||
color=discord.Color.blue()
|
||
)
|
||
embed.set_footer(text=f"Отправлено: {interaction.user.display_name}")
|
||
|
||
await channel.send(embed=embed)
|
||
log_action(interaction.user.id, "рассылка", channel.name, self.message.value[:50])
|
||
|
||
await interaction.followup.send(f"✅ Рассылка отправлена в <#{self.channel_id.value}>!", ephemeral=True)
|
||
except:
|
||
await interaction.followup.send("❌ Ошибка! Проверьте ID канала.", ephemeral=True)
|
||
|
||
# =====================================================
|
||
# 🔐 КОМАНДЫ РОЛЕЙ
|
||
# =====================================================
|
||
|
||
@bot.tree.command(name="role", description="🔐 Управление ролями")
|
||
@app_commands.describe(
|
||
action="Действие: add, remove, list, create",
|
||
member="Участник (для add/remove)",
|
||
role="Название роли или @упоминание",
|
||
name="Название для создания"
|
||
)
|
||
async def slash_role(interaction: discord.Interaction, action: str, member: discord.Member = None, role: str = None, name: str = None):
|
||
if action == "add":
|
||
if not member or not role:
|
||
await interaction.response.send_message("❌ Использование: `/role add <участник> <роль>`")
|
||
return
|
||
|
||
role_obj = discord.utils.get(interaction.guild.roles, name=role)
|
||
if not role_obj:
|
||
await interaction.response.send_message(f"❌ Роль **{role}** не найдена!")
|
||
return
|
||
|
||
await member.add_roles(role_obj)
|
||
log_action(interaction.user.id, "выдача роли", member.name, role)
|
||
await interaction.response.send_message(f"✅ Роль **{role}** выдана {member.mention}!")
|
||
|
||
elif action == "remove":
|
||
if not member or not role:
|
||
await interaction.response.send_message("❌ Использование: `/role remove <участник> <роль>`")
|
||
return
|
||
|
||
role_obj = discord.utils.get(interaction.guild.roles, name=role)
|
||
if not role_obj:
|
||
await interaction.response.send_message(f"❌ Роль **{role}** не найдена!")
|
||
return
|
||
|
||
await member.remove_roles(role_obj)
|
||
log_action(interaction.user.id, "удаление роли", member.name, role)
|
||
await interaction.response.send_message(f"✅ Роль **{role}** убрана у {member.mention}!")
|
||
|
||
elif action == "list":
|
||
embed = discord.Embed(
|
||
title="🔐 Список ролей",
|
||
description="Список всех ролей на сервере",
|
||
color=discord.Color.blue()
|
||
)
|
||
|
||
roles = [r for r in interaction.guild.roles if r.name != "@everyone"]
|
||
roles.sort(key=lambda x: x.position, reverse=True)
|
||
|
||
if not roles:
|
||
embed.description = "📭 На сервере нет ролей (кроме @everyone)"
|
||
await interaction.response.send_message(embed=embed)
|
||
return
|
||
|
||
for r in roles[:25]:
|
||
member_count = len(r.members)
|
||
embed.add_field(
|
||
name=r.name,
|
||
value=f"ID: {r.id}\n👥 {member_count} чел.",
|
||
inline=True
|
||
)
|
||
|
||
if len(roles) > 25:
|
||
embed.set_footer(text=f"И ещё {len(roles)-25} ролей...")
|
||
|
||
await interaction.response.send_message(embed=embed)
|
||
|
||
elif action == "create":
|
||
if not name:
|
||
await interaction.response.send_message("❌ Использование: `/role create <название>`")
|
||
return
|
||
|
||
role = await interaction.guild.create_role(name=name)
|
||
log_action(interaction.user.id, "создание роли", name, "")
|
||
await interaction.response.send_message(f"✅ Роль **{name}** создана (ID: {role.id})!")
|
||
|
||
else:
|
||
await interaction.response.send_message("❌ Доступные действия: `add`, `remove`, `list`, `create`")
|
||
|
||
# =====================================================
|
||
# 🎯 ОСНОВНАЯ КОМАНДА
|
||
# =====================================================
|
||
|
||
@bot.tree.command(name="staff", description="👮 Открыть панель управления персоналом")
|
||
async def slash_staff(interaction: discord.Interaction):
|
||
embed = discord.Embed(
|
||
title="👮 Панель управления персоналом",
|
||
description="Управляйте персоналом и модерацией",
|
||
color=discord.Color.blue()
|
||
)
|
||
embed.add_field(
|
||
name="📋 Доступные действия",
|
||
value="• 👮 Назначение/увольнение\n"
|
||
"• ⚔️ Наказания (Бан/Мут/Кик/Разбан/Размут)\n"
|
||
"• ⚠️ Предупреждения (выдача/снятие/список)\n"
|
||
"• 🌐 Бан по IP / Разбан по IP\n"
|
||
"• 👑 Управление привилегиями (LP)\n"
|
||
"• ➕ Добавить новый ранг\n"
|
||
"• 📝 Заявки\n"
|
||
"• 📢 Рассылки\n"
|
||
"• 📋 Журнал действий\n"
|
||
"• 🔐 Управление ролями\n"
|
||
"• 📊 Статистика\n"
|
||
"• ⚙️ Настройки RCON",
|
||
inline=False
|
||
)
|
||
embed.set_footer(text="При 3 предупреждениях - авто-бан на 2 дня")
|
||
|
||
await interaction.response.send_message(embed=embed, view=StaffMainMenu())
|
||
|
||
message = await interaction.original_response()
|
||
add_panel(interaction.guild_id, interaction.channel_id, message.id, "main")
|
||
|
||
# =====================================================
|
||
# 🎮 КОМАНДЫ ДЛЯ RCON
|
||
# =====================================================
|
||
|
||
@bot.tree.command(name="mc", description="🎮 Выполнить команду на Minecraft сервере")
|
||
@app_commands.describe(
|
||
command="Команда для выполнения на сервере"
|
||
)
|
||
async def slash_mc(interaction: discord.Interaction, command: str):
|
||
if not interaction.user.guild_permissions.administrator:
|
||
await interaction.response.send_message("❌ У вас нет прав для выполнения этой команды!", ephemeral=True)
|
||
return
|
||
|
||
await interaction.response.defer(ephemeral=True)
|
||
|
||
result, error = await execute_mc_command_async(command)
|
||
|
||
embed = discord.Embed(
|
||
title="🎮 Выполнение команды",
|
||
description=f"```{command}```",
|
||
color=discord.Color.blue()
|
||
)
|
||
embed.add_field(name="📡 Результат", value=f"```{error if error else result or '✅ Команда выполнена'}```", inline=False)
|
||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||
|
||
# =====================================================
|
||
# 🔄 KEEP-ALIVE ДЛЯ RENDER
|
||
# =====================================================
|
||
|
||
from flask import Flask
|
||
import threading
|
||
|
||
app = Flask('')
|
||
|
||
@app.route('/')
|
||
def home():
|
||
return "👮 Mohist_Staff работает!"
|
||
|
||
def run():
|
||
app.run(host='0.0.0.0', port=10000)
|
||
|
||
def keep_alive():
|
||
t = threading.Thread(target=run)
|
||
t.start()
|
||
|
||
keep_alive()
|
||
|
||
print("✅ Keep-Alive запущен!")
|
||
|
||
# =====================================================
|
||
# 🚀 ЗАПУСК
|
||
# =====================================================
|
||
|
||
async def main():
|
||
async with bot:
|
||
print("🔄 Запуск Mohist_Staff...")
|
||
await bot.start(TOKEN)
|
||
|
||
if __name__ == "__main__":
|
||
try:
|
||
asyncio.run(main())
|
||
except discord.LoginFailure:
|
||
print("❌ Неверный токен!")
|
||
except KeyboardInterrupt:
|
||
print("\n🛑 Бот остановлен")
|
||
except Exception as e:
|
||
print(f"❌ Ошибка: {e}") |