"""Статистика чата и участников: за всё время, за месяц, топ и итоги месяца"""
import re
from datetime import date
from aiogram import Router
from aiogram.filters import Command, CommandObject
from aiogram.types import Message
import db
from nlp import clean_name, genitive, h
from .common import count, in_group
router = Router()
MONTHS = ["январь", "февраль", "март", "апрель", "май", "июнь", "июль", "август", "сентябрь", "октябрь",
"ноябрь", "декабрь"]
def stat_lines(total, month):
return (f'- За всё время / За месяц -\n'
f'>> Сообщений в базе [ {total[0]} / {month[0]} ]\n\n'
f'> Ответов - [ {total[1]} / {month[1]} ]\n'
f'> Команд - [ {total[2]} / {month[2]} ]\n'
f'> Ссылок - [ {total[3]} / {month[3]} ]\n'
f'> Стикеров - [ {total[5]} / {month[5]} ]\n'
f'> Медиа файлов - [ {total[4]} / {month[4]} ]\n'
f'> Голос/Кружочки - [ {total[6]} / {month[6]} ]')
def month_title(period):
year, month = period.split("-")
return f"{MONTHS[int(month) - 1]} {year}"
def parse_period(arg):
"""«2026-09», «прошлый» или ничего (текущий месяц)"""
today = date.today()
if not arg:
return db.month_key(today)
arg = arg.strip().lower()
if arg in ("прошлый", "prev", "last"):
first = today.replace(day=1)
return db.month_key(date(first.year - (first.month == 1), (first.month - 2) % 12 + 1, 1))
if re.fullmatch(r"\d{4}-\d{2}", arg) and 1 <= int(arg[5:]) <= 12:
return arg
return None
@router.message(Command('stat_group'))
async def stat_group(message: Message):
if not in_group(message):
return
await count(message, 3, 1)
total = await db.chat_stats(message.chat.id, "all")
month = await db.chat_stats(message.chat.id, db.month_key())
await message.answer(f'| СТАТИСТИКА ГРУППЫ |\n\n'
f'>> В целом сообщений [ {message.message_id} ]\n\n' + stat_lines(total, month))
@router.message(Command('stat_user'))
async def stat_user(message: Message):
if not in_group(message):
return
await count(message, 3, 1)
total = await db.member_stats(message.chat.id, message.from_user.id, "all")
month = await db.member_stats(message.chat.id, message.from_user.id, db.month_key())
name = genitive(clean_name(message.from_user.first_name).replace(" ", "")) or message.from_user.first_name
await message.answer(f'| СТАТИСТИКА {h(name.upper())} |\n\n' + stat_lines(total, month))
@router.message(Command('top'))
async def top(message: Message, command: CommandObject):
"""Самые активные участники по сообщениям: за месяц или за всё время (/top all)"""
if not in_group(message):
return
await count(message, 3, 1)
all_time = (command.args or "").strip().lower() in ("all", "всё", "все")
period = "all" if all_time else db.month_key()
rows = await db.top(message.chat.id, period, "mes", 10)
if not rows:
await message.answer("Статистики пока нет!")
return
title = "за всё время" if all_time else f"за {month_title(period)}"
# без ссылок-упоминаний: иначе каждый из топа получил бы уведомление
lines = [f"{i}. {h(name.title())} — {value}" for i, (_, name, value) in enumerate(rows, 1)]
await message.answer(f"| ТОП УЧАСТНИКОВ {title.upper()} |\n\n" + "\n".join(lines),
disable_notification=True)
CATEGORIES = [("mes", "Больше всех писали"), ("rep", "Больше всех отвечали"), ("sti", "Короли стикеров"),
("voi", "Больше всех голосовых и кружочков"), ("med", "Больше всех медиа")]
@router.message(Command('month'))
async def month_results(message: Message, command: CommandObject):
"""Итоги месяца — только по команде: топ-3 в каждой категории"""
if not in_group(message):
return
await count(message, 3, 1)
period = parse_period(command.args)
if period is None:
await message.reply("Укажите месяц в формате 2026-09 или прошлый")
return
blocks = []
for field, title in CATEGORIES:
rows = await db.top(message.chat.id, period, field, 3)
if rows:
blocks.append(f"{title}:\n" + "\n".join(
f"{i}. {h(name.title())} — {value}" for i, (_, name, value) in enumerate(rows, 1)))
if not blocks:
await message.answer(f"За {month_title(period)} статистики нет!")
return
totals = await db.chat_stats(message.chat.id, period)
await message.answer(f"| ИТОГИ: {month_title(period).upper()} |\n\n"
f"Сообщений в чате: {totals[0]}\n\n" + "\n\n".join(blocks))