mirror of
https://github.com/EDeev/chatping_abobot.git
synced 2026-10-07 20:49:45 +03:00
База — 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.
66 lines
2.1 KiB
Python
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('<', '<')}</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
|