WL_Bots/WL tickets bot/bot_tickets.py

441 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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}")