diff --git a/Ruslan-Isaev/modules/GeoMod.py b/Ruslan-Isaev/modules/GeoMod.py new file mode 100644 index 0000000..e88b54c --- /dev/null +++ b/Ruslan-Isaev/modules/GeoMod.py @@ -0,0 +1,61 @@ +__version__ = (1, 0, 0) + +# meta developer: @RUIS_VlP + +from .. import loader, utils +import aiohttp +from telethon.tl.types import InputGeoPoint, InputMediaGeoPoint +from urllib.parse import quote + +async def get_coordinates(query: str): + base_url = "https://nominatim.openstreetmap.org/search" + encoded_query = quote(query) + + url = f"{base_url}?q={encoded_query}&format=json" + headers = { + "User-Agent": "Heroku-GeoMod/1.0 (https://t.me/RUIS_VlP)" + } + + async with aiohttp.ClientSession() as session: + async with session.get(url, headers=headers) as resp: + if resp.status == 200: + data = await resp.json() + if data: + lat = float(data[0]["lat"]) + lon = float(data[0]["lon"]) + return [lat, lon] + return None + +@loader.tds +class GeoMod(loader.Module): + """Модуль для отправки геолокации с указанным адресом или координатами""" + + strings = { + "name": "GeoMod", + } + + @loader.command() + async def sendgeo(self, message): + """<адрес> - отправить геолокацию с указанным адресом или координатами""" + args = utils.get_args_raw(message) + if not args: + await utils.answer( + message, + "Укажите адрес, например: .sendgeo Москва, Манежная улица, 2" + ) + return + + coords = await get_coordinates(args) + if coords: + await message.client.send_file( + message.chat_id, + InputMediaGeoPoint( + geo_point=InputGeoPoint( + lat=coords[0], + long=coords[1], + ) + ) + ) + await message.delete() + else: + await utils.answer(message, "Координаты не найдены.") \ No newline at end of file diff --git a/Ruslan-Isaev/modules/photos/cover.txt b/Ruslan-Isaev/modules/photos/cover.txt new file mode 100644 index 0000000..66de0c0 --- /dev/null +++ b/Ruslan-Isaev/modules/photos/cover.txt @@ -0,0 +1,36 @@ +PCFET0NUWVBFIGh0bWw+CjxodG1sPgo8aGVhZD4KICAgIDx0aXRsZT4vdG1wL2ZpbGVzIC0gNjFf +MjAyNTEwMTMwMjIzMjAuanBnPC90aXRsZT4KCiAgICA8bWV0YSBodHRwLWVxdWl2PSJjb250ZW50 +LVR5cGUiIGNvbnRlbnQ9InRleHQvaHRtbDsgY2hhcnNldD1VVEYtOCIvPgogICAgPG1ldGEgbmFt +ZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwgaW5pdGlhbC1zY2FsZT0x +LjAsIG1heGltdW0tc2NhbGU9MS4wLCB1c2VyLXNjYWxhYmxlPW5vIj4KCiAgICA8bGluayBocmVm +PSIvY3NzL3N0eWxlLmNzcyIgbWVkaWE9ImFsbCIgcmVsPSJzdHlsZXNoZWV0IiB0eXBlPSJ0ZXh0 +L2NzcyIvPgogICAgPGxpbmsgaHJlZj0nLy9mb250cy5nb29nbGVhcGlzLmNvbS9jc3M/ZmFtaWx5 +PU9wZW4rU2FucytDb25kZW5zZWQ6MzAwLDcwMCcgcmVsPSdzdHlsZXNoZWV0JyB0eXBlPSd0ZXh0 +L2NzcycvPgoKICAgIDxzY3JpcHQgZGF0YS1jZmFzeW5jPSJmYWxzZSIgc3JjPSIvL2RnYWYybmN5 +NGR0YW4uY2xvdWRmcm9udC5uZXQvP25mYWdkPTEyMTM0NTEiPjwvc2NyaXB0PgoKCiAgICA8IS0t +IEdsb2JhbCBzaXRlIHRhZyAoZ3RhZy5qcykgLSBHb29nbGUgQW5hbHl0aWNzIC0tPgogICAgPHNj +cmlwdCBhc3luYyBzcmM9Imh0dHBzOi8vd3d3Lmdvb2dsZXRhZ21hbmFnZXIuY29tL2d0YWcvanM/ +aWQ9VUEtNjYxMTIxNjEtMiI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0PgogICAgICAgIHdpbmRvdy5k +YXRhTGF5ZXIgPSB3aW5kb3cuZGF0YUxheWVyIHx8IFtdOwoKICAgICAgICBmdW5jdGlvbiBndGFn +KCkgewogICAgICAgICAgICBkYXRhTGF5ZXIucHVzaChhcmd1bWVudHMpOwogICAgICAgIH0KCiAg +ICAgICAgZ3RhZygnanMnLCBuZXcgRGF0ZSgpKTsKICAgICAgICBndGFnKCdjb25maWcnLCAnVUEt +NjYxMTIxNjEtMicpOwogICAgPC9zY3JpcHQ+CjwvaGVhZD4KPGJvZHk+CjxkaXYgaWQ9ImNvbnRh +aW5lciI+CiAgICA8aGVhZGVyPgogICAgICAgIDxoMT48YSBocmVmPSIvIj4vdG1wL2ZpbGVzPC9h +PjwvaDE+CiAgICAgICAgPGgyPlRlbXBvcmFyeSBGaWxlIEhvc3Rpbmc8L2gyPgogICAgPC9oZWFk +ZXI+CiAgICA8c2VjdGlvbj4KICAgICAgICAKICAgIDx0YWJsZSBzdHlsZT0iY29sb3I6ICNmZmZm +ZmY7IGZvbnQtc2l6ZTogMTJweDsiPgogICAgICAgIDx0cj4KICAgICAgICAgICAgPHRoIHN0eWxl +PSJ3aWR0aDogODBweDsiPkZpbGVuYW1lPC90aD4KICAgICAgICAgICAgPHRkPjYxXzIwMjUxMDEz +MDIyMzIwLmpwZzwvdGQ+CiAgICAgICAgPC90cj4KICAgICAgICA8dHI+CiAgICAgICAgICAgIDx0 +aD5TaXplPC90aD4KICAgICAgICAgICAgPHRkPjU1LjI1IEtCPC90ZD4KICAgICAgICA8L3RyPgog +ICAgICAgIDx0cj4KICAgICAgICAgICAgPHRoPlVSTDwvdGg+CiAgICAgICAgICAgIDx0ZD48YSB0 +YXJnZXQ9Il9ibGFuayIgaHJlZj0iaHR0cDovL3RtcGZpbGVzLm9yZy9kbC8zOTczNjYyLzYxXzIw +MjUxMDEzMDIyMzIwLmpwZyI+aHR0cDovL3RtcGZpbGVzLm9yZy9kbC8zOTczNjYyLzYxXzIwMjUx +MDEzMDIyMzIwLmpwZzwvYT48L3RkPgogICAgICAgIDwvdHI+CiAgICAgICAgPHRyPgogICAgICAg +ICAgICA8dGg+RXhwaXJlcyBhdDwvdGg+CiAgICAgICAgICAgIDx0ZD4yMDI1LTEwLTEzIDAwOjI2 +IFVUQzwvdGQ+CiAgICAgICAgPC90cj4KICAgIDwvdGFibGU+CiAgICA8YnI+CgogICAgICAgICAg +ICA8aW1nIGlkPSJpbWdfcHJldmlldyIgc3JjPSJodHRwOi8vdG1wZmlsZXMub3JnL2RsLzM5NzM2 +NjIvNjFfMjAyNTEwMTMwMjIzMjAuanBnIi8+CiAgICAKICAgIDwvc2VjdGlvbj4KICAgIDxmb290 +ZXI+CiAgICAgICAgPHVsPgogICAgICAgICAgICA8bGk+PGEgaHJlZj0iLyI+VXBsb2FkPC9hPjwv +bGk+CiAgICAgICAgICAgIDxsaT48YSBocmVmPSIvYXBpIj5BUEk8L2E+PC9saT4KICAgICAgICAg +ICAgPGxpPjxhIGhyZWY9Ii9hYm91dCI+QWJvdXQ8L2E+PC9saT4KICAgICAgICA8L3VsPgogICAg +PC9mb290ZXI+CjwvZGl2Pgo8L2JvZHk+CjwvaHRtbD4K \ No newline at end of file diff --git a/Ruslan-Isaev/modules/whois.py b/Ruslan-Isaev/modules/whois.py index 57b72d3..9d4cbf7 100755 --- a/Ruslan-Isaev/modules/whois.py +++ b/Ruslan-Isaev/modules/whois.py @@ -63,7 +63,7 @@ async def get_whois(identifier, API_KEY: str) -> dict: return response async def fetch_dns_record(session, domain, record_type): - url = "https://unfiltered.adguard-dns.com/resolve" + url = "https://dns.google/resolve" headers = {"accept": "application/dns-json"} params = {"name": domain, "type": record_type} diff --git a/fajox1/famods/picme.py b/fajox1/famods/picme.py index e53c993..22ad4e9 100644 --- a/fajox1/famods/picme.py +++ b/fajox1/famods/picme.py @@ -69,7 +69,8 @@ class PicMe(loader.Module): async def watcher(self, event): try: if event.from_id != self.tg_id: - return + if event.from_id.user_id != self.tg_id: + return except: return if not self.db.get(self.name, "picme", False): diff --git a/fiksofficial/python-modules/createavatarspack.py b/fiksofficial/python-modules/createavatarspack.py index afcb4db..2ebca46 100644 --- a/fiksofficial/python-modules/createavatarspack.py +++ b/fiksofficial/python-modules/createavatarspack.py @@ -11,7 +11,7 @@ # https://github.com/all-licenses/GNU-General-Public-License-v3.0 # meta developer: @pymodule -# requires: opencv-python +# requires: opencv-python pillow import os, shutil, cv2 from PIL import Image, UnidentifiedImageError diff --git a/fiksofficial/python-modules/deviceinfo.py b/fiksofficial/python-modules/deviceinfo.py new file mode 100644 index 0000000..0d6b2e6 --- /dev/null +++ b/fiksofficial/python-modules/deviceinfo.py @@ -0,0 +1,503 @@ +# ______ ___ ___ _ _ +# ____ | ___ \ | \/ | | | | | +# / __ \| |_/ / _| . . | ___ __| |_ _| | ___ +# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \ +# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/ +# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___| +# \____/ __/ | +# |___/ + +# На модуль распространяется лицензия "GNU General Public License v3.0" +# https://github.com/all-licenses/GNU-General-Public-License-v3.0 + +# meta developer: @pymodule +# requires: aiohttp cachetools + +import asyncio +import logging +from typing import List, Dict, Any +import aiohttp +from cachetools import TTLCache + +from .. import loader, utils +from ..inline.types import InlineMessage + +logger = logging.getLogger(__name__) + +@loader.tds +class DeviceInfo(loader.Module): + """A module for obtaining information about smartphones""" + + strings_ru = { + "name": "DeviceInfo", + "_cls_doc": "Модуль для получения информации о смартфонах", + "searching": "🔍 Ищу устройства по запросу: {}...", + "no_query": "❌ Укажи название устройства! Пример: .di iPhone 15", + "no_results": "📭 Устройства не найдены для запросу: {}", + "device_list": "📱 Найдено {} устройств по запросу {}:", + "device_info": "📱 {}\n\n{}", + "error": "❌ Ошибка: {}. Попробуй позже или проверь API.", + "network": "📡 Сеть: {}\n", + "launched": "📅 Дата выпуска:\n Анонс: {}\n Статус: {}\n", + "body": "📏 Корпус:\n Размеры: {}\n Вес: {}\n SIM: {}\n Прочее: {}\n", + "display": "🖥️ Дисплей:\n Тип: {}\n Размер: {}\n Разрешение: {}\n Защита: {}\n", + "platform": "⚙️ Платформа:\n ОС: {}\n Чипсет: {}\n CPU: {}\n GPU: {}\n", + "memory": "💾 Память:\n Карта памяти: {}\n Внутренняя: {}\n Прочее: {}\n", + "main_camera": "📷 Основная камера:\n Модули: {}\n Функции: {}\n Видео: {}\n", + "selfie_camera": "🤳 Фронтальная камера:\n Модули: {}\n Функции: {}\n Видео: {}\n", + "sound": "🔊 Звук:\n Динамик: {}\n Аудиоразъём: {}\n Прочее: {}\n", + "comms": "🌐 Связь:\n Wi-Fi: {}\n Bluetooth: {}\n GPS: {}\n NFC: {}\n Инфракрасный порт: {}\n Радио: {}\n USB: {}\n", + "sensors": "🛠️ Датчики: {}\n", + "battery": "🔋 Батарея:\n Тип: {}\n Зарядка: {}\n", + "misc": "🎨 Разное:\n Цвета: {}\n Модели: {}\n", + "show_body": "📏 Корпус", + "show_memory": "💾 Память", + "show_cameras": "📷 Камеры", + "show_sound": "🔊 Звук", + "show_comms": "🌐 Связь", + "show_sensors": "🛠️ Датчики", + "show_misc": "🎨 Разное", + "next_photo": "▶️ След. фото", + "prev_photo": "◀️ Пред. фото", + "back": "🔙 Назад", + "back_to_device": "🔙 К устройству", + "config_saved": "✅ Конфигурация сохранена!", + "retrying": "🔄 Повторяю запрос... (попытка {}/{} )" + } + + strings = { + "name": "DeviceInfo", + "searching": "🔍 Searching devices for: {}...", + "no_query": "❌ Specify a device name! Example: .di iPhone 15", + "no_results": "📭 No devices found for query: {}", + "device_list": "📱 Found {} devices for query {}:", + "device_info": "📱 {}\n\n{}", + "error": "❌ Error: {}. Try again later or check the API.", + "network": "📡 Network: {}\n", + "launched": "📅 Launch:\n Announced: {}\n Status: {}\n", + "body": "📏 Body:\n Dimensions: {}\n Weight: {}\n SIM: {}\n Other: {}\n", + "display": "🖥️ Display:\n Type: {}\n Size: {}\n Resolution: {}\n Protection: {}\n", + "platform": "⚙️ Platform:\n OS: {}\n Chipset: {}\n CPU: {}\n GPU: {}\n", + "memory": "💾 Memory:\n Card slot: {}\n Internal: {}\n Other: {}\n", + "main_camera": "📷 Main Camera:\n Modules: {}\n Features: {}\n Video: {}\n", + "selfie_camera": "🤳 Selfie Camera:\n Modules: {}\n Features: {}\n Video: {}\n", + "sound": "🔊 Sound:\n Loudspeaker: {}\n Audio Jack: {}\n Other: {}\n", + "comms": "🌐 Comms:\n Wi-Fi: {}\n Bluetooth: {}\n GPS: {}\n NFC: {}\n Infrared: {}\n Radio: {}\n USB: {}\n", + "sensors": "🛠️ Sensors: {}\n", + "battery": "🔋 Battery:\n Type: {}\n Charging: {}\n", + "misc": "🎨 Misc:\n Colors: {}\n Models: {}\n", + "show_body": "📏 Body", + "show_memory": "💾 Memory", + "show_cameras": "📷 Cameras", + "show_sound": "🔊 Sound", + "show_comms": "🌐 Comms", + "show_sensors": "🛠️ Sensors", + "show_misc": "🎨 Misc", + "next_photo": "▶️ Next Photo", + "prev_photo": "◀️ Prev Photo", + "back": "🔙 Back", + "back_to_device": "🔙 To Device", + "config_saved": "✅ Configuration saved!", + "retrying": "🔄 Retrying request... (attempt {}/{})" + } + + def __init__(self): + self.config = loader.ModuleConfig( + loader.ConfigValue( + "api_base_url", + "https://mobilespecs.fiksofficial.fun", + lambda: "API Url", + validator=loader.validators.String() + ), + loader.ConfigValue( + "max_results", + 20, + lambda: "Maximum search results to display", + validator=loader.validators.Integer(minimum=1, maximum=50) + ), + loader.ConfigValue( + "timeout", + 10, + lambda: "Timeout for API requests (seconds)", + validator=loader.validators.Integer(minimum=5, maximum=30) + ), + loader.ConfigValue( + "retry_attempts", + 3, + lambda: "Number of retry attempts for API requests", + validator=loader.validators.Integer(minimum=1, maximum=5) + ) + ) + self.cache = TTLCache(maxsize=100, ttl=300) + self.session: aiohttp.ClientSession = None + + async def client_ready(self, client, db): + """Initialize aiohttp session on client ready""" + self.session = aiohttp.ClientSession( + timeout=aiohttp.ClientTimeout(total=self.config["timeout"]) + ) + logger.info("DeviceInfo: aiohttp session initialized") + self.client = client + + async def on_unload(self): + """Close aiohttp session on module unload""" + if self.session: + await self.session.close() + logger.info("DeviceInfo: aiohttp session closed") + + async def _resolve_entity(self, call: InlineMessage, message_id: int, chat_id: int = None): + """Resolve Telegram entity to Message or int""" + if hasattr(call, "message") and call.message: + logger.debug("DeviceInfo: Using call.message") + return call.message + if chat_id: + logger.warning(f"DeviceInfo: call.message is None, falling back to chat_id {chat_id}") + return chat_id + logger.warning(f"DeviceInfo: call.message and chat_id are None, falling back to message_id {message_id}") + return message_id + + async def _fetch_json(self, endpoint: str, params: Dict[str, Any] = None) -> Any: + """Fetch JSON from API with retry and caching""" + cache_key = f"{endpoint}_{params}" if params else endpoint + if cache_key in self.cache: + logger.debug(f"Cache hit for {cache_key}") + return self.cache[cache_key] + + url = f"{self.config['api_base_url']}/gsm/{endpoint}" + params_clean = {k: v for k, v in (params or {}).items() if k != "message"} + for attempt in range(1, self.config["retry_attempts"] + 1): + try: + async with self.session.get(url, params=params_clean or None) as resp: + if resp.status != 200: + error_text = await resp.text() + logger.error(f"DeviceInfo: HTTP {resp.status} for {url}: {error_text}") + raise aiohttp.ClientError(f"HTTP {resp.status}: {error_text}") + content_type = resp.headers.get("Content-Type", "") + if "application/json" not in content_type: + error_text = await resp.text() + logger.error(f"DeviceInfo: Invalid content-type {content_type} for {url}: {error_text}") + raise ValueError(f"Invalid content-type: {content_type}") + data = await resp.json() + if data is None: + error_text = await resp.text() + logger.error(f"DeviceInfo: API returned None for {url}: {error_text}") + if endpoint.startswith("search"): + data = [] + else: + data = {} + if not isinstance(data, (list, dict)): + logger.error(f"DeviceInfo: Unexpected API response type for {url}: {type(data)}") + raise ValueError(f"Unexpected API response type: {type(data)}") + self.cache[cache_key] = data + logger.debug(f"Cache set for {cache_key}") + return data + except (aiohttp.ClientError, asyncio.TimeoutError, ValueError) as e: + logger.warning(f"DeviceInfo: Request failed for {endpoint} (attempt {attempt}): {e}") + if attempt < self.config["retry_attempts"]: + if params and "message" in params: + await utils.answer(params["message"], self.strings["retrying"].format(attempt, self.config["retry_attempts"])) + await asyncio.sleep(2 * attempt) + else: + logger.error(f"DeviceInfo: API failed after {self.config['retry_attempts']} attempts: {e}") + if endpoint.startswith("search"): + return [] + return {} + + def _format_essential_info(self, device: Dict[str, Any]) -> str: + """Format essential device info (name, network, launch, display, platform, battery)""" + info_text = "" + if "network" in device: + info_text += self.strings["network"].format(device.get("network", "N/A")) + if "launced" in device: + info_text += self.strings["launched"].format( + device["launced"].get("announced", "N/A"), + device["launced"].get("status", "N/A") + ) + if "display" in device: + info_text += self.strings["display"].format( + device["display"].get("type", "N/A"), + device["display"].get("size", "N/A"), + device["display"].get("resolution", "N/A"), + device["display"].get("protection", "N/A") + ) + if "platform" in device: + info_text += self.strings["platform"].format( + device["platform"].get("os", "N/A"), + device["platform"].get("chipset", "N/A"), + device["platform"].get("cpu", "N/A"), + device["platform"].get("gpu", "N/A") + ) + if "battery" in device: + info_text += self.strings["battery"].format( + device["battery"].get("battType", "N/A"), + device["battery"].get("charging", "N/A") + ) + return info_text + + def _format_section(self, section: str, device: Dict[str, Any]) -> str: + """Format a specific section of device info""" + if section == "body" and "body" in device: + return self.strings["body"].format( + device["body"].get("dimension", "N/A"), + device["body"].get("weight", "N/A"), + device["body"].get("sim", "N/A"), + device["body"].get("other", "N/A") + ) + if section == "memory" and "memory" in device: + memory = {item.get("label", ""): item.get("value", "N/A") for item in device.get("memory", [])} + return self.strings["memory"].format( + memory.get("card", "N/A"), + memory.get("internal", "N/A"), + memory.get("otherMemory", "N/A") + ) + if section == "cameras": + cam_text = "" + if "mainCamera" in device: + cam_text += self.strings["main_camera"].format( + device["mainCamera"].get("mainModules", "N/A"), + device["mainCamera"].get("mainFeatures", "N/A"), + device["mainCamera"].get("mainVideo", "N/A") + ) + if "selfieCamera" in device: + cam_text += self.strings["selfie_camera"].format( + device["selfieCamera"].get("selfieModules", "N/A"), + device["selfieCamera"].get("selfieFeatures", "N/A"), + device["selfieCamera"].get("selfieVideo", "N/A") + ) + return cam_text + if section == "sound" and "sound" in device: + return self.strings["sound"].format( + device["sound"].get("loudSpeaker", "N/A"), + device["sound"].get("audioJack", "N/A"), + device["sound"].get("otherSound", "N/A") + ) + if section == "comms" and "comms" in device: + return self.strings["comms"].format( + device["comms"].get("wlan", "N/A"), + device["comms"].get("bluetooth", "N/A"), + device["comms"].get("gps", "N/A"), + device["comms"].get("nfc", "N/A"), + device["comms"].get("infrared", "N/A"), + device["comms"].get("radio", "N/A"), + device["comms"].get("usb", "N/A") + ) + if section == "sensors" and "sensors" in device: + return self.strings["sensors"].format(device.get("sensors", "N/A")) + if section == "misc" and "misc" in device: + return self.strings["misc"].format( + device["misc"].get("colors", "N/A"), + device["misc"].get("models", "N/A") + ) + return "N/A" + + @loader.command(ru_doc="(.di) <название устройства> - Получить информацию о смартфоне", alias="di") + async def deviceinfo(self, message): + """(.di) - Get smartphone info by name""" + query = utils.get_args_raw(message).strip() + if not query: + await utils.answer(message, self.strings["no_query"]) + return + + await utils.answer(message, self.strings["searching"].format(query)) + try: + devices = await self._fetch_json("search", {"q": query, "message": message}) + if not devices: + await utils.answer(message, self.strings["no_results"].format(query)) + return + + devices = devices[:self.config["max_results"]] + button_rows = [[{"text": device["name"], "callback": self.show_device_info, "args": [device["id"], query, message.id, message.chat_id, 0, None]}] for device in devices] + await self.inline.list( + message=message, + strings=[self.strings["device_list"].format(len(devices), query)], + custom_buttons=button_rows, + ttl=60, + force_me=True, + manual_security=True, + silent=True + ) + except Exception as e: + logger.error(f"DeviceInfo: Failed to fetch search results: {e}") + await utils.answer(message, self.strings["error"].format(str(e))) + + async def show_device_info(self, call: InlineMessage, device_id: str, query: str, message_id: int, chat_id: int, photo_idx: int, prev_call_id: str = None): + """Handle device selection and show essential info with buttons for details""" + message = await self._resolve_entity(call, message_id, chat_id) + + try: + device = await self._fetch_json(f"info/{device_id}", {"message": message}) + if not device: + raise ValueError("No device info returned") + images_data = await self._fetch_json(f"images/{device_id}", {"message": message}) + images = images_data.get("images", []) if isinstance(images_data, dict) else [] + + info_text = self._format_essential_info(device) + full_text = self.strings["device_info"].format(device.get("name", "N/A"), info_text) + + # Truncate for media caption (Telegram limit: 1024 chars) + caption = full_text[:1024] + ("..." if len(full_text) > 1024 else "") if images else full_text + logger.debug(f"DeviceInfo: Caption length: {len(caption)} characters, photo_idx: {photo_idx}") + + # Buttons for additional sections and photo navigation + buttons = [ + [ + {"text": self.strings["show_body"], "callback": self.show_section, "args": ["body", device_id, query, message_id, chat_id, photo_idx]}, + {"text": self.strings["show_memory"], "callback": self.show_section, "args": ["memory", device_id, query, message_id, chat_id, photo_idx]}, + ], + [ + {"text": self.strings["show_cameras"], "callback": self.show_section, "args": ["cameras", device_id, query, message_id, chat_id, photo_idx]}, + {"text": self.strings["show_sound"], "callback": self.show_section, "args": ["sound", device_id, query, message_id, chat_id, photo_idx]}, + ], + [ + {"text": self.strings["show_comms"], "callback": self.show_section, "args": ["comms", device_id, query, message_id, chat_id, photo_idx]}, + {"text": self.strings["show_sensors"], "callback": self.show_section, "args": ["sensors", device_id, query, message_id, chat_id, photo_idx]}, + ], + [ + {"text": self.strings["show_misc"], "callback": self.show_section, "args": ["misc", device_id, query, message_id, chat_id, photo_idx]}, + ], + [ + {"text": self.strings["prev_photo"], "callback": self.show_device_info, "args": [device_id, query, message_id, chat_id, max(0, photo_idx - 1), call.id]} if photo_idx > 0 else None, + {"text": self.strings["next_photo"], "callback": self.show_device_info, "args": [device_id, query, message_id, chat_id, min(len(images) - 1, photo_idx + 1), call.id]} if images and photo_idx < len(images) - 1 else None, + {"text": self.strings["back"], "callback": self.back_to_search, "args": [query, message_id, chat_id]}, + ] + ] + # Filter out None buttons + buttons = [[btn for btn in row if btn] for row in buttons if any(row)] + + # Always edit the message (for device selection or photo navigation) + logger.debug(f"DeviceInfo: Editing message for device_id: {device_id}, photo_idx: {photo_idx}, call_id: {call.id}") + await call.edit( + text=caption, + reply_markup=buttons, + photo=images[photo_idx] if images else None, + disable_web_page_preview=True + ) + except Exception as e: + logger.error(f"DeviceInfo: Failed to show device info: {e}") + await call.edit( + text=self.strings["error"].format(str(e)), + reply_markup=[], + disable_web_page_preview=True + ) + + async def show_section(self, call: InlineMessage, section: str, device_id: str, query: str, message_id: int, chat_id: int, photo_idx: int): + """Show a specific section of device info""" + message = await self._resolve_entity(call, message_id, chat_id) + + try: + device = await self._fetch_json(f"info/{device_id}", {"message": message}) + if not device: + raise ValueError("No device info returned") + + section_text = self._format_section(section, device) + full_text = self.strings["device_info"].format(device.get("name", "N/A"), section_text) + + # Truncate for Telegram message limit (4000 chars) + full_text = full_text[:4000] + ("..." if len(full_text) > 4000 else "") + + # Buttons for returning to device info + buttons = [ + [{"text": self.strings["back_to_device"], "callback": self.show_device_info, "args": [device_id, query, message_id, chat_id, photo_idx, call.id]}] + ] + + # Try to edit the message + try: + logger.debug(f"DeviceInfo: Editing message for section: {section}, call_id: {call.id}") + await call.edit( + text=full_text, + reply_markup=buttons, + photo=None, # Sections don't include photos to avoid media/text mismatch + disable_web_page_preview=True + ) + except Exception as edit_error: + logger.warning(f"DeviceInfo: Failed to edit message for section {section}: {edit_error}") + # Fallback to new inline form if edit fails + await self.inline.form( + text=full_text, + message=message, + reply_markup=buttons, + ttl=300, + force_me=True, + silent=True + ) + except Exception as e: + logger.error(f"DeviceInfo: Failed to show section {section}: {e}") + try: + await call.edit( + text=self.strings["error"].format(str(e)), + reply_markup=[], + disable_web_page_preview=True + ) + except Exception as edit_error: + logger.warning(f"DeviceInfo: Failed to edit error message: {edit_error}") + await self.inline.form( + text=self.strings["error"].format(str(e)), + message=message, + silent=True + ) + + async def back_to_search(self, call: InlineMessage, query: str, message_id: int, chat_id: int): + """Handle 'Back' button to return to search results""" + message = await self._resolve_entity(call, message_id, chat_id) + + try: + devices = await self._fetch_json("search", {"q": query, "message": message}) + logger.debug(f"DeviceInfo: Fetched {len(devices)} devices for query: {query}") + if not devices: + logger.warning(f"DeviceInfo: No devices found for query: {query}") + try: + await call.edit( + text=self.strings["no_results"].format(query), + reply_markup=[], + photo=None, # Explicitly remove any existing photo + disable_web_page_preview=True + ) + except Exception as edit_error: + logger.warning(f"DeviceInfo: Failed to edit no_results message: {edit_error}") + await self.inline.form( + text=self.strings["no_results"].format(query), + message=message, + silent=True + ) + return + + devices = devices[:self.config["max_results"]] + button_rows = [[{"text": device["name"], "callback": self.show_device_info, "args": [device["id"], query, message_id, chat_id, 0, None]}] for device in devices] + list_text = self.strings["device_list"].format(len(devices), query) + + # Try to edit the message + try: + logger.debug(f"DeviceInfo: Editing message for back_to_search, query: {query}, call_id: {call.id}") + await call.edit( + text=list_text, + reply_markup=button_rows, + photo=None, # Explicitly remove any existing photo + disable_web_page_preview=True + ) + except Exception as edit_error: + logger.warning(f"DeviceInfo: Failed to edit back_to_search message: {edit_error}") + await self.inline.list( + message=message_id, + strings=[list_text], + custom_buttons=button_rows, + ttl=60, + force_me=True, + manual_security=True, + silent=True + ) + except Exception as e: + logger.error(f"DeviceInfo: Failed to return to search: {e}") + try: + await call.edit( + text=self.strings["error"].format(str(e)), + reply_markup=[], + photo=None, # Explicitly remove any existing photo + disable_web_page_preview=True + ) + except Exception as edit_error: + logger.warning(f"DeviceInfo: Failed to edit error message: {edit_error}") + await self.inline.form( + text=self.strings["error"].format(str(e)), + message=message, + silent=True + ) \ No newline at end of file diff --git a/fiksofficial/python-modules/full.txt b/fiksofficial/python-modules/full.txt index d49f815..73e0f01 100644 --- a/fiksofficial/python-modules/full.txt +++ b/fiksofficial/python-modules/full.txt @@ -17,4 +17,7 @@ qrgen wiki checkhost createavatarspack -multiunloadmodule \ No newline at end of file +multiunloadmodule +tagall2.0 +point +deviceinfo \ No newline at end of file diff --git a/fiksofficial/python-modules/lyrics.py b/fiksofficial/python-modules/lyrics.py index 3fb5e6e..d62a880 100644 --- a/fiksofficial/python-modules/lyrics.py +++ b/fiksofficial/python-modules/lyrics.py @@ -2,6 +2,8 @@ # https://github.com/all-licenses/GNU-General-Public-License-v3.0 # meta developer: @PyModule +# requires: lyricsgenius===3.7.0 + from lyricsgenius import Genius from .. import loader, utils diff --git a/fiksofficial/python-modules/point.py b/fiksofficial/python-modules/point.py new file mode 100644 index 0000000..f6112c7 --- /dev/null +++ b/fiksofficial/python-modules/point.py @@ -0,0 +1,160 @@ +# ______ ___ ___ _ _ +# ____ | ___ \ | \/ | | | | | +# / __ \| |_/ / _| . . | ___ __| |_ _| | ___ +# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \ +# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/ +# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___| +# \____/ __/ | +# |___/ + +# На модуль распространяется лицензия "GNU General Public License v3.0" +# https://github.com/all-licenses/GNU-General-Public-License-v3.0 + +# meta developer: @pymodule + +from .. import loader, utils + + +@loader.tds +class PointSentenceCaseMod(loader.Module): + """Automatically capitalizes the first letter of each sentence and adds a period at the end of the message (if there isn't one).""" + + strings = { + "name": "PointSentenceCase", + "enabled": "The module is activated ✅", + "disabled": "The module is deactivated ❌", + "status": "Current status: {status}\nIgnore channels: {ignore_channels}\n\nUsage:\n.pointcase on|off\n.pointcaseignore on|off", + "status_on": "✅ Enabled", + "status_off": "❌ Off", + "ignore_on": "✅ Ignoring channels", + "ignore_off": "❌ Not ignoring channels", + } + + strings_ru = { + "_cls_doc": "Автоматически делает первую букву каждого предложения заглавной и добавляет точку в конце сообщения (если её нет).", + "enabled": "Модуль активирован ✅", + "disabled": "Модуль деактивирован ❌", + "status": "Текущий статус: {status}\nИгнорировать каналы: {ignore_channels}\n\nИспользование:\n.pointcase on|off\n.pointcaseignore on|off", + "status_on": "✅ Включен", + "status_off": "❌ Выключен", + "ignore_on": "✅ Каналы игнорируются", + "ignore_off": "❌ Каналы не игнорируются", + } + + async def client_ready(self, client, db): + self.client = client + self.db = db + if self.db.get("PointSentenceCase", "enabled") is None: + self.db.set("PointSentenceCase", "enabled", True) + if self.db.get("PointSentenceCase", "ignore_channels") is None: + self.db.set("PointSentenceCase", "ignore_channels", True) + + @loader.command(ru_doc="{on/off} — включает/выключает модуль") + async def pointcase(self, message): + """{on/off} - enables/disables the module""" + args = utils.get_args_raw(message).lower() + + if args == "on": + self.db.set("PointSentenceCase", "enabled", True) + await utils.answer(message, self.strings("enabled")) + elif args == "off": + self.db.set("PointSentenceCase", "enabled", False) + await utils.answer(message, self.strings("disabled")) + else: + status = self.db.get("PointSentenceCase", "enabled", True) + ignore = self.db.get("PointSentenceCase", "ignore_channels", True) + await utils.answer( + message, + self.strings("status").format( + status=self.strings("status_on") if status else self.strings("status_off"), + ignore_channels=self.strings("ignore_on") if ignore else self.strings("ignore_off"), + ), + ) + + @loader.command(ru_doc="{on/off} — включает/выключает игнорирование каналов") + async def pointcaseignore(self, message): + """{on/off} - enables/disables ignoring channels""" + args = utils.get_args_raw(message).lower() + + if args == "on": + self.db.set("PointSentenceCase", "ignore_channels", True) + await utils.answer(message, self.strings("ignore_on")) + elif args == "off": + self.db.set("PointSentenceCase", "ignore_channels", False) + await utils.answer(message, self.strings("ignore_off")) + else: + ignore = self.db.get("PointSentenceCase", "ignore_channels", True) + await utils.answer( + message, + self.strings("ignore_on") if ignore else self.strings("ignore_off"), + ) + + async def watcher(self, message): + if not self.db.get("PointSentenceCase", "enabled", True): + return + + if not message.out or not message.text: + return + + if self.db.get("PointSentenceCase", "ignore_channels", True): + try: + peer = await message.get_chat() + if getattr(peer, "is_channel", False) and not getattr(peer, "is_group", False): + return + except Exception: + pass + + text = message.text.strip() + if not text: + return + + prefixes = self.get_prefix() + if isinstance(prefixes, str): + prefixes = [prefixes] + + if any(text.startswith(prefix) for prefix in prefixes): + return + + sentence_end_marks = {".", "!", "?", "…"} + result = "" + capitalize_next = True + + for char in text: + if capitalize_next and char.isalpha(): + result += char.upper() + capitalize_next = False + else: + result += char.lower() + if char in sentence_end_marks: + capitalize_next = True + elif char in {",", ":", "-", "#", "/", '"'}: + capitalize_next = False + + last_char = result[-1] if result else "" + is_special = not last_char.isalnum() and not self.is_emoji(last_char) + + if ( + result + and last_char not in sentence_end_marks + and not self.is_emoji(last_char) + and not is_special + ): + result += "." + + if result != text: + await message.edit(result) + + def is_emoji(self, char: str) -> bool: + return any([ + "\U0001F600" <= char <= "\U0001F64F", + "\U0001F300" <= char <= "\U0001F5FF", + "\U0001F680" <= char <= "\U0001F6FF", + "\U0001F700" <= char <= "\U0001F77F", + "\U0001F780" <= char <= "\U0001F7FF", + "\U0001F800" <= char <= "\U0001F8FF", + "\U0001F900" <= char <= "\U0001F9FF", + "\U0001FA00" <= char <= "\U0001FA6F", + "\U0001FA70" <= char <= "\U0001FAFF", + "\U00002702" <= char <= "\U000027B0", + "\U000024C2" <= char <= "\U0001F251", + ]) \ No newline at end of file diff --git a/fiksofficial/python-modules/tagall2.0.py b/fiksofficial/python-modules/tagall2.0.py new file mode 100644 index 0000000..3655629 --- /dev/null +++ b/fiksofficial/python-modules/tagall2.0.py @@ -0,0 +1,110 @@ +# ______ ___ ___ _ _ +# ____ | ___ \ | \/ | | | | | +# / __ \| |_/ / _| . . | ___ __| |_ _| | ___ +# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \ +# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/ +# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___| +# \____/ __/ | +# |___/ + +# На модуль распространяется лицензия "GNU General Public License v3.0" +# https://github.com/all-licenses/GNU-General-Public-License-v3.0 + +# meta developer: @pymodule + +from .. import loader, utils +from telethon.tl.types import ChannelParticipantsAdmins, UserStatusRecently, UserStatusOnline, Message +import typing +import asyncio + +@loader.tds +class TagAllMod(loader.Module): + """TagAll 2.0 — smart mention of chat participants: .tagall {all/admins/online/active} {text}""" + + strings = { + "name": "TagAll 2.0", + "done": "✅ {} users mentioned", + "no_users": "⚠️ No users found matching this filter", + "invalid_args": "❌ Invalid command format. Use: .tagall {all/admins/online/active} {text}", + } + + strings_ru = { + "_cls_doc": "TagAll 2.0 — умное упоминание участников чата: .tagall {all/admins/online/active} {текст}", + "done": "✅ Упомянуто {} пользователей", + "no_users": "⚠️ Не найдено пользователей по данному фильтру", + "invalid_args": "❌ Неверный формат команды. Используйте: .tagall {all/admins/online/active} {текст}", + } + + async def client_ready(self, client, db): + self.client = client + + @loader.command(ru_doc="Упомянуть участников: .tagall {all/admins/online/active} {текст}") + async def tagallcmd(self, message: Message): + """Mention members: .tagall {all/admins/online/active} {text}""" + args = utils.get_args_raw(message).split(maxsplit=1) + mode = args[0].lower() if args else None + text = args[1] if len(args) > 1 else "Срочное собрание!" + + valid_modes = {"all", "admins", "online", "active"} + + if mode not in valid_modes: + await utils.answer(message, self.strings["invalid_args"]) + return + + chat = await self.client.get_entity(message.chat_id) + tagged = await self._do_tagall(chat, mode, text) + if not tagged: + await utils.answer(message, self.strings["no_users"]) + return + await utils.answer(message, self.strings["done"].format(tagged)) + + async def _do_tagall(self, chat, filter_: str, text: str = "") -> typing.Optional[int]: + users = [] + + try: + if filter_ == "all": + async for user in self.client.iter_participants(chat): + if not user.bot: + users.append(user) + + elif filter_ == "admins": + async for user in self.client.iter_participants(chat, filter=ChannelParticipantsAdmins): + if not user.bot: + users.append(user) + + elif filter_ == "online": + async for user in self.client.iter_participants(chat): + if not user.bot and isinstance(user.status, (UserStatusRecently, UserStatusOnline)): + users.append(user) + + elif filter_ == "active": + user_ids = set() + async for msg in self.client.iter_messages(chat, limit=50): + if msg.sender_id and msg.sender_id not in user_ids: + try: + user = await self.client.get_entity(msg.sender_id) + if not user.bot: + users.append(user) + user_ids.add(msg.sender_id) + except Exception: + continue + + if not users: + return None + + batch_size = 5 + tagged = 0 + for i in range(0, len(users), batch_size): + batch = users[i:i + batch_size] + mentions = " ".join([f"{u.first_name or 'User'}" for u in batch]) + msg_text = f"{text}\n{mentions}" if text else mentions + await self.client.send_message(chat, msg_text, link_preview=False, parse_mode="html") + tagged += len(batch) + if i + batch_size < len(users): + await asyncio.sleep(2) + + return tagged + + except Exception as e: + self._log.error(f"Error in tagall: {e}") + return None \ No newline at end of file diff --git a/mead0wsss/mead0wsMods/README.md b/mead0wsss/mead0wsMods/README.md new file mode 100644 index 0000000..8f03577 --- /dev/null +++ b/mead0wsss/mead0wsMods/README.md @@ -0,0 +1 @@ +эрон дон дон diff --git a/mead0wsss/mead0wsMods/SenderGifts.py b/mead0wsss/mead0wsMods/SenderGifts.py index e56dd86..6b79d4c 100644 --- a/mead0wsss/mead0wsMods/SenderGifts.py +++ b/mead0wsss/mead0wsMods/SenderGifts.py @@ -1,5 +1,5 @@ # -- version -- -__version__ = (1, 0, 0) +__version__ = (1, 2, 0) # -- version -- @@ -17,95 +17,209 @@ __version__ = (1, 0, 0) # scope: heroku_only from .. import loader, utils -from herokutl.tl.functions.payments import GetPaymentFormRequest, SendStarsFormRequest +from herokutl.tl.functions.payments import GetPaymentFormRequest, SendStarsFormRequest, GetStarsStatusRequest from herokutl.tl.types import InputInvoiceStarGift, TextWithEntities from herokutl.errors.rpcerrorlist import BadRequestError import logging @loader.tds class SenderGifts(loader.Module): - """Модуль для отправки подарков""" - + """Модуль для отправки подарков Telegram прямиком в чате""" strings = { "name": "SenderGifts", - "usage": " Используйте в формате: .sendgift @username текст", + "usage": " Используйте в формате: .sendgift @username текст или реплай + .sendgift текст", "checking_user": "🔍 Проверка пользователя...", + "checking_balance": "🔍 Проверка баланса...", "user_not_found": " Пользователь не найден", - "gift_menu": "🎁 Выберите подарок.\n\n👤 Пользователь: {}\n📄 Текст: {}", + "gift_menu": "🎁 Выберите категорию подарков.\n\n👤 Пользователь: {}\n📄 Текст: {}\n Баланс: {} звезд", + "category_menu": "🎁 Подарки за {} ⭐\n\n👤 Пользователь: {}\n📄 Текст: {}", "sending_gift": "🛫 Отправка подарка...", "gift_sent": " Подарок успешно отправлен!", "not_enough_stars": " Недостаточно звезд для отправки подарка {}!", + "min_stars_error": " Недостаточно звезд для отправки минимального подарка!", + "no_available_gifts": " Нет доступных подарков для вашего баланса", + "balance_error": " Ошибка при проверке баланса", } - gifts = [ - [ - {"id": 5170145012310081615, "stars": 15, "emoji": "❤️", "name": "Сердце"}, - {"id": 5170233102089322756, "stars": 15, "emoji": "🧸", "name": "Мишка"}, - {"id": 5170250947678437525, "stars": 25, "emoji": "🎁", "name": "Подарок"}, + gift_categories = { + 15: [ + {"id": 5170145012310081615, "emoji": "❤️", "name": "Сердце"}, + {"id": 5170233102089322756, "emoji": "🧸", "name": "Мишка"}, ], - [ - {"id": 5168103777563050263, "stars": 25, "emoji": "🌹", "name": "Роза"}, - {"id": 5170144170496491616, "stars": 50, "emoji": "🎂", "name": "Тортик"}, - {"id": 5170314324215857265, "stars": 50, "emoji": "💐", "name": "Цветы"}, + 25: [ + {"id": 5170250947678437525, "emoji": "🎁", "name": "Подарок"}, + {"id": 5168103777563050263, "emoji": "🌹", "name": "Роза"}, ], - [ - {"id": 5170564780938756245, "stars": 50, "emoji": "🚀", "name": "Ракета"}, - {"id": 5168043875654172773, "stars": 100, "emoji": "🏆", "name": "Кубок"}, - {"id": 5170690322832818290, "stars": 100, "emoji": "💍", "name": "Кольцо"}, + 50: [ + {"id": 5170144170496491616, "emoji": "🎂", "name": "Тортик"}, + {"id": 5170314324215857265, "emoji": "💐", "name": "Цветы"}, + {"id": 5170564780938756245, "emoji": "🚀", "name": "Ракета"}, + ], + 100: [ + {"id": 5168043875654172773, "emoji": "🏆", "name": "Кубок"}, + {"id": 5170690322832818290, "emoji": "💍", "name": "Кольцо"}, + {"id": 5170521118301225164, "emoji": "💎", "name": "Алмаз"}, ] - ] + } async def client_ready(self, client, db): self.client = client + async def get_star_balance(self): + try: + balance_info = (await self.client(GetStarsStatusRequest("me"))) + return balance_info.balance.amount + except Exception as e: + logging.error(f"Error getting balance: {e}") + return 0 + @loader.command() async def sendgift(self, message): - """Отправить подарок пользователю""" + """- - отправить подарок пользователю (* - необязательный параметр.) Поддерживается реплай режим.""" args = utils.get_args_raw(message) - if not args: - await utils.answer(message, self.strings["usage"]) - return - - parts = args.split(maxsplit=1) - if len(parts) < 1: - await utils.answer(message, self.strings["usage"]) - return - - username = parts[0] - text = parts[1] if len(parts) > 1 else "" - if username.startswith('@'): - username = username[1:] - msg = await utils.answer(message, self.strings["checking_user"]) + reply = await message.get_reply_message() + if reply: + user = reply.sender + text = args if args else "" + else: + if not args: + await utils.answer(message, self.strings["usage"]) + return + parts = args.split(maxsplit=1) + if len(parts) < 1: + await utils.answer(message, self.strings["usage"]) + return + username = parts[0] + text = parts[1] if len(parts) > 1 else "" + if username.startswith('@'): + username = username[1:] + msg = await utils.answer(message, self.strings["checking_user"]) + try: + user = await self.client.get_entity(username) + except Exception as e: + logging.error(f"User not found: {e}") + await utils.answer(msg, self.strings["user_not_found"]) + return + + balance_msg = await utils.answer(message, self.strings["checking_balance"]) try: - user = await self.client.get_entity(username) + balance = await self.get_star_balance() except Exception as e: - logging.error(f"User not found: {e}") - await utils.answer(msg, self.strings["user_not_found"]) + logging.error(f"Balance error: {e}") + await utils.answer(balance_msg, self.strings["balance_error"]) + return + + min_price = min(self.gift_categories.keys()) + if balance < min_price: + await utils.answer(balance_msg, self.strings["min_stars_error"]) + return + + available_categories = [price for price in self.gift_categories.keys() if balance >= price] + if not available_categories: + await utils.answer(balance_msg, self.strings["no_available_gifts"]) return buttons = [] - for row in self.gifts: - btn_row = [] - for gift in row: - btn_row.append({ - "text": gift["emoji"], - "callback": self._send_gift, - "args": (user.id, gift["id"], text, gift["emoji"], msg.id), - }) - buttons.append(btn_row) + row = [] + for price in sorted(available_categories): + row.append({ + "text": f"{price} ⭐", + "callback": self._show_category, + "args": (user.id, price, text, balance, message.id), + }) + if len(row) == 2: + buttons.append(row) + row = [] + + if row: + buttons.append(row) + await utils.answer( - msg, + balance_msg, self.strings["gift_menu"].format( f"@{user.username}" if user.username else user.first_name, + text if text else "-", + balance + ), + reply_markup=buttons + ) + + async def _show_category(self, call, user_id, price, text, balance, msg_id): + gifts = self.gift_categories[price] + buttons = [] + row = [] + for gift in gifts: + row.append({ + "text": gift["emoji"], + "callback": self._send_gift, + "args": (user_id, gift["id"], text, gift["emoji"], msg_id, balance), + }) + if len(row) == 3: + buttons.append(row) + row = [] + + if row: + buttons.append(row) + buttons.append([{ + "text": "⬅️ Назад", + "callback": self._back_to_categories, + "args": (user_id, text, balance, msg_id), + }]) + + try: + user = await self.client.get_entity(user_id) + user_display = f"@{user.username}" if user.username else user.first_name + except: + user_display = f"ID: {user_id}" + + await call.edit( + self.strings["category_menu"].format( + price, + user_display, text if text else "-" ), reply_markup=buttons ) - async def _send_gift(self, call, user_id, gift_id, text, gift_emoji, msg_id): + + async def _back_to_categories(self, call, user_id, text, balance, msg_id): + try: + user = await self.client.get_entity(user_id) + except: + await call.answer("Ошибка получения пользователя", show_alert=True) + return + + available_categories = [price for price in self.gift_categories.keys() if balance >= price] + + buttons = [] + row = [] + for price in sorted(available_categories): + row.append({ + "text": f"{price} ⭐", + "callback": self._show_category, + "args": (user_id, price, text, balance, msg_id), + }) + if len(row) == 2: + buttons.append(row) + row = [] + + if row: + buttons.append(row) + + await call.edit( + self.strings["gift_menu"].format( + f"@{user.username}" if user.username else user.first_name, + text if text else "-", + balance + ), + reply_markup=buttons + ) + + async def _send_gift(self, call, user_id, gift_id, text, gift_emoji, msg_id, balance): try: await call.edit( self.strings["sending_gift"], reply_markup=None ) + user = await self.client.get_input_entity(user_id) inv = InputInvoiceStarGift( user, @@ -116,6 +230,7 @@ class SenderGifts(loader.Module): result = await self.client(SendStarsFormRequest(form.form_id, inv)) await call.edit(self.strings["gift_sent"]) + except BadRequestError as e: if "BALANCE_TOO_LOW" in str(e): await call.edit( @@ -125,13 +240,12 @@ class SenderGifts(loader.Module): else: logging.error(f"Error sending gift: {e}") await call.edit( - f"❌ Ошибка при отправке подарка: {str(e)}", + f" Ошибка при отправке подарка: {str(e)}", reply_markup=None ) except Exception as e: logging.error(f"Error sending gift: {e}") await call.edit( - f"❌ Ошибка при отправке подарка: {str(e)}", + f" Ошибка при отправке подарка: {str(e)}", reply_markup=None ) -# эрон Дон Дон diff --git a/modules.json b/modules.json index bd93185..03ca1a9 100644 --- a/modules.json +++ b/modules.json @@ -9,33 +9,33 @@ }, "commands": [ { - "limokacmd": "[query] - Search module with filter options" + "limokacmd": "[query] - Search module with filter options [запрос] - Поиск модуля с опциями фильтрации" }, { - "lshistorycmd": "- Showing the last 10 requests" + "lshistorycmd": "- Showing the last 10 requests - Показать последние 10 запросов" }, { - "limokadotd": "- Show the Module of the Day" + "limokadotd": "- Show the Module of the Day - Показать модуль дня" } ], "new_commands": [ { "limoka": { - "ru_doc": null, + "ru_doc": "[запрос] - Поиск модуля с опциями фильтрации", "en_doc": null, "doc": "[query] - Search module with filter options" } }, { "lshistory": { - "ru_doc": null, + "ru_doc": " - Показать последние 10 запросов", "en_doc": null, "doc": "- Showing the last 10 requests" } }, { "limokadotd": { - "ru_doc": null, + "ru_doc": "- Показать модуль дня", "en_doc": null, "doc": "- Show the Module of the Day" } @@ -46,911 +46,74 @@ "Chat" ] }, - "iamnalinor/FTG-modules/speedtest.py": { - "name": "SpeedtestMod", - "description": "Tests your internet speed via speedtest.net", - "meta": { - "pic": null, - "banner": null, - "developer": "@nalinormods" - }, - "commands": [ - { - "speedtestcmd": "Run speedtest" - } - ], - "new_commands": [ - { - "speedtest": { - "ru_doc": null, - "en_doc": null, - "doc": "Run speedtest" - } - } - ], - "category": [ - "Tools", - "Fun" - ] - }, - "iamnalinor/FTG-modules/swmute.py": { - "name": "SwmuteMod", - "description": "Deletes messages from certain users", - "meta": { - "pic": null, - "banner": null, - "developer": "@nalinormods" - }, - "commands": [ - { - "swmutecmd": "