Added and updated repositories 2026-04-12 14:57:04

This commit is contained in:
github-actions[bot]
2026-04-12 14:57:04 +00:00
parent 3156a432f0
commit c4b56bee9b
47 changed files with 5628 additions and 2 deletions

207
Fixyres/FModules/.gitignore vendored Normal file
View File

@@ -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__/

254
Fixyres/FModules/BSR.py Normal file
View File

@@ -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": "<b>Usage:</b> <code>{prefix}bsr (room code/link) (previous) (next)</code>",
"invalid_code": "<b>Invalid room code!</b>",
"at_least_one": "<b>At least one argument (previous or next) must be greater than 0!</b>",
"prev_block": "<b>Previous:</b>\n<blockquote expandable>{prev_list}</blockquote>",
"next_block": "<b>Next:</b>\n<blockquote expandable>{next_list}</blockquote>",
"btn_target": "Target Room"
}
strings_ru = {
"_cls_doc": "Модуль для поиска ближайших игровых комнат в BrawlStars.",
"invalid_args": "<b>Использование:</b> <code>{prefix}bsr (код комнаты/ссылка) (предыдущие) (следующие)</code>",
"invalid_code": "<b>Неверный код комнаты!</b>",
"at_least_one": "<b>Хотя бы один аргумент (предыдущие или следующие) должен быть больше 0!</b>",
"prev_block": "<b>Предыдущие:</b>\n<blockquote expandable>{prev_list}</blockquote>",
"next_block": "<b>Следующие:</b>\n<blockquote expandable>{next_list}</blockquote>",
"btn_target": "Целевая комната"
}
strings_ua = {
"_cls_doc": "Модуль для пошуку найближчих ігрових кімнат у BrawlStars.",
"invalid_args": "<b>Використання:</b> <code>{prefix}bsr (код кімнати/посилання) (попередні) (наступні)</code>",
"invalid_code": "<b>Невірний код кімнати!</b>",
"at_least_one": "<b>Хоча б один аргумент (попередні або наступні) повинен бути більшим за 0!</b>",
"prev_block": "<b>Попередні:</b>\n<blockquote expandable>{prev_list}</blockquote>",
"next_block": "<b>Наступні:</b>\n<blockquote expandable>{next_list}</blockquote>",
"btn_target": "Цільова кімната"
}
strings_kz = {
"_cls_doc": "BrawlStars ойынында жақын маңдағы ойын бөлмелерін табуға арналған модуль.",
"invalid_args": "<b>Қолдану:</b> <code>{prefix}bsr (бөлме коды/сілтеме) (алдыңғы) (келесі)</code>",
"invalid_code": "<b>Қате бөлме коды!</b>",
"at_least_one": "<b>Кем дегенде бір аргумент (алдыңғы немесе келесі) 0-ден үлкен болуы керек!</b>",
"prev_block": "<b>Алдыңғы:</b>\n<blockquote expandable>{prev_list}</blockquote>",
"next_block": "<b>Келесі:</b>\n<blockquote expandable>{next_list}</blockquote>",
"btn_target": "Мақсатты бөлме"
}
strings_uz = {
"_cls_doc": "BrawlStars'da eng yaqin o'yin xonalarini topish uchun modul.",
"invalid_args": "<b>Qo'llanilishi:</b> <code>{prefix}bsr (xona kodi/havolasi) (oldingi) (keyingi)</code>",
"invalid_code": "<b>Noto'g'ri xona kodi!</b>",
"at_least_one": "<b>Kamida bitta argument (oldingi yoki keyingi) 0 dan katta bo'lishi kerak!</b>",
"prev_block": "<b>Oldingi:</b>\n<blockquote expandable>{prev_list}</blockquote>",
"next_block": "<b>Keyingi:</b>\n<blockquote expandable>{next_list}</blockquote>",
"btn_target": "Maqsadli xona"
}
strings_fr = {
"_cls_doc": "Module pour trouver des salles de jeu à proximité dans BrawlStars.",
"invalid_args": "<b>Utilisation:</b> <code>{prefix}bsr (code/lien) (précédents) (suivants)</code>",
"invalid_code": "<b>Code de salle invalide!</b>",
"at_least_one": "<b>Au moins un argument doit être supérieur à 0 !</b>",
"prev_block": "<b>Précédents:</b>\n<blockquote expandable>{prev_list}</blockquote>",
"next_block": "<b>Suivants:</b>\n<blockquote expandable>{next_list}</blockquote>",
"btn_target": "Salle cible"
}
strings_de = {
"_cls_doc": "Modul zum Finden von nahegelegenen Spielräumen in BrawlStars.",
"invalid_args": "<b>Verwendung:</b> <code>{prefix}bsr (Raumcode/Link) (vorherige) (nächste)</code>",
"invalid_code": "<b>Ungültiger Raumcode!</b>",
"at_least_one": "<b>Mindestens ein argument muss größer als 0 sein!</b>",
"prev_block": "<b>Vorherige:</b>\n<blockquote expandable>{prev_list}</blockquote>",
"next_block": "<b>Nächste:</b>\n<blockquote expandable>{next_list}</blockquote>",
"btn_target": "Zielraum"
}
strings_jp = {
"_cls_doc": "BrawlStarsで近くのゲームルームを検索するためのモジュール。",
"invalid_args": "<b>使用法:</b> <code>{prefix}bsr (コード/リンク) (前) (次)</code>",
"invalid_code": "<b>無効なルームコード!</b>",
"at_least_one": "<b>少なくとも1つの引数は0より大きくなければなりません</b>",
"prev_block": "<b>前:</b>\n<blockquote expandable>{prev_list}</blockquote>",
"next_block": "<b>次:</b>\n<blockquote expandable>{next_list}</blockquote>",
"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'<a href="https://link.brawlstars.com/invite/gameroom/en?tag={c}">{c}</a>'
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'<a href="https://link.brawlstars.com/invite/gameroom/en?tag={c}">{c}</a>'
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
)

986
Fixyres/FModules/FHeta.py Normal file
View File

@@ -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"<external {name}>"), origin=f"<external {name}>")
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')} <code>{name}</code> <b>{self.main.strings['author']}</b> <code>{author}</code>"
if version != "?.?.?":
text += f" (<code>v{version}</code>)"
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')} <b>{self.main.strings['description']}:</b>\n<blockquote expandable>{utils.escape_html(str(string))}</blockquote>"
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"<code>{character}{name}</code> {description}".strip()
else:
row = f"<code>{{{name}}}</code> {description}".strip()
extra = f"<i>{self.main.strings[more].format(remaining=len(items) - index)}</i>"
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')} <b>{self.main.strings[title]}:</b>\n<blockquote expandable>{chr(10).join(lines)}</blockquote>"
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": '<tg-emoji emoji-id="5188217332748527444">🔍</tg-emoji>',
"error": '<tg-emoji emoji-id="5465665476971471368">❌</tg-emoji>',
"warn": '<tg-emoji emoji-id="5447644880824181073">⚠️</tg-emoji>',
"description": '<tg-emoji emoji-id="5334882760735598374">📝</tg-emoji>',
"command": '<tg-emoji emoji-id="5341715473882955310">⚙️</tg-emoji>',
"placeholder": '<tg-emoji emoji-id="5359785904535774578">🗒️</tg-emoji>',
"module": '<tg-emoji emoji-id="5454112830989025752">📦</tg-emoji>',
"channel": '<tg-emoji emoji-id="5278256077954105203">📢</tg-emoji>',
"modules_list": '<tg-emoji emoji-id="5197269100878907942">📋</tg-emoji>'
},
"winter": {
"search": '<tg-emoji emoji-id="5431895003821513760">❄️</tg-emoji>',
"error": '<tg-emoji emoji-id="5404728536810398694">🧊</tg-emoji>',
"warn": '<tg-emoji emoji-id="5447644880824181073">🌨️</tg-emoji>',
"description": '<tg-emoji emoji-id="5255850496291259327">📜</tg-emoji>',
"command": '<tg-emoji emoji-id="5199503707938505333">🎅</tg-emoji>',
"placeholder": '<tg-emoji emoji-id="5204046675236109418">🗒️</tg-emoji>',
"module": '<tg-emoji emoji-id="5197708768091061888">🎁</tg-emoji>',
"channel": '<tg-emoji emoji-id="5278256077954105203">📢</tg-emoji>',
"modules_list": '<tg-emoji emoji-id="5345935030143196497">🎄</tg-emoji>'
},
"summer": {
"search": '<tg-emoji emoji-id="5188217332748527444">🔍</tg-emoji>',
"error": '<tg-emoji emoji-id="5470049770997292425">🌡️</tg-emoji>',
"warn": '<tg-emoji emoji-id="5447644880824181073">⚠️</tg-emoji>',
"description": '<tg-emoji emoji-id="5361684086807076580">🍹</tg-emoji>',
"command": '<tg-emoji emoji-id="5442644589703866634">🏄</tg-emoji>',
"placeholder": '<tg-emoji emoji-id="5434121252874756456">🗒️</tg-emoji>',
"module": '<tg-emoji emoji-id="5433645645376264953">🏖️</tg-emoji>',
"channel": '<tg-emoji emoji-id="5278256077954105203">📢</tg-emoji>',
"modules_list": '<tg-emoji emoji-id="5472178859300363509">🏖️</tg-emoji>'
},
"spring": {
"search": '<tg-emoji emoji-id="5449885771420934013">🌱</tg-emoji>',
"error": '<tg-emoji emoji-id="5208923808169222461">🥀</tg-emoji>',
"warn": '<tg-emoji emoji-id="5447644880824181073">⚠️</tg-emoji>',
"description": '<tg-emoji emoji-id="5251524493561569780">🍃</tg-emoji>',
"command": '<tg-emoji emoji-id="5449850741667668411">🦋</tg-emoji>',
"placeholder": '<tg-emoji emoji-id="5434121252874756456">🗒️</tg-emoji>',
"module": '<tg-emoji emoji-id="5440911110838425969">🌿</tg-emoji>',
"channel": '<tg-emoji emoji-id="5278256077954105203">📢</tg-emoji>',
"modules_list": '<tg-emoji emoji-id="5440748683765227563">🌺</tg-emoji>'
},
"autumn": {
"search": '<tg-emoji emoji-id="5253944419870062295">🍂</tg-emoji>',
"error": '<tg-emoji emoji-id="5281026503658728615">🍁</tg-emoji>',
"warn": '<tg-emoji emoji-id="5447644880824181073">⚠️</tg-emoji>',
"description": '<tg-emoji emoji-id="5406631276042002796">📜</tg-emoji>',
"command": '<tg-emoji emoji-id="5212963577098417551">🍂</tg-emoji>',
"placeholder": '<tg-emoji emoji-id="5363965354391388799">🗒️</tg-emoji>',
"module": '<tg-emoji emoji-id="5249157915041865558">🍄</tg-emoji>',
"channel": '<tg-emoji emoji-id="5278256077954105203">📢</tg-emoji>',
"modules_list": '<tg-emoji emoji-id="5305495722618010655">🍂</tg-emoji>'
}
}
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')} <b>{self.strings['list']}</b>"
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')} <b>{self.strings['list']}</b>"
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')} <b>{self.strings['prompt']}</b>",
"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')} <b>{self.strings['toolong']}</b>",
"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')} <b>{self.strings['notfound'].format(query=utils.escape_html(query))}</b>",
"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')} <b>{self.strings['noquery'].format(prefix=self.get_prefix())}</b>")
if len(query) > 168:
return await utils.answer(message, f"{self.ui.emoji('warn')} <b>{self.strings['toolong']}</b>")
message = await utils.answer(message, f"{self.ui.emoji('search')} <b>{self.strings['search'].format(query=utils.escape_html(query))}</b>")
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')} <b>{self.strings['notfound'].format(query=utils.escape_html(query))}</b>")
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

201
Fixyres/FModules/LICENSE Normal file
View File

@@ -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.

View File

@@ -0,0 +1 @@
My modules for Heroku userbot, telegram channel: https://t.me/FModules

197
Fixyres/FModules/SCD.py Normal file
View File

@@ -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": "✘ <b>You didn't provide a link to the song, example of using the command: <code>{prefix}sc (link)</code></b>",
"downloading": "↓ <b>Downloading...</b>",
"not_found": "✘ <b>Song not found.</b>"
}
strings_ru = {
"_cls_doc": "Модуль для скачивания песен с SoundCloud.",
"no_args": "✘ <b>Вы не указали ссылку на песню, пример использования команды: <code>{prefix}sc (ссылка)</code></b>",
"downloading": "↓ <b>Скачивание...</b>",
"not_found": "✘ <b>Песня не найдена.</b>"
}
strings_ua = {
"_cls_doc": "Модуль для завантаження пісень із SoundCloud.",
"no_args": "✘ <b>Ви не вказали посилання на пісню, приклад використання команди: <code>{prefix}sc (посилання)</code></b>",
"downloading": "↓ <b>Завантаження...</b>",
"not_found": "✘ <b>Пісню не знайдено.</b>"
}
strings_de = {
"_cls_doc": "Modul zum Herunterladen von Liedern von SoundCloud.",
"no_args": "✘ <b>Sie haben keinen Link zum Lied angegeben, Anwendungsbeispiel des Befehls: <code>{prefix}sc (Link)</code></b>",
"downloading": "↓ <b>Wird heruntergeladen...</b>",
"not_found": "✘ <b>Lied nicht gefunden.</b>"
}
strings_uz = {
"_cls_doc": "SoundCloud-dan qo'shiqlarni yuklab olish uchun modul.",
"no_args": "✘ <b>Siz qo'shiq havolasini kiritmadingiz, buyruqdan foydalanish misoli: <code>{prefix}sc (havola)</code></b>",
"downloading": "↓ <b>Yuklab olinmoqda...</b>",
"not_found": "✘ <b>Qo'shiq topilmadi.</b>"
}
strings_kz = {
"_cls_doc": "SoundCloud-тан әндерді жүктеп алуға арналған модуль.",
"no_args": "✘ <b>Сіз әнге сілтеме көрсетпедіңіз, бұйрықты пайдалану мысалы: <code>{prefix}sc (сілтеме)</code></b>",
"downloading": "↓ <b>Жүктелуде...</b>",
"not_found": "✘ <b>Ән табылмады.</b>"
}
strings_fr = {
"_cls_doc": "Module pour télécharger des chansons depuis SoundCloud.",
"no_args": "✘ <b>Vous n'avez pas fourni de lien vers la chanson, exemple d'utilisation de la commande: <code>{prefix}sc (lien)</code></b>",
"downloading": "↓ <b>Téléchargement...</b>",
"not_found": "✘ <b>Chanson non trouvée.</b>"
}
strings_jp = {
"_cls_doc": "SoundCloudから曲をダウンロードするためのモジュール。",
"no_args": "✘ <b>曲へのリンクが指定されていません。コマンドの使用例: <code>{prefix}sc (リンク)</code></b>",
"downloading": "↓ <b>ダウンロード中...</b>",
"not_found": "✘ <b>曲が見つかりません。</b>"
}
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*(\[.*?\]);</script>', 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"))

View File

@@ -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".*?>(.+?)</p>', 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": "<b>Guess any character you have in mind, and click on the Start button.</b>",
"yes": "Yes",
"no": "No",
"idk": "I don't know",
"probably": "Probably",
"probably_not": "Probably not",
"this_is": "<b>This is <code>{name}</code>\n<code>{description}</code></b>",
"this_is_no_desc": "<b>This is <code>{name}</code></b>",
"not_right": "Not right",
"failed": "<b>Failed to guess the character.</b>"
}
strings_ru = {
"lang": "ru",
"_cls_doc": "Акинатор угадает любого вами загаданного персонажа.",
"child_mode": "Детский режим. Сложнее отгадать 18+ героев.",
"start": "Начать",
"text": "<b>Задумайте персонажа, и нажмите начать.</b>",
"yes": "Да",
"no": "Нет",
"idk": "Не знаю",
"probably": "Возможно",
"probably_not": "Скорее нет",
"this_is": "<b>Это <code>{name}</code>\n<code>{description}</code></b>",
"this_is_no_desc": "<b>Это <code>{name}</code></b>",
"not_right": "Это не он",
"failed": "<b>Не удалось угадать персонажа.</b>"
}
strings_ua = {
"lang": "uk",
"_cls_doc": "Акінатор вгадає будь-якого персонажа.",
"child_mode": "Дитячий режим. Складніше відгадати 18+ героїв.",
"start": "Почати",
"text": "<b>Загадайте персонажа, і натисніть почати.</b>",
"yes": "Так",
"no": "Ні",
"idk": "Не знаю",
"probably": "Можливо",
"probably_not": "Швидше ні",
"this_is": "<b>Це <code>{name}</code>\n<code>{description}</code></b>",
"this_is_no_desc": "<b>Це <code>{name}</code></b>",
"not_right": "Це не він",
"failed": "<b>Не вдалося вгадати персонажа.</b>"
}
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": "<b>Denk dir einen Charakter aus und klicke auf Start.</b>",
"yes": "Ja",
"no": "Nein",
"idk": "Ich weiß nicht",
"probably": "Wahrscheinlich",
"probably_not": "Wahrscheinlich nicht",
"this_is": "<b>Das ist <code>{name}</code>\n<code>{description}</code></b>",
"this_is_no_desc": "<b>Das ist <code>{name}</code></b>",
"not_right": "Das ist er nicht",
"failed": "<b>Charakter konnte nicht erraten werden.</b>"
}
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": "<b>Pensez à un personnage et cliquez sur Commencer.</b>",
"yes": "Oui",
"no": "Non",
"idk": "Je ne sais pas",
"probably": "Probablement",
"probably_not": "Probablement pas",
"this_is": "<b>C'est <code>{name}</code>\n<code>{description}</code></b>",
"this_is_no_desc": "<b>C'est <code>{name}</code></b>",
"not_right": "Ce n'est pas lui",
"failed": "<b>Impossible de deviner.</b>"
}
strings_jp = {
"lang": "ja",
"_cls_doc": "アキネーターはあなたが考えているキャラクターを当てます。",
"child_mode": "子供モード。有効にすると、18歳以上のキャラクターを推測するのが難しくなります。",
"start": "開始",
"text": "<b>キャラクターを思い浮かべて開始。</b>",
"yes": "はい",
"no": "いいえ",
"idk": "わかりません",
"probably": "おそらく",
"probably_not": "おそらく違う",
"this_is": "<b>これは <code>{name}</code>\n<code>{description}</code></b>",
"this_is_no_desc": "<b>これは <code>{name}</code></b>",
"not_right": "違います",
"failed": "<b>推測できませんでした。</b>"
}
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": "<b>Qahramonni o'ylang va Boshlash tugmasini bosing.</b>",
"yes": "Ha",
"no": "Yo'q",
"idk": "Bilmayman",
"probably": "Ehtimol",
"probably_not": "Ehtimol yo'q",
"this_is": "<b>Bu <code>{name}</code>\n<code>{description}</code></b>",
"this_is_no_desc": "<b>Bu <code>{name}</code></b>",
"not_right": "Bu u emas",
"failed": "<b>Qahramonni topib bo'lmadi.</b>"
}
strings_kz = {
"lang": "kk",
"_cls_doc": "Акинатор сіз ойлаған кез келген кейіпкерді табады.",
"child_mode": "Балалар режимі. Қосылған болса, 18+ кейіпкерлерді табу қиынырақ болады.",
"start": "Бастау",
"text": "<b>Кейіпкерді ойлаңыз және Бастау түймесін басыңыз.</b>",
"yes": "Иә",
"no": "Жоқ",
"idk": "Білмеймін",
"probably": "Мүмкін",
"probably_not": "Мүмкін емес",
"this_is": "<b>Бұл <code>{name}</code>\n<code>{description}</code></b>",
"this_is_no_desc": "<b>Бұл <code>{name}</code></b>",
"not_right": "Бұл ол емес",
"failed": "<b>Кейіпкерді таба алмадық.</b>"
}
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"<code>{e}</code>")
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"<b>{question}</b>",
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=[]
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

View File

@@ -0,0 +1,4 @@
akinator
FHeta
BSR
SCD