Добавить WL tickets bot/bot_tickets.py

This commit is contained in:
NIKITIK0 2026-10-16 15:54:10 +03:00
parent 127a280bfb
commit 6f613beb3d

View File

@ -0,0 +1,441 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
TOKEN = "Ваш токен"
import discord
from discord.ext import commands
from discord import app_commands
import json
import os
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# =====================================================
# 📁 НАСТРОЙКИ (JSON)
# =====================================================
SETTINGS_FILE = "settings.json"
def load_settings():
if not os.path.exists(SETTINGS_FILE):
return {}
with open(SETTINGS_FILE, 'r', encoding='utf-8') as f:
try:
return json.load(f)
except:
return {}
def save_settings(data):
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
def get_setting(key):
return load_settings().get(key)
def set_setting(key, value):
data = load_settings()
data[key] = value
save_settings(data)
# =====================================================
# 🤖 БОТ
# =====================================================
intents = discord.Intents.default()
intents.guilds = True
intents.message_content = True
intents.members = True
class TicketsBot(commands.Bot):
def __init__(self):
super().__init__(command_prefix='!', intents=intents)
self.tickets = {} # channel_id -> owner_id
self.reason_cache = {} # user_id -> reason_name (для панели)
async def setup_hook(self):
await self.tree.sync()
logger.info("✅ Слэш-команды синхронизированы!")
bot = TicketsBot()
@bot.event
async def on_ready():
logger.info(f'✅ Бот {bot.user} запущен!')
await bot.change_presence(activity=discord.Activity(
type=discord.ActivityType.watching,
name="🎫 /ticket-panel"
))
# =====================================================
# 🔧 КОМАНДЫ НАСТРОЙКИ (основные)
# =====================================================
@bot.tree.command(name="ticket-setup", description="Настройка системы тикетов (категория, роль модеров, лог)")
@app_commands.default_permissions(administrator=True)
@app_commands.describe(
category="Категория для тикетов (по умолчанию)",
mod_role="Роль модераторов",
log_channel="Канал для логов"
)
async def ticket_setup(interaction: discord.Interaction,
category: discord.CategoryChannel,
mod_role: discord.Role,
log_channel: discord.TextChannel):
set_setting('tickets_category', str(category.id))
set_setting('tickets_mod_role', str(mod_role.id))
set_setting('tickets_log_channel', str(log_channel.id))
await interaction.response.send_message(
f'✅ Категория по умолчанию: {category.name}, роль: {mod_role.mention}, лог: {log_channel.mention}',
ephemeral=True
)
logger.info(f"Ticket setup: category={category.id}, mod_role={mod_role.id}, log={log_channel.id}")
# =====================================================
# 📋 УПРАВЛЕНИЕ ПРИЧИНАМИ (админ)
# =====================================================
@bot.tree.command(name="ticket-reason-add", description="Добавить причину для тикета с привязкой к категории")
@app_commands.default_permissions(administrator=True)
@app_commands.describe(
name="Название причины (например, 'Техпомощь')",
category="Категория, куда будут создаваться тикеты с этой причиной",
emoji="Эмодзи для отображения (опционально)"
)
async def ticket_reason_add(interaction: discord.Interaction,
name: str,
category: discord.CategoryChannel,
emoji: str = "📌"):
reasons = load_settings().get('ticket_reasons', {})
if name in reasons:
await interaction.response.send_message(f'❌ Причина **{name}** уже существует!', ephemeral=True)
return
reasons[name] = {
'category_id': str(category.id),
'emoji': emoji
}
set_setting('ticket_reasons', reasons)
await interaction.response.send_message(
f'✅ Причина **{name}** добавлена (категория: {category.name}, эмодзи: {emoji})',
ephemeral=True
)
logger.info(f"Added reason: {name} -> category {category.id}")
@bot.tree.command(name="ticket-reason-remove", description="Удалить причину")
@app_commands.default_permissions(administrator=True)
@app_commands.describe(name="Название причины")
async def ticket_reason_remove(interaction: discord.Interaction, name: str):
reasons = load_settings().get('ticket_reasons', {})
if name not in reasons:
await interaction.response.send_message(f'❌ Причина **{name}** не найдена!', ephemeral=True)
return
del reasons[name]
set_setting('ticket_reasons', reasons)
await interaction.response.send_message(f'✅ Причина **{name}** удалена.', ephemeral=True)
logger.info(f"Removed reason: {name}")
@bot.tree.command(name="ticket-reason-list", description="Показать список всех причин")
async def ticket_reason_list(interaction: discord.Interaction):
reasons = load_settings().get('ticket_reasons', {})
if not reasons:
await interaction.response.send_message('📭 Причины не добавлены.', ephemeral=True)
return
embed = discord.Embed(
title="📋 Список причин тикетов",
color=discord.Color.blue()
)
for name, data in reasons.items():
category = interaction.guild.get_channel(int(data['category_id']))
cat_name = category.name if category else "❌ Удалена"
embed.add_field(
name=f"{data.get('emoji', '📌')} {name}",
value=f"Категория: {cat_name}",
inline=False
)
await interaction.response.send_message(embed=embed, ephemeral=True)
# =====================================================
# 🎫 СОЗДАНИЕ ПАНЕЛИ ТИКЕТОВ
# =====================================================
@bot.tree.command(name="ticket-panel", description="Создать панель тикетов в указанном канале")
@app_commands.default_permissions(administrator=True)
@app_commands.describe(channel="Канал, куда отправить панель")
async def ticket_panel(interaction: discord.Interaction, channel: discord.TextChannel):
reasons = load_settings().get('ticket_reasons', {})
if not reasons:
await interaction.response.send_message(
'❌ Нет ни одной причины. Добавьте их через `/ticket-reason-add`.',
ephemeral=True
)
return
# Создаём Select menu с причинами
options = []
for name, data in reasons.items():
emoji = data.get('emoji', '📌')
options.append(
discord.SelectOption(
label=name,
description=f"Тикет по причине '{name}'",
emoji=emoji,
value=name
)
)
select = discord.ui.Select(
placeholder="Выберите причину обращения",
options=options,
custom_id="ticket_reason_select"
)
button = discord.ui.Button(
label="📩 Создать тикет",
style=discord.ButtonStyle.primary,
custom_id="ticket_create_button"
)
view = discord.ui.View()
view.add_item(select)
view.add_item(button)
embed = discord.Embed(
title="🎫 Система тикетов",
description="Выберите причину обращения и нажмите кнопку, чтобы создать тикет.\nМодераторы свяжутся с вами в ближайшее время.",
color=discord.Color.blue()
)
embed.set_footer(text="Нажимайте только при необходимости")
await channel.send(embed=embed, view=view)
await interaction.response.send_message(f"✅ Панель отправлена в {channel.mention}", ephemeral=True)
# =====================================================
# 📥 ОБРАБОТКА ВЗАИМОДЕЙСТВИЙ (селект и кнопка)
# =====================================================
@bot.event
async def on_interaction(interaction: discord.Interaction):
# Селект причины
if interaction.type == discord.InteractionType.component:
custom_id = interaction.data.get('custom_id')
if custom_id == "ticket_reason_select":
reason_name = interaction.data["values"][0]
bot.reason_cache[interaction.user.id] = reason_name
await interaction.response.send_message(
f"✅ Выбрана причина: **{reason_name}**",
ephemeral=True
)
return
# Кнопка "Создать тикет"
if custom_id == "ticket_create_button":
reason_name = bot.reason_cache.get(interaction.user.id)
if not reason_name:
await interaction.response.send_message(
"❌ Сначала выберите причину в выпадающем меню!",
ephemeral=True
)
return
# Проверяем, существует ли ещё причина
reasons = load_settings().get('ticket_reasons', {})
if reason_name not in reasons:
await interaction.response.send_message(
f"❌ Причина **{reason_name}** была удалена. Выберите другую.",
ephemeral=True
)
del bot.reason_cache[interaction.user.id]
return
await create_ticket(interaction, reason_name)
return
# Обработка кнопок закрытия и добавления пользователя
if custom_id == "close_ticket":
await close_ticket(interaction)
return
if custom_id == "add_user_ticket":
await interaction.response.send_message(
"Используйте `/ticket-add @пользователь` в канале тикета",
ephemeral=True
)
return
# Модальные окна (если добавим позже) — пока нет
# =====================================================
# 🎫 ФУНКЦИЯ СОЗДАНИЯ ТИКЕТА
# =====================================================
async def create_ticket(interaction: discord.Interaction, reason_name: str):
guild = interaction.guild
member = interaction.user
reasons = load_settings().get('ticket_reasons', {})
reason_data = reasons.get(reason_name)
if not reason_data:
await interaction.response.send_message(
f'❌ Причина **{reason_name}** не найдена.',
ephemeral=True
)
del bot.reason_cache[interaction.user.id]
return
# Определяем категорию для тикета
category_id = reason_data.get('category_id')
if not category_id:
# Если не указана — используем дефолтную
category_id = get_setting('tickets_category')
if not category_id:
await interaction.response.send_message(
'❌ Категория не настроена. Используйте `/ticket-setup` или укажите категорию в причине.',
ephemeral=True
)
del bot.reason_cache[interaction.user.id]
return
category = guild.get_channel(int(category_id))
if not category:
await interaction.response.send_message(
f'❌ Категория не найдена (ID: {category_id})',
ephemeral=True
)
del bot.reason_cache[interaction.user.id]
return
# Проверяем, нет ли уже открытого тикета
for ch_id, uid in bot.tickets.items():
if uid == member.id:
ch = guild.get_channel(ch_id)
if ch:
await interaction.response.send_message(
f'❌ У вас уже есть открытый тикет: {ch.mention}',
ephemeral=True
)
del bot.reason_cache[interaction.user.id]
return
else:
del bot.tickets[ch_id]
# Роль модераторов
mod_role_id = get_setting('tickets_mod_role')
if not mod_role_id:
await interaction.response.send_message(
'❌ Роль модераторов не настроена. Используйте `/ticket-setup`.',
ephemeral=True
)
del bot.reason_cache[interaction.user.id]
return
mod_role = guild.get_role(int(mod_role_id))
# Счётчик
counter = int(get_setting('tickets_counter') or 0) + 1
set_setting('tickets_counter', str(counter))
# Название канала: причина-номер
safe_name = reason_name.lower().replace(' ', '-')
ticket_name = f"{safe_name}-{counter:03d}"
# Права доступа
overwrites = {
guild.default_role: discord.PermissionOverwrite(view_channel=False),
member: discord.PermissionOverwrite(view_channel=True, send_messages=True, attach_files=True),
mod_role: discord.PermissionOverwrite(view_channel=True, send_messages=True),
}
for role in guild.roles:
if role.permissions.administrator:
overwrites[role] = discord.PermissionOverwrite(view_channel=True, send_messages=True)
channel = await guild.create_text_channel(ticket_name, category=category, overwrites=overwrites)
bot.tickets[channel.id] = member.id
# Приветственное сообщение с причиной
embed = discord.Embed(
title=f'🎫 Тикет #{counter}',
description=f"**Причина:** {reason_name}\n"
f"Привет, {member.mention}!\n"
f"Опишите вашу проблему. Модераторы скоро подключатся.",
color=discord.Color.green()
)
view = discord.ui.View()
view.add_item(discord.ui.Button(label='🔒 Закрыть тикет', style=discord.ButtonStyle.danger, custom_id='close_ticket'))
view.add_item(discord.ui.Button(label='➕ Добавить пользователя', style=discord.ButtonStyle.primary, custom_id='add_user_ticket'))
await channel.send(embed=embed, view=view)
# Лог
log_channel_id = get_setting('tickets_log_channel')
if log_channel_id:
log_channel = guild.get_channel(int(log_channel_id))
if log_channel:
await log_channel.send(f'📝 Тикет {ticket_name} (причина: {reason_name}) открыт {member.mention}')
# Удаляем кэш причины
if interaction.user.id in bot.reason_cache:
del bot.reason_cache[interaction.user.id]
await interaction.response.send_message(f'✅ Тикет создан: {channel.mention}', ephemeral=True)
# =====================================================
# 🔒 ЗАКРЫТИЕ ТИКЕТА
# =====================================================
async def close_ticket(interaction):
channel = interaction.channel
if channel.id not in bot.tickets:
await interaction.response.send_message('❌ Это не тикет-канал.', ephemeral=True)
return
owner_id = bot.tickets[channel.id]
member = interaction.user
mod_role_id = get_setting('tickets_mod_role')
is_mod = mod_role_id and member.get_role(int(mod_role_id)) is not None
is_owner = member.id == owner_id
is_admin = member.guild_permissions.manage_channels
if not (is_owner or is_mod or is_admin):
await interaction.response.send_message('❌ У вас нет прав закрыть этот тикет.', ephemeral=True)
return
log_channel_id = get_setting('tickets_log_channel')
if log_channel_id:
log_channel = interaction.guild.get_channel(int(log_channel_id))
if log_channel:
await log_channel.send(f'🔒 Тикет {channel.name} закрыт {member.mention}')
await interaction.response.send_message('⏳ Тикет закрывается...')
del bot.tickets[channel.id]
await channel.delete()
# =====================================================
# ➕ ДОБАВЛЕНИЕ ПОЛЬЗОВАТЕЛЯ
# =====================================================
@bot.tree.command(name="ticket-add", description="Добавить пользователя в тикет")
@app_commands.describe(user="Пользователь для добавления")
async def ticket_add(interaction: discord.Interaction, user: discord.Member):
if interaction.channel.id not in bot.tickets:
await interaction.response.send_message('❌ Это не тикет-канал.', ephemeral=True)
return
owner_id = bot.tickets[interaction.channel.id]
member = interaction.user
mod_role_id = get_setting('tickets_mod_role')
is_mod = mod_role_id and member.get_role(int(mod_role_id)) is not None
is_owner = member.id == owner_id
is_admin = member.guild_permissions.manage_channels
if not (is_owner or is_mod or is_admin):
await interaction.response.send_message('❌ Недостаточно прав.', ephemeral=True)
return
await interaction.channel.set_permissions(user, view_channel=True, send_messages=True, attach_files=True)
await interaction.response.send_message(f'✅ {user.mention} добавлен в тикет.', ephemeral=True)
# =====================================================
# 🚀 ЗАПУСК
# =====================================================
if __name__ == "__main__":
try:
bot.run(TOKEN)
except discord.LoginFailure:
logger.error("❌ Неверный токен!")
except Exception as e:
logger.error(f"❌ Ошибка: {e}")