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": "