Добавить WL level bots/bot_level.py
This commit is contained in:
parent
fd3cbaeb8a
commit
06fad40ad0
951
WL level bots/bot_level.py
Normal file
951
WL level bots/bot_level.py
Normal file
@ -0,0 +1,951 @@
|
|||||||
|
#!/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
|
||||||
|
import math
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# 📁 РАБОТА С ДАННЫМИ
|
||||||
|
# =====================================================
|
||||||
|
|
||||||
|
LEVEL_FILE = "level_data.json"
|
||||||
|
CONFIG_FILE = "level_config.json"
|
||||||
|
VOICE_TIME_FILE = "voice_time.json"
|
||||||
|
PROFILE_FILE = "user_profiles.json"
|
||||||
|
|
||||||
|
def load_level_data():
|
||||||
|
try:
|
||||||
|
with open(LEVEL_FILE, 'r') as f:
|
||||||
|
return json.load(f)
|
||||||
|
except:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def save_level_data(data):
|
||||||
|
with open(LEVEL_FILE, 'w') as f:
|
||||||
|
json.dump(data, f, indent=4)
|
||||||
|
|
||||||
|
def load_voice_time():
|
||||||
|
try:
|
||||||
|
with open(VOICE_TIME_FILE, 'r') as f:
|
||||||
|
return json.load(f)
|
||||||
|
except:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def save_voice_time(data):
|
||||||
|
with open(VOICE_TIME_FILE, 'w') as f:
|
||||||
|
json.dump(data, f, indent=4)
|
||||||
|
|
||||||
|
def load_profiles():
|
||||||
|
try:
|
||||||
|
with open(PROFILE_FILE, 'r') as f:
|
||||||
|
return json.load(f)
|
||||||
|
except:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def save_profiles(data):
|
||||||
|
with open(PROFILE_FILE, 'w') as f:
|
||||||
|
json.dump(data, f, indent=4)
|
||||||
|
|
||||||
|
def get_user_profile(user_id, guild_id):
|
||||||
|
profiles = load_profiles()
|
||||||
|
guild_profiles = profiles.get(str(guild_id), {})
|
||||||
|
return guild_profiles.get(str(user_id), {})
|
||||||
|
|
||||||
|
def update_user_profile(user_id, guild_id, data):
|
||||||
|
profiles = load_profiles()
|
||||||
|
if str(guild_id) not in profiles:
|
||||||
|
profiles[str(guild_id)] = {}
|
||||||
|
profiles[str(guild_id)][str(user_id)] = data
|
||||||
|
save_profiles(profiles)
|
||||||
|
|
||||||
|
def load_config():
|
||||||
|
try:
|
||||||
|
with open(CONFIG_FILE, 'r') as f:
|
||||||
|
return json.load(f)
|
||||||
|
except:
|
||||||
|
return {
|
||||||
|
"message_xp": 1,
|
||||||
|
"voice_xp": 2,
|
||||||
|
"voice_interval": 60,
|
||||||
|
"level_up_message": True,
|
||||||
|
"level_up_channel": None,
|
||||||
|
"roles": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
def save_config(data):
|
||||||
|
with open(CONFIG_FILE, 'w') as f:
|
||||||
|
json.dump(data, f, indent=4)
|
||||||
|
|
||||||
|
def format_time(seconds):
|
||||||
|
hours = int(seconds // 3600)
|
||||||
|
minutes = int((seconds % 3600) // 60)
|
||||||
|
secs = int(seconds % 60)
|
||||||
|
|
||||||
|
if hours > 0:
|
||||||
|
return f"{hours} дн {hours} ч {minutes} мин {secs} сек" if hours > 24 else f"{hours} ч {minutes} мин {secs} сек"
|
||||||
|
elif minutes > 0:
|
||||||
|
return f"{minutes} мин {secs} сек"
|
||||||
|
else:
|
||||||
|
return f"{secs} сек"
|
||||||
|
|
||||||
|
def create_progress_bar(progress, length=15):
|
||||||
|
filled = int(progress / 100 * length)
|
||||||
|
empty = length - filled
|
||||||
|
return f"`{'█' * filled}{'░' * empty}`"
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# 🎮 БОТ
|
||||||
|
# =====================================================
|
||||||
|
|
||||||
|
intents = discord.Intents.default()
|
||||||
|
intents.message_content = True
|
||||||
|
intents.guilds = True
|
||||||
|
intents.members = True
|
||||||
|
intents.voice_states = True
|
||||||
|
|
||||||
|
bot = commands.Bot(command_prefix='!', intents=intents)
|
||||||
|
|
||||||
|
voice_tracker = {}
|
||||||
|
message_tracker = {}
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# 📊 ФУНКЦИИ ДЛЯ XP И УРОВНЕЙ
|
||||||
|
# =====================================================
|
||||||
|
|
||||||
|
def get_xp_for_level(level):
|
||||||
|
return 5 * (level ** 2) + 50 * level + 100
|
||||||
|
|
||||||
|
def get_level_from_xp(xp):
|
||||||
|
level = 0
|
||||||
|
while True:
|
||||||
|
required = get_xp_for_level(level)
|
||||||
|
if xp < required:
|
||||||
|
break
|
||||||
|
xp -= required
|
||||||
|
level += 1
|
||||||
|
return level
|
||||||
|
|
||||||
|
def get_progress(user_data):
|
||||||
|
xp = user_data.get("xp", 0)
|
||||||
|
level = user_data.get("level", 0)
|
||||||
|
current_level_xp = get_xp_for_level(level - 1) if level > 0 else 0
|
||||||
|
next_level_xp = get_xp_for_level(level)
|
||||||
|
progress = (xp - current_level_xp) / (next_level_xp - current_level_xp) * 100
|
||||||
|
return min(progress, 100)
|
||||||
|
|
||||||
|
def get_rank(user_id, guild_id):
|
||||||
|
data = load_level_data()
|
||||||
|
guild_data = data.get(str(guild_id), {})
|
||||||
|
|
||||||
|
sorted_users = sorted(
|
||||||
|
guild_data.items(),
|
||||||
|
key=lambda x: (x[1].get("level", 0), x[1].get("xp", 0)),
|
||||||
|
reverse=True
|
||||||
|
)
|
||||||
|
|
||||||
|
for i, (uid, _) in enumerate(sorted_users, 1):
|
||||||
|
if uid == str(user_id):
|
||||||
|
return i
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_voice_time(user_id, guild_id):
|
||||||
|
data = load_voice_time()
|
||||||
|
guild_data = data.get(str(guild_id), {})
|
||||||
|
return guild_data.get(str(user_id), 0)
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# ✏️ РЕДАКТИРОВАНИЕ ПРОФИЛЯ
|
||||||
|
# =====================================================
|
||||||
|
|
||||||
|
class ProfileEditModal(ui.Modal, title="✏️ Изменение профиля"):
|
||||||
|
def __init__(self, user_id, guild_id):
|
||||||
|
super().__init__()
|
||||||
|
self.user_id = user_id
|
||||||
|
self.guild_id = guild_id
|
||||||
|
|
||||||
|
profile = get_user_profile(user_id, guild_id)
|
||||||
|
|
||||||
|
self.name = ui.TextInput(
|
||||||
|
label="Имя",
|
||||||
|
placeholder="Введите ваше имя...",
|
||||||
|
default=profile.get("name", ""),
|
||||||
|
required=False,
|
||||||
|
style=discord.TextStyle.short,
|
||||||
|
max_length=50
|
||||||
|
)
|
||||||
|
self.add_item(self.name)
|
||||||
|
|
||||||
|
self.age = ui.TextInput(
|
||||||
|
label="Возраст",
|
||||||
|
placeholder="Введите ваш возраст...",
|
||||||
|
default=profile.get("age", ""),
|
||||||
|
required=False,
|
||||||
|
style=discord.TextStyle.short,
|
||||||
|
max_length=3
|
||||||
|
)
|
||||||
|
self.add_item(self.age)
|
||||||
|
|
||||||
|
self.gender = ui.TextInput(
|
||||||
|
label="Пол",
|
||||||
|
placeholder="Муж / Жен / Другой...",
|
||||||
|
default=profile.get("gender", ""),
|
||||||
|
required=False,
|
||||||
|
style=discord.TextStyle.short,
|
||||||
|
max_length=20
|
||||||
|
)
|
||||||
|
self.add_item(self.gender)
|
||||||
|
|
||||||
|
self.bio = ui.TextInput(
|
||||||
|
label="Биография",
|
||||||
|
placeholder="Расскажите о себе...",
|
||||||
|
default=profile.get("bio", ""),
|
||||||
|
required=False,
|
||||||
|
style=discord.TextStyle.paragraph,
|
||||||
|
max_length=500
|
||||||
|
)
|
||||||
|
self.add_item(self.bio)
|
||||||
|
|
||||||
|
async def on_submit(self, interaction: discord.Interaction):
|
||||||
|
if interaction.user.id != self.user_id:
|
||||||
|
await interaction.response.send_message("❌ Это не ваш профиль!", ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"name": self.name.value,
|
||||||
|
"age": self.age.value,
|
||||||
|
"gender": self.gender.value,
|
||||||
|
"bio": self.bio.value,
|
||||||
|
"updated_at": datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
update_user_profile(self.user_id, self.guild_id, data)
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="✅ Профиль обновлён!",
|
||||||
|
description="Ваши данные успешно сохранены.",
|
||||||
|
color=discord.Color.green()
|
||||||
|
)
|
||||||
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||||
|
|
||||||
|
class ProfileView(ui.View):
|
||||||
|
def __init__(self, user_id, guild_id):
|
||||||
|
super().__init__(timeout=180)
|
||||||
|
self.user_id = user_id
|
||||||
|
self.guild_id = guild_id
|
||||||
|
|
||||||
|
@ui.button(label="✏️ Редактировать профиль", style=ButtonStyle.primary, row=0)
|
||||||
|
async def edit_profile(self, interaction: discord.Interaction, button: ui.Button):
|
||||||
|
if interaction.user.id != self.user_id:
|
||||||
|
await interaction.response.send_message("❌ Это не ваш профиль!", ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
modal = ProfileEditModal(self.user_id, self.guild_id)
|
||||||
|
await interaction.response.send_modal(modal)
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# 🎯 КРАСИВЫЙ ПРОФИЛЬ
|
||||||
|
# =====================================================
|
||||||
|
|
||||||
|
@bot.tree.command(name="profile", description="📊 Показать красивый профиль")
|
||||||
|
@app_commands.describe(member="Участник (опционально)")
|
||||||
|
async def slash_profile(interaction: discord.Interaction, member: discord.Member = None):
|
||||||
|
if not member:
|
||||||
|
member = interaction.user
|
||||||
|
|
||||||
|
data = load_level_data()
|
||||||
|
voice_data = load_voice_time()
|
||||||
|
user_id = str(member.id)
|
||||||
|
guild_id = str(interaction.guild.id)
|
||||||
|
|
||||||
|
profile = get_user_profile(member.id, interaction.guild.id)
|
||||||
|
name = profile.get("name", member.display_name)
|
||||||
|
age = profile.get("age", "Не указан")
|
||||||
|
gender = profile.get("gender", "Не указан")
|
||||||
|
bio = profile.get("bio", "Не указана")
|
||||||
|
|
||||||
|
if guild_id not in data or user_id not in data[guild_id]:
|
||||||
|
data[guild_id] = data.get(guild_id, {})
|
||||||
|
data[guild_id][user_id] = {"xp": 0, "level": 0, "messages": 0}
|
||||||
|
save_level_data(data)
|
||||||
|
|
||||||
|
user_data = data[guild_id][user_id]
|
||||||
|
level = user_data.get("level", 0)
|
||||||
|
xp = user_data.get("xp", 0)
|
||||||
|
next_xp = get_xp_for_level(level)
|
||||||
|
progress = get_progress(user_data)
|
||||||
|
rank = get_rank(member.id, interaction.guild.id)
|
||||||
|
voice_time = get_voice_time(member.id, interaction.guild.id)
|
||||||
|
messages_count = user_data.get("messages", 0)
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=f"📊 Профиль участника {member.display_name}",
|
||||||
|
color=discord.Color.blue()
|
||||||
|
)
|
||||||
|
|
||||||
|
embed.set_thumbnail(url=member.avatar.url if member.avatar else member.default_avatar.url)
|
||||||
|
|
||||||
|
embed.add_field(name="📇 Имя", value=f"**{name}**", inline=True)
|
||||||
|
embed.add_field(name="🎂 Возраст", value=f"**{age}**", inline=True)
|
||||||
|
embed.add_field(name="⚧ Пол", value=f"**{gender}**", inline=True)
|
||||||
|
embed.add_field(name="💬 Активность", value=f"за всё время было отправлено **{messages_count}** сообщений", inline=False)
|
||||||
|
embed.add_field(name="🎤 Голосовая активность", value=f"**{format_time(voice_time)}**", inline=False)
|
||||||
|
embed.add_field(name="📝 Биография", value=bio if bio and bio != "Не указана" else "Не указана", inline=False)
|
||||||
|
|
||||||
|
bar = create_progress_bar(progress)
|
||||||
|
embed.add_field(
|
||||||
|
name="📊 Прогресс до следующего уровня",
|
||||||
|
value=f"{bar} **{progress:.1f}%**",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
|
embed.add_field(name="🎯 Уровень", value=f"**{level}**", inline=True)
|
||||||
|
embed.add_field(name="⭐ XP", value=f"**{xp}** / {next_xp}", inline=True)
|
||||||
|
embed.add_field(name="🏆 Место", value=f"**#{rank if rank else '—'}**", inline=True)
|
||||||
|
|
||||||
|
embed.set_footer(
|
||||||
|
text=f"🆔 {member.id} • Запрошено: {interaction.user.display_name}",
|
||||||
|
icon_url=interaction.user.avatar.url if interaction.user.avatar else None
|
||||||
|
)
|
||||||
|
|
||||||
|
view = ProfileView(member.id, interaction.guild.id)
|
||||||
|
await interaction.response.send_message(embed=embed, view=view)
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# 🏆 ТОП И РЕЙТИНГ
|
||||||
|
# =====================================================
|
||||||
|
|
||||||
|
@bot.tree.command(name="level", description="🎯 Открыть меню")
|
||||||
|
async def slash_level(interaction: discord.Interaction):
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🎯 Система уровней",
|
||||||
|
description="💬 За сообщения + 🎤 За голосовой канал",
|
||||||
|
color=discord.Color.blue()
|
||||||
|
)
|
||||||
|
embed.add_field(
|
||||||
|
name="📋 Команды",
|
||||||
|
value="`/level` - Меню\n`/profile` - Красивый профиль\n`/top` - Топ\n`/rank` - Рейтинг\n`/settings` - Настройки (админ)",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
embed.set_footer(text="💡 Будьте активны!")
|
||||||
|
await interaction.response.send_message(embed=embed, view=LevelMainMenu())
|
||||||
|
|
||||||
|
class LevelMainMenu(ui.View):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(timeout=180)
|
||||||
|
|
||||||
|
@ui.button(label="📊 Профиль", style=ButtonStyle.primary, row=0)
|
||||||
|
async def profile(self, interaction: discord.Interaction, button: ui.Button):
|
||||||
|
await slash_profile.callback(interaction)
|
||||||
|
|
||||||
|
@ui.button(label="🏆 Топ", style=ButtonStyle.primary, row=0)
|
||||||
|
async def top(self, interaction: discord.Interaction, button: ui.Button):
|
||||||
|
await slash_top(interaction)
|
||||||
|
|
||||||
|
@ui.button(label="⚙️ Настройки", style=ButtonStyle.secondary, row=1)
|
||||||
|
async def settings(self, interaction: discord.Interaction, button: ui.Button):
|
||||||
|
if not interaction.user.guild_permissions.administrator:
|
||||||
|
await interaction.response.send_message("❌ Только администраторы!", ephemeral=True)
|
||||||
|
return
|
||||||
|
await slash_settings.callback(interaction)
|
||||||
|
|
||||||
|
@bot.tree.command(name="top", description="🏆 Топ пользователей")
|
||||||
|
async def slash_top(interaction: discord.Interaction):
|
||||||
|
data = load_level_data()
|
||||||
|
guild_id = str(interaction.guild.id)
|
||||||
|
|
||||||
|
if guild_id not in data:
|
||||||
|
await interaction.response.send_message("📭 Нет активных пользователей")
|
||||||
|
return
|
||||||
|
|
||||||
|
sorted_users = sorted(
|
||||||
|
data[guild_id].items(),
|
||||||
|
key=lambda x: (x[1].get("level", 0), x[1].get("xp", 0)),
|
||||||
|
reverse=True
|
||||||
|
)
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🏆 Топ пользователей",
|
||||||
|
color=discord.Color.gold()
|
||||||
|
)
|
||||||
|
|
||||||
|
for i, (uid, user_data) in enumerate(sorted_users[:10], 1):
|
||||||
|
member = interaction.guild.get_member(int(uid))
|
||||||
|
name = member.display_name if member else f"<@{uid}>"
|
||||||
|
level = user_data.get("level", 0)
|
||||||
|
xp = user_data.get("xp", 0)
|
||||||
|
voice_time = get_voice_time(int(uid), interaction.guild.id)
|
||||||
|
|
||||||
|
medal = ["🥇", "🥈", "🥉"][i-1] if i <= 3 else f"{i}."
|
||||||
|
embed.add_field(
|
||||||
|
name=f"{medal} {name}",
|
||||||
|
value=f"🎯 Уровень {level} | ⭐ {xp} XP\n🎤 {format_time(voice_time)}",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(sorted_users) > 10:
|
||||||
|
embed.set_footer(text=f"И ещё {len(sorted_users)-10} участников...")
|
||||||
|
|
||||||
|
await interaction.response.send_message(embed=embed)
|
||||||
|
|
||||||
|
@bot.tree.command(name="rank", description="🏆 Ваше место в рейтинге")
|
||||||
|
async def slash_rank(interaction: discord.Interaction):
|
||||||
|
data = load_level_data()
|
||||||
|
user_id = str(interaction.user.id)
|
||||||
|
guild_id = str(interaction.guild.id)
|
||||||
|
|
||||||
|
if guild_id not in data or user_id not in data[guild_id]:
|
||||||
|
await interaction.response.send_message("📭 У вас пока нет XP!", ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
rank = get_rank(interaction.user.id, interaction.guild.id)
|
||||||
|
user_data = data[guild_id][user_id]
|
||||||
|
total = len(data[guild_id])
|
||||||
|
voice_time = get_voice_time(interaction.user.id, interaction.guild.id)
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🏆 Ваш рейтинг",
|
||||||
|
color=discord.Color.blue()
|
||||||
|
)
|
||||||
|
embed.add_field(name="📊 Место", value=f"**#{rank}** из {total}", inline=True)
|
||||||
|
embed.add_field(name="🎯 Уровень", value=f"**{user_data.get('level', 0)}**", inline=True)
|
||||||
|
embed.add_field(name="⭐ XP", value=f"**{user_data.get('xp', 0)}**", inline=True)
|
||||||
|
embed.add_field(name="🎤 Время в голосовых", value=format_time(voice_time), inline=True)
|
||||||
|
|
||||||
|
await interaction.response.send_message(embed=embed)
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# ⚙️ НАСТРОЙКИ
|
||||||
|
# =====================================================
|
||||||
|
|
||||||
|
@bot.tree.command(name="settings", description="⚙️ Настройки (админ)")
|
||||||
|
@app_commands.default_permissions(administrator=True)
|
||||||
|
async def slash_settings(interaction: discord.Interaction):
|
||||||
|
await interaction.response.defer()
|
||||||
|
config = load_config()
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="⚙️ Настройки",
|
||||||
|
color=discord.Color.blue()
|
||||||
|
)
|
||||||
|
embed.add_field(
|
||||||
|
name="📊 Текущие настройки",
|
||||||
|
value=f"💬 XP за сообщение: **{config.get('message_xp', 1)}**\n"
|
||||||
|
f"🎵 XP за голос: **{config.get('voice_xp', 2)}**\n"
|
||||||
|
f"⏱️ Интервал: **{config.get('voice_interval', 60)}** сек",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
await interaction.followup.send(embed=embed, view=FullSettingsView())
|
||||||
|
|
||||||
|
class FullSettingsView(ui.View):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(timeout=180)
|
||||||
|
self.update_buttons()
|
||||||
|
|
||||||
|
def update_buttons(self):
|
||||||
|
for item in self.children[:]:
|
||||||
|
self.remove_item(item)
|
||||||
|
|
||||||
|
config = load_config()
|
||||||
|
|
||||||
|
self.add_item(ui.Button(
|
||||||
|
label=f"💬 XP за сообщ: {config.get('message_xp', 1)}",
|
||||||
|
style=ButtonStyle.primary,
|
||||||
|
row=0,
|
||||||
|
custom_id="msg_xp"
|
||||||
|
))
|
||||||
|
self.add_item(ui.Button(
|
||||||
|
label=f"🎵 XP за голос: {config.get('voice_xp', 2)}",
|
||||||
|
style=ButtonStyle.primary,
|
||||||
|
row=0,
|
||||||
|
custom_id="voice_xp"
|
||||||
|
))
|
||||||
|
self.add_item(ui.Button(
|
||||||
|
label=f"⏱️ Интервал: {config.get('voice_interval', 60)}с",
|
||||||
|
style=ButtonStyle.primary,
|
||||||
|
row=1,
|
||||||
|
custom_id="interval_settings"
|
||||||
|
))
|
||||||
|
|
||||||
|
status = "✅ Вкл" if config.get('level_up_message', True) else "❌ Выкл"
|
||||||
|
self.add_item(ui.Button(
|
||||||
|
label=f"📢 Оповещения: {status}",
|
||||||
|
style=ButtonStyle.success if config.get('level_up_message', True) else ButtonStyle.danger,
|
||||||
|
row=1,
|
||||||
|
custom_id="notify_settings"
|
||||||
|
))
|
||||||
|
self.add_item(ui.Button(
|
||||||
|
label="📌 Канал оповещений",
|
||||||
|
style=ButtonStyle.secondary,
|
||||||
|
row=2,
|
||||||
|
custom_id="channel_settings"
|
||||||
|
))
|
||||||
|
self.add_item(ui.Button(
|
||||||
|
label="📊 Статистика",
|
||||||
|
style=ButtonStyle.secondary,
|
||||||
|
row=2,
|
||||||
|
custom_id="stats_settings"
|
||||||
|
))
|
||||||
|
self.add_item(ui.Button(
|
||||||
|
label="🗑️ Сбросить всё",
|
||||||
|
style=ButtonStyle.danger,
|
||||||
|
row=3,
|
||||||
|
custom_id="reset_settings"
|
||||||
|
))
|
||||||
|
self.add_item(ui.Button(
|
||||||
|
label="❌ Закрыть",
|
||||||
|
style=ButtonStyle.danger,
|
||||||
|
row=3,
|
||||||
|
custom_id="close_settings"
|
||||||
|
))
|
||||||
|
|
||||||
|
async def interaction_check(self, interaction: discord.Interaction):
|
||||||
|
if not interaction.user.guild_permissions.administrator:
|
||||||
|
await interaction.response.send_message("❌ Только администраторы!", ephemeral=True)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def callback(self, interaction: discord.Interaction):
|
||||||
|
custom_id = interaction.data.get('custom_id')
|
||||||
|
|
||||||
|
if custom_id == "msg_xp":
|
||||||
|
await self.msg_xp_menu(interaction)
|
||||||
|
elif custom_id == "voice_xp":
|
||||||
|
await self.voice_xp_menu(interaction)
|
||||||
|
elif custom_id == "interval_settings":
|
||||||
|
await self.interval_menu(interaction)
|
||||||
|
elif custom_id == "notify_settings":
|
||||||
|
await self.toggle_notify(interaction)
|
||||||
|
elif custom_id == "channel_settings":
|
||||||
|
await self.set_channel(interaction)
|
||||||
|
elif custom_id == "stats_settings":
|
||||||
|
await self.show_stats(interaction)
|
||||||
|
elif custom_id == "reset_settings":
|
||||||
|
await self.reset_data(interaction)
|
||||||
|
elif custom_id == "close_settings":
|
||||||
|
await interaction.message.delete()
|
||||||
|
await interaction.response.send_message("✅ Меню закрыто", ephemeral=True)
|
||||||
|
|
||||||
|
async def msg_xp_menu(self, interaction: discord.Interaction):
|
||||||
|
await interaction.response.defer()
|
||||||
|
view = MsgXPView()
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="💬 XP за сообщения",
|
||||||
|
description="Выберите XP за одно сообщение",
|
||||||
|
color=discord.Color.blue()
|
||||||
|
)
|
||||||
|
await interaction.followup.send(embed=embed, view=view)
|
||||||
|
|
||||||
|
async def voice_xp_menu(self, interaction: discord.Interaction):
|
||||||
|
await interaction.response.defer()
|
||||||
|
view = VoiceXPView()
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🎵 XP за голосовой канал",
|
||||||
|
description="Выберите XP за минуту в голосовом",
|
||||||
|
color=discord.Color.blue()
|
||||||
|
)
|
||||||
|
await interaction.followup.send(embed=embed, view=view)
|
||||||
|
|
||||||
|
async def interval_menu(self, interaction: discord.Interaction):
|
||||||
|
await interaction.response.defer()
|
||||||
|
view = IntervalView()
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="⏱️ Интервал проверки",
|
||||||
|
description="Выберите интервал в секундах",
|
||||||
|
color=discord.Color.blue()
|
||||||
|
)
|
||||||
|
await interaction.followup.send(embed=embed, view=view)
|
||||||
|
|
||||||
|
async def toggle_notify(self, interaction: discord.Interaction):
|
||||||
|
config = load_config()
|
||||||
|
config['level_up_message'] = not config.get('level_up_message', True)
|
||||||
|
save_config(config)
|
||||||
|
self.update_buttons()
|
||||||
|
status = "Включены" if config['level_up_message'] else "Выключены"
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="📢 Оповещения",
|
||||||
|
description=f"Оповещения **{status}**",
|
||||||
|
color=discord.Color.green() if config['level_up_message'] else discord.Color.red()
|
||||||
|
)
|
||||||
|
await interaction.response.edit_message(embed=embed, view=self)
|
||||||
|
|
||||||
|
async def set_channel(self, interaction: discord.Interaction):
|
||||||
|
modal = ChannelModal()
|
||||||
|
await interaction.response.send_modal(modal)
|
||||||
|
|
||||||
|
async def show_stats(self, interaction: discord.Interaction):
|
||||||
|
await interaction.response.defer()
|
||||||
|
data = load_level_data()
|
||||||
|
guild_id = str(interaction.guild.id)
|
||||||
|
total_users = len(data.get(guild_id, {}))
|
||||||
|
total_xp = sum(u.get("xp", 0) for u in data.get(guild_id, {}).values())
|
||||||
|
max_level = max((u.get("level", 0) for u in data.get(guild_id, {}).values()), default=0)
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="📊 Статистика сервера",
|
||||||
|
color=discord.Color.blue()
|
||||||
|
)
|
||||||
|
embed.add_field(name="👥 Участников", value=str(total_users), inline=True)
|
||||||
|
embed.add_field(name="⭐ Всего XP", value=str(total_xp), inline=True)
|
||||||
|
embed.add_field(name="🎯 Макс. уровень", value=str(max_level), inline=True)
|
||||||
|
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||||
|
|
||||||
|
async def reset_data(self, interaction: discord.Interaction):
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="⚠️ Подтверждение",
|
||||||
|
description="Вы уверены, что хотите сбросить ВСЕ данные?",
|
||||||
|
color=discord.Color.red()
|
||||||
|
)
|
||||||
|
view = ConfirmResetView()
|
||||||
|
await interaction.response.edit_message(embed=embed, view=view)
|
||||||
|
|
||||||
|
class MsgXPView(ui.View):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(timeout=60)
|
||||||
|
for val in [1, 2, 3, 4, 5, 8, 10]:
|
||||||
|
self.add_item(ui.Button(
|
||||||
|
label=f"{val} XP",
|
||||||
|
style=ButtonStyle.primary if val == load_config().get('message_xp', 1) else ButtonStyle.secondary,
|
||||||
|
row=0 if val <= 5 else 1,
|
||||||
|
custom_id=f"msgxp_{val}"
|
||||||
|
))
|
||||||
|
self.add_item(ui.Button(label="🔙 Назад", style=ButtonStyle.danger, row=2, custom_id="back"))
|
||||||
|
|
||||||
|
async def interaction_check(self, interaction: discord.Interaction):
|
||||||
|
if not interaction.user.guild_permissions.administrator:
|
||||||
|
await interaction.response.send_message("❌ Только администраторы!", ephemeral=True)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def callback(self, interaction: discord.Interaction):
|
||||||
|
custom_id = interaction.data.get('custom_id')
|
||||||
|
if custom_id == "back":
|
||||||
|
await interaction.response.defer()
|
||||||
|
await interaction.followup.send(embed=discord.Embed(title="⚙️ Настройки", color=discord.Color.blue()), view=FullSettingsView())
|
||||||
|
return
|
||||||
|
val = int(custom_id.split("_")[1])
|
||||||
|
config = load_config()
|
||||||
|
config['message_xp'] = val
|
||||||
|
save_config(config)
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="✅ Обновлено!",
|
||||||
|
description=f"Теперь за сообщение даётся **{val}** XP",
|
||||||
|
color=discord.Color.green()
|
||||||
|
)
|
||||||
|
await interaction.response.edit_message(embed=embed, view=MsgXPView())
|
||||||
|
|
||||||
|
class VoiceXPView(ui.View):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(timeout=60)
|
||||||
|
for val in [1, 2, 3, 4, 5, 8, 10, 15, 20]:
|
||||||
|
self.add_item(ui.Button(
|
||||||
|
label=f"{val} XP",
|
||||||
|
style=ButtonStyle.primary if val == load_config().get('voice_xp', 2) else ButtonStyle.secondary,
|
||||||
|
row=0 if val <= 5 else 1,
|
||||||
|
custom_id=f"voicexp_{val}"
|
||||||
|
))
|
||||||
|
self.add_item(ui.Button(label="🔙 Назад", style=ButtonStyle.danger, row=2, custom_id="back"))
|
||||||
|
|
||||||
|
async def interaction_check(self, interaction: discord.Interaction):
|
||||||
|
if not interaction.user.guild_permissions.administrator:
|
||||||
|
await interaction.response.send_message("❌ Только администраторы!", ephemeral=True)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def callback(self, interaction: discord.Interaction):
|
||||||
|
custom_id = interaction.data.get('custom_id')
|
||||||
|
if custom_id == "back":
|
||||||
|
await interaction.response.defer()
|
||||||
|
await interaction.followup.send(embed=discord.Embed(title="⚙️ Настройки", color=discord.Color.blue()), view=FullSettingsView())
|
||||||
|
return
|
||||||
|
val = int(custom_id.split("_")[1])
|
||||||
|
config = load_config()
|
||||||
|
config['voice_xp'] = val
|
||||||
|
save_config(config)
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="✅ Обновлено!",
|
||||||
|
description=f"Теперь за минуту в голосовом даётся **{val}** XP",
|
||||||
|
color=discord.Color.green()
|
||||||
|
)
|
||||||
|
await interaction.response.edit_message(embed=embed, view=VoiceXPView())
|
||||||
|
|
||||||
|
class IntervalView(ui.View):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(timeout=60)
|
||||||
|
for val in [15, 30, 45, 60, 90, 120, 180, 300]:
|
||||||
|
self.add_item(ui.Button(
|
||||||
|
label=f"{val}с",
|
||||||
|
style=ButtonStyle.primary if val == load_config().get('voice_interval', 60) else ButtonStyle.secondary,
|
||||||
|
row=0 if val <= 60 else 1,
|
||||||
|
custom_id=f"int_{val}"
|
||||||
|
))
|
||||||
|
self.add_item(ui.Button(label="🔙 Назад", style=ButtonStyle.danger, row=2, custom_id="back"))
|
||||||
|
|
||||||
|
async def interaction_check(self, interaction: discord.Interaction):
|
||||||
|
if not interaction.user.guild_permissions.administrator:
|
||||||
|
await interaction.response.send_message("❌ Только администраторы!", ephemeral=True)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def callback(self, interaction: discord.Interaction):
|
||||||
|
custom_id = interaction.data.get('custom_id')
|
||||||
|
if custom_id == "back":
|
||||||
|
await interaction.response.defer()
|
||||||
|
await interaction.followup.send(embed=discord.Embed(title="⚙️ Настройки", color=discord.Color.blue()), view=FullSettingsView())
|
||||||
|
return
|
||||||
|
val = int(custom_id.split("_")[1])
|
||||||
|
config = load_config()
|
||||||
|
config['voice_interval'] = val
|
||||||
|
save_config(config)
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="✅ Обновлено!",
|
||||||
|
description=f"Теперь проверка каждые **{val}** секунд",
|
||||||
|
color=discord.Color.green()
|
||||||
|
)
|
||||||
|
await interaction.response.edit_message(embed=embed, view=IntervalView())
|
||||||
|
|
||||||
|
class ChannelModal(ui.Modal, title="📌 Канал оповещений"):
|
||||||
|
channel_id = ui.TextInput(
|
||||||
|
label="ID канала",
|
||||||
|
placeholder="Введите ID...",
|
||||||
|
required=False,
|
||||||
|
style=discord.TextStyle.short
|
||||||
|
)
|
||||||
|
|
||||||
|
async def on_submit(self, interaction: discord.Interaction):
|
||||||
|
config = load_config()
|
||||||
|
if self.channel_id.value:
|
||||||
|
try:
|
||||||
|
channel_id = int(self.channel_id.value)
|
||||||
|
channel = interaction.guild.get_channel(channel_id)
|
||||||
|
if not channel:
|
||||||
|
await interaction.response.send_message("❌ Канал не найден!", ephemeral=True)
|
||||||
|
return
|
||||||
|
config['level_up_channel'] = channel_id
|
||||||
|
save_config(config)
|
||||||
|
await interaction.response.send_message(f"✅ Канал установлен: {channel.mention}", ephemeral=True)
|
||||||
|
except:
|
||||||
|
await interaction.response.send_message("❌ Неверный ID!", ephemeral=True)
|
||||||
|
else:
|
||||||
|
config['level_up_channel'] = None
|
||||||
|
save_config(config)
|
||||||
|
await interaction.response.send_message("✅ Канал сброшен", ephemeral=True)
|
||||||
|
|
||||||
|
class ConfirmResetView(ui.View):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(timeout=60)
|
||||||
|
|
||||||
|
@ui.button(label="✅ Да, сбросить", style=ButtonStyle.danger, row=0)
|
||||||
|
async def confirm(self, interaction: discord.Interaction, button: ui.Button):
|
||||||
|
if not interaction.user.guild_permissions.administrator:
|
||||||
|
await interaction.response.send_message("❌ Только администраторы!", ephemeral=True)
|
||||||
|
return
|
||||||
|
data = load_level_data()
|
||||||
|
voice_data = load_voice_time()
|
||||||
|
guild_id = str(interaction.guild.id)
|
||||||
|
if guild_id in data:
|
||||||
|
del data[guild_id]
|
||||||
|
save_level_data(data)
|
||||||
|
if guild_id in voice_data:
|
||||||
|
del voice_data[guild_id]
|
||||||
|
save_voice_time(voice_data)
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🗑️ Данные сброшены!",
|
||||||
|
color=discord.Color.red()
|
||||||
|
)
|
||||||
|
await interaction.response.edit_message(embed=embed, view=None)
|
||||||
|
|
||||||
|
@ui.button(label="❌ Отмена", style=ButtonStyle.secondary, row=0)
|
||||||
|
async def cancel(self, interaction: discord.Interaction, button: ui.Button):
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="✅ Отменено",
|
||||||
|
color=discord.Color.green()
|
||||||
|
)
|
||||||
|
await interaction.response.edit_message(embed=embed, view=FullSettingsView())
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# 💬 XP ЗА СООБЩЕНИЯ
|
||||||
|
# =====================================================
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_message(message):
|
||||||
|
if message.author.bot or not message.guild:
|
||||||
|
await bot.process_commands(message)
|
||||||
|
return
|
||||||
|
|
||||||
|
config = load_config()
|
||||||
|
message_xp = config.get('message_xp', 1)
|
||||||
|
cooldown = config.get('message_cooldown', 60)
|
||||||
|
|
||||||
|
user_id = str(message.author.id)
|
||||||
|
guild_id = str(message.guild.id)
|
||||||
|
|
||||||
|
if user_id not in message_tracker:
|
||||||
|
message_tracker[user_id] = {}
|
||||||
|
|
||||||
|
last_time = message_tracker[user_id].get(guild_id)
|
||||||
|
if last_time and (datetime.now() - last_time).seconds < cooldown:
|
||||||
|
await bot.process_commands(message)
|
||||||
|
return
|
||||||
|
|
||||||
|
message_tracker[user_id][guild_id] = datetime.now()
|
||||||
|
|
||||||
|
data = load_level_data()
|
||||||
|
if guild_id not in data:
|
||||||
|
data[guild_id] = {}
|
||||||
|
if user_id not in data[guild_id]:
|
||||||
|
data[guild_id][user_id] = {"xp": 0, "level": 0, "messages": 0}
|
||||||
|
|
||||||
|
data[guild_id][user_id]["xp"] += message_xp
|
||||||
|
data[guild_id][user_id]["messages"] = data[guild_id][user_id].get("messages", 0) + 1
|
||||||
|
|
||||||
|
current_level = data[guild_id][user_id]["level"]
|
||||||
|
new_level = get_level_from_xp(data[guild_id][user_id]["xp"])
|
||||||
|
|
||||||
|
if new_level > current_level:
|
||||||
|
data[guild_id][user_id]["level"] = new_level
|
||||||
|
if config.get('level_up_message', True):
|
||||||
|
try:
|
||||||
|
channel_id = config.get('level_up_channel')
|
||||||
|
channel = message.guild.get_channel(channel_id) if channel_id else message.guild.system_channel
|
||||||
|
if channel:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🎉 Повышение уровня!",
|
||||||
|
description=f"{message.author.mention} достиг **{new_level}** уровня!",
|
||||||
|
color=discord.Color.gold()
|
||||||
|
)
|
||||||
|
await channel.send(embed=embed)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
save_level_data(data)
|
||||||
|
await bot.process_commands(message)
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# 🎵 XP ЗА ГОЛОСОВОЙ КАНАЛ
|
||||||
|
# =====================================================
|
||||||
|
|
||||||
|
async def give_voice_xp():
|
||||||
|
await bot.wait_until_ready()
|
||||||
|
|
||||||
|
while not bot.is_closed():
|
||||||
|
try:
|
||||||
|
config = load_config()
|
||||||
|
voice_xp = config.get('voice_xp', 2)
|
||||||
|
interval = config.get('voice_interval', 60)
|
||||||
|
|
||||||
|
data = load_level_data()
|
||||||
|
voice_data = load_voice_time()
|
||||||
|
|
||||||
|
for guild in bot.guilds:
|
||||||
|
guild_id = str(guild.id)
|
||||||
|
|
||||||
|
if guild_id not in data:
|
||||||
|
data[guild_id] = {}
|
||||||
|
|
||||||
|
if guild_id not in voice_data:
|
||||||
|
voice_data[guild_id] = {}
|
||||||
|
|
||||||
|
for member in guild.members:
|
||||||
|
if member.voice and member.voice.channel:
|
||||||
|
user_id = str(member.id)
|
||||||
|
|
||||||
|
if user_id not in voice_tracker:
|
||||||
|
voice_tracker[user_id] = {}
|
||||||
|
|
||||||
|
if guild_id not in voice_tracker[user_id]:
|
||||||
|
voice_tracker[user_id][guild_id] = datetime.now()
|
||||||
|
continue
|
||||||
|
|
||||||
|
last_time = voice_tracker[user_id][guild_id]
|
||||||
|
seconds_passed = (datetime.now() - last_time).seconds
|
||||||
|
|
||||||
|
if seconds_passed < interval:
|
||||||
|
continue
|
||||||
|
|
||||||
|
voice_tracker[user_id][guild_id] = datetime.now()
|
||||||
|
|
||||||
|
if user_id not in voice_data[guild_id]:
|
||||||
|
voice_data[guild_id][user_id] = 0
|
||||||
|
voice_data[guild_id][user_id] += interval
|
||||||
|
save_voice_time(voice_data)
|
||||||
|
|
||||||
|
if user_id not in data[guild_id]:
|
||||||
|
data[guild_id][user_id] = {"xp": 0, "level": 0}
|
||||||
|
|
||||||
|
data[guild_id][user_id]["xp"] += voice_xp
|
||||||
|
|
||||||
|
current_level = data[guild_id][user_id]["level"]
|
||||||
|
new_level = get_level_from_xp(data[guild_id][user_id]["xp"])
|
||||||
|
|
||||||
|
if new_level > current_level:
|
||||||
|
data[guild_id][user_id]["level"] = new_level
|
||||||
|
if config.get('level_up_message', True):
|
||||||
|
try:
|
||||||
|
channel_id = config.get('level_up_channel')
|
||||||
|
channel = guild.get_channel(channel_id) if channel_id else guild.system_channel
|
||||||
|
if channel:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🎉 Повышение уровня!",
|
||||||
|
description=f"{member.mention} достиг **{new_level}** уровня в голосовом канале!",
|
||||||
|
color=discord.Color.gold()
|
||||||
|
)
|
||||||
|
await channel.send(embed=embed)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
save_level_data(data)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Ошибка: {e}")
|
||||||
|
|
||||||
|
await asyncio.sleep(interval)
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_voice_state_update(member, before, after):
|
||||||
|
if before.channel and not after.channel:
|
||||||
|
user_id = str(member.id)
|
||||||
|
guild_id = str(member.guild.id)
|
||||||
|
if user_id in voice_tracker and guild_id in voice_tracker[user_id]:
|
||||||
|
del voice_tracker[user_id][guild_id]
|
||||||
|
if not voice_tracker[user_id]:
|
||||||
|
del voice_tracker[user_id]
|
||||||
|
|
||||||
|
@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="🎯 Уровни | /profile"
|
||||||
|
))
|
||||||
|
try:
|
||||||
|
await bot.tree.sync()
|
||||||
|
print("✅ Команды синхронизированы!")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Ошибка: {e}")
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# 🚀 ЗАПУСК
|
||||||
|
# =====================================================
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with bot:
|
||||||
|
print("🔄 Запуск Mohist_Level...")
|
||||||
|
bot.loop.create_task(give_voice_xp())
|
||||||
|
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}")
|
||||||
Loading…
Reference in New Issue
Block a user