хуй
This commit is contained in:
5
.idea/.gitignore
generated
vendored
Normal file
5
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
10
.idea/Bot.iml
generated
Normal file
10
.idea/Bot.iml
generated
Normal 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>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal 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
7
.idea/misc.xml
generated
Normal 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
8
.idea/modules.xml
generated
Normal 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
6
.idea/vcs.xml
generated
Normal 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
2
bot.log
Normal 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
405
bot.py
Normal 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)
|
||||||
Reference in New Issue
Block a user