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/voice.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

86 lines
3.2 KiB
Python

"""Голосовые: /recognize и поиск имён в голосовых (Google Web Speech, выключается в /settings)"""
import asyncio
import logging
import os
import tempfile
import soundfile as sf
import speech_recognition as sr
from aiogram import Bot, F, Router
from aiogram.enums import ContentType
from aiogram.filters import Command
from aiogram.types import Message
import db
from nlp import find_names, h
from .common import count, in_group
from .mentions import mention_names
router = Router()
def recognize_file(oga_path):
"""Голосовое (OGG/Opus) → текст. Синхронно — вызывается в отдельном потоке"""
data, samplerate = sf.read(oga_path)
wav_path = oga_path[:-4] + ".wav"
sf.write(wav_path, data, samplerate)
r = sr.Recognizer()
with sr.AudioFile(wav_path) as source:
r.pause_threshold = 100
audio = r.listen(source)
return r.recognize_google(audio, language='ru-RU')
async def recognize_voice(bot: Bot, file_id):
"""Распознавание без блокировки бота, файлы — во временной папке на запрос"""
with tempfile.TemporaryDirectory() as tmp:
oga = os.path.join(tmp, "voice.oga")
file = await bot.get_file(file_id)
await bot.download_file(file.file_path, oga)
try:
return await asyncio.to_thread(recognize_file, oga)
except sr.UnknownValueError: # речь не разобрана
return None
except Exception:
logging.exception("Ошибка распознавания голосового")
return None
@router.message(Command("recognize"))
async def recognise(message: Message, bot: Bot):
if in_group(message):
await count(message, 3, 1)
voice = message.reply_to_message.voice if message.reply_to_message else None
if not voice:
await message.reply("Ответьте этой командой на голосовое сообщение!")
return
mes = await message.reply_to_message.reply("Распознавание.....")
query = await recognize_voice(bot, voice.file_id)
if query:
await mes.edit_text(f'<b>{h(message.reply_to_message.from_user.first_name)} сказал(a)</b> "{h(query)}"')
else:
await mes.edit_text("Распознать сообщение не удалось!")
@router.message(F.content_type.in_([ContentType.VOICE, ContentType.VIDEO_NOTE]))
async def voice_message(message: Message, bot: Bot):
if not in_group(message):
return
await count(message, 7, 1)
# имена в голосовом: голос уходит в Google, поэтому в каждом чате можно выключить
if not (message.voice and message.voice.duration <= 60):
return
settings = await db.settings(message.chat.id)
if not (settings["voice_names"] and settings["mentions_enabled"]):
return
query = await recognize_voice(bot, message.voice.file_id)
if not query:
return
found = find_names(query, set((await db.chat_names(message.chat.id)).keys()))
text = await mention_names(bot, message, found) if found else None
if text:
await message.reply(text)