Добавить WL voice bots/bot_voice.py
This commit is contained in:
parent
6f613beb3d
commit
7fc87c3652
394
WL voice bots/bot_voice.py
Normal file
394
WL voice bots/bot_voice.py
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user