diff --git a/.gitignore b/.gitignore index d80e779..a0c7666 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,8 @@ utils/__pycache__/ storage/message.txt /addons/dowloadmp4_to_youtube/gettoken.py /drevo.py -/addons/example_addon/cookies.txt +/addons/download_mp3_to_youtube/cookies.txt /storage/message.db +/addons/todo/todo.db +/addons/rule34/urls.db +/addons/x_days_to/days_to_new_year.db diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9aae5ca --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +Все значимые изменения проекта. + +## [Unreleased] +- Добавлена проверка MD5-хеша скриншота в `services/watcher_service.py` — бот теперь отправляет/закрепляет фото только при первом обнаружении и при изменениях. +- Исправлен импорт в `addons/x_days_to/__init__.py` (модуль `x_days_to.py` теперь импортируется корректно). +- (Замечание) Найдена потенциальная ошибка: `addons/send_message/handlers.py` использует `aiohttp` без явного импорта — нужно поправить. + +## 0.1.0 - WIP +- Начальная версия проекта. Планируется приватный релиз после исправлений и добавления CI. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..d77ea6f --- /dev/null +++ b/README.md @@ -0,0 +1,103 @@ +# MyFirstProgramm + +Лёгкий Telegram-бот на Python (aiogram + Playwright) для автоматической проверки и рассылки расписаний, а также набора аддонов. + +## Краткое описание +Проект периодически проверяет расписание на сайте (через Playwright), делает скриншоты нужных фрагментов, вычисляет хеш (MD5) и отправляет/закрепляет изображение в целевых чатах Telegram только при изменении (чтобы не флудить чат). Также в `addons/` есть дополнительные модули (мини-приложения). + +## Структура (важные файлы/папки) +- `main.py` — точка входа приложения (запуск бота). +- `config.py` — конфигурация (переменные окружения). +- `services/` — сервисы: `schedule_service.py`, `watcher_service.py` (логика слежки/хэшей). +- `models/state.py` — in-memory состояние бота (кэши, last_clip_hash и т.д.). +- `addons/` — набор дополнительных модулей (каждый аддон экспортирует `register` / `unregister`). +- `requirements.txt` — зависимости. + +## Быстрый старт (локально) +1. Установите зависимости (рекомендуется виртуальное окружение): + +```bash +python -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +``` + +2. Создайте `.env` (или экспортируйте переменные окружения): +- `TELEGRAM_BOT_TOKEN` — токен бота +- (опционально) `DEEPGRAM_API_KEY`, `ACCESS_TOKEN` — если используются аддоны + +Создайте файл `.env` рядом с `config.py`: + +```env +TELEGRAM_BOT_TOKEN=your_bot_token_here +#DEEPGRAM_API_KEY=... +#ACCESS_TOKEN=... +``` + +3. Запустите бота (пример): + +```bash +python main.py +``` + +(В вашем окружении запуск может быть оформлен скриптом — например `./Desktop/my.sh`.) + +## Конфигурация +Основные настройки находятся в `config.py`. Обратите внимание, что `Config` сейчас поднимает исключение, если `TELEGRAM_BOT_TOKEN` не задан. + +## Что изменилось (коротко) +- Добавлена проверка MD5-хеша скриншота в `services/watcher_service.py` — теперь бот отправляет фото и закрепляет сообщение только при первом обнаружении и при изменениях. +- Исправлен импорт в `addons/x_days_to/__init__.py` (модуль назывался `x_days_to.py`, вместо отсутствующего `handlers`). + +(Более полный список изменений — в `CHANGELOG.md`.) + +## Чек-лист готовности к релизу (private GitHub) +Ниже — рекомендации и текущий статус (на основе быстрого статического анализа): + +- [ ] Тесты (unit/integration) — отсутствуют. Рекомендую добавить хотя бы базовые тесты для критичных сервисов (например мок ScheduleService и проверка логики хеширования). +- [x] README (есть) — ✅ +- [x] CHANGELOG (есть) — ✅ +- [ ] Линт/статический анализ — есть предупреждения/ошибки (см. ниже). Нужно исправить перед релизом. +- [ ] Безопасность/секреты — убедитесь, что `TELEGRAM_BOT_TOKEN` и другие ключи не коммитятся; добавьте `.env.example` и `.gitignore`. +- [ ] CI — рекомендую GitHub Actions: lint (ruff), тесты (pytest), security scan. +- [ ] Версионирование — добавьте `__version__` в `pyproject`/`setup.py` или тег релиза в Git. + +### Проблемы, найденные статически +- `addons/send_message/handlers.py`: используется `aiohttp` без импорта (нужно `import aiohttp`). Это вызовет ошибку при импорте этого аддона. +- Возможны другие runtime-ошибки — рекомендую прогнать `ruff check . --fix` и запустить бота в dev окружении. + +## Рекомендации перед релизом +1. Исправить импорт/синтаксисные ошибки (см. `ruff` / `get_errors`). +2. Добавить `.env.example` и `.gitignore` (включить `.venv/`, `storage/` с чувствительными файлами и логами). +3. Добавить базовые тесты и настроить GitHub Actions (lint + tests). +4. Решить поведение state-персистенции: `BotState` хранит состояние в памяти — после рестарта всё теряется. Если важно сохранять историю/фиксированный pinned_id — добавить simple JSON/SQLite storage. +5. Проверить, что все аддоны регистрируют хендлеры корректно (в `x_days_to` ранее не было регистрации а-ля `@dp.message(...)` — возможно требуется доработить). + +## Как выпустить на приватный GitHub +1. Создайте репозиторий и инициализируйте git: + +```bash +git init +git add . +git commit -m "Initial commit" +# создайте приватный репо в GitHub и затем +git remote add origin git@github.com:yourorg/yourrepo.git +git branch -M main +git push -u origin main +``` + +2. Настройте GitHub Actions (workflow) для lint & tests. +3. Добавьте релизный тег и changelog entry: + +```bash +git tag -a v0.1.0 -m "Initial private release" +git push origin v0.1.0 +``` + +--- + +Если хотите, я могу сразу: +- добавить `.env.example` и `.gitignore`; +- исправить `addons/send_message/handlers.py` (добавить импорт `aiohttp`) и/или добавить базовый тест для `watcher_service`. + +Скажите, что выполнить следующим шагом — добавлять `.env.example`/`.gitignore`, исправлять найденную ошибку или настроить CI (создать пример workflow). \ No newline at end of file diff --git a/addons/example_addon/__init__.py b/addons/download_mp3_to_youtube/__init__.py similarity index 100% rename from addons/example_addon/__init__.py rename to addons/download_mp3_to_youtube/__init__.py diff --git a/addons/example_addon/dowloadmp3_to_youtube.py b/addons/download_mp3_to_youtube/dowloadmp3_to_youtube.py similarity index 97% rename from addons/example_addon/dowloadmp3_to_youtube.py rename to addons/download_mp3_to_youtube/dowloadmp3_to_youtube.py index 78e3c3f..0c920f5 100644 --- a/addons/example_addon/dowloadmp3_to_youtube.py +++ b/addons/download_mp3_to_youtube/dowloadmp3_to_youtube.py @@ -21,7 +21,7 @@ async def get_video_info(url: str) -> dict: "--dump-json", "--no-playlist", "--cookies", - "~/myfirstprogramm/addons/example_addon/cookies.txt", + "/addons/download_mp3_to_youtube/cookies.txt", url, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, @@ -116,7 +116,7 @@ async def download_mp3_isolated(url: str) -> tuple[str, dict]: "--audio-format", "mp3", "--cookies", - "~/myfirstprogramm/addons/example_addon/cookies.txt", + "/addons/download_mp3_to_youtube/cookies.txt", "--audio-quality", "320K", "--no-playlist", diff --git a/addons/example_addon/handlers.py b/addons/download_mp3_to_youtube/handlers.py similarity index 100% rename from addons/example_addon/handlers.py rename to addons/download_mp3_to_youtube/handlers.py diff --git a/addons/miniapps/__init__.py b/addons/miniapps/__init__.py new file mode 100644 index 0000000..8a90404 --- /dev/null +++ b/addons/miniapps/__init__.py @@ -0,0 +1,9 @@ +def register(dp, state, bot): + from . import handlers + + handlers.register_handlers(dp, state, bot) + + +def unregister(dp): + # Здесь можно удалить хендлеры, если нужно + dp.message_handlers.handlers.clear() \ No newline at end of file diff --git a/addons/miniapps/handlers.py b/addons/miniapps/handlers.py new file mode 100644 index 0000000..e6f6a5e --- /dev/null +++ b/addons/miniapps/handlers.py @@ -0,0 +1,29 @@ +from aiogram import Dispatcher, Bot +from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, WebAppInfo +from models.state import BotState +from aiogram.filters import Command +from logging import getLogger +from datetime import datetime + +logger = getLogger(__name__) + + +def get_day() -> int: + day = datetime.now().day + if day == 6: + return day + 1 + return day + + +def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): + @dp.message(Command("app")) + async def send_welcome(message: Message): + # Создаём инлайн-кнопку для открытия Web App + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Открыть мини-приложение", web_app=WebAppInfo(url=f"https://college.by/accounts/raspis/{datetime.now().month}/{get_day()}-PODNAM.htm"))] + ]) + + await message.answer( + f"Расписание на {get_day()} число месяца:", + reply_markup=keyboard + ) \ No newline at end of file diff --git a/addons/poll/handlers.py b/addons/poll/handlers.py index b9e3ee6..82984b3 100644 --- a/addons/poll/handlers.py +++ b/addons/poll/handlers.py @@ -20,7 +20,7 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): poll_msg = await bot.send_poll( chat_id=chat_id, question="Кто опоздает?", - options=["Я", "Я очень сильно опоздаю", "Наверное"], + options=["Я", "Я очень сильно опоздаю", "Я пиздец как опоздаю", "Наверное"], is_anonymous=False, allows_multiple_answers=False, ) @@ -56,6 +56,11 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): ) save_message(msg.chat.id, msg.message_id) elif option_ids and option_ids[0] == 2: + msg = await bot.send_message( + chat_id=6394047531, text=f"{username} Пиздец опоздаеты" + ) + save_message(msg.chat.id, msg.message_id) + elif option_ids[0] == 3: msg = await bot.send_message( chat_id=6394047531, text=f"{username} возможно опоздает" ) diff --git a/addons/rule34/__init__.py b/addons/rule34/__init__.py new file mode 100644 index 0000000..e0acb2f --- /dev/null +++ b/addons/rule34/__init__.py @@ -0,0 +1,9 @@ +def register(dp, state, bot): + from . import handlers + + handlers.register_handlers(dp, state, bot) + + +def unregister(dp): + # Здесь можно удалить хендлеры, если нужно + dp.message_handlers.handlers.clear() diff --git a/addons/rule34/get_post.py b/addons/rule34/get_post.py new file mode 100644 index 0000000..8ac5e86 --- /dev/null +++ b/addons/rule34/get_post.py @@ -0,0 +1,73 @@ +import requests +from random import randint, choice +from bs4 import BeautifulSoup + +BASE_URL = "https://rule34.xxx" +HEADERS = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" +} +URL = f"{BASE_URL}/index.php?page=post&s=list" +MAXIMUM = 999 + +# Хранилище тегов в памяти +TAGS = set() + +def add_tags(tags: list[str]): + TAGS.update(tags) + +def del_tags(tags: list[str]): + for t in tags: + TAGS.discard(t) + +def get_tags_str() -> str: + return "+".join(TAGS) if TAGS else "(нет тегов)" + +def get_tags() -> str: + return "+".join(TAGS) if TAGS else "" + +def get_url(): + while True: + try: + tags = get_tags() + pid = randint(1, MAXIMUM) + + # Формируем корректный URL с query‑параметрами + if tags: + url_page = f"{URL}&tags={tags}&pid={pid}" + else: + url_page = f"{URL}&pid={pid}" + + r = requests.get(url_page, headers=HEADERS, timeout=5) + soup = BeautifulSoup(r.text, 'lxml') + block = soup.find(class_="image-list") + if not block: + continue + block = block.find_all("span") + if not block: + continue + + link = choice(block).find("a") + if not link: + continue + + r2 = requests.get(f"{BASE_URL}{link.get('href')}", headers=HEADERS, timeout=10) + soup_two = BeautifulSoup(r2.text, 'lxml') + + flexi = soup_two.find(class_="flexi") + if not flexi: + continue + + img = flexi.find("img") + if not img: + continue + + url = img.get("src") + if not url: + continue + + return url + + except Exception as e: + print(f"[get_url ERROR] {e}") + continue + diff --git a/addons/rule34/handlers.py b/addons/rule34/handlers.py new file mode 100644 index 0000000..38d441e --- /dev/null +++ b/addons/rule34/handlers.py @@ -0,0 +1,85 @@ +from aiogram import Dispatcher, Bot +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, InputMediaPhoto, Message, CallbackQuery +from aiogram.filters import Command +from aiogram.exceptions import TelegramBadRequest, TelegramRetryAfter +from aiogram import F +from logging import getLogger +from storage.message_storage import save_message +from .get_post import get_url, add_tags, del_tags, get_tags_str +import asyncio + + +logger = getLogger(__name__) + + +def get_keyboard(): + keyboard = InlineKeyboardMarkup( + inline_keyboard=[ + [InlineKeyboardButton(text="Следующее фото", callback_data="next")] + ] + ) + return keyboard + + +def register_handlers(dp: Dispatcher, state, bot: Bot): + + @dp.message(Command("rule34")) + async def rule34(message: Message): + msg = await message.answer_photo( + photo=get_url(), + caption="Вот фото 📷", + reply_markup=get_keyboard() + ) + + # сохраняем id сообщения, если нужно + save_message(msg.chat.id, msg.message_id) + + @dp.callback_query(F.data == "next") + async def next_photo(callback: CallbackQuery): + for attempt in range(3): # максимум 3 попытки + try: + media = InputMediaPhoto( + media=get_url(), + caption=f"Новое фото 🌄 (попытка {attempt + 1})" + ) + await callback.message.edit_media( + media=media, + reply_markup=get_keyboard() + ) + break # успех — выходим из цикла + + except TelegramRetryAfter as e: + # Telegram сказал подождать e.retry_after секунд + logger.warning(f"Flood control: ждем {e.retry_after} сек") + await asyncio.sleep(e.retry_after) + continue # пробуем снова + + except TelegramBadRequest as e: + logger.warning(f"Ошибка при загрузке фото (попытка {attempt + 1}): {e}") + continue + + else: + # если все попытки неудачные + logger.error("Не удалось загрузить фото после 3 попыток") + + # закрываем "часики" в любом случае + await callback.answer() + + @dp.message(Command("addteg")) + async def cmd_addteg(message: Message): + # Разбиваем текст после команды на теги + parts = message.text.split()[1:] + if not parts: + await message.answer("❌ Укажи теги через пробел: /addteg tag1 tag2") + return + add_tags(parts) + await message.answer(f"✅ Добавлены теги: {', '.join(parts)}\nТекущие: {get_tags_str()}") + + @dp.message(Command("delteg")) + async def cmd_delteg(message: Message): + parts = message.text.split()[1:] + if not parts: + await message.answer("❌ Укажи теги для удаления: /delteg tag1 tag2") + return + del_tags(parts) + await message.answer(f"🗑 Удалены теги: {', '.join(parts)}\nТекущие: {get_tags_str()}") \ No newline at end of file diff --git a/addons/todo/DB.py b/addons/todo/DB.py new file mode 100644 index 0000000..6fa9bd1 --- /dev/null +++ b/addons/todo/DB.py @@ -0,0 +1,57 @@ +import sqlite3 +from datetime import datetime + +DIR = "/Users/mac/myfirstprogramm/addons/todo/todo.db" # лучше указать полный путь + +# создаём подключение +db = sqlite3.connect(DIR) +cursor = db.cursor() + +# создаём таблицу, если её ещё нет +cursor.execute(""" +CREATE TABLE IF NOT EXISTS tasks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + who INTEGER NOT NULL, + task TEXT NOT NULL, + due_date TEXT NOT NULL +) +""") +db.commit() + +# функция для добавления задачи +def add_task(who: int, task: str, due_date: str): + cursor.execute( + "INSERT INTO tasks (who, task, due_date) VALUES (?, ?, ?)", + (who, task, due_date) + ) + db.commit() + +# функция для получения всех задач пользователя +def get_tasks(who: int): + cursor.execute("SELECT id, task, due_date FROM tasks WHERE who = ?", (who,)) + return cursor.fetchall() + +# функция для удаления всех задач пользователя +def delete_tasks_by_who(who: int): + cursor.execute("DELETE FROM tasks WHERE who = ?", (who,)) + db.commit() + +# пример использования +if __name__ == "__main__": + # добавляем задачи + add_task(123456789, "Сделать бота", datetime.now().strftime("%d.%m.%Y %H:%M:%S")) + add_task(123456789, "Проверить базу", datetime.now().strftime("%d.%m.%Y %H:%M:%S")) + + print("Задачи до удаления:") + tasks = get_tasks(123456789) + for t in tasks: + print(t) + + # удаляем все задачи пользователя + delete_tasks_by_who(123456789) + + print("\nЗадачи после удаления:") + tasks = get_tasks(123456789) + for t in tasks: + print(t) + diff --git a/addons/todo/__init__.py b/addons/todo/__init__.py new file mode 100644 index 0000000..e0acb2f --- /dev/null +++ b/addons/todo/__init__.py @@ -0,0 +1,9 @@ +def register(dp, state, bot): + from . import handlers + + handlers.register_handlers(dp, state, bot) + + +def unregister(dp): + # Здесь можно удалить хендлеры, если нужно + dp.message_handlers.handlers.clear() diff --git a/addons/todo/handlers.py b/addons/todo/handlers.py new file mode 100644 index 0000000..afbf29f --- /dev/null +++ b/addons/todo/handlers.py @@ -0,0 +1,68 @@ +from aiogram import Dispatcher, Bot +from aiogram.types import Message +from aiogram.filters import Command +from aiogram.fsm.state import State, StatesGroup +from aiogram.fsm.context import FSMContext +import logging +from .DB import get_tasks, add_task, delete_tasks_by_who +from storage.message_storage import save_message +from datetime import datetime + +logger = logging.getLogger(__name__) + +class TodoForm(StatesGroup): + waiting_for_task = State() + +def register_handlers(dp: Dispatcher, state, bot: Bot): + # /todos — начать добавление задачи + @dp.message(Command("todos")) + async def cmd_todos(message: Message, state: FSMContext): + save_message(message.chat.id, message.message_id) # сохраняем сообщение + await message.answer("Введите текст задачи:") + await state.set_state(TodoForm.waiting_for_task) + + # обработка текста задачи + @dp.message(TodoForm.waiting_for_task) + async def process_task(message: Message, state: FSMContext): + save_message(message.chat.id, message.message_id) # сохраняем сообщение + task_text = message.text + user_id = message.from_user.id + try: + created_at = datetime.now().strftime("%d.%m.%Y %H:%M:%S") + add_task(user_id, task_text, created_at) + reply = await message.answer(f"Задача сохранена ✅\n{task_text}") + save_message(reply.chat.id, reply.message_id) # сохраняем ответ бота + except Exception as e: + logger.error(f"Ошибка при добавлении задачи: {e}") + reply = await message.answer("Не удалось сохранить задачу ❌") + save_message(reply.chat.id, reply.message_id) + finally: + await state.clear() + + # /todor — показать список задач + @dp.message(Command("todor")) + async def cmd_todor(message: Message): + save_message(message.chat.id, message.message_id) # сохраняем сообщение + user_id = message.from_user.id + try: + tasks = get_tasks(user_id) + if not tasks: + reply = await message.answer("У вас пока нет задач 📝") + save_message(reply.chat.id, reply.message_id) + return + + text = "Ваши задачи:\n\n" + for tid, task, created_at in tasks: + text += f"• {task} (создана {created_at})\n" + reply = await message.answer(text) + save_message(reply.chat.id, reply.message_id) + except Exception as e: + logger.error(f"Ошибка при чтении задач: {e}") + reply = await message.answer("Не удалось получить список задач ❌") + save_message(reply.chat.id, reply.message_id) + + @dp.message(Command("todod")) + async def del_todo(message: Message): + delete_tasks_by_who(message.from_user.id) + + diff --git a/addons/x_days_to/__init__.py b/addons/x_days_to/__init__.py new file mode 100644 index 0000000..e0acb2f --- /dev/null +++ b/addons/x_days_to/__init__.py @@ -0,0 +1,9 @@ +def register(dp, state, bot): + from . import handlers + + handlers.register_handlers(dp, state, bot) + + +def unregister(dp): + # Здесь можно удалить хендлеры, если нужно + dp.message_handlers.handlers.clear() diff --git a/addons/x_days_to/handlers.py b/addons/x_days_to/handlers.py new file mode 100644 index 0000000..39eb011 --- /dev/null +++ b/addons/x_days_to/handlers.py @@ -0,0 +1,99 @@ +import asyncio +import aiosqlite +from datetime import datetime +from logging import getLogger +from aiogram import Dispatcher, Bot +from config import Config +from models.state import BotState +from utils.antispam import save_message + +logger = getLogger(__name__) + + +def register_handlers(dp: Dispatcher, state: BotState, bot: Bot) -> int: + async def init_db(): + async with aiosqlite.connect(Config.DAYS_TO_DB_PATH) as db: + await db.execute(""" + CREATE TABLE IF NOT EXISTS days_to_new_year ( + user_id INTEGER PRIMARY KEY, + days INTEGER NOT NULL, + timestamp TEXT NOT NULL + ) + """) + await db.commit() + logger.info("База данных инициализирована") + + async def save_days_to_db(user_id: int, days: int): + logger.debug(f"Сохраняем user_id={user_id}, days={days}") + async with aiosqlite.connect(Config.DAYS_TO_DB_PATH) as db: + await db.execute(""" + INSERT OR REPLACE INTO days_to_new_year (user_id, days, timestamp) + VALUES (?, ?, ?) + """, (int(user_id), int(days), datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) + await db.commit() + logger.info(f"Запись сохранена: user_id={user_id}, days={days}") + + async def get_last_days(user_id: int) -> int | None: + async with aiosqlite.connect(Config.DAYS_TO_DB_PATH) as db: + async with db.execute( + "SELECT days FROM days_to_new_year WHERE user_id = ?", (int(user_id),) + ) as cursor: + row = await cursor.fetchone() + return row[0] if row else None + + async def days_to_new_years() -> int: + now = datetime.now() + new_year = datetime(now.year + 1, 1, 1) + delta = (new_year - now).days + logger.debug(f"До Нового года осталось {delta} дней") + return delta + + async def days_to_summer() -> int: + """Считает дни до 1 июня текущего года (или следующего, если уже лето прошло).""" + now = datetime.now() + summer = datetime(now.year, 6, 1) + if now >= summer: + summer = datetime(now.year + 1, 6, 1) + delta = (summer - now).days + logger.debug(f"До лета осталось {delta} дней") + return delta + + async def send_days_to_new_years(user_id: int): + days_ny = await days_to_new_years() + days_summer = await days_to_summer() + last_days = await get_last_days(user_id) + + if last_days == days_ny: + logger.info(f"user_id={user_id}: запись уже есть ({days_ny} дней), пропускаем") + return + + await save_days_to_db(user_id, days_ny) + + message_text = ( + f"🌞 До лета осталось {days_summer} дней!\n" + f"🎄 До Нового Года осталось {days_ny} дней!" + ) + + for chat_id in Config.CHAT_IDS: + try: + logger.info(f"Отправляем сообщение в чат {chat_id} для user_id={user_id}") + await bot.send_message(chat_id, message_text) + except Exception as e: + logger.error(f"Ошибка при отправке в чат {chat_id}: {e}") + + async def periodic_task(): + await init_db() + while True: + logger.info("Запуск цикла periodic_task") + for uid in Config.CHAT_IDS: + try: + msg = await send_days_to_new_years(int(uid)) + if msg: + save_message(msg.chat.id, msg.message_id) + except Exception as e: + logger.exception(f"Ошибка при обработке uid={uid}: {e}") + logger.info("Завершён цикл periodic_task, спим 6 часов") + await asyncio.sleep(21600) # каждые 6 часов + + asyncio.create_task(periodic_task()) + return 0 diff --git a/bot/core.py b/bot/core.py index 77d1414..b687aea 100644 --- a/bot/core.py +++ b/bot/core.py @@ -22,13 +22,17 @@ class TelegramBot: # common.register_handlers(self.dp, self.state, self.bot) # add addons - self.addons.load("example_addon") + self.addons.load("download_mp3_to_youtube") self.addons.load("id") self.addons.load("send_message") self.addons.load("poll") self.addons.load("hello") - self.addons.load("draw") - self.addons.load("gpt") + # self.addons.load("draw") + # self.addons.load("gpt") + # self.addons.load("rule34") + # self.addons.load("todo") + self.addons.load("miniapps") + self.addons.load("x_days_to") async def start(self): """Запуск бота""" diff --git a/config.py b/config.py index 1275b1a..98ced34 100644 --- a/config.py +++ b/config.py @@ -37,6 +37,7 @@ class Config: # Пути LOG_FILE = "storage/log/bot.log" + DAYS_TO_DB_PATH = "addons/x_days_to/days_to_new_year.db" if __name__ == "__main__":