- utils/morning_runner.py: Scheduler + run_morning() - bot.py: автоматический запуск планировщика при старте - commands/morning.py: использовать run_morning() вместо дублирования - .env.example: добавить MORNING_TIME=07:00 - AGENTS.md: обновить документацию - tests/test_morning_runner.py: 10 тестов для Scheduler
139 lines
4.5 KiB
Python
139 lines
4.5 KiB
Python
import asyncio
|
||
import inspect
|
||
import logging
|
||
import os
|
||
import sys
|
||
import threading
|
||
|
||
import discord
|
||
from discord.ext import commands
|
||
from discord.ext.commands import CommandNotFound
|
||
from dotenv import load_dotenv
|
||
|
||
from commands import ALL_COMMANDS
|
||
from console_commands import ALL_CONSOLE_COMMANDS
|
||
from utils.morning_runner import Scheduler
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
load_dotenv()
|
||
|
||
intents = discord.Intents.default()
|
||
intents.message_content = True
|
||
|
||
bot = commands.Bot(command_prefix="!", intents=intents)
|
||
stop_event = threading.Event()
|
||
bot_ready = threading.Event()
|
||
scheduler: Scheduler | None = None
|
||
|
||
|
||
@bot.event
|
||
async def on_ready():
|
||
global scheduler
|
||
print(f"Бот вошёл как {bot.user}")
|
||
for cog_class in ALL_COMMANDS:
|
||
cog = cog_class()
|
||
await bot.add_cog(cog)
|
||
for cog in bot.cogs:
|
||
print(f" Загружен: {cog}")
|
||
|
||
# Запуск планировщика
|
||
morning_time = os.getenv("MORNING_TIME", "07:00")
|
||
scheduler = Scheduler(bot, morning_time)
|
||
print(f" Планировщик запущен (время: {morning_time})")
|
||
|
||
bot_ready.set()
|
||
|
||
|
||
@bot.event
|
||
async def on_command_error(ctx, error):
|
||
if isinstance(error, CommandNotFound):
|
||
return
|
||
|
||
# Терминал — детали для разработчика
|
||
logger.error(
|
||
f"Ошибка команды {ctx.command.name if ctx else '?'}: {error}",
|
||
exc_info=True,
|
||
)
|
||
|
||
# Discord — только если команда не ответила сама
|
||
if ctx and ctx.interaction and ctx.interaction.response.is_done():
|
||
return
|
||
|
||
try:
|
||
await ctx.send("Не удалось выполнить команду. Попробуйте позже.")
|
||
except (discord.NotFound, discord.Forbidden):
|
||
pass # Бот не может писать в канал — игнорируем
|
||
|
||
|
||
@bot.command(name="msg")
|
||
async def msg(ctx, *, text: str):
|
||
"""Повторяет текст после !msg"""
|
||
await ctx.send(text)
|
||
|
||
|
||
def _print_commands():
|
||
"""Вывести список доступных консольных команд."""
|
||
available = {k: v for k, v in ALL_CONSOLE_COMMANDS.items() if k != "stop"}
|
||
print("\nДоступные команды:")
|
||
for idx, (name, func) in enumerate(available.items(), 1):
|
||
print(f" {idx}. {name}")
|
||
print(" 0. stop")
|
||
|
||
|
||
def console_input():
|
||
bot_ready.wait()
|
||
_print_commands()
|
||
|
||
while not stop_event.is_set():
|
||
try:
|
||
choice = input("\nВыберите команду (номер): ").strip()
|
||
if choice == "0":
|
||
print("\nОстановка бота...")
|
||
stop_event.set()
|
||
asyncio.run_coroutine_threadsafe(bot.close(), bot.loop).result(timeout=5)
|
||
break
|
||
try:
|
||
available = {k: v for k, v in ALL_CONSOLE_COMMANDS.items() if k != "stop"}
|
||
idx = int(choice)
|
||
if 0 < idx <= len(available):
|
||
cmd_name = list(available.keys())[idx - 1]
|
||
cmd_func = ALL_CONSOLE_COMMANDS[cmd_name]
|
||
if inspect.iscoroutinefunction(cmd_func):
|
||
asyncio.run_coroutine_threadsafe(cmd_func(stop_event, bot), bot.loop).result()
|
||
else:
|
||
cmd_func(stop_event, bot)
|
||
else:
|
||
print(f"Неизвестная команда: {choice}")
|
||
except (ValueError, IndexError):
|
||
print(f"Неверный формат: {choice}")
|
||
_print_commands()
|
||
except (EOFError, KeyboardInterrupt):
|
||
stop_event.set()
|
||
try:
|
||
asyncio.run_coroutine_threadsafe(bot.close(), bot.loop).result(timeout=5)
|
||
except Exception as e:
|
||
print(f"Ошибка при остановке бота: {e}")
|
||
break
|
||
|
||
|
||
if __name__ == "__main__":
|
||
print("Введите 'stop' для остановки бота")
|
||
|
||
thread = threading.Thread(target=console_input, daemon=True)
|
||
thread.start()
|
||
|
||
try:
|
||
token = os.getenv("DISCORD_TOKEN")
|
||
if not token:
|
||
print("Ошибка: токен не найден в .env")
|
||
sys.exit(1)
|
||
bot.run(token)
|
||
except KeyboardInterrupt:
|
||
print("\nОстановка бота...")
|
||
stop_event.set()
|
||
if scheduler:
|
||
scheduler.stop()
|
||
asyncio.run_coroutine_threadsafe(bot.close(), bot.loop).result()
|
||
sys.exit(0)
|