commit 3a4544808bd113ff97b51608a95bf969c2a00b62 Author: valer Date: Mon Dec 8 13:17:28 2025 +0700 хуй diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/Bot.iml b/.idea/Bot.iml new file mode 100644 index 0000000..c637a74 --- /dev/null +++ b/.idea/Bot.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0e7b492 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..23f0fb5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/bot.log b/bot.log new file mode 100644 index 0000000..69d14bc --- /dev/null +++ b/bot.log @@ -0,0 +1,2 @@ +2025-12-08 13:08:26,502:WARNING:discord.client: PyNaCl is not installed, voice will NOT be supported +2025-12-08 13:08:26,504:INFO:discord.client: logging in using static token diff --git a/bot.py b/bot.py new file mode 100644 index 0000000..7019e44 --- /dev/null +++ b/bot.py @@ -0,0 +1,405 @@ +import discord +from discord.ext import commands, tasks +from discord.utils import get +import datetime +import logging +import os +import json + +# --- Настройки и константы --- +BOT_TOKEN = 'MTM0Mjc5ODAyMjg5NTA3NTMzOQ.Gqipu1.-ZEvR6Oz-dxyyXJAnr6F7aqGCvHzLLCQ0kPdak' +WELCOME_CHANNEL_ID = 1342797233250107482 # ID канала для приветствий +ADMIN_ROLE_NAME = "Администратор" + +WARNINGS_FILE = "warnings.json" +REMINDERS_FILE = "reminders.json" +BLACKLIST_FILE = "blacklist.json" + +# --- Логирование --- +logging.basicConfig( + filename='bot.log', + level=logging.INFO, + format='%(asctime)s:%(levelname)s:%(name)s: %(message)s' +) + +# --- Интенты --- +intents = discord.Intents.default() +intents.message_content = True +intents.members = True + +# --- Глобальные переменные --- +reminders = [] +user_warnings = {} +blacklist = [] + +# --- Хелп команда --- +class MyHelpCommand(commands.HelpCommand): + async def send_bot_help(self, mapping): + channel = self.get_destination() + help_text = ( + "**Доступные команды:**\n" + "`!help` — показать это сообщение\n" + "`!rules` — показать правила сервера\n" + "`!reminder add <минуты> <текст>` — добавить напоминание (только для админов)\n" + "`!reminder list` — показать все активные напоминания\n" + "`!reminder remove <номер>` — удалить напоминание\n" + "`!kick @пользователь [причина]` — исключить участника\n" + "`!ban @пользователь [причина]` — забанить участника\n" + "`!unban имя#дискриминатор` — разбанить участника\n" + "`!mute @пользователь [причина]` — заглушить участника\n" + "`!unmute @пользователь` — снять заглушение\n" + "`!warn @пользователь [причина]` — выдать предупреждение\n" + "`!warnings @пользователь` — посмотреть предупреждения\n" + "`!clear <кол-во>` — удалить сообщения\n" + "`!blacklist_show` — показать чёрный список (админ)\n" + "`!blacklist_add <слово>` — добавить слово в чёрный список (админ)\n" + "`!blacklist_remove <слово>` — удалить слово из чёрного списка (админ)\n" + ) + await channel.send(help_text) + +# --- Инициализация бота --- +bot = commands.Bot(command_prefix='!', intents=intents, help_command=MyHelpCommand()) + +# --- Функции загрузки и сохранения данных --- +def load_data(): + global reminders, user_warnings + if os.path.isfile(WARNINGS_FILE): + with open(WARNINGS_FILE, 'r', encoding='utf-8') as f: + try: + user_warnings.update(json.load(f)) + except json.JSONDecodeError: + user_warnings.clear() + if os.path.isfile(REMINDERS_FILE): + with open(REMINDERS_FILE, 'r', encoding='utf-8') as f: + try: + reminders.extend(json.load(f)) + except json.JSONDecodeError: + reminders.clear() + +def save_warnings(): + with open(WARNINGS_FILE, 'w', encoding='utf-8') as f: + json.dump(user_warnings, f, ensure_ascii=False, indent=2) + +def save_reminders(): + with open(REMINDERS_FILE, 'w', encoding='utf-8') as f: + json.dump(reminders, f, ensure_ascii=False, indent=2) + +def load_blacklist_local(): + global blacklist + if os.path.isfile(BLACKLIST_FILE): + try: + with open(BLACKLIST_FILE, 'r', encoding='utf-8') as f: + blacklist = json.load(f) + except Exception: + blacklist = [] + else: + blacklist = [] + +def save_blacklist_local(): + with open(BLACKLIST_FILE, 'w', encoding='utf-8') as f: + json.dump(blacklist, f, ensure_ascii=False, indent=2) + +# --- Проверка и создание ролей --- +async def ensure_roles_exist(): + for guild in bot.guilds: + muted_role = get(guild.roles, name="Muted") + if not muted_role: + try: + muted_role = await guild.create_role(name="Muted") + for channel in guild.channels: + await channel.set_permissions(muted_role, send_messages=False, speak=False) + logging.info(f"Создана роль 'Muted' в {guild.name}") + except Exception as e: + logging.error(f"Ошибка при создании роли Muted в {guild.name}: {e}") + + new_member_role = get(guild.roles, name="New Member") + if not new_member_role: + try: + new_member_role = await guild.create_role(name="New Member") + logging.info(f"Создана роль 'New Member' в {guild.name}") + except Exception as e: + logging.error(f"Ошибка при создании роли New Member в {guild.name}: {e}") + +# --- События --- + +@bot.event +async def on_ready(): + print(f'Бот запущен как {bot.user}') + if not check_reminders.is_running(): + check_reminders.start() + load_data() + load_blacklist_local() + await ensure_roles_exist() + +@bot.event +async def on_member_join(member): + role = get(member.guild.roles, name="New Member") + if role: + await member.add_roles(role) + channel = bot.get_channel(WELCOME_CHANNEL_ID) + if channel: + await channel.send(f"Приветствуем {member.mention} на сервере!") + +@bot.event +async def on_message(message): + if message.author.bot: + return + # Проверка на запрещённые слова + msg_lower = message.content.lower() + if any(word in msg_lower for word in blacklist): + try: + await message.delete() + await message.channel.send(f"{message.author.mention}, ваше сообщение содержит запрещённые слова.") + logging.info(f"Удалено сообщение с запрещёнными словами от {message.author} в {message.channel}") + except Exception as e: + logging.error(f"Ошибка при удалении сообщения: {e}") + return + + await bot.process_commands(message) + +# --- Проверка прав администратора --- +def is_admin(): + def predicate(ctx): + return get(ctx.author.roles, name=ADMIN_ROLE_NAME) is not None or ctx.author.guild_permissions.administrator + return commands.check(predicate) + +# --- Команды модерации и управления --- + +@bot.command() +@is_admin() +async def blacklist_show(ctx): + if not blacklist: + await ctx.send("Чёрный список пуст.") + else: + await ctx.send("Чёрный список:\n" + ", ".join(blacklist)) + +@bot.command() +@is_admin() +async def blacklist_add(ctx, *, word: str): + word = word.lower() + if word in blacklist: + await ctx.send(f"Слово `{word}` уже в чёрном списке.") + else: + blacklist.append(word) + save_blacklist_local() + await ctx.send(f"Слово `{word}` добавлено в чёрный список.") + +@bot.command() +@is_admin() +async def blacklist_remove(ctx, *, word: str): + word = word.lower() + if word not in blacklist: + await ctx.send(f"Слово `{word}` отсутствует в чёрном списке.") + else: + blacklist.remove(word) + save_blacklist_local() + await ctx.send(f"Слово `{word}` удалено из чёрного списка.") + +@bot.command() +@is_admin() +async def rules(ctx): + rules_text = ( + "**Правила сервера:**\n" + "1. Уважайте других участников.\n" + "2. Запрещена реклама и спам.\n" + "3. Не используйте запрещённые слова.\n" + "4. Соблюдайте тематику каналов.\n" + "5. Выполняйте указания модераторов.\n" + ) + await ctx.send(rules_text) + +@bot.command() +@is_admin() +async def kick(ctx, member: discord.Member, *, reason=None): + try: + await member.kick(reason=reason) + await ctx.send(f"{member} был исключён. Причина: {reason}") + logging.info(f"{member} был исключён администратором {ctx.author}. Причина: {reason}") + except Exception as e: + await ctx.send(f"Не удалось исключить {member}.") + logging.error(f"Ошибка при исключении: {e}") + +@bot.command() +@is_admin() +async def ban(ctx, member: discord.Member, *, reason=None): + try: + await member.ban(reason=reason) + await ctx.send(f"{member} был забанен. Причина: {reason}") + logging.info(f"{member} был забанен администратором {ctx.author}. Причина: {reason}") + except Exception as e: + await ctx.send(f"Не удалось забанить {member}.") + logging.error(f"Ошибка при бане: {e}") + + +@bot.command() +@commands.has_permissions(ban_members=True) +async def unban(ctx, *, member_name): + banned_users = [] + async for ban_entry in ctx.guild.bans(): + banned_users.append(ban_entry) + + # Если указан полный тег с # + if '#' in member_name: + try: + name, discriminator = member_name.split('#') + except ValueError: + await ctx.send("Неверный формат пользователя. Используйте Имя#Тег.") + return + + for ban_entry in banned_users: + user = ban_entry.user + if (user.name, user.discriminator) == (name, discriminator): + try: + await ctx.guild.unban(user) + await ctx.send(f"Пользователь {user} разбанен.") + return + except Exception as e: + await ctx.send("Ошибка при разбане.") + logging.error(f"Ошибка при разбане: {e}") + return + + await ctx.send(f"Пользователь {member_name} не найден в бан-листе.") + return + + # Если указан только имя без тега — ищем все совпадения по имени + matching = [ban_entry.user for ban_entry in banned_users if ban_entry.user.name.lower() == member_name.lower()] + + if not matching: + await ctx.send(f"Пользователь с именем `{member_name}` не найден в бан-листе.") + return + + if len(matching) == 1: + user = matching[0] + try: + await ctx.guild.unban(user) + await ctx.send(f"Пользователь {user} разбанен.") + except Exception as e: + await ctx.send("Ошибка при разбане.") + logging.error(f"Ошибка при разбане: {e}") + return + + # Если совпадений несколько — выводим список для выбора + msg = "Найдено несколько пользователей с таким именем. Укажите полный тег для разбанивания:\n" + for user in matching: + msg += f"- {user.name}#{user.discriminator}\n" + await ctx.send(msg) + +@bot.command() +@is_admin() +async def mute(ctx, member: discord.Member, *, reason=None): + muted_role = get(ctx.guild.roles, name="Muted") + if not muted_role: + await ctx.send("Роль Muted не найдена.") + return + try: + await member.add_roles(muted_role) + await ctx.send(f"{member} заглушен. Причина: {reason}") + except Exception as e: + await ctx.send("Не удалось выдать мут.") + logging.error(f"Ошибка при муте: {e}") + +@bot.command() +@is_admin() +async def unmute(ctx, member: discord.Member): + muted_role = get(ctx.guild.roles, name="Muted") + if not muted_role: + await ctx.send("Роль Muted не найдена.") + return + try: + await member.remove_roles(muted_role) + await ctx.send(f"С мутом снято с {member}.") + except Exception as e: + await ctx.send("Не удалось снять мут.") + logging.error(f"Ошибка при снятии мута: {e}") + +@bot.command() +@is_admin() +async def warn(ctx, member: discord.Member, *, reason=None): + user_id = str(member.id) + if user_id not in user_warnings: + user_warnings[user_id] = [] + user_warnings[user_id].append({"reason": reason or "Без причины", "date": str(datetime.datetime.now())}) + save_warnings() + await ctx.send(f"{member} получил предупреждение. Причина: {reason or 'Без причины'}") + +@bot.command() +async def warnings(ctx, member: discord.Member): + user_id = str(member.id) + warns = user_warnings.get(user_id, []) + if not warns: + await ctx.send(f"У пользователя {member} нет предупреждений.") + return + msg = f"Предупреждения пользователя {member}:\n" + for i, w in enumerate(warns, 1): + msg += f"{i}. {w['reason']} ({w['date']})\n" + await ctx.send(msg) + +@bot.command() +@is_admin() +async def clear(ctx, amount: int): + if amount <= 0: + await ctx.send("Количество должно быть положительным числом.") + return + deleted = await ctx.channel.purge(limit=amount + 1) + await ctx.send(f"Удалено сообщений: {len(deleted)-1}", delete_after=5) + +# --- Команды напоминаний --- +@bot.group() +@is_admin() +async def reminder(ctx): + if ctx.invoked_subcommand is None: + await ctx.send("Используйте `!reminder add <минуты> <текст>`, `!reminder list` или `!reminder remove <номер>`") + +@reminder.command(name="add") +async def reminder_add(ctx, minutes: int, *, text: str): + if minutes <= 0: + await ctx.send("Время должно быть положительным числом минут.") + return + remind_time = datetime.datetime.now() + datetime.timedelta(minutes=minutes) + reminders.append({ + "time": remind_time.timestamp(), + "channel_id": ctx.channel.id, + "user_mention": ctx.author.mention, + "text": text + }) + save_reminders() + await ctx.send(f"Напоминание добавлено через {minutes} минут: {text}") + +@reminder.command(name="list") +async def reminder_list(ctx): + if not reminders: + await ctx.send("Активных напоминаний нет.") + return + msg = "Активные напоминания:\n" + for i, rem in enumerate(reminders, 1): + t = datetime.datetime.fromtimestamp(rem["time"]).strftime("%Y-%m-%d %H:%M:%S") + msg += f"{i}. Через {t} — {rem['text']} (от {rem['user_mention']})\n" + await ctx.send(msg) + +@reminder.command(name="remove") +async def reminder_remove(ctx, number: int): + if number <= 0 or number > len(reminders): + await ctx.send("Неверный номер напоминания.") + return + removed = reminders.pop(number - 1) + save_reminders() + await ctx.send(f"Удалено напоминание: {removed['text']}") + +# --- Фоновая задача проверки напоминаний --- +@tasks.loop(seconds=30) +async def check_reminders(): + now = datetime.datetime.now().timestamp() + to_remove = [] + for rem in reminders: + if rem['time'] <= now: + channel = bot.get_channel(rem['channel_id']) + if channel: + await channel.send(f"{rem['user_mention']} Напоминание: {rem['text']}") + to_remove.append(rem) + for rem in to_remove: + reminders.remove(rem) + if to_remove: + save_reminders() + +# --- Запуск бота --- +bot.run(BOT_TOKEN)