mirror of
https://github.com/MuRuLOSE/limoka.git
synced 2026-06-17 14:54:18 +02:00
lang and dev_username hotfix
This commit is contained in:
499
Limoka.py
499
Limoka.py
@@ -2,7 +2,7 @@
|
|||||||
# requires: whoosh cryptography
|
# requires: whoosh cryptography
|
||||||
|
|
||||||
|
|
||||||
import datetime
|
from collections import Counter, defaultdict
|
||||||
from whoosh.index import create_in, open_dir
|
from whoosh.index import create_in, open_dir
|
||||||
from whoosh.fields import Schema, TEXT, ID
|
from whoosh.fields import Schema, TEXT, ID
|
||||||
from whoosh.qparser import QueryParser, OrGroup
|
from whoosh.qparser import QueryParser, OrGroup
|
||||||
@@ -15,27 +15,41 @@ import html
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Union, List, Dict, Any, Optional
|
from typing import Iterable, Union, List, Dict, Any, Optional
|
||||||
import hashlib
|
import hashlib
|
||||||
from telethon.types import Message
|
from telethon.types import Message
|
||||||
from telethon.errors.rpcerrorlist import WebpageMediaEmptyError
|
from telethon.errors.rpcerrorlist import WebpageMediaEmptyError
|
||||||
from telethon import TelegramClient
|
from telethon import TelegramClient
|
||||||
from telethon.errors.rpcerrorlist import YouBlockedUserError
|
from telethon.errors.rpcerrorlist import YouBlockedUserError
|
||||||
from telethon import functions, types
|
from telethon import functions
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from aiogram.utils.exceptions import BadRequest
|
from aiogram.utils.exceptions import BadRequest
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from aiogram.exceptions import TelegramBadRequest as BadRequest
|
from aiogram.exceptions import TelegramBadRequest as BadRequest
|
||||||
from .. import utils, loader
|
from .. import utils, loader
|
||||||
from ..types import InlineCall
|
from ..types import InlineCall
|
||||||
|
|
||||||
logger = logging.getLogger("Limoka")
|
logger = logging.getLogger("Limoka")
|
||||||
__version__ = (1, 4, 0)
|
__version__ = (1, 4, 1)
|
||||||
|
|
||||||
|
WEIGHTS = {
|
||||||
|
"inline.token_obtainment": 15,
|
||||||
|
"main": 10,
|
||||||
|
"inline": 7,
|
||||||
|
"translations": 5,
|
||||||
|
"security": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_WEIGHT = 1
|
||||||
|
|
||||||
|
|
||||||
def _get_lang_value(data: Dict[str, Any], lang: str) -> str:
|
def _get_lang_value(data: Dict[str, Any], lang: str) -> str:
|
||||||
if not isinstance(data, dict):
|
if not isinstance(data, dict):
|
||||||
return str(data) if data else ""
|
return str(data) if data else ""
|
||||||
return data.get(lang, data.get("default", data.get("en", "")))
|
return data.get(lang, data.get("default", data.get("en", "")))
|
||||||
|
|
||||||
|
|
||||||
class Search:
|
class Search:
|
||||||
def __init__(self, query, ix):
|
def __init__(self, query, ix):
|
||||||
self.schema = Schema(
|
self.schema = Schema(
|
||||||
@@ -56,6 +70,7 @@ class Search:
|
|||||||
return list(set(result["path"] for result in results))
|
return list(set(result["path"] for result in results))
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
class LimokaAPI:
|
class LimokaAPI:
|
||||||
async def fetch_json(self, base_url, path):
|
async def fetch_json(self, base_url, path):
|
||||||
url = f"{base_url}{path}"
|
url = f"{base_url}{path}"
|
||||||
@@ -63,9 +78,11 @@ class LimokaAPI:
|
|||||||
async with session.get(url) as response:
|
async with session.get(url) as response:
|
||||||
return json.loads(await response.text())
|
return json.loads(await response.text())
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
class Limoka(loader.Module):
|
class Limoka(loader.Module):
|
||||||
"""Modules are now in one place with easy searching!"""
|
"""Modules are now in one place with easy searching!"""
|
||||||
|
|
||||||
strings = {
|
strings = {
|
||||||
"name": "Limoka",
|
"name": "Limoka",
|
||||||
"wait": (
|
"wait": (
|
||||||
@@ -81,9 +98,7 @@ class Limoka(loader.Module):
|
|||||||
"<b><emoji document_id=5418299289141004396>🧑💻</emoji> Developer:</b> {username}\n\n"
|
"<b><emoji document_id=5418299289141004396>🧑💻</emoji> Developer:</b> {username}\n\n"
|
||||||
"<b><emoji document_id=5418376169055602355>🏷</emoji> Tags:</b> {tags}\n\n"
|
"<b><emoji document_id=5418376169055602355>🏷</emoji> Tags:</b> {tags}\n\n"
|
||||||
),
|
),
|
||||||
"found_body": (
|
"found_body": ("{commands}"),
|
||||||
"{commands}"
|
|
||||||
),
|
|
||||||
"found_footer": (
|
"found_footer": (
|
||||||
"\n<emoji document_id=5411143117711624172>🪄</emoji> <code>{prefix}dlm {url}{module_path}</code>"
|
"\n<emoji document_id=5411143117711624172>🪄</emoji> <code>{prefix}dlm {url}{module_path}</code>"
|
||||||
),
|
),
|
||||||
@@ -168,8 +183,8 @@ class Limoka(loader.Module):
|
|||||||
"hikkatrusted": "Hikka Trusted",
|
"hikkatrusted": "Hikka Trusted",
|
||||||
"nonactive": "Non-Active Repository",
|
"nonactive": "Non-Active Repository",
|
||||||
"nonlongermaintained": "No Longer Maintained Repository",
|
"nonlongermaintained": "No Longer Maintained Repository",
|
||||||
"newbie": "Newbie"
|
"newbie": "Newbie",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
"name": "Limoka",
|
"name": "Limoka",
|
||||||
@@ -185,9 +200,7 @@ class Limoka(loader.Module):
|
|||||||
"<b><emoji document_id=5418299289141004396>🧑💻</emoji> Разработчик:</b> {username}\n\n"
|
"<b><emoji document_id=5418299289141004396>🧑💻</emoji> Разработчик:</b> {username}\n\n"
|
||||||
"<b><emoji document_id=5418376169055602355>🏷</emoji> Теги:</b> {tags}\n\n"
|
"<b><emoji document_id=5418376169055602355>🏷</emoji> Теги:</b> {tags}\n\n"
|
||||||
),
|
),
|
||||||
"found_body": (
|
"found_body": ("{commands}"),
|
||||||
"{commands}"
|
|
||||||
),
|
|
||||||
"found_footer": (
|
"found_footer": (
|
||||||
"\n<emoji document_id=5411143117711624172>🪄</emoji> <code>{prefix}dlm {url}{module_path}</code>"
|
"\n<emoji document_id=5411143117711624172>🪄</emoji> <code>{prefix}dlm {url}{module_path}</code>"
|
||||||
),
|
),
|
||||||
@@ -220,7 +233,7 @@ class Limoka(loader.Module):
|
|||||||
(
|
(
|
||||||
"<emoji document_id=5188311512791393083>🔎</emoji> Limoka имеет лучший поиск*!\n"
|
"<emoji document_id=5188311512791393083>🔎</emoji> Limoka имеет лучший поиск*!\n"
|
||||||
"<i>* В сравнении с предыдущей версией Limoka</i>"
|
"<i>* В сравнении с предыдущей версией Limoka</i>"
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
"inline404": "Не найдено",
|
"inline404": "Не найдено",
|
||||||
"inline?": "Запрос слишком короткий / не найден",
|
"inline?": "Запрос слишком короткий / не найден",
|
||||||
@@ -276,7 +289,7 @@ class Limoka(loader.Module):
|
|||||||
"hikkatrusted": "Hikka Trusted",
|
"hikkatrusted": "Hikka Trusted",
|
||||||
"nonactive": "Неактивный репозиторий",
|
"nonactive": "Неактивный репозиторий",
|
||||||
"nonlongermaintained": "Неподдерживаемый репозиторий",
|
"nonlongermaintained": "Неподдерживаемый репозиторий",
|
||||||
"newbie": "Новичок"
|
"newbie": "Новичок",
|
||||||
},
|
},
|
||||||
"_cls_doc": "Модули теперь в одном месте с простым и удобным поиском!",
|
"_cls_doc": "Модули теперь в одном месте с простым и удобным поиском!",
|
||||||
}
|
}
|
||||||
@@ -307,22 +320,22 @@ class Limoka(loader.Module):
|
|||||||
self._invalid_banners = set()
|
self._invalid_banners = set()
|
||||||
self._bot_username = "limoka_bbot"
|
self._bot_username = "limoka_bbot"
|
||||||
self._base_url = self.config["limokaurl"]
|
self._base_url = self.config["limokaurl"]
|
||||||
|
|
||||||
# Search session states
|
# Search session states
|
||||||
self.SEARCH_STATES = {
|
self.SEARCH_STATES = {
|
||||||
"no_banner": "no_banner", # 404 - Нет баннера
|
"no_banner": "no_banner", # 404 - Нет баннера
|
||||||
"global_search": "global_search", # Глобальный поиск
|
"global_search": "global_search", # Глобальный поиск
|
||||||
"not_found": "not_found", # Не найдено (модуль)
|
"not_found": "not_found", # Не найдено (модуль)
|
||||||
"filter_select": "filter_select", # Выбор категорий (фильтров)
|
"filter_select": "filter_select", # Выбор категорий (фильтров)
|
||||||
}
|
}
|
||||||
|
|
||||||
# State banners - placeholders for now
|
# State banners - placeholders for now
|
||||||
self.state_banners = {
|
self.state_banners = {
|
||||||
"no_banner": "https://raw.githubusercontent.com/MuRuLOSE/hikka-assets/refs/heads/main/Limoka%20-%20No%20banner.png",
|
"no_banner": "https://raw.githubusercontent.com/MuRuLOSE/hikka-assets/refs/heads/main/Limoka%20-%20No%20banner.png",
|
||||||
"global_search": "https://raw.githubusercontent.com/MuRuLOSE/hikka-assets/main/Limoka%20-%20Global%20Search.png",
|
"global_search": "https://raw.githubusercontent.com/MuRuLOSE/hikka-assets/main/Limoka%20-%20Global%20Search.png",
|
||||||
"not_found": "https://raw.githubusercontent.com/MuRuLOSE/hikka-assets/main/Limoka%20-%20Not%20Found.png",
|
"not_found": "https://raw.githubusercontent.com/MuRuLOSE/hikka-assets/main/Limoka%20-%20Not%20Found.png",
|
||||||
"filter_select": "https://raw.githubusercontent.com/MuRuLOSE/hikka-assets/main/Limoka%20-%20Categories.png",
|
"filter_select": "https://raw.githubusercontent.com/MuRuLOSE/hikka-assets/main/Limoka%20-%20Categories.png",
|
||||||
}
|
}
|
||||||
|
|
||||||
def _filter_newbies(self, modules: Dict[str, Any]) -> Dict[str, Any]:
|
def _filter_newbies(self, modules: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""Filter out modules which belong to repositories tagged as 'newbie'.
|
"""Filter out modules which belong to repositories tagged as 'newbie'.
|
||||||
@@ -357,7 +370,7 @@ class Limoka(loader.Module):
|
|||||||
current_index: int = 0,
|
current_index: int = 0,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Create a search session dictionary to track state across callbacks.
|
"""Create a search session dictionary to track state across callbacks.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
state: Current search state (one of SEARCH_STATES values)
|
state: Current search state (one of SEARCH_STATES values)
|
||||||
query: Current search query
|
query: Current search query
|
||||||
@@ -365,7 +378,7 @@ class Limoka(loader.Module):
|
|||||||
results: Search results list
|
results: Search results list
|
||||||
current_index: Index of current result being displayed
|
current_index: Index of current result being displayed
|
||||||
banner_url: Banner image URL for current state
|
banner_url: Banner image URL for current state
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary containing the complete session state
|
Dictionary containing the complete session state
|
||||||
"""
|
"""
|
||||||
@@ -393,16 +406,13 @@ class Limoka(loader.Module):
|
|||||||
else:
|
else:
|
||||||
self.ix = open_dir("limoka_search")
|
self.ix = open_dir("limoka_search")
|
||||||
self._history = self.pointer("history", [])
|
self._history = self.pointer("history", [])
|
||||||
self.modules = (await self.api.fetch_json(
|
self.modules = (await self.api.fetch_json(self._base_url, "modules.json")).get(
|
||||||
self._base_url, "modules.json"
|
"modules", {}
|
||||||
)).get("modules", {})
|
)
|
||||||
raw = (await self.api.fetch_json(
|
raw = (await self.api.fetch_json(self._base_url, "repositories.json")).get(
|
||||||
self._base_url, "repositories.json"
|
"repositories", []
|
||||||
)).get("repositories", [])
|
)
|
||||||
self.repositories = {
|
self.repositories = {repo["url"]: repo for repo in raw}
|
||||||
repo["url"]: repo
|
|
||||||
for repo in raw
|
|
||||||
}
|
|
||||||
# Apply newbie filter if enabled
|
# Apply newbie filter if enabled
|
||||||
try:
|
try:
|
||||||
self.modules = self._filter_newbies(self.modules)
|
self.modules = self._filter_newbies(self.modules)
|
||||||
@@ -414,24 +424,28 @@ class Limoka(loader.Module):
|
|||||||
try:
|
try:
|
||||||
message = await self.client.get_messages(self._bot_username, limit=1)
|
message = await self.client.get_messages(self._bot_username, limit=1)
|
||||||
if not message:
|
if not message:
|
||||||
message = await self.client.send_message(self._bot_username, "/start")
|
message = await self.client.send_message(
|
||||||
|
self._bot_username, "/start"
|
||||||
|
)
|
||||||
await message.delete()
|
await message.delete()
|
||||||
await self.client(functions.messages.DeleteHistoryRequest(
|
await self.client(
|
||||||
peer=self._bot_username,
|
functions.messages.DeleteHistoryRequest(
|
||||||
max_id=0,
|
peer=self._bot_username,
|
||||||
just_clear=True,
|
max_id=0,
|
||||||
revoke=True,
|
just_clear=True,
|
||||||
))
|
revoke=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
except YouBlockedUserError:
|
except YouBlockedUserError:
|
||||||
logger.warning(f"Please unblock {self._bot_username} to enable external installation feature. Or disable external_install_allowed in Limoka settings to get rid of this message.")
|
logger.warning(
|
||||||
|
f"Please unblock {self._bot_username} to enable external installation feature. Or disable external_install_allowed in Limoka settings to get rid of this message."
|
||||||
|
)
|
||||||
self._userbot_bot_username = (await self.inline.bot.get_me()).username
|
self._userbot_bot_username = (await self.inline.bot.get_me()).username
|
||||||
|
|
||||||
@loader.loop(interval=3600)
|
@loader.loop(interval=3600)
|
||||||
async def _update_modules_loop(self):
|
async def _update_modules_loop(self):
|
||||||
self.modules = await self.api.fetch_json(
|
self.modules = await self.api.fetch_json(self._base_url, "modules.json")
|
||||||
self._base_url, "modules.json"
|
|
||||||
)
|
|
||||||
# Re-apply newbie filter after modules refresh
|
# Re-apply newbie filter after modules refresh
|
||||||
try:
|
try:
|
||||||
self.modules = self._filter_newbies(self.modules)
|
self.modules = self._filter_newbies(self.modules)
|
||||||
@@ -446,7 +460,18 @@ class Limoka(loader.Module):
|
|||||||
writer.add_document(
|
writer.add_document(
|
||||||
title=module_data["name"],
|
title=module_data["name"],
|
||||||
path=module_path,
|
path=module_path,
|
||||||
content=module_data["name"] + " " + (module_data.get("description") or "" + " " + ((module_data.get("meta").get("developer") or "") if module_data.get("meta") else "")),
|
content=module_data["name"]
|
||||||
|
+ " "
|
||||||
|
+ (
|
||||||
|
module_data.get("description")
|
||||||
|
or ""
|
||||||
|
+ " "
|
||||||
|
+ (
|
||||||
|
(module_data.get("meta").get("developer") or "")
|
||||||
|
if module_data.get("meta")
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
for func in module_data.get("commands", []):
|
for func in module_data.get("commands", []):
|
||||||
for command, description in func.items():
|
for command, description in func.items():
|
||||||
@@ -462,7 +487,9 @@ class Limoka(loader.Module):
|
|||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.head(url, timeout=5, allow_redirects=True) as response:
|
async with session.head(
|
||||||
|
url, timeout=5, allow_redirects=True
|
||||||
|
) as response:
|
||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
self._invalid_banners.add(url)
|
self._invalid_banners.add(url)
|
||||||
return None
|
return None
|
||||||
@@ -475,9 +502,43 @@ class Limoka(loader.Module):
|
|||||||
if url:
|
if url:
|
||||||
self._invalid_banners.add(url)
|
self._invalid_banners.add(url)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def find_userbot(self, keys: Iterable[str]) -> str | None:
|
||||||
|
scores = defaultdict(int)
|
||||||
|
|
||||||
|
for key in keys:
|
||||||
|
parts = key.split(".")
|
||||||
|
|
||||||
|
# проходим все префиксы
|
||||||
|
for i in range(1, len(parts)):
|
||||||
|
prefix = ".".join(parts[:i])
|
||||||
|
suffix = ".".join(parts[i:])
|
||||||
|
|
||||||
|
weight = WEIGHTS.get(suffix, DEFAULT_WEIGHT)
|
||||||
|
|
||||||
|
scores[prefix] += weight
|
||||||
|
|
||||||
|
if not scores:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return max(scores, key=scores.get)
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
def user_lang(self) -> str:
|
def user_lang(self) -> str:
|
||||||
self.db.get("heroku.translations", "lang")
|
|
||||||
|
userbot = self.find_userbot(self.db.keys())
|
||||||
|
|
||||||
|
if not userbot:
|
||||||
|
logger.warning(
|
||||||
|
"Cannot determine userbot type. "
|
||||||
|
"Probably not FTG-like Userbot? "
|
||||||
|
"Defaulting language to English. "
|
||||||
|
"If this is unexpected, please report to the module developer."
|
||||||
|
)
|
||||||
|
return "en"
|
||||||
|
|
||||||
|
return self.db.get(f"{userbot}.translations", "lang")
|
||||||
|
|
||||||
def generate_commands(self, module_info, lang: str = "en"):
|
def generate_commands(self, module_info, lang: str = "en"):
|
||||||
commands = []
|
commands = []
|
||||||
@@ -507,7 +568,6 @@ class Limoka(loader.Module):
|
|||||||
)
|
)
|
||||||
return commands
|
return commands
|
||||||
|
|
||||||
|
|
||||||
def _format_module_content(
|
def _format_module_content(
|
||||||
self,
|
self,
|
||||||
module_info: Dict[str, Any],
|
module_info: Dict[str, Any],
|
||||||
@@ -520,12 +580,14 @@ class Limoka(loader.Module):
|
|||||||
name = html.escape(module_info.get("name") or self.strings["no_info"])
|
name = html.escape(module_info.get("name") or self.strings["no_info"])
|
||||||
cls_doc = module_info.get("cls_doc", {})
|
cls_doc = module_info.get("cls_doc", {})
|
||||||
description = html.escape(
|
description = html.escape(
|
||||||
_get_lang_value(cls_doc, lang) or
|
_get_lang_value(cls_doc, lang)
|
||||||
_get_lang_value(module_info.get("description", ""), lang) or
|
or _get_lang_value(module_info.get("description", ""), lang)
|
||||||
self.strings["no_info"]
|
or self.strings["no_info"]
|
||||||
|
)
|
||||||
|
dev_username = html.escape(module_info["meta"].get("developer") or "Unknown")
|
||||||
|
raw_path = (
|
||||||
|
module_path if module_path is not None else module_info.get("path", "")
|
||||||
)
|
)
|
||||||
dev_username = html.escape(module_info["meta"].get("developer", "Unknown"))
|
|
||||||
raw_path = module_path if module_path is not None else module_info.get("path", "")
|
|
||||||
clean_module_path = (raw_path or "").replace("\\", "/")
|
clean_module_path = (raw_path or "").replace("\\", "/")
|
||||||
commands = self.generate_commands(module_info, lang)
|
commands = self.generate_commands(module_info, lang)
|
||||||
categories_text = ""
|
categories_text = ""
|
||||||
@@ -537,7 +599,9 @@ class Limoka(loader.Module):
|
|||||||
)
|
)
|
||||||
if len(description) > 300:
|
if len(description) > 300:
|
||||||
description = description[:297] + "…"
|
description = description[:297] + "…"
|
||||||
repo_key = "/".join(module_path.split("/")[:2]) if "/" in module_path else module_path
|
repo_key = (
|
||||||
|
"/".join(module_path.split("/")[:2]) if "/" in module_path else module_path
|
||||||
|
)
|
||||||
tags_list = []
|
tags_list = []
|
||||||
for x in self.repositories:
|
for x in self.repositories:
|
||||||
if x.replace("https://github.com/", "") == repo_key:
|
if x.replace("https://github.com/", "") == repo_key:
|
||||||
@@ -577,14 +641,12 @@ class Limoka(loader.Module):
|
|||||||
)
|
)
|
||||||
return header, body_pages, footer, categories_text
|
return header, body_pages, footer, categories_text
|
||||||
|
|
||||||
def _build_navigation_markup(
|
def _build_navigation_markup(self, session: Dict[str, Any]) -> list:
|
||||||
self, session: Dict[str, Any]
|
|
||||||
) -> list:
|
|
||||||
result = session["results"]
|
result = session["results"]
|
||||||
index = session["current_index"]
|
index = session["current_index"]
|
||||||
query = session["query"]
|
query = session["query"]
|
||||||
filters = session["filters"]
|
filters = session["filters"]
|
||||||
|
|
||||||
page = index + 1
|
page = index + 1
|
||||||
markup = [
|
markup = [
|
||||||
[
|
[
|
||||||
@@ -596,7 +658,11 @@ class Limoka(loader.Module):
|
|||||||
{"text": f"{page}/{len(result)}", "callback": self._inline_void},
|
{"text": f"{page}/{len(result)}", "callback": self._inline_void},
|
||||||
{
|
{
|
||||||
"text": "⏩" if index + 1 < len(result) else "🚫",
|
"text": "⏩" if index + 1 < len(result) else "🚫",
|
||||||
"callback": self._next_page if index + 1 < len(result) else self._inline_void,
|
"callback": (
|
||||||
|
self._next_page
|
||||||
|
if index + 1 < len(result)
|
||||||
|
else self._inline_void
|
||||||
|
),
|
||||||
"args": (session,) if index + 1 < len(result) else (),
|
"args": (session,) if index + 1 < len(result) else (),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -625,60 +691,93 @@ class Limoka(loader.Module):
|
|||||||
return markup
|
return markup
|
||||||
|
|
||||||
def _build_module_markup(
|
def _build_module_markup(
|
||||||
self, session: Dict[str, Any], body_pages: List[str], page_body: int, module_path: str
|
self,
|
||||||
|
session: Dict[str, Any],
|
||||||
|
body_pages: List[str],
|
||||||
|
page_body: int,
|
||||||
|
module_path: str,
|
||||||
) -> list:
|
) -> list:
|
||||||
result = session["results"]
|
result = session["results"]
|
||||||
index = session["current_index"]
|
index = session["current_index"]
|
||||||
query = session["query"]
|
query = session["query"]
|
||||||
filters = session["filters"]
|
filters = session["filters"]
|
||||||
|
|
||||||
markup = []
|
markup = []
|
||||||
if len(body_pages) > 1:
|
if len(body_pages) > 1:
|
||||||
markup.append([
|
markup.append(
|
||||||
{
|
[
|
||||||
"text": "◀️" if page_body > 0 else "🚫",
|
{
|
||||||
"callback": self._previous_body_page if page_body > 0 else self._inline_void,
|
"text": "◀️" if page_body > 0 else "🚫",
|
||||||
"args": (session, module_path, page_body) if page_body > 0 else (),
|
"callback": (
|
||||||
},
|
self._previous_body_page
|
||||||
{"text": f"Body {page_body + 1}/{len(body_pages)}", "callback": self._inline_void},
|
if page_body > 0
|
||||||
{
|
else self._inline_void
|
||||||
"text": "▶️" if page_body + 1 < len(body_pages) else "🚫",
|
),
|
||||||
"callback": self._next_body_page if page_body + 1 < len(body_pages) else self._inline_void,
|
"args": (
|
||||||
"args": (session, module_path, page_body) if page_body + 1 < len(body_pages) else (),
|
(session, module_path, page_body) if page_body > 0 else ()
|
||||||
},
|
),
|
||||||
])
|
},
|
||||||
|
{
|
||||||
|
"text": f"Body {page_body + 1}/{len(body_pages)}",
|
||||||
|
"callback": self._inline_void,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "▶️" if page_body + 1 < len(body_pages) else "🚫",
|
||||||
|
"callback": (
|
||||||
|
self._next_body_page
|
||||||
|
if page_body + 1 < len(body_pages)
|
||||||
|
else self._inline_void
|
||||||
|
),
|
||||||
|
"args": (
|
||||||
|
(session, module_path, page_body)
|
||||||
|
if page_body + 1 < len(body_pages)
|
||||||
|
else ()
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
page = index + 1
|
page = index + 1
|
||||||
markup.append([
|
markup.append(
|
||||||
{
|
[
|
||||||
"text": "⏪" if index > 0 else "🚫",
|
{
|
||||||
"callback": self._previous_page if index > 0 else self._inline_void,
|
"text": "⏪" if index > 0 else "🚫",
|
||||||
"args": (session,) if index > 0 else (),
|
"callback": self._previous_page if index > 0 else self._inline_void,
|
||||||
},
|
"args": (session,) if index > 0 else (),
|
||||||
{"text": f"{page}/{len(result)}", "callback": self._inline_void},
|
},
|
||||||
{
|
{"text": f"{page}/{len(result)}", "callback": self._inline_void},
|
||||||
"text": "⏩" if index + 1 < len(result) else "🚫",
|
{
|
||||||
"callback": self._next_page if index + 1 < len(result) else self._inline_void,
|
"text": "⏩" if index + 1 < len(result) else "🚫",
|
||||||
"args": (session,) if index + 1 < len(result) else (),
|
"callback": (
|
||||||
},
|
self._next_page
|
||||||
])
|
if index + 1 < len(result)
|
||||||
markup.append([
|
else self._inline_void
|
||||||
{
|
),
|
||||||
"text": "🔍 " + self.strings["filter_menu"].split(":")[0],
|
"args": (session,) if index + 1 < len(result) else (),
|
||||||
"callback": self._display_filter_menu,
|
},
|
||||||
"args": (session,),
|
]
|
||||||
},
|
)
|
||||||
{
|
markup.append(
|
||||||
"text": "🔄 " + self.strings["change_query"],
|
[
|
||||||
"callback": self._enter_query,
|
{
|
||||||
},
|
"text": "🔍 " + self.strings["filter_menu"].split(":")[0],
|
||||||
])
|
"callback": self._display_filter_menu,
|
||||||
markup.append([
|
"args": (session,),
|
||||||
{
|
},
|
||||||
"text": self.strings["global_button"],
|
{
|
||||||
"callback": self._show_global_results,
|
"text": "🔄 " + self.strings["change_query"],
|
||||||
"args": (session,),
|
"callback": self._enter_query,
|
||||||
},
|
},
|
||||||
])
|
]
|
||||||
|
)
|
||||||
|
markup.append(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"text": self.strings["global_button"],
|
||||||
|
"callback": self._show_global_results,
|
||||||
|
"args": (session,),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
markup.append(
|
markup.append(
|
||||||
[{"text": self.strings.get("close", "❌ Close"), "action": "close"}]
|
[{"text": self.strings.get("close", "❌ Close"), "action": "close"}]
|
||||||
)
|
)
|
||||||
@@ -709,7 +808,9 @@ class Limoka(loader.Module):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if photo is not None:
|
if photo is not None:
|
||||||
await message_or_call.edit(text=text, reply_markup=markup, photo=photo)
|
await message_or_call.edit(
|
||||||
|
text=text, reply_markup=markup, photo=photo
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await message_or_call.edit(text=text, reply_markup=markup)
|
await message_or_call.edit(text=text, reply_markup=markup)
|
||||||
except (BadRequest, WebpageMediaEmptyError) as e:
|
except (BadRequest, WebpageMediaEmptyError) as e:
|
||||||
@@ -730,8 +831,11 @@ class Limoka(loader.Module):
|
|||||||
try:
|
try:
|
||||||
query = session["query"]
|
query = session["query"]
|
||||||
filters = session["filters"]
|
filters = session["filters"]
|
||||||
|
|
||||||
lang = self.user_lang()
|
lang = self.user_lang
|
||||||
|
logger.info(
|
||||||
|
f"Displaying module: {module_path} for query: {query} with filters: {filters} in language: {lang}"
|
||||||
|
)
|
||||||
module_banner_raw = module_info.get("meta", {}).get("banner")
|
module_banner_raw = module_info.get("meta", {}).get("banner")
|
||||||
photo = await self._validate_url(module_banner_raw)
|
photo = await self._validate_url(module_banner_raw)
|
||||||
|
|
||||||
@@ -750,11 +854,11 @@ class Limoka(loader.Module):
|
|||||||
current_body = body_pages[min(page_body, len(body_pages) - 1)]
|
current_body = body_pages[min(page_body, len(body_pages) - 1)]
|
||||||
full_message = header + current_body + footer + categories_text
|
full_message = header + current_body + footer + categories_text
|
||||||
|
|
||||||
markup = self._build_module_markup(session, body_pages, page_body, module_path)
|
markup = self._build_module_markup(
|
||||||
|
session, body_pages, page_body, module_path
|
||||||
await self._safe_display(
|
|
||||||
message_or_call, full_message, markup, photo
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await self._safe_display(message_or_call, full_message, markup, photo)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Error in _display_module: {e}")
|
logger.exception(f"Error in _display_module: {e}")
|
||||||
if isinstance(message_or_call, Message):
|
if isinstance(message_or_call, Message):
|
||||||
@@ -763,30 +867,45 @@ class Limoka(loader.Module):
|
|||||||
await message_or_call.edit(self.strings["error_occurred"])
|
await message_or_call.edit(self.strings["error_occurred"])
|
||||||
|
|
||||||
async def _previous_body_page(
|
async def _previous_body_page(
|
||||||
self, call: InlineCall, session: Dict[str, Any], module_path: str, page_body: int
|
self,
|
||||||
|
call: InlineCall,
|
||||||
|
session: Dict[str, Any],
|
||||||
|
module_path: str,
|
||||||
|
page_body: int,
|
||||||
):
|
):
|
||||||
module_info = self.modules[module_path]
|
module_info = self.modules[module_path]
|
||||||
new_page_body = max(page_body - 1, 0)
|
new_page_body = max(page_body - 1, 0)
|
||||||
await self._display_module(call, module_info, module_path, session, page_body=new_page_body)
|
await self._display_module(
|
||||||
|
call, module_info, module_path, session, page_body=new_page_body
|
||||||
|
)
|
||||||
|
|
||||||
async def _next_body_page(
|
async def _next_body_page(
|
||||||
self, call: InlineCall, session: Dict[str, Any], module_path: str, page_body: int
|
self,
|
||||||
|
call: InlineCall,
|
||||||
|
session: Dict[str, Any],
|
||||||
|
module_path: str,
|
||||||
|
page_body: int,
|
||||||
):
|
):
|
||||||
module_info = self.modules[module_path]
|
module_info = self.modules[module_path]
|
||||||
query = session["query"]
|
query = session["query"]
|
||||||
filters = session["filters"]
|
filters = session["filters"]
|
||||||
header, body_pages, footer, categories_text = self._format_module_content(
|
header, body_pages, footer, categories_text = self._format_module_content(
|
||||||
module_info, query, filters, include_categories=True, module_path=module_path, lang=self.user_lang()
|
module_info,
|
||||||
|
query,
|
||||||
|
filters,
|
||||||
|
include_categories=True,
|
||||||
|
module_path=module_path,
|
||||||
|
lang=self.user_lang,
|
||||||
)
|
)
|
||||||
new_page_body = min(page_body + 1, len(body_pages) - 1)
|
new_page_body = min(page_body + 1, len(body_pages) - 1)
|
||||||
await self._display_module(call, module_info, module_path, session, page_body=new_page_body)
|
await self._display_module(
|
||||||
|
call, module_info, module_path, session, page_body=new_page_body
|
||||||
|
)
|
||||||
|
|
||||||
async def _display_filter_menu(
|
async def _display_filter_menu(self, call: InlineCall, session: Dict[str, Any]):
|
||||||
self, call: InlineCall, session: Dict[str, Any]
|
|
||||||
):
|
|
||||||
query = session["query"]
|
query = session["query"]
|
||||||
current_filters = session["filters"]
|
current_filters = session["filters"]
|
||||||
|
|
||||||
categories = current_filters.get("category", [])
|
categories = current_filters.get("category", [])
|
||||||
filters_text = self.strings["selected_categories"].format(
|
filters_text = self.strings["selected_categories"].format(
|
||||||
categories=(
|
categories=(
|
||||||
@@ -823,14 +942,14 @@ class Limoka(loader.Module):
|
|||||||
[{"text": self.strings.get("close", "❌ Close"), "action": "close"}],
|
[{"text": self.strings.get("close", "❌ Close"), "action": "close"}],
|
||||||
]
|
]
|
||||||
text = self.strings["filter_menu"].format(query=query) + f"\n{filters_text}"
|
text = self.strings["filter_menu"].format(query=query) + f"\n{filters_text}"
|
||||||
await call.edit(text, reply_markup=markup, photo=self._get_banner_for_state("filter_select"))
|
await call.edit(
|
||||||
|
text, reply_markup=markup, photo=self._get_banner_for_state("filter_select")
|
||||||
|
)
|
||||||
|
|
||||||
async def _select_category(
|
async def _select_category(self, call: InlineCall, session: Dict[str, Any]):
|
||||||
self, call: InlineCall, session: Dict[str, Any]
|
|
||||||
):
|
|
||||||
query = session["query"]
|
query = session["query"]
|
||||||
current_filters = session["filters"]
|
current_filters = session["filters"]
|
||||||
|
|
||||||
all_categories = set()
|
all_categories = set()
|
||||||
for module_data in self.modules.values():
|
for module_data in self.modules.values():
|
||||||
all_categories.update(module_data.get("category", ["No category"]))
|
all_categories.update(module_data.get("category", ["No category"]))
|
||||||
@@ -860,7 +979,7 @@ class Limoka(loader.Module):
|
|||||||
)
|
)
|
||||||
if cat in selected_categories:
|
if cat in selected_categories:
|
||||||
button_text = "✅ " + button_text
|
button_text = "✅ " + button_text
|
||||||
|
|
||||||
# Create new session with updated filters
|
# Create new session with updated filters
|
||||||
new_session = session.copy()
|
new_session = session.copy()
|
||||||
row.append(
|
row.append(
|
||||||
@@ -893,7 +1012,7 @@ class Limoka(loader.Module):
|
|||||||
):
|
):
|
||||||
query = session["query"]
|
query = session["query"]
|
||||||
current_filters = session["filters"]
|
current_filters = session["filters"]
|
||||||
|
|
||||||
new_filters = current_filters.copy()
|
new_filters = current_filters.copy()
|
||||||
selected_categories = new_filters.get("category", [])
|
selected_categories = new_filters.get("category", [])
|
||||||
if category in selected_categories:
|
if category in selected_categories:
|
||||||
@@ -921,7 +1040,7 @@ class Limoka(loader.Module):
|
|||||||
):
|
):
|
||||||
query = session["query"]
|
query = session["query"]
|
||||||
filters = session["filters"]
|
filters = session["filters"]
|
||||||
|
|
||||||
searcher = Search(query.lower(), self.ix)
|
searcher = Search(query.lower(), self.ix)
|
||||||
try:
|
try:
|
||||||
result = searcher.search_module()
|
result = searcher.search_module()
|
||||||
@@ -983,7 +1102,7 @@ class Limoka(loader.Module):
|
|||||||
return
|
return
|
||||||
module_path = filtered_result[0]
|
module_path = filtered_result[0]
|
||||||
module_info = self.modules[module_path]
|
module_info = self.modules[module_path]
|
||||||
|
|
||||||
# Create session for displaying module
|
# Create session for displaying module
|
||||||
display_session = self._create_search_session(
|
display_session = self._create_search_session(
|
||||||
state=self.SEARCH_STATES["global_search"],
|
state=self.SEARCH_STATES["global_search"],
|
||||||
@@ -992,9 +1111,7 @@ class Limoka(loader.Module):
|
|||||||
results=filtered_result,
|
results=filtered_result,
|
||||||
current_index=0,
|
current_index=0,
|
||||||
)
|
)
|
||||||
await self._display_module(
|
await self._display_module(call, module_info, module_path, display_session, 0)
|
||||||
call, module_info, module_path, display_session, 0
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _enter_query_handler(
|
async def _enter_query_handler(
|
||||||
self, call_or_query, query: Optional[str] = None, *args, **kwargs
|
self, call_or_query, query: Optional[str] = None, *args, **kwargs
|
||||||
@@ -1074,7 +1191,7 @@ class Limoka(loader.Module):
|
|||||||
return
|
return
|
||||||
module_path = result[0]
|
module_path = result[0]
|
||||||
module_info = self.modules[module_path]
|
module_info = self.modules[module_path]
|
||||||
|
|
||||||
# Create session for displaying module
|
# Create session for displaying module
|
||||||
display_session = self._create_search_session(
|
display_session = self._create_search_session(
|
||||||
state=self.SEARCH_STATES["global_search"],
|
state=self.SEARCH_STATES["global_search"],
|
||||||
@@ -1098,11 +1215,13 @@ class Limoka(loader.Module):
|
|||||||
{
|
{
|
||||||
"text": self.strings["back_to_results"],
|
"text": self.strings["back_to_results"],
|
||||||
"callback": self._show_results,
|
"callback": self._show_results,
|
||||||
"args": (self._create_search_session(
|
"args": (
|
||||||
state=self.SEARCH_STATES["global_search"],
|
self._create_search_session(
|
||||||
query=query or "",
|
state=self.SEARCH_STATES["global_search"],
|
||||||
filters={},
|
query=query or "",
|
||||||
),),
|
filters={},
|
||||||
|
),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -1116,7 +1235,7 @@ class Limoka(loader.Module):
|
|||||||
|
|
||||||
async def _show_global_results(self, call: InlineCall, session: Dict[str, Any]):
|
async def _show_global_results(self, call: InlineCall, session: Dict[str, Any]):
|
||||||
query = session["query"]
|
query = session["query"]
|
||||||
|
|
||||||
searcher = Search(query.lower(), self.ix)
|
searcher = Search(query.lower(), self.ix)
|
||||||
try:
|
try:
|
||||||
result = searcher.search_module()
|
result = searcher.search_module()
|
||||||
@@ -1145,7 +1264,7 @@ class Limoka(loader.Module):
|
|||||||
if not info:
|
if not info:
|
||||||
continue
|
continue
|
||||||
name = info.get("name", "Unknown")
|
name = info.get("name", "Unknown")
|
||||||
|
|
||||||
global_session = self._create_search_session(
|
global_session = self._create_search_session(
|
||||||
state=self.SEARCH_STATES["global_search"],
|
state=self.SEARCH_STATES["global_search"],
|
||||||
query=query,
|
query=query,
|
||||||
@@ -1171,47 +1290,37 @@ class Limoka(loader.Module):
|
|||||||
self, call: InlineCall, module_path: str, session: Dict[str, Any]
|
self, call: InlineCall, module_path: str, session: Dict[str, Any]
|
||||||
):
|
):
|
||||||
module_info = self.modules[module_path]
|
module_info = self.modules[module_path]
|
||||||
await self._display_module(
|
await self._display_module(call, module_info, module_path, session, 0)
|
||||||
call, module_info, module_path, session, 0
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _next_page(
|
async def _next_page(self, call: InlineCall, session: Dict[str, Any]):
|
||||||
self, call: InlineCall, session: Dict[str, Any]
|
|
||||||
):
|
|
||||||
result = session["results"]
|
result = session["results"]
|
||||||
index = session["current_index"]
|
index = session["current_index"]
|
||||||
|
|
||||||
if index + 1 >= len(result):
|
if index + 1 >= len(result):
|
||||||
await call.answer(self.strings["last_page"])
|
await call.answer(self.strings["last_page"])
|
||||||
return
|
return
|
||||||
index += 1
|
index += 1
|
||||||
module_path = result[index]
|
module_path = result[index]
|
||||||
module_info = self.modules[module_path]
|
module_info = self.modules[module_path]
|
||||||
|
|
||||||
new_session = session.copy()
|
new_session = session.copy()
|
||||||
new_session["current_index"] = index
|
new_session["current_index"] = index
|
||||||
await self._display_module(
|
await self._display_module(call, module_info, module_path, new_session, 0)
|
||||||
call, module_info, module_path, new_session, 0
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _previous_page(
|
async def _previous_page(self, call: InlineCall, session: Dict[str, Any]):
|
||||||
self, call: InlineCall, session: Dict[str, Any]
|
|
||||||
):
|
|
||||||
result = session["results"]
|
result = session["results"]
|
||||||
index = session["current_index"]
|
index = session["current_index"]
|
||||||
|
|
||||||
if index - 1 < 0:
|
if index - 1 < 0:
|
||||||
await call.answer(self.strings["first_page"])
|
await call.answer(self.strings["first_page"])
|
||||||
return
|
return
|
||||||
index -= 1
|
index -= 1
|
||||||
module_path = result[index]
|
module_path = result[index]
|
||||||
module_info = self.modules[module_path]
|
module_info = self.modules[module_path]
|
||||||
|
|
||||||
new_session = session.copy()
|
new_session = session.copy()
|
||||||
new_session["current_index"] = index
|
new_session["current_index"] = index
|
||||||
await self._display_module(
|
await self._display_module(call, module_info, module_path, new_session, 0)
|
||||||
call, module_info, module_path, new_session, 0
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _inline_void(self, call: InlineCall):
|
async def _inline_void(self, call: InlineCall):
|
||||||
await call.answer()
|
await call.answer()
|
||||||
@@ -1244,7 +1353,7 @@ class Limoka(loader.Module):
|
|||||||
text=self.strings["start_search_form"],
|
text=self.strings["start_search_form"],
|
||||||
message=message,
|
message=message,
|
||||||
reply_markup=markup,
|
reply_markup=markup,
|
||||||
photo=self._get_banner_for_state("global_search")
|
photo=self._get_banner_for_state("global_search"),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
history = self.get("history", [])
|
history = self.get("history", [])
|
||||||
@@ -1269,7 +1378,7 @@ class Limoka(loader.Module):
|
|||||||
return await utils.answer(message, self.strings["404"].format(query=args))
|
return await utils.answer(message, self.strings["404"].format(query=args))
|
||||||
module_path = result[0]
|
module_path = result[0]
|
||||||
module_info = self.modules[module_path]
|
module_info = self.modules[module_path]
|
||||||
|
|
||||||
# Create session for displaying module
|
# Create session for displaying module
|
||||||
display_session = self._create_search_session(
|
display_session = self._create_search_session(
|
||||||
state=self.SEARCH_STATES["global_search"],
|
state=self.SEARCH_STATES["global_search"],
|
||||||
@@ -1278,7 +1387,9 @@ class Limoka(loader.Module):
|
|||||||
results=result,
|
results=result,
|
||||||
current_index=0,
|
current_index=0,
|
||||||
)
|
)
|
||||||
await self._display_module(message, module_info, module_path, display_session, 0)
|
await self._display_module(
|
||||||
|
message, module_info, module_path, display_session, 0
|
||||||
|
)
|
||||||
|
|
||||||
async def _show_global_form(self, call: InlineCall, message: Message):
|
async def _show_global_form(self, call: InlineCall, message: Message):
|
||||||
markup = [
|
markup = [
|
||||||
@@ -1309,12 +1420,12 @@ class Limoka(loader.Module):
|
|||||||
self, call: InlineCall, query: str, message: Message, *args, **kwargs
|
self, call: InlineCall, query: str, message: Message, *args, **kwargs
|
||||||
):
|
):
|
||||||
global_session = self._create_search_session(
|
global_session = self._create_search_session(
|
||||||
state=self.SEARCH_STATES["global_search"],
|
state=self.SEARCH_STATES["global_search"],
|
||||||
query=query,
|
query=query,
|
||||||
filters={},
|
filters={},
|
||||||
results=[],
|
results=[],
|
||||||
current_index=0,
|
current_index=0,
|
||||||
) # idk what is that crap but it works lol
|
) # idk what is that crap but it works lol
|
||||||
if len(query) <= 1:
|
if len(query) <= 1:
|
||||||
await call.edit(
|
await call.edit(
|
||||||
self.strings["?"],
|
self.strings["?"],
|
||||||
@@ -1442,16 +1553,20 @@ class Limoka(loader.Module):
|
|||||||
elif hasattr(message.from_id, "channel_id"):
|
elif hasattr(message.from_id, "channel_id"):
|
||||||
sender_id = message.from_id.channel_id
|
sender_id = message.from_id.channel_id
|
||||||
if sender_id != self._service_bot_id:
|
if sender_id != self._service_bot_id:
|
||||||
logger.debug("Message not from official bot, ignoring")
|
# logger.debug("Message not from official bot, ignoring")
|
||||||
return
|
return
|
||||||
if not self.config["external_install_allowed"]:
|
if not self.config["external_install_allowed"]:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
clean_text = getattr(message, "raw_text", None) or getattr(
|
clean_text = (
|
||||||
message, "message", None
|
getattr(message, "raw_text", None)
|
||||||
) or message.text or ""
|
or getattr(message, "message", None)
|
||||||
|
or message.text
|
||||||
|
or ""
|
||||||
|
)
|
||||||
if message.entities:
|
if message.entities:
|
||||||
from html import unescape
|
from html import unescape
|
||||||
|
|
||||||
clean_text = unescape(clean_text)
|
clean_text = unescape(clean_text)
|
||||||
clean_text = re.sub(r"<[^>]+>", "", clean_text)
|
clean_text = re.sub(r"<[^>]+>", "", clean_text)
|
||||||
match = re.search(r"#limoka:([^\s\"'<>]+)", clean_text)
|
match = re.search(r"#limoka:([^\s\"'<>]+)", clean_text)
|
||||||
@@ -1480,25 +1595,37 @@ class Limoka(loader.Module):
|
|||||||
if not found:
|
if not found:
|
||||||
logger.warning(f"Module not found after cleanup: {module_path}")
|
logger.warning(f"Module not found after cleanup: {module_path}")
|
||||||
await utils.answer(
|
await utils.answer(
|
||||||
message, self.strings["watcher_module_not_found"].format(path=html.escape(module_path))
|
message,
|
||||||
|
self.strings["watcher_module_not_found"].format(
|
||||||
|
path=html.escape(module_path)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
import base64
|
import base64
|
||||||
from cryptography.hazmat.primitives.asymmetric import ed25519
|
from cryptography.hazmat.primitives.asymmetric import ed25519
|
||||||
PUB_KEY_B64 = "MCowBQYDK2VwAyEA1ltSnqtf3pGBuctuAYqHivCXsaRtKOVxavai7yin7ZE="
|
|
||||||
|
PUB_KEY_B64 = (
|
||||||
|
"MCowBQYDK2VwAyEA1ltSnqtf3pGBuctuAYqHivCXsaRtKOVxavai7yin7ZE="
|
||||||
|
)
|
||||||
der_bytes = base64.b64decode(PUB_KEY_B64)
|
der_bytes = base64.b64decode(PUB_KEY_B64)
|
||||||
raw_pubkey = der_bytes[-32:]
|
raw_pubkey = der_bytes[-32:]
|
||||||
module_url = self.config["limokaurl"] + module_path
|
module_url = self.config["limokaurl"] + module_path
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(module_url, timeout=10) as resp:
|
async with session.get(module_url, timeout=10) as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
logger.error(f"Failed to fetch module for verification: {module_url} (HTTP {resp.status})")
|
logger.error(
|
||||||
await utils.answer(message, self.strings["watcher_loader_missing"])
|
f"Failed to fetch module for verification: {module_url} (HTTP {resp.status})"
|
||||||
|
)
|
||||||
|
await utils.answer(
|
||||||
|
message, self.strings["watcher_loader_missing"]
|
||||||
|
)
|
||||||
return
|
return
|
||||||
module_bytes = await resp.read()
|
module_bytes = await resp.read()
|
||||||
sha256 = hashlib.sha256(module_bytes).hexdigest()
|
sha256 = hashlib.sha256(module_bytes).hexdigest()
|
||||||
public_key = ed25519.Ed25519PublicKey.from_public_bytes(raw_pubkey)
|
public_key = ed25519.Ed25519PublicKey.from_public_bytes(
|
||||||
|
raw_pubkey
|
||||||
|
)
|
||||||
signature = bytes.fromhex(signature_hex)
|
signature = bytes.fromhex(signature_hex)
|
||||||
signed_payload = f"{module_path}|{sha256}".encode()
|
signed_payload = f"{module_path}|{sha256}".encode()
|
||||||
public_key.verify(signature, signed_payload)
|
public_key.verify(signature, signed_payload)
|
||||||
@@ -1522,21 +1649,27 @@ class Limoka(loader.Module):
|
|||||||
if status:
|
if status:
|
||||||
try:
|
try:
|
||||||
bot_peer = await self.client.get_entity(self._service_bot_id)
|
bot_peer = await self.client.get_entity(self._service_bot_id)
|
||||||
await self.client.send_message(bot_peer, f"#limoka:sucsess:{message.id}")
|
await self.client.send_message(
|
||||||
|
bot_peer, f"#limoka:sucsess:{message.id}"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to send success confirmation: {e}")
|
logger.error(f"Failed to send success confirmation: {e}")
|
||||||
else:
|
else:
|
||||||
logger.error(f"Installation failed with status: {status}")
|
logger.error(f"Installation failed with status: {status}")
|
||||||
try:
|
try:
|
||||||
bot_peer = await self.client.get_entity(self._service_bot_id)
|
bot_peer = await self.client.get_entity(self._service_bot_id)
|
||||||
await self.client.send_message(bot_peer, f"#limoka:failed:{message.id}")
|
await self.client.send_message(
|
||||||
|
bot_peer, f"#limoka:failed:{message.id}"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to send failure notification: {e}")
|
logger.error(f"Failed to send failure notification: {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"CRITICAL ERROR in secure_install_watcher: {e}")
|
logger.exception(f"CRITICAL ERROR in secure_install_watcher: {e}")
|
||||||
try:
|
try:
|
||||||
await utils.answer(message, self.strings["watcher_critical"].format(error=str(e)[:100]))
|
await utils.answer(
|
||||||
|
message, self.strings["watcher_critical"].format(error=str(e)[:100])
|
||||||
|
)
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
await message.delete()
|
await message.delete()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|||||||
Reference in New Issue
Block a user