Добавить WL voice bots/bot_voice.py

This commit is contained in:
NIKITIK0 2026-10-16 15:55:12 +03:00
parent 6f613beb3d
commit 7fc87c3652

394
WL voice bots/bot_voice.py Normal file
View File

@ -0,0 +1,394 @@
#!/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.voice_states = True
intents.guilds = True
intents.message_content = True
intents.members = True
class VoiceBot(commands.Bot):
def __init__(self):
super().__init__(command_prefix='!', intents=intents)
self.voice_data = {\} # owner_id -> {voice_id, panel_channel_id, panel_msg_id\}
self.modal_owner_cache = {\} # user_id -> owner_id
async def setup_hook(self):
await self.tree.sync()
logger.info("✅ Слэш-команды синхронизированы!")
bot = VoiceBot()
@bot.event
async def on_ready():
logger.info(f'✅ Бот {bot.user\} запущен!')
await bot.change_presence(activity=discord.Activity(
type=discord.ActivityType.listening,
name="🎵 /voice-setup"
))
await cleanup_old_channels()
# =====================================================
# 🧹 ОЧИСТКА ПРИ ЗАПУСКЕ (удаляем каналы без людей)
# =====================================================
def has_humans(channel):
"""Проверяет, есть ли в голосовом канале хотя бы один человек (не бот)"""
return any(not member.bot for member in channel.members)
async def cleanup_old_channels():
"""Удаляет голосовые каналы, в которых нет людей, и все текстовые каналы, созданные ботом"""
for guild in bot.guilds:
category_id = get_setting('voice_category')
if not category_id:
continue
category = guild.get_channel(int(category_id))
if not category:
continue
deleted_count = 0
for channel in category.channels:
if channel.name.startswith(('🎧 Комната', '💬-чат-')):
if isinstance(channel, discord.VoiceChannel):
# Удаляем только если нет людей
if not has_humans(channel):
try:
await channel.delete()
deleted_count += 1
logger.info(f"Удалён голосовой канал без людей: {channel.name\}")
except Exception as e:
logger.error(f"Не удалось удалить {channel.name\}: {e\}")
elif isinstance(channel, discord.TextChannel):
# Все текстовые каналы удаляем смело (они не содержат пользователей)
try:
await channel.delete()
deleted_count += 1
logger.info(f"Удалён текстовый канал: {channel.name\}")
except Exception as e:
logger.error(f"Не удалось удалить {channel.name\}: {e\}")
if deleted_count > 0:
logger.info(f"Очищено {deleted_count\} каналов в категории {category.name\}")
bot.voice_data.clear()
# =====================================================
# 🔧 КОМАНДЫ НАСТРОЙКИ
# =====================================================
@bot.tree.command(name="voice-setup", description="Настройка модуля временных каналов")
@app_commands.default_permissions(administrator=True)
@app_commands.describe(
trigger="Голосовой канал-триггер",
category="Категория для временных каналов"
)
async def voice_setup(interaction: discord.Interaction,
trigger: discord.VoiceChannel,
category: discord.CategoryChannel):
set_setting('voice_trigger', str(trigger.id))
set_setting('voice_category', str(category.id))
await interaction.response.send_message(
f'✅ Триггер: {trigger.mention\}, категория: {category.name\}',
ephemeral=True
)
logger.info(f"Voice setup: trigger={trigger.id\}, category={category.id\}")
@bot.tree.command(name="voice-panel-channel", description="Установить канал для всех панелей (общий)")
@app_commands.default_permissions(administrator=True)
@app_commands.describe(channel="Текстовый канал для панелей (None = отдельные каналы)")
async def voice_panel_channel(interaction: discord.Interaction, channel: discord.TextChannel = None):
if channel:
set_setting('voice_panel_channel', str(channel.id))
await interaction.response.send_message(
f'✅ Панели будут отправляться в {channel.mention\}',
ephemeral=True
)
else:
set_setting('voice_panel_channel', None)
await interaction.response.send_message(
'✅ Панели будут создаваться в отдельных каналах',
ephemeral=True
)
logger.info(f"Panel channel set to {channel.id if channel else 'None'\}")
# =====================================================
# 🎤 СОЗДАНИЕ ВРЕМЕННОГО КАНАЛА + ПАНЕЛИ
# =====================================================
async def get_panel_channel(guild):
"""Возвращает канал для панели (общий или None для отдельного)"""
panel_channel_id = get_setting('voice_panel_channel')
if panel_channel_id:
channel = guild.get_channel(int(panel_channel_id))
if channel and isinstance(channel, discord.TextChannel):
return channel
return None
@bot.event
async def on_voice_state_update(member, before, after):
trigger_id = get_setting('voice_trigger')
if not trigger_id:
return
trigger_id = int(trigger_id)
# Вход в триггерный канал
if after.channel and after.channel.id == trigger_id and before.channel != after.channel:
guild = member.guild
category_id = get_setting('voice_category')
if category_id:
category = guild.get_channel(int(category_id))
else:
category = await guild.create_category('🎙️ Временные каналы')
set_setting('voice_category', str(category.id))
# Проверяем существование каналов для этого пользователя
if member.id in bot.voice_data:
data = bot.voice_data[member.id]
vc = guild.get_channel(data['voice_id'])
if vc:
await member.move_to(vc)
return
else:
# Если голосовой удалён, удаляем панель и данные
panel_channel = guild.get_channel(data['panel_channel_id'])
if panel_channel:
try:
msg = await panel_channel.fetch_message(data['panel_msg_id'])
await msg.delete()
except:
pass
del bot.voice_data[member.id]
# Создаём голосовой канал
voice = await guild.create_voice_channel(
f'🎧 Комната {member.display_name\}',
category=category,
user_limit=5,
overwrites={
guild.default_role: discord.PermissionOverwrite(view_channel=True, connect=True),
member: discord.PermissionOverwrite(manage_channels=True, mute_members=True, deafen_members=True)
\}
)
# Определяем канал для панели
panel_channel = await get_panel_channel(guild)
if panel_channel is None:
# Создаём отдельный текстовый канал
panel_channel = await guild.create_text_channel(
f'💬-чат-{member.display_name\}',
category=category,
overwrites={
guild.default_role: discord.PermissionOverwrite(view_channel=False),
member: discord.PermissionOverwrite(view_channel=True, send_messages=True)
\}
)
# Формируем embed с названием канала сверху
embed = discord.Embed(
title=f'🎛️ Управление: {voice.name\}',
description=f'Ваш голосовой канал: {voice.mention\}\\nИспользуйте кнопки ниже.',
color=discord.Color.green()
)
embed.set_footer(text=f'Владелец: {member.display_name\}')
view = discord.ui.View()
view.add_item(discord.ui.Button(label='Изменить название', style=discord.ButtonStyle.primary, custom_id='voice_name'))
view.add_item(discord.ui.Button(label='Лимит участников', style=discord.ButtonStyle.secondary, custom_id='voice_limit'))
view.add_item(discord.ui.Button(label='Приватный/Публичный', style=discord.ButtonStyle.success, custom_id='voice_private'))
view.add_item(discord.ui.Button(label='Забрать владельца', style=discord.ButtonStyle.danger, custom_id='voice_claim'))
msg = await panel_channel.send(embed=embed, view=view)
# Сохраняем данные
bot.voice_data[member.id] = {
'voice_id': voice.id,
'panel_channel_id': panel_channel.id,
'panel_msg_id': msg.id
\}
await member.move_to(voice)
logger.info(f"Создан канал {voice.name\} для {member\} (панель в {panel_channel.name\})")
# ====== ОЧИСТКА ПУСТЫХ КАНАЛОВ (ТОЛЬКО ЕСЛИ НЕТ ЛЮДЕЙ) ======
guild = member.guild
to_delete = []
for owner_id, data in bot.voice_data.items():
vc = guild.get_channel(data['voice_id'])
# Удаляем, если канал пуст или в нём только боты
if vc and not has_humans(vc):
to_delete.append(owner_id)
for owner_id in to_delete:
data = bot.voice_data[owner_id]
vc = guild.get_channel(data['voice_id'])
panel_channel = guild.get_channel(data['panel_channel_id'])
if vc:
await vc.delete()
if panel_channel:
try:
msg = await panel_channel.fetch_message(data['panel_msg_id'])
await msg.delete()
except:
pass
del bot.voice_data[owner_id]
logger.info(f"Удалены каналы для {owner_id\} (в канале не было людей)")
# =====================================================
# 🎛️ ОБРАБОТКА КНОПОК И МОДАЛЬНЫХ ОКОН
# =====================================================
@bot.event
async def on_interaction(interaction: discord.Interaction):
if interaction.type == discord.InteractionType.component:
custom_id = interaction.data.get('custom_id')
if not custom_id:
return
if custom_id.startswith('voice_'):
# Находим владельца по ID сообщения панели
owner_id = None
for uid, data in bot.voice_data.items():
if data['panel_msg_id'] == interaction.message.id:
owner_id = uid
break
if not owner_id:
await interaction.response.send_message('❌ Панель не найдена.', ephemeral=True)
return
if interaction.user.id != owner_id:
await interaction.response.send_message('❌ Только владелец канала может управлять.', ephemeral=True)
return
data = bot.voice_data[owner_id]
voice = interaction.guild.get_channel(data['voice_id'])
if not voice:
await interaction.response.send_message('❌ Голосовой канал удалён.', ephemeral=True)
return
if custom_id == 'voice_name':
bot.modal_owner_cache[interaction.user.id] = owner_id
modal = discord.ui.Modal(title='Изменить название')
modal.add_item(discord.ui.TextInput(label='Новое название', default=voice.name))
await interaction.response.send_modal(modal)
elif custom_id == 'voice_limit':
bot.modal_owner_cache[interaction.user.id] = owner_id
modal = discord.ui.Modal(title='Изменить лимит')
modal.add_item(discord.ui.TextInput(label='Лимит (0 — безлимит)', default=str(voice.user_limit)))
await interaction.response.send_modal(modal)
elif custom_id == 'voice_private':
perms = voice.overwrites_for(interaction.guild.default_role)
is_private = perms.connect is False
if is_private:
await voice.set_permissions(interaction.guild.default_role, connect=None)
await interaction.response.send_message('🔓 Канал теперь публичный.', ephemeral=True)
else:
await voice.set_permissions(interaction.guild.default_role, connect=False)
await interaction.response.send_message('🔒 Канал теперь приватный.', ephemeral=True)
elif custom_id == 'voice_claim':
await interaction.response.send_message('Используйте команду `/voice-transfer @пользователь`', ephemeral=True)
elif interaction.type == discord.InteractionType.modal_submit:
custom_id = interaction.custom_id
owner_id = bot.modal_owner_cache.get(interaction.user.id)
if not owner_id:
# Фолбэк: поиск по сообщению панели
for uid, data in bot.voice_data.items():
if data['panel_msg_id'] == interaction.message.id:
owner_id = uid
break
if not owner_id:
await interaction.response.send_message('❌ Не удалось определить владельца.', ephemeral=True)
return
voice = interaction.guild.get_channel(bot.voice_data[owner_id]['voice_id'])
if not voice:
await interaction.response.send_message('❌ Голосовой канал удалён.', ephemeral=True)
return
if custom_id == 'voice_name':
new_name = interaction.data['components'][0]['components'][0]['value']
await voice.edit(name=new_name)
# Обновляем заголовок в сообщении панели
data = bot.voice_data[owner_id]
panel_channel = interaction.guild.get_channel(data['panel_channel_id'])
if panel_channel:
try:
msg = await panel_channel.fetch_message(data['panel_msg_id'])
embed = msg.embeds[0]
embed.title = f'🎛️ Управление: {new_name\}'
await msg.edit(embed=embed)
except:
pass
await interaction.response.send_message(f'✅ Название изменено на **{new_name\}**.', ephemeral=True)
elif custom_id == 'voice_limit':
try:
limit = int(interaction.data['components'][0]['components'][0]['value'])
if limit < 0:
raise ValueError
except:
await interaction.response.send_message('❌ Введите число >= 0.', ephemeral=True)
return
await voice.edit(user_limit=limit)
await interaction.response.send_message(f'✅ Лимит установлен на **{limit\}**.', ephemeral=True)
# Очищаем кэш
if interaction.user.id in bot.modal_owner_cache:
del bot.modal_owner_cache[interaction.user.id]
# =====================================================
# 🚀 ЗАПУСК
# =====================================================
if __name__ == "__main__":
try:
bot.run(TOKEN)
except discord.LoginFailure:
logger.error("❌ Неверный токен!")
except Exception as e:
logger.error(f"❌ Ошибка: {e\}")$0