1
0
Fork 0
mirror of https://github.com/EDeev/chatping_abobot.git synced 2026-10-07 20:49:45 +03:00
chatping_abobot/code/bot.py
Egor Deev a2a0adcf8e AboBot 4.0: PostgreSQL, обработчики по роутерам, HTML-разметка, новые команды
База — PostgreSQL (asyncpg) вместо четырёх файлов SQLite с таблицей на каждый чат:
chats, users, members, chat_stats и member_stats с полем «период» — месяцы хранятся, а не
обнуляются; счётчики растут одним запросом без блокировки бота. scripts/migrate_sqlite.py
переносит старые базы со сверкой сумм (проверено на копии рабочих баз: 20 694 пользователя,
45 чатов, 3 142 038 сообщений — совпадает).

Учёт бота в чатах: статус и права бота (событие my_chat_member и сверка с Telegram при
запуске и раз в 6 часов), история изменений в bot_status_history; из чатов, где бота
исключили, данные не удаляются — меняется только статус.

Большие чаты (больше 100 участников): /all — только для админов, раз в 5 минут и только
писавшие за 30 дней; упоминание одного человека по имени — не чаще раза в минуту. В любом
чате /all делится на сообщения до 4096 символов (раньше в чате на 14 тысяч падал).

Новое: /settings (упоминания, имена в голосовых, ивенты, удаление служебных сообщений —
меняют админы), /top, /month (итоги месяца по команде). /stop_bot раньше ничего не
выключал — флаг не проверялся; теперь работает. Участник, вышедший из чата, больше не
упоминается, но его статистика сохраняется. Ошибки — в технический чат DEBUG_CHAT_ID.

Код: handlers.py разбит на роутеры, Markdown заменён на HTML (экранирование через html.escape),
тесты на PostgreSQL (18), CI на Python 3.10 и 3.12.
2026-10-06 11:16:00 +00:00

66 lines
2.1 KiB
Python

import asyncio
import logging
import traceback
from aiogram import Bot, Dispatcher
from aiogram.client.bot import DefaultBotProperties
from aiogram.enums.parse_mode import ParseMode
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.types import ErrorEvent
import config
import db
from handlers import routers
from handlers.chat import check_all
CHECK_INTERVAL = 6 * 3600 # сверка статуса бота в чатах
def create_dispatcher():
dp = Dispatcher(storage=MemoryStorage())
for router in routers:
dp.include_router(router)
@dp.errors()
async def on_error(event: ErrorEvent, bot: Bot):
"""Ошибка обработчика — в лог и в технический чат"""
logging.exception("Ошибка обработки", exc_info=event.exception)
if config.DEBUG_CHAT_ID:
tb = "".join(traceback.format_exception(event.exception))[-3500:]
try:
await bot.send_message(config.DEBUG_CHAT_ID, f"<b>AboBot: ошибка</b>\n<pre>{tb.replace('<', '&lt;')}</pre>")
except Exception:
pass
return True
return dp
async def periodic_check(bot: Bot):
while True:
try:
await check_all(bot)
except Exception:
logging.exception("Сверка статуса бота в чатах")
await asyncio.sleep(CHECK_INTERVAL)
async def main() -> None:
bot = Bot(token=config.BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
await db.connect(config.DATABASE_URL)
dp = create_dispatcher()
await bot.delete_webhook(drop_pending_updates=True)
checker = asyncio.create_task(periodic_check(bot))
try:
await dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types())
finally:
checker.cancel()
await db.close()
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
try:
asyncio.run(main())
except KeyboardInterrupt:
pass