diff --git a/Fixyres/FModules/FHeta.py b/Fixyres/FModules/FHeta.py index ab706ae..5100682 100644 --- a/Fixyres/FModules/FHeta.py +++ b/Fixyres/FModules/FHeta.py @@ -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() diff --git a/Fixyres/FModules/FSecurity.py b/Fixyres/FModules/FSecurity.py new file mode 100644 index 0000000..82704c8 --- /dev/null +++ b/Fixyres/FModules/FSecurity.py @@ -0,0 +1,298 @@ +__version__ = (1, 0, 0) + +# meta developer: @FModules +# meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/FSecurity/banner.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 html +import sys +import uuid +import copy +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": "en", + "unavailable": "AI module check is unavailable.", + "suspicious": "AI interrupted installation of a suspicious module, reason:", + "blocked": "AI blocked module installation, reason:", + "continue": "Continue installation?" + } + + strings_ru = { + "lang": "ru", + "_cls_doc": "Модуль для автоматической проверки устанавливаемых модулей через ИИ.", + "unavailable": "Проверка модуля через ИИ недоступна.", + "suspicious": "ИИ прервал установку подозрительного модуля, причина:", + "blocked": "ИИ заблокировал установку модуля, причина:", + "continue": "Продолжить установку?" + } + + strings_ua = { + "lang": "ua", + "_cls_doc": "Модуль для автоматичної перевірки встановлюваних модулів через ШІ.", + "unavailable": "Перевірка модуля через ШІ недоступна.", + "suspicious": "ШІ перервав встановлення підозрілого модуля, причина:", + "blocked": "ШІ заблокував встановлення модуля, причина:", + "continue": "Продовжити встановлення?" + } + + strings_de = { + "lang": "de", + "_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?" + } + + strings_jp = { + "lang": "jp", + "_cls_doc": "AIでインストールされるモジュールを自動チェックするモジュール。", + "unavailable": "AIモジュールのチェックが利用できません。", + "suspicious": "AIが疑わしいモジュールのインストールを中断しました、理由:", + "blocked": "AIがモジュールのインストールをブロックしました、理由:", + "continue": "インストールを続行しますか?" + } + + strings_tr = { + "lang": "tr", + "_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?" + } + + strings_uz = { + "lang": "uz", + "_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?" + } + + strings_kz = { + "lang": "kz", + "_cls_doc": "Орнатылатын модульдерді ЖИ арқылы автоматты тексеретін модуль.", + "unavailable": "AI модульін тексеру қолжетімсіз.", + "suspicious": "AI күдікті модульді орнатуды тоқтатты, себебі:", + "blocked": "AI модульді орнатуды бұғаттады, себебі:", + "continue": "Орнатуды жалғастырасыз ба?" + } + + def __init__(self): + self.tasks = {} + self.oreg = None + self.oload = None + + async def client_ready(self, client, db): + self.core = self.lookup("loader") + self.modules = self.core.allmodules + self.patch() + + async def on_unload(self): + self.unpatch() + + async def check(self, code): + try: + form = aiohttp.FormData() + form.add_field('file', code.encode('utf-8'), filename='module.py', content_type='text/x-python') + form.add_field('lang', self.strings("lang") or "en") + + async with aiohttp.ClientSession() as session: + async with session.post("https://api.fixyres.com/check", data=form, timeout=30) as resp: + if resp.status != 200: + return False + return await resp.json() + except Exception: + return False + + def format(self, state, reason=""): + if state == "unavailable": + return f'{self.strings("unavailable")}\n{self.strings("continue")}' + if state == "suspicious": + return f'{self.strings("suspicious")}\n
{utils.escape_html(reason)}
\n{self.strings("continue")}' + return f'{self.strings("blocked")}\n
{utils.escape_html(reason)}
' + + def buttons(self, task): + return [[ + {"text": "✓", "callback": self.confirm, "args": (task, "yes")}, + {"text": "✗", "callback": self.confirm, "args": (task, "no")} + ]] + + 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] + '😖' + + return await base(message, response, *a, **k) + + utils.answer = answer + try: + 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 + autoload = 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') + ): + msg = locals['message'] + fmsg = locals.get('msg') + break + if ( + frame.f_code.co_name in {"_register_modules", "register_all"} + and locals.get("self") is self.modules + ): + autoload = True + frame = frame.f_back + + return msg, fmsg, autoload + + def target_chat(self, msg=None, fmsg=None): + if msg: + with suppress(Exception): + target = copy.copy(msg) + if fmsg: + target.reply_to_msg_id = fmsg.id + elif not getattr(target, 'reply_to_msg_id', None): + target.reply_to_msg_id = target.id + return target + return None + + async def register(self, spec, name, origin="", save_fs=False): + if origin != "" and name != self.__module__: + 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, autoload = 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 = "" + + if autoload: + return await self.oreg(spec, name, origin, save_fs=save_fs) + + if not msg or not target: + raise loader.LoadError("") + + if msg: + with suppress(Exception): + msg.out = False + + if status == "blocked": + text = self.format("blocked", reason) + raise loader.LoadError(text) + + 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), + message=target, + reply_markup=self.buttons(task) + ) + + if not form: + raise loader.LoadError(reason) + + await asyncio.wait_for(event.wait(), timeout=60.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.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/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/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/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