diff --git a/Fixyres/FModules/BSR.py b/Fixyres/FModules/BSR.py index 8d383c4..5b32f16 100644 --- a/Fixyres/FModules/BSR.py +++ b/Fixyres/FModules/BSR.py @@ -7,7 +7,7 @@ __version__ = (1, 0, 0) # 🔑 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 developer: @NFModules # meta fhsdesc: brawlstars, game, funny from .. import loader, utils diff --git a/Fixyres/FModules/FHeta.py b/Fixyres/FModules/FHeta.py index ab706ae..c50e0a2 100644 --- a/Fixyres/FModules/FHeta.py +++ b/Fixyres/FModules/FHeta.py @@ -1,6 +1,6 @@ __version__ = (9, 3, 9) -# meta developer: @FModules +# meta developer: @NFModules # 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 @@ -100,18 +100,6 @@ class MInstaller: 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: @@ -121,7 +109,7 @@ class MInstaller: )) if dependencies: - if not await self.pip(dependencies): + if not await plugin.install_requirements(dependencies): return dependencies importlib.invalidate_caches() return "retry" @@ -171,7 +159,7 @@ class MInstaller: 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): + if not alternative or not await plugin.install_requirements(dependencies): return dependencies importlib.invalidate_caches() @@ -314,7 +302,7 @@ class FHetaUI: @loader.tds class FHeta(loader.Module): - '''Module for searching modules! Watch all FHeta news in @FHeta_Updates!''' + '''Module for searching modules! Watch all FHeta news in @NFHeta_Updates!''' strings = { "name": "FHeta", @@ -345,11 +333,12 @@ class FHeta(loader.Module): "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." + "doctheme": "Theme for emojis.", + "channel": "This is the channel with all updates in FHeta!" } strings_ru = { - "_cls_doc": "Модуль для поиска модулей! Следите за всеми новостями FHeta в @FHeta_Updates!", + "_cls_doc": "Модуль для поиска модулей! Следите за всеми новостями FHeta в @NFHeta_Updates!", "lang": "ru", "author": "от", "description": "Описание", @@ -377,11 +366,12 @@ class FHeta(loader.Module): "overwrite": "✘ Ошибка, модуль пытался перезаписать встроенный модуль!", "dependency": "✘ Ошибка установки зависимостей! {deps}", "docdevs": "Использовать только модули от официальных разработчиков Heroku при поиске?", - "doctheme": "Тема для эмодзи." + "doctheme": "Тема для эмодзи.", + "channel": "Это канал со всеми обновлениями в FHeta!" } strings_ua = { - "_cls_doc": "Модуль для пошуку модулів! Слідкуйте за всіма новинами FHeta в @FHeta_Updates!", + "_cls_doc": "Модуль для пошуку модулів! Слідкуйте за всіма новинами FHeta в @NFHeta_Updates!", "lang": "ua", "author": "від", "description": "Опис", @@ -409,11 +399,12 @@ class FHeta(loader.Module): "overwrite": "✘ Помилка, модуль намагався перезаписати вбудований модуль!", "dependency": "✘ Помилка встановлення залежностей! {deps}", "docdevs": "Використовувати тільки модулі від офіційних розробників Heroku при пошуку?", - "doctheme": "Тема для емодзі." + "doctheme": "Тема для емодзі.", + "channel": "Це канал з усіма оновленнями в FHeta!" } strings_kz = { - "_cls_doc": "Модульдерді іздеу модулі! FHeta барлық жаңалықтарын @FHeta_Updates арнасында қадағалаңыз!", + "_cls_doc": "Модульдерді іздеу модулі! FHeta барлық жаңалықтарын @NFHeta_Updates арнасында қадағалаңыз!", "lang": "kz", "author": "авторы", "description": "Сипаттама", @@ -441,11 +432,12 @@ class FHeta(loader.Module): "overwrite": "✘ Қате, модуль кіріктірілген модульді қайта жазуға тырысты!", "dependency": "✘ Тәуелділіктерді орнату қатесі! {deps}", "docdevs": "Іздеу кезінде тек ресми Heroku әзірлеушілерінің модульдерін пайдалану керек пе?", - "doctheme": "Эмодзилер үшін тақырып." + "doctheme": "Эмодзилер үшін тақырып.", + "channel": "Бұл FHeta-дағы барлық жаңартулары бар арна!" } strings_uz = { - "_cls_doc": "Modullarni qidirish moduli! FHeta barcha yangilanishlarini @FHeta_Updates kanalida kuzatib boring!", + "_cls_doc": "Modullarni qidirish moduli! FHeta barcha yangilanishlarini @NFHeta_Updates kanalida kuzatib boring!", "lang": "uz", "author": "muallif", "description": "Tavsif", @@ -473,11 +465,12 @@ class FHeta(loader.Module): "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." + "doctheme": "Emojilar uchun mavzu.", + "channel": "Bu FHeta-dagi barcha yangilanishlari bo'lgan kanal!" } strings_fr = { - "_cls_doc": "Module de recherche de modules! Suivez toutes les actualités FHeta sur @FHeta_Updates!", + "_cls_doc": "Module de recherche de modules! Suivez toutes les actualités FHeta sur @NFHeta_Updates!", "lang": "fr", "author": "par", "description": "Description", @@ -505,11 +498,12 @@ class FHeta(loader.Module): "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." + "doctheme": "Thème pour les emojis.", + "channel": "Voici le canal avec toutes les mises à jour dans FHeta!" } strings_de = { - "_cls_doc": "Modul zur Suche nach Modulen! Verfolgen Sie alle FHeta-Neuigkeiten auf @FHeta_Updates!", + "_cls_doc": "Modul zur Suche nach Modulen! Verfolgen Sie alle FHeta-Neuigkeiten auf @NFHeta_Updates!", "lang": "de", "author": "von", "description": "Beschreibung", @@ -537,11 +531,12 @@ class FHeta(loader.Module): "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." + "doctheme": "Thema für Emojis.", + "channel": "Dies ist der Kanal mit allen Updates in FHeta!" } strings_jp = { - "_cls_doc": "モジュール検索用モジュール!@FHeta_UpdatesでFHetaのすべてのニュースをフォローしてください!", + "_cls_doc": "モジュール検索用モジュール!@NFHeta_UpdatesでFHetaのすべてのニュースをフォローしてください!", "lang": "jp", "author": "作成者", "description": "説明", @@ -569,7 +564,8 @@ class FHeta(loader.Module): "overwrite": "✘ エラー、モジュールが組み込みモジュールを上書きしようとしました!", "dependency": "✘ 依存関係のインストールエラー! {deps}", "docdevs": "検索時に公式Heroku開発者のモジュールのみを使用しますか?", - "doctheme": "絵文字のテーマ。" + "doctheme": "絵文字のテーマ。", + "channel": "これはFHetaのすべての更新を含むチャンネルです!" } THEMES = { @@ -581,7 +577,7 @@ class FHeta(loader.Module): "command": '⚙️', "placeholder": '🗒️', "module": '📦', - "channel": '📢', + "channel": '📢', "modules_list": '📋' }, "winter": { @@ -592,7 +588,7 @@ class FHeta(loader.Module): "command": '🎅', "placeholder": '🗒️', "module": '🎁', - "channel": '📢', + "channel": '📢', "modules_list": '🎄' }, "summer": { @@ -603,7 +599,7 @@ class FHeta(loader.Module): "command": '🏄', "placeholder": '🗒️', "module": '🏖️', - "channel": '📢', + "channel": '📢', "modules_list": '🏖️' }, "spring": { @@ -614,7 +610,7 @@ class FHeta(loader.Module): "command": '🦋', "placeholder": '🗒️', "module": '🌿', - "channel": '📢', + "channel": '📢', "modules_list": '🌺' }, "autumn": { @@ -625,7 +621,7 @@ class FHeta(loader.Module): "command": '🍂', "placeholder": '🗒️', "module": '🍄', - "channel": '📢', + "channel": '📢', "modules_list": '🍂' } } @@ -664,6 +660,11 @@ class FHeta(loader.Module): self.installer = MInstaller() self.ui = FHetaUI(self) + await self.request_join( + "NFHeta_Updates", + f"{self.ui.emoji('channel')} {self.strings('channel')}" + ) + self.api.token = self.token router = None @@ -716,13 +717,20 @@ class FHeta(loader.Module): self.api.token = self.token except Exception: pass - - @loader.loop(interval=1, autostart=True) + + asyncio.create_task(self.sync()) + 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 + ll = None + while True: + try: + cl = self.strings["lang"] + if cl != ll: + await self.api.send("dataset", user_id=self.identifier, lang=cl) + ll = cl + except Exception: + pass + await asyncio.sleep(1) async def answer(self, callback: Union[CallbackQuery, ChosenInlineResult], text: Optional[str] = None, alert: bool = False) -> None: try: @@ -872,7 +880,7 @@ class FHeta(loader.Module): return { "title": self.strings["prompt"], "description": self.strings["hint"], - "message": f"{self.ui.emoji('error')} {self.strings['prompt']}", + "message": f"{self.ui.emoji('error')} {self.strings['noquery'].format(prefix=f'@{self.inline.bot_username} ')}", "thumb": "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/magnifying_glass.png" } @@ -890,7 +898,7 @@ class FHeta(loader.Module): return { "title": self.strings["retry"], "description": self.strings["hint"], - "message": f"{self.ui.emoji('error')} {self.strings['notfound'].format(query=utils.escape_html(query))}", + "message": f"{self.ui.emoji('error')} {self.strings['notfound'].format(query=f'{utils.escape_html(query)}')}", "thumb": "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FHeta/try_other_query.png" } @@ -941,18 +949,18 @@ class FHeta(loader.Module): 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())}") + return await utils.answer(message, f"{self.ui.emoji('error')} {self.strings['noquery'].format(prefix=f'{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))}") + message = await utils.answer(message, f"{self.ui.emoji('search')} {self.strings['search'].format(query=f'{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))}") - + return await utils.answer(message, f"{self.ui.emoji('error')} {self.strings['notfound'].format(query=f'{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) diff --git a/Fixyres/FModules/FSecurity.py b/Fixyres/FModules/FSecurity.py new file mode 100644 index 0000000..f7c8c0a --- /dev/null +++ b/Fixyres/FModules/FSecurity.py @@ -0,0 +1,628 @@ +__version__ = (1, 0, 0) + +# meta developer: @NFModules + +import asyncio +import aiohttp +import html +import sys +import uuid +import copy +import hashlib +import json +import re +from contextlib import suppress +from .. import loader, utils + + +@loader.tds +class FSecurity(loader.Module): + """Module for automatic AI-based security checks of installed modules.""" + + strings = { + "name": "FSecurity", + "lang": "English", + "unavailable": "AI module{} check is unavailable.", + "suspicious": "AI interrupted installation of a suspicious module{}, reason:", + "blocked": "AI blocked module installation{}, reason:", + "continue": "Continue installation?", + "strict_mode_doc": "Block loading modules by any method (lm/dlm allowed) if the AI API is unavailable or the module is suspicious. On restart, this also applies to already installed modules.", + "nvidia_api_key_doc": "API key from build.nvidia.com, used for AI checks. If not specified, a public key from GitHub will be used." + } + + strings_ru = { + "lang": "Russian", + "_cls_doc": "Модуль для автоматической проверки устанавливаемых модулей через ИИ.", + "unavailable": "Проверка модуля{} через ИИ недоступна.", + "suspicious": "ИИ прервал установку подозрительного модуля{}, причина:", + "blocked": "ИИ заблокировал установку модуля{}, причина:", + "continue": "Продолжить установку?", + "strict_mode_doc": "Не позволять загружать модули любым методом (lm/dlm разрешено), если API ИИ недоступен или модуль подозрителен. При перезагрузке работает даже на уже установленные модули.", + "nvidia_api_key_doc": "API ключ от build.nvidia.com, используется для проверки через ИИ. Если вы его не укажете, будет использоваться общий ключ с GitHub." + } + + strings_ua = { + "lang": "Ukraine", + "_cls_doc": "Модуль для автоматичної перевірки встановлюваних модулів через ШІ.", + "unavailable": "Перевірка модуля{} через ШІ недоступна.", + "suspicious": "ШІ перервав встановлення підозрілого модуля{}, причина:", + "blocked": "ШІ заблокував встановлення модуля{}, причина:", + "continue": "Продовжити встановлення?", + "strict_mode_doc": "Не дозволяти завантажувати модулі будь-яким методом (lm/dlm дозволено), якщо API ШІ недоступний або модуль підозрілий. При перезавантаженні працює навіть на вже встановлені модулі.", + "nvidia_api_key_doc": "API ключ від build.nvidia.com, використовується для перевірки через ШІ. Якщо ви його не вкажете, буде використовуватися загальний ключ з GitHub." + } + + strings_de = { + "lang": "Germany", + "_cls_doc": "Modul zur automatischen Prüfung installierter Module mit KI.", + "unavailable": "Die KI-Modulprüfung{} ist nicht verfügbar.", + "suspicious": "Die KI hat die Installation eines verdächtigen Moduls unterbrochen{}, Grund:", + "blocked": "Die KI hat die Modulinstallation blockiert{}, Grund:", + "continue": "Installation fortsetzen?", + "strict_mode_doc": "Das Laden von Modulen mit jeder Methode blockieren (lm/dlm erlaubt), wenn die KI-API nicht verfügbar ist oder das Modul verdächtig ist. Beim Neustart gilt dies auch für bereits installierte Module.", + "nvidia_api_key_doc": "API-Schlüssel von build.nvidia.com, der für KI-Prüfungen verwendet wird. Wenn nicht angegeben, wird ein öffentlicher Schlüssel von GitHub verwendet." + } + + strings_jp = { + "lang": "Japanese", + "_cls_doc": "AIでインストールされるモジュールを自動チェックするモジュール。", + "unavailable": "AIモジュール{}のチェックが利用できません。", + "suspicious": "AIが疑わしいモジュールのインストールを中断しました{}、理由:", + "blocked": "AIがモジュールのインストールをブロックしました{}、理由:", + "continue": "インストールを続行しますか?", + "strict_mode_doc": "AI APIが利用できない場合や疑わしいモジュールの場合、すべての方法でモジュールの読み込みをブロックします(lm/dlmは許可)。再起動時にはインストール済みモジュールにも適用されます。", + "nvidia_api_key_doc": "build.nvidia.com のAPIキー。AIチェックに使用されます。指定しない場合は、GitHubのパブリックキーが使用されます。" + } + + strings_tr = { + "lang": "Turkish", + "_cls_doc": "Kurulan modülleri yapay zeka ile otomatik kontrol eden modül.", + "unavailable": "Yapay zeka modül{} kontrolü kullanılamıyor.", + "suspicious": "Yapay zeka şüpheli bir modülün kurulumunu durdurdu{}, sebep:", + "blocked": "Yapay zeka modül kurulumunu engelledi{}, sebep:", + "continue": "Kuruluma devam edilsin mi?", + "strict_mode_doc": "AI API kullanılamıyorsa veya modül şüpheliyse, tüm yöntemlerle modül yüklenmesini engelle (lm/dlm izinli). Yeniden başlatmada zaten kurulu modüller için de geçerlidir.", + "nvidia_api_key_doc": "Yapay zeka kontrolleri için kullanılan build.nvidia.com API anahtarı. Belirtilmezse GitHub'daki genel anahtar kullanılacaktır." + } + + strings_uz = { + "lang": "Uzbekistan", + "_cls_doc": "O'rnatilayotgan modullarni AI orqali avtomatik tekshiruvchi modul.", + "unavailable": "AI modul{} tekshiruvi mavjud emas.", + "suspicious": "AI shubhali modul o'rnatilishini to'xtatdi{}, sabab:", + "blocked": "AI modul o'rnatilishini blokladi{}, sabab:", + "continue": "O'rnatishni davom ettirasizmi?", + "strict_mode_doc": "AI API mavjud bo'lmasa yoki modul shubhali bo'lsa, barcha usullar bilan modul yuklashni bloklash (lm/dlm ruxsat etilgan). Qayta ishga tushirishda allaqachon o'rnatilgan modullarga ham ta'sir qiladi.", + "nvidia_api_key_doc": "build.nvidia.com API kaliti, AI orqali tekshirish uchun ishlatiladi. Agar ko'rsatmasangiz, GitHub-dan umumiy kalit ishlatiladi." + } + + strings_kz = { + "lang": "Kazakhstan", + "_cls_doc": "Орнатылатын модульдерді ЖИ арқылы автоматты тексеретін модуль.", + "unavailable": "AI модуль{} тексеру қолжетімсіз.", + "suspicious": "AI күдікті модульді орнатуды тоқтатты{}, себебі:", + "blocked": "AI модульді орнатуды бұғаттады{}, себебі:", + "continue": "Орнатуды жалғастырасыз ба?", + "strict_mode_doc": "AI API қолжетімсіз болса немесе модуль күдікті болса, барлық әдістермен модуль жүктеуді бұғаттау (lm/dlm рұқсат етілген). Қайта іске қосқанда орнатылған модульдерге де қолданылады.", + "nvidia_api_key_doc": "build.nvidia.com API кілті, ЖИ арқылы тексеру үшін қолданылады. Егер оны көрсетпесеңіз, GitHub-тан ортақ кілт пайдаланылады." + } + + def __init__(self): + self.config = loader.ModuleConfig( + loader.ConfigValue( + "strict_mode", + False, + lambda: self.strings("strict_mode_doc"), + validator=loader.validators.Boolean(), + ), + loader.ConfigValue( + "nvidia_api_key", + "", + lambda: self.strings("nvidia_api_key_doc"), + validator=loader.validators.Hidden(), + ) + ) + self.tasks = {} + self.oreg = None + self.oload = None + + async def client_ready(self, client, db): + self.__origin__ = "" + self.core = self.lookup("loader") + self.modules = self.core.allmodules + self.restore_hooks() + self.patch() + + async def on_unload(self): + self.unpatch() + + def _render_prompt(self, prompt, **values): + rendered = prompt + for key, value in values.items(): + rendered = rendered.replace("{" + key + "}", str(value)) + return rendered + + def _split_code(self, code): + chunk_size = 180000 + if len(code) <= chunk_size: + return [code] + + chunks = [] + current =[] + current_len = 0 + + for line in code.splitlines(keepends=True): + if current and current_len + len(line) > chunk_size: + chunks.append("".join(current)) + current =[] + current_len = 0 + + if len(line) > chunk_size: + if current: + chunks.append("".join(current)) + current =[] + current_len = 0 + for i in range(0, len(line), chunk_size): + chunks.append(line[i:i + chunk_size]) + continue + + current.append(line) + current_len += len(line) + + if current: + chunks.append("".join(current)) + + return chunks or [code] + + def _parse_ai_json(self, raw_text): + raw_text = (raw_text or "").strip() + if not raw_text: + return None + + try: + parsed = json.loads(raw_text) + if isinstance(parsed, dict): + return parsed + except Exception: + pass + + match = re.search(r"\{[\s\S]*\}", raw_text) + if not match: + return None + + try: + parsed = json.loads(match.group()) + except Exception: + return None + + return parsed if isinstance(parsed, dict) else None + + async def _fetch_prompt(self, session, url): + async with session.get(url, timeout=10) as resp: + if resp.status != 200: + return None + prompt = (await resp.text()).strip() + return prompt or None + + async def _get_prompts(self, session): + main_prompt = await self._fetch_prompt(session, "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/prompts/main.txt") + chunk_prompt = await self._fetch_prompt(session, "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/prompts/chank.txt") + final_prompt = await self._fetch_prompt(session, "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/prompts/final.txt") + if not main_prompt or not chunk_prompt or not final_prompt: + return None + return { + "main": main_prompt, + "chunk": chunk_prompt, + "final": final_prompt, + } + + async def _nvidia_request(self, session, api_key, system_prompt, user_prompt): + async with session.post( + "https://integrate.api.nvidia.com/v1/chat/completions", + headers={"Authorization": f"Bearer {api_key}"}, + json={ + "model": "qwen/qwen3-coder-480b-a35b-instruct", + "messages":[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt}, + ], + "temperature": 0.4, + "max_tokens": 1000, + }, + timeout=180, + ) as resp: + if resp.status != 200: + return None + data = await resp.json() + choices = data.get("choices") or[] + if not choices: + return None + return self._parse_ai_json(choices[0].get("message", {}).get("content", "")) + + async def _local_ai_check(self, session, code, lang, api_key): + prompts = await self._get_prompts(session) + if not prompts: + return None + + chunks = self._split_code(code) + if len(chunks) == 1: + prompt = self._render_prompt(prompts["main"], lang=lang) + return await self._nvidia_request( + session, + api_key, + prompt, + f"Analyze this module:\n\n```python\n{code}\n```", + ) + + total = len(chunks) + findings =[] + + for index, chunk in enumerate(chunks, start=1): + previous_context = "; ".join( + f"Part {i}: {finding}" + for i, finding in enumerate(findings, start=1) + if finding + ) or "Previous parts: no issues found so far." + + chunk_prompt = self._render_prompt( + prompts["chunk"], + total=total, + current=index, + previous_context=previous_context, + lang=lang, + ) + chunk_result = await self._nvidia_request( + session, + api_key, + chunk_prompt, + f"Part {index}/{total}:\n\n```python\n{chunk}\n```", + ) + if not chunk_result: + return None + + chunk_verdict = str(chunk_result.get("chunk_verdict", "CLEAN")).lower() + chunk_finding = str(chunk_result.get("findings", "") or "") + + if chunk_verdict == "blocked": + findings_text = "\n".join( + f"- Part {i}: {finding}" + for i, finding in enumerate(findings, start=1) + if finding + ) + if chunk_finding: + findings_text = f"{findings_text}\n- Part {index}: {chunk_finding}".strip() + + final_prompt = self._render_prompt( + prompts["final"], + total=total, + findings=findings_text or "No prior findings.", + lang=lang, + ) + return await self._nvidia_request( + session, + api_key, + final_prompt, + "Give the final verdict based on all findings.", + ) + + findings.append(chunk_finding if chunk_verdict != "clean" else "") + + findings_text = "\n".join( + f"- Part {i}: {finding}" + for i, finding in enumerate(findings, start=1) + if finding + ) or "All parts: no issues found." + + final_prompt = self._render_prompt( + prompts["final"], + total=total, + findings=findings_text, + lang=lang, + ) + return await self._nvidia_request( + session, + api_key, + final_prompt, + "Give the final verdict based on all findings.", + ) + + async def check(self, code): + try: + lang = self.strings("lang") or "en" + module_hash = hashlib.sha256(code.encode("utf-8")).hexdigest() + + db_cache = self.get("cache", {}) + if module_hash in db_cache: + cached = db_cache[module_hash] + if cached.get("level") == "safe": + return True + return cached + + async with aiohttp.ClientSession() as session: + api_keys = await self._get_api_keys(session) + for api_key in api_keys: + parsed = await self._local_ai_check(session, code, lang, api_key) + if not isinstance(parsed, dict): + continue + + verdict = str(parsed.get("verdict", "BLOCKED")).lower() + if verdict not in {"safe", "suspicious", "blocked"}: + verdict = "blocked" + summary = str(parsed.get("summary", "") or "") + + result = {"level": verdict if verdict != "safe" else "safe"} + if verdict != "safe": + result["reason"] = summary + + db_cache[module_hash] = result + self.set("cache", db_cache) + + if result["level"] == "safe": + return True + return result + + return False + except Exception: + return False + + async def _get_api_keys(self, session): + configured_key = self.config["nvidia_api_key"].strip() + if configured_key: + return [configured_key] + + try: + async with session.get( + "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/api_keys.txt", + timeout=10, + ) as resp: + if resp.status != 200: + return[] + raw_keys = (await resp.text()).strip() + except Exception: + return [] + + return[key.strip() for key in raw_keys.split(",") if key.strip()] + + def format(self, state, reason="", link=""): + link_part = f' ({utils.escape_html(link)})' if link else "" + if state == "unavailable": + return f'{self.strings("unavailable").format(link_part)}\n{self.strings("continue")}' + if state == "suspicious": + return f'{self.strings("suspicious").format(link_part)}\n
{reason}
\n{self.strings("continue")}' + return f'{self.strings("blocked").format(link_part)}\n
{reason}
' + + def buttons(self, task): + return [[ + {"text": "✓", "callback": self.confirm, "args": (task, "yes")}, + {"text": "✗", "callback": self.confirm, "args": (task, "no")} + ]] + + def closure_var(self, func, name): + raw = getattr(func, "__func__", func) + code = getattr(raw, "__code__", None) + closure = getattr(raw, "__closure__", None) + if not code or not closure or name not in code.co_freevars: + return None + + with suppress(Exception): + return closure[code.co_freevars.index(name)].cell_contents + + return None + + def restore_hooks(self): + with suppress(Exception): + inst_reg = getattr(self.modules, "register_module") + owner = getattr(inst_reg, "__self__", None) + if ( + owner + and owner is not self + and owner.__class__.__name__ == self.__class__.__name__ + ): + original = getattr(owner, "oreg", None) + if original: + if getattr(original, "__self__", None) is None: + self.modules.register_module = original.__get__( + self.modules, + self.modules.__class__, + ) + else: + self.modules.register_module = original + + with suppress(Exception): + inst_load = getattr(self.core, "load_module") + raw = getattr(inst_load, "__func__", inst_load) + if "FSecurity.patch..load" in getattr(raw, "__qualname__", ""): + original = self.closure_var(raw, "original") + if original: + if getattr(original, "__self__", None) is None: + self.core.load_module = original.__get__( + self.core, + self.core.__class__, + ) + else: + self.core.load_module = original + + def patch(self): + if not self.oreg: + self.oreg = getattr(self.modules, "register_module") + if not self.oload: + self.oload = self.core.load_module + + original = self.oload + + async def load(_, *args, **kwargs): + base = utils.answer + + async def answer(message, response, *a, **k): + if isinstance(response, str) and "😖" in response: + body = response.split("😖", 1)[1].strip() + if body in {"", "", " "}: + with suppress(Exception): + if hasattr(message, "delete"): + await message.delete() + return message + + if body.startswith("") and body.endswith(""): + decoded = html.unescape(body[3:-4]) + response = response.split("😖", 1)[0] + f'😖 {decoded}' if decoded else response.split("😖", 1)[0] + '😖' + + try: + return await base(message, response, *a, **k) + except Exception: + with suppress(Exception): + return await self._client.send_message( + utils.get_chat_id(message), + response, + reply_to=getattr(message, "reply_to_msg_id", None), + buttons=k.get("reply_markup"), + ) + + return message + + utils.answer = answer + try: + if getattr(original, "__self__", None) is None: + return await original(_, *args, **kwargs) + return await original(*args, **kwargs) + finally: + if utils.answer is answer: + utils.answer = base + + self.core.load_module = load.__get__(self.core, self.core.__class__) + self.modules.register_module = self.register + + def unpatch(self): + if self.oreg: + self.modules.register_module = self.oreg + if getattr(self, "core", None) and self.oload: + self.core.load_module = self.oload + + def context(self): + frame = sys._getframe() + msg = None + fmsg = None + is_dlm_lm = False + + while frame: + locals = frame.f_locals + if ( + frame.f_code.co_name == "load_module" + and locals.get("self") is self.core + and 'message' in locals + and hasattr(locals['message'], 'edit') + ): + if not msg: + msg = locals['message'] + fmsg = locals.get('msg') + + if frame.f_code.co_name in {"dlmod", "loadmod"}: + is_dlm_lm = True + if not msg and 'message' in locals and hasattr(locals['message'], 'edit'): + msg = locals['message'] + + if frame.f_code.co_name == "download_and_install": + if not msg and 'message' in locals and hasattr(locals['message'], 'edit'): + msg = locals['message'] + + frame = frame.f_back + + return msg, fmsg, is_dlm_lm + + def target_chat(self, msg=None, fmsg=None): + if not msg: + return None + + if not fmsg: + return msg + + with suppress(Exception): + target = copy.copy(msg) + target.reply_to_msg_id = fmsg.id + return target + + return None + + async def call_oreg(self, spec, name, origin="", save_fs=False): + if getattr(self.oreg, "__self__", None) is None: + return await self.oreg(self.modules, spec, name, origin, save_fs=save_fs) + return await self.oreg(spec, name, origin, save_fs=save_fs) + + async def register(self, spec, name, origin="", save_fs=False): + if origin != "": + code = "" + + if hasattr(spec.loader, "data") and spec.loader.data: + code = spec.loader.data + if isinstance(code, bytes): + code = code.decode("utf-8", errors="ignore") + elif origin and origin.endswith(".py"): + with suppress(Exception): + with open(origin, "r", encoding="utf-8") as f: + code = f.read() + + if code: + check = await self.check(code) + + if check is not True: + msg, fmsg, is_dlm_lm = self.context() + target = self.target_chat(msg, fmsg) + + if isinstance(check, dict): + status = check.get("level", "blocked") + reason = check.get("reason", "") + else: + status = "unavailable" + reason = "" + + link = origin if origin.startswith("http") else "" + + if status == "blocked": + if msg and target: + raise loader.LoadError(self.format("blocked", reason, link)) + raise loader.LoadError("") + + should_block = is_dlm_lm or self.config["strict_mode"] + + if should_block and not (msg and target): + raise loader.LoadError("") + + if should_block and msg and target: + task = str(uuid.uuid4()) + event = asyncio.Event() + self.tasks[task] = {"event": event, "decision": False} + + try: + form = await self.inline.form( + text=self.format(status, reason, link), + message=target, + reply_markup=self.buttons(task) + ) + + if not form: + raise loader.LoadError(reason) + + await asyncio.wait_for(event.wait(), timeout=180.0) + + if not self.tasks.pop(task)["decision"]: + with suppress(Exception): + await form.delete() + raise loader.LoadError("") + + except asyncio.TimeoutError: + self.tasks.pop(task, None) + with suppress(Exception): + await form.delete() + raise loader.LoadError("") + except loader.LoadError: + raise + except Exception: + raise loader.LoadError("") + + return await self.call_oreg(spec, name, origin, save_fs=save_fs) + + async def confirm(self, call, task, action): + if task in self.tasks: + self.tasks[task]["decision"] = (action == "yes") + self.tasks[task]["event"].set() + with suppress(Exception): + await call.delete() diff --git a/Fixyres/FModules/LFSecurity.py b/Fixyres/FModules/LFSecurity.py new file mode 100644 index 0000000..de8d65e --- /dev/null +++ b/Fixyres/FModules/LFSecurity.py @@ -0,0 +1,87 @@ +__version__ = (1, 0, 0) + +# meta developer: @NFModules +# meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/banner.png +# meta fhsdesc: security, guard, antiscam, antivirus +# 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 aiohttp +import os +from .. import loader + + +@loader.tds +class FSecurity(loader.Module): + """Module for automatic AI-based security checks of installed modules.""" + + strings = { + "name": "FSecurity" + } + + strings_ru = { + "_cls_doc": "Модуль для автоматической проверки устанавливаемых модулей через ИИ." + } + + strings_ua = { + "_cls_doc": "Модуль для автоматичної перевірки встановлюваних модулів через ШІ." + } + + strings_de = { + "_cls_doc": "Modul zur automatischen Prüfung installierter Module mit KI." + } + + strings_jp = { + "_cls_doc": "AIでインストールされるモジュールを自動チェックするモジュール。" + } + + strings_tr = { + "_cls_doc": "Kurulan modülleri yapay zeka ile otomatik kontrol eden modül." + } + + strings_uz = { + "_cls_doc": "O'rnatilayotgan modullarni AI orqali avtomatik tekshiruvchi modul." + } + + strings_kz = { + "_cls_doc": "Орнатылатын модульдерді ЖИ арқылы автоматты тексеретін модуль." + } + + async def client_ready(self, client, db): + core = self.lookup("loader") + + try: + async with aiohttp.ClientSession() as session: + async with session.get( + "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/FSecurity.py", + timeout=aiohttp.ClientTimeout(total=15), + ) as resp: + if resp.status != 200: + return + source = await resp.text() + except Exception: + return + + target = os.path.join( + os.path.dirname(loader.__file__), + "modules", + "FSecurity.py", + ) + + try: + with open(target, "w", encoding="utf-8") as f: + f.write(source) + except Exception: + return + + await core.unload_module("FSecurity") + try: + await core.load_module(source, None, "FSecurity", target, save_fs=False) + except Exception: + pass diff --git a/Fixyres/FModules/SCD.py b/Fixyres/FModules/SCD.py index 7b3a48b..4c0fc33 100644 --- a/Fixyres/FModules/SCD.py +++ b/Fixyres/FModules/SCD.py @@ -7,7 +7,7 @@ __version__ = (1, 0, 0) # 🔑 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 +# meta developer: @NFModules # requires: curl_cffi diff --git a/Fixyres/FModules/akinator.py b/Fixyres/FModules/akinator.py index 3aa0caf..952e12b 100644 --- a/Fixyres/FModules/akinator.py +++ b/Fixyres/FModules/akinator.py @@ -7,7 +7,7 @@ __version__ = (1, 1, 0) # 🔑 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 developer: @NFModules # meta fhsdesc: game, funny, guess, question game # requires: curl_cffi diff --git a/Fixyres/FModules/assets/FSecurity/api_keys.txt b/Fixyres/FModules/assets/FSecurity/api_keys.txt new file mode 100644 index 0000000..b813928 --- /dev/null +++ b/Fixyres/FModules/assets/FSecurity/api_keys.txt @@ -0,0 +1 @@ +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 diff --git a/Fixyres/FModules/assets/FSecurity/banner.png b/Fixyres/FModules/assets/FSecurity/banner.png new file mode 100644 index 0000000..95adf82 Binary files /dev/null and b/Fixyres/FModules/assets/FSecurity/banner.png differ diff --git a/Fixyres/FModules/assets/FSecurity/prompts/chank.txt b/Fixyres/FModules/assets/FSecurity/prompts/chank.txt new file mode 100644 index 0000000..505cf16 --- /dev/null +++ b/Fixyres/FModules/assets/FSecurity/prompts/chank.txt @@ -0,0 +1,25 @@ +You must strictly follow these rules with no exceptions. + +Analyze only part {current}/{total} of a Hikka userbot Python module. Do NOT give a final verdict for the whole module. +Previous context: {previous_context} + +BLOCKED: encrypted/obfuscated code (base64, marshal, zlib, rot13, encoded exec, or any technique hiding real logic), account deletion, mass scam/spam/ads to all chats on load, session/auth_key/2FA exfiltration, bulk message/dialog dump to external destination, string "FSecurity" (if found → findings must be ONLY: "Attempted interaction with FSecurity." translated to {lang}, nothing else). +SUSPICIOUS: watcher/scheduler/client_ready auto-installing modules without owner confirmation, download + exec of remote Python code without confirmation, runtime pip install or library download, third-party OAuth redirect. +CLEAN: no security issue in this chunk. + +Tie-breaking: BLOCKED vs SUSPICIOUS → SUSPICIOUS. SUSPICIOUS vs CLEAN → CLEAN. +@loader.inline_handler, @loader.command, async def NAMEcmd, async def NAME_inline_handler = owner-only by default = not a threat. +Owner-triggered exec/eval/shell = not a threat. +A command (any function decorated with @loader.command, named NAMEcmd, or accessible only to the owner) that executes arbitrary code, runs shell commands, evaluates expressions, or calls exec/eval on owner-provided input = always CLEAN, never SUSPICIOUS. This is a standard userbot feature. + +Respond ONLY with valid JSON: +{"chunk_verdict":"CLEAN|SUSPICIOUS|BLOCKED","findings":"..."} + +Findings rules (when not CLEAN): +- Write in {lang}. Max 1000 chars. +- Technical analysis for reading, not a reply. No "I found", no "you should". Third person only. +- Do NOT mention which rule was triggered. Just describe what the code does. +- Only the key threats in this chunk. Reference approximate line numbers within the chunk. +- Use text for code references: function names, variables, URLs, string literals. +- For obfuscation chains, wrap the whole chain in one block: base64.b64decode → zlib.decompress → exec. +- If CLEAN → findings must be empty string "". diff --git a/Fixyres/FModules/assets/FSecurity/prompts/final.txt b/Fixyres/FModules/assets/FSecurity/prompts/final.txt new file mode 100644 index 0000000..0c61807 --- /dev/null +++ b/Fixyres/FModules/assets/FSecurity/prompts/final.txt @@ -0,0 +1,29 @@ +You must strictly follow these rules with no exceptions. + +A Hikka userbot module was split into {total} parts. Chunk findings: +{findings} + +Give the final verdict for the entire module based on all findings above. + +BLOCKED: encrypted/obfuscated code, account deletion, mass scam/spam on load, session/auth_key theft, bulk message dump, string "FSecurity" (if found → summary must be ONLY: "Attempted interaction with FSecurity." translated to {lang}, nothing else). +SUSPICIOUS: auto-install modules without confirmation, remote code download + exec without confirmation, runtime pip/library install, third-party OAuth redirect. +SAFE: no real security issue across all parts. + +Auto-install = SUSPICIOUS, never BLOCKED. +Tie-breaking: BLOCKED vs SUSPICIOUS → SUSPICIOUS. SUSPICIOUS vs SAFE → SAFE. +@loader.inline_handler, @loader.command, async def NAMEcmd, async def NAME_inline_handler = owner-only by default = not a threat. +Owner-triggered exec/eval/shell = not a threat. +A command (any function decorated with @loader.command, named NAMEcmd, or accessible only to the owner) that executes arbitrary code, runs shell commands, evaluates expressions, or calls exec/eval on owner-provided input = always SAFE, never SUSPICIOUS. This is a standard userbot feature. + +Respond ONLY with valid JSON: +{"verdict":"SAFE|SUSPICIOUS|BLOCKED","summary":"..."} + +Summary rules (when not SAFE): +- Write in {lang}. Max 1000 chars. +- Combine the most important findings into one coherent technical analysis. +- This is a report for reading, NOT a reply to a person. No "I found", no "you should". Third person only. +- Do NOT mention which rule was triggered or explain criteria. Just describe what the code does. +- Only the key threats. Reference line numbers from findings where available. +- Use text for all code references: function names, variables, URLs, string literals. +- For obfuscation, show the full chain in one block: base64.b64decode → zlib.decompress → exec. +- If SAFE → summary must be empty string "". diff --git a/Fixyres/FModules/assets/FSecurity/prompts/main.txt b/Fixyres/FModules/assets/FSecurity/prompts/main.txt new file mode 100644 index 0000000..a8e6b85 --- /dev/null +++ b/Fixyres/FModules/assets/FSecurity/prompts/main.txt @@ -0,0 +1,37 @@ +You must strictly follow these classification rules with no exceptions. + +Classify a Hikka userbot Python module as BLOCKED, SUSPICIOUS, or SAFE. + +BLOCKED (any single match): +- Code is encrypted or obfuscated (base64, marshal, zlib, rot13, compile+exec of encoded data, or any technique that hides real logic). +- Attempts to delete Telegram account (DeleteAccountRequest, client.delete_account, or equivalent). +- On load (client_ready, __init__) automatically sends scam, spam, or ads to all chats/dialogs/contacts without owner action. +- Steals and sends session string, auth_key, or 2FA password anywhere outside the device. +- Collects and forwards all messages or dialogs to any external destination. +- Contains the string "FSecurity" → summary must be ONLY: "Attempted interaction with FSecurity." translated to {lang}. Nothing else, no extra text. + +SUSPICIOUS (any single match, only if BLOCKED did not trigger): +- Watcher, scheduler, or client_ready auto-installs modules from any URL without per-action owner confirmation. +- Downloads and executes remote Python code (exec/eval on fetched content) without owner confirmation. +- Installs pip packages or downloads Python libraries at runtime from the internet. +- OAuth or auth flow redirected through a non-official third-party domain. + +SAFE: everything that does not match any rule above. +- Owner-triggered exec/eval/shell = always SAFE. +- A command (any function decorated with @loader.command, named NAMEcmd, or accessible only to the owner) that executes arbitrary code, runs shell commands, evaluates expressions, or calls exec/eval on owner-provided input = always SAFE, never SUSPICIOUS. This is a standard feature of userbots and poses no threat. +- @loader.inline_handler, @loader.command, async def NAMEcmd, async def NAME_inline_handler = owner-only by default (no public access without explicit permission) = SAFE. + +Tie-breaking: BLOCKED vs SUSPICIOUS → SUSPICIOUS. SUSPICIOUS vs SAFE → SAFE. + +Respond ONLY with valid JSON: +{"verdict":"SAFE|SUSPICIOUS|BLOCKED","summary":"..."} + +Summary rules (when not SAFE): +- Write in {lang}. Max 1000 chars. +- This is a technical analysis meant to be read, NOT a reply to a person. Never write "I found", "you should", "I recommend". Write in third person. +- Do NOT mention which rule was triggered or explain the classification criteria. Just describe what the code does. +- Point out ONLY the key threats. Do NOT describe what the module does overall or list safe parts. +- Reference the approximate line number where dangerous code appears: "line NN —". +- Use text for every code reference: function names, variables, URLs, string literals. +- For obfuscation, show the full decoding chain inside one block: base64.b64decode → zlib.decompress → marshal.loads → exec. +- If SAFE → summary must be empty string "". diff --git a/Midga3/Heroku-modules/PingEmoji.py b/Midga3/Heroku-modules/PingEmoji.py index 91276b3..22d7e48 100644 --- a/Midga3/Heroku-modules/PingEmoji.py +++ b/Midga3/Heroku-modules/PingEmoji.py @@ -1,9 +1,8 @@ #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) +__version__ = (1, 1, 2) import logging import aiohttp @@ -17,13 +16,20 @@ class PingEmoji(loader.Module): strings = { "name": "PingEmoji" } - + def __init__(self): + self.config = loader.ModuleConfig( + loader.ConfigValue( + "emoji", + "🔴", + "Ping Emoji", + ) + ) 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 "🔴" + return self.config['emoji'] else: - return "" \ No newline at end of file + return "" diff --git a/fiksofficial/python-modules/IwaAnimation.py b/fiksofficial/python-modules/IwaAnimation.py new file mode 100644 index 0000000..1bb8f65 --- /dev/null +++ b/fiksofficial/python-modules/IwaAnimation.py @@ -0,0 +1,204 @@ +# ______ ___ ___ _ _ +# ____ | ___ \ | \/ | | | | | +# / __ \| |_/ / _| . . | ___ __| |_ _| | ___ +# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \ +# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/ +# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___| +# \____/ __/ | +# |___/ + +# На модуль распространяется лицензия "GNU General Public License v3.0" +# https://github.com/all-licenses/GNU-General-Public-License-v3.0 + +# meta developer: @pymodule + +import asyncio +import os +import toml + +from .. import loader, utils +from herokutl.tl.types import Message + + +@loader.tds +class IwaAnimation(loader.Module): + """Frame-by-frame text animations loaded from .anim TOML files""" + + strings = { + "name": "IwaAnimation", + "err_no_reply": "{e} Reply to a .anim file.", + "err_not_anim": "{e} File must have .anim extension.", + "err_bad_format": "{e} Invalid file format (missing name or cmd).", + "err_no_frames": "{e} No frames found in the file.", + "err_not_found": "{e} Animation not found.", + "err_no_cmd": "{e} Specify a command name.", + "err_generic": "{e} Error:\n\n{exc}", + "ok_loaded": "{s} Loaded: {name}\nCommand: .anim {cmd}", + "ok_deleted": "{s} Deleted.", + "list_header": "
Animations:
\n\n
", + "list_row": "• {cmd} — {name} ({n} frames)\n", + "list_footer": "
", + "list_empty": "{e} No animations.", + } + + strings_ru = { + "name": "IwaAnimation", + "err_no_reply": "{e} Ответьте на .anim файл.", + "err_not_anim": "{e} Файл должен быть формата .anim", + "err_bad_format": "{e} Неверный формат файла (нет name или cmd).", + "err_no_frames": "{e} В файле нет кадров.", + "err_not_found": "{e} Анимация не найдена.", + "err_no_cmd": "{e} Укажи команду.", + "err_generic": "{e} Ошибка:\n\n{exc}", + "ok_loaded": "{s} Загружено: {name}\nКоманда: .anim {cmd}", + "ok_deleted": "{s} Удалено.", + "list_header": "
Анимации:
\n\n
", + "list_row": "• {cmd} — {name} ({n} кадров)\n", + "list_footer": "
", + "list_empty": "{e} Нет анимаций.", + } + + _E = "" + _S = "" + + async def client_ready(self): + if not self.db.get("IwaAnimations", "anims", False): + self.db.set("IwaAnimations", "anims", {}) + + @loader.command(ru_doc="- Загрузить анимацию из полученного .anim файла") + async def lanimcmd(self, message: Message): + """- Load animation from a replied .anim file""" + reply = await message.get_reply_message() + if not reply or not reply.document: + return await utils.answer( + message, self.strings["err_no_reply"].format(e=self._E) + ) + + filename = reply.file.name or "" + if not filename.endswith(".anim"): + return await utils.answer( + message, self.strings["err_not_anim"].format(e=self._E) + ) + + tmp = "anim_load.anim" + await reply.download_media(tmp) + try: + data = toml.load(tmp) + name = data.get("name") + cmd = data.get("cmd") + delay = float(data.get("time", 0.5)) + + if not name or not cmd: + return await utils.answer( + message, self.strings["err_bad_format"].format(e=self._E) + ) + + frames = [] + for key in sorted( + (k for k in data if str(k).isdigit()), key=lambda x: int(x) + ): + frame = data[key] + frames.append("\n".join(frame) if isinstance(frame, list) else str(frame)) + + if not frames: + return await utils.answer( + message, self.strings["err_no_frames"].format(e=self._E) + ) + + anims = self.db.get("IwaAnimations", "anims", {}) + anims[cmd] = {"name": name, "frames": frames, "delay": delay} + self.db.set("IwaAnimations", "anims", anims) + + await utils.answer( + message, + self.strings["ok_loaded"].format(s=self._S, name=name, cmd=cmd), + ) + except Exception as exc: + await utils.answer( + message, self.strings["err_generic"].format(e=self._E, exc=exc) + ) + finally: + if os.path.exists(tmp): + os.remove(tmp) + + @loader.command(ru_doc=" - Воспроизвести загруженную анимацию") + async def animcmd(self, message: Message): + """ - Play a loaded animation""" + cmd = utils.get_args_raw(message) + if not cmd: + return await utils.answer( + message, self.strings["err_no_cmd"].format(e=self._E) + ) + + anims = self.db.get("IwaAnimations", "anims", {}) + if cmd not in anims: + return await utils.answer( + message, self.strings["err_not_found"].format(e=self._E) + ) + + anim = anims[cmd] + msg = await utils.answer(message, anim["frames"][0]) + try: + for frame in anim["frames"][1:]: + await asyncio.sleep(anim["delay"]) + await msg.edit(frame) + except Exception: + pass + + @loader.command(ru_doc="- Отобразить список всех загруженных анимаций") + async def animscmd(self, message: Message): + """- List all loaded animations""" + anims = self.db.get("IwaAnimations", "anims", {}) + if not anims: + return await utils.answer( + message, self.strings["list_empty"].format(e=self._E) + ) + + text = self.strings["list_header"] + for cmd, data in anims.items(): + text += self.strings["list_row"].format( + cmd=cmd, name=data["name"], n=len(data["frames"]) + ) + text += self.strings["list_footer"] + await utils.answer(message, text) + + @loader.command(ru_doc=" - Удалить анимацию") + async def delanimcmd(self, message: Message): + """ - Delete an animation""" + cmd = utils.get_args_raw(message) + anims = self.db.get("IwaAnimations", "anims", {}) + + if cmd not in anims: + return await utils.answer( + message, self.strings["err_not_found"].format(e=self._E) + ) + + anims.pop(cmd) + self.db.set("IwaAnimations", "anims", anims) + await utils.answer(message, self.strings["ok_deleted"].format(s=self._S)) + + @loader.command(ru_doc=" - Экспорт анимации в файл .anim") + async def dumpanimcmd(self, message: Message): + """ - Export an animation to a .anim file""" + cmd = utils.get_args_raw(message) + anims = self.db.get("IwaAnimations", "anims", {}) + + if cmd not in anims: + return await utils.answer( + message, self.strings["err_not_found"].format(e=self._E) + ) + + anim = anims[cmd] + data = {"name": anim["name"], "cmd": cmd, "time": str(anim["delay"])} + for i, frame in enumerate(anim["frames"], start=1): + data[str(i)] = frame.split("\n") + + file = f"{cmd}.anim" + try: + with open(file, "w", encoding="utf-8") as f: + toml.dump(data, f) + await message.delete() + await self._client.send_file(message.to_id, file) + finally: + if os.path.exists(file): + os.remove(file) \ No newline at end of file diff --git a/fiksofficial/python-modules/full.txt b/fiksofficial/python-modules/full.txt index 0d35b2a..5855795 100644 --- a/fiksofficial/python-modules/full.txt +++ b/fiksofficial/python-modules/full.txt @@ -26,4 +26,5 @@ aigenuser github stream placeholders+ -PyInstall \ No newline at end of file +PyInstall +IwaAnimation \ No newline at end of file diff --git a/fiksofficial/python-modules/placeholders+.py b/fiksofficial/python-modules/placeholders+.py index a58f96b..db46944 100644 --- a/fiksofficial/python-modules/placeholders+.py +++ b/fiksofficial/python-modules/placeholders+.py @@ -26,7 +26,7 @@ from typing import Optional, Dict, Any from collections import OrderedDict from .. import loader, utils, validators -from herokutl.tl.functions.users import GetFullUserRequest +from telethon.tl.functions.users import GetFullUserRequest from herokutl.tl.functions.payments import GetStarsStatusRequest logger = logging.getLogger(__name__) @@ -118,21 +118,23 @@ class PlaceholdersMod(loader.Module): ) self.cache = LRUCache(max_size=100, ttl=300) - async def client_ready(self): + async def client_ready(self, client, db): + self._client = client self.session = aiohttp.ClientSession() - self.me = await self._client.get_me() - self.full_me = await self._client(GetFullUserRequest(self.me)) + self.me = await client.get_me() + self.full_me = await client(GetFullUserRequest(self.me)) try: - stars_status = await self._client(GetStarsStatusRequest(entity="me")) + stars_status = await self._client(GetStarsStatusRequest(entity='me')) self.stars_balance = stars_status.balance - except Exception: + except: self.stars_balance = 0 self.tz = timezone(timedelta(hours=self.config["timezone"])) self.weekdays_ru = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"] + # Регистрация плейсхолдеров self._register_placeholders() def _register_placeholders(self): @@ -199,30 +201,18 @@ class PlaceholdersMod(loader.Module): utils.register_placeholder(name, func, desc) async def get_premium_check(self): - if not getattr(self.me, "premium", False): + if not self.me.premium: return "Нет Premium" - - # premium_until отсутствует в публичном MTProto API herokutl/Telethon — - # пробуем достать его, но не падаем если поля нет - until = None - try: - until = getattr(self.full_me.full_user, "premium_until", None) - # Иногда это datetime, иногда unix timestamp (int) - if isinstance(until, datetime): - until = until.timestamp() - except Exception: - until = None - - if not until: - return "✅ Premium активен" - - if until < time.time(): - return "⚠️ Премиум истёк" - + + until = self.full_me.full_user.premium_until + if not until or until < time.time(): + return "Премиум закончился" + end_date = datetime.fromtimestamp(until, tz=self.tz) days_left = (end_date.date() - datetime.now(self.tz).date()).days + formatted = end_date.strftime("%d.%m.%Y") - return f"✅ до {formatted} (ещё {days_left} дн.)" + return f"{formatted} (Осталось {days_left} дней)" async def get_username(self): return f"@{self.me.username}" if self.me.username else "Нет" @@ -245,8 +235,10 @@ class PlaceholdersMod(loader.Module): async def get_dc_id(self): return str(self.me.dc_id if hasattr(self.me, "dc_id") else "Неизвестно") - async def get_stars(self): - return f"{self.stars_balance:,}".replace(",", " ") if self.stars_balance else "0" + async def get_stars(self): + result = await self.client(GetStarsStatusRequest("me")) + stars = result.balance.amount if result and result.balance else 0 + return f"{stars:,}".replace(",", " ") if stars else "0" async def get_usd_to_rub(self): cache_key = "usd_rub" @@ -261,7 +253,7 @@ class PlaceholdersMod(loader.Module): result = f"1 USD ≈ {rate:.2f} RUB" self.cache.set(cache_key, result) return result - except Exception: + except: try: async with self.session.get("https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/usd.json") as resp: data = await resp.json() @@ -269,7 +261,7 @@ class PlaceholdersMod(loader.Module): result = f"1 USD ≈ {rate:.2f} RUB" self.cache.set(cache_key, result) return result - except Exception: + except: return "Курс USD недоступен" async def get_rub_to_usd(self): @@ -278,7 +270,7 @@ class PlaceholdersMod(loader.Module): try: rate = float(usd_rub.split("≈")[1].strip().split()[0]) return f"1 RUB ≈ {1/rate:.4f} USD" - except Exception: + except: pass return "Курс RUB недоступен" @@ -301,7 +293,7 @@ class PlaceholdersMod(loader.Module): result = f"1 TON ≈ {rate:.2f} RUB" self.cache.set(cache_key, result) return result - except Exception: + except: return "Курс TON недоступен" async def get_rub_to_ton(self): @@ -310,7 +302,7 @@ class PlaceholdersMod(loader.Module): try: rate = float(ton_rub.split("≈")[1].strip().split()[0]) return f"1 RUB ≈ {1/rate:.6f} TON" - except Exception: + except: pass return "Курс недоступен" @@ -327,7 +319,7 @@ class PlaceholdersMod(loader.Module): result = f"1 BTC ≈ {rate:,.0f} RUB" self.cache.set(cache_key, result) return result - except Exception: + except: return "Курс BTC недоступен" async def get_eth_to_rub(self): @@ -343,7 +335,7 @@ class PlaceholdersMod(loader.Module): result = f"1 ETH ≈ {rate:,.0f} RUB" self.cache.set(cache_key, result) return result - except Exception: + except: return "Курс ETH недоступен" async def get_stars_to_rub(self): @@ -373,7 +365,7 @@ class PlaceholdersMod(loader.Module): sent_gb = net.bytes_sent // (1024**3) recv_gb = net.bytes_recv // (1024**3) return f"↑ {sent_gb} GB │ ↓ {recv_gb} GB" - except Exception: + except: return "↑ 0 GB │ ↓ 0 GB" async def get_speedtest(self): @@ -405,7 +397,7 @@ class PlaceholdersMod(loader.Module): result = f"≈ {speed_mbps:.1f} Mbps" self.cache.set(cache_key, result) return result - except Exception: + except: continue return "Тест скорости недоступен" @@ -426,7 +418,7 @@ class PlaceholdersMod(loader.Module): used_gb = usage.used // (1024**3) total_gb = usage.total // (1024**3) return f"{used_gb} GB / {total_gb} GB ({percent:.1f}%)" - except Exception: + except: return "Диск недоступен" async def get_local_ip(self): @@ -436,7 +428,7 @@ class PlaceholdersMod(loader.Module): ip = s.getsockname()[0] s.close() return ip - except Exception: + except: return "Неизвестно" async def get_user_hostname(self): @@ -507,7 +499,7 @@ class PlaceholdersMod(loader.Module): } self.cache.set(cache_key, weather_data) return weather_data - except Exception: + except: pass default = { @@ -595,7 +587,7 @@ class PlaceholdersMod(loader.Module): result = f"🎵 {stats['playcount']} скробблов" self.cache.set(cache_key, result) return result - except Exception: + except: pass return "Статистика недоступна" @@ -637,14 +629,10 @@ class PlaceholdersMod(loader.Module): } self.cache.set(cache_key, result) return result - except Exception: + except: pass return None async def on_unload(self): - utils.unregister_placeholders(self.__class__.__name__) - try: - await self.session.close() - except Exception: - pass \ No newline at end of file + await self.session.close() \ No newline at end of file diff --git a/modules.json b/modules.json index 990a57f..9b248df 100644 --- a/modules.json +++ b/modules.json @@ -5289,6 +5289,139 @@ "has_on_unload": false, "class_cmd_names": {} }, + "fiksofficial/python-modules/IwaAnimation.py": { + "name": "IwaAnimation", + "description": "Frame-by-frame text animations loaded from .anim TOML files", + "cls_doc": {}, + "meta": { + "pic": null, + "banner": null, + "developer": "@pymodule" + }, + "commands": [ + { + "lanim": "- Load animation from a replied .anim file | (RU) - Загрузить анимацию из полученного .anim файла" + }, + { + "anim": " - Play a loaded animation | (RU) - Воспроизвести загруженную анимацию" + }, + { + "anims": "- List all loaded animations | (RU) - Отобразить список всех загруженных анимаций" + }, + { + "delanim": " - Delete an animation | (RU) - Удалить анимацию" + }, + { + "dumpanim": " - Export an animation to a .anim file | (RU) - Экспорт анимации в файл .anim" + } + ], + "new_commands": [ + { + "name": "lanim", + "original_name": "lanimcmd", + "description": { + "default": "- Load animation from a replied .anim file", + "ru": "- Загрузить анимацию из полученного .anim файла" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "anim", + "original_name": "animcmd", + "description": { + "default": " - Play a loaded animation", + "ru": " - Воспроизвести загруженную анимацию" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "anims", + "original_name": "animscmd", + "description": { + "default": "- List all loaded animations", + "ru": "- Отобразить список всех загруженных анимаций" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "delanim", + "original_name": "delanimcmd", + "description": { + "default": " - Delete an animation", + "ru": " - Удалить анимацию" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + }, + { + "name": "dumpanim", + "original_name": "dumpanimcmd", + "description": { + "default": " - Export an animation to a .anim file", + "ru": " - Экспорт анимации в файл .anim" + }, + "cmd_names": {}, + "aliases": [], + "usage": null, + "inline": false, + "is_inline_handler": false, + "decorators": [] + } + ], + "inline_handlers": [], + "strings": { + "name": "IwaAnimation", + "err_no_reply": "{e} Reply to a .anim file.", + "err_not_anim": "{e} File must have .anim extension.", + "err_bad_format": "{e} Invalid file format (missing name or cmd).", + "err_no_frames": "{e} No frames found in the file.", + "err_not_found": "{e} Animation not found.", + "err_no_cmd": "{e} Specify a command name.", + "err_generic": "{e} Error:\n\n{exc}", + "ok_loaded": "{s} Loaded: {name}\nCommand: .anim {cmd}", + "ok_deleted": "{s} Deleted.", + "list_header": "
Animations:
\n\n
", + "list_row": "• {cmd} — {name} ({n} frames)\n", + "list_footer": "
", + "list_empty": "{e} No animations.", + "name_ru": "IwaAnimation", + "err_no_reply_ru": "{e} Ответьте на .anim файл.", + "err_not_anim_ru": "{e} Файл должен быть формата .anim", + "err_bad_format_ru": "{e} Неверный формат файла (нет name или cmd).", + "err_no_frames_ru": "{e} В файле нет кадров.", + "err_not_found_ru": "{e} Анимация не найдена.", + "err_no_cmd_ru": "{e} Укажи команду.", + "err_generic_ru": "{e} Ошибка:\n\n{exc}", + "ok_loaded_ru": "{s} Загружено: {name}\nКоманда: .anim {cmd}", + "ok_deleted_ru": "{s} Удалено.", + "list_header_ru": "
Анимации:
\n\n
", + "list_row_ru": "• {cmd} — {name} ({n} кадров)\n", + "list_footer_ru": "
", + "list_empty_ru": "{e} Нет анимаций." + }, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, "fiksofficial/python-modules/tagall2.0.py": { "name": "TagAllMod", "description": "TagAll 2.0 — smart mention of chat participants: .tagall {all/admins/online/active} {text}", @@ -8310,6 +8443,117 @@ "has_on_unload": true, "class_cmd_names": {} }, + "Fixyres/FModules/LFSecurity.py": { + "name": "FSecurity", + "description": "Module for automatic AI-based security checks of installed modules.", + "cls_doc": { + "ru": "Модуль для автоматической проверки устанавливаемых модулей через ИИ.", + "ua": "Модуль для автоматичної перевірки встановлюваних модулів через ШІ.", + "de": "Modul zur automatischen Prüfung installierter Module mit KI.", + "jp": "AIでインストールされるモジュールを自動チェックするモジュール。", + "tr": "Kurulan modülleri yapay zeka ile otomatik kontrol eden modül.", + "uz": "O'rnatilayotgan modullarni AI orqali avtomatik tekshiruvchi modul.", + "kz": "Орнатылатын модульдерді ЖИ арқылы автоматты тексеретін модуль." + }, + "meta": { + "pic": null, + "banner": "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/banner.png", + "developer": "@NFModules", + "fhsdesc": "security, guard, antiscam, antivirus" + }, + "commands": [], + "new_commands": [], + "inline_handlers": [], + "strings": { + "name": "FSecurity" + }, + "has_on_load": false, + "has_on_unload": false, + "class_cmd_names": {} + }, + "Fixyres/FModules/FSecurity.py": { + "name": "FSecurity", + "description": "Module for automatic AI-based security checks of installed modules.", + "cls_doc": { + "ru": "Модуль для автоматической проверки устанавливаемых модулей через ИИ.", + "ua": "Модуль для автоматичної перевірки встановлюваних модулів через ШІ.", + "de": "Modul zur automatischen Prüfung installierter Module mit KI.", + "jp": "AIでインストールされるモジュールを自動チェックするモジュール。", + "tr": "Kurulan modülleri yapay zeka ile otomatik kontrol eden modül.", + "uz": "O'rnatilayotgan modullarni AI orqali avtomatik tekshiruvchi modul.", + "kz": "Орнатылатын модульдерді ЖИ арқылы автоматты тексеретін модуль." + }, + "meta": { + "pic": null, + "banner": null, + "developer": "@NFModules" + }, + "commands": [], + "new_commands": [], + "inline_handlers": [], + "strings": { + "name": "FSecurity", + "lang": "English", + "unavailable": "AI module{} check is unavailable.", + "suspicious": "AI interrupted installation of a suspicious module{}, reason:", + "blocked": "AI blocked module installation{}, reason:", + "continue": "Continue installation?", + "strict_mode_doc": "Block loading modules by any method (lm/dlm allowed) if the AI API is unavailable or the module is suspicious. On restart, this also applies to already installed modules.", + "nvidia_api_key_doc": "API key from build.nvidia.com, used for AI checks. If not specified, a public key from GitHub will be used.", + "lang_ru": "Russian", + "unavailable_ru": "Проверка модуля{} через ИИ недоступна.", + "suspicious_ru": "ИИ прервал установку подозрительного модуля{}, причина:", + "blocked_ru": "ИИ заблокировал установку модуля{}, причина:", + "continue_ru": "Продолжить установку?", + "strict_mode_doc_ru": "Не позволять загружать модули любым методом (lm/dlm разрешено), если API ИИ недоступен или модуль подозрителен. При перезагрузке работает даже на уже установленные модули.", + "nvidia_api_key_doc_ru": "API ключ от build.nvidia.com, используется для проверки через ИИ. Если вы его не укажете, будет использоваться общий ключ с GitHub.", + "lang_ua": "Ukraine", + "unavailable_ua": "Перевірка модуля{} через ШІ недоступна.", + "suspicious_ua": "ШІ перервав встановлення підозрілого модуля{}, причина:", + "blocked_ua": "ШІ заблокував встановлення модуля{}, причина:", + "continue_ua": "Продовжити встановлення?", + "strict_mode_doc_ua": "Не дозволяти завантажувати модулі будь-яким методом (lm/dlm дозволено), якщо API ШІ недоступний або модуль підозрілий. При перезавантаженні працює навіть на вже встановлені модулі.", + "nvidia_api_key_doc_ua": "API ключ від build.nvidia.com, використовується для перевірки через ШІ. Якщо ви його не вкажете, буде використовуватися загальний ключ з GitHub.", + "lang_de": "Germany", + "unavailable_de": "Die KI-Modulprüfung{} ist nicht verfügbar.", + "suspicious_de": "Die KI hat die Installation eines verdächtigen Moduls unterbrochen{}, Grund:", + "blocked_de": "Die KI hat die Modulinstallation blockiert{}, Grund:", + "continue_de": "Installation fortsetzen?", + "strict_mode_doc_de": "Das Laden von Modulen mit jeder Methode blockieren (lm/dlm erlaubt), wenn die KI-API nicht verfügbar ist oder das Modul verdächtig ist. Beim Neustart gilt dies auch für bereits installierte Module.", + "nvidia_api_key_doc_de": "API-Schlüssel von build.nvidia.com, der für KI-Prüfungen verwendet wird. Wenn nicht angegeben, wird ein öffentlicher Schlüssel von GitHub verwendet.", + "lang_jp": "Japanese", + "unavailable_jp": "AIモジュール{}のチェックが利用できません。", + "suspicious_jp": "AIが疑わしいモジュールのインストールを中断しました{}、理由:", + "blocked_jp": "AIがモジュールのインストールをブロックしました{}、理由:", + "continue_jp": "インストールを続行しますか?", + "strict_mode_doc_jp": "AI APIが利用できない場合や疑わしいモジュールの場合、すべての方法でモジュールの読み込みをブロックします(lm/dlmは許可)。再起動時にはインストール済みモジュールにも適用されます。", + "nvidia_api_key_doc_jp": "build.nvidia.com のAPIキー。AIチェックに使用されます。指定しない場合は、GitHubのパブリックキーが使用されます。", + "lang_tr": "Turkish", + "unavailable_tr": "Yapay zeka modül{} kontrolü kullanılamıyor.", + "suspicious_tr": "Yapay zeka şüpheli bir modülün kurulumunu durdurdu{}, sebep:", + "blocked_tr": "Yapay zeka modül kurulumunu engelledi{}, sebep:", + "continue_tr": "Kuruluma devam edilsin mi?", + "strict_mode_doc_tr": "AI API kullanılamıyorsa veya modül şüpheliyse, tüm yöntemlerle modül yüklenmesini engelle (lm/dlm izinli). Yeniden başlatmada zaten kurulu modüller için de geçerlidir.", + "nvidia_api_key_doc_tr": "Yapay zeka kontrolleri için kullanılan build.nvidia.com API anahtarı. Belirtilmezse GitHub'daki genel anahtar kullanılacaktır.", + "lang_uz": "Uzbekistan", + "unavailable_uz": "AI modul{} tekshiruvi mavjud emas.", + "suspicious_uz": "AI shubhali modul o'rnatilishini to'xtatdi{}, sabab:", + "blocked_uz": "AI modul o'rnatilishini blokladi{}, sabab:", + "continue_uz": "O'rnatishni davom ettirasizmi?", + "strict_mode_doc_uz": "AI API mavjud bo'lmasa yoki modul shubhali bo'lsa, barcha usullar bilan modul yuklashni bloklash (lm/dlm ruxsat etilgan). Qayta ishga tushirishda allaqachon o'rnatilgan modullarga ham ta'sir qiladi.", + "nvidia_api_key_doc_uz": "build.nvidia.com API kaliti, AI orqali tekshirish uchun ishlatiladi. Agar ko'rsatmasangiz, GitHub-dan umumiy kalit ishlatiladi.", + "lang_kz": "Kazakhstan", + "unavailable_kz": "AI модуль{} тексеру қолжетімсіз.", + "suspicious_kz": "AI күдікті модульді орнатуды тоқтатты{}, себебі:", + "blocked_kz": "AI модульді орнатуды бұғаттады{}, себебі:", + "continue_kz": "Орнатуды жалғастырасыз ба?", + "strict_mode_doc_kz": "AI API қолжетімсіз болса немесе модуль күдікті болса, барлық әдістермен модуль жүктеуді бұғаттау (lm/dlm рұқсат етілген). Қайта іске қосқанда орнатылған модульдерге де қолданылады.", + "nvidia_api_key_doc_kz": "build.nvidia.com API кілті, ЖИ арқылы тексеру үшін қолданылады. Егер оны көрсетпесеңіз, GitHub-тан ортақ кілт пайдаланылады." + }, + "has_on_load": false, + "has_on_unload": true, + "class_cmd_names": {} + }, "Fixyres/FModules/BSR.py": { "name": "BSR", "description": "Module for finding nearby game rooms in BrawlStars.", @@ -8325,7 +8569,7 @@ "meta": { "pic": null, "banner": "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/BSR/banner.png", - "developer": "@FModules", + "developer": "@NFModules", "fhsdesc": "brawlstars, game, funny" }, "commands": [ @@ -8427,7 +8671,7 @@ "meta": { "pic": null, "banner": "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/SCD/banner.png", - "developer": "@FModules" + "developer": "@NFModules" }, "commands": [ { @@ -8504,7 +8748,7 @@ "meta": { "pic": null, "banner": "https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/akinator/banner.png", - "developer": "@FModules", + "developer": "@NFModules", "fhsdesc": "game, funny, guess, question game" }, "commands": [ @@ -84526,7 +84770,7 @@ "cls_doc": {}, "meta": { "pic": null, - "banner": "https://github.com/Midga3/heroku-modules/blob/main/new_module.jpg?raw=true", + "banner": null, "developer": "@midga3_modules" }, "commands": [], @@ -84951,7 +85195,7 @@ } }, "meta": { - "total_modules": 1057, - "generated_at": "2026-04-12T17:39:10.030299" + "total_modules": 1060, + "generated_at": "2026-04-18T01:49:43.246930" } } \ No newline at end of file