This commit is contained in:
2025-12-08 13:17:28 +07:00
commit 3a4544808b
8 changed files with 449 additions and 0 deletions

5
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

10
.idea/Bot.iml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.13 (Bot)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.13 (Bot)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (Bot)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Bot.iml" filepath="$PROJECT_DIR$/.idea/Bot.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

2
bot.log Normal file
View File

@@ -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

405
bot.py Normal file
View File

@@ -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)