mirror of
https://github.com/MuRuLOSE/limoka.git
synced 2026-06-16 22:34:19 +02:00
feat: now supports emojies in inline + little refactor
This commit is contained in:
671
Limoka.py
671
Limoka.py
@@ -33,7 +33,7 @@ from .. import utils, loader
|
|||||||
from ..types import InlineCall
|
from ..types import InlineCall
|
||||||
|
|
||||||
logger = logging.getLogger("Limoka")
|
logger = logging.getLogger("Limoka")
|
||||||
__version__ = (1, 4, 4)
|
__version__ = (1, 4, 5)
|
||||||
|
|
||||||
WEIGHTS = {
|
WEIGHTS = {
|
||||||
"inline.token_obtainment": 15,
|
"inline.token_obtainment": 15,
|
||||||
@@ -54,17 +54,25 @@ def _get_lang_value(data: Dict[str, Any], lang: str) -> str:
|
|||||||
return data.get(lang, data.get("default", data.get("en", "")))
|
return data.get(lang, data.get("default", data.get("en", "")))
|
||||||
|
|
||||||
|
|
||||||
class Search:
|
class SearchIndex:
|
||||||
def __init__(self, query, ix):
|
"""Handles full-text search operations."""
|
||||||
|
|
||||||
|
def __init__(self, query: str, index):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
query: Search query string
|
||||||
|
index: Whoosh index instance
|
||||||
|
"""
|
||||||
self.schema = Schema(
|
self.schema = Schema(
|
||||||
title=TEXT(stored=True), path=ID(stored=True), content=TEXT(stored=True)
|
title=TEXT(stored=True), path=ID(stored=True), content=TEXT(stored=True)
|
||||||
)
|
)
|
||||||
self.query = query
|
self.query = query
|
||||||
self.ix = ix
|
self.index = index
|
||||||
|
|
||||||
def search_module(self):
|
def search(self) -> List[str]:
|
||||||
with self.ix.searcher() as searcher:
|
"""Execute search and return list of module paths."""
|
||||||
parser = QueryParser("content", self.ix.schema, group=OrGroup.factory(0.8))
|
with self.index.searcher() as searcher:
|
||||||
|
parser = QueryParser("content", self.index.schema, group=OrGroup.factory(0.8))
|
||||||
query = parser.parse(self.query)
|
query = parser.parse(self.query)
|
||||||
wildcard_query = Wildcard("content", f"*{self.query}*")
|
wildcard_query = Wildcard("content", f"*{self.query}*")
|
||||||
fuzzy_query = FuzzyTerm("content", self.query, maxdist=2, prefixlength=1)
|
fuzzy_query = FuzzyTerm("content", self.query, maxdist=2, prefixlength=1)
|
||||||
@@ -75,12 +83,257 @@ class Search:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
class LimokaAPI:
|
class APIClient:
|
||||||
async def fetch_json(self, base_url, path):
|
"""Handles HTTP requests for fetching JSON data."""
|
||||||
url = f"{base_url}{path}"
|
|
||||||
|
async def fetch_json(self, base_url: str, path: str) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Fetch JSON data from a URL.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
base_url: Base URL for the API
|
||||||
|
path: Path to append to base URL
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Parsed JSON as dictionary
|
||||||
|
"""
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(url) as response:
|
async with session.get(f"{base_url}{path}") as resp:
|
||||||
return json.loads(await response.text())
|
if resp.status == 200:
|
||||||
|
return json.loads(await resp.text())
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleRepository:
|
||||||
|
"""Manages module data and filtering."""
|
||||||
|
|
||||||
|
def __init__(self, modules: Dict[str, Any], repositories: Dict[str, Any]):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
modules: Dictionary of module definitions
|
||||||
|
repositories: Dictionary of repository metadata
|
||||||
|
"""
|
||||||
|
self.modules = modules
|
||||||
|
self.repositories = repositories
|
||||||
|
|
||||||
|
def apply_newbie_filter(self, filter_enabled: bool) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Filter out modules from repositories tagged as 'newbie'.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filter_enabled: Whether filtering is enabled
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Filtered modules dictionary
|
||||||
|
"""
|
||||||
|
if not filter_enabled:
|
||||||
|
return self.modules
|
||||||
|
|
||||||
|
filtered = {}
|
||||||
|
for path, info in self.modules.items():
|
||||||
|
repo_key = "/".join(path.split("/")[:2]) if "/" in path else path
|
||||||
|
repo = self.repositories.get(repo_key)
|
||||||
|
tags = repo.get("tags", []) if repo else []
|
||||||
|
if "newbie" not in tags:
|
||||||
|
filtered[path] = info
|
||||||
|
return filtered
|
||||||
|
|
||||||
|
def get_tags_for_module(self, module_path: str) -> List[str]:
|
||||||
|
"""Get repository tags for a specific module."""
|
||||||
|
repo_key = "/".join(module_path.split("/")[:2]) if "/" in module_path else module_path
|
||||||
|
for repo_url in self.repositories:
|
||||||
|
if repo_url.replace("https://github.com/", "") == repo_key:
|
||||||
|
return self.repositories[repo_url].get("tags", [])
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_all_categories(self) -> List[str]:
|
||||||
|
"""Get all unique categories from modules."""
|
||||||
|
categories = set()
|
||||||
|
for module_data in self.modules.values():
|
||||||
|
categories.update(module_data.get("category", ["No category"]))
|
||||||
|
return sorted(categories)
|
||||||
|
|
||||||
|
|
||||||
|
class CommandFormatter:
|
||||||
|
"""Formats module commands and metadata."""
|
||||||
|
|
||||||
|
def __init__(self, strings: Dict[str, Any], bot_username: str, prefix: str):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
strings: Localized strings dictionary
|
||||||
|
bot_username: Bot username for inline handlers
|
||||||
|
prefix: Command prefix
|
||||||
|
"""
|
||||||
|
self.strings = strings
|
||||||
|
self.bot_username = bot_username
|
||||||
|
self.prefix = prefix
|
||||||
|
|
||||||
|
def format_commands(self, module_info: Dict[str, Any], lang: str = "en") -> List[str]:
|
||||||
|
"""
|
||||||
|
Format module commands and handlers into display strings.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
module_info: Module information dictionary
|
||||||
|
lang: Language code
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of formatted command strings
|
||||||
|
"""
|
||||||
|
commands = []
|
||||||
|
for i, cmd in enumerate(module_info.get("new_commands", []), 1):
|
||||||
|
name = cmd.get("name", "")
|
||||||
|
desc_map = cmd.get("description", {})
|
||||||
|
emoji = self._get_emoji_for_number(i)
|
||||||
|
desc = _get_lang_value(desc_map, lang) or self.strings["no_info"]
|
||||||
|
commands.append(
|
||||||
|
self.strings["command_template"].format(
|
||||||
|
prefix=self.prefix,
|
||||||
|
command=html.escape(name),
|
||||||
|
emoji=emoji,
|
||||||
|
description=html.escape(desc),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for handler in module_info.get("inline_handlers", []):
|
||||||
|
name = handler.get("name", "")
|
||||||
|
desc_map = handler.get("description", {})
|
||||||
|
desc = _get_lang_value(desc_map, lang) or self.strings["no_info"]
|
||||||
|
commands.append(
|
||||||
|
self.strings["inline_handler_template"].format(
|
||||||
|
inline_bot=self.bot_username,
|
||||||
|
command=html.escape(name),
|
||||||
|
description=html.escape(desc),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return commands
|
||||||
|
|
||||||
|
def _get_emoji_for_number(self, num: int) -> str:
|
||||||
|
"""Get emoji representation for a number."""
|
||||||
|
if num <= 10:
|
||||||
|
return self.strings["emojis"].get(num, "")
|
||||||
|
emoji = ""
|
||||||
|
for digit in str(num):
|
||||||
|
emoji += self.strings["emojis"].get(int(digit), "")
|
||||||
|
return emoji
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleContentBuilder:
|
||||||
|
"""Builds formatted module content for display."""
|
||||||
|
|
||||||
|
def __init__(self, strings: Dict[str, Any], formatter: CommandFormatter, repository: ModuleRepository):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
strings: Localized strings dictionary
|
||||||
|
formatter: CommandFormatter instance
|
||||||
|
repository: ModuleRepository instance
|
||||||
|
"""
|
||||||
|
self.strings = strings
|
||||||
|
self.formatter = formatter
|
||||||
|
self.repository = repository
|
||||||
|
|
||||||
|
def build_content(
|
||||||
|
self,
|
||||||
|
module_info: Dict[str, Any],
|
||||||
|
query: str,
|
||||||
|
filters: Dict[str, List[str]],
|
||||||
|
include_categories: bool = True,
|
||||||
|
module_path: Optional[str] = None,
|
||||||
|
lang: str = "en",
|
||||||
|
) -> tuple:
|
||||||
|
"""
|
||||||
|
Build complete formatted module content.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (header, body_pages, footer, categories_text)
|
||||||
|
"""
|
||||||
|
name = html.escape(module_info.get("name") or self.strings["no_info"])
|
||||||
|
cls_doc = module_info.get("cls_doc", {})
|
||||||
|
description = html.escape(
|
||||||
|
_get_lang_value(cls_doc, lang)
|
||||||
|
or _get_lang_value(module_info.get("description", ""), lang)
|
||||||
|
or self.strings["no_info"]
|
||||||
|
)
|
||||||
|
dev_username = html.escape(module_info["meta"].get("developer") or "Unknown")
|
||||||
|
|
||||||
|
if len(description) > 300:
|
||||||
|
description = description[:297] + "…"
|
||||||
|
|
||||||
|
categories_text = self._build_categories_text(filters)
|
||||||
|
commands = self.formatter.format_commands(module_info, lang)
|
||||||
|
header = self._build_header(query, name, description, dev_username, module_path)
|
||||||
|
footer = self._build_footer(module_path)
|
||||||
|
body_pages = self._paginate_commands(commands)
|
||||||
|
|
||||||
|
return header, body_pages, footer, categories_text
|
||||||
|
|
||||||
|
def _build_header(self, query: str, name: str, description: str, dev_username: str, module_path: Optional[str]) -> str:
|
||||||
|
"""Build message header with module info and tags."""
|
||||||
|
tags_list = self.repository.get_tags_for_module(module_path) if module_path else []
|
||||||
|
tags_text = ", ".join(self.strings["tags"].get(tag, tag) for tag in tags_list)
|
||||||
|
|
||||||
|
header_template = self.strings["found_header"]
|
||||||
|
if not tags_text:
|
||||||
|
header_template = header_template.replace(
|
||||||
|
"<blockquote><b><tg-emoji emoji-id=5418376169055602355>🏷</tg-emoji> Tags:</b> {tags}</blockquote>\n\n",
|
||||||
|
""
|
||||||
|
)
|
||||||
|
header_template = header_template.replace(
|
||||||
|
"<blockquote><b><tg-emoji emoji-id=5418376169055602355>🏷</tg-emoji> Теги:</b> {tags}</blockquote>\n\n",
|
||||||
|
""
|
||||||
|
)
|
||||||
|
|
||||||
|
return header_template.format(
|
||||||
|
query=html.escape(query),
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
username=dev_username,
|
||||||
|
tags=tags_text,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _build_footer(self, module_path: Optional[str]) -> str:
|
||||||
|
"""Build message footer with download command."""
|
||||||
|
clean_path = (module_path or "").replace("\\", "/")
|
||||||
|
return self.strings["found_footer"].format(
|
||||||
|
url=html.escape(self.formatter.strings.get("limokaurl", "")),
|
||||||
|
module_path=html.escape(clean_path),
|
||||||
|
prefix=self.formatter.prefix,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _build_categories_text(self, filters: Dict[str, List[str]]) -> str:
|
||||||
|
"""Build categories text if any are selected."""
|
||||||
|
categories = filters.get("category", [])
|
||||||
|
if categories:
|
||||||
|
return "\n" + self.strings["selected_categories"].format(
|
||||||
|
categories=", ".join(html.escape(c) for c in categories)
|
||||||
|
)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _paginate_commands(self, commands: List[str], max_length: int = 500) -> List[str]:
|
||||||
|
"""Split commands into pages based on length."""
|
||||||
|
if not commands:
|
||||||
|
return [""]
|
||||||
|
|
||||||
|
commands_text = "".join(commands)
|
||||||
|
if len(commands_text) <= max_length:
|
||||||
|
return [commands_text]
|
||||||
|
|
||||||
|
pages = []
|
||||||
|
current_page = []
|
||||||
|
current_length = 0
|
||||||
|
|
||||||
|
for cmd in commands:
|
||||||
|
if current_length + len(cmd) > max_length:
|
||||||
|
if current_page:
|
||||||
|
pages.append("".join(current_page))
|
||||||
|
current_page = []
|
||||||
|
current_length = 0
|
||||||
|
current_page.append(cmd)
|
||||||
|
current_length += len(cmd)
|
||||||
|
|
||||||
|
if current_page:
|
||||||
|
pages.append("".join(current_page))
|
||||||
|
|
||||||
|
return pages or [""]
|
||||||
|
|
||||||
|
|
||||||
@loader.tds
|
@loader.tds
|
||||||
@@ -91,53 +344,53 @@ class Limoka(loader.Module):
|
|||||||
"name": "Limoka",
|
"name": "Limoka",
|
||||||
"wait": (
|
"wait": (
|
||||||
"<blockquote>Just wait\n"
|
"<blockquote>Just wait\n"
|
||||||
"<emoji document_id=5404630946563515782>🔍</emoji> A search is underway among {count} modules "
|
"<tg-emoji emoji-id=5404630946563515782>🔍</tg-emoji> A search is underway among {count} modules "
|
||||||
"for the query: <code>{query}</code>\n"
|
"for the query: <code>{query}</code>\n"
|
||||||
"<i>{fact}</i></blockquote>"
|
"<i>{fact}</i></blockquote>"
|
||||||
),
|
),
|
||||||
"found_header": (
|
"found_header": (
|
||||||
"<blockquote><emoji document_id=5413334818047940135>🔍</emoji> Found module <b>{name}</b> "
|
"<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> Found module <b>{name}</b> "
|
||||||
"by query: <b>{query}</b>\n\n"
|
"by query: <b>{query}</b>\n\n"
|
||||||
"<b><emoji document_id=5418376169055602355>ℹ️</emoji> Description:</b> {description}\n"
|
"<b><tg-emoji emoji-id=5418376169055602355>ℹ️</tg-emoji> Description:</b> {description}\n"
|
||||||
"<b><emoji document_id=5418299289141004396>🧑💻</emoji> Developer:</b> {username}\n\n"
|
"<b><tg-emoji emoji-id=5418299289141004396>🧑💻</tg-emoji> Developer:</b> {username}\n\n"
|
||||||
"<blockquote><b><emoji document_id=5418376169055602355>🏷</emoji> Tags:</b> {tags}</blockquote>\n\n"
|
"<blockquote><b><tg-emoji emoji-id=5418376169055602355>🏷</tg-emoji> Tags:</b> {tags}</blockquote>\n\n"
|
||||||
),
|
),
|
||||||
"found_body": ("{commands}"),
|
"found_body": ("{commands}"),
|
||||||
"found_footer": (
|
"found_footer": (
|
||||||
"<blockquote>\n<emoji document_id=5411143117711624172>🪄</emoji> <code>{prefix}dlm {url}{module_path}</code></blockquote>"
|
"<blockquote>\n<tg-emoji emoji-id=5411143117711624172>🪄</tg-emoji> <code>{prefix}dlm {url}{module_path}</code></blockquote>"
|
||||||
),
|
),
|
||||||
"caption_short": (
|
"caption_short": (
|
||||||
"<blockquote><emoji document_id=5413334818047940135>🔍</emoji> <b>{safe_name}</b>\n"
|
"<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> <b>{safe_name}</b>\n"
|
||||||
"<b><emoji document_id=5418376169055602355>ℹ️</emoji> Description:</b> {safe_desc}\n"
|
"<b><tg-emoji emoji-id=5418376169055602355>ℹ️</tg-emoji> Description:</b> {safe_desc}\n"
|
||||||
"<b><emoji document_id=5418299289141004396>🧑💻</emoji> Dev:</b> {dev_username}\n"
|
"<b><tg-emoji emoji-id=5418299289141004396>🧑💻</tg-emoji> Dev:</b> {dev_username}\n"
|
||||||
"<emoji document_id=5411143117711624172>🪄</emoji> <code>{prefix}dlm {module_path}</code></blockquote>"
|
"<tg-emoji emoji-id=5411143117711624172>🪄</tg-emoji> <code>{prefix}dlm {module_path}</code></blockquote>"
|
||||||
),
|
),
|
||||||
"command_template": "{emoji} <code>{prefix}{command}</code> — {description}\n",
|
"command_template": "{emoji} <code>{prefix}{command}</code> — {description}\n",
|
||||||
"inline_handler_template": "{inline_bot} {command} — {description}\n",
|
"inline_handler_template": "{inline_bot} {command} — {description}\n",
|
||||||
"emojis": {
|
"emojis": {
|
||||||
1: "<emoji document_id=5416037945909987712>1️⃣</emoji>",
|
1: "<tg-emoji emoji-id=5416037945909987712>1️⃣</tg-emoji>",
|
||||||
2: "<emoji document_id=5413855071731470617>2️⃣</emoji>",
|
2: "<tg-emoji emoji-id=5413855071731470617>2️⃣</tg-emoji>",
|
||||||
3: "<emoji document_id=5416068826724850291>3️⃣</emoji>",
|
3: "<tg-emoji emoji-id=5416068826724850291>3️⃣</tg-emoji>",
|
||||||
4: "<emoji document_id=5415843998071803071>4️⃣</emoji>",
|
4: "<tg-emoji emoji-id=5415843998071803071>4️⃣</tg-emoji>",
|
||||||
5: "<emoji document_id=5415684843763686989>5️⃣</emoji>",
|
5: "<tg-emoji emoji-id=5415684843763686989>5️⃣</tg-emoji>",
|
||||||
6: "<emoji document_id=5415975458430796879>6️⃣</emoji>",
|
6: "<tg-emoji emoji-id=5415975458430796879>6️⃣</tg-emoji>",
|
||||||
7: "<emoji document_id=5415769763857060166>7️⃣</emoji>",
|
7: "<tg-emoji emoji-id=5415769763857060166>7️⃣</tg-emoji>",
|
||||||
8: "<emoji document_id=5416006506749383505>8️⃣</emoji>",
|
8: "<tg-emoji emoji-id=5416006506749383505>8️⃣</tg-emoji>",
|
||||||
9: "<emoji document_id=5415963015910544694>9️⃣</emoji>",
|
9: "<tg-emoji emoji-id=5415963015910544694>9️⃣</tg-emoji>",
|
||||||
},
|
},
|
||||||
"404": "<blockquote><emoji document_id=5210952531676504517>❌</emoji> <b>Not found by query: <i>{query}</i></b></blockquote>",
|
"404": "<blockquote><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> <b>Not found by query: <i>{query}</i></b></blockquote>",
|
||||||
"noargs": "<blockquote><emoji document_id=5210952531676504517>❌</emoji> <b>No args</b></blockquote>",
|
"noargs": "<blockquote><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> <b>No args</b></blockquote>",
|
||||||
"?": "<blockquote><emoji document_id=5951895176908640647>🔎</emoji> Request too short / not found</blockquote>",
|
"?": "<blockquote><tg-emoji emoji-id=5951895176908640647>🔎</tg-emoji> Request too short / not found</blockquote>",
|
||||||
"no_info": "<blockquote>No information</blockquote>",
|
"no_info": "<blockquote>No information</blockquote>",
|
||||||
"facts": [
|
"facts": [
|
||||||
"<blockquote><emoji document_id=5472193350520021357>🛡</emoji> The limoka catalog is carefully moderated!</blockquote>",
|
"<blockquote><tg-emoji emoji-id=5472193350520021357>🛡</tg-emoji> The limoka catalog is carefully moderated!</blockquote>",
|
||||||
"<blockquote><emoji document_id=5940434198413184876>🚀</emoji> Limoka performance allows you to search for modules quickly!</blockquote>",
|
"<blockquote><tg-emoji emoji-id=5940434198413184876>🚀</tg-emoji> Limoka performance allows you to search for modules quickly!</blockquote>",
|
||||||
],
|
],
|
||||||
"inline404": "<blockquote>Not found</blockquote>",
|
"inline404": "<blockquote>Not found</blockquote>",
|
||||||
"inline?": "<blockquote>Request too short / not found</blockquote>",
|
"inline?": "<blockquote>Request too short / not found</blockquote>",
|
||||||
"inlinenoargs": "<blockquote>Please, enter query</blockquote>",
|
"inlinenoargs": "<blockquote>Please, enter query</blockquote>",
|
||||||
"history": (
|
"history": (
|
||||||
"<blockquote><emoji document_id=5879939498149679716>🔎</emoji> <b>Your search history:</b>\n"
|
"<blockquote><tg-emoji emoji-id=5879939498149679716>🔎</tg-emoji> <b>Your search history:</b>\n"
|
||||||
"{history}</blockquote>"
|
"{history}</blockquote>"
|
||||||
),
|
),
|
||||||
"filter_menu": "Choose filters",
|
"filter_menu": "Choose filters",
|
||||||
@@ -145,9 +398,9 @@ class Limoka(loader.Module):
|
|||||||
"apply_filters": "✅ Apply Filters",
|
"apply_filters": "✅ Apply Filters",
|
||||||
"clear_filters": "🗑 Clear Filters",
|
"clear_filters": "🗑 Clear Filters",
|
||||||
"back_to_results": "🔙 Back to Results",
|
"back_to_results": "🔙 Back to Results",
|
||||||
"empty_history": "<blockquote><emoji document_id=5879939498149679716>🔎</emoji> <b>Your search history is empty!</b></blockquote>",
|
"empty_history": "<blockquote><tg-emoji emoji-id=5879939498149679716>🔎</tg-emoji> <b>Your search history is empty!</b></blockquote>",
|
||||||
"enter_query": "🔍 Enter new search query:",
|
"enter_query": "🔍 Enter new search query:",
|
||||||
"global_search": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> Global search for <b>{query}</b> — found <b>{count}</b> modules</blockquote>",
|
"global_search": "<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> Global search for <b>{query}</b> — found <b>{count}</b> modules</blockquote>",
|
||||||
"change_query": "🔍 Change query",
|
"change_query": "🔍 Change query",
|
||||||
"no_modules": "<blockquote>No modules available.</blockquote>",
|
"no_modules": "<blockquote>No modules available.</blockquote>",
|
||||||
"filter_title": "🏷 Filters",
|
"filter_title": "🏷 Filters",
|
||||||
@@ -166,15 +419,15 @@ class Limoka(loader.Module):
|
|||||||
"inline_short_query": "<blockquote>❌ Query too short (min 2 chars)</blockquote>",
|
"inline_short_query": "<blockquote>❌ Query too short (min 2 chars)</blockquote>",
|
||||||
"inline_switch_pm": "💬 Open in chat",
|
"inline_switch_pm": "💬 Open in chat",
|
||||||
"inline_switch_pm_text": "🔍 Results for: {query}",
|
"inline_switch_pm_text": "🔍 Results for: {query}",
|
||||||
"inline_start_message": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> <b>Limoka Search</b>\nType module name or keyword</blockquote>",
|
"inline_start_message": "<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> <b>Limoka Search</b>\nType module name or keyword</blockquote>",
|
||||||
"first_page": "<blockquote>This is the first page!</blockquote>",
|
"first_page": "<blockquote>This is the first page!</blockquote>",
|
||||||
"last_page": "<blockquote>This is the last page!</blockquote>",
|
"last_page": "<blockquote>This is the last page!</blockquote>",
|
||||||
"display_error": "<blockquote>Error displaying module. Please try again.</blockquote>",
|
"display_error": "<blockquote>Error displaying module. Please try again.</blockquote>",
|
||||||
"error_occurred": "<blockquote>An error occurred. Please try again.</blockquote>",
|
"error_occurred": "<blockquote>An error occurred. Please try again.</blockquote>",
|
||||||
"start_search_form": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> <b>Limoka Search</b>\nEnter your query to search for modules:</blockquote>",
|
"start_search_form": "<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> <b>Limoka Search</b>\nEnter your query to search for modules:</blockquote>",
|
||||||
"global_search_form": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> <b>Global Search</b>\nEnter your query to search ALL modules without filters:</blockquote>",
|
"global_search_form": "<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> <b>Global Search</b>\nEnter your query to search ALL modules without filters:</blockquote>",
|
||||||
"history_cleared": "<blockquote><emoji document_id=5427009710268689068>🧹</emoji> <b>Search history cleared!</b></blockquote>",
|
"history_cleared": "<blockquote><tg-emoji emoji-id=5427009710268689068>🧹</tg-emoji> <b>Search history cleared!</b></blockquote>",
|
||||||
"invalid_history_arg": "<blockquote><emoji document_id=5210952531676504517>❌</emoji> <b>Invalid argument for history command. Use:</b>\n<code>.lshistory</code> - show history\n<code>.lshistory clear</code> - clear history</blockquote>",
|
"invalid_history_arg": "<blockquote><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> <b>Invalid argument for history command. Use:</b>\n<code>.lshistory</code> - show history\n<code>.lshistory clear</code> - clear history</blockquote>",
|
||||||
"close": "❌ Close",
|
"close": "❌ Close",
|
||||||
"watcher_no_tag": "<blockquote>❌ Invalid message format. No #limoka tag found.</blockquote>",
|
"watcher_no_tag": "<blockquote>❌ Invalid message format. No #limoka tag found.</blockquote>",
|
||||||
"watcher_invalid_format": "<blockquote>❌ Invalid format. Expected: #limoka:path:signature</blockquote>",
|
"watcher_invalid_format": "<blockquote>❌ Invalid format. Expected: #limoka:path:signature</blockquote>",
|
||||||
@@ -197,30 +450,31 @@ class Limoka(loader.Module):
|
|||||||
"If error persists again, report to developers</blockquote>"
|
"If error persists again, report to developers</blockquote>"
|
||||||
),
|
),
|
||||||
"body_page": "Commands",
|
"body_page": "Commands",
|
||||||
|
"limokaurl": "https://raw.githubusercontent.com/MuRuLOSE/limoka/refs/heads/main/"
|
||||||
}
|
}
|
||||||
strings_ru = {
|
strings_ru = {
|
||||||
"name": "Limoka",
|
"name": "Limoka",
|
||||||
"wait": (
|
"wait": (
|
||||||
"<blockquote>Подождите\n"
|
"<blockquote>Подождите\n"
|
||||||
"<emoji document_id=5404630946563515782>🔍</emoji> Идёт поиск среди {count} модулей по запросу: <code>{query}</code>\n"
|
"<tg-emoji emoji-id=5404630946563515782>🔍</tg-emoji> Идёт поиск среди {count} модулей по запросу: <code>{query}</code>\n"
|
||||||
"<i>{fact}</i></blockquote>"
|
"<i>{fact}</i></blockquote>"
|
||||||
),
|
),
|
||||||
"found_header": (
|
"found_header": (
|
||||||
"<blockquote><emoji document_id=5413334818047940135>🔍</emoji> Найден модуль <b>{name}</b> "
|
"<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> Найден модуль <b>{name}</b> "
|
||||||
"по запросу: <b>{query}</b></blockquote>\n\n"
|
"по запросу: <b>{query}</b></blockquote>\n\n"
|
||||||
"<blockquote><b><emoji document_id=5418376169055602355>ℹ️</emoji> Описание:</b> {description}</blockquote>\n"
|
"<blockquote><b><tg-emoji emoji-id=5418376169055602355>ℹ️</tg-emoji> Описание:</b> {description}</blockquote>\n"
|
||||||
"<blockquote><b><emoji document_id=5418299289141004396>🧑💻</emoji> Разработчик:</b> {username}</blockquote>\n\n"
|
"<blockquote><b><tg-emoji emoji-id=5418299289141004396>🧑💻</tg-emoji> Разработчик:</b> {username}</blockquote>\n\n"
|
||||||
"<blockquote><b><emoji document_id=5418376169055602355>🏷</emoji> Теги:</b> {tags}</blockquote>\n\n"
|
"<blockquote><b><tg-emoji emoji-id=5418376169055602355>🏷</tg-emoji> Теги:</b> {tags}</blockquote>\n\n"
|
||||||
),
|
),
|
||||||
"found_body": ("{commands}"),
|
"found_body": ("{commands}"),
|
||||||
"found_footer": (
|
"found_footer": (
|
||||||
"\n<blockquote><emoji document_id=5411143117711624172>🪄</emoji> <code>{prefix}dlm {url}{module_path}</code></blockquote>"
|
"\n<blockquote><tg-emoji emoji-id=5411143117711624172>🪄</tg-emoji> <code>{prefix}dlm {url}{module_path}</code></blockquote>"
|
||||||
),
|
),
|
||||||
"caption_short": (
|
"caption_short": (
|
||||||
"<blockquote><emoji document_id=5413334818047940135>🔍</emoji> <b>{safe_name}</b>\n"
|
"<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> <b>{safe_name}</b>\n"
|
||||||
"<b><emoji document_id=5418376169055602355>ℹ️</emoji> Описание:</b> {safe_desc}\n"
|
"<b><tg-emoji emoji-id=5418376169055602355>ℹ️</tg-emoji> Описание:</b> {safe_desc}\n"
|
||||||
"<b><emoji document_id=5418299289141004396>🧑💻</emoji> Разработчик:</b> {dev_username}\n"
|
"<b><tg-emoji emoji-id=5418299289141004396>🧑💻</tg-emoji> Разработчик:</b> {dev_username}\n"
|
||||||
"<emoji document_id=5411143117711624172>🪄</emoji> <code>{prefix}dlm {module_path}</code></blockquote>"
|
"<tg-emoji emoji-id=5411143117711624172>🪄</tg-emoji> <code>{prefix}dlm {module_path}</code></blockquote>"
|
||||||
),
|
),
|
||||||
"command_template": "<blockquote>{emoji} <code>{prefix}{command}</code> — {description}</blockquote>\n",
|
"command_template": "<blockquote>{emoji} <code>{prefix}{command}</code> — {description}</blockquote>\n",
|
||||||
"inline_handler_template": "{inline_bot} {command} — {description}\n",
|
"inline_handler_template": "{inline_bot} {command} — {description}\n",
|
||||||
@@ -236,15 +490,15 @@ class Limoka(loader.Module):
|
|||||||
9: "<tg-emoji emoji-id=5415963015910544694>9️⃣</tg-emoji>",
|
9: "<tg-emoji emoji-id=5415963015910544694>9️⃣</tg-emoji>",
|
||||||
10: "<tg-emoji emoji-id=5415642160378696377>🔟</tg-emoji>"
|
10: "<tg-emoji emoji-id=5415642160378696377>🔟</tg-emoji>"
|
||||||
},
|
},
|
||||||
"404": "<blockquote><emoji document_id=5210952531676504517>❌</emoji> <b>Не найдено по запросу: <i>{query}</i></b></blockquote>",
|
"404": "<blockquote><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> <b>Не найдено по запросу: <i>{query}</i></b></blockquote>",
|
||||||
"noargs": "<blockquote><emoji document_id=5210952531676504517>❌</emoji> <b>Нет аргументов</b></blockquote>",
|
"noargs": "<blockquote><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> <b>Нет аргументов</b></blockquote>",
|
||||||
"?": "<blockquote><emoji document_id=5951895176908640647>🔎</emoji> Запрос слишком короткий / не найден</blockquote>",
|
"?": "<blockquote><tg-emoji emoji-id=5951895176908640647>🔎</tg-emoji> Запрос слишком короткий / не найден</blockquote>",
|
||||||
"no_info": "<blockquote>Нет информации</blockquote>",
|
"no_info": "<blockquote>Нет информации</blockquote>",
|
||||||
"facts": [
|
"facts": [
|
||||||
"<blockquote><emoji document_id=5472193350520021357>🛡</emoji> Каталог Limoka тщательно модерируется!</blockquote>",
|
"<blockquote><tg-emoji emoji-id=5472193350520021357>🛡</tg-emoji> Каталог Limoka тщательно модерируется!</blockquote>",
|
||||||
"<blockquote><emoji document_id=5940434198413184876>🚀</emoji> Limoka позволяет искать модули с невероятной скоростью!</blockquote>",
|
"<blockquote><tg-emoji emoji-id=5940434198413184876>🚀</tg-emoji> Limoka позволяет искать модули с невероятной скоростью!</blockquote>",
|
||||||
(
|
(
|
||||||
"<blockquote><emoji document_id=5188311512791393083>🔎</emoji> Limoka имеет лучший поиск*!\n"
|
"<blockquote><tg-emoji emoji-id=5188311512791393083>🔎</tg-emoji> Limoka имеет лучший поиск*!\n"
|
||||||
"<i>* В сравнении с предыдущей версией Limoka</i></blockquote>"
|
"<i>* В сравнении с предыдущей версией Limoka</i></blockquote>"
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -252,7 +506,7 @@ class Limoka(loader.Module):
|
|||||||
"inline?": "<blockquote>Запрос слишком короткий / не найден</blockquote>",
|
"inline?": "<blockquote>Запрос слишком короткий / не найден</blockquote>",
|
||||||
"inlinenoargs": "<blockquote>Введите запрос</blockquote>",
|
"inlinenoargs": "<blockquote>Введите запрос</blockquote>",
|
||||||
"history": (
|
"history": (
|
||||||
"<blockquote><emoji document_id=5879939498149679716>🔎</emoji> <b>История поиска:</b>\n"
|
"<blockquote><tg-emoji emoji-id=5879939498149679716>🔎</tg-emoji> <b>История поиска:</b>\n"
|
||||||
"{history}</blockquote>"
|
"{history}</blockquote>"
|
||||||
),
|
),
|
||||||
"filter_menu": "Выберите фильтры",
|
"filter_menu": "Выберите фильтры",
|
||||||
@@ -260,9 +514,9 @@ class Limoka(loader.Module):
|
|||||||
"apply_filters": "✅ Применить фильтры",
|
"apply_filters": "✅ Применить фильтры",
|
||||||
"clear_filters": "🗑 Очистить фильтры",
|
"clear_filters": "🗑 Очистить фильтры",
|
||||||
"back_to_results": "🔙 Вернуться к результатам",
|
"back_to_results": "🔙 Вернуться к результатам",
|
||||||
"empty_history": "<blockquote><emoji document_id=5879939498149679716>🔎</emoji> <b>История поиска пуста!</b></blockquote>",
|
"empty_history": "<blockquote><tg-emoji emoji-id=5879939498149679716>🔎</tg-emoji> <b>История поиска пуста!</b></blockquote>",
|
||||||
"enter_query": "🔍 Введите новый поисковый запрос:",
|
"enter_query": "🔍 Введите новый поисковый запрос:",
|
||||||
"global_search": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> Глобальный поиск по <b>{query}</b> — найдено <b>{count}</b> модулей</blockquote>",
|
"global_search": "<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> Глобальный поиск по <b>{query}</b> — найдено <b>{count}</b> модулей</blockquote>",
|
||||||
"change_query": "🔍 Изменить запрос",
|
"change_query": "🔍 Изменить запрос",
|
||||||
"no_modules": "<blockquote>Модули недоступны.</blockquote>",
|
"no_modules": "<blockquote>Модули недоступны.</blockquote>",
|
||||||
"filter_title": "🏷 Фильтры",
|
"filter_title": "🏷 Фильтры",
|
||||||
@@ -281,15 +535,15 @@ class Limoka(loader.Module):
|
|||||||
"inline_short_query": "<blockquote>❌ Запрос слишком короткий (мин. 2 символа)</blockquote>",
|
"inline_short_query": "<blockquote>❌ Запрос слишком короткий (мин. 2 символа)</blockquote>",
|
||||||
"inline_switch_pm": "💬 Открыть в чате",
|
"inline_switch_pm": "💬 Открыть в чате",
|
||||||
"inline_switch_pm_text": "🔍 Результаты для: {query}",
|
"inline_switch_pm_text": "🔍 Результаты для: {query}",
|
||||||
"inline_start_message": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> <b>Limoka Поиск</b>\nВведите название модуля или ключевое слово</blockquote>",
|
"inline_start_message": "<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> <b>Limoka Поиск</b>\nВведите название модуля или ключевое слово</blockquote>",
|
||||||
"first_page": "<blockquote>Это первая страница!</blockquote>",
|
"first_page": "<blockquote>Это первая страница!</blockquote>",
|
||||||
"last_page": "<blockquote>Это последняя страница!</blockquote>",
|
"last_page": "<blockquote>Это последняя страница!</blockquote>",
|
||||||
"display_error": "<blockquote>Ошибка отображения модуля. Пожалуйста, попробуйте еще раз.</blockquote>",
|
"display_error": "<blockquote>Ошибка отображения модуля. Пожалуйста, попробуйте еще раз.</blockquote>",
|
||||||
"error_occurred": "<blockquote>Произошла ошибка. Пожалуйста, попробуйте еще раз.</blockquote>",
|
"error_occurred": "<blockquote>Произошла ошибка. Пожалуйста, попробуйте еще раз.</blockquote>",
|
||||||
"start_search_form": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> <b>Limoka Поиск</b>\nВведите ваш запрос для поиска модулей:</blockquote>",
|
"start_search_form": "<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> <b>Limoka Поиск</b>\nВведите ваш запрос для поиска модулей:</blockquote>",
|
||||||
"global_search_form": "<blockquote><emoji document_id=5413334818047940135>🔍</emoji> <b>Глобальный Поиск</b>\nВведите запрос для поиска ВСЕХ модулей без фильтров:</blockquote>",
|
"global_search_form": "<blockquote><tg-emoji emoji-id=5413334818047940135>🔍</tg-emoji> <b>Глобальный Поиск</b>\nВведите запрос для поиска ВСЕХ модулей без фильтров:</blockquote>",
|
||||||
"history_cleared": "<blockquote><emoji document_id=5427009710268689068>🧹</emoji> <b>История поиска очищена!</b></blockquote>",
|
"history_cleared": "<blockquote><tg-emoji emoji-id=5427009710268689068>🧹</tg-emoji> <b>История поиска очищена!</b></blockquote>",
|
||||||
"invalid_history_arg": "<blockquote><emoji document_id=5210952531676504517>❌</emoji> <b>Неверный аргумент для команды истории. Используйте:</b>\n<code>.lshistory</code> - показать историю\n<code>.lshistory clear</code> - очистить историю</blockquote>",
|
"invalid_history_arg": "<blockquote><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> <b>Неверный аргумент для команды истории. Используйте:</b>\n<code>.lshistory</code> - показать историю\n<code>.lshistory clear</code> - очистить историю</blockquote>",
|
||||||
"close": "❌ Закрыть",
|
"close": "❌ Закрыть",
|
||||||
"watcher_no_tag": "<blockquote>❌ Неверный формат сообщения. Тег #limoka не найден.</blockquote>",
|
"watcher_no_tag": "<blockquote>❌ Неверный формат сообщения. Тег #limoka не найден.</blockquote>",
|
||||||
"watcher_invalid_format": "<blockquote>❌ Неверный формат. Ожидается: #limoka:path:signature</blockquote>",
|
"watcher_invalid_format": "<blockquote>❌ Неверный формат. Ожидается: #limoka:path:signature</blockquote>",
|
||||||
@@ -316,7 +570,7 @@ class Limoka(loader.Module):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.api = LimokaAPI()
|
self.api = APIClient()
|
||||||
self.config = loader.ModuleConfig(
|
self.config = loader.ModuleConfig(
|
||||||
loader.ConfigValue(
|
loader.ConfigValue(
|
||||||
"limokaurl",
|
"limokaurl",
|
||||||
@@ -342,15 +596,13 @@ class Limoka(loader.Module):
|
|||||||
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
|
|
||||||
self.SEARCH_STATES = {
|
self.SEARCH_STATES = {
|
||||||
"no_banner": "no_banner", # 404 - No banner
|
"no_banner": "no_banner",
|
||||||
"global_search": "global_search", # Global search
|
"global_search": "global_search",
|
||||||
"not_found": "not_found", # Not found (module)
|
"not_found": "not_found",
|
||||||
"filter_select": "filter_select", # Select categories (filters)
|
"filter_select": "filter_select",
|
||||||
}
|
}
|
||||||
|
|
||||||
# State banners
|
|
||||||
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",
|
||||||
@@ -359,28 +611,8 @@ class Limoka(loader.Module):
|
|||||||
}
|
}
|
||||||
|
|
||||||
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'.
|
"""[DEPRECATED] Use ModuleRepository.apply_newbie_filter instead."""
|
||||||
Returns the original dict when the feature is disabled or repositories
|
return self.repository.apply_newbie_filter(self.config.get("filter_newbies_modules", False))
|
||||||
metadata is not available.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if not self.config.get("filter_newbies_modules"):
|
|
||||||
return modules
|
|
||||||
except Exception:
|
|
||||||
return modules
|
|
||||||
|
|
||||||
if not getattr(self, "repositories", None):
|
|
||||||
return modules
|
|
||||||
|
|
||||||
filtered: Dict[str, Any] = {}
|
|
||||||
for path, info in modules.items():
|
|
||||||
repo_key = "/".join(path.split("/")[:2]) if "/" in path else path
|
|
||||||
repo = self.repositories.get(repo_key)
|
|
||||||
tags = repo.get("tags", []) if repo else []
|
|
||||||
if "newbie" in tags:
|
|
||||||
continue
|
|
||||||
filtered[path] = info
|
|
||||||
return filtered
|
|
||||||
|
|
||||||
def _create_search_session(
|
def _create_search_session(
|
||||||
self,
|
self,
|
||||||
@@ -415,9 +647,10 @@ class Limoka(loader.Module):
|
|||||||
return self.state_banners.get(state)
|
return self.state_banners.get(state)
|
||||||
|
|
||||||
async def client_ready(self, client, db):
|
async def client_ready(self, client, db):
|
||||||
|
"""Initialize client and load data."""
|
||||||
self.client: TelegramClient = client
|
self.client: TelegramClient = client
|
||||||
self.db = db
|
self.db = db
|
||||||
self.api = LimokaAPI()
|
self.api = APIClient()
|
||||||
self.schema = Schema(
|
self.schema = Schema(
|
||||||
title=TEXT(stored=True), path=ID(stored=True), content=TEXT(stored=True)
|
title=TEXT(stored=True), path=ID(stored=True), content=TEXT(stored=True)
|
||||||
)
|
)
|
||||||
@@ -426,19 +659,19 @@ class Limoka(loader.Module):
|
|||||||
self.ix = create_in("limoka_search", self.schema)
|
self.ix = create_in("limoka_search", self.schema)
|
||||||
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._base_url, "modules.json")).get(
|
raw_modules = (await self.api.fetch_json(self._base_url, "modules.json")).get("modules", {})
|
||||||
"modules", {}
|
raw_repos = (await self.api.fetch_json(self._base_url, "repositories.json")).get("repositories", [])
|
||||||
)
|
|
||||||
raw = (await self.api.fetch_json(self._base_url, "repositories.json")).get(
|
repositories = {repo["url"]: repo for repo in raw_repos}
|
||||||
"repositories", []
|
self.repository = ModuleRepository(raw_modules, repositories)
|
||||||
)
|
self.modules = self.repository.apply_newbie_filter(self.config["filter_newbies_modules"])
|
||||||
self.repositories = {repo["url"]: repo for repo in raw}
|
|
||||||
# Apply newbie filter if enabled
|
self._userbot_bot_username = (await self.inline.bot.get_me()).username
|
||||||
try:
|
self.formatter = CommandFormatter(self.strings, self._userbot_bot_username, self.get_prefix())
|
||||||
self.modules = self._filter_newbies(self.modules)
|
self.content_builder = ModuleContentBuilder(self.strings, self.formatter, self.repository)
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
self._service_bot_id = (await self.client.get_entity(self._bot_username)).id
|
self._service_bot_id = (await self.client.get_entity(self._bot_username)).id
|
||||||
|
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
@@ -448,9 +681,7 @@ 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(
|
message = await self.client.send_message(self._bot_username, "/start")
|
||||||
self._bot_username, "/start"
|
|
||||||
)
|
|
||||||
await message.delete()
|
await message.delete()
|
||||||
await self.client(
|
await self.client(
|
||||||
functions.messages.DeleteHistoryRequest(
|
functions.messages.DeleteHistoryRequest(
|
||||||
@@ -460,41 +691,35 @@ class Limoka(loader.Module):
|
|||||||
revoke=True,
|
revoke=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
except YouBlockedUserError:
|
except YouBlockedUserError:
|
||||||
logger.warning(
|
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."
|
f"Please unblock {self._bot_username} to enable external installation feature."
|
||||||
)
|
)
|
||||||
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._base_url, "modules.json")
|
"""Periodically update modules list and rebuild index."""
|
||||||
# Re-apply newbie filter after modules refresh
|
raw_modules = await self.api.fetch_json(self._base_url, "modules.json")
|
||||||
try:
|
self.modules = self.repository.apply_newbie_filter(
|
||||||
self.modules = self._filter_newbies(self.modules)
|
self.config.get("filter_newbies_modules", False)
|
||||||
except Exception:
|
)
|
||||||
pass
|
|
||||||
await self._update_index()
|
await self._update_index()
|
||||||
|
|
||||||
async def _update_index(self):
|
async def _update_index(self):
|
||||||
|
"""Rebuild full-text search index from modules."""
|
||||||
try:
|
try:
|
||||||
writer = self.ix.writer()
|
writer = self.ix.writer()
|
||||||
modules_to_index = self._filter_newbies(self.modules)
|
for module_path, module_data in self.modules.items():
|
||||||
for module_path, module_data in modules_to_index.items():
|
|
||||||
writer.add_document(
|
writer.add_document(
|
||||||
title=module_data["name"],
|
title=module_data["name"],
|
||||||
path=module_path,
|
path=module_path,
|
||||||
content=module_data["name"]
|
content=module_data["name"]
|
||||||
+ " "
|
+ " "
|
||||||
+ (
|
+ (
|
||||||
module_data.get("description")
|
module_data.get("description", "")
|
||||||
or ""
|
|
||||||
+ " "
|
+ " "
|
||||||
+ (
|
+ (
|
||||||
(module_data.get("meta").get("developer") or "")
|
(module_data.get("meta", {}).get("developer") or "")
|
||||||
if module_data.get("meta")
|
|
||||||
else ""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -508,17 +733,11 @@ class Limoka(loader.Module):
|
|||||||
writer.commit()
|
writer.commit()
|
||||||
except LockError:
|
except LockError:
|
||||||
folder = os.path.join(BASE_DIR, "limoka_search")
|
folder = os.path.join(BASE_DIR, "limoka_search")
|
||||||
|
|
||||||
if os.path.commonpath([folder, BASE_DIR]) == BASE_DIR and os.path.exists(folder):
|
if os.path.commonpath([folder, BASE_DIR]) == BASE_DIR and os.path.exists(folder):
|
||||||
shutil.rmtree(folder)
|
shutil.rmtree(folder)
|
||||||
await self._update_index()
|
await self._update_index()
|
||||||
else:
|
else:
|
||||||
logger.error(
|
logger.error(f"Skipping unsafe rmtree for {folder}")
|
||||||
(
|
|
||||||
f"Skipping unsafe rmtree for {folder}. Please, report this to developer. ",
|
|
||||||
f"Debug info: folder={folder}, base_dir={BASE_DIR}, common_path={os.path.commonpath([folder, BASE_DIR])}, exists={os.path.exists(folder)}",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _validate_url(self, url: str) -> Optional[str]:
|
async def _validate_url(self, url: str) -> Optional[str]:
|
||||||
if not url or url in self._invalid_banners:
|
if not url or url in self._invalid_banners:
|
||||||
@@ -578,37 +797,8 @@ class Limoka(loader.Module):
|
|||||||
return self.db.get(f"{userbot}.translations", "lang")
|
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 = []
|
"""[DEPRECATED] Use CommandFormatter.format_commands instead."""
|
||||||
for i, cmd in enumerate(module_info.get("new_commands", []), 1):
|
return self.formatter.format_commands(module_info, lang)
|
||||||
name = cmd.get("name", "")
|
|
||||||
desc_map = cmd.get("description", {})
|
|
||||||
if i <= 10:
|
|
||||||
emoji = self.strings["emojis"].get(i, "")
|
|
||||||
else:
|
|
||||||
emoji = ""
|
|
||||||
for digit in str(i):
|
|
||||||
emoji += self.strings["emojis"].get(int(digit), "")
|
|
||||||
desc = _get_lang_value(desc_map, lang) or self.strings["no_info"]
|
|
||||||
commands.append(
|
|
||||||
self.strings["command_template"].format(
|
|
||||||
prefix=self.get_prefix(),
|
|
||||||
command=html.escape(name),
|
|
||||||
emoji=emoji,
|
|
||||||
description=html.escape(desc),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for i, handler in enumerate(module_info.get("inline_handlers", []), 1):
|
|
||||||
name = handler.get("name", "")
|
|
||||||
desc_map = handler.get("description", {})
|
|
||||||
desc = _get_lang_value(desc_map, lang) or self.strings["no_info"]
|
|
||||||
commands.append(
|
|
||||||
self.strings["inline_handler_template"].format(
|
|
||||||
inline_bot=self._userbot_bot_username,
|
|
||||||
command=html.escape(name),
|
|
||||||
description=html.escape(desc),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _format_module_content(
|
def _format_module_content(
|
||||||
self,
|
self,
|
||||||
@@ -619,81 +809,10 @@ class Limoka(loader.Module):
|
|||||||
module_path: Optional[str] = None,
|
module_path: Optional[str] = None,
|
||||||
lang: str = "en",
|
lang: str = "en",
|
||||||
) -> tuple:
|
) -> tuple:
|
||||||
name = html.escape(module_info.get("name") or self.strings["no_info"])
|
"""[DEPRECATED] Use ModuleContentBuilder.build_content instead."""
|
||||||
cls_doc = module_info.get("cls_doc", {})
|
return self.content_builder.build_content(
|
||||||
description = html.escape(
|
module_info, query, filters, include_categories, module_path, lang
|
||||||
_get_lang_value(cls_doc, lang)
|
|
||||||
or _get_lang_value(module_info.get("description", ""), lang)
|
|
||||||
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", "")
|
|
||||||
)
|
|
||||||
clean_module_path = (raw_path or "").replace("\\", "/")
|
|
||||||
commands = self.generate_commands(module_info, lang)
|
|
||||||
categories_text = ""
|
|
||||||
if include_categories:
|
|
||||||
categories = filters.get("category", [])
|
|
||||||
if categories:
|
|
||||||
categories_text = "\n" + self.strings["selected_categories"].format(
|
|
||||||
categories=", ".join(html.escape(c) for c in categories)
|
|
||||||
)
|
|
||||||
if len(description) > 300:
|
|
||||||
description = description[:297] + "…"
|
|
||||||
repo_key = (
|
|
||||||
"/".join(module_path.split("/")[:2]) if "/" in module_path else module_path
|
|
||||||
)
|
|
||||||
tags_list = []
|
|
||||||
for x in self.repositories:
|
|
||||||
if x.replace("https://github.com/", "") == repo_key:
|
|
||||||
tags_list = self.repositories.get(x, {}).get("tags", [])
|
|
||||||
break
|
|
||||||
tags_text = ", ".join(self.strings["tags"].get(tag, tag) for tag in tags_list)
|
|
||||||
|
|
||||||
header_template = self.strings["found_header"]
|
|
||||||
if not tags_text:
|
|
||||||
header_template = header_template.replace(
|
|
||||||
"<blockquote><b><emoji document_id=5418376169055602355>🏷</emoji> Tags:</b> {tags}</blockquote>\n\n",
|
|
||||||
""
|
|
||||||
)
|
|
||||||
header_template = header_template.replace(
|
|
||||||
"<blockquote><b><emoji document_id=5418376169055602355>🏷</emoji> Теги:</b> {tags}</blockquote>\n\n",
|
|
||||||
""
|
|
||||||
)
|
|
||||||
|
|
||||||
header = header_template.format(
|
|
||||||
query=html.escape(query),
|
|
||||||
name=name,
|
|
||||||
description=description,
|
|
||||||
username=dev_username,
|
|
||||||
tags=tags_text,
|
|
||||||
)
|
|
||||||
commands_text = "".join(commands)
|
|
||||||
if len(commands_text) <= 500:
|
|
||||||
body_pages = [commands_text] if commands_text else [""]
|
|
||||||
else:
|
|
||||||
body_pages = []
|
|
||||||
current_page = []
|
|
||||||
current_length = 0
|
|
||||||
for cmd in commands:
|
|
||||||
if current_length + len(cmd) > 500:
|
|
||||||
if current_page:
|
|
||||||
body_pages.append("".join(current_page))
|
|
||||||
current_page = []
|
|
||||||
current_length = 0
|
|
||||||
current_page.append(cmd)
|
|
||||||
current_length += len(cmd)
|
|
||||||
if current_page:
|
|
||||||
body_pages.append("".join(current_page))
|
|
||||||
if not body_pages:
|
|
||||||
body_pages = [""]
|
|
||||||
footer = self.strings["found_footer"].format(
|
|
||||||
url=html.escape(self.config["limokaurl"]),
|
|
||||||
module_path=html.escape(clean_module_path),
|
|
||||||
prefix=html.escape(self.get_prefix()),
|
|
||||||
)
|
|
||||||
return header, body_pages, footer, categories_text
|
|
||||||
|
|
||||||
def _build_navigation_markup(self, session: Dict[str, Any]) -> list:
|
def _build_navigation_markup(self, session: Dict[str, Any]) -> list:
|
||||||
result = session["results"]
|
result = session["results"]
|
||||||
@@ -849,17 +968,21 @@ class Limoka(loader.Module):
|
|||||||
logger.error("message_or_call is None in _safe_display")
|
logger.error("message_or_call is None in _safe_display")
|
||||||
return
|
return
|
||||||
if isinstance(message_or_call, Message):
|
if isinstance(message_or_call, Message):
|
||||||
|
# WORKAROUND: Telegram doesn't show premium emojis in first call,
|
||||||
|
# until it's edited. Firstly sending something, than fixing.
|
||||||
if photo is not None:
|
if photo is not None:
|
||||||
await self.inline.form(
|
msg = await self.inline.form(
|
||||||
text=text,
|
text="🍋",
|
||||||
message=message_or_call,
|
message=message_or_call,
|
||||||
reply_markup=markup,
|
reply_markup=[[{"text": "🍋", "action": "close"}]],
|
||||||
photo=photo,
|
photo=photo,
|
||||||
)
|
)
|
||||||
|
await msg.edit(text=text, reply_markup=markup, photo=photo)
|
||||||
else:
|
else:
|
||||||
await self.inline.form(
|
msg = await self.inline.form(
|
||||||
text=text, message=message_or_call, reply_markup=markup
|
text="🍋", message=message_or_call, reply_markup=[[{"text": "🍋", "action": "close"}]]
|
||||||
)
|
)
|
||||||
|
await msg.edit(text=text, reply_markup=markup)
|
||||||
else:
|
else:
|
||||||
if photo is not None:
|
if photo is not None:
|
||||||
await message_or_call.edit(
|
await message_or_call.edit(
|
||||||
@@ -998,13 +1121,11 @@ class Limoka(loader.Module):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def _select_category(self, call: InlineCall, session: Dict[str, Any]):
|
async def _select_category(self, call: InlineCall, session: Dict[str, Any]):
|
||||||
|
"""Display category selection menu."""
|
||||||
query = session["query"]
|
query = session["query"]
|
||||||
current_filters = session["filters"]
|
current_filters = session["filters"]
|
||||||
|
|
||||||
all_categories = set()
|
categories = self.repository.get_all_categories()
|
||||||
for module_data in self.modules.values():
|
|
||||||
all_categories.update(module_data.get("category", ["No category"]))
|
|
||||||
categories = sorted(all_categories)
|
|
||||||
if not categories:
|
if not categories:
|
||||||
await call.edit(
|
await call.edit(
|
||||||
self.strings["no_categories"],
|
self.strings["no_categories"],
|
||||||
@@ -1031,8 +1152,6 @@ 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
|
|
||||||
new_session = session.copy()
|
|
||||||
row.append(
|
row.append(
|
||||||
{
|
{
|
||||||
"text": button_text,
|
"text": button_text,
|
||||||
@@ -1089,12 +1208,13 @@ class Limoka(loader.Module):
|
|||||||
async def _show_results(
|
async def _show_results(
|
||||||
self, call: InlineCall, session: Dict[str, Any], from_filters: bool = False
|
self, call: InlineCall, session: Dict[str, Any], from_filters: bool = False
|
||||||
):
|
):
|
||||||
|
"""Display search results with optional category filtering."""
|
||||||
query = session["query"]
|
query = session["query"]
|
||||||
filters = session["filters"]
|
filters = session["filters"]
|
||||||
|
|
||||||
searcher = Search(query.lower(), self.ix)
|
searcher = SearchIndex(query.lower(), self.ix)
|
||||||
try:
|
try:
|
||||||
result = searcher.search_module()
|
result = searcher.search()
|
||||||
except Exception:
|
except Exception:
|
||||||
await call.edit(self.strings["?"], reply_markup=[])
|
await call.edit(self.strings["?"], reply_markup=[])
|
||||||
return
|
return
|
||||||
@@ -1154,7 +1274,6 @@ class Limoka(loader.Module):
|
|||||||
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
|
|
||||||
display_session = self._create_search_session(
|
display_session = self._create_search_session(
|
||||||
state=self.SEARCH_STATES["global_search"],
|
state=self.SEARCH_STATES["global_search"],
|
||||||
query=query,
|
query=query,
|
||||||
@@ -1205,9 +1324,8 @@ class Limoka(loader.Module):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
searcher = Search(query.lower(), self.ix)
|
|
||||||
try:
|
try:
|
||||||
result = searcher.search_module()
|
result = SearchIndex(query.lower(), self.ix).search()
|
||||||
except Exception:
|
except Exception:
|
||||||
await call.edit(
|
await call.edit(
|
||||||
self.strings["?"],
|
self.strings["?"],
|
||||||
@@ -1289,9 +1407,8 @@ 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)
|
|
||||||
try:
|
try:
|
||||||
result = searcher.search_module()
|
result = SearchIndex(query.lower(), self.ix).search()
|
||||||
except Exception:
|
except Exception:
|
||||||
await call.edit(self.strings["?"], reply_markup=[])
|
await call.edit(self.strings["?"], reply_markup=[])
|
||||||
return
|
return
|
||||||
@@ -1407,11 +1524,35 @@ class Limoka(loader.Module):
|
|||||||
markup.append(
|
markup.append(
|
||||||
[{"text": self.strings.get("close", "❌ Close"), "action": "close", "style": "danger"}]
|
[{"text": self.strings.get("close", "❌ Close"), "action": "close", "style": "danger"}]
|
||||||
)
|
)
|
||||||
await self.inline.form(
|
# WORKAROUND: Telegram doesnt show emojis in inline forms as expected,
|
||||||
text=self.strings["start_search_form"],
|
# until the form is edited. Sending with lemon, then fixing.
|
||||||
|
workaround_markup = [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"text": "🍋 Enter query",
|
||||||
|
"input": "Enter query",
|
||||||
|
"handler": self._enter_query_handler,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"text": "🍋 Results",
|
||||||
|
"callback": self._show_global_form,
|
||||||
|
"args": (message,),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
]
|
||||||
|
workaround_markup.append(
|
||||||
|
[{"text": "🍋 Close", "action": "close", "style": "danger"}]
|
||||||
|
)
|
||||||
|
msg = await self.inline.form(
|
||||||
|
text="🍋 Limoka\n🍋 Enter query",
|
||||||
message=message,
|
message=message,
|
||||||
|
reply_markup=workaround_markup,
|
||||||
|
)
|
||||||
|
await msg.edit(
|
||||||
|
text=self.strings["start_search_form"],
|
||||||
reply_markup=markup,
|
reply_markup=markup,
|
||||||
photo=self._get_banner_for_state("global_search"),
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
history = self.get("history", [])
|
history = self.get("history", [])
|
||||||
@@ -1427,9 +1568,8 @@ class Limoka(loader.Module):
|
|||||||
query=args,
|
query=args,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
searcher = Search(args.lower(), self.ix)
|
|
||||||
try:
|
try:
|
||||||
result = searcher.search_module()
|
result = SearchIndex(args.lower(), self.ix).search()
|
||||||
except Exception:
|
except Exception:
|
||||||
return await utils.answer(message, self.strings["?"])
|
return await utils.answer(message, self.strings["?"])
|
||||||
if not result:
|
if not result:
|
||||||
@@ -1505,9 +1645,8 @@ class Limoka(loader.Module):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
searcher = Search(query.lower(), self.ix)
|
|
||||||
try:
|
try:
|
||||||
result = searcher.search_module()
|
result = SearchIndex(query.lower(), self.ix).search()
|
||||||
except Exception:
|
except Exception:
|
||||||
await call.edit(
|
await call.edit(
|
||||||
self.strings["?"],
|
self.strings["?"],
|
||||||
|
|||||||
Reference in New Issue
Block a user