__version__ = (2, 0, 1)
# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
# █▀█ █ █ █ █▀█ █▀▄ █
# © Copyright 2022
# https://t.me/hikariatama
#
# 🔒 Licensed under the GNU AGPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
# meta pic: https://static.dan.tatar/vtt_icon.png
# meta banner: https://mods.hikariatama.ru/badges/vtt.jpg
# meta developer: @hikarimods
# scope: ffmpeg
# scope: hikka_only
# scope: hikka_min 1.3.3
# requires: pydub speechrecognition python-ffmpeg
import asyncio
import logging
import os
import tempfile
import speech_recognition as sr
from pydub import AudioSegment
from telethon.tl.types import DocumentAttributeVideo, Message
from .. import loader, utils
logger = logging.getLogger(__name__)
@loader.tds
class VoicyMod(loader.Module):
"""Recognize voice messages, audios, videos and round messages"""
strings = {
"name": "Voicy",
"converting": (
"🫠 Recognizing voice"
" message..."
),
"converted": (
"🫠"
" Recognized:\n{}"
),
"voice_not_found": (
"🫠 Voice not found"
),
"autovoice_off": (
"🫠 I will not recognize"
" voice messages in this chat"
),
"autovoice_on": (
"🫠 I will recognize"
" voice messages in this chat"
),
"_cfg_lang": "Language of voices to recognize",
"_cfg_engine": "Recognition engine",
"error": "🚫 Recognition error!",
"_cfg_ignore_users": "Users to ignore",
"_cfg_silent": "Silent mode - do not notify about errors",
"too_big": "🫥 Voice message is too big, I can't recognise it...",
}
strings_ru = {
"converting": (
"🫠 Распознаю голосовое"
" сообщение..."
),
"converted": (
"🫠"
" Распознано:\n{}"
),
"voice_not_found": (
"🫠 Нет ответа на"
" войс"
),
"autovoice_off": (
"🫠 Я больше не буду"
" распознавать голосовые сообщения в этом чате"
),
"autovoice_on": (
"🫠 Я буду распознавать"
" голосовые сообщения в этом чате"
),
"_cmd_doc_voicy": "Распознает голосовое сообщение",
"_cmd_doc_autovoice": (
"Включить\\выключить автораспознавание голосовых сообщений в чате"
),
"_cls_doc": "Распознает голосовые сообщения, аудио, видео и кругляши",
"_cfg_lang": "Язык для распознавания голосовых сообщений",
"_cfg_engine": "Распознаватель",
"_cfg_ignore_users": "Игнорировать пользователей",
"_cfg_silent": "Тихий режим - не оповещать об ошибках",
"error": "🚫 Ошибка распознавания!",
"too_big": (
"🫥 Голосовое сообщение слишком большое, я не могу его распознать..."
),
}
strings_de = {
"converting": (
"🫠 Sprachnachricht wird"
" erkannt..."
),
"converted": (
"🫠"
" Erkannt:\n{}"
),
"voice_not_found": (
"🫠 Keine Antwort auf"
" Voice"
),
"autovoice_off": (
"🫠 Ich werde in diesem"
" Chat keine Sprachnachrichten mehr erkennen"
),
"autovoice_on": (
"🫠 Ich werde in diesem"
" Chat Sprachnachrichten erkennen"
),
"_cmd_doc_voicy": "Erkennt eine Sprachnachricht",
"_cmd_doc_autovoice": (
"Aktiviert\\Deaktiviert die automatische Erkennung von Sprachnachrichten im"
" Chat"
),
"_cls_doc": "Erkennt Sprachnachrichten, Audios, Videos und Rundnachrichten",
"_cfg_lang": "Sprache für die Spracherkennung",
"_cfg_engine": "Erkennungsprogramm",
"_cfg_ignore_users": "Benutzer ignorieren",
"_cfg_silent": "Stiller Modus - Fehler nicht melden",
"error": "🚫 Erkennungsfehler!",
"too_big": (
"🫥 Sprachnachricht ist zu groß, ich kann sie nicht erkennen..."
),
}
strings_tr = {
"converting": (
"🫠 Sesli mesajı"
" tanıyorum..."
),
"converted": (
"🫠"
" Tanımlandı:\n{}"
),
"voice_not_found": (
"🫠 Sesli mesaja cevap"
" yok"
),
"autovoice_off": (
"🫠 Bu sohbetteki sesli"
" mesajları artık tanımayacağım"
),
"autovoice_on": (
"🫠 Bu sohbetteki sesli"
" mesajları tanıyacağım"
),
"_cmd_doc_voicy": "Sesli mesajı tanır",
"_cmd_doc_autovoice": (
"Sohbetteki sesli mesajların otomatik tanınmasını etkinleştirir\\devre dışı"
" bırakır"
),
"_cls_doc": "Sesli mesajları, sesleri, videoları ve çevirileri tanır",
"_cfg_lang": "Ses tanıma için dil",
"_cfg_engine": "Tanıyıcı",
"_cfg_ignore_users": "Kullanıcıları yoksay",
"_cfg_silent": "Sessiz mod - hataları bildirmeyin",
"error": "🚫 Tanıma hatası!",
"too_big": "🫥 Sesli mesaj çok büyük, tanıyamıyorum...",
}
strings_uz = {
"converting": (
"🫠 So'zli xabar"
" aniqlanmoqda..."
),
"converted": (
"🫠"
" Aniqlandi:\n{}"
),
"voice_not_found": (
"🫠 So'zli xabarga"
" javob yo'q"
),
"autovoice_off": (
"🫠 Bu suhbatda so'zli"
" xabarlar aniqlanmaydi"
),
"autovoice_on": (
"🫠 Bu suhbatda so'zli"
" xabarlar aniqlanadi"
),
"_cmd_doc_voicy": "So'zli xabarni aniqlash",
"_cmd_doc_autovoice": (
"Suhbatdagi so'zli xabarlar avtomatik aniqlashini yoqish\\o'chirish"
),
"_cls_doc": "So'zli xabarlar, audio, videolar va qarishmalarni aniqlaydi",
"_cfg_lang": "Tilni aniqlash uchun",
"_cfg_engine": "Aniqlash moliyaviyasi",
"_cfg_ignore_users": "Foydalanuvchilarni e'tiborsiz qoldirish",
"_cfg_silent": "Sessiz rejim - xatolarni bildirmang",
"error": "🚫 Aniqlash xatosi!",
"too_big": "🫥 So'zli xabar juda katta, aniqlay olmayman...",
}
strings_hi = {
"converting": (
"🫠 वायस संदेश"
" पहचान रहा है..."
),
"converted": (
"🫠"
" पहचान लिया:\n{}"
),
"voice_not_found": (
"🫠 वायस संदेश"
" के लिए जवाब नहीं"
),
"autovoice_off": (
"🫠 इस चैट में वायस"
" संदेश पहचान नहीं करेंगे"
),
"autovoice_on": (
"🫠 इस चैट में वायस"
" संदेश पहचान करेंगे"
),
"_cmd_doc_voicy": "वायस संदेश पहचान करें",
"_cmd_doc_autovoice": "इस चैट में वायस संदेशों को ऑटोमैटिक पहचानने को सक्षम\\अक्षम करें",
"_cls_doc": "वायस संदेश, ऑडियो, वीडियो और रैडियो संदेश पहचानता है",
"_cfg_lang": "पहचान के लिए भाषा",
"_cfg_engine": "पहचानकर्ता",
"_cfg_ignore_users": "उपयोगकर्ताओं को नजरअंदाज करें",
"_cfg_silent": "शांत मोड - त्रुटियों को सूचित न करें",
"error": "🚫 पहचान त्रुटि!",
"too_big": "🫥 वायस संदेश बहुत बड़ा है, पहचान नहीं कर सकता...",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"language",
"ru-RU",
lambda: self.strings("_cfg_lang"),
validator=loader.validators.RegExp(r"^[a-z]{2}-[A-Z]{2}$"),
),
loader.ConfigValue(
"ignore_users",
[],
lambda: self.strings("_cfg_ignore_users"),
validator=loader.validators.Series(
validator=loader.validators.TelegramID()
),
),
loader.ConfigValue(
"silent",
False,
lambda: self.strings("_cfg_silent"),
validator=loader.validators.Boolean(),
),
)
async def client_ready(self):
self.v2a = await self.import_lib(
"https://libs.hikariatama.ru/v2a.py",
suspend_on_error=True,
)
self.chats = self.pointer("chats", [])
async def recognize(self, message: Message):
try:
m = await utils.answer(message, self.strings("converting"))
with tempfile.TemporaryDirectory() as tmpdir:
file = os.path.join(
tmpdir,
"audio.mp3" if message.audio else "audio.ogg",
)
data = await message.download_media(bytes)
if message.video:
data = await self.v2a.convert(data, "audio.ogg")
with open(file, "wb") as f:
f.write(data)
song = AudioSegment.from_file(
file, format="mp3" if message.audio else "ogg"
)
song.export(os.path.join(tmpdir, "audio.wav"), format="wav")
r = sr.Recognizer()
with sr.AudioFile(os.path.join(tmpdir, "audio.wav")) as source:
audio_data = r.record(source)
text = await utils.run_sync(
r.recognize_google,
audio_data,
language=self.config["language"],
)
m = await utils.answer(
m,
self.strings("converted").format(text),
)
except Exception:
logger.exception("Can't recognize")
if not self.config["silent"]:
m = await utils.answer(m, self.strings("error"))
await asyncio.sleep(3)
if not message.out:
await m.delete()
@loader.unrestricted
async def voicycmd(self, message: Message):
"""Recognize voice message"""
reply = await message.get_reply_message()
try:
is_voice = (
reply.video or reply.audio or reply.media.document.attributes[0].voice
)
except (AttributeError, IndexError):
is_voice = False
if not reply or not reply.media or not is_voice:
await utils.answer(message, self.strings("voice_not_found"))
return
if message.out:
await message.delete()
await self.recognize(reply)
if message.out:
await message.delete()
async def watcher(self, message: Message):
try:
if (
utils.get_chat_id(message) not in self.get("chats", [])
or not message.media
or not message.video
and not message.audio
and not message.media.document.attributes[0].voice
or message.gif
or message.sticker
):
return
except Exception:
return
if message.sender_id in self.config["ignore_users"]:
return
if (
(
message.video
and (
next(
attr
for attr in message.video.attributes
if isinstance(attr, DocumentAttributeVideo)
).duration
> 120
)
)
or getattr(
(
getattr(
getattr(getattr(message, "media", None), "document", None),
"attributes",
False,
)
or [None]
)[0],
"duration",
0,
)
> 300
or message.document.size / 1024 / 1024 > 5
):
if not self.config["silent"]:
await utils.answer(message, self.strings("too_big"))
return
await self.recognize(message)
async def autovoicecmd(self, message: Message):
"""Toggle automatic recognition in current chat"""
chat_id = utils.get_chat_id(message)
if chat_id in self.get("chats", []):
self.chats.remove(chat_id)
await utils.answer(message, self.strings("autovoice_off"))
else:
self.chats.append(chat_id)
await utils.answer(message, self.strings("autovoice_on"))