Added and updated repositories 2025-11-07 01:04:48

This commit is contained in:
github-actions[bot]
2025-11-07 01:04:48 +00:00
parent d036812a2a
commit e00a5bd171
14 changed files with 1687 additions and 615 deletions

View File

@@ -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,
"<b>Укажите адрес, например:</b> <code>.sendgeo Москва, Манежная улица, 2</code>"
)
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, "<b>Координаты не найдены.</b>")

View File

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

View File

@@ -63,7 +63,7 @@ async def get_whois(identifier, API_KEY: str) -> dict:
return response return response
async def fetch_dns_record(session, domain, record_type): 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"} headers = {"accept": "application/dns-json"}
params = {"name": domain, "type": record_type} params = {"name": domain, "type": record_type}

View File

@@ -69,7 +69,8 @@ class PicMe(loader.Module):
async def watcher(self, event): async def watcher(self, event):
try: try:
if event.from_id != self.tg_id: if event.from_id != self.tg_id:
return if event.from_id.user_id != self.tg_id:
return
except: except:
return return
if not self.db.get(self.name, "picme", False): if not self.db.get(self.name, "picme", False):

View File

@@ -11,7 +11,7 @@
# https://github.com/all-licenses/GNU-General-Public-License-v3.0 # https://github.com/all-licenses/GNU-General-Public-License-v3.0
# meta developer: @pymodule # meta developer: @pymodule
# requires: opencv-python # requires: opencv-python pillow
import os, shutil, cv2 import os, shutil, cv2
from PIL import Image, UnidentifiedImageError from PIL import Image, UnidentifiedImageError

View File

@@ -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": "🔍 Ищу устройства по запросу: <b>{}</b>...",
"no_query": "❌ Укажи название устройства! Пример: <code>.di iPhone 15</code>",
"no_results": "📭 Устройства не найдены для запросу: <b>{}</b>",
"device_list": "📱 Найдено {} устройств по запросу <b>{}</b>:",
"device_info": "📱 <b>{}</b>\n\n{}",
"error": "❌ Ошибка: {}. Попробуй позже или проверь API.",
"network": "📡 <b>Сеть</b>: {}\n",
"launched": "📅 <b>Дата выпуска</b>:\n Анонс: {}\n Статус: {}\n",
"body": "📏 <b>Корпус</b>:\n Размеры: {}\n Вес: {}\n SIM: {}\n Прочее: {}\n",
"display": "🖥️ <b>Дисплей</b>:\n Тип: {}\n Размер: {}\n Разрешение: {}\n Защита: {}\n",
"platform": "⚙️ <b>Платформа</b>:\n ОС: {}\n Чипсет: {}\n CPU: {}\n GPU: {}\n",
"memory": "💾 <b>Память</b>:\n Карта памяти: {}\n Внутренняя: {}\n Прочее: {}\n",
"main_camera": "📷 <b>Основная камера</b>:\n Модули: {}\n Функции: {}\n Видео: {}\n",
"selfie_camera": "🤳 <b>Фронтальная камера</b>:\n Модули: {}\n Функции: {}\n Видео: {}\n",
"sound": "🔊 <b>Звук</b>:\n Динамик: {}\n Аудиоразъём: {}\n Прочее: {}\n",
"comms": "🌐 <b>Связь</b>:\n Wi-Fi: {}\n Bluetooth: {}\n GPS: {}\n NFC: {}\n Инфракрасный порт: {}\n Радио: {}\n USB: {}\n",
"sensors": "🛠️ <b>Датчики</b>: {}\n",
"battery": "🔋 <b>Батарея</b>:\n Тип: {}\n Зарядка: {}\n",
"misc": "🎨 <b>Разное</b>:\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: <b>{}</b>...",
"no_query": "❌ Specify a device name! Example: <code>.di iPhone 15</code>",
"no_results": "📭 No devices found for query: <b>{}</b>",
"device_list": "📱 Found {} devices for query <b>{}</b>:",
"device_info": "📱 <b>{}</b>\n\n{}",
"error": "❌ Error: {}. Try again later or check the API.",
"network": "📡 <b>Network</b>: {}\n",
"launched": "📅 <b>Launch</b>:\n Announced: {}\n Status: {}\n",
"body": "📏 <b>Body</b>:\n Dimensions: {}\n Weight: {}\n SIM: {}\n Other: {}\n",
"display": "🖥️ <b>Display</b>:\n Type: {}\n Size: {}\n Resolution: {}\n Protection: {}\n",
"platform": "⚙️ <b>Platform</b>:\n OS: {}\n Chipset: {}\n CPU: {}\n GPU: {}\n",
"memory": "💾 <b>Memory</b>:\n Card slot: {}\n Internal: {}\n Other: {}\n",
"main_camera": "📷 <b>Main Camera</b>:\n Modules: {}\n Features: {}\n Video: {}\n",
"selfie_camera": "🤳 <b>Selfie Camera</b>:\n Modules: {}\n Features: {}\n Video: {}\n",
"sound": "🔊 <b>Sound</b>:\n Loudspeaker: {}\n Audio Jack: {}\n Other: {}\n",
"comms": "🌐 <b>Comms</b>:\n Wi-Fi: {}\n Bluetooth: {}\n GPS: {}\n NFC: {}\n Infrared: {}\n Radio: {}\n USB: {}\n",
"sensors": "🛠️ <b>Sensors</b>: {}\n",
"battery": "🔋 <b>Battery</b>:\n Type: {}\n Charging: {}\n",
"misc": "🎨 <b>Misc</b>:\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) <device name> - 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
)

View File

@@ -17,4 +17,7 @@ qrgen
wiki wiki
checkhost checkhost
createavatarspack createavatarspack
multiunloadmodule multiunloadmodule
tagall2.0
point
deviceinfo

View File

@@ -2,6 +2,8 @@
# https://github.com/all-licenses/GNU-General-Public-License-v3.0 # https://github.com/all-licenses/GNU-General-Public-License-v3.0
# meta developer: @PyModule # meta developer: @PyModule
# requires: lyricsgenius===3.7.0
from lyricsgenius import Genius from lyricsgenius import Genius
from .. import loader, utils from .. import loader, utils

View File

@@ -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": "<b>The module is activated ✅</b>",
"disabled": "<b>The module is deactivated ❌</b>",
"status": "Current status: {status}\nIgnore channels: {ignore_channels}\n\nUsage:\n<code>.pointcase on|off</code>\n<code>.pointcaseignore on|off</code>",
"status_on": "✅ Enabled",
"status_off": "❌ Off",
"ignore_on": "✅ Ignoring channels",
"ignore_off": "❌ Not ignoring channels",
}
strings_ru = {
"_cls_doc": "Автоматически делает первую букву каждого предложения заглавной и добавляет точку в конце сообщения (если её нет).",
"enabled": "<b>Модуль активирован ✅</b>",
"disabled": "<b>Модуль деактивирован ❌</b>",
"status": "Текущий статус: {status}\nИгнорировать каналы: {ignore_channels}\n\nИспользование:\n<code>.pointcase on|off</code>\n<code>.pointcaseignore on|off</code>",
"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",
])

View File

@@ -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": "✅ <b>{}</b> 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": "✅ Упомянуто <b>{}</b> пользователей",
"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"<span class='tg-spoiler'><a href='tg://user?id={u.id}'>{u.first_name or 'User'}</a></span>" for u in batch])
msg_text = f"<b>{text}</b>\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

View File

@@ -0,0 +1 @@
эрон дон дон

View File

@@ -1,5 +1,5 @@
# -- version -- # -- version --
__version__ = (1, 0, 0) __version__ = (1, 2, 0)
# -- version -- # -- version --
@@ -17,95 +17,209 @@ __version__ = (1, 0, 0)
# scope: heroku_only # scope: heroku_only
from .. import loader, utils 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.tl.types import InputInvoiceStarGift, TextWithEntities
from herokutl.errors.rpcerrorlist import BadRequestError from herokutl.errors.rpcerrorlist import BadRequestError
import logging import logging
@loader.tds @loader.tds
class SenderGifts(loader.Module): class SenderGifts(loader.Module):
"""Модуль для отправки подарков""" """Модуль для отправки подарков Telegram прямиком в чате"""
strings = { strings = {
"name": "SenderGifts", "name": "SenderGifts",
"usage": "<emoji document_id=4958526153955476488>❌</emoji> Используйте в формате: <code>.sendgift @username текст</code>", "usage": "<emoji document_id=4958526153955476488>❌</emoji> Используйте в формате: <code>.sendgift @username текст</code> или реплай + <code>.sendgift текст</code>",
"checking_user": "<emoji document_id=5206634672204829887>🔍</emoji> Проверка пользователя...", "checking_user": "<emoji document_id=5206634672204829887>🔍</emoji> Проверка пользователя...",
"checking_balance": "<emoji document_id=5206634672204829887>🔍</emoji> Проверка баланса...",
"user_not_found": "<emoji document_id=4958526153955476488>❌</emoji> Пользователь не найден", "user_not_found": "<emoji document_id=4958526153955476488>❌</emoji> Пользователь не найден",
"gift_menu": "<emoji document_id=5931696400982088015>🎁</emoji> Выберите подарок.\n\n<emoji document_id=6032693626394382504>👤</emoji> Пользователь: {}\n<emoji document_id=5873153278023307367>📄</emoji> Текст: {}", "gift_menu": "<emoji document_id=5931696400982088015>🎁</emoji> Выберите категорию подарков.\n\n<emoji document_id=6032693626394382504>👤</emoji> Пользователь: {}\n<emoji document_id=5873153278023307367>📄</emoji> Текст: {}\n<emoji document_id=5951810621887484519>⭐</emoji> Баланс: {} звезд",
"category_menu": "<emoji document_id=5931696400982088015>🎁</emoji> Подарки за {}\n\n<emoji document_id=6032693626394382504>👤</emoji> Пользователь: {}\n<emoji document_id=5873153278023307367>📄</emoji> Текст: {}",
"sending_gift": "<emoji document_id=5201691993775818138>🛫</emoji> Отправка подарка...", "sending_gift": "<emoji document_id=5201691993775818138>🛫</emoji> Отправка подарка...",
"gift_sent": "<emoji document_id=5021905410089550576>✅</emoji> Подарок успешно отправлен!", "gift_sent": "<emoji document_id=5021905410089550576>✅</emoji> Подарок успешно отправлен!",
"not_enough_stars": "<emoji document_id=4958526153955476488>❌</emoji> Недостаточно звезд для отправки подарка {}!", "not_enough_stars": "<emoji document_id=4958526153955476488>❌</emoji> Недостаточно звезд для отправки подарка {}!",
"min_stars_error": "<emoji document_id=4958526153955476488>❌</emoji> Недостаточно звезд для отправки минимального подарка!",
"no_available_gifts": "<emoji document_id=4958526153955476488>❌</emoji> Нет доступных подарков для вашего баланса",
"balance_error": "<emoji document_id=4958526153955476488>❌</emoji> Ошибка при проверке баланса",
} }
gifts = [ gift_categories = {
[ 15: [
{"id": 5170145012310081615, "stars": 15, "emoji": "❤️", "name": "Сердце"}, {"id": 5170145012310081615, "emoji": "❤️", "name": "Сердце"},
{"id": 5170233102089322756, "stars": 15, "emoji": "🧸", "name": "Мишка"}, {"id": 5170233102089322756, "emoji": "🧸", "name": "Мишка"},
{"id": 5170250947678437525, "stars": 25, "emoji": "🎁", "name": "Подарок"},
], ],
[ 25: [
{"id": 5168103777563050263, "stars": 25, "emoji": "🌹", "name": "Роза"}, {"id": 5170250947678437525, "emoji": "🎁", "name": "Подарок"},
{"id": 5170144170496491616, "stars": 50, "emoji": "🎂", "name": "Тортик"}, {"id": 5168103777563050263, "emoji": "🌹", "name": "Роза"},
{"id": 5170314324215857265, "stars": 50, "emoji": "💐", "name": "Цветы"},
], ],
[ 50: [
{"id": 5170564780938756245, "stars": 50, "emoji": "🚀", "name": "Ракета"}, {"id": 5170144170496491616, "emoji": "🎂", "name": "Тортик"},
{"id": 5168043875654172773, "stars": 100, "emoji": "🏆", "name": "Кубок"}, {"id": 5170314324215857265, "emoji": "💐", "name": "Цветы"},
{"id": 5170690322832818290, "stars": 100, "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): async def client_ready(self, client, db):
self.client = client 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() @loader.command()
async def sendgift(self, message): async def sendgift(self, message):
"""Отправить подарок пользователю""" """- <username> <text*> - отправить подарок пользователю (* - необязательный параметр.) Поддерживается реплай режим."""
args = utils.get_args_raw(message) args = utils.get_args_raw(message)
if not args: reply = await message.get_reply_message()
await utils.answer(message, self.strings["usage"]) if reply:
return user = reply.sender
text = args if args else ""
parts = args.split(maxsplit=1) else:
if len(parts) < 1: if not args:
await utils.answer(message, self.strings["usage"]) await utils.answer(message, self.strings["usage"])
return return
parts = args.split(maxsplit=1)
username = parts[0] if len(parts) < 1:
text = parts[1] if len(parts) > 1 else "" await utils.answer(message, self.strings["usage"])
if username.startswith('@'): return
username = username[1:] username = parts[0]
msg = await utils.answer(message, self.strings["checking_user"]) 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: try:
user = await self.client.get_entity(username) balance = await self.get_star_balance()
except Exception as e: except Exception as e:
logging.error(f"User not found: {e}") logging.error(f"Balance error: {e}")
await utils.answer(msg, self.strings["user_not_found"]) 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 return
buttons = [] buttons = []
for row in self.gifts: row = []
btn_row = [] for price in sorted(available_categories):
for gift in row: row.append({
btn_row.append({ "text": f"{price}",
"text": gift["emoji"], "callback": self._show_category,
"callback": self._send_gift, "args": (user.id, price, text, balance, message.id),
"args": (user.id, gift["id"], text, gift["emoji"], msg.id), })
}) if len(row) == 2:
buttons.append(btn_row) buttons.append(row)
row = []
if row:
buttons.append(row)
await utils.answer( await utils.answer(
msg, balance_msg,
self.strings["gift_menu"].format( self.strings["gift_menu"].format(
f"@{user.username}" if user.username else user.first_name, 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 "-" text if text else "-"
), ),
reply_markup=buttons 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: try:
await call.edit( await call.edit(
self.strings["sending_gift"], self.strings["sending_gift"],
reply_markup=None reply_markup=None
) )
user = await self.client.get_input_entity(user_id) user = await self.client.get_input_entity(user_id)
inv = InputInvoiceStarGift( inv = InputInvoiceStarGift(
user, user,
@@ -116,6 +230,7 @@ class SenderGifts(loader.Module):
result = await self.client(SendStarsFormRequest(form.form_id, inv)) result = await self.client(SendStarsFormRequest(form.form_id, inv))
await call.edit(self.strings["gift_sent"]) await call.edit(self.strings["gift_sent"])
except BadRequestError as e: except BadRequestError as e:
if "BALANCE_TOO_LOW" in str(e): if "BALANCE_TOO_LOW" in str(e):
await call.edit( await call.edit(
@@ -125,13 +240,12 @@ class SenderGifts(loader.Module):
else: else:
logging.error(f"Error sending gift: {e}") logging.error(f"Error sending gift: {e}")
await call.edit( await call.edit(
f" Ошибка при отправке подарка: {str(e)}", f"<emoji document_id=4958526153955476488>❌</emoji> Ошибка при отправке подарка: {str(e)}",
reply_markup=None reply_markup=None
) )
except Exception as e: except Exception as e:
logging.error(f"Error sending gift: {e}") logging.error(f"Error sending gift: {e}")
await call.edit( await call.edit(
f" Ошибка при отправке подарка: {str(e)}", f"<emoji document_id=4958526153955476488>❌</emoji> Ошибка при отправке подарка: {str(e)}",
reply_markup=None reply_markup=None
) )
# эрон Дон Дон

View File

@@ -0,0 +1,63 @@
en:
guide: "<emoji document_id=5956561916573782596>📜</emoji> <b><a href=\"https://yandex-music.rtfd.io/en/main/token.html\">Guide for obtaining access token for Yandex.Music</a></b>"
iguide: "📜 <b><a href=\"https://yandex-music.rtfd.io/en/main/token.html\">Guide for obtaining access token for Yandex.Music</a></b>"
no_token: "<emoji document_id=5778527486270770928>❌</emoji> <b>You didn't specify the access token in the config!</b>"
autobio:
d: "<emoji document_id=5429189857324841688>🎧</emoji> <b>Autobio is off now</b>"
e: "<emoji document_id=5429189857324841688>🎧</emoji> <b>Autobio is on now</b>"
there_is_no_playing: "<emoji document_id=5474140048741901455>❌</emoji> <b>You don't listening to anything right now</b>"
queue_types:
VARIOUS: "Your queue"
RADIO: "«My Wave»"
PLAYLIST: "Playlist «{}»"
ALBUM: "«{}»"
ARTIST: "Popular tracks by {}"
downloading: "\n\n<emoji document_id=5841359499146825803>🕔</emoji> <i>Downloading audio…</i>"
uploading_banner: "\n\n<emoji document_id=5841359499146825803>🕔</emoji> <i>Uploading banner…</i>"
likes:
liked: "<emoji document_id=6037533152593842454>❤️</emoji> <b>Track <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> was liked</b>"
unliked: "<emoji document_id=5992453811510186287>❤️</emoji> <b>Track <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> was unliked</b>"
disliked: "<emoji document_id=5222400230133081714>💔</emoji> <b>Track <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> was disliked</b>"
lyrics: "<emoji document_id=5956561916573782596>📜</emoji> <b>Lyrics of the <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> track:</b>\n<blockquote expandable>{text}</blockquote>\n\n<emoji document_id=5247213725080890199>©️</emoji> <b>Writers:</b> {writers}"
no_lyrics: "<emoji document_id=5886285363869126932>❌</emoji> <b>Track <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> has no lyrics!</b>"
args: "<emoji document_id=5778527486270770928>❌</emoji> <b>Specify search query</b>"
searching: "<emoji document_id=5258274739041883702>🔍</emoji> <b>Searching…</b>"
404: "<emoji document_id=5778527486270770928>❌</emoji> <b>No results found</b>"
search: "<emoji document_id=5474304919651491706>🎧</emoji> <b>{performer} — {title}</b>\n<emoji document_id=5429189857324841688>🎵</emoji> <b><a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">Yandex.Music</a> | <a href=\"https://song.link/ya/{track_id}\">song.link</a></b>"
_cfg:
token: "Your access token for Yandex.Music"
now_playing_text: "The text that is used in commands to get now playing track. May contain {performer}, {title}, {device}, {volume}, {playing_from}, {link}, {track_id}, {album_id} keywords"
autobio: "Automatic bio template (may contain {artist} and {title} keywords)"
no_playing_bio: "Bio that is set when nothing is playing"
ru:
guide: "<emoji document_id=5956561916573782596>📜</emoji> <b><a href=\"https://yandex-music.rtfd.io/en/main/token.html\">Гайд по получению токена Яндекс.Музыки</a></b>"
iguide: "📜 <b><a href=\"https://yandex-music.rtfd.io/en/main/token.html\">Гайд по получению токена Яндекс.Музыки</a></b>"
no_token: "<emoji document_id=5312526098750252863>❌</emoji> <b>Вы не указали токен Яндекс.Музыки в конфиге!</b>"
autobio:
d: "<emoji document_id=5429189857324841688>🎧</emoji> <b>Автобио выключено</b>"
e: "<emoji document_id=5429189857324841688>🎧</emoji> <b>Автобио включено</b>"
there_is_no_playing: "<emoji document_id=5474140048741901455>❌</emoji> <b>Вы ничего не слушаете сейчас</b>"
queue_types:
VARIOUS: "Ваша очередь"
RADIO: "«Моя Волна»"
PLAYLIST: "Плейлист «{}»"
ALBUM: "«{}»"
ARTIST: "Популярные треки {}"
downloading: "\n\n<emoji document_id=5841359499146825803>🕔</emoji> <i>Загрузка трека…</i>"
uploading_banner: "\n\n<emoji document_id=5841359499146825803>🕔</emoji> <i>Загрузка баннера…</i>"
likes:
liked: "<emoji document_id=6037533152593842454>❤️</emoji> <b>Трек <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> лайкнут</b>"
unliked: "<emoji document_id=5992453811510186287>❤️</emoji> <b>С трека <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> снят лайк</b>"
disliked: "<emoji document_id=5222400230133081714>💔</emoji> <b>Трек <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> дизлайкнут</b>"
lyrics: "<emoji document_id=5956561916573782596>📜</emoji> <b>Текст трека <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a>:</b>\n<blockquote expandable>{text}</blockquote>\n\n<emoji document_id=5247213725080890199>©️</emoji> <b>Авторы:</b> {writers}"
no_lyrics: "<emoji document_id=5886285363869126932>❌</emoji> <b>У трека <a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">{track}</a> нет текста!</b>"
args: "<emoji document_id=5312526098750252863>❌</emoji> <b>Укажите поисковый запрос</b>"
searching: "<emoji document_id=5258274739041883702>🔍</emoji> <b>Ищем…</b>"
404: "<emoji document_id=5312526098750252863>❌</emoji> <b>Ничего не найдено</b>"
search: "<emoji document_id=5474304919651491706>🎧</emoji> <b>{performer} — {title}</b>\n<emoji document_id=5429189857324841688>🎵</emoji> <b><a href=\"https://music.yandex.ru/album/{album_id}/track/{track_id}\">Яндекс.Музыка</a> | <a href=\"https://song.link/ya/{track_id}\">song.link</a></b>"
_cfg:
token: "Ваш токен от Яндекс.Музыки"
now_playing_text: "Текст, использующийся в командах для получения прослушиваемого трека. Может содержать ключевые слова {performer}, {title}, {device}, {volume}, {playing_from}, {link}, {track_id}, {album_id}"
autobio: "Шаблон автоматического био (может содержать ключевые слова {artist} и {title})"
no_playing_bio: "Био, которое ставится, когда ничего не играет"

File diff suppressed because it is too large Load Diff