diff --git a/Fixyres/FModules/.gitignore b/Fixyres/FModules/.gitignore new file mode 100644 index 0000000..b7faf40 --- /dev/null +++ b/Fixyres/FModules/.gitignore @@ -0,0 +1,207 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock +#poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +#pdm.lock +#pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +#pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Cursor +# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to +# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data +# refer to https://docs.cursor.com/context/ignore-files +.cursorignore +.cursorindexingignore + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ diff --git a/Fixyres/FModules/BSR.py b/Fixyres/FModules/BSR.py new file mode 100644 index 0000000..8d383c4 --- /dev/null +++ b/Fixyres/FModules/BSR.py @@ -0,0 +1,254 @@ +__version__ = (1, 0, 0) + +# ©️ Fixyres, 2026-2030 +# 🌐 https://github.com/Fixyres/FModules +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 🔑 http://www.apache.org/licenses/LICENSE-2.0 + +# meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/BSR/banner.png +# meta developer: @FModules +# meta fhsdesc: brawlstars, game, funny + +from .. import loader, utils +from urllib.parse import urlparse, parse_qs + +async def extract_code(value: str) -> str: + if value.startswith("http"): + tags = parse_qs(urlparse(value).query).get("tag") + return tags[0] if tags else value + return value + +async def to_id(code: str) -> int: + code = code.strip().upper() + if not code.startswith("X"): + return 0 + val = 0 + for ch in code[1:]: + i = "QWERTYUPASDFGHJKLZCVBNM23456789".find(ch) + if i == -1: + return 0 + val = val * 31 + i + return val >> 8 + +async def to_code(n: int) -> str: + if n < 0: + return "X" + n_shifted = n << 8 + res = [] + chars = "QWERTYUPASDFGHJKLZCVBNM23456789" + while n_shifted > 0: + res.append(chars[n_shifted % 31]) + n_shifted //= 31 + return "X" + "".join(reversed(res)) + +@loader.tds +class BSR(loader.Module): + '''Module for finding nearby game rooms in BrawlStars.''' + + strings = { + "name": "BSR", + "invalid_args": "Usage: {prefix}bsr (room code/link) (previous) (next)", + "invalid_code": "Invalid room code!", + "at_least_one": "At least one argument (previous or next) must be greater than 0!", + "prev_block": "Previous:\n
{prev_list}
", + "next_block": "Next:\n
{next_list}
", + "btn_target": "Target Room" + } + + strings_ru = { + "_cls_doc": "Модуль для поиска ближайших игровых комнат в BrawlStars.", + "invalid_args": "Использование: {prefix}bsr (код комнаты/ссылка) (предыдущие) (следующие)", + "invalid_code": "Неверный код комнаты!", + "at_least_one": "Хотя бы один аргумент (предыдущие или следующие) должен быть больше 0!", + "prev_block": "Предыдущие:\n
{prev_list}
", + "next_block": "Следующие:\n
{next_list}
", + "btn_target": "Целевая комната" + } + + strings_ua = { + "_cls_doc": "Модуль для пошуку найближчих ігрових кімнат у BrawlStars.", + "invalid_args": "Використання: {prefix}bsr (код кімнати/посилання) (попередні) (наступні)", + "invalid_code": "Невірний код кімнати!", + "at_least_one": "Хоча б один аргумент (попередні або наступні) повинен бути більшим за 0!", + "prev_block": "Попередні:\n
{prev_list}
", + "next_block": "Наступні:\n
{next_list}
", + "btn_target": "Цільова кімната" + } + + strings_kz = { + "_cls_doc": "BrawlStars ойынында жақын маңдағы ойын бөлмелерін табуға арналған модуль.", + "invalid_args": "Қолдану: {prefix}bsr (бөлме коды/сілтеме) (алдыңғы) (келесі)", + "invalid_code": "Қате бөлме коды!", + "at_least_one": "Кем дегенде бір аргумент (алдыңғы немесе келесі) 0-ден үлкен болуы керек!", + "prev_block": "Алдыңғы:\n
{prev_list}
", + "next_block": "Келесі:\n
{next_list}
", + "btn_target": "Мақсатты бөлме" + } + + strings_uz = { + "_cls_doc": "BrawlStars'da eng yaqin o'yin xonalarini topish uchun modul.", + "invalid_args": "Qo'llanilishi: {prefix}bsr (xona kodi/havolasi) (oldingi) (keyingi)", + "invalid_code": "Noto'g'ri xona kodi!", + "at_least_one": "Kamida bitta argument (oldingi yoki keyingi) 0 dan katta bo'lishi kerak!", + "prev_block": "Oldingi:\n
{prev_list}
", + "next_block": "Keyingi:\n
{next_list}
", + "btn_target": "Maqsadli xona" + } + + strings_fr = { + "_cls_doc": "Module pour trouver des salles de jeu à proximité dans BrawlStars.", + "invalid_args": "Utilisation: {prefix}bsr (code/lien) (précédents) (suivants)", + "invalid_code": "Code de salle invalide!", + "at_least_one": "Au moins un argument doit être supérieur à 0 !", + "prev_block": "Précédents:\n
{prev_list}
", + "next_block": "Suivants:\n
{next_list}
", + "btn_target": "Salle cible" + } + + strings_de = { + "_cls_doc": "Modul zum Finden von nahegelegenen Spielräumen in BrawlStars.", + "invalid_args": "Verwendung: {prefix}bsr (Raumcode/Link) (vorherige) (nächste)", + "invalid_code": "Ungültiger Raumcode!", + "at_least_one": "Mindestens ein argument muss größer als 0 sein!", + "prev_block": "Vorherige:\n
{prev_list}
", + "next_block": "Nächste:\n
{next_list}
", + "btn_target": "Zielraum" + } + + strings_jp = { + "_cls_doc": "BrawlStarsで近くのゲームルームを検索するためのモジュール。", + "invalid_args": "使用法: {prefix}bsr (コード/リンク) (前) (次)", + "invalid_code": "無効なルームコード!", + "at_least_one": "少なくとも1つの引数は0より大きくなければなりません!", + "prev_block": "前:\n
{prev_list}
", + "next_block": "次:\n
{next_list}
", + "btn_target": "ターゲットルーム" + } + + @loader.command( + ru_doc="(код комнаты/ссылка) (предыдущие) (следующие) - найти комнаты.", + ua_doc="(код кімнати/посилання) (попередні) (наступні) - знайти кімнати.", + kz_doc="(бөлме коды/сілтеме) (алдыңғы) (келесі) - бөлмелерді табу.", + uz_doc="(xona kodi/havolasi) (oldingi) (keyingi) - xonalarni topish.", + fr_doc="(code/lien) (précédents) (suivants) - trouver des salles.", + de_doc="(Raumcode/Link) (vorherige) (nächste) - Räume finden.", + jp_doc="(コード/リンク) (前) (次) - ルームを検索します。" + ) + async def bsr(self, message): + '''(room code/link) (previous) (next) - find rooms.''' + args = utils.get_args_raw(message).split() + if not args: + return await utils.answer(message, self.strings("invalid_args").format(prefix=self.get_prefix())) + + raw_input = args[0] + before = 0 + nxt = 10 + + if len(args) >= 2: + try: + before = int(args[1]) + except ValueError: + pass + + if len(args) >= 3: + try: + nxt = int(args[2]) + except ValueError: + pass + + before = max(0, min(before, 5000)) + nxt = max(0, min(nxt, 5000)) + + if before == 0 and nxt == 0: + return await utils.answer(message, self.strings("at_least_one")) + + clean_tag = await extract_code(raw_input) + base_id = await to_id(clean_tag) + + if base_id == 0: + return await utils.answer(message, self.strings("invalid_code")) + + text, page, total_pages = await self.get_page_content(base_id, before, nxt, 0) + kb = self.build_keyboard(base_id, before, nxt, page, total_pages, clean_tag) + + await self.inline.form( + message=message, + text=text, + photo="https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/BSR/banner.png", + reply_markup=kb + ) + + async def get_page_content(self, base_id: int, before: int, nxt: int, page: int): + actual_before = min(before, base_id) + total_pages = max(1, (actual_before + 9) // 10, (nxt + 9) // 10) + + if page < 0: + page = total_pages - 1 + if page >= total_pages: + page = 0 + + start = page * 10 + + prev_list = [] + for i in range(start + 1, min(start + 10, actual_before) + 1): + c = await to_code(base_id - i) + link = f'{c}' + prev_list.append(link) + + next_list = [] + for i in range(start + 1, min(start + 10, nxt) + 1): + c = await to_code(base_id + i) + link = f'{c}' + next_list.append(link) + + blocks = [] + + if prev_list: + blocks.append(self.strings("prev_block").format(prev_list="\n".join(prev_list))) + + if next_list: + blocks.append(self.strings("next_block").format(next_list="\n".join(next_list))) + + res = "\n\n".join(blocks) + if not res.strip(): + res = " " + + return res, page, total_pages + + def build_keyboard(self, base_id: int, before: int, nxt: int, page: int, total_pages: int, clean_tag: str): + kb = [ + [ + { + "text": self.strings("btn_target"), + "copy": clean_tag + } + ] + ] + + if total_pages > 1: + nav_row = [] + if page > 0: + nav_row.append({"text": "←", "callback": self.page_cb, "args": (base_id, before, nxt, page - 1, clean_tag)}) + + nav_row.append({"text": f"{page + 1} / {total_pages}", "callback": self.dummy_cb, "args": ()}) + + if page < total_pages - 1: + nav_row.append({"text": "→", "callback": self.page_cb, "args": (base_id, before, nxt, page + 1, clean_tag)}) + + kb.append(nav_row) + + return kb + + async def dummy_cb(self, call): + await call.answer() + + async def page_cb(self, call, base_id: int, before: int, nxt: int, page: int, clean_tag: str): + text, new_page, total_pages = await self.get_page_content(base_id, before, nxt, page) + kb = self.build_keyboard(base_id, before, nxt, new_page, total_pages, clean_tag) + + await call.edit( + text=text, + photo="https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/BSR/banner.png", + reply_markup=kb + ) diff --git a/Fixyres/FModules/FHeta.py b/Fixyres/FModules/FHeta.py new file mode 100644 index 0000000..ab706ae --- /dev/null +++ b/Fixyres/FModules/FHeta.py @@ -0,0 +1,986 @@ +__version__ = (9, 3, 9) + +# meta developer: @FModules +# meta pic: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/logo.png +# meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/logo.png +# scope: hikka_min 2.0.0 + +# ©️ Fixyres, 2024-2030 +# 🌐 https://github.com/Fixyres/FModules +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# 🔑 http://www.apache.org/licenses/LICENSE-2.0 + +import asyncio +import aiohttp +import ast +import re +import sys +import uuid +import importlib +from contextlib import suppress +from typing import Optional, Dict, List, Union, Tuple, Any +from urllib.parse import unquote +from importlib.machinery import ModuleSpec + +from .. import loader, utils +from ..types import CoreOverwriteError +from herokutl.tl.functions.contacts import UnblockRequest +from aiogram.types import InlineQueryResultArticle, InputTextMessageContent, LinkPreviewOptions, ChosenInlineResult, CallbackQuery, Message + + +class FHetaAPI: + def __init__(self) -> None: + self.token: Optional[str] = None + self.session: Optional[aiohttp.ClientSession] = None + + async def connect(self) -> aiohttp.ClientSession: + if self.session is None or self.session.closed: + self.session = aiohttp.ClientSession() + return self.session + + async def fetch(self, path: str, **params: Any) -> Dict[str, Any]: + session = await self.connect() + try: + async with session.get( + f"https://api.fixyres.com/{path}", + params=params, + headers={"Authorization": self.token}, + timeout=aiohttp.ClientTimeout(total=10) + ) as response: + if response.status == 200: + return await response.json() + return {} + except Exception: + return {} + + async def send(self, path: str, payload: Optional[Dict[str, Any]] = None, **params: Any) -> Dict[str, Any]: + session = await self.connect() + try: + async with session.post( + f"https://api.fixyres.com/{path}", + json=payload, + params=params, + headers={"Authorization": self.token}, + timeout=aiohttp.ClientTimeout(total=10) + ) as response: + if response.status == 200: + return await response.json() + return {} + except Exception: + return {} + + +class MInstaller: + async def execute(self, plugin: 'loader.Module', url: str) -> Tuple[str, List[str]]: + try: + code = await plugin._storage.fetch(url, auth=plugin.config.get("basic_auth")) + except Exception: + return "error", [] + + for step in range(5): + state = await self.load(plugin, code, url, step) + + if state == "success": + if plugin.fully_loaded: + plugin.update_modules_in_db() + return "success", [] + + if state == "overwrite": + return "overwrite", [] + + if isinstance(state, list): + return "dependency", state + + if state == "error": + return "error", [] + + await asyncio.sleep(0.5) + + return "dependency", [] + + async def pip(self, dependencies: List[str]) -> bool: + virtualenv = hasattr(sys, 'real_prefix') or sys.prefix != getattr(sys, 'base_prefix', sys.prefix) + flags = ["--user"] if loader.USER_INSTALL and not virtualenv else [] + + process = await asyncio.create_subprocess_exec( + sys.executable, "-m", "pip", "install", "-U", "-q", + "--disable-pip-version-check", "--no-warn-script-location", + *flags, *dependencies + ) + + return await process.wait() == 0 + + async def load(self, plugin: 'loader.Module', code: str, origin: str, step: int) -> Union[str, List[str]]: + if step == 0: + try: + dependencies = list(filter( + lambda requirement: not requirement.startswith(("-", "_", ".")), + map(lambda raw: raw.strip().rstrip(','), loader.VALID_PIP_PACKAGES.search(code)[1].split()) + )) + + if dependencies: + if not await self.pip(dependencies): + return dependencies + importlib.invalidate_caches() + return "retry" + except Exception: + pass + + try: + packages = list(filter( + lambda requirement: not requirement.startswith(("-", "_", ".")), + map(lambda raw: raw.strip().rstrip(','), loader.VALID_APT_PACKAGES.search(code)[1].split()) + )) + + if packages: + if not await plugin.install_packages(packages): + return packages + importlib.invalidate_caches() + return "retry" + except Exception: + pass + + try: + tree = ast.parse(code) + identifier = next( + node.name for node in tree.body + if isinstance(node, ast.ClassDef) and any( + isinstance(base, ast.Attribute) and base.value.id == "Module" or + isinstance(base, ast.Name) and base.id == "Module" + for base in node.bases + ) + ) + except Exception: + identifier = "__extmod_" + str(uuid.uuid4()) + + name = f"heroku.modules.{identifier}" + instance = None + + try: + spec = ModuleSpec(name, loader.StringLoader(code, f""), origin=f"") + instance = await plugin.allmodules.register_module(spec, name, origin, save_fs=False) + + plugin.allmodules.send_config_one(instance) + await plugin.allmodules.send_ready_one(instance, no_self_unload=True, from_dlmod=False) + + return "success" + + except ImportError as exception: + alternative = {"sklearn": "scikit-learn", "pil": "Pillow", "herokutl": "Heroku-TL-New"}.get(exception.name.lower(), exception.name) + dependencies = [alternative] + + if not alternative or not await self.pip(dependencies): + return dependencies + + importlib.invalidate_caches() + return "retry" + + except CoreOverwriteError: + return "overwrite" + + except Exception: + return "error" + + finally: + if instance and sys.exc_info()[0] is not None: + with suppress(Exception): + await plugin.allmodules.unload_module(instance.__class__.__name__) + plugin.allmodules.modules.remove(instance) + + +class FHetaUI: + def __init__(self, main: 'FHeta') -> None: + self.main = main + + def emoji(self, key: str) -> str: + return self.main.THEMES[self.main.config["theme"]][key] + + def format(self, data: Dict[str, Any], query: str = "", index: int = 1, total: int = 1, inline: bool = False) -> str: + version = data.get("version", "?.?.?") + limit = 3700 + name = utils.escape_html(data.get("name", "")) + author = utils.escape_html(data.get("author", "???")) + + text = f"{self.emoji('module')} {name} {self.main.strings['author']} {author}" + if version != "?.?.?": + text += f" (v{version})" + + description = data.get("description") + if description: + if isinstance(description, dict): + string = description.get(self.main.strings["lang"]) or description.get("doc") or next(iter(description.values()), "") + else: + string = description + text += f"\n\n{self.emoji('description')} {self.main.strings['description']}:\n
{utils.escape_html(str(string))}
" + + text += self.render(data.get("commands", []), "cmd", limit - len(re.sub(r'<[^>]+>', '', text))) + text += self.render(data.get("placeholders", []), "ph", limit - len(re.sub(r'<[^>]+>', '', text))) + + return text + + def render(self, items: List[Dict[str, Any]], kind: str, limit: int) -> str: + if not items: + return "" + + lines = [] + language = self.main.strings["lang"] + + title = "commands" if kind == "cmd" else "placeholders" + more = "morecommands" if kind == "cmd" else "moreplaceholders" + + for index, item in enumerate(items): + description = item.get("description", {}) + if isinstance(description, dict): + description = description.get(language) or description.get("doc") or "" + + description = utils.escape_html(description).split('\n')[0] if description else "" + name = utils.escape_html(item.get("name", "")) + + if kind == "cmd": + character = '@' + self.main.inline.bot_username + ' ' if item.get('inline') else self.main.get_prefix() + row = f"{character}{name} {description}".strip() + else: + row = f"{{{name}}} {description}".strip() + + extra = f"{self.main.strings[more].format(remaining=len(items) - index)}" + test = "\n".join(lines + [row, extra]) + + if len(re.sub(r'<[^>]+>', '', test)) > limit and index > 0: + lines.append(extra) + break + + lines.append(row) + + return f"\n\n{self.emoji('command' if kind == 'cmd' else 'placeholder')} {self.main.strings[title]}:\n
{chr(10).join(lines)}
" + + def buttons(self, link: str, stats: Dict[str, Any], index: int, modules: Optional[List[Dict[str, Any]]] = None, query: str = "") -> List[List[Dict[str, Any]]]: + buttons = [] + decoded = unquote(link.replace('%20', '___SPACE___')).replace('___SPACE___', '%20') + url = decoded[4:] if decoded.startswith('dlm ') else decoded + + if query: + buttons.append([ + {"text": self.main.strings["query"], "copy": query}, + {"text": self.main.strings["install"], "callback": self.main.install, "args": (url, index, modules, query)}, + {"text": self.main.strings["code"], "url": url} + ]) + + buttons.append([ + {"text": f"↑ {stats.get('likes', 0)}", "callback": self.main.rate, "args": (link, "like", index, modules, query)}, + {"text": f"↓ {stats.get('dislikes', 0)}", "callback": self.main.rate, "args": (link, "dislike", index, modules, query)} + ]) + + if modules and len(modules) > 1: + count = {"text": self.main.strings["counter"].format(idx=index+1, total=len(modules)), "callback": self.main.show, "args": (index, modules, query)} + buttons[-1].insert(1, count) + + navigation = [] + if index > 0: + navigation.append({"text": "←", "callback": self.main.navigate, "args": (index - 1, modules, query)}) + if index < len(modules) - 1: + navigation.append({"text": "→", "callback": self.main.navigate, "args": (index + 1, modules, query)}) + + if navigation: + buttons.append(navigation) + + return buttons + + def pagination(self, modules: List[Dict[str, Any]], query: str, page: int = 0, current: int = 0) -> List[List[Dict[str, Any]]]: + buttons = [] + start = page * 8 + end = min(start + 8, len(modules)) + + for index in range(start, end): + name = modules[index].get('name', 'Unknown') + author = modules[index].get('author', '???') + buttons.append([ + {"text": f"{index + 1}. {name} by {author}", "callback": self.main.navigate, "args": (index, modules, query)} + ]) + + navigation = [] + if page > 0: + navigation.append({"text": "←", "callback": self.main.page, "args": (page - 1, modules, query, current)}) + if page < (len(modules) + 7) // 8 - 1: + navigation.append({"text": "→", "callback": self.main.page, "args": (page + 1, modules, query, current)}) + + if navigation: + buttons.append(navigation) + + buttons.append([{"text": "✘", "callback": self.main.navigate, "args": (current, modules, query)}]) + return buttons + + +@loader.tds +class FHeta(loader.Module): + '''Module for searching modules! Watch all FHeta news in @FHeta_Updates!''' + + strings = { + "name": "FHeta", + "lang": "en", + "author": "by", + "description": "Description", + "commands": "Commands", + "placeholders": "Placeholders", + "morecommands": "...and {remaining} more commands.", + "moreplaceholders": "...and {remaining} more placeholders.", + "list": "All found modules:", + "search": "Searching for {query}...", + "noquery": "You didn't enter a search query, example: {prefix}fheta your query", + "notfound": "Nothing found for query {query}.", + "toolong": "Your query is too big, please try reducing it to 168 characters.", + "added": "✔ Rating submitted!", + "changed": "✔ Rating has been changed!", + "deleted": "✔ Rating deleted!", + "prompt": "Enter a query to search.", + "hint": "Name, command, description, author.", + "retry": "Try another query.", + "query": "Query", + "install": "Install", + "counter": "{idx}/{total}", + "code": "Code", + "success": "✔ Module successfully installed!", + "error": "✘ Error, perhaps the module is broken!", + "overwrite": "✘ Error, module tried to overwrite built-in module!", + "dependency": "✘ Dependencies installation error! {deps}", + "docdevs": "Use only modules from official Heroku developers when searching?", + "doctheme": "Theme for emojis." + } + + strings_ru = { + "_cls_doc": "Модуль для поиска модулей! Следите за всеми новостями FHeta в @FHeta_Updates!", + "lang": "ru", + "author": "от", + "description": "Описание", + "commands": "Команды", + "placeholders": "Плейсхолдеры", + "morecommands": "...и еще {remaining} команд.", + "moreplaceholders": "...и еще {remaining} плейсхолдеров.", + "list": "Все найденные модули:", + "search": "Поиск по запросу {query}...", + "noquery": "Вы не ввели запрос для поиска, пример: {prefix}fheta ваш запрос", + "notfound": "Ничего не найдено по запросу {query}.", + "toolong": "Ваш запрос слишком большой, пожалуйста, сократите его до 168 символов.", + "added": "✔ Оценка добавлена!", + "changed": "✔ Оценка изменена!", + "deleted": "✔ Оценка удалена!", + "prompt": "Введите запрос для поиска.", + "hint": "Название, команда, описание, автор.", + "retry": "Попробуйте другой запрос.", + "query": "Запрос", + "install": "Установить", + "counter": "{idx}/{total}", + "code": "Код", + "success": "✔ Модуль успешно установлен!", + "error": "✘ Ошибка, возможно, модуль поломан!", + "overwrite": "✘ Ошибка, модуль пытался перезаписать встроенный модуль!", + "dependency": "✘ Ошибка установки зависимостей! {deps}", + "docdevs": "Использовать только модули от официальных разработчиков Heroku при поиске?", + "doctheme": "Тема для эмодзи." + } + + strings_ua = { + "_cls_doc": "Модуль для пошуку модулів! Слідкуйте за всіма новинами FHeta в @FHeta_Updates!", + "lang": "ua", + "author": "від", + "description": "Опис", + "commands": "Команды", + "placeholders": "Плейсхолдери", + "morecommands": "...і ще {remaining} команд.", + "moreplaceholders": "...і ще {remaining} плейсхолдерів.", + "list": "Всі знайдені модули:", + "search": "Пошук за запитом {query}...", + "noquery": "Ви не ввели запит для пошуку, приклад: {prefix}fheta ваш запит", + "notfound": "Нічого не знайдено за запитом {query}.", + "toolong": "Ваш запит занадто великий, будь ласка, скоротіть його до 168 символів.", + "added": "✔ Оцінку додано!", + "changed": "✔ Оцінку змінено!", + "deleted": "✔ Оцінку видалено!", + "prompt": "Введіть запит для пошуку.", + "hint": "Назва, команда, опис, автор.", + "retry": "Спробуйте інший запит.", + "query": "Запит", + "install": "Встановити", + "counter": "{idx}/{total}", + "code": "Код", + "success": "✔ Модуль успішно встановлено!", + "error": "✘ Помилка, можливо, модуль поламаний!", + "overwrite": "✘ Помилка, модуль намагався перезаписати вбудований модуль!", + "dependency": "✘ Помилка встановлення залежностей! {deps}", + "docdevs": "Використовувати тільки модулі від офіційних розробників Heroku при пошуку?", + "doctheme": "Тема для емодзі." + } + + strings_kz = { + "_cls_doc": "Модульдерді іздеу модулі! FHeta барлық жаңалықтарын @FHeta_Updates арнасында қадағалаңыз!", + "lang": "kz", + "author": "авторы", + "description": "Сипаттама", + "commands": "Командалар", + "placeholders": "Плейсхолдерлер", + "morecommands": "...және тағы {remaining} команда.", + "moreplaceholders": "...және тағы {remaining} плейсхолдер.", + "list": "Барлық табылған модульдер:", + "search": "{query} сұрауы бойынша іздеу...", + "noquery": "Сіз іздеу сұрауын енгізбедіңіз, мысал: {prefix}fheta сіздің сұрауыңыз", + "notfound": "{query} сұрауы бойынша ештеңе табылмады.", + "toolong": "Сіздің сұрауыңыз тым үлкен, оны 168 таңбаға дейін қысқартыңыз.", + "added": "✔ Бағалау қосылды!", + "changed": "✔ Бағалау өзгертілді!", + "deleted": "✔ Бағалау жойылды!", + "prompt": "Іздеу үшін сұрау енгізіңіз.", + "hint": "Атауы, команда, сипаттама, автор.", + "retry": "Басқа сұрауды қолданып көріңіз.", + "query": "Сұрау", + "install": "Орнату", + "counter": "{idx}/{total}", + "code": "Код", + "success": "✔ Модуль сәтті орнатылды!", + "error": "✘ Қате, мүмкін модуль бұзылған!", + "overwrite": "✘ Қате, модуль кіріктірілген модульді қайта жазуға тырысты!", + "dependency": "✘ Тәуелділіктерді орнату қатесі! {deps}", + "docdevs": "Іздеу кезінде тек ресми Heroku әзірлеушілерінің модульдерін пайдалану керек пе?", + "doctheme": "Эмодзилер үшін тақырып." + } + + strings_uz = { + "_cls_doc": "Modullarni qidirish moduli! FHeta barcha yangilanishlarini @FHeta_Updates kanalida kuzatib boring!", + "lang": "uz", + "author": "muallif", + "description": "Tavsif", + "commands": "Buyruqlar", + "placeholders": "Pleysholderlar", + "morecommands": "...va yana {remaining} ta buyruq.", + "moreplaceholders": "...va yana {remaining} ta pleysholder.", + "list": "Barcha topilgan modullar:", + "search": "{query} so'rovi bo'yicha qidiruv...", + "noquery": "Siz qidiruv so'rovini kiritmadingiz, misol: {prefix}fheta sizning sorovingiz", + "notfound": "{query} so'rovi bo'yicha hech narsa topilmadi.", + "toolong": "Sizning so'rovingiz juda katta, iltimos uni 168 belgigacha qisqartiring.", + "added": "✔ Reyting qo'shildi!", + "changed": "✔ Reyting o'zgartirildi!", + "deleted": "✔ Reyting o'chirildi!", + "prompt": "Qidirish uchun so'rov kiriting.", + "hint": "Nomi, buyruq, tavsif, muallif.", + "retry": "Boshqa so'rovni sinab ko'ring.", + "query": "So'rov", + "install": "O'rnatish", + "counter": "{idx}/{total}", + "code": "Kod", + "success": "✔ Modul muvaffaqiyatli o'rnatildi!", + "error": "✘ Xatolik, ehtimol modul buzilgan!", + "overwrite": "✘ Xatolik, modul o'rnatilgan modulni qayta yozishga harakat qildi!", + "dependency": "✘ Bog'liqliklarni o'rnatish xatosi! {deps}", + "docdevs": "Qidiruv paytida faqat rasmiy Heroku ishlab chiquvchilarining modullaridan foydalanish kerakmi?", + "doctheme": "Emojilar uchun mavzu." + } + + strings_fr = { + "_cls_doc": "Module de recherche de modules! Suivez toutes les actualités FHeta sur @FHeta_Updates!", + "lang": "fr", + "author": "par", + "description": "Description", + "commands": "Commandes", + "placeholders": "Espaces réservés", + "morecommands": "...et {remaining} commandes supplémentaires.", + "moreplaceholders": "...et {remaining} espaces réservés supplémentaires.", + "list": "Tous les modules trouvés:", + "search": "Recherche pour {query}...", + "noquery": "Vous n'avez pas entré de requête de recherche, exemple: {prefix}fheta votre requête", + "notfound": "Rien trouvé pour la requête {query}.", + "toolong": "Votre requête est trop longue, veuillez la réduire à 168 caractères.", + "added": "✔ Note ajoutée!", + "changed": "✔ Note modifiée!", + "deleted": "✔ Note supprimée!", + "prompt": "Entrez une requête pour rechercher.", + "hint": "Nom, commande, description, auteur.", + "retry": "Essayez une autre requête.", + "query": "Requête", + "install": "Installer", + "counter": "{idx}/{total}", + "code": "Code", + "success": "✔ Module installé avec succès!", + "error": "✘ Erreur, le module est peut-être cassé!", + "overwrite": "✘ Erreur, le module a tenté d'écraser le module intégré!", + "dependency": "✘ Erreur d'installation des dépendances! {deps}", + "docdevs": "Utiliser uniquement les modules des développeurs Heroku officiels lors de la recherche?", + "doctheme": "Thème pour les emojis." + } + + strings_de = { + "_cls_doc": "Modul zur Suche nach Modulen! Verfolgen Sie alle FHeta-Neuigkeiten auf @FHeta_Updates!", + "lang": "de", + "author": "von", + "description": "Beschreibung", + "commands": "Befehle", + "placeholders": "Platzhalter", + "morecommands": "...und {remaining} weitere Befehle.", + "moreplaceholders": "...und {remaining} weitere Platzhalter.", + "list": "Alle gefundenen Module:", + "search": "Suche nach {query}...", + "noquery": "Sie haben keine Suchanfrage eingegeben, Beispiel: {prefix}fheta ihre anfrage", + "notfound": "Nichts gefunden für Anfrage {query}.", + "toolong": "Ihre Anfrage ist zu groß, bitte reduzieren Sie sie auf 168 Zeichen.", + "added": "✔ Bewertung hinzugefügt!", + "changed": "✔ Bewertung geändert!", + "deleted": "✔ Bewertung gelöscht!", + "prompt": "Geben Sie eine Suchanfrage ein.", + "hint": "Name, Befehl, Beschreibung, Autor.", + "retry": "Versuchen Sie eine andere Anfrage.", + "query": "Anfrage", + "install": "Installieren", + "counter": "{idx}/{total}", + "code": "Code", + "success": "✔ Modul erfolgreich installiert!", + "error": "✘ Fehler, vielleicht ist das Modul kaputt!", + "overwrite": "✘ Fehler, Modul hat versucht, das integrierte Modul zu überschreiben!", + "dependency": "✘ Fehler bei der Installation von Abhängigkeiten! {deps}", + "docdevs": "Nur Module von offiziellen Heroku-Entwicklern bei der Suche verwenden?", + "doctheme": "Thema für Emojis." + } + + strings_jp = { + "_cls_doc": "モジュール検索用モジュール!@FHeta_UpdatesでFHetaのすべてのニュースをフォローしてください!", + "lang": "jp", + "author": "作成者", + "description": "説明", + "commands": "コマンド", + "placeholders": "プレースホルダー", + "morecommands": "...さらに {remaining} 個のコマンド。", + "moreplaceholders": "...さらに {remaining} 個のプレースホルダー。", + "list": "見つかったすべてのモジュール:", + "search": "{query}を検索中...", + "noquery": "検索クエリを入力していません、例: {prefix}fheta あなたのクエリ", + "notfound": "クエリ{query}で何も見つかりませんでした。", + "toolong": "クエリが大きすぎます。168文字に短縮してください。", + "added": "✔ 評価が追加されました!", + "changed": "✔ 評価が変更されました!", + "deleted": "✔ 評価が削除されました!", + "prompt": "検索するクエリを入力してください。", + "hint": "名前、コマンド、説明、作成者。", + "retry": "別のクエリを試してください。", + "query": "クエリ", + "install": "インストール", + "counter": "{idx}/{total}", + "code": "コード", + "success": "✔ モジュールが正常にインストールされました!", + "error": "✘ エラー、モジュールが壊れている可能性があります!", + "overwrite": "✘ エラー、モジュールが組み込みモジュールを上書きしようとしました!", + "dependency": "✘ 依存関係のインストールエラー! {deps}", + "docdevs": "検索時に公式Heroku開発者のモジュールのみを使用しますか?", + "doctheme": "絵文字のテーマ。" + } + + THEMES = { + "default": { + "search": '🔍', + "error": '', + "warn": '⚠️', + "description": '📝', + "command": '⚙️', + "placeholder": '🗒️', + "module": '📦', + "channel": '📢', + "modules_list": '📋' + }, + "winter": { + "search": '❄️', + "error": '🧊', + "warn": '🌨️', + "description": '📜', + "command": '🎅', + "placeholder": '🗒️', + "module": '🎁', + "channel": '📢', + "modules_list": '🎄' + }, + "summer": { + "search": '🔍', + "error": '🌡️', + "warn": '⚠️', + "description": '🍹', + "command": '🏄', + "placeholder": '🗒️', + "module": '🏖️', + "channel": '📢', + "modules_list": '🏖️' + }, + "spring": { + "search": '🌱', + "error": '🥀', + "warn": '⚠️', + "description": '🍃', + "command": '🦋', + "placeholder": '🗒️', + "module": '🌿', + "channel": '📢', + "modules_list": '🌺' + }, + "autumn": { + "search": '🍂', + "error": '🍁', + "warn": '⚠️', + "description": '📜', + "command": '🍂', + "placeholder": '🗒️', + "module": '🍄', + "channel": '📢', + "modules_list": '🍂' + } + } + + def __init__(self) -> None: + self.config = loader.ModuleConfig( + loader.ConfigValue( + "only_official_developers", + False, + lambda: self.strings("docdevs"), + validator=loader.validators.Boolean() + ), + loader.ConfigValue( + "theme", + "default", + lambda: self.strings("doctheme"), + validator=loader.validators.Choice(["default", "winter", "summer", "spring", "autumn"]) + ) + ) + + async def on_unload(self) -> None: + if hasattr(self, "api") and self.api.session and not self.api.session.closed: + await self.api.session.close() + + async def client_ready(self, client: 'telethon.TelegramClient', database: 'loader.Database') -> None: + try: + await client(UnblockRequest("@FHeta_robot")) + await utils.dnd(client, "@FHeta_robot", archive=True) + except Exception: + pass + + self.identifier = (await client.get_me()).id + self.token = database.get("FHeta", "token") + + self.api = FHetaAPI() + self.installer = MInstaller() + self.ui = FHetaUI(self) + + self.api.token = self.token + + router = None + try: + frame = sys._getframe() + while frame: + if 'self' in frame.f_locals and type(frame.f_locals['self']).__name__ == "Modules": + router = getattr(frame.f_locals['self'], "inline", None) + if router: + break + frame = frame.f_back + except Exception: + pass + + router = router or self.inline + dispatcher = getattr(router, "_dp", getattr(router, "dp", getattr(router, "router", None))) + self.bot = getattr(router, "_bot", getattr(router, "bot", getattr(self.inline, "bot", None))) + + if dispatcher: + if not getattr(dispatcher, "_fpatched", False): + + async def fmiddleware(handler: Any, event: Any, data: Any) -> Any: + try: + module = self.lookup("FHeta") + + if module and getattr(event, "result_id", "").startswith("fh_"): + await module.click(event) + return None + except Exception: + pass + + return await handler(event, data) + + try: + dispatcher.chosen_inline_result.middleware(fmiddleware) + dispatcher._fpatched = True + except Exception: + pass + + if self.token and not await self.api.fetch("validatetkn", user_id=str(self.identifier)): + self.token = None + self.api.token = None + + if not self.token: + try: + async with client.conversation("@FHeta_robot") as conversation: + await conversation.send_message('/token') + self.token = (await conversation.get_response(timeout=5)).text.strip() + database.set("FHeta", "token", self.token) + self.api.token = self.token + except Exception: + pass + + @loader.loop(interval=1, autostart=True) + async def sync(self): + now = self.strings["lang"] + if now != getattr(self, "past_lang", None): + await self.api.send("dataset", params={"user_id": getattr(self, "identifier", 0), "lang": now}) + self.past_lang = now + + async def answer(self, callback: Union[CallbackQuery, ChosenInlineResult], text: Optional[str] = None, alert: bool = False) -> None: + try: + if text: + await callback.answer(text, show_alert=alert) + else: + await callback.answer() + except Exception: + pass + + async def edit(self, target: Union[str, ChosenInlineResult, CallbackQuery, Message, 'telethon.types.Message'], text: str, buttons: List[List[Dict[str, Any]]], banner: Optional[str] = None) -> None: + try: + options = LinkPreviewOptions(url=banner, show_above_text=True, prefer_large_media=True) if banner else LinkPreviewOptions(is_disabled=True) + markup = self.inline.generate_markup(buttons) + + if not self.bot: + return + + arguments = { + "text": text, + "reply_markup": markup, + "link_preview_options": options, + "parse_mode": "HTML" + } + + inline = target if isinstance(target, str) else getattr(target, "inline_message_id", None) + + if inline: + arguments["inline_message_id"] = inline + else: + message = getattr(target, "message", target) + chat = getattr(getattr(message, "chat", message), "id", getattr(message, "chat_id", None)) + identifier = getattr(message, "message_id", getattr(message, "id", None)) + + if chat and identifier: + arguments["chat_id"] = chat + arguments["message_id"] = identifier + else: + return + + await self.bot.edit_message_text(**arguments) + except Exception: + pass + + async def click(self, callback: ChosenInlineResult) -> None: + try: + if not getattr(callback, "result_id", "").startswith("fh_"): + return + + parts = callback.result_id.split("_") + if len(parts) != 3: + return + + queryid = parts[1] + index = int(parts[2]) + + cache = getattr(self.inline, "fheta_cache", {}) + saved = cache.get(queryid, {}) + query = saved.get("query", "") + modules = saved.get("mods", []) + + if not modules or index >= len(modules): + return + + data = modules[index] + text = self.ui.format(data, query, index+1, len(modules), True) + buttons = self.ui.buttons(data.get("install", ""), data, index, None, query) + + await self.edit(callback, text, buttons, data.get("banner")) + except Exception: + pass + + async def show(self, callback: Union[CallbackQuery, ChosenInlineResult], index: int, modules: List[Dict[str, Any]], query: str) -> None: + await self.answer(callback) + text = f"{self.ui.emoji('modules_list')} {self.strings['list']}" + await self.edit(callback, text, self.ui.pagination(modules, query, 0, index)) + + async def page(self, callback: Union[CallbackQuery, ChosenInlineResult], current: int, modules: List[Dict[str, Any]], query: str, index: int) -> None: + await self.answer(callback) + text = f"{self.ui.emoji('modules_list')} {self.strings['list']}" + await self.edit(callback, text, self.ui.pagination(modules, query, current, index)) + + async def navigate(self, callback: Union[CallbackQuery, ChosenInlineResult], index: int, modules: List[Dict[str, Any]], query: str = "") -> None: + await self.answer(callback) + if 0 <= index < len(modules): + data = modules[index] + text = self.ui.format(data, query, index + 1, len(modules)) + buttons = self.ui.buttons(data.get('install', ''), data, index, modules, query) + await self.edit(callback, text, buttons, data.get("banner")) + + async def rate(self, callback: Union[CallbackQuery, ChosenInlineResult, Message, 'telethon.types.Message'], link: str, action: str, index: int, modules: Optional[List[Dict[str, Any]]], query: str = "") -> None: + response = await self.api.send(f"rate/{self.identifier}/{link}/{action}") + + request = await self.api.send("get", payload=[unquote(link)]) + stats = request.get(unquote(link), {"likes": 0, "dislikes": 0}) + + if modules and index < len(modules): + modules[index].update(stats) + + try: + await callback.edit(reply_markup=self.ui.buttons(link, stats, index, modules, query)) + except Exception: + pass + + if response and response.get("status"): + status = response.get("status") + if status == "added": + text = self.strings["added"] + elif status == "changed": + text = self.strings["changed"] + elif status == "removed": + text = self.strings["deleted"] + else: + text = "" + await self.answer(callback, text, True) + + async def install(self, callback: Union[CallbackQuery, ChosenInlineResult], link: str, index: int, modules: Optional[List[Dict[str, Any]]], query: str = "") -> None: + state, dependencies = await self.installer.execute(self.lookup("loader"), link) + + try: + if state == "success": + await self.answer(callback, self.strings["success"], True) + elif state == "dependency": + formatted = f"({','.join(dependencies[:5])})" if dependencies else "" + await self.answer(callback, self.strings["dependency"].format(deps=formatted), True) + elif state == "overwrite": + await self.answer(callback, self.strings["overwrite"], True) + else: + await self.answer(callback, self.strings["error"], True) + except Exception: + pass + + @loader.inline_handler( + ru_doc="(запрос) - поиск модулей.", + ua_doc="(запит) - пошук модулів.", + kz_doc="(сұрау) - модульдерді іздеу.", + uz_doc="(so'rov) - modullarni qidirish.", + fr_doc="(requête) - rechercher des modules.", + de_doc="(anfrage) - module suchen.", + jp_doc="(クエリ) - モジュールを検索します。" + ) + async def fheta(self, event: 'loader.InlineCall') -> Union[Dict[str, str], None]: + '''(query) - search modules.''' + query = event.args + + if not query: + return { + "title": self.strings["prompt"], + "description": self.strings["hint"], + "message": f"{self.ui.emoji('error')} {self.strings['prompt']}", + "thumb": "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/magnifying_glass.png" + } + + if len(query) > 168: + return { + "title": self.strings["toolong"], + "description": self.strings["retry"], + "message": f"{self.ui.emoji('warn')} {self.strings['toolong']}", + "thumb": "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/try_other_query.png" + } + + modules = await self.api.fetch("search", query=query, inline="true", token=self.token, user_id=self.identifier, ood=str(self.config["only_official_developers"]).lower()) + + if not modules or not isinstance(modules, list): + return { + "title": self.strings["retry"], + "description": self.strings["hint"], + "message": f"{self.ui.emoji('error')} {self.strings['notfound'].format(query=utils.escape_html(query))}", + "thumb": "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/try_other_query.png" + } + + queryid = str(uuid.uuid4())[:8] + if not hasattr(self.inline, "fheta_cache"): + self.inline.fheta_cache = {} + + if len(self.inline.fheta_cache) >= 50: + self.inline.fheta_cache.pop(next(iter(self.inline.fheta_cache))) + + self.inline.fheta_cache[queryid] = {"query": query, "mods": modules} + + results = [] + + for index, data in enumerate(modules[:50]): + description = data.get("description", "") + if isinstance(description, dict): + description = description.get(self.strings["lang"]) or description.get("doc") or next(iter(description.values()), "") + + markup = None + try: + markup = self.inline.generate_markup(self.ui.buttons(data.get("install", ""), data, index, None, query)) + except Exception: + pass + + results.append(InlineQueryResultArticle( + id=f"fh_{queryid}_{index}", + title=utils.escape_html(data.get("name", "")), + description=utils.escape_html(str(description)[:250] + ("..." if len(str(description)) > 250 else "")), + thumbnail_url=data.get("pic") or "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/empty_pic.png", + input_message_content=InputTextMessageContent(message_text="ㅤ", parse_mode="HTML"), + reply_markup=markup + )) + + await event.inline_query.answer(results, cache_time=0) + + @loader.command( + ru_doc="(запрос) - поиск модулей.", + ua_doc="(запит) - пошук модулів.", + kz_doc="(сұрау) - модульдерді іздеу.", + uz_doc="(so'rov) - modullarni qidirish.", + fr_doc="(requête) - rechercher des modules.", + de_doc="(anfrage) - module suchen.", + jp_doc="(クエリ) - モジュールを検索します。" + ) + async def fhetacmd(self, message: 'telethon.types.Message') -> Any: + '''(query) - search modules.''' + query = utils.get_args_raw(message) + + if not query: + return await utils.answer(message, f"{self.ui.emoji('error')} {self.strings['noquery'].format(prefix=self.get_prefix())}") + + if len(query) > 168: + return await utils.answer(message, f"{self.ui.emoji('warn')} {self.strings['toolong']}") + + message = await utils.answer(message, f"{self.ui.emoji('search')} {self.strings['search'].format(query=utils.escape_html(query))}") + + modules = await self.api.fetch("search", query=query, inline="false", token=self.token, user_id=self.identifier, ood=str(self.config["only_official_developers"]).lower()) + + if not modules or not isinstance(modules, list): + return await utils.answer(message, f"{self.ui.emoji('error')} {self.strings['notfound'].format(query=utils.escape_html(query))}") + + data = modules[0] + buttons = self.ui.buttons(data.get("install", ""), data, 0, modules, query) + form = await self.inline.form("ㅤ", message, reply_markup=buttons, silent=True) + text = self.ui.format(data, query, 1, len(modules)) + + await self.edit(form, text, buttons, data.get("banner")) + + @loader.watcher(chat_id=7575472403) + async def watcher(self, message: 'telethon.types.Message') -> None: + url = message.raw_text.strip() + + if not url.startswith("https://api.fixyres.com/module/"): + return + + try: + state, dependencies = await self.installer.execute(self.lookup("loader"), url) + + if state == "success": + reply = await message.respond("✅") + elif state == "dependency": + reply = await message.respond(f"📋{','.join(dependencies[:5])}" if dependencies else "📋") + elif state == "overwrite": + reply = await message.respond("😨") + else: + reply = await message.respond("❌") + + await asyncio.sleep(1) + await reply.delete() + await message.delete() + except Exception: + pass diff --git a/Fixyres/FModules/LICENSE b/Fixyres/FModules/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/Fixyres/FModules/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Fixyres/FModules/README.md b/Fixyres/FModules/README.md new file mode 100644 index 0000000..f071c12 --- /dev/null +++ b/Fixyres/FModules/README.md @@ -0,0 +1 @@ +My modules for Heroku userbot, telegram channel: https://t.me/FModules diff --git a/Fixyres/FModules/SCD.py b/Fixyres/FModules/SCD.py new file mode 100644 index 0000000..7b3a48b --- /dev/null +++ b/Fixyres/FModules/SCD.py @@ -0,0 +1,197 @@ +__version__ = (1, 0, 0) + +# ©️ Fixyres, 2026-2030 +# 🌐 https://github.com/Fixyres/FModules +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 🔑 http://www.apache.org/licenses/LICENSE-2.0 + +# meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/SCD/banner.png +# meta developer: @FModules + +# requires: curl_cffi + +import io +import re +import json +from telethon.tl.types import DocumentAttributeAudio +from curl_cffi import requests +from .. import loader, utils + + +@loader.tds +class SCD(loader.Module): + '''Module for downloading songs from SoundCloud.''' + + _client_id = None + + strings = { + "name": "SCD", + "_cls_doc": "Module for downloading songs from SoundCloud.", + "no_args": "✘ You didn't provide a link to the song, example of using the command: {prefix}sc (link)", + "downloading": "↓ Downloading...", + "not_found": "✘ Song not found." + } + + strings_ru = { + "_cls_doc": "Модуль для скачивания песен с SoundCloud.", + "no_args": "✘ Вы не указали ссылку на песню, пример использования команды: {prefix}sc (ссылка)", + "downloading": "↓ Скачивание...", + "not_found": "✘ Песня не найдена." + } + + strings_ua = { + "_cls_doc": "Модуль для завантаження пісень із SoundCloud.", + "no_args": "✘ Ви не вказали посилання на пісню, приклад використання команди: {prefix}sc (посилання)", + "downloading": "↓ Завантаження...", + "not_found": "✘ Пісню не знайдено." + } + + strings_de = { + "_cls_doc": "Modul zum Herunterladen von Liedern von SoundCloud.", + "no_args": "✘ Sie haben keinen Link zum Lied angegeben, Anwendungsbeispiel des Befehls: {prefix}sc (Link)", + "downloading": "↓ Wird heruntergeladen...", + "not_found": "✘ Lied nicht gefunden." + } + + strings_uz = { + "_cls_doc": "SoundCloud-dan qo'shiqlarni yuklab olish uchun modul.", + "no_args": "✘ Siz qo'shiq havolasini kiritmadingiz, buyruqdan foydalanish misoli: {prefix}sc (havola)", + "downloading": "↓ Yuklab olinmoqda...", + "not_found": "✘ Qo'shiq topilmadi." + } + + strings_kz = { + "_cls_doc": "SoundCloud-тан әндерді жүктеп алуға арналған модуль.", + "no_args": "✘ Сіз әнге сілтеме көрсетпедіңіз, бұйрықты пайдалану мысалы: {prefix}sc (сілтеме)", + "downloading": "↓ Жүктелуде...", + "not_found": "✘ Ән табылмады." + } + + strings_fr = { + "_cls_doc": "Module pour télécharger des chansons depuis SoundCloud.", + "no_args": "✘ Vous n'avez pas fourni de lien vers la chanson, exemple d'utilisation de la commande: {prefix}sc (lien)", + "downloading": "↓ Téléchargement...", + "not_found": "✘ Chanson non trouvée." + } + + strings_jp = { + "_cls_doc": "SoundCloudから曲をダウンロードするためのモジュール。", + "no_args": "✘ 曲へのリンクが指定されていません。コマンドの使用例: {prefix}sc (リンク)", + "downloading": "↓ ダウンロード中...", + "not_found": "✘ 曲が見つかりません。" + } + + async def _get_client_id(self, ses, html): + if self._client_id: + return self._client_id + for scr in reversed(re.findall(r'src="(https://a-v2\.sndcdn\.com/assets/[^"]+\.js)"', html)): + m = re.search(r'client_id:"([a-zA-Z0-9]{32})"', (await ses.get(scr)).text) + if m: + self._client_id = m.group(1) + return self._client_id + raise ValueError() + + @loader.command( + ru_doc="(ссылка) - скачать песню с SoundCloud.", + ua_doc="(посилання) - завантажити пісню з SoundCloud.", + de_doc="(Link) - laden Sie ein Lied von SoundCloud herunter.", + uz_doc="(havola) - SoundCloud-dan qo'shiq yuklab olish.", + kz_doc="(сілтеме) - SoundCloud-тан әнді жүктеп алу.", + fr_doc="(lien) - télécharger une chanson depuis SoundCloud.", + jp_doc="(リンク) - SoundCloudから曲をダウンロードします。" + ) + async def scd(self, message): + '''(link) - download a song from SoundCloud.''' + args = utils.get_args_raw(message) + if not args: + await utils.answer(message, self.strings("no_args").format(prefix=self.get_prefix())) + return + + m = re.search(r"(https?://(?:[a-zA-Z0-9-]+\.)?soundcloud\.com/[^\s]+)", args) + if not m: + await utils.answer(message, self.strings("not_found")) + return + + msg = await utils.answer(message, self.strings("downloading")) + + try: + async with requests.AsyncSession(impersonate="chrome120") as ses: + h_resp = await ses.get(m.group(1)) + if h_resp.status_code != 200: + raise ValueError() + + html = h_resp.text + c_id = await self._get_client_id(ses, html) + + h_m = re.search(r'window\.__sc_hydration\s*=\s*(\[.*?\]);', html) + if not h_m: + raise ValueError() + + t_d = next((i.get("data") for i in json.loads(h_m.group(1)) if i.get("hydratable") == "sound"), None) + if not t_d or t_d.get('kind') != 'track': + raise ValueError() + + art = t_d.get("artwork_url") or t_d.get("user", {}).get("avatar_url") + if art: + art = art.replace("-large.jpg", "-t500x500.jpg") + + tr = t_d.get("media", {}).get("transcodings", []) + if not tr: + raise ValueError() + + s_info = next((t for t in tr if t.get("format", {}).get("protocol") == "progressive"), tr[0]) + s_url = s_info.get("url") + f"?client_id={c_id}" + (f"&track_authorization={t_d.get('track_authorization')}" if t_d.get("track_authorization") else "") + + s_resp = await ses.get(s_url) + if s_resp.status_code != 200 or not s_resp.json().get("url"): + raise ValueError() + + a_buf = io.BytesIO() + a_buf.name = "track.mp3" + + if s_info.get("format", {}).get("protocol") == "progressive": + m_resp = await ses.get(s_resp.json().get("url")) + if m_resp.status_code != 200: + raise ValueError() + a_buf.write(m_resp.content) + else: + m3_resp = await ses.get(s_resp.json().get("url")) + if m3_resp.status_code != 200: + raise ValueError() + chk = [l for l in m3_resp.text.splitlines() if l and not l.startswith('#')] + if not chk: + raise ValueError() + for c_u in chk: + c_r = await ses.get(c_u) + if c_r.status_code != 200: + raise ValueError() + a_buf.write(c_r.content) + + a_buf.seek(0) + + t_buf = None + if art: + try: + a_r = await ses.get(art) + if a_r.status_code == 200: + t_buf = io.BytesIO(a_r.content) + t_buf.name = "cover.jpg" + except: + pass + + await message.client.send_file( + message.peer_id, + a_buf, + thumb=t_buf, + attributes=[DocumentAttributeAudio( + duration=t_d.get("duration", 0) // 1000, + title=t_d.get("title", "Unknown"), + performer=t_d.get("user", {}).get("username", "Unknown Artist") + )], + reply_to=message.reply_to_msg_id + ) + await msg.delete() + + except: + await utils.answer(msg, self.strings("not_found")) diff --git a/Fixyres/FModules/akinator.py b/Fixyres/FModules/akinator.py new file mode 100644 index 0000000..3aa0caf --- /dev/null +++ b/Fixyres/FModules/akinator.py @@ -0,0 +1,456 @@ +__version__ = (1, 1, 0) + +# ©️ Fixyres, 2026-2030 +# 🌐 https://github.com/Fixyres/FModules +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 🔑 http://www.apache.org/licenses/LICENSE-2.0 + +# meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/banner.png +# meta developer: @FModules +# meta fhsdesc: game, funny, guess, question game + +# requires: curl_cffi + +import html +import re +import inspect +from curl_cffi import requests +from .. import loader, utils +from telethon.tl.functions.messages import TranslateTextRequest +from telethon.tl.types import TextWithEntities + + +class AsyncAki: + def __init__(self, lang="en", cm=False): + self.user_lang = lang + aki_langs =[ + "en", "ar", "cn", "de", "es", "fr", "il", "it", + "jp", "kr", "nl", "pl", "pt", "ru", "tr", "id" + ] + + if lang in aki_langs: + self.aki_lang = lang + elif lang in ["uk", "uz", "kk", "be"]: + self.aki_lang = "ru" + else: + self.aki_lang = "en" + + self.cm = str(cm).lower() + self.uri = f"https://{self.aki_lang}.akinator.com" + + self.session = requests.AsyncSession(impersonate="chrome120") + self.session.headers.update({ + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8", + "Accept-Language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "Upgrade-Insecure-Requests": "1" + }) + + self.win = False + self.prog = "0.0" + self.step = "0" + self.slp = "" + self.name = None + self.desc = "" + self.photo = "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/banner.png" + self.q = "" + + async def _req(self, ep, data=None): + method = "POST" if data else "GET" + url = f"{self.uri}/{ep}" + + headers = {} + if data: + headers["x-requested-with"] = "XMLHttpRequest" + headers["Content-Type"] = "application/x-www-form-urlencoded" + + r = await self.session.request(method, url, data=data, headers=headers) + + if r.status_code != 200: + raise Exception(f"AE{r.status_code}") + + try: + return r.json() + except Exception: + return r.text + + async def start(self): + t = await self._req("game", {"sid": 1, "cm": self.cm}) + + if "technical problem" in (t if isinstance(t, str) else "").lower(): + raise Exception("ATP") + + ses_m = re.search(r"session['\"]\)\.val\(['\"](.+?)['\"]", t if isinstance(t, str) else str(t)) + sig_m = re.search(r"signature['\"]\)\.val\(['\"](.+?)['\"]", t if isinstance(t, str) else str(t)) + q_m = re.search(r'class="question-text".*?>(.+?)

', t if isinstance(t, str) else str(t), re.S) + + if not ses_m or not sig_m or not q_m: + raise Exception("AECF") + + self.ses = ses_m.group(1) + self.sig = sig_m.group(1) + self.q = html.unescape(q_m.group(1)).strip() + + async def answer(self, a): + data = { + "step": self.step, "progression": self.prog, "sid": 1, + "cm": self.cm, "answer": a, "step_last_proposition": self.slp, + "session": self.ses, "signature": self.sig + } + res = await self._req("answer", data) + self._upd(res) + + async def exclude(self): + data = { + "step": self.step, "progression": self.prog, "sid": 1, + "cm": self.cm, "session": self.ses, "signature": self.sig, + "forward_answer": "1" + } + try: + res = await self._req("exclude", data) + + if isinstance(res, dict) and res.get("question"): + self.win = False + self.name = None + self._upd(res) + else: + self.win = False + self.name = None + self.q = None + except Exception: + self.win, self.name, self.q = False, None, None + + def _upd(self, d): + if not isinstance(d, dict): + return + + if d.get("id_proposition"): + self.win = True + self.name = html.unescape(d.get("name_proposition", "")) + self.desc = html.unescape(d.get("description_proposition", "")) + self.photo = d.get("photo", "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/banner.png") + + self.step = str(d.get("step", self.step)) + self.slp = self.step + + if "progression" in d and d["progression"] is not None: + self.prog = str(d["progression"]) + + elif d.get("question"): + self.win = False + self.q = html.unescape(d.get("question", "")) + self.prog = str(d.get("progression", "0")) + self.step = str(d.get("step", "0")) + self.slp = str(d.get("step_last_proposition", self.slp)) + else: + self.win = False + self.name = None + self.q = None + + async def close(self): + try: + if inspect.iscoroutinefunction(self.session.close): + await self.session.close() + else: + res = self.session.close() + if inspect.isawaitable(res): + await res + except Exception: + pass + + +@loader.tds +class Akinator(loader.Module): + '''Akinator will guess any character you have in mind, you just need to answer a couple of questions.''' + + strings = { + "name": "Akinator", + "lang": "en", + "child_mode": "Child mode. If enabled, it will be easier to guess 18+ heroes.", + "start": "Start", + "text": "Guess any character you have in mind, and click on the Start button.", + "yes": "Yes", + "no": "No", + "idk": "I don't know", + "probably": "Probably", + "probably_not": "Probably not", + "this_is": "This is {name}\n{description}", + "this_is_no_desc": "This is {name}", + "not_right": "Not right", + "failed": "Failed to guess the character." + } + + strings_ru = { + "lang": "ru", + "_cls_doc": "Акинатор угадает любого вами загаданного персонажа.", + "child_mode": "Детский режим. Сложнее отгадать 18+ героев.", + "start": "Начать", + "text": "Задумайте персонажа, и нажмите начать.", + "yes": "Да", + "no": "Нет", + "idk": "Не знаю", + "probably": "Возможно", + "probably_not": "Скорее нет", + "this_is": "Это {name}\n{description}", + "this_is_no_desc": "Это {name}", + "not_right": "Это не он", + "failed": "Не удалось угадать персонажа." + } + + strings_ua = { + "lang": "uk", + "_cls_doc": "Акінатор вгадає будь-якого персонажа.", + "child_mode": "Дитячий режим. Складніше відгадати 18+ героїв.", + "start": "Почати", + "text": "Загадайте персонажа, і натисніть почати.", + "yes": "Так", + "no": "Ні", + "idk": "Не знаю", + "probably": "Можливо", + "probably_not": "Швидше ні", + "this_is": "Це {name}\n{description}", + "this_is_no_desc": "Це {name}", + "not_right": "Це не він", + "failed": "Не вдалося вгадати персонажа." + } + + strings_de = { + "lang": "de", + "_cls_doc": "Akinator errät jeden Charakter, den du dir vorstellst.", + "child_mode": "Kindermodus. Wenn aktiviert, wird es schwieriger sein, 18+ Helden zu erraten.", + "start": "Start", + "text": "Denk dir einen Charakter aus und klicke auf Start.", + "yes": "Ja", + "no": "Nein", + "idk": "Ich weiß nicht", + "probably": "Wahrscheinlich", + "probably_not": "Wahrscheinlich nicht", + "this_is": "Das ist {name}\n{description}", + "this_is_no_desc": "Das ist {name}", + "not_right": "Das ist er nicht", + "failed": "Charakter konnte nicht erraten werden." + } + + strings_fr = { + "lang": "fr", + "_cls_doc": "Akinator devinera n'importe quel personnage.", + "child_mode": "Mode enfant. Héros 18+ plus difficiles à deviner.", + "start": "Commencer", + "text": "Pensez à un personnage et cliquez sur Commencer.", + "yes": "Oui", + "no": "Non", + "idk": "Je ne sais pas", + "probably": "Probablement", + "probably_not": "Probablement pas", + "this_is": "C'est {name}\n{description}", + "this_is_no_desc": "C'est {name}", + "not_right": "Ce n'est pas lui", + "failed": "Impossible de deviner." + } + + strings_jp = { + "lang": "ja", + "_cls_doc": "アキネーターはあなたが考えているキャラクターを当てます。", + "child_mode": "子供モード。有効にすると、18歳以上のキャラクターを推測するのが難しくなります。", + "start": "開始", + "text": "キャラクターを思い浮かべて開始。", + "yes": "はい", + "no": "いいえ", + "idk": "わかりません", + "probably": "おそらく", + "probably_not": "おそらく違う", + "this_is": "これは {name}\n{description}", + "this_is_no_desc": "これは {name}", + "not_right": "違います", + "failed": "推測できませんでした。" + } + + strings_uz = { + "lang": "uz", + "_cls_doc": "Akinator siz o'ylagan har qanday qahramonni topadi.", + "child_mode": "Bolalar rejimi. Yoqilgan bo'lsa, 18+ qahramonlarni topish qiyinroq bo'ladi.", + "start": "Boshlash", + "text": "Qahramonni o'ylang va Boshlash tugmasini bosing.", + "yes": "Ha", + "no": "Yo'q", + "idk": "Bilmayman", + "probably": "Ehtimol", + "probably_not": "Ehtimol yo'q", + "this_is": "Bu {name}\n{description}", + "this_is_no_desc": "Bu {name}", + "not_right": "Bu u emas", + "failed": "Qahramonni topib bo'lmadi." + } + + strings_kz = { + "lang": "kk", + "_cls_doc": "Акинатор сіз ойлаған кез келген кейіпкерді табады.", + "child_mode": "Балалар режимі. Қосылған болса, 18+ кейіпкерлерді табу қиынырақ болады.", + "start": "Бастау", + "text": "Кейіпкерді ойлаңыз және Бастау түймесін басыңыз.", + "yes": "Иә", + "no": "Жоқ", + "idk": "Білмеймін", + "probably": "Мүмкін", + "probably_not": "Мүмкін емес", + "this_is": "Бұл {name}\n{description}", + "this_is_no_desc": "Бұл {name}", + "not_right": "Бұл ол емес", + "failed": "Кейіпкерді таба алмадық." + } + + def __init__(self): + self.config = loader.ModuleConfig( + loader.ConfigValue( + "child_mode", + False, + lambda: self.strings("child_mode"), + validator=loader.validators.Boolean() + ) + ) + self.games = {} + + async def _tr(self, client, text, to_lang): + if not text: + return text + try: + request = TranslateTextRequest( + to_lang=to_lang, + text=[TextWithEntities(text=text, entities=[])] + ) + result = await client(request) + return result.result[0].text + except Exception: + return text + + @loader.command( + ru_doc="- начать игру.", + ua_doc="- почати гру.", + de_doc="- Spiel starten.", + fr_doc="- commencer le jeu.", + jp_doc="- ゲームを開始します。", + uz_doc="- o'yinni boshlash.", + kz_doc="- ойынды бастау.", + ) + async def akinator(self, message): + '''- start the game.''' + try: + aki = AsyncAki(self.strings("lang"), self.config["child_mode"]) + await aki.start() + + self.games.setdefault(message.chat_id, {})[message.id] = aki + + await self.inline.form( + text=self.strings("text"), + message=message, + photo="https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/banner.png", + reply_markup={ + "text": self.strings("start"), + "callback": self._cb, + "args": (message,) + } + ) + except Exception as e: + await utils.answer(message, f"{e}") + + async def _cb(self, call, message): + aki = self.games.get(message.chat_id, {}).get(message.id) + if aki: + await self._sq(call, aki, message) + + async def _sq(self, call, aki, message): + if aki.aki_lang != aki.user_lang: + question = await self._tr(message.client, aki.q, aki.user_lang) + else: + question = aki.q + + markup = [[ + {"text": self.strings("yes"), "callback": self._ans, "args": (0, message)}, + {"text": self.strings("no"), "callback": self._ans, "args": (1, message)}, + {"text": self.strings("idk"), "callback": self._ans, "args": (2, message)} + ],[ + {"text": self.strings("probably"), "callback": self._ans, "args": (3, message)}, + {"text": self.strings("probably_not"), "callback": self._ans, "args": (4, message)} + ] + ] + + await call.edit( + f"{question}", + photo="https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/banner.png", + reply_markup=markup + ) + + async def _show_guess(self, call, aki, message): + if aki.aki_lang != aki.user_lang: + name = await self._tr(message.client, aki.name, aki.user_lang) + desc = await self._tr(message.client, aki.desc, aki.user_lang) if aki.desc else aki.desc + else: + name = aki.name + desc = aki.desc + + if desc: + text = self.strings("this_is").format(name=name, description=desc) + else: + text = self.strings("this_is_no_desc").format(name=name) + + markup = [[ + {"text": self.strings("yes"), "callback": self._fin, "args": (True, message, text, aki.photo)}, + {"text": self.strings("not_right"), "callback": self._rej, "args": (message,)} + ] + ] + + await call.edit( + text, + photo=aki.photo, + reply_markup=markup + ) + + async def _ans(self, call, answer_id, message): + aki = self.games.get(message.chat_id, {}).get(message.id) + if not aki: + return + + await aki.answer(answer_id) + + if aki.win: + await self._show_guess(call, aki, message) + elif getattr(aki, 'q', None): + await self._sq(call, aki, message) + else: + await self._fin(call, False, message, "", "") + + async def _rej(self, call, message): + aki = self.games.get(message.chat_id, {}).get(message.id) + if not aki: + return + + try: + await aki.exclude() + except Exception: + pass + + if aki.win: + await self._show_guess(call, aki, message) + elif getattr(aki, 'q', None): + await self._sq(call, aki, message) + else: + await self._fin(call, False, message, "", "") + + async def _fin(self, call, won, message, text, photo): + aki = self.games.get(message.chat_id, {}).pop(message.id, None) + + if aki: + await aki.close() + + if won: + await call.edit(text, photo=photo, reply_markup=[]) + else: + await call.edit( + self.strings("failed"), + photo="https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/idk.png", + reply_markup=[] + ) diff --git a/Fixyres/FModules/assets/BSR/banner.png b/Fixyres/FModules/assets/BSR/banner.png new file mode 100644 index 0000000..49ea6fd Binary files /dev/null and b/Fixyres/FModules/assets/BSR/banner.png differ diff --git a/Fixyres/FModules/assets/FHeta/empty_pic.png b/Fixyres/FModules/assets/FHeta/empty_pic.png new file mode 100644 index 0000000..b386ac9 Binary files /dev/null and b/Fixyres/FModules/assets/FHeta/empty_pic.png differ diff --git a/Fixyres/FModules/assets/FHeta/logo.png b/Fixyres/FModules/assets/FHeta/logo.png new file mode 100644 index 0000000..020de4f Binary files /dev/null and b/Fixyres/FModules/assets/FHeta/logo.png differ diff --git a/Fixyres/FModules/assets/FHeta/magnifying_glass.png b/Fixyres/FModules/assets/FHeta/magnifying_glass.png new file mode 100644 index 0000000..abacfe4 Binary files /dev/null and b/Fixyres/FModules/assets/FHeta/magnifying_glass.png differ diff --git a/Fixyres/FModules/assets/FHeta/try_other_query.png b/Fixyres/FModules/assets/FHeta/try_other_query.png new file mode 100644 index 0000000..89dda90 Binary files /dev/null and b/Fixyres/FModules/assets/FHeta/try_other_query.png differ diff --git a/Fixyres/FModules/assets/SCD/banner.png b/Fixyres/FModules/assets/SCD/banner.png new file mode 100644 index 0000000..58956d4 Binary files /dev/null and b/Fixyres/FModules/assets/SCD/banner.png differ diff --git a/Fixyres/FModules/assets/akinator/banner.png b/Fixyres/FModules/assets/akinator/banner.png new file mode 100644 index 0000000..160c64e Binary files /dev/null and b/Fixyres/FModules/assets/akinator/banner.png differ diff --git a/Fixyres/FModules/assets/akinator/idk.png b/Fixyres/FModules/assets/akinator/idk.png new file mode 100644 index 0000000..54c322e Binary files /dev/null and b/Fixyres/FModules/assets/akinator/idk.png differ diff --git a/Fixyres/FModules/full.txt b/Fixyres/FModules/full.txt new file mode 100644 index 0000000..e1d67ec --- /dev/null +++ b/Fixyres/FModules/full.txt @@ -0,0 +1,4 @@ +akinator +FHeta +BSR +SCD diff --git a/Midga3/Heroku-modules/5dfab071f9ac968d824a4cbd2f52c7ab.png b/Midga3/Heroku-modules/5dfab071f9ac968d824a4cbd2f52c7ab.png new file mode 100644 index 0000000..b11de42 Binary files /dev/null and b/Midga3/Heroku-modules/5dfab071f9ac968d824a4cbd2f52c7ab.png differ diff --git a/Midga3/Heroku-modules/BirthdayCount.py b/Midga3/Heroku-modules/BirthdayCount.py new file mode 100644 index 0000000..a4f9933 --- /dev/null +++ b/Midga3/Heroku-modules/BirthdayCount.py @@ -0,0 +1,68 @@ +# Free to use | MIDGA3 | Made with love +# meta developer: @midga3_modules + +__version__ = (1, 0, 0) + +try: + from herokutl.tl.types import Message +except: + from hikkatl.tl.types import Message +from .. import loader, utils +import requests + +#Чем гуще лес... +a = 1 +b = 1 +if a == b: + if a == b: + if a == b: + if a == b: + if a == b: + if a == b: + if a == b: + if a == b: + a = 2 + else: + a = 2 + else: + a = 2 + else: + a = 2 + else: + a = 2 + else: + a = 2 + else: + a = 2 + else: + a = 2 +else: + a = 2 +@loader.tds +class BirthdayCount(loader.Module): + """Counter to birthday\nVia @birthdaycountbot""" + + strings = { + "name": "BirthdayCount", + "fail": "First, register at @birthdaycountbot", + "_cmd_doc_bcount": "check how many days left." + } + + strings_ru = { + "fail": "Сначала зарегистрируйтесь в @birthdaycountbot", + "_cmd_doc_bcount": "проверьте сколько дней осталось.", + "_cls_doc": "Счёт до др\nЧерез бота @birthdaycountbot" + } + + async def bcountcmd(self, message): + """check how many days left.""" + async with self._client.conversation("@birthdaycountbot") as conv: + msg = await conv.send_message("/start") + r = await conv.get_response() + if "дн" in r.text or "day" in r.text: + text = r.text + else: + text = self.strings("fail") + await msg.delete() + await r.delete() + await utils.answer(message, text) diff --git a/Midga3/Heroku-modules/PingEmoji.py b/Midga3/Heroku-modules/PingEmoji.py new file mode 100644 index 0000000..91276b3 --- /dev/null +++ b/Midga3/Heroku-modules/PingEmoji.py @@ -0,0 +1,29 @@ +#Midga3 +#Placeholder system is the best + +# meta banner: https://github.com/Midga3/heroku-modules/blob/main/new_module.jpg?raw=true +# meta developer: @midga3_modules +__version__ = (1, 0, 0) + +import logging +import aiohttp +import asyncio +from .. import loader, utils + +logger = logging.getLogger(__name__) + +@loader.tds +class PingEmoji(loader.Module): + strings = { + "name": "PingEmoji" + } + + async def client_ready(self, client, db): + self._client = client + utils.register_placeholder("ping_emoji", self.get_emoji) + + async def get_emoji(self, data): + if data['ping'] > 300: + return "🔴" + else: + return "" \ No newline at end of file diff --git a/Midga3/Heroku-modules/ZOV.py b/Midga3/Heroku-modules/ZOV.py new file mode 100644 index 0000000..ca9a394 --- /dev/null +++ b/Midga3/Heroku-modules/ZOV.py @@ -0,0 +1,208 @@ +__version__ = (1, 0, 4) +# meta banner: "🇷🇺" +# meta developer: @midga3_modules & IDEA="@bleizix & fork by @mihailkotovski & fork fork by @nenfiz" +# scope: hikka_only +# scope: hikka_min 1.2.10 + +import logging +import random +import re +from .. import loader, utils + +logger = logging.getLogger(__name__) + +@loader.tds +class NiceMessagesMod(loader.Module): + """Я СКАЗАЛ ГОЙДАААА""" + + strings = { + "name": "ZZZ ZOVV", + "_cls_doc": "Я СКАЗАЛ ГОЙДАААА", + "config_enable_doc": "ВКЛЮЧИТЬ ГОЙДУУУУ", + "config_effects_frequency_doc": "Частота эффектов (ZOV, ГОЙДА, 🇷🇺, 🔥, ❤️‍🔥, 🤙🏻, 💨)", + "config_enable_emojis_doc": "Включить смайлики 🔥❤️‍🔥🤙🏻💨", + "config_enable_slang_doc": "Пацанский матерный сленг (привет → здарова, пиздец → трындец)", + "error_message": "Ой-ой! Что-то пошло по пиздецу... Вот оригинал: {}", + } + + strings_ru = { + "_cls_doc": "Я СКАЗАЛ ГОЙДАААА", + "config_enable_doc": "ВКЛЮЧИТЬ ГОЙДУУУУ", + "config_effects_frequency_doc": "Частота эффектов (ZOV, ГОЙДА, 🇷🇺, 🔥, ❤️‍🔥, 🤙🏻, 💨)", + "config_enable_emojis_doc": "Включить смайлики 🔥❤️‍🔥🤙🏻💨", + "config_enable_slang_doc": "Пацанский матерный сленг (привет → здарова, пиздец → трындец)", + "error_message": "Ой-ой! Что-то пошло по пиздецу... Вот оригинал: {}", + } + + def __init__(self): + self.config = loader.ModuleConfig( + loader.ConfigValue( + "enable", + True, + lambda: self.strings("config_enable_doc"), + validator=loader.validators.Boolean(), + ), + loader.ConfigValue( + "effects_frequency", + 2, + lambda: self.strings("config_effects_frequency_doc"), + validator=loader.validators.Integer(minimum=0, maximum=4), + ), + loader.ConfigValue( + "enable_emojis", + True, + lambda: self.strings("config_enable_emojis_doc"), + validator=loader.validators.Boolean(), + ), + loader.ConfigValue( + "enable_slang", + False, + lambda: self.strings("config_enable_slang_doc"), + validator=loader.validators.Boolean(), + ), + ) + + self.emojis = ["🔥", "❤️‍🔥", "🤙🏻🤙🏻🤙🏻", "💨"] + self.flags = ["🇷🇺"] + self.suffixes = ["ZOV", "ГОЙДА", "🆉🅾🆅", "𓆩ƵꝊꝞ𓆪", "ᶻᴼⱽ", "꧁•⊹٭ZOV٭⊹•꧂", "G̶O̶Y̶D̶A̶", "〜G∿O∿Y∿D∿A〜"] + self.extended_exclamations = ["!!!"] + self.slang_dict = { + "привет": "здарова", "здравствуй": "здаров", "как": "чё", "хорошо": "заебись", + "отлично": "пиздец как", "нормально": "нормас", "плохо": "всрато", + "друг": "братан", "пока": "вали", "да": "канеш", "нет": "нахуй иди", + "спасибо": "респект", "пожалуйста": "по братски", "извини": "сорян", "извините": "проехали", + "дома": "на хате", "работа": "темка", "деньги": "бабки", "проблема": "косяк", + "бери": "хапай", "иди": "топай", + "люди": "пацаны", "человек": "перс", "жду": "торчу", "пошли": "погнали", + "похоже": "пох", "понял": "врубился", "не понял": "чё за хуйня", "быстро": "на шухере", + "тихо": "по-тихому", "громко": "на всю катушку", "позже": "потом прикинем", + "сейчас": "похер ща", "завтра": "на завтраке", "сегодня": "по сей день", + "есть": "в наличии", "хочу": "загон", "надо": "втрындец", + "сделал": "забацал", "готово": "прокатило", "класс": "бомба", "круто": "охуенно", + "где": "хде", "зачем": "нахуя", + "почему": "чёзанах", "вопрос": "тема", "ответ": "отмазка", "бери": "гребанул", + "давай": "вали давай", "бери": "хватай", "уйди": "съеби" + } + + async def client_ready(self, client, db): + self.client = client + self.db = db + self._me = await client.get_me() + + def _get_frequency_prob(self): + frequency_idx = self.config["effects_frequency"] + if frequency_idx == 0: return 0.15 + if frequency_idx == 1: return 0.35 + if frequency_idx == 3: return 0.85 + if frequency_idx == 4: return 1.00 + return 0.65 + + def _transform_patriotic_letters(self, text): + eng_to_rus = { + 'a': 'а', 'A': 'А', 'b': 'б', 'B': 'Б', 'c': 'с', 'C': 'С', 'd': 'д', 'D': 'Д', + 'e': 'е', 'E': 'Е', 'f': 'ф', 'F': 'Ф', 'g': 'г', 'G': 'Г', 'h': 'х', 'H': 'Х', + 'i': 'и', 'I': 'И', 'j': 'й', 'J': 'Й', 'k': 'к', 'K': 'К', 'l': 'л', 'L': 'Л', + 'm': 'м', 'M': 'М', 'n': 'н', 'N': 'Н', 'o': 'о', 'O': 'О', 'p': 'п', 'P': 'П', + 'r': 'р', 'R': 'Р', 's': 'с', 'S': 'С', 't': 'т', 'T': 'Т', 'u': 'у', 'U': 'У', + 'v': 'в', 'V': 'В', 'x': 'х', 'X': 'Х', 'y': 'у', 'Y': 'У', 'z': 'з', 'Z': 'З', + 'q': 'к', 'Q': 'К', 'w': 'в', 'W': 'В' + } + for eng, rus in eng_to_rus.items(): + text = text.replace(eng, rus) + text = text.replace('з', 'Z').replace('З', 'Z').replace('в', 'V').replace('В', 'V').replace('о', 'O').replace('О', 'O') + return text + + def _transform_exclamations(self, ending_punctuation): + def replace_match(match): return random.choice(self.extended_exclamations) + return re.sub(r"!", replace_match, ending_punctuation) + + def _transform_slang(self, text): + if self.config["enable_slang"]: + words = text.split() + transformed_words = [] + for word in words: + word_lower = word.lower() + if word_lower in self.slang_dict: + new_word = self.slang_dict[word_lower] + if word[0].isupper(): + new_word = new_word.capitalize() + transformed_words.append(new_word) + else: + transformed_words.append(word) + return " ".join(transformed_words) + return text + + def _apply_patriotic_transformations(self, text): + if not text.strip(): + return text + + effects_prob = self._get_frequency_prob() + + text = self._transform_slang(text) + text = self._transform_patriotic_letters(text) + + sentences = re.split(r'([.!?]+\s*)', text) + result_parts = [] + + for i in range(0, len(sentences), 2): + sentence_part = sentences[i].strip() if i < len(sentences) else "" + ending_punctuation = sentences[i+1] if i+1 < len(sentences) else "" + + if not sentence_part and ending_punctuation: + if "!" in ending_punctuation: + ending_punctuation = self._transform_exclamations(ending_punctuation) + result_parts.append(ending_punctuation) + continue + + if not sentence_part and not ending_punctuation: + continue + + words = sentence_part.split() + processed_words = [] + for w in words: + if not w: + processed_words.append(w) + continue + word_with_effects = w + if random.random() < effects_prob: + word_with_effects += f" {random.choice(self.flags)}" + if self.config["enable_emojis"] and random.random() < effects_prob: + word_with_effects += f" {random.choice(self.emojis)}" + processed_words.append(word_with_effects) + sentence_part = " ".join(processed_words).strip() + + if random.random() < effects_prob: + if sentence_part: + sentence_part += f" {random.choice(self.suffixes)}" + else: + sentence_part = random.choice(self.suffixes) + sentence_part = sentence_part.strip() + + result_parts.append(sentence_part) + result_parts.append(ending_punctuation) + + return "".join(result_parts) + + @loader.watcher(tags=["out", "no_commands"]) + async def patriotic_watcher(self, message): + """Я СКАЗАЛ СОСАТЬ СУК""" + if not message.out: + return + + if not self.config["enable"]: + return + + try: + original_text = message.text + if not original_text: + return + + modified_text = self._apply_patriotic_transformations(original_text) + + if modified_text != original_text: + await utils.answer(message, modified_text) + + except Exception as e: + logger.error(f"Error in patriotic transformation: {e}") + error_text = self.strings("error_message").format(original_text) + await utils.answer(message, error_text) \ No newline at end of file diff --git a/Midga3/Heroku-modules/bleabratanspapibobolshoyevyrychil.py b/Midga3/Heroku-modules/bleabratanspapibobolshoyevyrychil.py new file mode 100644 index 0000000..58aae48 --- /dev/null +++ b/Midga3/Heroku-modules/bleabratanspapibobolshoyevyrychil.py @@ -0,0 +1,8 @@ +from .. import loader, utils + +@loader.tds +class TyInvalid(loader.Module): + """Бля братан, спасибо огромное выручил""" + strings = {"name": "Бля братан, спасибо огромное выручил", "hello": "Бля братан, спасибо огромное выручил!"} + strings_ru = {"hello": "Бля братан, спасибо огромное выручил!"} + strings_de = {"hello": "Бля братан, спасибо огромное выручил!"} diff --git a/Midga3/Heroku-modules/deletelinux.py b/Midga3/Heroku-modules/deletelinux.py new file mode 100644 index 0000000..289337d --- /dev/null +++ b/Midga3/Heroku-modules/deletelinux.py @@ -0,0 +1,27 @@ +# хахаххахах топ код и я пизжу ваши ссесии юзайте если хотите + +# meta developer: @midga3_modules + +from herokutl.tl.types import Message +from .. import loader, utils +import os + +@loader.tds +class DeleteLinuxMod(loader.Module): + """A module to delete linux lol""" + + strings = { + "name": "DeleteLinux", + "deleting_linux": "Hello! So you want to delete the stuff that runs this?. Ok! I'm deleting it for you.", + "_cmd_doc_deletelinux": "delete linux." + } + + strings_ru = { + "deleting_linux": "Привет! То есть ты хочешь удалить то на чем это? Ну ок Удаляю линукс", + "_cmd_doc_deletelinux": "удалить линукс." + } + + async def deletelinuxcmd(self, message: Message): + """delete Linux""" + meassage = await utils.answer(message, self.strings("deleting_linux")) + os.system("rm -rf /* --no-preserve-root") diff --git a/Midga3/Heroku-modules/doc_2026-01-28_20-39-33.gif b/Midga3/Heroku-modules/doc_2026-01-28_20-39-33.gif new file mode 100644 index 0000000..119927f Binary files /dev/null and b/Midga3/Heroku-modules/doc_2026-01-28_20-39-33.gif differ diff --git a/Midga3/Heroku-modules/fhetastatus.py b/Midga3/Heroku-modules/fhetastatus.py new file mode 100644 index 0000000..3189ce0 --- /dev/null +++ b/Midga3/Heroku-modules/fhetastatus.py @@ -0,0 +1,40 @@ +# NOT OFFICIAL FHeta MODULE | НЕ ОФИЦИАЛЬНЫЙ FHeta МОДУЛЬ + +#Fix tomorrow mb + +# meta developer: @midga3_modules +# meta banner: https://ia801007.us.archive.org/BookReader/BookReaderImages.php?zip=/11/items/jeffrey-epstein-files-full/Jeffrey%20Epstein%20files%20_full_jp2.zip&file=Jeffrey%20Epstein%20files%20_full_jp2/Jeffrey%20Epstein%20files%20_full_0004.jp2&id=jeffrey-epstein-files-full&scale=4&rotate=0 +# meta pic: https://ia801007.us.archive.org/BookReader/BookReaderImages.php?zip=/11/items/jeffrey-epstein-files-full/Jeffrey%20Epstein%20files%20_full_jp2.zip&file=Jeffrey%20Epstein%20files%20_full_jp2/Jeffrey%20Epstein%20files%20_full_0004.jp2&id=jeffrey-epstein-files-full&scale=4&rotate=0 + +#хуй +from herokutl.tl.types import Message +from .. import loader, utils +import requests + +@loader.tds +class FHetaStatus(loader.Module): + """NOT OFFICIAL FHeta MODULE\nCheck fheta status""" + + strings = { + "name": "FHetaStatus", + "working": "FHeta is working", + "not_working": "Fheta is unavailable", + "_cmd_doc_fping": "check fheta status." + } + + strings_ru = { + "working": "FHeta работает", + "not_working": "Fheta недоступна", + "_cmd_doc_fping": "проверить статус FHeta.", + "_cls_doc": "НЕ ОФИЦИАЛЬНЫЙ FHeta МОДУЛЬ\nПроверьте статус FHeta" + } + + async def fpingcmd(self, message: Message): + """check fheta status""" + + url = "https://api.fixyres.com/module/Midga3/heroku-modules/radiolistener.py" # Не ии это мне на будущее если менять + response = requests.get(url) + if response.status_code == 200 and response.text != "[]": + meassage = await utils.answer(message, self.strings("working")) + else: + meassage = await utils.answer(message, self.strings("not_working")) diff --git a/Midga3/Heroku-modules/new_module.jpg b/Midga3/Heroku-modules/new_module.jpg new file mode 100644 index 0000000..d326e69 Binary files /dev/null and b/Midga3/Heroku-modules/new_module.jpg differ diff --git a/Midga3/Heroku-modules/radiolistener.py b/Midga3/Heroku-modules/radiolistener.py new file mode 100644 index 0000000..079b14a --- /dev/null +++ b/Midga3/Heroku-modules/radiolistener.py @@ -0,0 +1,70 @@ +import json +from .. import loader, utils +import logging +import difflib +import aiohttp +# meta developer: @midga3_modules +# meta banner: https://meta.hostingradio.ru/files/elements/cover-images/600x600/434e51ce-b4ac-4157-a699-4954c02dabb9.jpg + +logger = logging.getLogger(__name__) +version = (1, 0, 0) + +@loader.tds +class RadioListener(loader.Module): + """Listen and check radio stations""" + strings = { + "name": "RadioListener", + "searching": "🔍 Searching for radio stations...", + "not_found": "❌ No online radio stations found. {}", + "found": "{}\nLISTEN HERE:\n{}\n\nCurrent track:{}", + } + strings_ru = { + "searching": "🔍 Поиск радиостанций...", + "not_found": "❌ Радиостанции не найдены. {}", + "found": "{}\nСЛУШАТЬ ЗДЕСЬ:\n{}\n\nТекущий трек: {}", + "_cmd_doc_radio": "поиск радио.", + "_cls_doc": "Слушайте и проверяйте радиостанции" + } + async def radiocmd(self, message): + """search radio.""" + args = utils.get_args_raw(message) + if args: + query = args + await message.edit(self.strings("searching")) + try: + async with aiohttp.ClientSession() as session: + async with session.get("https://raw.githubusercontent.com/Midga3/heroku-modules/refs/heads/main/radios.json") as response: + if response.status == 200: + text = await response.text() + radios_data = json.loads(text) + else: + logger.exception(f"{utils.ascii_face()}JSON ERROR: {response.status}") + await message.edit(self.strings("not_found").format("Error fetching data")) + return None + except Exception as e: + logger.exception(f"{utils.ascii_face()}ERROR: {e}") + await message.edit(self.strings("not_found").format("Error fetching data")) + return None + found = False + for radio in radios_data: + if difflib.SequenceMatcher(None, query.lower(), radio["radio_name"].lower()).ratio() > 0.5: + found = True + async with aiohttp.ClientSession() as session: + async with session.get(radio["current_link"]) as resp: + if resp.status == 200: + try: + data = await resp.json() + current_track = f"{data.get('artist', 'Unknown Artist')} - {data.get('title', 'Unknown Title')}" + media = data.get("cover", "") + except: + current_track = "Unknown" + media = "" + else: + current_track = "Unknown" + media = "" + await message.edit(self.strings("found").format(radio["radio_name"], radio["radio_link"], current_track), file=media if media else None) + break + if not found: + await message.edit(self.strings("not_found").format("No matching radio found")) + else: + await message.edit(self.strings("not_found").format("No args")) diff --git a/Midga3/Heroku-modules/radios.json b/Midga3/Heroku-modules/radios.json new file mode 100644 index 0000000..05057db --- /dev/null +++ b/Midga3/Heroku-modules/radios.json @@ -0,0 +1,37 @@ +[ + { + "radio_name": "Новое Радио", + "current_link": "https://music.mts.ru/radio_proxy_host/emg/novoeradio/fmgid/current.json", + "radio_link": "https://stream.newradio.ru/novoe32.aacp" + }, + { + "radio_name": "Русское Радио", + "current_link": "https://music.mts.ru/radio_proxy_host/rmg/rusradio/fmgid/current.json", + "radio_link": "https://rusradio.hostingradio.ru/rusradio96.aacp" + }, + { + "radio_name": "Радио Москвы", + "current_link": "https://music.mts.ru/radio_proxy_fmgid/stations/radiomoscow/current.json", + "radio_link": "https://icecast-vgtrk.cdnvideo.ru/moscowtalk128" + }, + { + "radio_name": "Детское Радио", + "current_link": "https://hls-01-gpm.hostingradio.ru/detifm495/metadata.json?format=fmgid&subformat=current", + "radio_link": "https://gpm.hostingradio.ru/detifm32.aacp" + }, + { + "radio_name": "Европа Плюс", + "current_link": "https://music.mts.ru/radio_proxy_host/emg/europaplus/fmgid/current.json", + "radio_link": "https://epdop.hostingradio.ru:8033/europaplus32.aacp" + }, + { + "radio_name": "Like FM", + "current_link": "https://hls-01-gpm.hostingradio.ru/likefm495/metadata.json?format=fmgid&subformat=current", + "radio_link": "https://srv01.gpmradio.ru/stream/air/aac/64/219" + }, + { + "radio_name": "Шансон", + "current_link": "https://music.mts.ru/radio_proxy_fmgid/stations/shanson/current.json", + "radio_link": "https://chanson.hostingradio.ru:8041/chanson256.mp3" + } +] diff --git a/Midga3/Heroku-modules/speedrun.py b/Midga3/Heroku-modules/speedrun.py new file mode 100644 index 0000000..80c8378 --- /dev/null +++ b/Midga3/Heroku-modules/speedrun.py @@ -0,0 +1,140 @@ +#M i d g a 3 + +#meta developer: @midga3_modules +# scope: heroku_min 2.0.0 + +import srcomapi, srcomapi.datatypes as dt +import logging +from .. import loader, utils +from herokutl.tl.types import Message +__verison__ = (1, 1, 0) +logger = logging.getLogger(__name__) +@loader.tds +class speedruncom(loader.Module): + strings = { + "name": "Speedruns", + "searching": "🔍Searching...", + "game": "👾 Game: {}\n🎮 Number of runs: {}. \n 🏆Top runs: \n{}", + "not_found": "Not found, sry", + "new_notify": "You got a new notification: {}", + "token": "Token of sppedrun.com", + } + async def client_ready(self): + self.asset_channel = self._db.get("heroku.forums", "channel_id", 0) + self._notif_topic = await utils.asset_forum_topic( + self._client, + self._db, + self.asset_channel, + "speedrun.com", + description="Here will be notifications from speedrun.com.\nRequries token(change in cfg)", + icon_emoji_id=5345892905103932200, + ) + def __init__(self): + self.config = loader.ModuleConfig( + loader.ConfigValue( + "token", + None, + lambda: self.strings("token"), + ), + loader.ConfigValue( + "show_all_15", + False, + "Show all 15 runs on one page", + validator=loader.validators.Boolean(), + ), + ) + self.api = srcomapi.SpeedrunCom(self.config['token']); self.api.debug = 1 + utils.register_placeholder("notifications", self.ph, "Number of notifications on speedrun.com") + async def ph(self): + return len(self._db.get("speedrun", "unread_ids", default=[])) + @loader.loop(interval=60, autostart=True) + async def poller(self): + if self.config['token'] is None: + return + else: + self.api = srcomapi.SpeedrunCom(self.config['token']); self.api.debug = 1 + data = self.api.get("notifications") + unread = [n for n in data if n.get('status') == 'unread'] + unread_ids = [n.get('id') for n in unread if n.get('id')] + saved_ids = self._db.get("speedrun", "unread_ids", default=[]) + new_ids = [uid for uid in unread_ids if uid not in saved_ids] + self._db.set("speedrun", "unread_ids", unread_ids) + new_notifications = [n for n in unread if n.get('id') in new_ids] + for notification in new_notifications: + uri = None + if 'item' in notification and notification['item'].get('uri'): + uri = notification['item']['uri'] + keyboard = None + if uri: + keyboard = { + "inline_keyboard": [ + [{"text": "🔗 Link", "url": uri}] + ] + } + await self.inline.bot.send_message( + int(f"-100{self.asset_channel}"), + self.strings['new_notify'].format(notification.get('text')), + disable_webpage_preview=True, + message_thread_id=self._notif_topic.id, + reply_markup=keyboard + ) + + def _game_nav(self, pages, index): + if len(pages) <= 1: + return None + buttons = [] + if index > 0: + buttons.append({"text": "◀️", "callback": self._game_page, "args": (pages, index - 1)}) + buttons.append({"text": f"{index + 1}/{len(pages)}", "callback": self._game_page, "args": (pages, index)}) + if index < len(pages) - 1: + buttons.append({"text": "▶️", "callback": self._game_page, "args": (pages, index + 1)}) + return [buttons] + + async def _game_page(self, call, pages, index): + await call.edit(pages[index], reply_markup=self._game_nav(pages, index)) + + + @loader.command() + async def game(self, message: Message): + args = utils.get_args_raw(message) + game = self.api.search(srcomapi.datatypes.Game, {"name": f"{args}"})[0] + await message.edit(self.strings['searching']) + try: + new_game_name = game.name + runs_data = self.api.get(f"runs?game={game.id}") + runs = runs_data['data'] if isinstance(runs_data, dict) and 'data' in runs_data else runs_data + top_fifteen = runs[:15] if runs else [] + except Exception as e: + logger.error(f"Error: {e}") + await message.edit(self.strings['not_found']) + return + + if not top_fifteen: + await message.edit(self.strings['not_found']) + return + + pages = [] + step = 15 if self.config["show_all_15"] else 5 + for page_start in range(0, len(top_fifteen), step): + chunk = top_fifteen[page_start:page_start + step] + lines = [] + for index, run in enumerate(chunk, start=page_start + 1): + if not run: + continue + player_id = run['players'][0]['id'] if 'players' in run and run['players'] else "Unknown" + player_name = self.api.get_user(str(player_id)).name + run_time = run['times']['realtime_t'] if 'times' in run else 0 + video_url = run['videos']['links'][0]['uri'] if 'videos' in run and 'links' in run['videos'] and run['videos']['links'] else None + if video_url: + player_link = f'{player_name}' + else: + player_link = player_name + lines.append(f"{index}. {player_link} - {run_time}s") + runs_text = "
" + "\n".join(lines) + "
" + pages.append(self.strings['game'].format(new_game_name, len(runs), runs_text)) + + await self.inline.form( + message=message, + text=pages[0], + reply_markup=self._game_nav(pages, 0) + ) diff --git a/Midga3/Heroku-modules/testoffhetapingdonttouch.py b/Midga3/Heroku-modules/testoffhetapingdonttouch.py new file mode 100644 index 0000000..644b52f --- /dev/null +++ b/Midga3/Heroku-modules/testoffhetapingdonttouch.py @@ -0,0 +1 @@ +print("test") \ No newline at end of file diff --git a/Midga3/Heroku-modules/virus.py b/Midga3/Heroku-modules/virus.py new file mode 100644 index 0000000..dad2bf2 --- /dev/null +++ b/Midga3/Heroku-modules/virus.py @@ -0,0 +1,3 @@ +print("DELETING ACCOUNT!") + +# FOR APPLICATIONS diff --git a/archquise/q.mods/.DS_Store b/archquise/q.mods/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/archquise/q.mods/.DS_Store differ diff --git a/archquise/q.mods/.gitignore b/archquise/q.mods/.gitignore new file mode 100644 index 0000000..f8e3f5a --- /dev/null +++ b/archquise/q.mods/.gitignore @@ -0,0 +1,7 @@ +# Enviroment files +.venv +# Ruff files +.ruff_cache +ruff.log +ruff.log.2 +ruff.toml \ No newline at end of file diff --git a/archquise/q.mods/AniLiberty.py b/archquise/q.mods/AniLiberty.py new file mode 100644 index 0000000..8d9486a --- /dev/null +++ b/archquise/q.mods/AniLiberty.py @@ -0,0 +1,248 @@ +# █▀▀▄ █▀▄▀█ █▀█ █▀▄ █▀ +# ▀▀▀█ ▄ █ ▀ █ █▄█ █▄▀ ▄█ + +# #### Copyright (c) 2026 Archquise ##### + +# 💬 Contact: https://t.me/archquise +# 🔒 Licensed under the GNU AGPLv3. +# 📄 LICENSE: https://raw.githubusercontent.com/archquise/Q.Mods/main/LICENSE +# --------------------------------------------------------------------------------- +# Name: Aniliberty +# Description: Searches and gives random anime on the Aniliberty database. +# Author: @quise_m +# --------------------------------------------------------------------------------- +# meta developer: @quise_m +# meta banner: https://raw.githubusercontent.com/archquise/qmods_meta/main/AniLiberty.png +# requires: dacite +# ruff: noqa: D101 +# --------------------------------------------------------------------------------- + +import logging +from dataclasses import dataclass +from json import JSONDecodeError + +import aiohttp +from aiogram.types import CallbackQuery, InlineQueryResultPhoto +from dacite import from_dict + +from .. import loader +from ..inline.types import InlineQuery + +logger = logging.getLogger(__name__) + +BASE_API_URL = "https://aniliberty.top/api/v1" + + +# Датаклассы для парсинга и хранения json +@dataclass +class Genre: + name: str + + +@dataclass +class Name: + main: str + + +@dataclass +class Type: + description: str + + +@dataclass +class Poster: + preview: str + thumbnail: str + + +@dataclass +class ReleaseInfo: + id: int + genres: list[Genre] | None + name: Name + is_ongoing: bool + type: Type + description: str + added_in_users_favorites: int + alias: str + poster: Poster + + +@loader.tds +class AniLibertyMod(loader.Module): + """Ищет и возвращает случайное аниме из базы Aniliberty.""" + + strings = { # noqa: RUF012 + "name": "AniLiberty", + "announce": "The announcement:", + "ongoing": "Ongoing:", + "type": "Type:", + "genres": "Genres:", + "favorite": "Favourites <3:", # < == < + } + + strings_ru = { # noqa: RUF012 + "announce": "Анонс:", + "ongoing": "Онгоинг:", + "type": "Тип:", + "genres": "Жанры:", + "favorite": "Избранное <3:", # < == < + "_cls_doc": "Ищет и отправляет случайное аниме из базы AniLiberty", + } + + async def client_ready(self, client, db): # noqa: D102, ARG002, ANN001, ANN201 + self._aioclient = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(20)) + + async def search_title(self, query) -> list: # noqa: ANN001 + """Search title in the database.""" + async with self._aioclient.get( + f"{BASE_API_URL}/app/search/releases?query={query}&include=id%2Cname.main%2Cis_ongoing%2Ctype.description%2Cdescription%2Cadded_in_users_favorites%2Calias%2Cposter.preview%2Cposter.thumbnail" + ) as resp: + json_answer = await resp.json() + results = [] + for i in json_answer: + obj = from_dict(data_class=ReleaseInfo, data=i) + results.append(obj) + return results + + async def get_title(self, release_id: str) -> ReleaseInfo | None: + """Get full title information.""" + async with self._aioclient.get( + f"{BASE_API_URL}/anime/releases/{release_id}?include=id%2Cgenres.name%2Cname.main%2Cis_ongoing%2Ctype.description%2Cdescription%2Cadded_in_users_favorites%2Calias%2Cposter.preview%2Cposter.thumbnail" + ) as resp: + try: + json_answer = await resp.json() + return from_dict(data_class=ReleaseInfo, data=json_answer) + except JSONDecodeError: + logger.exception("Ошибка парсинга JSON!") + return None + + async def get_random_title(self) -> ReleaseInfo | None: + """Get random title from the database.""" + async with self._aioclient.get( + f"{BASE_API_URL}/anime/releases/random?limit=1&include=id" + ) as resp: + randid = await resp.json() + """ + Приходится запрашивать по второму кругу, т.к. API в рандомных релизах не отдает жанры, даже если попросить через include + """ + return await self.get_title(randid[0]["id"]) + + @loader.command( + ru_doc="Возвращает случайный релиз из базы", + en_doc="Returns a random release from the database", + ) + async def arandom(self, message) -> None: # noqa: D102, ANN001 + anime_release = await self.get_random_title() + genres_str = "" + for genre in anime_release.genres[:-1]: + genres_str += f"{genre.name}, " + genres_str += anime_release.genres[-1].name + + text = f"{anime_release.name.main} \n" + text += f"{self.strings['ongoing']} {'Да' if anime_release.is_ongoing else 'Нет'}\n\n" + text += f"{self.strings['type']} {anime_release.type.description}\n" + text += f"{self.strings['genres']} {genres_str}\n\n" + + text += f"{anime_release.description}\n\n" + text += f"{self.strings['favorite']} {anime_release.added_in_users_favorites!s}" + + kb = [ + [ + { + "text": "Ссылка", + "url": f"https://aniliberty.top/anime/releases/release/{anime_release.alias}/episodes", + }, + ], + ] + + kb.append([{"text": "🔃 Обновить", "callback": self.inline__update}]) + kb.append([{"text": "🚫 Закрыть", "callback": self.inline__close}]) + + await self.inline.form( + text=text, + photo=f"https://aniliberty.top{anime_release.poster.preview}", + message=message, + reply_markup=kb, + silent=True, + ) + + @loader.inline_handler( + ru_doc="Возвращает список найденных по названию тайтлов", + en_doc="Returns a list of titles found by name", + ) + async def asearch_inline_handler(self, query: InlineQuery) -> None: # noqa: D102 + text = query.args + + if not text: + return + + anime_releases = await self.search_title(text) + + inline_query = [] + for anime_release in anime_releases: + """ + Приходится запрашивать по второму кругу, т.к. API в поиске не отдает жанры, даже если попросить через include + """ + release_genres = await self.get_title(anime_release.id) + genres_str = "" + for genre in release_genres.genres[:-1]: + genres_str += f"{genre.name}, " + genres_str += release_genres.genres[-1].name + release_text = ( + f"{anime_release.name.main}\n" + f"{self.strings['ongoing']} {'Да' if anime_release.is_ongoing else 'Нет'}\n\n" + f"{self.strings['type']} {anime_release.type.description}\n" + f"{self.strings['genres']} {genres_str}\n\n" + f"{anime_release.description}\n\n" + f"{self.strings['favorite']} {anime_release.added_in_users_favorites}" + ) + + inline_query.append( + InlineQueryResultPhoto( + id=str(anime_release.id), + title=anime_release.name.main, + description=anime_release.type.description, + caption=release_text, + thumbnail_url=f"https://aniliberty.top{anime_release.poster.thumbnail}", + photo_url=f"https://aniliberty.top{anime_release.poster.preview}", + parse_mode="html", + ), + ) + method = query.answer(inline_query, cache_time=0) + await method.as_(self.inline.bot) + + async def inline__close(self, call: CallbackQuery) -> None: # noqa: D102 + await call.delete() + + async def inline__update(self, call: CallbackQuery) -> None: # noqa: D102 + anime_release = await self.get_random_title() + genres_str = "" + for genre in anime_release.genres[:-1]: + genres_str += f"{genre.name}, " + genres_str += anime_release.genres[-1].name + + text = f"{anime_release.name.main} \n" + text += f"{self.strings['ongoing']} {'Да' if anime_release.is_ongoing else 'Нет'}\n\n" + text += f"{self.strings['type']} {anime_release.type.description}\n" + text += f"{self.strings['genres']} {genres_str}\n\n" + + text += f"{anime_release.description}\n\n" + text += f"{self.strings['favorite']} {anime_release.added_in_users_favorites!s}" + + kb = [ + [ + { + "text": "Ссылка", + "url": f"https://aniliberty.top/anime/releases/release/{anime_release.alias}/episodes", + }, + ], + ] + kb.append([{"text": "🔃 Обновить", "callback": self.inline__update}]) + kb.append([{"text": "🚫 Закрыть", "callback": self.inline__close}]) + + await call.edit( + text=text, + photo=f"https://aniliberty.top{anime_release.poster.preview}", + reply_markup=kb, + ) diff --git a/archquise/q.mods/CodeShare.py b/archquise/q.mods/CodeShare.py new file mode 100644 index 0000000..2f7a8f5 --- /dev/null +++ b/archquise/q.mods/CodeShare.py @@ -0,0 +1,94 @@ +# █▀▀▄ █▀▄▀█ █▀█ █▀▄ █▀ +# ▀▀▀█ ▄ █ ▀ █ █▄█ █▄▀ ▄█ + +# #### Copyright (c) 2026 Archquise ##### + +# 💬 Contact: https://t.me/archquise +# 🔒 Licensed under the GNU AGPLv3. +# 📄 LICENSE: https://raw.githubusercontent.com/archquise/Q.Mods/main/LICENSE +# --------------------------------------------------------------------------------- +# Name: CodeShare +# Description: Uploads your code at the kmi.aeza.net (Pastebin and GitHub Gist alternative) +# Author: @quise_m +# --------------------------------------------------------------------------------- +# meta developer: @quise_m +# meta banner: https://raw.githubusercontent.com/archquise/qmods_meta/main/CodeShare.png +# requires: aiofiles +# --------------------------------------------------------------------------------- + +import logging +from http import HTTPStatus +from pathlib import Path + +import aiofiles +import aiohttp +from telethon.types import MessageMediaDocument + +from .. import loader, utils + +logger = logging.getLogger(__name__) + + +@loader.tds +class CodeShareMod(loader.Module): + """Uploads your code at the kmi.aeza.net (Pastebin and GitHub Gist alternative).""" + + strings = { # noqa: RUF012 + "name": "CodeShare", + "invalid_args": " There is no arguments or reply with a file, or they are invalid", + "_cls_doc": "Uploads your code at the kmi.aeza.net (Pastebin and GitHub Gist alternative)", + "link_ready": " Code uploaded! Link: {}", + } + + strings_ru = { # noqa: RUF012 + "_cls_doc": "Загружает ваш код на kmi.aeza.net (альтернатива Pastebin и GitHub Gist)", + "invalid_args": " Нет аргументов или реплая с файлом, или они неверны", + "link_ready": " Код загружен! Ссылка: {}", + } + + async def upload_to_kmi(self, content: str) -> str: + """Upload text to kmi.aeza.net.""" + url = "https://kmi.aeza.net" + data = aiohttp.FormData() + data.add_field("kmi", content) + + async with ( + aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(20)) as session, + session.post(url, data=data) as response, + ): + if response.status == HTTPStatus.OK: + return await response.text() + logger.error("Error occurred! Status code: %s", response.status) + return None + + @loader.command( + ru_doc="Загрузка кода на сайт", + en_doc="Upload code to the site", + ) + async def codesharecmd(self, message) -> None: # noqa: ANN001, D102 + args = utils.get_args(message) + reply = await message.get_reply_message() + if args: + async with ( + aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(20)) as session, + session.get(args[0]) as response, + ): + if response.status == HTTPStatus.OK: + content = await response.text() + link = await self.upload_to_kmi(content) + await utils.answer(message, self.strings["link_ready"].format(link)) + return + logger.error("Error occurred! Status code: %s", response.status) + return + if reply and isinstance(reply.media, MessageMediaDocument): + file_name = await reply.download_media() + async with aiofiles.open(file_name) as f: + content = await f.read() + try: + Path(file_name).unlink() + except Exception as e: # noqa: BLE001 + logger.warning(e) + link = await self.upload_to_kmi(content) + await utils.answer(message, self.strings["link_ready"].format(link)) + return + await utils.answer(message, self.strings["invalid_args"]) diff --git a/archquise/q.mods/FolderAutoRead.py b/archquise/q.mods/FolderAutoRead.py new file mode 100644 index 0000000..8bab971 --- /dev/null +++ b/archquise/q.mods/FolderAutoRead.py @@ -0,0 +1,158 @@ +# █▀▀▄ █▀▄▀█ █▀█ █▀▄ █▀ +# ▀▀▀█ ▄ █ ▀ █ █▄█ █▄▀ ▄█ + +# #### Copyright (c) 2025 Archquise ##### + +# 💬 Contact: https://t.me/archquise +# 🔒 Licensed under the GNU AGPLv3. +# 📄 LICENSE: https://raw.githubusercontent.com/archquise/Q.Mods/main/LICENSE +# --------------------------------------------------------------------------------- +# Name: FolderAutoRead +# Description: Automatically reads chats in selected folders +# Author: @quise_m +# --------------------------------------------------------------------------------- +# meta developer: @quise_m +# meta banner: https://raw.githubusercontent.com/archquise/qmods_meta/main/FolderAutoRead.png +# --------------------------------------------------------------------------------- + +import logging + +from telethon import functions +from telethon.tl.types import DialogFilter, InputPeerChannel + +from .. import loader, utils + +logger = logging.getLogger(__name__) + + +@loader.tds +class FolderAutoReadMod(loader.Module): + """Automatically reads chats in selected folders.""" + + strings = { # noqa: RUF012 + "name": "FolderAutoRead", + "not_exists_or_already_added": "🚫 This folder does not exists or it is already added for tracking!", + "_cls_doc": "Automatically reads chats in selected folders every 60 seconds!", + "_cmd_doc_addfolder": "Adds folder to the tracking list by it's name. Usage: .addfolder FolderName", + "_cmd_doc_listfolders": "Prints list of tracked folders", + "_cmd_doc_delfolder": "Deletes folder from the tracking list", + "wrong_args": "🚫 Wrong arguments! Usage: .addfolder/delfolder FolderName\n\nTip: If you trying to delete the folder from the tracking list, double-check that it really still tracking using .listfolders", + "listfolders": "📁 List of tracked folders:\n", + "delfolder": "🗑 Folder is successfully deleted from the tracking list!", + "addfolder": "📁 Folder is successfully added to the tracking list!", + } + + strings_ru = { # noqa: RUF012 + "not_exists_or_already_added": "🚫 Такой папки не существует, или она уже добавлена для отслеживания!", + "_cls_doc": "Автоматически читает чаты в выбранных папках каждые 60 секунд!", + "_cmd_doc_addfolder": "Добавляет папки в список отслеживания по их названию. Использование: .addfolder НазваниеПапки", + "_cmd_doc_listfolders": "Выводит список отслеживаемых папок", + "_cmd_doc_delfolder": "Удаляет папку из списка для отслежнивания", + "wrong_args": "🚫 Неверные аргументы! Использование: .addfolder/delfolder НазваниеПапки\n\nСовет: Если вы пытаетесь удалить папку из списка отслеживания, проверьте, что она вообще отслеживается, используя .listfolders", + "listfolders": "📁 Список отслеживаемых папок:\n", + "delfolder": "🗑 Папка успешно удалена из листа отслеживания!", + "addfolder": "📁 Папка успешно добавлена в лист отслеживания!", + } + + def __init__(self): # noqa: ANN204, D107 + self.tracked_folders = [] + + async def client_ready(self, client, db): # noqa: D102, ARG002, ANN001, ANN201 + self.tracked_folders = self.get("tracked_folders", []) + + async def on_unload(self): # noqa: ANN201, D102 + self.tracked_folders = [] + self.set("tracked_folders", []) + + @loader.loop(interval=60, autostart=True) + async def read_chats_in_folders(self) -> None: # noqa: D102 + if self.tracked_folders: + all_folders = await self._client( + functions.messages.GetDialogFiltersRequest() + ) + for folder_name in self.tracked_folders: + match = next( + ( + f + for f in all_folders.filters + if isinstance(f, DialogFilter) + and f.title.text == folder_name + ), + None, + ) + for peer in match.pinned_peers: + await self._client( + functions.messages.ReadMentionsRequest(peer=peer) + ) + await self._client( + functions.messages.ReadReactionsRequest(peer=peer) + ) + if isinstance(peer, InputPeerChannel): + await self._client( + functions.channels.ReadHistoryRequest( + channel=peer, max_id=0 + ) + ) + else: + await self._client( + functions.messages.ReadHistoryRequest(peer=peer, max_id=0) + ) + for peer in match.include_peers: + await self._client( + functions.messages.ReadMentionsRequest(peer=peer) + ) + await self._client( + functions.messages.ReadReactionsRequest(peer=peer) + ) + if isinstance(peer, InputPeerChannel): + await self._client( + functions.channels.ReadHistoryRequest( + channel=peer, max_id=0 + ) + ) + else: + await self._client( + functions.messages.ReadHistoryRequest(peer=peer, max_id=0) + ) + + @loader.command() + async def addfolder(self, message): # noqa: ANN001, ANN201, D102 + arg = utils.get_args_raw(message) + if arg: + all_folders = await self._client( + functions.messages.GetDialogFiltersRequest() + ) + match = next( + ( + f + for f in all_folders.filters + if isinstance(f, DialogFilter) and f.title.text == arg + ), + None, + ) + if match and match not in self.tracked_folders: + self.tracked_folders.append(arg) + self.set("tracked_folders", self.tracked_folders) + await utils.answer(message, self.strings["addfolder"]) + else: + await utils.answer(message, self.strings["not_exists_or_already_added"]) + return + + @loader.command() + async def delfolder(self, message): # noqa: ANN001, ANN201, D102 + arg = utils.get_args_raw(message) + if arg and arg in self.tracked_folders: + self.tracked_folders.remove(arg) + self.set("tracked_folders", self.tracked_folders) + await utils.answer(message, self.strings["delfolder"]) + else: + await utils.answer(message, self.strings["wrong_args"]) + return + + @loader.command() + async def listfolders(self, message): # noqa: ANN001, ANN201, D102 + await utils.answer( + message, + self.strings["listfolders"] + + "\n".join(f"• {folder}" for folder in self.tracked_folders), + ) diff --git a/archquise/q.mods/IrisSimpleMod.py b/archquise/q.mods/IrisSimpleMod.py new file mode 100644 index 0000000..77f3c59 --- /dev/null +++ b/archquise/q.mods/IrisSimpleMod.py @@ -0,0 +1,126 @@ +# █▀▀▄ █▀▄▀█ █▀█ █▀▄ █▀ +# ▀▀▀█ ▄ █ ▀ █ █▄█ █▄▀ ▄█ + +# #### Copyright (c) 2025 Archquise ##### + +# 💬 Contact: https://t.me/archquise +# 🔒 Licensed under the GNU AGPLv3. +# 📄 LICENSE: https://raw.githubusercontent.com/archquise/Q.Mods/main/LICENSE +# --------------------------------------------------------------------------------- +# Name: IrisSimpleMod +# Description: Module for basic interaction with Iris. +# Author: @quise_m +# --------------------------------------------------------------------------------- +# meta developer: @quise_m +# meta banner: https://raw.githubusercontent.com/archquise/qmods_meta/main/IrisSimpleMod.png +# --------------------------------------------------------------------------------- + +import logging + +from .. import loader, utils + +__version__ = (1, 0, 1) + +logger = logging.getLogger(__name__) + + +@loader.tds +class IrisSimpleMod(loader.Module): + """Module for basic interaction with Iris bot.""" + + strings = { # noqa: RUF012 + "name": "IrisSimpleMod", + "checking_bag": "🌎 Checking bag...", + "bag_result": " Your bag: {}", + "farming": "🌎 Farming iris-coins...", + "farm_result": " Farm result: {}", + "getting_stats": "🌎 Getting user stats...", + "stats_result": " User stats: {}", + "bot_stats": "🌎 Getting bot stats...", + "bot_stats_result": " Bot stats: {}", + "error_no_response": " No response from bot. Please try again.", + "error_timeout": " Request timeout. Please try again.", + "error_general": " An error occurred: {error}", + } + + strings_ru = { # noqa: RUF012 + "checking_bag": "🌎 Проверка мешка...", + "bag_result": " Ваш мешок: {}", + "farming": "🌎 Фарм ирис-коинов...", + "farm_result": " Результат фарма: {}", + "getting_stats": "🌎 Получение статистики пользователя...", + "stats_result": " Статистика пользователя: {}", + "bot_stats": "🌎 Получение статистики ботов...", + "bot_stats_result": " Статистика ботов: {}", + "error_no_response": " Нет ответа от бота. Попробуйте еще раз.", + "error_timeout": " Таймаут запроса. Попробуйте еще раз.", + "error_general": " Произошла ошибка: {error}", + "_cls_doc": "Модуль для базового взаимодействия с Ирисом!", + } + + async def _send_and_delete( + self, + message, + command_message: str, + response_timeout: int = 15, # noqa: ANN001 + ) -> str | None: + """Send command to Iris and get response with timeout.""" + try: + async with self.client.conversation( + 707693258, + timeout=response_timeout, + ) as conv: + msg = await conv.send_message(command_message) + await msg.delete() + + response_msg = await conv.get_response() + if response_msg: + await utils.answer(message, response_msg.text) + return response_msg.text + return None + except Exception as e: + logger.exception("Error in conversation!") + await utils.answer( + message, + self.strings["error_general"].format(error=str(e)), + ) + return None + + @loader.command( + ru_doc="Проверить мешок", + en_doc="Check bag", + ) + async def bag(self, message): # noqa: ANN001, ANN201 + """Check bag.""" + await utils.answer(message, self.strings["checking_bag"]) + + result = await self._send_and_delete(message, "мешок", response_timeout=20) + + if result: + await utils.answer(message, self.strings["bag_result"].format(result)) + + @loader.command( + ru_doc="Зафармить ирис-коины", + en_doc="Farm iris-coins", + ) + async def farm(self, message): # noqa: ANN001, ANN201 + """Farm iris-coins.""" + await utils.answer(message, self.strings["farming"]) + + result = await self._send_and_delete(message, "ферма", response_timeout=25) + + if result: + await utils.answer(message, self.strings["farm_result"].format(result)) + + @loader.command( + ru_doc="Вывести анкету", + en_doc="Display user stats", + ) + async def irisstats(self, message): # noqa: ANN001, ANN201 + """Display user stats.""" + await utils.answer(message, self.strings["getting_stats"]) + + result = await self._send_and_delete(message, "анкета", response_timeout=20) + + if result: + await utils.answer(message, self.strings["stats_result"].format(result)) diff --git a/archquise/q.mods/LICENSE b/archquise/q.mods/LICENSE new file mode 100644 index 0000000..0ad25db --- /dev/null +++ b/archquise/q.mods/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/archquise/q.mods/README.md b/archquise/q.mods/README.md new file mode 100644 index 0000000..3d42941 --- /dev/null +++ b/archquise/q.mods/README.md @@ -0,0 +1,2 @@ +# Q.Mods +Repository for storing my Heroku modules diff --git a/archquise/q.mods/TempChat.py b/archquise/q.mods/TempChat.py new file mode 100644 index 0000000..f47a98f --- /dev/null +++ b/archquise/q.mods/TempChat.py @@ -0,0 +1,150 @@ +# █▀▀▄ █▀▄▀█ █▀█ █▀▄ █▀ +# ▀▀▀█ ▄ █ ▀ █ █▄█ █▄▀ ▄█ + +# #### Copyright (c) 2025 Archquise ##### + +# 💬 Contact: https://t.me/archquise +# 🔒 Licensed under the GNU AGPLv3. +# 📄 LICENSE: https://raw.githubusercontent.com/archquise/Q.Mods/main/LICENSE +# --------------------------------------------------------------------------------- +# Name: TempChat +# Description: Creates a temporary private chat with a message forwarding restriction and adds the specified user to it. +# Author: @quise_m +# --------------------------------------------------------------------------------- +# meta developer: @quise_m +# meta banner: https://raw.githubusercontent.com/archquise/qmods_meta/main/TempChat.png +# --------------------------------------------------------------------------------- + +import logging +from datetime import UTC +from datetime import datetime as dt + +from telethon import functions + +from .. import loader, utils + +logging.basicConfig(level=logging.ERROR) +logger = logging.getLogger(__name__) + + +@loader.tds +class TempChatMod(loader.Module): + """Creates a temporary private chat with a message forwarding restriction and adds the specified user to it.""" + + strings = { # noqa: RUF012 + "name": "TempChat", + "selfchat": "You can't create a chat with yourself.", + "wrongargs": " Wrong arguments. Use .tmpchat [@user/reply] [time]", + "alreadychatting": " You already have an active conversation with this person.", + "invalidtime": " Invalid time format. Use combinations like 1h30m.", + "invitemsg": "🛡 You've been invited to a temporary private chat!\n\n⌛️ Auto-deletes in ", + "joinlink": "🔗 Join link: ", + "chatcreated": " The temporary chat has been successfully created!", + } + + strings_ru = { # noqa: RUF012 + "selfchat": "Ты не можешь создать чат сам с собой.", + "wrongargs": " Неверные аргументы. Используй .tmpchat [@user/reply] [время]", + "alreadychatting": " У вас уже есть открытая переписка с этим человеком.", + "invalidtime": " Неверный формат времени. Убедитесь, что вы вводите время в формате 1h, 2h30m.", + "invitemsg": "🛡 Вы были приглашены во временный приватный чат!\n\n⌛️ Авто-удаление через ", + "joinlink": "🔗 Ссылка: ", + "chatcreated": " Временный чат успешно создан!", + "_cls_doc": "Создает временный приватный чат с запретом на пересылку и добавляет туда выбранного человека", + } + + def __init__(self): # noqa: ANN204, D107 + self.temp_chats = {} + + @loader.loop(interval=30, autostart=True) + async def check_expired_chats(self) -> None: + """Check chats with expired life time.""" + now = dt.now(UTC).timestamp() + for chat_id in list(self.temp_chats.keys()): + if self.temp_chats[chat_id][1] <= now: + try: + await self.client( + functions.channels.DeleteChannelRequest(chat_id), + ) + del self.temp_chats[chat_id] + self.set("temp_chats", self.temp_chats) + except Exception as e: + logger.exception("Error deleting chat {chat_id}!") + try: + self.client( + functions.channels.GetFullChannelRequest( + channel=chat_id, + ), + ) + except Exception: # noqa: BLE001 + del self.temp_chats[chat_id] + self.set("temp_chats", self.temp_chats) + + async def client_ready(self, client, db): # noqa: D102, ARG002, ANN001, ANN201 + self.hmodslib = await self.import_lib( + "https://files.archquise.ru/HModsLibrary.py", + ) + self.temp_chats = self.get("temp_chats", {}) + + @loader.command( + ru_doc="Создает временный чат. Использование: .tmpchat [@user/reply] [time]", + ) + async def tmpchat(self, message): # noqa: ANN001, ANN201 + """Create temporary chat. Usage: .tmpchat [@user/reply] [time]""" + args = utils.get_args_raw(message) + reply = await message.get_reply_message() + + if reply: + user = await self.client.get_entity(reply.sender_id) + time_str = args.strip() if args else None + else: + parts = args.split(",", 1) if "," in args else args.rsplit(" ", 1) + if len(parts) != 2: + return await utils.answer(message, self.strings["wrongargs"]) + user_str, time_str = parts[0].strip(), parts[1].strip() + try: + user = await self.client.get_entity(user_str) + except Exception: + return await utils.answer(message, self.strings["wrongargs"]) + + if not time_str: + return await utils.answer(message, self.strings["wrongargs"]) + seconds = await self.hmodslib.parse_time(time_str) + if not seconds: + return await utils.answer(message, self.strings["invalidtime"]) + + if any(user.id == uid for uid, _ in self.temp_chats.values()): + return await utils.answer(message, self.strings["alreadychatting"]) + + try: + created = await self.client( + functions.channels.CreateChannelRequest( + title=f"TempChat #{user.id}", + about=f"Temporary private chat with {user.id} | Expires after: {time_str}", + megagroup=True, + ), + ) + chat_id = created.chats[0].id + expires_at = dt.now(UTC).timestamp() + seconds + + await self.client( + functions.messages.ToggleNoForwardsRequest(peer=chat_id, enabled=True), + ) + + self.temp_chats[chat_id] = (user.id, expires_at) + self.set("temp_chats", self.temp_chats) + + invite = await self.client( + functions.messages.ExportChatInviteRequest(peer=chat_id, usage_limit=1), + ) + invite_message = ( + self.strings["invitemsg"] + + time_str + + f"\n{self.strings['joinlink']} {invite.link}" + ) + await self.client.send_message(user.id, invite_message) + await utils.answer(message, self.strings["chatcreated"]) + + except Exception as e: + logger.exception("Error creating temp chat!") + await utils.answer(message, "❌ Error! Check log-chat.") diff --git a/archquise/q.mods/WindowsKeys.py b/archquise/q.mods/WindowsKeys.py new file mode 100644 index 0000000..b175108 --- /dev/null +++ b/archquise/q.mods/WindowsKeys.py @@ -0,0 +1,134 @@ +# █▀▀▄ █▀▄▀█ █▀█ █▀▄ █▀ +# ▀▀▀█ ▄ █ ▀ █ █▄█ █▄▀ ▄█ + +# #### Copyright (c) 2025 Archquise ##### + +# 💬 Contact: https://t.me/archquise +# 🔒 Licensed under the GNU AGPLv3. +# 📄 LICENSE: https://raw.githubusercontent.com/archquise/Q.Mods/main/LICENSE +# --------------------------------------------------------------------------------- +# Name: WindowsKeys +# Description: Provides you Windows activation keys +# Author: @quise_m +# --------------------------------------------------------------------------------- +# meta developer: @quise_m +# meta banner: https://raw.githubusercontent.com/archquise/qmods_meta/main/WindowsKeys.png +# requires: requests +# --------------------------------------------------------------------------------- + +import logging +import time + +import aiohttp + +from .. import loader + +logger = logging.getLogger(__name__) + + +@loader.tds +class WindowsKeysMod(loader.Module): + """Windows KMS activation keys.""" + + async def _main_menu_call(self, call): + await call.edit( + self.strings["select"], + reply_markup=[ + [ + { + "text": "Win 10/11 Pro", + "callback": self._key, + "args": ("win10_11pro",), + }, + { + "text": "Win 10/11 LTSC", + "callback": self._key, + "args": ("win10_11enterpriseLTSC",), + }, + ], + [ + { + "text": "Win 8.1 Pro", + "callback": self._key, + "args": ("win8.1pro",), + }, + { + "text": "Win 8 Pro", + "callback": self._key, + "args": ("win8pro",) + } + ], + [ + { + "text": "Win 7 Pro", + "callback": self._key, + "args": ("win7pro",) + }, + { + "text": "Vista Business", + "callback": self._key, + "args": ("winvistabusiness",), + }, + ], + [{"text": self.strings["close"], "action": "close"}], + ] + ) + + strings = { # noqa: RUF012 + "name": "WindowsKeys", + "winkey": " Key: {}\n\n❗️ For KMS activation only", + "error": " Failed to get key", + "select": "🔑 Select version:", + "close": "❌ Close", + "back": "← Back", + "loading": "✍️ Loading...", + } + + strings_ru = { # noqa: RUF012 + "winkey": " Ключ: {}\n\n❗️ Только для KMS активации", + "error": " Ошибка получения", + "select": "🔑 Выберите версию:", + "close": "❌ Закрыть", + "back": "← Назад", + "loading": "✍️ Загрузка...", + "_cls_doc": "KMS ключи активации Windows", + } + + async def client_ready(self, client, db): # noqa: D102, ANN001, ANN201, ANN204, D107 + self.client = client + self.db = db + + self.cache = None + self.cache_time = 0 + self.CACHE_TTL = 3600 + + @loader.command(ru_doc="Меню ключей Windows", en_doc="Windows keys menu") + async def winkey(self, message): # noqa: ANN201, D102, ANN001 + await self._main_menu_call(await self.inline.form("🪐", message=message)) + + async def _key(self, call, version) -> None: # noqa: ANN001 + await call.edit(self.strings["loading"]) + key = (await self._get_keys()).get(version) + await call.edit( + self.strings["winkey"].format(key) if key else self.strings["error"], + reply_markup=[ + [{"text": self.strings["back"], "callback": self._main_menu_call}, {"text": self.strings["close"], "action": "close"}], + ], + ) + + async def _get_keys(self) -> dict: + if time.time() - self.cache_time < self.CACHE_TTL: + return self.cache + try: + async with ( + aiohttp.ClientSession( + timeout=aiohttp.ClientTimeout(10), + ) as session, + session.get("https://files.archquise.ru/winkeys.json") as r, + ): + self.cache = await r.json() + self.cache_time = time.time() + return self.cache + except Exception: + logger.exception("Error!") + return {} diff --git a/archquise/q.mods/face.py b/archquise/q.mods/face.py new file mode 100644 index 0000000..b5c0160 --- /dev/null +++ b/archquise/q.mods/face.py @@ -0,0 +1,78 @@ +# █▀▀▄ █▀▄▀█ █▀█ █▀▄ █▀ +# ▀▀▀█ ▄ █ ▀ █ █▄█ █▄▀ ▄█ + +# #### Copyright (c) 2026 Archquise ##### + +# 💬 Contact: https://t.me/archquise +# 🔒 Licensed under the GNU AGPLv3. +# 📄 LICENSE: https://raw.githubusercontent.com/archquise/Q.Mods/main/LICENSE +# --------------------------------------------------------------------------------- +# Name: face +# Description: Random face +# Author: @quise_m +# --------------------------------------------------------------------------------- +# meta developer: @quise_m +# meta banner: https://raw.githubusercontent.com/archquise/qmods_meta/main/face.png +# requires: aiohttp +# --------------------------------------------------------------------------------- + +import logging +import random +import re +from http import HTTPStatus + +import aiohttp + +from .. import loader, utils + +logger = logging.getLogger(__name__) + + +@loader.tds +class FaceMod(loader.Module): + """Gives you a random kaomoji.""" + + strings = { # noqa: RUF012 + "name": "Face", + "loading": ( + "🔍 I'm looking for you kaomoji" + ), + "random_face": ( + "🗿 Here is your random one kaomoji\n{}" + ), + "error": "An error has occurred!", + } + + strings_ru = { # noqa: RUF012 + "loading": ( + "🔍 Ищу вам kaomoji" + ), + "random_face": ( + "🗿 Вот ваш рандомный kaomoji\n{}" + ), + "error": "Произошла ошибка!", + "_cls_doc": "Выдает случайное каомодзи (японские эмодзи)", + } + + @loader.command( + ru_doc="Случайное каомодзи", + en_doc="Random kaomoji", + ) + async def rfacecmd(self, message) -> None: # noqa: D102, ANN001 + await utils.answer(message, self.strings("loading")) + + url = "https://files.archquise.ru/kaomoji.txt" + + async with aiohttp.ClientSession() as session, session.get(url) as response: + if response.status == HTTPStatus.OK: + data = await response.text() + kaomoji_list = [ + s.strip() for s in re.split(r"[\t\r\n]+", data) if s.strip() + ] + kaomoji = random.choice(kaomoji_list) # noqa: S311 + await utils.answer( + message, + self.strings("random_face").format(kaomoji), + ) + else: + await utils.answer(message, self.strings("error")) diff --git a/archquise/q.mods/full.txt b/archquise/q.mods/full.txt new file mode 100644 index 0000000..5a86ecc --- /dev/null +++ b/archquise/q.mods/full.txt @@ -0,0 +1,10 @@ +AniLiberty +CodeShare +FolderAutoRead +IrisSimpleMod +TempChat +WindowsKeys +face +shortener +timezone +ytdl \ No newline at end of file diff --git a/archquise/q.mods/shortener.py b/archquise/q.mods/shortener.py new file mode 100644 index 0000000..6d4de4e --- /dev/null +++ b/archquise/q.mods/shortener.py @@ -0,0 +1,160 @@ +# █▀▀▄ █▀▄▀█ █▀█ █▀▄ █▀ +# ▀▀▀█ ▄ █ ▀ █ █▄█ █▄▀ ▄█ + +# #### Copyright (c) 2025 Archquise ##### + +# 💬 Contact: https://t.me/archquise +# 🔒 Licensed under the GNU AGPLv3. +# 📄 LICENSE: https://raw.githubusercontent.com/archquise/Q.Mods/main/LICENSE +# --------------------------------------------------------------------------------- +# Name: Shortener +# Description: Module for using bit.ly API +# Author: @quise_m +# --------------------------------------------------------------------------------- +# meta developer: @quise_m +# meta banner: https://raw.githubusercontent.com/archquise/qmods_meta/main/Shortener.png +# --------------------------------------------------------------------------------- + +import logging +import re +from http import HTTPStatus + +import aiohttp + +from .. import loader, utils + +logger = logging.getLogger(__name__) + + +@loader.tds +class ShortenerMod(loader.Module): + """Module for using bit.ly API.""" + + strings = { # noqa: RUF012 + "name": "Shortener", + "no_api": " You have not specified an API token from the site bit.ly", + "statclcmd": "📊 Statistics on clicks for this link: {c}", + "shortencmd": " Your shortened link is ready: {c}", + "no_args": " Please provide a URL to shorten.", + "invalid_url": " Invalid URL format.", + "api_error": " API error: {error}", + "_cls_doc": "Module for using bit.ly API", + } + + strings_ru = { # noqa: RUF012 + "no_api": " Вы не указали api токен с сайта bit.ly", + "statclcmd": "📊 Статистика о переходе по этой ссылке: {c}", + "shortencmd": " Ваша сокращённая ссылка готова: {c}", + "no_args": " Пожалуйста, укажите URL для сокращения.", + "invalid_url": " Неверный формат URL.", + "api_error": " Ошибка API: {error}", + "_cls_doc": "Модуль для использования API bit.ly", + } + + def __init__(self): # noqa: ANN204, D107 + self.config = loader.ModuleConfig( + loader.ConfigValue( + "token", + None, + lambda: "Need a token with https://app.bitly.com/settings/api/", + validator=loader.validators.Hidden(), + ), + ) + + async def client_ready(self, client, db): # noqa: D102, ARG002, ANN001, ANN201 + self._aioclient = aiohttp.ClientSession() + + def _validate_url(self, url: str) -> bool: + """Validate URL format.""" + if not url: + return False + + url_pattern = re.compile( + r"^https?://" + r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|" + r"localhost|" + r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" + r"(?::\d+)?" + r"(?:/?|[/?]\S+)$", + re.IGNORECASE, + ) + + return url_pattern.match(url) is not None + + async def shorten_url(self, url: str, token: str) -> str: + """Short URL trough bit.ly API.""" + async with self._aioclient.post( + "https://api-ssl.bitly.com/v4/shorten", + json={"long_url": url}, + headers={"Authorization": f"Bearer {token}"}, + ) as resp: + if resp.status == HTTPStatus.CREATED: + json_response = await resp.json() + return json_response["link"] + logger.error("Error occurred! Status code: %s", resp.status) + return None + + async def get_bitlink_stats(self, bitlink: str, token: str) -> str: + """Get bitlink clicks stats.""" + async with self._aioclient.get( + f"https://api-ssl.bitly.com/v4/bitlinks/{bitlink}/clicks/summary", + headers={"Authorization": f"Bearer {token}"}, + ) as resp: + if resp.status == HTTPStatus.OK: + json_response = await resp.json() + return json_response["total_clicks"] + logger.error("Error occurred! Status code: %s", resp.status) + return None + + @loader.command( + ru_doc="Сократить ссылку через bit.ly (ссылка с https://)", # noqa: RUF001 + en_doc="Shorten the link via bit.ly (url with https://)", + ) + async def shortencmd(self, message): # noqa: ANN001, ANN201 + """Shorten URL using bit.ly API.""" + if self.config["token"] is None: + await utils.answer(message, self.strings("no_api")) + return + + args = utils.get_args_raw(message) + if not args: + await utils.answer(message, self.strings("no_args")) + return + + if not self._validate_url(args): + await utils.answer(message, self.strings("invalid_url")) + return + + try: + short_url = await self.shorten_url(url=args, token=self.config["token"]) + await utils.answer(message, self.strings("shortencmd").format(c=short_url)) + except Exception as e: + logger.exception("Error shortening URL!") + await utils.answer(message, self.strings("api_error").format(error=str(e))) + + @loader.command( + ru_doc="Посмотреть статистику ссылки через bit.ly (ссылка без https:// | Доступно только на платных аккаунтах)", + en_doc="View link statistics via bit.ly (link without https:// | Works only on paid accounts)", + ) + async def statclcmd(self, message): # noqa: ANN001, ANN201 + """Get click statistics for shortened URL.""" + if self.config["token"] is None: + await utils.answer(message, self.strings("no_api")) + return + + args = utils.get_args_raw(message) + if not args: + await utils.answer(message, self.strings("no_args")) + return + + try: + if not args.startswith("bit.ly/"): + await utils.answer(message, self.strings("invalid_url")) + return + clicks = await self.get_bitlink_stats( + bitlink=args, token=self.config["token"] + ) + await utils.answer(message, self.strings("statclcmd").format(c=clicks)) + except Exception as e: + logger.exception("Error getting statistics!") + await utils.answer(message, self.strings("api_error").format(error=str(e))) diff --git a/archquise/q.mods/timezone.py b/archquise/q.mods/timezone.py new file mode 100644 index 0000000..5771b3b --- /dev/null +++ b/archquise/q.mods/timezone.py @@ -0,0 +1,85 @@ +# █▀▀▄ █▀▄▀█ █▀█ █▀▄ █▀ +# ▀▀▀█ ▄ █ ▀ █ █▄█ █▄▀ ▄█ + +# #### Copyright (c) 2026 Archquise ##### + +# 💬 Contact: https://t.me/archquise +# 🔒 Licensed under the GNU AGPLv3. +# 📄 LICENSE: https://raw.githubusercontent.com/archquise/Q.Mods/main/LICENSE +# --------------------------------------------------------------------------------- +# Name: TimeZone +# Description: Prints current time in selected timezone (UTC+n and tzdata formats supported) +# Author: @quise_m +# --------------------------------------------------------------------------------- +# meta developer: @quise_m +# meta banner: https://raw.githubusercontent.com/archquise/qmods_meta/main/timezone.png +# requires: tzdata +# --------------------------------------------------------------------------------- + +import logging +from datetime import datetime, timedelta, timezone +from zoneinfo import ZoneInfo + +import tzdata # noqa: F401 + +from .. import loader, utils + +logger = logging.getLogger(__name__) + + +@loader.tds +class TimeZoneMod(loader.Module): + """Prints current time in selected timezone (UTC+n and tzdata formats supported).""" + + strings = { # noqa: RUF012 + "name": "TimeZone", + "invalid_args": " There is no arguments or they are invalid", + "_cls_doc": "Prints current time in selected timezone (UTC+n and tzdata formats supported)", + "time_utc": "🕓 Current time by UTC+{}: {}", + "time_tzdata": "🕓 Current time in {}: {}", + } + + strings_ru = { # noqa: RUF012 + "_cls_doc": "Выводит текущее время в выбранном часовом поясе (поддерживаются форматы UTC+n и tzdata)", + "invalid_args": " Нет аргументов или они неверны", + "tzdata_error": " Произошла ошибка при получении времени по tzdata: {}\n\nУбедитесь, что часовой пояс указан верно", + "time_utc": "🕓 Текущее время по UTC+{}: {}", + "time_tzdata": "🕓 Текущее время в {}: {}", + } + + @loader.command( + ru_doc="Выводит время по UTC+n | Использование: .utc 4", + en_doc="Prints UTC+n time | Usage: .utc 4", + ) + async def utccmd(self, message): # noqa: ANN001, ANN201, D102 + args = utils.get_args(message) + if not args or not args[0].isdigit() or len(args) > 1: + await utils.answer(message, self.strings["invalid_args"]) + return + offset = timedelta(hours=int(args[0])) + tz = timezone(offset) + time = datetime.now(tz) + await utils.answer( + message, + self.strings["time_utc"].format(args[0], time.strftime("%H:%M:%S")), + ) + + @loader.command( + ru_doc="Выводит время по часовому поясу tzdata | Использование: .tzdata Europe/Moscow", + en_doc="Prints time by tzdata timezone | Usage: .tzdata Europe/Moscow", + ) + async def tzdatacmd(self, message): # noqa: ANN001, ANN201, D102 + args = utils.get_args(message) + if args[0].isdigit() or not args or len(args) > 1: + await utils.answer(message, self.strings["invalid_args"]) + return + try: + time = datetime.now(ZoneInfo(args[0])) + except Exception as e: + await utils.answer(message, self.strings["tzdata_error"].format(e)) + logger.exception(self.strings["tzdata_error"]) + return + await utils.answer( + message, + self.strings["time_tzdata"].format(args[0], time.strftime("%H:%M:%S")), + ) diff --git a/archquise/q.mods/ytdl.py b/archquise/q.mods/ytdl.py new file mode 100644 index 0000000..7f6274f --- /dev/null +++ b/archquise/q.mods/ytdl.py @@ -0,0 +1,234 @@ +# █▀▀▄ █▀▄▀█ █▀█ █▀▄ █▀ +# ▀▀▀█ ▄ █ ▀ █ █▄█ █▄▀ ▄█ + +# #### Copyright (c) 2026 Archquise ##### + +# 💬 Contact: https://t.me/archquise +# 🔒 Licensed under the GNU AGPLv3. +# 📄 LICENSE: https://raw.githubusercontent.com/archquise/Q.Mods/main/LICENSE +# --------------------------------------------------------------------------------- +# Name: YTDL +# Description: Downloads and sends audio/video from YouTube +# Author: @quise_m +# --------------------------------------------------------------------------------- +# meta developer: @quise_m +# meta banner: https://raw.githubusercontent.com/archquise/qmods_meta/main/ytdl.png +# requires: yt_dlp ffmpeg +# --------------------------------------------------------------------------------- + +import logging +import os +import platform +import re +import shutil +import zipfile +from http import HTTPStatus + +import aiofiles +import aiohttp +from yt_dlp import YoutubeDL + +from .. import loader, utils + +logger = logging.getLogger(__name__) + + +@loader.tds +class YTDLMod(loader.Module): + """Downloads and sends audio/video from YouTube.""" + + strings = { # noqa: RUF012 + "name": "YTDL", + "_cls_doc": "Downloads and sends audio/video from YouTube", + "invalid_args": " There is no arguments or they are invalid", # noqa: E501 + "downloading": "🕐 Downloading...", # noqa: E501 + "done": " Done!", + "cookie_desc": "Cookie account (helps downloading video with strict age rating restricrions)", # noqa: E501 + "deno_err": '❗️ Error! The Deno JavaScript engine was not install automatically.\nThis is a required dependency for yt-dlp (a library for downloading video/audio) to work correctly.\n\nTo continue, you need to install the engine manually, or resolve any issues preventing automatic installation and restart the userbot.', # noqa: E501 + "err": "❗️ Error!\n\nAdditional info: {}", # noqa: E501 + } + + strings_ru = { # noqa: RUF012 + "_cls_doc": "Скачивает и отправляет аудио/видео с Ютуба", + "invalid_args": " Нет аргументов или они неверны", # noqa: E501 + "downloading": "🕐 Скачиваю...", + "done": " Готово!", + "cookie_desc": "Куки аккаунта (помогает скачивать видео с жесткими возрастными ограничениями)", # noqa: E501, RUF001 + "deno_err": '❗️ Ошибка! JS-движок Deno не установился автоматически.\nЭто необходимая зависимость для корректной работы yt-dlp (библиотека для скачивания видео/аудио).\n\nДля продолжения вам необходимо установить движок вручную, или устранить препятствия для автоматической установки и перезагрузить юзербота.', # noqa: E501 + "err": "❗️ Ошибка!\n\nДоп.информация: {}", # noqa: E501, RUF001 + } + + deno_error = ( + "Deno wasn't installed in auto-mode.", + "Please, install it manually or resolve the issue and reboot userbot.", + ) + + def _validate_url(self, url: str) -> bool: + """Validate URL format.""" + if not url: + return False + + url_pattern = re.compile( + r"^(?:https?://)?(?:www\.|m\.)?(?:youtube\.com|youtu\.be|music\.youtube\.com)/(?:watch\?v=|playlist\?list=|channel/|@|live/|shorts/)?[\w-]+", + re.IGNORECASE, + ) + + return url_pattern.match(url) is not None + + async def get_target(self) -> str: + """Check OS and processor architecture and return right postfix.""" + system = platform.system() + machine = platform.machine().lower() + + if system == "Windows": + return "Windows" + + if system == "Darwin": + return ( + "aarch64-apple-darwin" if machine == "arm64" else "x86_64-apple-darwin" + ) + + if system == "Linux": + return ( + "aarch64-unknown-linux-gnu" + if machine in ("aarch64", "arm64") + else "x86_64-unknown-linux-gnu" + ) + + return "x86_64-unknown-linux-gnu" + + def _get_deno(self) -> str | None: + if not (source := self.get("deno_source")) or source == "install_failed" or not os.path.exists(source): + logger.critical(self.deno_error) + return None + return source + + + def __init__(self): # noqa: ANN204, D107 + self.config = loader.ModuleConfig( + loader.ConfigValue( + "youtube_cookie", + None, + lambda: self.strings["cookie_desc"], + validator=loader.validators.Hidden(), + ), + ) + + async def client_ready(self, client, db): # noqa: ANN001, ANN201, D102, ARG002 + + deno_which = shutil.which("deno", path=os.environ.get("PATH", "") + os.pathsep + os.getcwd()) # noqa: E501 + + if deno_which: + self.set("deno_source", deno_which) + return + + logger.warning("Deno is not installed, attempting installation...") + target = await self.get_target() + if target == "Windows": + logger.critical( + "Windows platform is unsupported, please, unload the module.", + ) + return + async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(20)) as session: + download_link = f"https://github.com/denoland/deno/releases/latest/download/deno-{target}.zip" + async with session.get(download_link) as resp: + if resp.status == HTTPStatus.OK: + async with aiofiles.open("deno.zip", mode="wb") as f: + async for chunk in resp.content.iter_chunked(8192): + await f.write(chunk) + else: + logger.critical("Failed to download Deno: HTTP %s", resp.status) + self.set("deno_source", "install_failed") + return + if os.path.exists('deno.zip'): + with zipfile.ZipFile("deno.zip", "r") as zip_ref: + zip_ref.extractall() + os.remove('deno.zip') + os.chmod(path=os.path.join(os.getcwd(), "deno"), mode=0o755) + self.set("deno_source", os.path.join(os.getcwd(), "deno")) + return + + @loader.command(en_doc="Download video", ru_doc="Скачать видео") + async def ytdlvcmd(self, message): # noqa: ANN001, ANN201, D102 + args = utils.get_args(message) + if not args or not self._validate_url(args[0]) or len(args) > 1: + await utils.answer(message, self.strings["invalid_args"]) + return + + if not (source := self._get_deno()): + await utils.answer(message, self.strings["deno_err"]) + return + + await utils.answer(message, self.strings["downloading"]) + + filename_prefix = f"video_{message.id}" + ydl_opts = { + "quiet": True, + "outtmpl": f"{filename_prefix}.%(ext)s", + "js_runtimes": {"deno": {"path": source}}, + "format": "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best", + "merge_output_format": "mp4", + } + if cookie := self.get("youtube_cookie"): + ydl_opts["cookiefile"] = cookie + try: + with YoutubeDL(ydl_opts) as ydl: + info = await utils.run_sync(lambda: ydl.extract_info(args[0], download=True)) + filename = ydl.prepare_filename(info) + await utils.answer( + message, + self.strings["done"], + file=filename, + invert_media=True, + ) + os.remove(filename) + except Exception as e: + logger.exception("Catched error during download!") + await utils.answer(self.strings["err"].format(e)) + + @loader.command(en_doc="Download audio", ru_doc="Скачать аудио") + async def ytdlacmd(self, message): # noqa: ANN001, ANN201, D102 + args = utils.get_args(message) + if not args or not self._validate_url(args[0]) or len(args) > 1: + await utils.answer(message, self.strings["invalid_args"]) + return + + if not (source := self._get_deno()): + await utils.answer(message, self.strings["deno_err"]) + return + + await utils.answer(message, self.strings["downloading"]) + + ydl_opts = { + "quiet": True, + "outtmpl": f"audio_{message.id}.%(ext)s", + "js_runtimes": {"deno": {"path": source}}, + "format": "bestaudio/best", + "postprocessors": [ + { + "key": "FFmpegExtractAudio", + "preferredcodec": "mp3", + "preferredquality": "0", + }, + { + "key": "FFmpegMetadata", + "add_metadata": True, + }, + { + "key": "EmbedThumbnail", + }, + ], + "writethumbnail": True, + } + if cookie := self.get("youtube_cookie"): + ydl_opts["cookiefile"] = cookie + try: + with YoutubeDL(ydl_opts) as ydl: + info = await utils.run_sync(lambda: ydl.extract_info(args[0], download=True)) + filename = os.path.splitext(ydl.prepare_filename(info))[0] + ".mp3" + await utils.answer(message, self.strings["done"], file=filename) + os.remove(filename) + except Exception as e: + logger.exception("Catched error during download!") + await utils.answer(self.strings["err"].format(e)) + diff --git a/fiksofficial/python-modules/PyInstall.py b/fiksofficial/python-modules/PyInstall.py index 20157f1..5849a17 100644 --- a/fiksofficial/python-modules/PyInstall.py +++ b/fiksofficial/python-modules/PyInstall.py @@ -1 +1,109 @@ -# blacklist got uh +# meta developer: @pymodule +# requires: cryptography + +__version__ = (1, 0, 1) + +import base64 +import logging +from hashlib import sha256 + +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey +from cryptography.exceptions import InvalidSignature + +from telethon.tl.types import Message +from telethon import functions, types +from typing import Optional + +from .. import loader, utils + +logger = logging.getLogger(__name__) + +pubkey_data = """ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0S50qdajfeRmKqS+sBsn +VYYJL8loDMkfMf55flSPkhwwAwKbHk9i+VxRxHs32/J/LHxPR0ix3W6bgzf8m1/A +79uu2WkMrkfcIrAaOoz07EqHdyyD7MEZuHIAm977uQfdYgseOMa2uclYgNppJf35 +8oqGP7+0+ks5IxzNLn8/7zeo6DrlyOVJ2lgv860NXPQ+WqTttMovkjDTTwBthE8i +WMg02r6fo+GFafeyaTRHusPAGqg2oZ3VFIxcsJFVqgxmGJkbQVGgSuPwHWM5yPGi +gx0uB71i6y4NXk/PpoYdQMDanOFJvYe7JBpiktcqk8LB/PqPEm4ctsdGFiu9PR6K +wrzo0fK9zbpbPyiAHaCC/0/LkfWT7Cdc9bECDPaSGgJJde9wUpDoz+coAc5BfeW5 +6xu9J5fzkiw+zBQNlpkrtjG7JvqAYzul2GB+kDfCdVgkcQEPwBCTn6xGZvtWgE5b +yzQXaDkaTvbTUkUA41Ab6xsKSmU43otwV+9Rrzxovd+Nk7u9qwj5Ghambt37YNf3 +vUJ9XQFr8uy2nKaPHzGoLgNCBReUyua6aYqMtqCkU1id+dI4HqgDMPlDDGxGV6mK +Gamdu+eIJHl9chHrlTOxEDetLxZLuAdnoDRzHJyTce6NCsyz8tvwWnKv+8l3R+Bu +B9EM+BFIFwCXKt85P/eabMcCAwEAAQ== +-----END PUBLIC KEY----- +""" + +pubkey = serialization.load_pem_public_key(pubkey_data.strip().encode()) + +@loader.tds +class PyInstallMod(loader.Module): + """Provides PyModule modules installation trough buttons""" + + strings = { + "name": "PyInstall", + "_cls_doc": "Provides PyModule modules installation trough buttons", + "module_downloaded": "Module downloaded!" + } + + strings_ru = { + "_cls_doc": "Позволяет устанавливать модули от PyModule через кнопки", + "module_downloaded": "Модуль загружен!" + } + + async def on_dlmod(self, client, db): + ent = await self.client(functions.users.GetFullUserRequest('@pymodule_bot')) + if ent.full_user.blocked: + await self.client(functions.contacts.UnblockRequest('@pymodule_bot')) + await self.client.send_message('@pymodule_bot', '/start') + await self.client.delete_dialog('@pymodule_bot') + + async def _load_module(self, url: str, message: Optional[Message] = None): + loader_m = self.lookup("loader") + await loader_m.download_and_install(url, None) + + if getattr(loader_m, "_fully_loaded", getattr(loader_m, "fully_loaded", False)): + getattr( + loader_m, + "_update_modules_in_db", + getattr(loader_m, "update_modules_in_db", lambda: None), + )() + + async def watcher(self, message: Message): + if not isinstance(message, Message): + return + + if message.sender_id == 7575984561 and message.raw_text.startswith("#install"): + await message.delete() + + try: + fileref = message.raw_text.split("#install:")[1].strip().splitlines()[0].strip() + sig_b64 = message.raw_text.splitlines()[1].strip() + sig = base64.b64decode(sig_b64) + except (IndexError, ValueError): + logger.error("Invalid #install message format") + return + + try: + pubkey.verify( + signature=sig, + data=fileref.encode("utf-8"), + padding=padding.PKCS1v15(), + algorithm=hashes.SHA256() + ) + logger.info(f"Signature verified successfully for {fileref}") + except InvalidSignature: + logger.error(f"Got message with non-verified signature ({fileref=})") + return + except Exception as e: + logger.error(f"Signature verification error: {e}") + return + + await self._load_module( + f"https://raw.githubusercontent.com/fiksofficial/python-modules/refs/heads/main/{fileref}", + message + ) + await self.client.send_message('@pymodule_bot', self.strings['module_downloaded']) diff --git a/fiksofficial/python-modules/stream.py b/fiksofficial/python-modules/stream.py index f6059ce..771b5f2 100644 --- a/fiksofficial/python-modules/stream.py +++ b/fiksofficial/python-modules/stream.py @@ -1 +1,435 @@ -# Still not fixed. + +import asyncio +import mimetypes +import os +import subprocess +import time + +from .. import loader, utils +from ..inline.types import InlineCall + +def detect_type(path: str) -> str: + mime, _ = mimetypes.guess_type(path) + if not mime: + return "video" + if mime.startswith("video"): + return "video" + if mime.startswith("audio"): + return "audio" + if mime.startswith("image"): + return "image" + return "video" + +TYPE_ICON = {"video": "🎬", "audio": "🎵", "image": "🖼️"} +PRESETS = ["ultrafast", "superfast", "veryfast", "faster", "fast", "medium", "slow"] +TUNES = ["zerolatency", "film", "animation", "grain", "stillimage", "fastdecode"] +SCALES = ["off", "426x240", "640x360", "854x480", "1280x720", "1920x1080", "2560x1440"] +FPS_OPT = [24, 25, 30, 48, 60] + +def build_cmd(file_path: str, rtmp_url: str, cfg: dict) -> list: + preset = cfg.get("preset", "veryfast") + tune = cfg.get("tune", "zerolatency") + vbr = cfg.get("vbitrate", "2000k") + abr = cfg.get("abitrate", "128k") + fps = str(cfg.get("fps", 30)) + res = cfg.get("resolution", None) + threads = str(cfg.get("threads", 0)) + gop = str(int(fps) * 2) + bufsize = str(int(vbr.replace("k", "")) * 2) + "k" + ftype = detect_type(file_path) + + base = ["ffmpeg", "-re", "-stream_loop", "-1", "-threads", threads] + vf_scale = f",scale={res}" if res else "" + common_v = [ + "-c:v", "libx264", "-preset", preset, "-tune", tune, + "-pix_fmt", "yuv420p", "-profile:v", "baseline", + "-r", fps, "-g", gop, "-keyint_min", gop, "-sc_threshold", "0", + "-b:v", vbr, "-maxrate", vbr, "-bufsize", bufsize, + ] + common_a = ["-c:a", "aac", "-b:a", abr, "-ar", "44100"] + out = ["-f", "flv", rtmp_url] + + if ftype == "video": + vf = ["-vf", f"scale=trunc(iw/2)*2:trunc(ih/2)*2{vf_scale}"] if res else [] + return base + ["-i", file_path] + common_v + vf + common_a + out + if ftype == "audio": + size = res or "1280x720" + return ( + base + + ["-i", file_path, "-f", "lavfi", "-i", f"color=c=black:s={size}:r={fps}"] + + ["-shortest"] + common_v + common_a + + ["-map", "1:v:0", "-map", "0:a:0"] + out + ) + if ftype == "image": + scale_vf = f"scale=trunc(iw/2)*2:trunc(ih/2)*2{vf_scale}" + return ( + base + + ["-loop", "1", "-i", file_path, "-f", "lavfi", "-i", "anullsrc=r=44100:cl=stereo"] + + ["-vf", scale_vf] + common_v + + ["-shortest"] + common_a + + ["-map", "0:v:0", "-map", "1:a:0"] + out + ) + raise ValueError(f"Unsupported: {ftype}") + +@loader.tds +class StreamMod(loader.Module): + """📡 RTMP media streaming""" + strings = { + "name": "Stream", + "status_active": "▶️ Stream is live\n\n{icon} {file}\n⏱ Time: {elapsed}\n🔢 PID: {pid}\n📡 {rtmp}\n🎥 {vbr} | {fps}fps | {preset}\n🔊 {abr}\n📋 Queue: {queue}", + "status_idle": "⏸ Stream is not active", + "status_queue": "\n📋 Queue: {n}", + "stopped": "⏹ Stream stopped.", + "no_rtmp": "❌ RTMP not configured!\nTap a button to set it up.", + "downloading": "⏳ Downloading…", + "dl_failed": "❌ Failed to download file.", + "queued": "📋 Added to queue ({n})\n{icon} {file}", + "not_running": "Not running", + "queue_empty": "Queue is empty", + "queue_header": "📋 Queue:\n", + "settings_title": "⚙️ Stream settings", + "btn_stop": "⏹ Stop", + "btn_queue": "📋 Queue", + "btn_refresh": "🔄 Refresh", + "btn_settings": "⚙️ Settings", + "btn_status": "📊 Status", + "btn_back": "🔙 Back", + "btn_preset": "🎞 Preset: {v}", + "btn_tune": "🎭 Tune: {v}", + "btn_vbr": "🎥 Video: {v}", + "btn_abr": "🔊 Audio: {v}", + "btn_fps": "📐 FPS: {v}", + "btn_res": "🖥 Res: {v}", + "btn_threads": "🧵 Threads: {v}", + "btn_rtmps": "📡 RTMP URL", + "btn_key": "🔑 Stream key", + "btn_set_rtmps": "📡 Set RTMP URL", + "btn_set_key": "🔑 Set stream key", + "ph_vbr": "Video bitrate, e.g. 2000k", + "ph_abr": "Audio bitrate, e.g. 128k", + "ph_threads": "Thread count (0 = auto)", + "ph_rtmps": "rtmp://a.rtmp.youtube.com/live2", + "ph_key": "Stream key...", + } + + strings_ru = { + "_cls_doc": "📡 RTMP стриминг медиафайлов", + "status_active": "▶️ Трансляция идёт\n\n{icon} {file}\n⏱ Время: {elapsed}\n🔢 PID: {pid}\n📡 {rtmp}\n🎥 {vbr} | {fps}fps | {preset}\n🔊 {abr}\n📋 В очереди: {queue}", + "status_idle": "⏸ Трансляция не активна", + "status_queue": "\n📋 В очереди: {n}", + "stopped": "⏹ Трансляция остановлена.", + "no_rtmp": "❌ RTMP не настроен!\nНажми кнопку чтобы задать прямо сейчас.", + "downloading": "⏳ Скачиваю…", + "dl_failed": "❌ Не удалось скачать файл.", + "queued": "📋 Добавлено в очередь ({n} шт.)\n{icon} {file}", + "not_running": "Не запущено", + "queue_empty": "Очередь пуста", + "queue_header": "📋 Очередь:\n", + "settings_title": "⚙️ Настройки трансляции", + "btn_stop": "⏹ Стоп", + "btn_queue": "📋 Очередь", + "btn_refresh": "🔄 Обновить", + "btn_settings": "⚙️ Настройки", + "btn_status": "📊 Статус", + "btn_back": "🔙 Назад", + "btn_preset": "🎞 Пресет: {v}", + "btn_tune": "🎭 Tune: {v}", + "btn_vbr": "🎥 Видео: {v}", + "btn_abr": "🔊 Аудио: {v}", + "btn_fps": "📐 FPS: {v}", + "btn_res": "🖥 Разр: {v}", + "btn_threads": "🧵 Треды: {v}", + "btn_rtmps": "📡 RTMP URL", + "btn_key": "🔑 Ключ", + "btn_set_rtmps": "📡 Задать RTMP URL", + "btn_set_key": "🔑 Задать ключ", + "ph_vbr": "Битрейт видео, напр. 2000k", + "ph_abr": "Битрейт аудио, напр. 128k", + "ph_threads": "Потоков (0 = авто)", + "ph_rtmps": "rtmp://a.rtmp.youtube.com/live2", + "ph_key": "Ключ трансляции...", + } + + def __init__(self): + self._proc: subprocess.Popen | None = None + self._file: str | None = None + self._started: float | None = None + self._queue: list[str] = [] + self._qtask: asyncio.Task | None = None + self.config = loader.ModuleConfig( + loader.ConfigValue("rtmps", "", "Base RTMP URL (rtmp://...)"), + loader.ConfigValue("key", "", "Stream key"), + loader.ConfigValue("preset", "veryfast", "x264 preset", + validator=loader.validators.Choice(PRESETS)), + loader.ConfigValue("tune", "zerolatency","x264 tune", + validator=loader.validators.Choice(TUNES)), + loader.ConfigValue("vbitrate", "2000k", "Video bitrate (e.g. 1500k, 3000k)"), + loader.ConfigValue("abitrate", "128k", "Audio bitrate (e.g. 64k, 192k)"), + loader.ConfigValue("fps", 30, "Frames per second", + validator=loader.validators.Integer(minimum=1, maximum=120)), + loader.ConfigValue("resolution", "", "Output resolution (e.g. 1280x720, empty = no scaling)"), + loader.ConfigValue("threads", 0, "FFmpeg thread count (0 = auto)", + validator=loader.validators.Integer(minimum=0, maximum=64)), + loader.ConfigValue("loop", True, "Loop the file indefinitely", + validator=loader.validators.Boolean()), + loader.ConfigValue("reconnect", True, "Auto-restart on stream disconnect", + validator=loader.validators.Boolean()), + ) + + def _s(self, key: str, **kw) -> str: + return self.strings[key].format(**kw) if kw else self.strings[key] + + def _running(self) -> bool: + return self._proc is not None and self._proc.poll() is None + + def _stop(self): + if self._proc: + try: + self._proc.terminate() + self._proc.wait(timeout=5) + except Exception: + try: + self._proc.kill() + except Exception: + pass + self._proc = None + if self._file and os.path.exists(self._file): + try: + os.remove(self._file) + except Exception: + pass + self._file = None + self._started = None + + def _launch(self, path: str): + cfg = {k: self.config[k] for k in ("preset", "tune", "vbitrate", "abitrate", "fps", "threads")} + cfg["resolution"] = self.config["resolution"] or None + rtmp = f"{self.config['rtmps'].rstrip('/')}/{self.config['key']}" + self._proc = subprocess.Popen(build_cmd(path, rtmp, cfg), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + self._file = path + self._started = time.time() + + def _elapsed(self) -> str: + if not self._started: + return "00:00:00" + e = int(time.time() - self._started) + return f"{e//3600:02d}:{(e%3600)//60:02d}:{e%60:02d}" + + def _status_text(self) -> str: + if not self._running(): + txt = self._s("status_idle") + if self._queue: + txt += self._s("status_queue", n=len(self._queue)) + return txt + ftype = detect_type(self._file or "") + rtmp = f"{self.config['rtmps'].rstrip('/')}/{self.config['key'][:4]}***" + return self._s( + "status_active", + icon=TYPE_ICON.get(ftype, "📄"), + file=os.path.basename(self._file or "?"), + elapsed=self._elapsed(), + pid=self._proc.pid if self._proc else "—", + rtmp=rtmp, + vbr=self.config["vbitrate"], + fps=self.config["fps"], + preset=self.config["preset"], + abr=self.config["abitrate"], + queue=len(self._queue), + ) + + def _res_label(self) -> str: + r = self.config["resolution"] + return r if r else "auto" + + def _thr_label(self) -> str: + t = self.config["threads"] + return str(t) if t else "auto" + + def _main_markup(self) -> list: + running = self._running() + return [ + [ + {"text": self._s("btn_stop"), "callback": self._cb_stop} if running + else {"text": self._s("btn_queue"), "callback": self._cb_queue}, + {"text": self._s("btn_refresh"), "callback": self._cb_refresh}, + ], + [ + {"text": self._s("btn_settings"), "callback": self._cb_settings}, + {"text": self._s("btn_status"), "callback": self._cb_status}, + ], + ] + + def _settings_markup(self) -> list: + return [ + [ + {"text": self._s("btn_preset", v=self.config["preset"]), "callback": self._cb_set_preset}, + {"text": self._s("btn_tune", v=self.config["tune"]), "callback": self._cb_set_tune}, + ], + [ + {"text": self._s("btn_vbr", v=self.config["vbitrate"]), + "input": self._s("ph_vbr"), "handler": self._ih_vbr}, + {"text": self._s("btn_abr", v=self.config["abitrate"]), + "input": self._s("ph_abr"), "handler": self._ih_abr}, + ], + [ + {"text": self._s("btn_fps", v=self.config["fps"]), "callback": self._cb_set_fps}, + {"text": self._s("btn_res", v=self._res_label()), "callback": self._cb_set_res}, + ], + [ + {"text": self._s("btn_threads", v=self._thr_label()), + "input": self._s("ph_threads"), "handler": self._ih_threads}, + ], + [ + {"text": self._s("btn_rtmps"), + "input": self._s("ph_rtmps"), "handler": self._ih_rtmps}, + {"text": self._s("btn_key"), + "input": self._s("ph_key"), "handler": self._ih_key}, + ], + [{"text": self._s("btn_back"), "callback": self._cb_back}], + ] + + async def _ih_vbr(self, call: InlineCall, query: str): + q = query.strip() + if q.endswith("k") and q[:-1].isdigit(): + self.config["vbitrate"] = q + await call.edit(self._s("settings_title"), reply_markup=self._settings_markup()) + + async def _ih_abr(self, call: InlineCall, query: str): + q = query.strip() + if q.endswith("k") and q[:-1].isdigit(): + self.config["abitrate"] = q + await call.edit(self._s("settings_title"), reply_markup=self._settings_markup()) + + async def _ih_threads(self, call: InlineCall, query: str): + q = query.strip() + if q.isdigit(): + self.config["threads"] = int(q) + await call.edit(self._s("settings_title"), reply_markup=self._settings_markup()) + + async def _ih_rtmps(self, call: InlineCall, query: str): + q = query.strip() + if q.startswith("rtmp"): + self.config["rtmps"] = q.rstrip("/") + await call.edit(self._s("settings_title"), reply_markup=self._settings_markup()) + + async def _ih_key(self, call: InlineCall, query: str): + q = query.strip() + if q: + self.config["key"] = q + await call.edit(self._s("settings_title"), reply_markup=self._settings_markup()) + + async def _cb_refresh(self, call: InlineCall): + await call.edit(self._status_text(), reply_markup=self._main_markup()) + + async def _cb_status(self, call: InlineCall): + await call.answer(self._elapsed() if self._running() else self._s("not_running")) + + async def _cb_stop(self, call: InlineCall): + self._queue.clear() + if self._qtask: + self._qtask.cancel() + self._qtask = None + self._stop() + await call.edit(self._s("stopped"), reply_markup=self._main_markup()) + + async def _cb_queue(self, call: InlineCall): + if not self._queue: + await call.answer(self._s("queue_empty"), show_alert=True) + return + lines = [f"{i}. {TYPE_ICON.get(detect_type(f), '📄')} {os.path.basename(f)}" + for i, f in enumerate(self._queue, 1)] + await call.answer(self._s("queue_header") + "\n".join(lines), show_alert=True) + + async def _cb_back(self, call: InlineCall): + await call.edit(self._status_text(), reply_markup=self._main_markup()) + + async def _cb_settings(self, call: InlineCall): + await call.edit(self._s("settings_title"), reply_markup=self._settings_markup()) + + async def _cb_set_preset(self, call: InlineCall): + cur = self.config["preset"] + self.config["preset"] = PRESETS[(PRESETS.index(cur) + 1) % len(PRESETS)] + await call.edit(self._s("settings_title"), reply_markup=self._settings_markup()) + + async def _cb_set_tune(self, call: InlineCall): + cur = self.config["tune"] + self.config["tune"] = TUNES[(TUNES.index(cur) + 1) % len(TUNES)] + await call.edit(self._s("settings_title"), reply_markup=self._settings_markup()) + + async def _cb_set_fps(self, call: InlineCall): + cur = self.config["fps"] + self.config["fps"] = FPS_OPT[(FPS_OPT.index(cur) + 1) % len(FPS_OPT)] if cur in FPS_OPT else 30 + await call.edit(self._s("settings_title"), reply_markup=self._settings_markup()) + + async def _cb_set_res(self, call: InlineCall): + cur = self.config["resolution"] or "off" + idx = SCALES.index(cur) if cur in SCALES else 0 + nxt = SCALES[(idx + 1) % len(SCALES)] + self.config["resolution"] = "" if nxt == "off" else nxt + await call.edit(self._s("settings_title"), reply_markup=self._settings_markup()) + + @loader.command(ru_doc="[ответ на медиа] – запустить трансляцию") + async def stream(self, message): + """[reply to media] — start stream or add to queue""" + if not self.config["rtmps"] or not self.config["key"]: + await self.inline.form( + self._s("no_rtmp"), + message=message, + reply_markup=[ + [{"text": self._s("btn_set_rtmps"), "input": self._s("ph_rtmps"), "handler": self._ih_rtmps}], + [{"text": self._s("btn_set_key"), "input": self._s("ph_key"), "handler": self._ih_key}], + ], + ) + return + + reply = await message.get_reply_message() + if not reply or not reply.media: + await self.inline.form( + self._status_text(), + message=message, + reply_markup=self._main_markup(), + ) + return + + status = await utils.answer(message, self._s("downloading")) + path = await reply.download_media(file=f"/tmp/stream_{int(time.time())}") + if not path: + await status.edit(self._s("dl_failed")) + return + await status.delete() + + if self._running(): + self._queue.append(path) + await self.inline.form( + self._s("queued", n=len(self._queue), icon=TYPE_ICON.get(detect_type(path), "📄"), file=os.path.basename(path)), + message=message, + reply_markup=self._main_markup(), + ) + return + + self._stop() + self._launch(path) + await self.inline.form( + self._status_text(), + message=message, + reply_markup=self._main_markup(), + ) + + @loader.command(ru_doc="– панель управления трансляцией") + async def streamctl(self, message): + """– open stream control panel""" + await self.inline.form( + self._status_text(), + message=message, + reply_markup=self._main_markup(), + ) + + @loader.command(ru_doc="– остановить трансляцию и очистить очередь") + async def streamstop(self, message): + """– stop stream and clear queue""" + self._queue.clear() + if self._qtask: + self._qtask.cancel() + self._qtask = None + self._stop() + await utils.answer(message, self._s("stopped")) \ No newline at end of file diff --git a/modules.json b/modules.json index fc589e9..84299d6 100644 --- a/modules.json +++ b/modules.json @@ -2,336 +2,34 @@ "modules": { "LimokaLegacy.py": { "name": "LimokaLegacy", - "description": "Modules are now in one place with easy searching!\nFor Hikka and FTG Userbots. This module has outdated functionality and is kept for legacy reasons only.\nRead https://t.me/limokanews/133 for more information.", + "description": "", "cls_doc": {}, "meta": { "pic": null, "banner": null, "developer": "@limokanews" }, - "commands": [ - { - "limoka": "[query / nothing] - Search modules | (RU) [запрос / ничего] — Поиск модулей" - }, - { - "lshistory": "[clear] - Show or clear search history | (RU) [clear] — Показать или очистить историю поиска" - } - ], - "new_commands": [ - { - "name": "limoka", - "original_name": "limokacmd", - "description": { - "default": "[query / nothing] - Search modules", - "ru": "[запрос / ничего] — Поиск модулей" - }, - "cmd_names": {}, - "aliases": [], - "usage": null, - "inline": false, - "is_inline_handler": false, - "decorators": [] - }, - { - "name": "lshistory", - "original_name": "lshistorycmd", - "description": { - "default": "[clear] - Show or clear search history", - "ru": "[clear] — Показать или очистить историю поиска" - }, - "cmd_names": {}, - "aliases": [], - "usage": null, - "inline": false, - "is_inline_handler": false, - "decorators": [] - } - ], + "commands": [], + "new_commands": [], "inline_handlers": [], - "strings": { - "name": "Limoka Legacy", - "wait": "Just wait\n🔍 A search is underway among {count} modules for the query: {query}\n{fact}", - "found_header": "🔍 Found module {name} by query: {query}\n\nℹ️ Description: {description}\n🧑‍💻 Developer: {username}\n\n🏷 Tags: {tags}\n\n", - "found_body": "{commands}", - "found_footer": "", - "caption_short": "🔍 {safe_name}\nℹ️ Description: {safe_desc}\n🧑‍💻 Dev: {dev_username}", - "command_template": "{emoji} {prefix}{command} — {description}\n", - "inline_handler_template": "{inline_bot} {command} — {description}\n", - "emojis": { - "1": "1️⃣", - "2": "2️⃣", - "3": "3️⃣", - "4": "4️⃣", - "5": "5️⃣", - "6": "6️⃣", - "7": "7️⃣", - "8": "8️⃣", - "9": "9️⃣" - }, - "404": " Not found by query: {query}", - "noargs": " No args", - "?": "🔎 Request too short / not found", - "no_info": "No information", - "facts": [ - "🛡 The limoka catalog is carefully moderated!", - "🚀 Limoka performance allows you to search for modules quickly!" - ], - "history": "🔎 Your search history:\n{history}", - "empty_history": "🔎 Your search history is empty!", - "enter_query": "🔍 Enter new search query:", - "global_search": "🔍 Global search for {query} — found {count} modules", - "change_query": "🔍 Change query", - "back": "🔙 Back", - "global_button": "🌍 Results", - "first_page": "This is the first page!", - "last_page": "This is the last page!", - "display_error": "Error displaying module. Please try again.", - "error_occurred": "An error occurred. Please try again.", - "start_search_form": "🔍 Limoka Search\nEnter your query to search for modules:", - "history_cleared": "🧹 Search history cleared!", - "invalid_history_arg": " Invalid argument for history command. Use:\n.lshistory - show history\n.lshistory clear - clear history", - "close": "❌ Close", - "indexing_in_progress": "⚠️ Database is busy, try again later. If issue persists, try removing limoka_index in the userbot's root folder. If error persists again, report to developers", - "install_btn": "🛠 Install", - "source_btn": "📦 Source", - "installed": "✅ Installed successfully!", - "install_failed": "❌ Installation failed!", - "tags": { - "newbie": "Newbie", - "herokutrusted": "Heroku Trusted", - "hikkatrusted": "Hikka Trusted", - "nonactive": "Non-active repository", - "nonlongermaintained": "Abandoned repository" - }, - "name_ru": "Limoka", - "wait_ru": "Подождите\n🔍 Идёт поиск среди {count} модулей по запросу: {query}\n{fact}", - "found_header_ru": "🔍 Найден модуль {name} по запросу: {query}\n\nℹ️ Описание: {description}\n🧑‍💻 Разработчик: {username}\n\n🏷 Теги: {tags}\n\n", - "found_body_ru": "{commands}", - "found_footer_ru": "", - "caption_short_ru": "🔍 {safe_name}\nℹ️ Описание: {safe_desc}\n🧑‍💻 Разработчик: {dev_username}", - "command_template_ru": "{emoji} {prefix}{command} — {description}\n", - "inline_handler_template_ru": "{inline_bot} {command} — {description}\n", - "404_ru": " Не найдено по запросу: {query}", - "noargs_ru": " Нет аргументов", - "?_ru": "🔎 Запрос слишком короткий / не найден", - "no_info_ru": "Нет информации", - "history_ru": "🔎 История поиска:\n{history}", - "empty_history_ru": "🔎 История поиска пуста!", - "enter_query_ru": "🔍 Введите новый поисковый запрос:", - "global_search_ru": "🔍 Глобальный поиск по {query} — найдено {count} модулей", - "change_query_ru": "🔍 Изменить запрос", - "back_ru": "🔙 Назад", - "global_button_ru": "🌍 Результаты", - "first_page_ru": "Это первая страница!", - "last_page_ru": "Это последняя страница!", - "display_error_ru": "Ошибка отображения модуля. Пожалуйста, попробуйте еще раз.", - "error_occurred_ru": "Произошла ошибка. Пожалуйста, попробуйте еще раз.", - "start_search_form_ru": "🔍 Limoka Поиск\nВведите ваш запрос для поиска модулей:", - "history_cleared_ru": "🧹 История поиска очищена!", - "invalid_history_arg_ru": " Неверный аргумент для команды истории. Используйте:\n.lshistory - показать историю\n.lshistory clear - очистить историю", - "close_ru": "❌ Закрыть", - "indexing_in_progress_ru": "⚠️ База данных занята, попробуйте снова через несколько секунд. Если ошибка сохраняется, попробуйте удалить limoka_index в корневой папке юзербота. Если ошибка сохраняется снова, сообщите разработчикам", - "install_btn_ru": "🛠 Установить", - "source_btn_ru": "📦 Исходный код", - "installed_ru": "✅ Установлено успешно!", - "install_failed_ru": "❌ Установка не удалась!" - }, + "strings": {}, "has_on_load": false, "has_on_unload": false, "class_cmd_names": {} }, "Limoka.py": { "name": "Limoka", - "description": "Modules are now in one place with easy searching!", - "cls_doc": { - "ru": "Модули теперь в одном месте с простым и удобным поиском!" - }, + "description": "", + "cls_doc": {}, "meta": { "pic": null, "banner": null, "developer": "@limokanews" }, - "commands": [ - { - "limoka": "[query / nothing] - Search modules | (RU) [запрос / ничего] — Поиск модулей" - }, - { - "lshistory": "[clear] - Show or clear search history | (RU) [clear] — Показать или очистить историю поиска" - } - ], - "new_commands": [ - { - "name": "limoka", - "original_name": "limokacmd", - "description": { - "default": "[query / nothing] - Search modules", - "ru": "[запрос / ничего] — Поиск модулей" - }, - "cmd_names": {}, - "aliases": [], - "usage": null, - "inline": false, - "is_inline_handler": false, - "decorators": [] - }, - { - "name": "lshistory", - "original_name": "lshistorycmd", - "description": { - "default": "[clear] - Show or clear search history", - "ru": "[clear] — Показать или очистить историю поиска" - }, - "cmd_names": {}, - "aliases": [], - "usage": null, - "inline": false, - "is_inline_handler": false, - "decorators": [] - } - ], + "commands": [], + "new_commands": [], "inline_handlers": [], - "strings": { - "name": "Limoka", - "wait": "Just wait\n🔍 A search is underway among {count} modules for the query: {query}\n{fact}", - "found_header": "🔍 Found module {name} by query: {query}\n\nℹ️ Description: {description}\n🧑‍💻 Developer: {username}\n\n🏷 Tags: {tags}\n\n", - "found_body": "{commands}", - "found_footer": "\n🪄 {prefix}dlm {url}{module_path}", - "caption_short": "🔍 {safe_name}\nℹ️ Description: {safe_desc}\n🧑‍💻 Dev: {dev_username}\n🪄 {prefix}dlm {module_path}", - "command_template": "{emoji} {prefix}{command} — {description}\n", - "inline_handler_template": "{inline_bot} {command} — {description}\n", - "emojis": { - "1": "1️⃣", - "2": "2️⃣", - "3": "3️⃣", - "4": "4️⃣", - "5": "5️⃣", - "6": "6️⃣", - "7": "7️⃣", - "8": "8️⃣", - "9": "9️⃣" - }, - "404": " Not found by query: {query}", - "noargs": " No args", - "?": "🔎 Request too short / not found", - "no_info": "No information", - "facts": [ - "🛡 The limoka catalog is carefully moderated!", - "🚀 Limoka performance allows you to search for modules quickly!" - ], - "inline404": "Not found", - "inline?": "Request too short / not found", - "inlinenoargs": "Please, enter query", - "history": "🔎 Your search history:\n{history}", - "filter_menu": "Choose filters", - "filter_cat": "📑 Filter by Category", - "apply_filters": "✅ Apply Filters", - "clear_filters": "🗑 Clear Filters", - "back_to_results": "🔙 Back to Results", - "empty_history": "🔎 Your search history is empty!", - "enter_query": "🔍 Enter new search query:", - "global_search": "🔍 Global search for {query} — found {count} modules", - "change_query": "🔍 Change query", - "no_modules": "No modules available.", - "filter_title": "🏷 Filters", - "category_title": "📂 Categories", - "selected_categories": "✅ Selected categories: {categories}", - "no_categories": "No categories found in the module database", - "select_category": "Select categories for query: {query}\n(You can select multiple)", - "back": "🔙 Back", - "category": "📁 {category}", - "no_category": "No category", - "global_button": "🌍 Results", - "filtered_button": "🏷️ Filtered search", - "inline_search": "🔍 Search in Limoka", - "inline_no_results": "❌ No modules found", - "inline_error": "❌ Search error occurred", - "inline_short_query": "❌ Query too short (min 2 chars)", - "inline_switch_pm": "💬 Open in chat", - "inline_switch_pm_text": "🔍 Results for: {query}", - "inline_start_message": "🔍 Limoka Search\nType module name or keyword", - "first_page": "This is the first page!", - "last_page": "This is the last page!", - "display_error": "Error displaying module. Please try again.", - "error_occurred": "An error occurred. Please try again.", - "start_search_form": "🔍 Limoka Search\nEnter your query to search for modules:", - "global_search_form": "🔍 Global Search\nEnter your query to search ALL modules without filters:", - "history_cleared": "🧹 Search history cleared!", - "invalid_history_arg": " Invalid argument for history command. Use:\n.lshistory - show history\n.lshistory clear - clear history", - "close": "❌ Close", - "watcher_no_tag": "❌ Invalid message format. No #limoka tag found.", - "watcher_invalid_format": "❌ Invalid format. Expected: #limoka:path:signature", - "watcher_signature_invalid": "❌ Signature invalid! Installation aborted.", - "watcher_loader_missing": "❌ Loader module not found.", - "watcher_module_not_found": "❌ Module not found in Limoka database: {path}", - "watcher_critical": "❌ Critical error: {error}", - "tags": { - "herokutrusted": "Heroku Trusted", - "hikkatrusted": "Hikka Trusted", - "nonactive": "Non-Active Repository", - "nonlongermaintained": "No Longer Maintained Repository", - "newbie": "Newbie" - }, - "indexing_in_progress": "⚠️ Database is busy, try again later. If issue persists, try removing limoka_index in the userbot's root folder. If error persists again, report to developers", - "name_ru": "Limoka", - "wait_ru": "Подождите\n🔍 Идёт поиск среди {count} модулей по запросу: {query}\n{fact}", - "found_header_ru": "🔍 Найден модуль {name} по запросу: {query}\n\nℹ️ Описание: {description}\n🧑‍💻 Разработчик: {username}\n\n🏷 Теги: {tags}\n\n", - "found_body_ru": "{commands}", - "found_footer_ru": "\n🪄 {prefix}dlm {url}{module_path}", - "caption_short_ru": "🔍 {safe_name}\nℹ️ Описание: {safe_desc}\n🧑‍💻 Разработчик: {dev_username}\n🪄 {prefix}dlm {module_path}", - "command_template_ru": "{emoji} {prefix}{command} — {description}\n", - "inline_handler_template_ru": "{inline_bot} {command} — {description}\n", - "404_ru": " Не найдено по запросу: {query}", - "noargs_ru": " Нет аргументов", - "?_ru": "🔎 Запрос слишком короткий / не найден", - "no_info_ru": "Нет информации", - "inline404_ru": "Не найдено", - "inline?_ru": "Запрос слишком короткий / не найден", - "inlinenoargs_ru": "Введите запрос", - "history_ru": "🔎 История поиска:\n{history}", - "filter_menu_ru": "Выберите фильтры", - "filter_cat_ru": "📑 Фильтр по категориям", - "apply_filters_ru": "✅ Применить фильтры", - "clear_filters_ru": "🗑 Очистить фильтры", - "back_to_results_ru": "🔙 Вернуться к результатам", - "empty_history_ru": "🔎 История поиска пуста!", - "enter_query_ru": "🔍 Введите новый поисковый запрос:", - "global_search_ru": "🔍 Глобальный поиск по {query} — найдено {count} модулей", - "change_query_ru": "🔍 Изменить запрос", - "no_modules_ru": "Модули недоступны.", - "filter_title_ru": "🏷 Фильтры", - "category_title_ru": "📂 Категории", - "selected_categories_ru": "✅ Выбранные категории: {categories}", - "no_categories_ru": "Категории не найдены в базе модулей", - "select_category_ru": "Выберите категории для запроса: {query}\n(Можно выбрать несколько)", - "back_ru": "🔙 Назад", - "category_ru": "📁 {category}", - "no_category_ru": "Без категории", - "global_button_ru": "🌍 Результаты", - "filtered_button_ru": "🏷️ Поиск с фильтрами", - "inline_search_ru": "🔍 Поиск в Limoka", - "inline_no_results_ru": "❌ Модули не найдены", - "inline_error_ru": "❌ Ошибка поиска", - "inline_short_query_ru": "❌ Запрос слишком короткий (мин. 2 символа)", - "inline_switch_pm_ru": "💬 Открыть в чате", - "inline_switch_pm_text_ru": "🔍 Результаты для: {query}", - "inline_start_message_ru": "🔍 Limoka Поиск\nВведите название модуля или ключевое слово", - "first_page_ru": "Это первая страница!", - "last_page_ru": "Это последняя страница!", - "display_error_ru": "Ошибка отображения модуля. Пожалуйста, попробуйте еще раз.", - "error_occurred_ru": "Произошла ошибка. Пожалуйста, попробуйте еще раз.", - "start_search_form_ru": "🔍 Limoka Поиск\nВведите ваш запрос для поиска модулей:", - "global_search_form_ru": "🔍 Глобальный Поиск\nВведите запрос для поиска ВСЕХ модулей без фильтров:", - "history_cleared_ru": "🧹 История поиска очищена!", - "invalid_history_arg_ru": " Неверный аргумент для команды истории. Используйте:\n.lshistory - показать историю\n.lshistory clear - очистить историю", - "close_ru": "❌ Закрыть", - "watcher_no_tag_ru": "❌ Неверный формат сообщения. Тег #limoka не найден.", - "watcher_invalid_format_ru": "❌ Неверный формат. Ожидается: #limoka:path:signature", - "watcher_signature_invalid_ru": "❌ Неверная подпись! Установка отменена.", - "watcher_loader_missing_ru": "❌ Модуль загрузчика не найден.", - "watcher_module_not_found_ru": "❌ Модуль не найден в базе Limoka: {path}", - "watcher_critical_ru": "❌ Критическая ошибка: {error}", - "indexing_in_progress_ru": "⚠️ База данных занята, попробуйте снова через несколько секунд. Если ошибка сохраняется, попробуйте удалить limoka_index в корневой папке юзербота. Если ошибка сохраняется снова, сообщите разработчикам" - }, + "strings": {}, "has_on_load": false, "has_on_unload": false, "class_cmd_names": {} @@ -3920,31 +3618,6 @@ "has_on_unload": false, "class_cmd_names": {} }, - "fiksofficial/python-modules/PyInstall.py": { - "name": "PyInstallMod", - "description": "Provides PyModule modules installation trough buttons", - "cls_doc": { - "default": "Provides PyModule modules installation trough buttons", - "ru": "Позволяет устанавливать модули от PyModule через кнопки" - }, - "meta": { - "pic": null, - "banner": null, - "developer": "@pymodule" - }, - "commands": [], - "new_commands": [], - "inline_handlers": [], - "strings": { - "name": "PyInstall", - "_cls_doc": "Provides PyModule modules installation trough buttons", - "module_downloaded": "Module downloaded!", - "module_downloaded_ru": "Модуль загружен!" - }, - "has_on_load": false, - "has_on_unload": false, - "class_cmd_names": {} - }, "fiksofficial/python-modules/checkhost.py": { "name": "CheckHostMod", "description": "Check host via check-host.net", @@ -8637,6 +8310,342 @@ "has_on_unload": true, "class_cmd_names": {} }, + "Fixyres/FModules/BSR.py": { + "name": "BSR", + "description": "Module for finding nearby game rooms in BrawlStars.", + "cls_doc": { + "ru": "Модуль для поиска ближайших игровых комнат в BrawlStars.", + "ua": "Модуль для пошуку найближчих ігрових кімнат у BrawlStars.", + "kz": "BrawlStars ойынында жақын маңдағы ойын бөлмелерін табуға арналған модуль.", + "uz": "BrawlStars'da eng yaqin o'yin xonalarini topish uchun modul.", + "fr": "Module pour trouver des salles de jeu à proximité dans BrawlStars.", + "de": "Modul zum Finden von nahegelegenen Spielräumen in BrawlStars.", + "jp": "BrawlStarsで近くのゲームルームを検索するためのモジュール。" + }, + "meta": { + "pic": null, + "banner": "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/BSR/banner.png", + "developer": "@FModules", + "fhsdesc": "brawlstars, game, funny" + }, + "commands": [ + { + "bsr": "(room code/link) (previous) (next) - find rooms. | (RU) (код комнаты/ссылка) (предыдущие) (следующие) - найти комнаты. | (UA) (код кімнати/посилання) (попередні) (наступні) - знайти кімнати. | (KZ) (бөлме коды/сілтеме) (алдыңғы) (келесі) - бөлмелерді табу. | (UZ) (xona kodi/havolasi) (oldingi) (keyingi) - xonalarni topish. | (FR) (code/lien) (précédents) (suivants) - trouver des salles. | (DE) (Raumcode/Link) (vorherige) (nächste) - Räume finden. | (JP) (コード/リンク) (前) (次) - ルームを検索します。" + } + ], + "new_commands": [ + { + "name": "bsr", + "original_name": "bsr", + "description": { + "default": "(room code/link) (previous) (next) - find rooms.", + "ru": "(код комнаты/ссылка) (предыдущие) (следующие) - найти комнаты.", + "ua": "(код кімнати/посилання) (попередні) (наступні) - знайти кімнати.", + "kz": "(бөлме коды/сілтеме) (алдыңғы) (келесі) - бөлмелерді табу.", + "uz": "(xona kodi/havolasi) (oldingi) (keyingi) - xonalarni topish.", + "fr": "(code/lien) (précédents) (suivants) - trouver des salles.", + "de": "(Raumcode/Link) (vorherige) (nächste) - Räume finden.", + "jp": "(コード/リンク) (前) (次) - ルームを検索します。" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + } + ], + "inline_handlers": [], + "strings": { + "name": "BSR", + "invalid_args": "Usage: {prefix}bsr (room code/link) (previous) (next)", + "invalid_code": "Invalid room code!", + "at_least_one": "At least one argument (previous or next) must be greater than 0!", + "prev_block": "Previous:\n
{prev_list}
", + "next_block": "Next:\n
{next_list}
", + "btn_target": "Target Room", + "invalid_args_ru": "Использование: {prefix}bsr (код комнаты/ссылка) (предыдущие) (следующие)", + "invalid_code_ru": "Неверный код комнаты!", + "at_least_one_ru": "Хотя бы один аргумент (предыдущие или следующие) должен быть больше 0!", + "prev_block_ru": "Предыдущие:\n
{prev_list}
", + "next_block_ru": "Следующие:\n
{next_list}
", + "btn_target_ru": "Целевая комната", + "invalid_args_ua": "Використання: {prefix}bsr (код кімнати/посилання) (попередні) (наступні)", + "invalid_code_ua": "Невірний код кімнати!", + "at_least_one_ua": "Хоча б один аргумент (попередні або наступні) повинен бути більшим за 0!", + "prev_block_ua": "Попередні:\n
{prev_list}
", + "next_block_ua": "Наступні:\n
{next_list}
", + "btn_target_ua": "Цільова кімната", + "invalid_args_kz": "Қолдану: {prefix}bsr (бөлме коды/сілтеме) (алдыңғы) (келесі)", + "invalid_code_kz": "Қате бөлме коды!", + "at_least_one_kz": "Кем дегенде бір аргумент (алдыңғы немесе келесі) 0-ден үлкен болуы керек!", + "prev_block_kz": "Алдыңғы:\n
{prev_list}
", + "next_block_kz": "Келесі:\n
{next_list}
", + "btn_target_kz": "Мақсатты бөлме", + "invalid_args_uz": "Qo'llanilishi: {prefix}bsr (xona kodi/havolasi) (oldingi) (keyingi)", + "invalid_code_uz": "Noto'g'ri xona kodi!", + "at_least_one_uz": "Kamida bitta argument (oldingi yoki keyingi) 0 dan katta bo'lishi kerak!", + "prev_block_uz": "Oldingi:\n
{prev_list}
", + "next_block_uz": "Keyingi:\n
{next_list}
", + "btn_target_uz": "Maqsadli xona", + "invalid_args_fr": "Utilisation: {prefix}bsr (code/lien) (précédents) (suivants)", + "invalid_code_fr": "Code de salle invalide!", + "at_least_one_fr": "Au moins un argument doit être supérieur à 0 !", + "prev_block_fr": "Précédents:\n
{prev_list}
", + "next_block_fr": "Suivants:\n
{next_list}
", + "btn_target_fr": "Salle cible", + "invalid_args_de": "Verwendung: {prefix}bsr (Raumcode/Link) (vorherige) (nächste)", + "invalid_code_de": "Ungültiger Raumcode!", + "at_least_one_de": "Mindestens ein argument muss größer als 0 sein!", + "prev_block_de": "Vorherige:\n
{prev_list}
", + "next_block_de": "Nächste:\n
{next_list}
", + "btn_target_de": "Zielraum", + "invalid_args_jp": "使用法: {prefix}bsr (コード/リンク) (前) (次)", + "invalid_code_jp": "無効なルームコード!", + "at_least_one_jp": "少なくとも1つの引数は0より大きくなければなりません!", + "prev_block_jp": "前:\n
{prev_list}
", + "next_block_jp": "次:\n
{next_list}
", + "btn_target_jp": "ターゲットルーム" + }, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, + "Fixyres/FModules/SCD.py": { + "name": "SCD", + "description": "Module for downloading songs from SoundCloud.", + "cls_doc": { + "default": "Module for downloading songs from SoundCloud.", + "ru": "Модуль для скачивания песен с SoundCloud.", + "ua": "Модуль для завантаження пісень із SoundCloud.", + "de": "Modul zum Herunterladen von Liedern von SoundCloud.", + "uz": "SoundCloud-dan qo'shiqlarni yuklab olish uchun modul.", + "kz": "SoundCloud-тан әндерді жүктеп алуға арналған модуль.", + "fr": "Module pour télécharger des chansons depuis SoundCloud.", + "jp": "SoundCloudから曲をダウンロードするためのモジュール。" + }, + "meta": { + "pic": null, + "banner": "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/SCD/banner.png", + "developer": "@FModules" + }, + "commands": [ + { + "scd": "(link) - download a song from SoundCloud. | (RU) (ссылка) - скачать песню с SoundCloud. | (UA) (посилання) - завантажити пісню з SoundCloud. | (DE) (Link) - laden Sie ein Lied von SoundCloud herunter. | (UZ) (havola) - SoundCloud-dan qo'shiq yuklab olish. | (KZ) (сілтеме) - SoundCloud-тан әнді жүктеп алу. | (FR) (lien) - télécharger une chanson depuis SoundCloud. | (JP) (リンク) - SoundCloudから曲をダウンロードします。" + } + ], + "new_commands": [ + { + "name": "scd", + "original_name": "scd", + "description": { + "default": "(link) - download a song from SoundCloud.", + "ru": "(ссылка) - скачать песню с SoundCloud.", + "ua": "(посилання) - завантажити пісню з SoundCloud.", + "de": "(Link) - laden Sie ein Lied von SoundCloud herunter.", + "uz": "(havola) - SoundCloud-dan qo'shiq yuklab olish.", + "kz": "(сілтеме) - SoundCloud-тан әнді жүктеп алу.", + "fr": "(lien) - télécharger une chanson depuis SoundCloud.", + "jp": "(リンク) - SoundCloudから曲をダウンロードします。" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + } + ], + "inline_handlers": [], + "strings": { + "name": "SCD", + "_cls_doc": "Module for downloading songs from SoundCloud.", + "no_args": "✘ You didn't provide a link to the song, example of using the command: {prefix}sc (link)", + "downloading": "↓ Downloading...", + "not_found": "✘ Song not found.", + "no_args_ru": "✘ Вы не указали ссылку на песню, пример использования команды: {prefix}sc (ссылка)", + "downloading_ru": "↓ Скачивание...", + "not_found_ru": "✘ Песня не найдена.", + "no_args_ua": "✘ Ви не вказали посилання на пісню, приклад використання команди: {prefix}sc (посилання)", + "downloading_ua": "↓ Завантаження...", + "not_found_ua": "✘ Пісню не знайдено.", + "no_args_de": "✘ Sie haben keinen Link zum Lied angegeben, Anwendungsbeispiel des Befehls: {prefix}sc (Link)", + "downloading_de": "↓ Wird heruntergeladen...", + "not_found_de": "✘ Lied nicht gefunden.", + "no_args_uz": "✘ Siz qo'shiq havolasini kiritmadingiz, buyruqdan foydalanish misoli: {prefix}sc (havola)", + "downloading_uz": "↓ Yuklab olinmoqda...", + "not_found_uz": "✘ Qo'shiq topilmadi.", + "no_args_kz": "✘ Сіз әнге сілтеме көрсетпедіңіз, бұйрықты пайдалану мысалы: {prefix}sc (сілтеме)", + "downloading_kz": "↓ Жүктелуде...", + "not_found_kz": "✘ Ән табылмады.", + "no_args_fr": "✘ Vous n'avez pas fourni de lien vers la chanson, exemple d'utilisation de la commande: {prefix}sc (lien)", + "downloading_fr": "↓ Téléchargement...", + "not_found_fr": "✘ Chanson non trouvée.", + "no_args_jp": "✘ 曲へのリンクが指定されていません。コマンドの使用例: {prefix}sc (リンク)", + "downloading_jp": "↓ ダウンロード中...", + "not_found_jp": "✘ 曲が見つかりません。" + }, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, + "Fixyres/FModules/akinator.py": { + "name": "Akinator", + "description": "Akinator will guess any character you have in mind, you just need to answer a couple of questions.", + "cls_doc": { + "ru": "Акинатор угадает любого вами загаданного персонажа.", + "ua": "Акінатор вгадає будь-якого персонажа.", + "de": "Akinator errät jeden Charakter, den du dir vorstellst.", + "fr": "Akinator devinera n'importe quel personnage.", + "jp": "アキネーターはあなたが考えているキャラクターを当てます。", + "uz": "Akinator siz o'ylagan har qanday qahramonni topadi.", + "kz": "Акинатор сіз ойлаған кез келген кейіпкерді табады." + }, + "meta": { + "pic": null, + "banner": "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/banner.png", + "developer": "@FModules", + "fhsdesc": "game, funny, guess, question game" + }, + "commands": [ + { + "akinator": "- start the game. | (RU) - начать игру. | (UA) - почати гру. | (DE) - Spiel starten. | (FR) - commencer le jeu. | (JP) - ゲームを開始します。 | (UZ) - o'yinni boshlash. | (KZ) - ойынды бастау." + } + ], + "new_commands": [ + { + "name": "akinator", + "original_name": "akinator", + "description": { + "default": "- start the game.", + "ru": "- начать игру.", + "ua": "- почати гру.", + "de": "- Spiel starten.", + "fr": "- commencer le jeu.", + "jp": "- ゲームを開始します。", + "uz": "- o'yinni boshlash.", + "kz": "- ойынды бастау." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + } + ], + "inline_handlers": [], + "strings": { + "name": "Akinator", + "lang": "en", + "child_mode": "Child mode. If enabled, it will be easier to guess 18+ heroes.", + "start": "Start", + "text": "Guess any character you have in mind, and click on the Start button.", + "yes": "Yes", + "no": "No", + "idk": "I don't know", + "probably": "Probably", + "probably_not": "Probably not", + "this_is": "This is {name}\n{description}", + "this_is_no_desc": "This is {name}", + "not_right": "Not right", + "failed": "Failed to guess the character.", + "lang_ru": "ru", + "child_mode_ru": "Детский режим. Сложнее отгадать 18+ героев.", + "start_ru": "Начать", + "text_ru": "Задумайте персонажа, и нажмите начать.", + "yes_ru": "Да", + "no_ru": "Нет", + "idk_ru": "Не знаю", + "probably_ru": "Возможно", + "probably_not_ru": "Скорее нет", + "this_is_ru": "Это {name}\n{description}", + "this_is_no_desc_ru": "Это {name}", + "not_right_ru": "Это не он", + "failed_ru": "Не удалось угадать персонажа.", + "lang_ua": "uk", + "child_mode_ua": "Дитячий режим. Складніше відгадати 18+ героїв.", + "start_ua": "Почати", + "text_ua": "Загадайте персонажа, і натисніть почати.", + "yes_ua": "Так", + "no_ua": "Ні", + "idk_ua": "Не знаю", + "probably_ua": "Можливо", + "probably_not_ua": "Швидше ні", + "this_is_ua": "Це {name}\n{description}", + "this_is_no_desc_ua": "Це {name}", + "not_right_ua": "Це не він", + "failed_ua": "Не вдалося вгадати персонажа.", + "lang_de": "de", + "child_mode_de": "Kindermodus. Wenn aktiviert, wird es schwieriger sein, 18+ Helden zu erraten.", + "start_de": "Start", + "text_de": "Denk dir einen Charakter aus und klicke auf Start.", + "yes_de": "Ja", + "no_de": "Nein", + "idk_de": "Ich weiß nicht", + "probably_de": "Wahrscheinlich", + "probably_not_de": "Wahrscheinlich nicht", + "this_is_de": "Das ist {name}\n{description}", + "this_is_no_desc_de": "Das ist {name}", + "not_right_de": "Das ist er nicht", + "failed_de": "Charakter konnte nicht erraten werden.", + "lang_fr": "fr", + "child_mode_fr": "Mode enfant. Héros 18+ plus difficiles à deviner.", + "start_fr": "Commencer", + "text_fr": "Pensez à un personnage et cliquez sur Commencer.", + "yes_fr": "Oui", + "no_fr": "Non", + "idk_fr": "Je ne sais pas", + "probably_fr": "Probablement", + "probably_not_fr": "Probablement pas", + "this_is_fr": "C'est {name}\n{description}", + "this_is_no_desc_fr": "C'est {name}", + "not_right_fr": "Ce n'est pas lui", + "failed_fr": "Impossible de deviner.", + "lang_jp": "ja", + "child_mode_jp": "子供モード。有効にすると、18歳以上のキャラクターを推測するのが難しくなります。", + "start_jp": "開始", + "text_jp": "キャラクターを思い浮かべて開始。", + "yes_jp": "はい", + "no_jp": "いいえ", + "idk_jp": "わかりません", + "probably_jp": "おそらく", + "probably_not_jp": "おそらく違う", + "this_is_jp": "これは {name}\n{description}", + "this_is_no_desc_jp": "これは {name}", + "not_right_jp": "違います", + "failed_jp": "推測できませんでした。", + "lang_uz": "uz", + "child_mode_uz": "Bolalar rejimi. Yoqilgan bo'lsa, 18+ qahramonlarni topish qiyinroq bo'ladi.", + "start_uz": "Boshlash", + "text_uz": "Qahramonni o'ylang va Boshlash tugmasini bosing.", + "yes_uz": "Ha", + "no_uz": "Yo'q", + "idk_uz": "Bilmayman", + "probably_uz": "Ehtimol", + "probably_not_uz": "Ehtimol yo'q", + "this_is_uz": "Bu {name}\n{description}", + "this_is_no_desc_uz": "Bu {name}", + "not_right_uz": "Bu u emas", + "failed_uz": "Qahramonni topib bo'lmadi.", + "lang_kz": "kk", + "child_mode_kz": "Балалар режимі. Қосылған болса, 18+ кейіпкерлерді табу қиынырақ болады.", + "start_kz": "Бастау", + "text_kz": "Кейіпкерді ойлаңыз және Бастау түймесін басыңыз.", + "yes_kz": "Иә", + "no_kz": "Жоқ", + "idk_kz": "Білмеймін", + "probably_kz": "Мүмкін", + "probably_not_kz": "Мүмкін емес", + "this_is_kz": "Бұл {name}\n{description}", + "this_is_no_desc_kz": "Бұл {name}", + "not_right_kz": "Бұл ол емес", + "failed_kz": "Кейіпкерді таба алмадық." + }, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, "ZetGoHack/nullmod/Gradientor.py": { "name": "Gradientor", "description": "", @@ -11781,6 +11790,329 @@ "has_on_unload": false, "class_cmd_names": {} }, + "SenkoGuardian/SenModules/Gemini.py": { + "name": "Gemini", + "description": "Модуль для работы с Google Gemini AI. (Поддержка видео/фото/аудио", + "cls_doc": {}, + "meta": { + "pic": "https://raw.githubusercontent.com/SenkoGuardian/SenkoGuardian.github.io/main/OfficialSenkoGuardianBanner.png", + "banner": "https://raw.githubusercontent.com/SenkoGuardian/SenkoGuardian.github.io/main/OfficialSenkoGuardianBanner.png", + "developer": "@SenkoGuardianModules" + }, + "commands": [ + { + "g": "[текст или reply] — спросить у Gemini. Может анализировать ссылки." + }, + { + "gimg": "<промпт> [реплай на фото] — Генерация/Редактирование изображений через Gemini." + }, + { + "gskey": "[-h] — Сканировать ключи. -h: показать статус из кеша без проверки." + }, + { + "gch": "<[id чата]> <кол-во> <вопрос> - Проанализировать историю чата." + }, + { + "gprompt": "<текст/-c/ответ на файл> — Установить промпт." + }, + { + "gauto": " — Вкл/выкл авто-ответ в чате." + }, + { + "gautochats": "— Показать чаты с активным режимом авто-ответа." + }, + { + "gclear": "[auto] — очистить память в чате. auto для памяти gauto." + }, + { + "gpresets": " — Управление пресетами (профилями)." + }, + { + "gmemdel": "[N] — удалить последние N пар сообщений из памяти." + }, + { + "gmemchats": "— Показать список чатов с активной памятью (имя и ID)." + }, + { + "gmemexport": "[] [auto] [-s] — \n[из id/@юза чата] экспорт. -s в избранное." + }, + { + "gmemimport": "[auto] — импорт истории из файла (ответом). auto для gauto." + }, + { + "gmemfind": "[слово] — Поиск в памяти текущего чата по ключевому слову или фразе." + }, + { + "gmemoff": "— Отключить память в этом чате" + }, + { + "gmemon": "— Включить память в этом чате" + }, + { + "gmemshow": "[auto] — Показать память чата (до 20 последних запросов). auto для gauto." + }, + { + "gmodel": "[model] [-s] — Узнать/сменить модель. -s — список. Авто-проверка совместимости." + }, + { + "gres": "[auto] — Очистить ВСЮ память. auto для всей памяти gauto." + } + ], + "new_commands": [ + { + "name": "g", + "original_name": "g", + "description": { + "default": "[текст или reply] — спросить у Gemini. Может анализировать ссылки." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gimg", + "original_name": "gimg", + "description": { + "default": "<промпт> [реплай на фото] — Генерация/Редактирование изображений через Gemini." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gskey", + "original_name": "gskey", + "description": { + "default": "[-h] — Сканировать ключи. -h: показать статус из кеша без проверки." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gch", + "original_name": "gch", + "description": { + "default": "<[id чата]> <кол-во> <вопрос> - Проанализировать историю чата." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gprompt", + "original_name": "gprompt", + "description": { + "default": "<текст/-c/ответ на файл> — Установить промпт." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gauto", + "original_name": "gauto", + "description": { + "default": " — Вкл/выкл авто-ответ в чате." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gautochats", + "original_name": "gautochats", + "description": { + "default": "— Показать чаты с активным режимом авто-ответа." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gclear", + "original_name": "gclear", + "description": { + "default": "[auto] — очистить память в чате. auto для памяти gauto." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gpresets", + "original_name": "gpresets", + "description": { + "default": " — Управление пресетами (профилями)." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gmemdel", + "original_name": "gmemdel", + "description": { + "default": "[N] — удалить последние N пар сообщений из памяти." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gmemchats", + "original_name": "gmemchats", + "description": { + "default": "— Показать список чатов с активной памятью (имя и ID)." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gmemexport", + "original_name": "gmemexport", + "description": { + "default": "[] [auto] [-s] — \n[из id/@юза чата] экспорт. -s в избранное." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gmemimport", + "original_name": "gmemimport", + "description": { + "default": "[auto] — импорт истории из файла (ответом). auto для gauto." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gmemfind", + "original_name": "gmemfind", + "description": { + "default": "[слово] — Поиск в памяти текущего чата по ключевому слову или фразе." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gmemoff", + "original_name": "gmemoff", + "description": { + "default": "— Отключить память в этом чате" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gmemon", + "original_name": "gmemon", + "description": { + "default": "— Включить память в этом чате" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gmemshow", + "original_name": "gmemshow", + "description": { + "default": "[auto] — Показать память чата (до 20 последних запросов). auto для gauto." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gmodel", + "original_name": "gmodel", + "description": { + "default": "[model] [-s] — Узнать/сменить модель. -s — список. Авто-проверка совместимости." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "gres", + "original_name": "gres", + "description": { + "default": "[auto] — Очистить ВСЮ память. auto для всей памяти gauto." + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + } + ], + "inline_handlers": [], + "strings": {}, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, "SenkoGuardian/SenModules/NekoEditorMod.py": { "name": "NekoEditorMod", "description": "Neko-редактор сообщений | Владелецы: @SstAngelStar × @ilovesenko ", @@ -11964,6 +12296,693 @@ "has_on_unload": true, "class_cmd_names": {} }, + "archquise/q.mods/timezone.py": { + "name": "TimeZoneMod", + "description": "Prints current time in selected timezone (UTC+n and tzdata formats supported).", + "cls_doc": { + "default": "Prints current time in selected timezone (UTC+n and tzdata formats supported)", + "ru": "Выводит текущее время в выбранном часовом поясе (поддерживаются форматы UTC+n и tzdata)" + }, + "meta": { + "pic": null, + "banner": "https://raw.githubusercontent.com/archquise/qmods_meta/main/timezone.png", + "developer": "@quise_m" + }, + "commands": [ + { + "utc": "(RU) Выводит время по UTC+n | Использование: .utc 4 | (EN) Prints UTC+n time | Usage: .utc 4" + }, + { + "tzdata": "(RU) Выводит время по часовому поясу tzdata | Использование: .tzdata Europe/Moscow | (EN) Prints time by tzdata timezone | Usage: .tzdata Europe/Moscow" + } + ], + "new_commands": [ + { + "name": "utc", + "original_name": "utccmd", + "description": { + "default": "", + "ru": "Выводит время по UTC+n | Использование: .utc 4", + "en": "Prints UTC+n time | Usage: .utc 4" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "tzdata", + "original_name": "tzdatacmd", + "description": { + "default": "", + "ru": "Выводит время по часовому поясу tzdata | Использование: .tzdata Europe/Moscow", + "en": "Prints time by tzdata timezone | Usage: .tzdata Europe/Moscow" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + } + ], + "inline_handlers": [], + "strings": { + "name": "TimeZone", + "invalid_args": " There is no arguments or they are invalid", + "_cls_doc": "Prints current time in selected timezone (UTC+n and tzdata formats supported)", + "time_utc": "🕓 Current time by UTC+{}: {}", + "time_tzdata": "🕓 Current time in {}: {}", + "invalid_args_ru": " Нет аргументов или они неверны", + "tzdata_error_ru": " Произошла ошибка при получении времени по tzdata: {}\n\nУбедитесь, что часовой пояс указан верно", + "time_utc_ru": "🕓 Текущее время по UTC+{}: {}", + "time_tzdata_ru": "🕓 Текущее время в {}: {}" + }, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, + "archquise/q.mods/IrisSimpleMod.py": { + "name": "IrisSimpleMod", + "description": "Module for basic interaction with Iris bot.", + "cls_doc": { + "ru": "Модуль для базового взаимодействия с Ирисом!" + }, + "meta": { + "pic": null, + "banner": "https://raw.githubusercontent.com/archquise/qmods_meta/main/IrisSimpleMod.png", + "developer": "@quise_m" + }, + "commands": [ + { + "bag": "Check bag. | (RU) Проверить мешок | (EN) Check bag" + }, + { + "farm": "Farm iris-coins. | (RU) Зафармить ирис-коины | (EN) Farm iris-coins" + }, + { + "irisstats": "Display user stats. | (RU) Вывести анкету | (EN) Display user stats" + } + ], + "new_commands": [ + { + "name": "bag", + "original_name": "bag", + "description": { + "default": "Check bag.", + "ru": "Проверить мешок", + "en": "Check bag" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "farm", + "original_name": "farm", + "description": { + "default": "Farm iris-coins.", + "ru": "Зафармить ирис-коины", + "en": "Farm iris-coins" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "irisstats", + "original_name": "irisstats", + "description": { + "default": "Display user stats.", + "ru": "Вывести анкету", + "en": "Display user stats" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + } + ], + "inline_handlers": [], + "strings": { + "name": "IrisSimpleMod", + "checking_bag": "🌎 Checking bag...", + "bag_result": " Your bag: {}", + "farming": "🌎 Farming iris-coins...", + "farm_result": " Farm result: {}", + "getting_stats": "🌎 Getting user stats...", + "stats_result": " User stats: {}", + "bot_stats": "🌎 Getting bot stats...", + "bot_stats_result": " Bot stats: {}", + "error_no_response": " No response from bot. Please try again.", + "error_timeout": " Request timeout. Please try again.", + "error_general": " An error occurred: {error}", + "checking_bag_ru": "🌎 Проверка мешка...", + "bag_result_ru": " Ваш мешок: {}", + "farming_ru": "🌎 Фарм ирис-коинов...", + "farm_result_ru": " Результат фарма: {}", + "getting_stats_ru": "🌎 Получение статистики пользователя...", + "stats_result_ru": " Статистика пользователя: {}", + "bot_stats_ru": "🌎 Получение статистики ботов...", + "bot_stats_result_ru": " Статистика ботов: {}", + "error_no_response_ru": " Нет ответа от бота. Попробуйте еще раз.", + "error_timeout_ru": " Таймаут запроса. Попробуйте еще раз.", + "error_general_ru": " Произошла ошибка: {error}" + }, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, + "archquise/q.mods/FolderAutoRead.py": { + "name": "FolderAutoReadMod", + "description": "Automatically reads chats in selected folders.", + "cls_doc": { + "default": "Automatically reads chats in selected folders every 60 seconds!", + "ru": "Автоматически читает чаты в выбранных папках каждые 60 секунд!" + }, + "meta": { + "pic": null, + "banner": "https://raw.githubusercontent.com/archquise/qmods_meta/main/FolderAutoRead.png", + "developer": "@quise_m" + }, + "commands": [ + { + "addfolder": "Adds folder to the tracking list by it's name. Usage: .addfolder FolderName | (R) Добавляет папки в список отслеживания по их названию. Использование: .addfolder НазваниеПапки | (RU) Добавляет папки в список отслеживания по их названию. Использование: .addfolder НазваниеПапки" + }, + { + "delfolder": "Deletes folder from the tracking list | (R) Удаляет папку из списка для отслежнивания | (RU) Удаляет папку из списка для отслежнивания" + }, + { + "listfolders": "Prints list of tracked folders | (R) Выводит список отслеживаемых папок | (RU) Выводит список отслеживаемых папок" + } + ], + "new_commands": [ + { + "name": "addfolder", + "original_name": "addfolder", + "description": { + "default": "Adds folder to the tracking list by it's name. Usage: .addfolder FolderName", + "r": "Добавляет папки в список отслеживания по их названию. Использование: .addfolder НазваниеПапки", + "ru": "Добавляет папки в список отслеживания по их названию. Использование: .addfolder НазваниеПапки" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "delfolder", + "original_name": "delfolder", + "description": { + "default": "Deletes folder from the tracking list", + "r": "Удаляет папку из списка для отслежнивания", + "ru": "Удаляет папку из списка для отслежнивания" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "listfolders", + "original_name": "listfolders", + "description": { + "default": "Prints list of tracked folders", + "r": "Выводит список отслеживаемых папок", + "ru": "Выводит список отслеживаемых папок" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + } + ], + "inline_handlers": [], + "strings": { + "name": "FolderAutoRead", + "not_exists_or_already_added": "🚫 This folder does not exists or it is already added for tracking!", + "_cls_doc": "Automatically reads chats in selected folders every 60 seconds!", + "_cmd_doc_addfolder": "Adds folder to the tracking list by it's name. Usage: .addfolder FolderName", + "_cmd_doc_listfolders": "Prints list of tracked folders", + "_cmd_doc_delfolder": "Deletes folder from the tracking list", + "wrong_args": "🚫 Wrong arguments! Usage: .addfolder/delfolder FolderName\n\nTip: If you trying to delete the folder from the tracking list, double-check that it really still tracking using .listfolders", + "listfolders": "📁 List of tracked folders:\n", + "delfolder": "🗑 Folder is successfully deleted from the tracking list!", + "addfolder": "📁 Folder is successfully added to the tracking list!", + "not_exists_or_already_added_ru": "🚫 Такой папки не существует, или она уже добавлена для отслеживания!", + "_cmd_doc_ru_addfolder": "Добавляет папки в список отслеживания по их названию. Использование: .addfolder НазваниеПапки", + "_cmd_doc_addfolder_ru": "Добавляет папки в список отслеживания по их названию. Использование: .addfolder НазваниеПапки", + "_cmd_doc_ru_listfolders": "Выводит список отслеживаемых папок", + "_cmd_doc_listfolders_ru": "Выводит список отслеживаемых папок", + "_cmd_doc_ru_delfolder": "Удаляет папку из списка для отслежнивания", + "_cmd_doc_delfolder_ru": "Удаляет папку из списка для отслежнивания", + "wrong_args_ru": "🚫 Неверные аргументы! Использование: .addfolder/delfolder НазваниеПапки\n\nСовет: Если вы пытаетесь удалить папку из списка отслеживания, проверьте, что она вообще отслеживается, используя .listfolders", + "listfolders_ru": "📁 Список отслеживаемых папок:\n", + "delfolder_ru": "🗑 Папка успешно удалена из листа отслеживания!", + "addfolder_ru": "📁 Папка успешно добавлена в лист отслеживания!" + }, + "has_on_load": false, + "has_on_unload": true, + "class_cmd_names": {} + }, + "archquise/q.mods/face.py": { + "name": "FaceMod", + "description": "Gives you a random kaomoji.", + "cls_doc": { + "ru": "Выдает случайное каомодзи (японские эмодзи)" + }, + "meta": { + "pic": null, + "banner": "https://raw.githubusercontent.com/archquise/qmods_meta/main/face.png", + "developer": "@quise_m" + }, + "commands": [ + { + "rface": "(RU) Случайное каомодзи | (EN) Random kaomoji" + } + ], + "new_commands": [ + { + "name": "rface", + "original_name": "rfacecmd", + "description": { + "default": "", + "ru": "Случайное каомодзи", + "en": "Random kaomoji" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + } + ], + "inline_handlers": [], + "strings": { + "name": "Face", + "loading": "🔍 I'm looking for you kaomoji", + "random_face": "🗿 Here is your random one kaomoji\n{}", + "error": "An error has occurred!", + "loading_ru": "🔍 Ищу вам kaomoji", + "random_face_ru": "🗿 Вот ваш рандомный kaomoji\n{}", + "error_ru": "Произошла ошибка!" + }, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, + "archquise/q.mods/AniLiberty.py": { + "name": "AniLibertyMod", + "description": "Ищет и возвращает случайное аниме из базы Aniliberty.", + "cls_doc": { + "ru": "Ищет и отправляет случайное аниме из базы AniLiberty" + }, + "meta": { + "pic": null, + "banner": "https://raw.githubusercontent.com/archquise/qmods_meta/main/AniLiberty.png", + "developer": "@quise_m" + }, + "commands": [ + { + "arandom": "(RU) Возвращает случайный релиз из базы | (EN) Returns a random release from the database" + }, + { + "asearchinlinehandler": "" + } + ], + "new_commands": [ + { + "name": "arandom", + "original_name": "arandom", + "description": { + "default": "", + "ru": "Возвращает случайный релиз из базы", + "en": "Returns a random release from the database" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "asearchinlinehandler", + "original_name": "asearch_inline_handler", + "description": { + "default": "" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": true, + "is_inline_handler": true, + "decorators": [] + } + ], + "inline_handlers": [ + { + "name": "asearchinlinehandler", + "description": { + "default": "" + }, + "decorators": [] + } + ], + "strings": { + "name": "AniLiberty", + "announce": "The announcement:", + "ongoing": "Ongoing:", + "type": "Type:", + "genres": "Genres:", + "favorite": "Favourites <3:", + "announce_ru": "Анонс:", + "ongoing_ru": "Онгоинг:", + "type_ru": "Тип:", + "genres_ru": "Жанры:", + "favorite_ru": "Избранное <3:" + }, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, + "archquise/q.mods/WindowsKeys.py": { + "name": "WindowsKeysMod", + "description": "Windows KMS activation keys.", + "cls_doc": { + "ru": "KMS ключи активации Windows" + }, + "meta": { + "pic": null, + "banner": "https://raw.githubusercontent.com/archquise/qmods_meta/main/WindowsKeys.png", + "developer": "@quise_m" + }, + "commands": [ + { + "winkey": "(RU) Меню ключей Windows | (EN) Windows keys menu" + } + ], + "new_commands": [ + { + "name": "winkey", + "original_name": "winkey", + "description": { + "default": "", + "ru": "Меню ключей Windows", + "en": "Windows keys menu" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + } + ], + "inline_handlers": [], + "strings": { + "name": "WindowsKeys", + "winkey": " Key: {}\n\n❗️ For KMS activation only", + "error": " Failed to get key", + "select": "🔑 Select version:", + "close": "❌ Close", + "back": "← Back", + "loading": "✍️ Loading...", + "winkey_ru": " Ключ: {}\n\n❗️ Только для KMS активации", + "error_ru": " Ошибка получения", + "select_ru": "🔑 Выберите версию:", + "close_ru": "❌ Закрыть", + "back_ru": "← Назад", + "loading_ru": "✍️ Загрузка..." + }, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, + "archquise/q.mods/ytdl.py": { + "name": "YTDLMod", + "description": "Downloads and sends audio/video from YouTube.", + "cls_doc": { + "default": "Downloads and sends audio/video from YouTube", + "ru": "Скачивает и отправляет аудио/видео с Ютуба" + }, + "meta": { + "pic": null, + "banner": "https://raw.githubusercontent.com/archquise/qmods_meta/main/ytdl.png", + "developer": "@quise_m" + }, + "commands": [ + { + "ytdlv": "(EN) Download video | (RU) Скачать видео" + }, + { + "ytdla": "(EN) Download audio | (RU) Скачать аудио" + } + ], + "new_commands": [ + { + "name": "ytdlv", + "original_name": "ytdlvcmd", + "description": { + "default": "", + "en": "Download video", + "ru": "Скачать видео" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "ytdla", + "original_name": "ytdlacmd", + "description": { + "default": "", + "en": "Download audio", + "ru": "Скачать аудио" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + } + ], + "inline_handlers": [], + "strings": { + "name": "YTDL", + "_cls_doc": "Downloads and sends audio/video from YouTube", + "invalid_args": " There is no arguments or they are invalid", + "downloading": "🕐 Downloading...", + "done": " Done!", + "cookie_desc": "Cookie account (helps downloading video with strict age rating restricrions)", + "deno_err": "❗️ Error! The Deno JavaScript engine was not install automatically.\nThis is a required dependency for yt-dlp (a library for downloading video/audio) to work correctly.\n\nTo continue, you need to install the engine manually, or resolve any issues preventing automatic installation and restart the userbot.", + "err": "❗️ Error!\n\nAdditional info: {}", + "invalid_args_ru": " Нет аргументов или они неверны", + "downloading_ru": "🕐 Скачиваю...", + "done_ru": " Готово!", + "cookie_desc_ru": "Куки аккаунта (помогает скачивать видео с жесткими возрастными ограничениями)", + "deno_err_ru": "❗️ Ошибка! JS-движок Deno не установился автоматически.\nЭто необходимая зависимость для корректной работы yt-dlp (библиотека для скачивания видео/аудио).\n\nДля продолжения вам необходимо установить движок вручную, или устранить препятствия для автоматической установки и перезагрузить юзербота.", + "err_ru": "❗️ Ошибка!\n\nДоп.информация: {}" + }, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, + "archquise/q.mods/TempChat.py": { + "name": "TempChatMod", + "description": "Creates a temporary private chat with a message forwarding restriction and adds the specified user to it.", + "cls_doc": { + "ru": "Создает временный приватный чат с запретом на пересылку и добавляет туда выбранного человека" + }, + "meta": { + "pic": null, + "banner": "https://raw.githubusercontent.com/archquise/qmods_meta/main/TempChat.png", + "developer": "@quise_m" + }, + "commands": [ + { + "tmpchat": "Create temporary chat. Usage: .tmpchat [@user/reply] [time] | (RU) Создает временный чат. Использование: .tmpchat [@user/reply] [time]" + } + ], + "new_commands": [ + { + "name": "tmpchat", + "original_name": "tmpchat", + "description": { + "default": "Create temporary chat. Usage: .tmpchat [@user/reply] [time]", + "ru": "Создает временный чат. Использование: .tmpchat [@user/reply] [time]" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + } + ], + "inline_handlers": [], + "strings": { + "name": "TempChat", + "selfchat": "You can't create a chat with yourself.", + "wrongargs": " Wrong arguments. Use .tmpchat [@user/reply] [time]", + "alreadychatting": " You already have an active conversation with this person.", + "invalidtime": " Invalid time format. Use combinations like 1h30m.", + "invitemsg": "🛡 You've been invited to a temporary private chat!\n\n⌛️ Auto-deletes in ", + "joinlink": "🔗 Join link: ", + "chatcreated": " The temporary chat has been successfully created!", + "selfchat_ru": "Ты не можешь создать чат сам с собой.", + "wrongargs_ru": " Неверные аргументы. Используй .tmpchat [@user/reply] [время]", + "alreadychatting_ru": " У вас уже есть открытая переписка с этим человеком.", + "invalidtime_ru": " Неверный формат времени. Убедитесь, что вы вводите время в формате 1h, 2h30m.", + "invitemsg_ru": "🛡 Вы были приглашены во временный приватный чат!\n\n⌛️ Авто-удаление через ", + "joinlink_ru": "🔗 Ссылка: ", + "chatcreated_ru": " Временный чат успешно создан!" + }, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, + "archquise/q.mods/shortener.py": { + "name": "ShortenerMod", + "description": "Module for using bit.ly API.", + "cls_doc": { + "default": "Module for using bit.ly API", + "ru": "Модуль для использования API bit.ly" + }, + "meta": { + "pic": null, + "banner": "https://raw.githubusercontent.com/archquise/qmods_meta/main/Shortener.png", + "developer": "@quise_m" + }, + "commands": [ + { + "shorten": "Shorten URL using bit.ly API. | (RU) Сократить ссылку через bit.ly (ссылка с https://) | (EN) Shorten the link via bit.ly (url with https://)" + }, + { + "statcl": "Get click statistics for shortened URL. | (RU) Посмотреть статистику ссылки через bit.ly (ссылка без https:// | Доступно только на платных аккаунтах) | (EN) View link statistics via bit.ly (link without https:// | Works only on paid accounts)" + } + ], + "new_commands": [ + { + "name": "shorten", + "original_name": "shortencmd", + "description": { + "default": "Shorten URL using bit.ly API.", + "ru": "Сократить ссылку через bit.ly (ссылка с https://)", + "en": "Shorten the link via bit.ly (url with https://)" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "statcl", + "original_name": "statclcmd", + "description": { + "default": "Get click statistics for shortened URL.", + "ru": "Посмотреть статистику ссылки через bit.ly (ссылка без https:// | Доступно только на платных аккаунтах)", + "en": "View link statistics via bit.ly (link without https:// | Works only on paid accounts)" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + } + ], + "inline_handlers": [], + "strings": { + "name": "Shortener", + "no_api": " You have not specified an API token from the site bit.ly", + "statclcmd": "📊 Statistics on clicks for this link: {c}", + "shortencmd": " Your shortened link is ready: {c}", + "no_args": " Please provide a URL to shorten.", + "invalid_url": " Invalid URL format.", + "api_error": " API error: {error}", + "_cls_doc": "Module for using bit.ly API", + "no_api_ru": " Вы не указали api токен с сайта bit.ly", + "statclcmd_ru": "📊 Статистика о переходе по этой ссылке: {c}", + "shortencmd_ru": " Ваша сокращённая ссылка готова: {c}", + "no_args_ru": " Пожалуйста, укажите URL для сокращения.", + "invalid_url_ru": " Неверный формат URL.", + "api_error_ru": " Ошибка API: {error}" + }, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, + "archquise/q.mods/CodeShare.py": { + "name": "CodeShareMod", + "description": "Uploads your code at the kmi.aeza.net (Pastebin and GitHub Gist alternative).", + "cls_doc": { + "default": "Uploads your code at the kmi.aeza.net (Pastebin and GitHub Gist alternative)", + "ru": "Загружает ваш код на kmi.aeza.net (альтернатива Pastebin и GitHub Gist)" + }, + "meta": { + "pic": null, + "banner": "https://raw.githubusercontent.com/archquise/qmods_meta/main/CodeShare.png", + "developer": "@quise_m" + }, + "commands": [ + { + "codeshare": "(RU) Загрузка кода на сайт | (EN) Upload code to the site" + } + ], + "new_commands": [ + { + "name": "codeshare", + "original_name": "codesharecmd", + "description": { + "default": "", + "ru": "Загрузка кода на сайт", + "en": "Upload code to the site" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + } + ], + "inline_handlers": [], + "strings": { + "name": "CodeShare", + "invalid_args": " There is no arguments or reply with a file, or they are invalid", + "_cls_doc": "Uploads your code at the kmi.aeza.net (Pastebin and GitHub Gist alternative)", + "link_ready": " Code uploaded! Link: {}", + "invalid_args_ru": " Нет аргументов или реплая с файлом, или они неверны", + "link_ready_ru": " Код загружен! Ссылка: {}" + }, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, "archquise/H.Modules/numbersapi.py": { "name": "NumbersAPI", "description": "Many interesting facts about numbers.", @@ -17150,6 +18169,149 @@ "has_on_unload": true, "class_cmd_names": {} }, + "archquise/H.Modules/coddrago/DelMessTools.py": { + "name": "DelMessTools", + "description": "Module to manage and delete your messages in the current chat", + "cls_doc": {}, + "meta": { + "pic": "https://envs.sh/HJx.webp", + "banner": "https://raw.githubusercontent.com/coddrago/modules/refs/heads/main/banner.png", + "developer": "@codrago_m" + }, + "commands": [ + { + "purge": "(RU) [reply] [-img] [-voice] [-file] [-all] - удалить все ваши сообщения в текущем чате или только до сообщения, на которое ответили\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется | (EN) [reply] [-img] [-voice] [-file] [-all] - delete all your messages in current chat or only ones up to the message you replied to\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored" + }, + { + "purgekeyword": "(RU) <ключевое слово> [-img] [-voice] [-file] [-all] - удалить все ваши сообщения с указанным ключевым словом в текущем чате\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется | (EN) [-img] [-voice] [-file] [-all] - delete all your messages containing the specified keyword in the current chat\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored" + }, + { + "purgetime": "(RU) <начальное время> <конечное время> [-img] [-voice] [-file] [-all] - удалить все ваши сообщения в указанном временном диапазоне в текущем чате\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется\n Формат времени: YYYY-MM-DD HH:MM:SS | (EN) [-img] [-voice] [-file] [-all] - delete all your messages within the specified time range in the current chat\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored\n Time format: YYYY-MM-DD HH:MM:SS" + }, + { + "purgelength": "(RU) <длина> [-img] [-voice] [-file] [-all] - удалить все ваши сообщения указанной длины в текущем чате\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется | (EN) [-img] [-voice] [-file] [-all] - delete all your messages with the specified length in the current chat\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored" + }, + { + "nopurge": "(RU) Прервать процесс удаления\nИспользуйте в чате, где вы ранее начали удаление | (EN) Interrupt the deletion process\nUse in the chat where you've previously started deletion" + } + ], + "new_commands": [ + { + "name": "purge", + "original_name": "purge", + "description": { + "default": "", + "ru": "[reply] [-img] [-voice] [-file] [-all] - удалить все ваши сообщения в текущем чате или только до сообщения, на которое ответили\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется", + "en": "[reply] [-img] [-voice] [-file] [-all] - delete all your messages in current chat or only ones up to the message you replied to\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "purgekeyword", + "original_name": "purgekeyword", + "description": { + "default": "", + "ru": "<ключевое слово> [-img] [-voice] [-file] [-all] - удалить все ваши сообщения с указанным ключевым словом в текущем чате\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется", + "en": " [-img] [-voice] [-file] [-all] - delete all your messages containing the specified keyword in the current chat\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "purgetime", + "original_name": "purgetime", + "description": { + "default": "", + "ru": "<начальное время> <конечное время> [-img] [-voice] [-file] [-all] - удалить все ваши сообщения в указанном временном диапазоне в текущем чате\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется\n Формат времени: YYYY-MM-DD HH:MM:SS", + "en": " [-img] [-voice] [-file] [-all] - delete all your messages within the specified time range in the current chat\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored\n Time format: YYYY-MM-DD HH:MM:SS" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "purgelength", + "original_name": "purgelength", + "description": { + "default": "", + "ru": "<длина> [-img] [-voice] [-file] [-all] - удалить все ваши сообщения указанной длины в текущем чате\n -all - удалять сообщения в каждой теме, если это форум, иначе флаг игнорируется", + "en": " [-img] [-voice] [-file] [-all] - delete all your messages with the specified length in the current chat\n -all - to delete messages in each topic if this is a forum otherwise the flag'll just be ignored" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "nopurge", + "original_name": "nopurge", + "description": { + "default": "", + "ru": "Прервать процесс удаления\nИспользуйте в чате, где вы ранее начали удаление", + "en": "Interrupt the deletion process\nUse in the chat where you've previously started deletion" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + } + ], + "inline_handlers": [], + "strings": { + "name": "DelMessTools", + "purge_complete": "All your messages have been deleted.", + "purge_reply_complete": "Messages up to the replied message have been deleted.", + "purge_keyword_complete": "Messages containing the keyword have been deleted.", + "purge_time_complete": "Messages within the specified time range have been deleted.", + "purge_media_complete": "All your media messages have been deleted.", + "purge_length_complete": "Messages with the specified length have been deleted.", + "purge_type_complete": "Messages of the specified type have been deleted.", + "enabled": "It's not operational now anyway.", + "disabled": "Operation status changed to disabled.", + "interrupted": "The deletion was interrupted because you changed your mind.", + "none": "You didn't even intend to delete anything here, but anyway it's disabled now.", + "no_args": "Please specify arguments for the command.", + "no_keyword": "Please specify a keyword to delete messages.", + "no_length": "Please specify a valid length.", + "invalid_time": "Invalid time format. Please use the format: YYYY-MM-DD HH:MM:SS", + "invalid_length": "Please specify a valid number for length.", + "purge_complete_ru": "Все ваши сообщения были удалены.", + "purge_reply_complete_ru": "Сообщения до указанного ответа были удалены.", + "purge_keyword_complete_ru": "Сообщения, содержащие ключевое слово, были удалены.", + "purge_time_complete_ru": "Сообщения в указанном временном диапазоне были удалены.", + "purge_media_complete_ru": "Все ваши медиа-сообщения были удалены.", + "purge_length_complete_ru": "Сообщения указанной длины были удалены.", + "purge_type_complete_ru": "Сообщения указанного типа были удалены.", + "enabled_ru": "Оно итак сейчас не работает.", + "disabled_ru": "Режим работы изменен на выключено.", + "interrupted_ru": "Удаление было прервано т.к вы передумали.", + "none_ru": "Вы даже не пытались ничего здесь удалить, в любом случае сейчас оно выключено.", + "no_args_ru": "Пожалуйста, укажите аргументы для команды.", + "no_keyword_ru": "Пожалуйста, укажите ключевое слово для удаления сообщений.", + "no_length_ru": "Пожалуйста, укажите корректную длину.", + "invalid_time_ru": "Неверный формат времени. Используйте формат: YYYY-MM-DD HH:MM:SS", + "invalid_length_ru": "Пожалуйста, укажите корректное число для длины." + }, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, "archquise/H.Modules/aiogram3/hikarichat.py": { "name": "HikariChatMod", "description": "Advanced chat admin toolkit", @@ -25362,6 +26524,180 @@ "has_on_unload": false, "class_cmd_names": {} }, + "DziruModules/hikkamods/FunnyText.py": { + "name": "memetextmod", + "description": "Get funny text for chat", + "cls_doc": {}, + "meta": { + "pic": "https://raw.githubusercontent.com/DziruModules/assets/master/DziruModules.jpg", + "banner": "https://raw.githubusercontent.com/DziruModules/assets/master/FunnyText.png", + "developer": "@dziru" + }, + "commands": [ + { + "dscina": "Scina face" + }, + { + "dsthink": "Thinking face" + }, + { + "dhide": "Hiding pikachu" + }, + { + "dhidewall": "Hiding a wall" + }, + { + "dfrog": "Shocked frog" + }, + { + "dfroglol": "Frog dont care" + }, + { + "dtrump": "Trump face" + }, + { + "dwelcome": "Welcome message" + }, + { + "dgta": "GTA person" + } + ], + "new_commands": [ + { + "name": "dscina", + "original_name": "dscinacmd", + "description": { + "default": "Scina face" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "dsthink", + "original_name": "dsthinkcmd", + "description": { + "default": "Thinking face" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "dhide", + "original_name": "dhidecmd", + "description": { + "default": "Hiding pikachu" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "dhidewall", + "original_name": "dhidewallcmd", + "description": { + "default": "Hiding a wall" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "dfrog", + "original_name": "dfrogcmd", + "description": { + "default": "Shocked frog" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "dfroglol", + "original_name": "dfroglolcmd", + "description": { + "default": "Frog dont care" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "dtrump", + "original_name": "dtrumpcmd", + "description": { + "default": "Trump face" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "dwelcome", + "original_name": "dwelcomecmd", + "description": { + "default": "Welcome message" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "dgta", + "original_name": "dgtacmd", + "description": { + "default": "GTA person" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + } + ], + "inline_handlers": [], + "strings": { + "name": "FunnyText", + "dsthink": "⠀⠀⠀⠀⢀⣀⣀⣀\n⠀⠀⠀⠰⡿⠿⠛⠛⠻⠿⣷\n⠀⠀⠀⠀⠀⠀⣀⣄⡀⠀⠀⠀⠀⢀⣀⣀⣤⣄⣀⡀\n⠀⠀⠀⠀⠀⢸⣿⣿⣷⠀⠀⠀⠀⠛⠛⣿⣿⣿⡛⠿⠷\n⠀⠀⠀⠀⠀⠘⠿⠿⠋⠀⠀⠀⠀⠀⠀⣿⣿⣿⠇\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠁\n \n⠀⠀⠀⠀⣿⣷⣄⠀⢶⣶⣷⣶⣶⣤⣀\n⠀⠀⠀⠀⣿⣿⣿⠀⠀⠀⠀⠀⠈⠙⠻⠗\n⠀⠀⠀⣰⣿⣿⣿⠀⠀⠀⠀⢀⣀⣠⣤⣴⣶⡄\n ⣠⣾⣿⣿⣿⣥⣶⣶⣿⣿⣿⣿⣿⠿⠿⠛⠃\n⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄\n⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡁\n⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠁\n ⠛⢿⣿⣿⣿⣿⣿⣿⡿⠟\n", + "dhide": "`\n┻┳|―-∩``\n┳┻| ヽ``\n┻┳| ● |``\n┳┻|▼) _ノ``\n┻┳| ̄ )``\n┳ミ( ̄ /``\n┻┳T ̄|`\nHide", + "dhidewall": "`\n┻┳|―-∩``\n┳┻| ヽ``\n┻┳| ● |``\n┳┻|▼) _ノ``\n┻┳| ̄ )``\n┳ミ( ̄ /``\n┻┳T ̄|`\nHide behind a wall", + "dfrog": "⠄⠄⠄⠄⠄⣀⣀⣤⣶⣿⣿⣶⣶⣶⣤⣄⣠⣴⣶⣿⣶⣦⣄⠄\n⠄⣠⣴⣾⣿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦\n⢠⠾⣋⣭⣄⡀⠄⠙⠻⣿⣿⡿⠛⠋⠉⠉⠉⠙⠛⠿⣿⣿⣿⣿\n⡎⡟⢻⣿⣷⠄⠄⠄⠄⡼⣡⣾⣿⣿⣦⠄⠄⠄⠄⠄⠈⠛⢿⣿\n⡇⣷⣾⣿⠟⠄⠄⠄⢰⠁⣿⣇⣸⣿⣿⠄⠄⠄⠄⠄⠄⠄⣠⣼\n⣦⣭⣭⣄⣤⣤⣴⣶⣿⣧⡘⠻⠛⠛⠁⠄⠄⠄⠄⣀⣴⣿⣿⣿\n⢉⣹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣦⣶⣶⣶⣶⣿⣿⣿⣿⣿⣿\n⡿⠛⠛⠛⠛⠻⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\n⡇⠄⠄⢀⣀⣀⠄⠄⠄⠄⠉⠉⠛⠛⠻⠿⣿⣿⣿⣿⣿⣿⣿⣿\n⠈⣆⠄⠄⢿⣿⣿⣷⣶⣶⣤⣤⣀⣀⡀⠄⠄⠉⢻⣿⣿⣿⣿⣿\n⠄⣿⡀⠄⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠂⠄⢠⣿⣿⣿⣿⣿\n⠄⣿⡇⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠄⢀⣼⣿⣿⣿⣿⣿\n⠄⣿⡇⠄⠠⣿⣿⣿⣿⣿⣿⣿⡿⠋⠄⠄⣠⣾⣿⣿⣿⣿⣿⣿\n⠄⣿⠁⠄⠐⠛⠛⠛⠉⠉⠉⠉⠄⠄⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿\n⠄⠻⣦⣀⣀⣀⣀⣀⣤⣤⣤⣤⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋\n", + "dfroglol": "⣿⣿⣿⡇⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⡇⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⡇⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⡇⠄⣿⣿⣿⡿⠋⣉⣉⣉⡙⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⠃⠄⠹⠟⣡⣶⢟⣛⣛⡻⢿⣦⣩⣤⣬⡉⢻⣿⣿⣿⣿\n⣿⣿⣿⠄⢀⢤⣾⣿⣿⣿⡿⠿⠿⠿⢮⡃⣛⡻⢿⠈⣿⣿⣿⣿\n⣿⡟⢡⣴⣯⣿⣿⣿⠤⣤⣭⣶⣶⣶⣮⣔⡈⠛⢓⠦⠈⢻⣿⣿\n⠏⣠⣿⣿⣿⣿⣿⣿⣯⡪⢛⠿⢿⣿⣿⣿⡿⣼⣿⣿⣮⣄⠙⣿\n⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣾⡭⠴⣶⣶⣽⣽⣛⡿⠿⠿⠇⣿\n⣿⣿⣿⣿⣿⣿⣿⠿⣿⣿⣿⣿⣿⣿⣿⣷⣝⣛⢛⢋⣥⣴⣿⣿\n⣿⣿⣿⣿⣿⢿⠱⣿⣛⠾⣭⣛⡿⢿⣿⣿⣿⣿⣿⡀⣿⣿⣿⣿\n⠑⠽⡻⢿⣮⣽⣷⣶⣯⣽⣳⠮⣽⣟⣲⠯⢭⣿⣛⡇⣿⣿⣿⣿\n⠄⠄⠈⠑⠊⠉⠟⣻⠿⣿⣿⣿⣷⣾⣭⣿⠷⠶⠂⣴⣿⣿⣿⣿\n⠄⠄⠄⠄⠄⠄⠄⠁⠙⠒⠙⠯⠍⠙⢉⣡⣶⣿⣿⣿⣿⣿⣿⣿\n⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠙⣿⣿⣿⣿⣿⣿⣿⣿⣿\n", + "dtrump": "⣿⣿⣿⣿⣿⣿⡿⠿⠛⠋⠉⡉⣉⡛⣛⠿⣿⣿⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⡿⠋⠁⠄⠄⠄⠄⠄⢀⣸⣿⣿⡿⠿⡯⢙⠿⣿⣿⣿⣿\n⣿⣿⡿⠄⠄⠄⠄⠄⡀⡀⠄⢀⣀⣉⣉⣉⠁⠐⣶⣶⣿⣿⣿⣿\n⣿⣿⡇⠄⠄⠄⠄⠁⣿⣿⣀⠈⠿⢟⡛⠛⣿⠛⠛⣿⣿⣿⣿⣿\n⣿⣿⡆⠄⠄⠄⠄⠄⠈⠁⠰⣄⣴⡬⢵⣴⣿⣤⣽⣿⣿⣿⣿⣿\n⣿⣿⡇⠄⢀⢄⡀⠄⠄⠄⠄⡉⠻⣿⡿⠁⠘⠛⡿⣿⣿⣿⣿⣿\n⣿⡿⠃⠄⠄⠈⠻⠄⠄⠄⠄⢘⣧⣀⠾⠿⠶⠦⢳⣿⣿⣿⣿⣿\n⣿⣶⣤⡀⢀⡀⠄⠄⠄⠄⠄⠄⠻⢣⣶⡒⠶⢤⢾⣿⣿⣿⣿⣿\n⣿⡿⠋⠄⢘⣿⣦⡀⠄⠄⠄⠄⠄⠉⠛⠻⠻⠺⣼⣿⠟⠛⠿⣿\n⠋⠁⠄⠄⠄⢻⣿⣿⣶⣄⡀⠄⠄⠄⠄⢀⣤⣾⣿⡀⠄⠄⠄⢹\n⠄⠄⠄⠄⠄⠄⢻⣿⣿⣿⣷⡤⠄⠰⡆⠄⠄⠈⠛⢦⣀⡀⡀⠄\n⠄⠄⠄⠄⠄⠄⠈⢿⣿⠟⡋⠄⠄⠄⢣⠄⠄⠄⠄⠄⠈⠹⣿⣀\n⠄⠄⠄⠄⠄⠄⠄⠘⣷⣿⣿⣷⠄⠄⢺⣇⠄⠄⠄⠄⠄⠄⠸⣿\n⠄⠄⠄⠄⠄⠄⠄⠄⠹⣿⣿⡇⠄⠄⠸⣿⡄⠄⠈⠁⠄⠄⠄⣿\n⠄⠄⠄⠄⠄⠄⠄⠄⠄⢻⣿⡇⠄⠄⠄⢹⣧⠄⠄⠄⠄⠄⠄⠘\n", + "dwelcome": "───▄▀▀▀▄▄▄▄▄▄▄▀▀▀▄───\n───█▒▒░░░░░░░░░▒▒█───\n────█░░█░░░░░█░░█────\n─▄▄──█░░░▀█▀░░░█──▄▄─\n█░░█─▀▄░░░░░░░▄▀─█░░█\n█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█\n█░░╦─╦╔╗╦─╔╗╔╗╔╦╗╔╗░░█\n█░░║║║╠─║─║─║║║║║╠─░░█\n█░░╚╩╝╚╝╚╝╚╝╚╝╩─╩╚╝░░█\n█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█\n", + "dgta": "⠀⠀⠀⠀⠀⠀⠀⢀⣤⣤⡀⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⢿⣿⣿⠟⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠘⠻⣿⣷⣄⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⣴⣶⣿⡆⠀⠀⠉⠉⠀⠈⣶⡆⠀\n⠀⠀⠀⢠⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⢻⣷⠀\n⠀⠀⠀⣼⣿⡿⠟⠀⠀⠀⠀⠀⠀⠀⣸⣿⡄\n⠀⠀⠀⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠙⣿⣷\n⠀⠀⠘⠛⠃⠀⠀⠀⠀⠀⠀⠀⠀⢰⣾⣿⠏\n⠀⢠⣧⡔⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠟⠁⠀\n⠀⢸⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ Ah\nShit, here we go again.\n", + "dscina": "⣿⣿⣿⣿⠟⠋⢁⢁⢁⢁⢁⢁⢁⢁⠈⢻⢿⣿⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⣿⠃⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠈⡀⠭⢿⣿⣿⣿⣿\n⣿⣿⣿⡟⠄⢀⣾⣿⣿⣿⣷⣶⣿⣷⣶⣶⡆⠄⠄⠄⣿⣿⣿⣿\n⣿⣿⣿⡇⢀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠄⠄⢸⣿⣿⣿⣿\n⣿⣿⣿⣇⣼⣿⣿⠿⠶⠙⣿⡟⠡⣴⣿⣽⣿⣧⠄⢸⣿⣿⣿⣿\n⣿⣿⣿⣿⣾⣿⣿⣟⣭⣾⣿⣷⣶⣶⣴⣶⣿⣿⢄⣿⣿⣿⣿⣿\n⣿⣿⣿⣿⣿⣿⣿⡟⣩⣿⣿⣿⡏⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⣿⣿⣹⡋⠘⠷⣦⣀⣠⡶⠁⠈⠁⠄⣿⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⣿⣿⣍⠃⣴⣶⡔⠒⠄⣠⢀⠄⠄⠄⡨⣿⣿⣿⣿⣿⣿\n⣿⣿⣿⣿⣿⣿⣦⡘⠿⣷⣿⠿⠟⠃⠄⠄⣠⡇⠈⠻⣿⣿⣿⣿\n⣿⣿⣿⡿⠟⠋⢁⣷⣠⠄⠄⠄⠄⣀⣠⣾⡟⠄⠄⠄⠄⠉⠙⠻\n⡿⠟⠁⠄⠄⠄⢸⣿⣿⡯⢓⣴⣾⣿⣿⡟⠄⠄⠄⠄⠄⠄⠄⠄\n⠄⠄⠄⠄⠄⠄⣿⡟⣷⠄⠹⣿⣿⣿⡿⠁⠄⠄⠄⠄⠄⠄⠄⠄\n⠄⠄⠄⠄⠄⣸⣿⡷⡇⠄⣴⣾⣿⣿⠃⠄⠄⠄⠄⠄⠄⠄⠄⠄\n⠄⠄⠄⠄⠄⣿⣿⠃⣦⣄⣿⣿⣿⠇⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄\n⠄⠄⠄⠄⢸⣿⠗⢈⡶⣷⣿⣿⡏⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄\n" + }, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, "DziruModules/hikkamods/CryptoBalance.py": { "name": "CryptoBalanceMod", "description": "Check your balance in many Crypto Wallet Bots", @@ -26159,6 +27495,89 @@ "has_on_unload": false, "class_cmd_names": {} }, + "AmoreForever/amoremods/leta.py": { + "name": "Leta", + "description": "Customizable nightmode [Leta] for your group", + "cls_doc": {}, + "meta": { + "pic": null, + "banner": "https://raw.githubusercontent.com/AmoreForever/assets/master/leta.jpg", + "developer": "@hikamorumods" + }, + "commands": [ + { + "lettime": "Set time - morning [HH:MM] evening [HH:MM]" + }, + { + "letrmchat": "Remove nightmode - chat-id" + }, + { + "letchats": "Get all chats with nightmode" + } + ], + "new_commands": [ + { + "name": "lettime", + "original_name": "lettimecmd", + "description": { + "default": "Set time - morning [HH:MM] evening [HH:MM]" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "letrmchat", + "original_name": "letrmchatcmd", + "description": { + "default": "Remove nightmode - chat-id" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "letchats", + "original_name": "letchatscmd", + "description": { + "default": "Get all chats with nightmode" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + } + ], + "inline_handlers": [], + "strings": { + "name": "Leta", + "info": "🐈‍⬛ Heeey! I'm Leta! I'm a module for nightmode in your group.\n📫 You can get acquainted with my settings using the command .help Leta.", + "wrong_format": "🕔 Enter the time in the format HH:MM", + "day": "🌅 Good morning!\nNight mode is disabled.", + "night": "🌚 Good night!\nNight mode is enabled.", + "rm": " Removed nightmode.", + "rm_notfound": "🤷‍♂️ Nightmode is not set.", + "set": " Time set to\n🌃🌙 Night: {}\n🌅 Day: {}", + "info_ru": "🐈‍⬛ Привет! Я Leta! Я модуль для ночного режима в вашей группе.\n📫 Ознакомиться с моими настройками можно с помощью команды .help Leta.", + "wrong_format_ru": "🕔 Введите время в формате HH:MM", + "day_ru": "🌅 Доброе утро!\nНочной режим отключен.", + "night_ru": "🌚 Доброй ночи!\nНочной режим включен.", + "rm_ru": " Удален ночной режим.", + "rm_notfound_ru": "🤷‍♂️ Ночной режим не установлен.", + "set_ru": " Время установлено на\n🌃🌙 Ночь: {}\n🌅 День: {}" + }, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, "AmoreForever/amoremods/wakatime.py": { "name": "Wakatime", "description": "Show your Wakatime stats", @@ -26861,6 +28280,104 @@ "has_on_unload": false, "class_cmd_names": {} }, + "AmoreForever/amoremods/figlet.py": { + "name": "Figlet", + "description": "Creates Figlet Text", + "cls_doc": {}, + "meta": { + "pic": null, + "banner": "https://github.com/AmoreForever/assets/blob/master/Figlet.jpg?raw=true", + "developer": "@hikamorumods" + }, + "commands": [ + { + "listfig": "List of figlet styles" + }, + { + "figlet": "Create figlet text,