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/handlers/mentions.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

123 lines
5.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Упоминания: /all, упоминание по имени, своё имя для упоминаний"""
import time
from datetime import datetime, timezone
from aiogram import Bot, Router
from aiogram.filters import Command
from aiogram.types import Message
import config
import db
from nlp import clean_name, find_names, h, join_names, mention, morph
from .common import count, in_group, is_admin, is_big
router = Router()
TEXT_LIMIT = 4096
_last_mention = {} # (чат, участник) → время последнего упоминания по имени
def chunks(parts, suffix, limit=TEXT_LIMIT):
"""Список упоминаний, разбитый на сообщения не длиннее лимита Telegram"""
messages, current = [], []
for part in parts:
candidate = join_names(current + [part]) + suffix
if current and len(candidate) > limit:
messages.append(join_names(current))
current = []
current.append(part)
messages.append(join_names(current) + suffix)
return messages
async def mention_names(bot: Bot, message: Message, found):
"""Текст упоминания найденных в сообщении имён; в большом чате одного человека — не чаще раза в минуту"""
chat_id, author = message.chat.id, message.from_user.id
names = await db.chat_names(chat_id)
targets = [(names[n], n) for n in found if n in names and names[n] != author]
if not targets:
return None
if await is_big(bot, chat_id):
now = time.monotonic()
targets = [(uid, n) for uid, n in targets
if now - _last_mention.get((chat_id, uid), 0) >= config.MENTION_COOLDOWN]
for uid, _ in targets:
_last_mention[(chat_id, uid)] = now
if not targets:
return None
links = [mention(uid, n) for uid, n in targets]
return f"{links[0]}, тебя упомянули)" if len(links) == 1 else f"{join_names(links)} вас упомянули)"
async def mention_in_text(bot: Bot, message: Message, text):
if not await db.get_setting(message.chat.id, "mentions_enabled"):
return None
found = find_names(text, set((await db.chat_names(message.chat.id)).keys()))
return await mention_names(bot, message, found) if found else None
@router.message(Command('all'))
async def every(message: Message, bot: Bot):
if not in_group(message):
await message.reply('Эта команда предназначена для вызова в чате!')
return
await count(message, 3, 1)
chat_id = message.chat.id
big = await is_big(bot, chat_id)
if big:
# в большом чате — только админы, не чаще раза в 5 минут и только недавно писавшие
if not await is_admin(bot, chat_id, message.from_user.id):
await message.reply(f'В чатах больше {config.BIG_CHAT} участников /all доступна только администраторам!')
return
last = await db.last_all(chat_id)
if last and (datetime.now(timezone.utc) - last).total_seconds() < config.ALL_COOLDOWN:
await message.reply(f'В больших чатах /all можно вызывать раз в {config.ALL_COOLDOWN // 60} минут!')
return
members = await db.active_members(chat_id, config.ALL_ACTIVE_DAYS)
else:
members = await db.active_members(chat_id)
others = [(uid, name) for uid, name in members if uid != message.from_user.id]
if len(others) < 2:
await message.reply('В группе состоит менее 3х человек, из-за чего команда не работает!')
return
author = await db.member_name(chat_id, message.from_user.id) or clean_name(message.from_user.first_name)
parts = chunks([mention(uid, name) for uid, name in others], f" вас вызывает {h(author.title())}")
await message.reply(parts[0])
for part in parts[1:]:
await message.answer(part)
await db.mark_all(chat_id)
@router.message(Command('edit'))
async def edit_name(message: Message):
if not in_group(message):
return
await count(message, 3, 1)
words = clean_name(message.text).split()
if len(words) != 2:
await message.reply('Вы не правильно ввели имя! Имя должно быть из одного слова и идти сразу после команды!')
return
name = morph.parse(words[1])[0].normal_form or words[1]
if name in await db.chat_names(message.chat.id):
await message.reply(f'{h(message.from_user.first_name.title())}, такое имя уже присутствует в чате!')
return
await db.set_custom_name(message.chat.id, message.from_user.id, name)
await message.reply(f'{h(name.title())}, ваше имя было успешно изменено)')
@router.message(Command('back_edit'))
async def back_edit(message: Message):
if not in_group(message):
return
await count(message, 3, 1)
name = clean_name(message.from_user.first_name)
if await db.reset_custom_name(message.chat.id, message.from_user.id, name):
await message.reply(f'{h(name.title())}, вы успешно вернулись к динамическому изменению имени)')
else:
await message.reply(f'{h(name.title())}, вы не устанавливали постоянное имя!')