# ©️ Dan Gazizullin, 2021-2023
# This file is a part of Hikka Userbot
# 🌐 https://github.com/hikariatama/Hikka
# You can redistribute it and/or modify it under the terms of the GNU AGPLv3
# 🔑 https://www.gnu.org/licenses/agpl-3.0.html
import asyncio
import base64
import logging
import random
import re
import typing
import requests
import rsa
from hikkatl.tl.types import Message
from hikkatl.utils import resolve_inline_message_id
from .. import loader, translations, utils
from ..types import InlineCall
logger = logging.getLogger(__name__)
pubkey = rsa.PublicKey(
7110455561671499155469672749235101198284219627796886527432331759773809536504953770286294224729310191037878347906574131955439231159825047868272932664151403,
65537,
)
REGEXES = [
re.compile(
r"https:\/\/github\.com\/([^\/]+?)\/([^\/]+?)\/raw\/(?:main|master)\/([^\/]+\.py)"
),
re.compile(
r"https:\/\/raw\.githubusercontent\.com\/([^\/]+?)\/([^\/]+?)\/(?:main|master)\/([^\/]+\.py)"
),
]
@loader.tds
class UnitHeta(loader.Module):
"""Gives @hikkamods_bot a right to download modules from official modules aggregator and autoupdate them"""
e = "❌"
strings = {
"name": "UnitHeta",
"no_query": f"{e} You must specify query",
"no_results": f"{e} No results",
"api_error": f"{e} API is having issues",
"result": (
"🥰 Results for {query}
:\n\n🧳 {name}
"
" by {dev}
\n👨🏫 {cls_doc}\n\n📚"
" Commands:\n{commands}\n\n🔗 Install: {prefix}dlm"
" {link}
"
),
"install": "🪄 Install",
"loaded": "✅ Sucessfully installed",
"not_loaded": "❌ Installation failed",
"language": "en",
}
strings_ru = {
"no_query": f"{e} Вы должны указать запрос",
"no_results": f"{e} Нет результатов",
"api_error": f"{e} С API случилась беда",
"result": (
"🥰 Результаты для {query}
:\n\n🧳"
" {name}
от {dev}
\n👨🏫"
" {cls_doc}\n\n📚 Команды:\n{commands}\n\n🔗 Установить:"
" {prefix}dlm {link}
"
),
"install": "🪄 Установить",
"loaded": "✅ Успешно установлено",
"not_loaded": "❌ Установка не удалась",
"language": "ru",
}
strings_es = {
"no_query": f"{e} Debes especificar una consulta",
"no_results": f"{e} No hay resultados",
"api_error": f"{e} Hay problemas con la API",
"result": (
"🥰 Resultados para {query}
:\n\n🧳"
" {name}
por {dev}
\n👨🏫"
" {cls_doc}\n\n📚 Comandos:\n{commands}\n\n🔗 Instalar:"
" {prefix}dlm {link}
"
),
"install": "🪄 Instalar",
"loaded": "✅ Instalado con éxito",
"not_loaded": "❌ La instalación falló",
"language": "es",
}
strings_de = {
"no_query": f"{e} Du musst eine Abfrage angeben",
"no_results": f"{e} Keine Ergebnisse",
"api_error": f"{e} Es gibt Probleme mit der API",
"result": (
"🥰 Ergebnisse für {query}
:\n\n🧳"
" {name}
von {dev}
\n👨🏫"
" {cls_doc}\n\n📚 Befehle:\n{commands}\n\n🔗"
" Installieren: {prefix}dlm {link}
"
),
"install": "🪄 Installieren",
"loaded": "✅ Erfolgreich installiert",
"not_loaded": "❌ Die Installation ist fehlgeschlagen",
"language": "de",
}
strings_fr = {
"no_query": f"{e} Vous devez spécifier une requête",
"no_results": f"{e} Aucun résultat",
"api_error": f"{e} Quelque chose s'est mal passé avec l'API",
"result": (
"🥰 Résultats pour {query}
:\n\n🧳"
" {name}
par {dev}
\n👨🏫"
" {cls_doc}\n\n📚 Commandes:\n{commands}\n\n🔗"
" Installer: {prefix}dlm {link}
"
),
"install": "🪄 Installer",
"loaded": "✅ Installation réussie",
"not_loaded": "❌ Installation échouée",
"language": "fr",
}
strings_uz = {
"no_query": f"{e} Siz so'rovni belgilamadingiz",
"no_results": f"{e} Natija topilmadi",
"api_error": f"{e} API bilan muammo yuz berdi",
"result": (
"🥰 Ushbu {query}
uchun natijalar:\n\n🧳"
" {name}
to'g'risida {dev}
\n👨🏫"
" {cls_doc}\n\n📚 Komandalar:\n{commands}\n\n🔗"
" O'rnatish: {prefix}dlm {link}
"
),
"install": "🪄 O'rnatish",
"loaded": "✅ Muvaffaqiyatli o'rnatildi",
"not_loaded": "❌ O'rnatish muvaffaqiyatsiz bo'ldi",
"language": "uz",
}
strings_tr = {
"no_query": f"{e} Bir sorgu belirtmelisiniz",
"no_results": f"{e} Sonuç yok",
"api_error": f"{e} API ile ilgili bir sorun oluştu",
"result": (
"🥰 Sonuçlar için {query}
:\n\n🧳"
" {name}
geliştirici {dev}
\n👨🏫"
" {cls_doc}\n\n📚 Komutlar:\n{commands}\n\n🔗 Yükle:"
" {prefix}dlm {link}
"
),
"install": "🪄 Yükle",
"loaded": "✅ Başarıyla yüklendi",
"not_loaded": "❌ Yükleme başarısız oldu",
"language": "tr",
}
strings_it = {
"no_query": f"{e} Devi specificare una query",
"no_results": f"{e} Nessun risultato",
"api_error": f"{e} Si è verificato un'errore con l'API",
"result": (
"🥰 Risultati per {query}
:\n\n🧳"
" {name}
da {dev}
\n👨🏫"
" {cls_doc}\n\n📚 Comandi:\n{commands}\n\n🔗 Installare:"
" {prefix}dlm {link}
"
),
"install": "🪄 Installare",
"loaded": "✅ Installazione riuscita",
"not_loaded": "❌ Installazione non riuscita",
"language": "it",
}
strings_kk = {
"no_query": f"{e} Сұранымды көрсетуіңіз керек",
"no_results": f"{e} Нәтижелер жоқ",
"api_error": f"{e} API-ға қате кетті",
"result": (
"🥰 Сұранымдың нәтижелері {query}
:\n\n🧳"
" {name}
төлесін {dev}
\n👨🏫"
" {cls_doc}\n\n📚 Командалар:\n{commands}\n\n🔗 Орнату:"
" {prefix}dlm {link}
"
),
"install": "🪄 Орнату",
"loaded": "✅ Орнату сәтті аяқталды",
"not_loaded": "❌ Орнату сәтсіз аяқталды",
"language": "kk",
}
strings_tt = {
"no_query": f"{e} Зиндергә мәгълүматләр кертмәгәнсез",
"no_results": f"{e} Нәтиҗәләр табылмады",
"api_error": f"{e} API-сәхифәсе белән хата",
"result": (
"🥰 Зиндергә нәтиҗәләр {query}
:\n\n🧳"
" {name}
төзәтелгән {dev}
\n👨🏫"
" {cls_doc}\n\n📚 Командалар:\n{commands}\n\n🔗"
" Установить: {prefix}dlm {link}
"
),
"install": "🪄 Установить",
"loaded": "✅ Установка уңышлы тамамланды",
"not_loaded": "❌ Установка үтәлмәде",
"language": "tt",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"autoupdate",
False,
(
"Do you want to autoupdate modules? (Join @heta_updates in order"
" for this option to take effect) ⚠️ Use at your own risk!"
),
validator=loader.validators.Boolean(),
),
loader.ConfigValue(
"translate",
True,
(
"Do you want to translate module descriptions and command docs to"
" the language, specified in Hikka? (This option is experimental,"
" and might not work properly)"
),
validator=loader.validators.Boolean(),
),
)
async def client_ready(self):
if self.config["autoupdate"]:
await self.request_join(
"@heta_updates",
"This channel is the source of update notifications",
)
if self.get("nomute"):
return
await utils.dnd(self._client, "@hikkamods_bot", archive=False)
self.set("nomute", True)
async def _install(self, call: InlineCall, url: str, text: str):
await call.edit(
text,
reply_markup={
"text": (
self.strings("loaded")
if await self._load_module(url)
else self.strings("not_loaded")
),
"data": "empty",
},
)
@loader.command(
ru_doc="<запрос> - Ищет модули в репозитории Heta",
de_doc=" - Sucht Module im Heta-Repository",
uz_doc=" - Heta ombori uchun modullarni qidiradi",
tr_doc=" - Heta deposunda modülleri arar",
it_doc=" - Cerca moduli nel repository Heta",
fr_doc=" - Recherche des modules dans le référentiel Heta",
kk_doc="<сұраным> - Heta орталығында модульларды іздейді",
tt_doc="<зиндергә> - Heta депозиториясендә модульләрне таба",
es_doc=" - Busca módulos en el repositorio Heta",
)
async def heta(self, message: Message):
""" - Searches Heta repository for modules"""
if not (query := utils.get_args_raw(message)):
await utils.answer(message, self.strings("no_query"))
return
if not (
response := await utils.run_sync(
requests.get,
"https://heta.hikariatama.ru/search",
params={"q": query, "limit": 1},
)
):
await utils.answer(message, self.strings("no_results"))
return
try:
response.raise_for_status()
except requests.exceptions.HTTPError:
await utils.answer(message, self.strings("api_error"))
return
if not (result := response.json()):
await utils.answer(message, self.strings("no_results"))
return
result = result[0]
commands = "\n".join(
[
f"▫️ {self.get_prefix()}{cmd}
:"
f" {utils.escape_html(cmd_doc)}"
for cmd, cmd_doc in result["module"]["commands"].items()
]
)
kwargs = {
"name": utils.escape_html(result["module"]["name"]),
"dev": utils.escape_html(result["module"]["dev"]),
"commands": commands,
"cls_doc": utils.escape_html(result["module"]["cls_doc"]),
"link": result["module"]["link"],
"query": utils.escape_html(query),
"prefix": self.get_prefix(),
}
strings = (
self.strings._base_strings["result"]
if self.config["translate"]
else self.strings("result")
)
text = strings.format(**kwargs)
if len(text) > 2048:
kwargs["commands"] = "..."
text = strings.format(**kwargs)
mark = lambda text: {
"text": self.strings("install"),
"callback": self._install,
"args": (result["module"]["link"], text),
}
form = await self.inline.form(
message=message,
text=text,
**(
{"photo": result["module"]["banner"]}
if result["module"].get("banner")
else {}
),
reply_markup=mark(text),
)
if not self.config["translate"]:
return
message_id, peer, _, _ = resolve_inline_message_id(form.inline_message_id)
try:
text = await self._client.translate(
peer,
message_id,
self.strings("language"),
)
await form.edit(text=text, reply_markup=mark(text))
except Exception:
text = self.strings("result").format(**kwargs)
await form.edit(text=text, reply_markup=mark(text))
async def _load_module(
self,
url: str,
message: typing.Optional[Message] = None,
) -> bool:
loader_m = self.lookup("loader")
await loader_m.download_and_install(url, None)
if getattr(loader_m, "fully_loaded", False):
loader_m.update_modules_in_db()
loaded = any(
link == url for link in loader_m.get("loaded_modules", {}).values()
)
if message:
if loaded:
await self._client.inline_query(
"@hikkamods_bot",
f"#confirm_load {message.raw_text.splitlines()[2].strip()}",
)
else:
await self._client.inline_query(
"@hikkamods_bot",
f"#confirm_fload {message.raw_text.splitlines()[2].strip()}",
)
return loaded
@loader.watcher("in", "only_messages", chat_id=1688624566, contains="Heta url: ")
async def update_watcher(self, message: Message):
url = message.raw_text.split("Heta url: ")[1].strip()
dev, repo, mod = url.lower().split("hikariatama.ru/")[1].split("/")
if dev == "hikariatama" and repo == "ftg":
urls = [f"https://mods.hikariatama.ru/{mod}", url]
if any(
getattr(module, "__origin__", None).lower().strip("/") in urls
for module in self.allmodules.modules
):
await self._load_module(urls[0])
await asyncio.sleep(random.randint(1, 10))
await self._client.inline_query(
"@hikkamods_bot",
f"#confirm_update_noheta {url.split('hikariatama.ru/')[1]}",
)
return
if any(
getattr(module, "__origin__", "").lower().strip("/")
== url.lower().strip("/")
for module in self.allmodules.modules
):
await self._load_module(url)
await asyncio.sleep(random.randint(1, 10))
await self._client.inline_query(
"@hikkamods_bot",
f"#confirm_update {url.split('hikariatama.ru/')[1]}",
)
return
for module in self.allmodules.modules:
link = getattr(module, "__origin__", "").lower().strip("/")
for regex in REGEXES:
if regex.search(link):
dev, repo, mod = regex.search(link).groups()
if dev == dev and repo == repo and mod == mod:
await self._load_module(link)
await asyncio.sleep(random.randint(1, 10))
await self._client.inline_query(
"@hikkamods_bot",
f"#confirm_update_noheta {url.split('hikariatama.ru/')[1]}",
)
return
@loader.watcher("in", "only_messages", from_user=5519484330)
async def watcher(self, message: Message):
if message.raw_text.startswith("#install"):
await message.delete()
fileref = (
message.raw_text.split("#install:")[1].strip().splitlines()[0].strip()
)
sig = base64.b64decode(message.raw_text.splitlines()[1].strip().encode())
try:
rsa.verify(
rsa.compute_hash(fileref.encode("utf-8"), "SHA-1"), sig, pubkey
)
except rsa.pkcs1.VerificationError:
logger.error("Got message with non-verified signature %s", fileref)
return
await self._load_module(f"https://heta.hikariatama.ru/{fileref}", message)
elif message.raw_text.startswith("#setlang"):
lang = message.raw_text.split()[1]
self._db.set(translations.__name__, "lang", lang)
await self.allmodules.reload_translations()
await self._client.inline_query("@hikkamods_bot", "#confirm_setlang")