# ______ ___ ___ _ _ # ____ | ___ \ | \/ | | | | | # / __ \| |_/ / _| . . | ___ __| |_ _| | ___ # / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \ # | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/ # \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___| # \____/ __/ | # |___/ # На модуль распространяется лицензия "GNU General Public License v3.0" # https://github.com/all-licenses/GNU-General-Public-License-v3.0 # meta developer: @pymodule import logging import platform import socket import os import time import aiohttp import psutil import json import random from datetime import datetime, timezone, timedelta from typing import Optional, Dict, Any from collections import OrderedDict from .. import loader, utils, validators from telethon.tl.functions.users import GetFullUserRequest from herokutl.tl.functions.payments import GetStarsStatusRequest logger = logging.getLogger(__name__) class LRUCache: """LRU-кэш с TTL""" def __init__(self, max_size: int = 100, ttl: int = 300): self.cache = OrderedDict() self.max_size = max_size self.ttl = ttl self.timestamps = {} def get(self, key: str) -> Optional[Any]: if key not in self.cache: return None if time.time() - self.timestamps[key] > self.ttl: del self.cache[key] del self.timestamps[key] return None self.cache.move_to_end(key) return self.cache[key] def set(self, key: str, value: Any): if len(self.cache) >= self.max_size: oldest = next(iter(self.cache)) del self.cache[oldest] del self.timestamps[oldest] self.cache[key] = value self.timestamps[key] = time.time() @loader.tds class PlaceholdersMod(loader.Module): """Плейсхолдеры""" strings = {"name": "Placeholders+"} def __init__(self): self.config = loader.ModuleConfig( loader.ConfigValue( "timezone", 5, "Часовой пояс (offset от UTC)", validator=validators.Integer(), ), loader.ConfigValue( "weather_city", "Oral", "Город для погоды", validator=validators.String(), ), loader.ConfigValue( "lastfm_user", "", "Last.FM username", validator=validators.String(), ), loader.ConfigValue( "crypto_address", "YOUR_WALLET_ADDRESS", "Крипто-кошелёк", validator=validators.String(), ), loader.ConfigValue( "card_number", "**** **** **** ****", "Номер карты", validator=validators.String(), ), loader.ConfigValue( "donate_site", "Boosty:https://boosty.to/yourname", "Донат: имя:ссылка", validator=validators.String(), ), loader.ConfigValue( "channel", "@yourchannel", "Канал", validator=validators.String(), ), loader.ConfigValue( "social_network", "https://vk.com/your", "Соцсеть", validator=validators.String(), ), ) self.cache = LRUCache(max_size=100, ttl=300) async def client_ready(self, client, db): self._client = client self.session = aiohttp.ClientSession() self.me = await client.get_me() self.full_me = await client(GetFullUserRequest(self.me)) try: stars_status = await self._client(GetStarsStatusRequest(entity='me')) self.stars_balance = stars_status.balance except: self.stars_balance = 0 self.tz = timezone(timedelta(hours=self.config["timezone"])) self.weekdays_ru = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"] # Регистрация плейсхолдеров self._register_placeholders() def _register_placeholders(self): placeholders = [ ("username", self.get_username, "Username"), ("name", self.get_name, "Имя"), ("surname", self.get_surname, "Фамилия"), ("bio_description", self.get_bio, "Описание"), ("user_id", self.get_user_id, "ID"), ("phone_number", self.get_phone, "Телефон"), ("dc_id", self.get_dc_id, "DC ID"), ("amount_stars", self.get_stars, "Stars"), ("premium_check", self.get_premium_check, "Дата окончания Premium"), ("dollars_in_rub", self.get_usd_to_rub, "USD → RUB"), ("rub_in_dollars", self.get_rub_to_usd, "RUB → USD"), ("usdt_in_rub", self.get_usdt_to_rub, "USDT → RUB"), ("rub_in_usdt", self.get_rub_to_usdt, "RUB → USDT"), ("ton_in_rub", self.get_ton_to_rub, "TON → RUB"), ("rub_in_ton", self.get_rub_to_ton, "RUB → TON"), ("btc_in_rub", self.get_btc_to_rub, "BTC → RUB"), ("eth_in_rub", self.get_eth_to_rub, "ETH → RUB"), ("stars_in_rub", self.get_stars_to_rub, "Stars → RUB"), ("stars_in_ton", self.get_stars_to_ton, "Stars → TON"), ("stars_in_usdt", self.get_stars_to_usdt, "Stars → USDT"), ("os_uptime", self.get_os_uptime, "Аптайм системы"), ("internet_usage", self.get_internet_usage, "Статистика трафика"), ("speedtest", self.get_speedtest, "Скорость интернета"), ("host", self.get_host, "Hostname ОС"), ("shell", self.get_shell, "Оболочка"), ("gpu", self.get_gpu, "GPU"), ("disk", self.get_disk, "Использование диска"), ("local_ip", self.get_local_ip, "Локальный IP"), ("user_and_hostname", self.get_user_hostname, "user@hostname"), ("time", self.get_time, "Время"), ("date", self.get_date, "Дата"), ("day_of_the_week", self.get_weekday, "День недели"), ("data_and_time", self.get_date_time, "Дата и время"), ("data_and_time_and_day_of_the_week", self.get_full_date_time_weekday, "Дата, время, день недели"), ("weather", self.get_weather_condition, "Погода"), ("outdoor_temperature", self.get_temperature, "Температура"), ("weather_and_temperature", self.get_weather_temp, "Погода и температура"), ("humidity", self.get_humidity, "Влажность"), ("pressure", self.get_pressure, "Давление"), ("wind_speed", self.get_wind_speed, "Скорость ветра"), ("my_crypto_address", self.get_crypto_address, "Крипто-адрес"), ("my_card_number", self.get_card_number, "Номер карты"), ("my_donate_site", self.get_donate_site, "Донат"), ("my_channel", self.get_channel, "Канал"), ("my_social_network", self.get_social, "Соцсеть"), ("now_playing", self.get_now_playing, "Сейчас играет"), ("last_fm_user_and_now_playing", self.get_user_and_playing, "Last.FM + трек"), ("song_name", self.get_song_name, "Название трека"), ("song_artist", self.get_song_artist, "Артист"), ("last_fm_user", self.get_lastfm_user, "Last.FM username"), ("lastfm_stats", self.get_lastfm_stats, "Last.FM статистика"), ] for name, func, desc in placeholders: utils.register_placeholder(name, func, desc) async def get_premium_check(self): if not self.me.premium: return "Нет Premium" until = self.full_me.full_user.premium_until if not until or until < time.time(): return "Премиум закончился" end_date = datetime.fromtimestamp(until, tz=self.tz) days_left = (end_date.date() - datetime.now(self.tz).date()).days formatted = end_date.strftime("%d.%m.%Y") return f"{formatted} (Осталось {days_left} дней)" async def get_username(self): return f"@{self.me.username}" if self.me.username else "Нет" async def get_name(self): return self.me.first_name or "Нет" async def get_surname(self): return self.me.last_name or "Нет" async def get_bio(self): return self.full_me.full_user.about or "Нет описания" async def get_user_id(self): return str(self.me.id) async def get_phone(self): return self.me.phone or "Скрыт" async def get_dc_id(self): return str(self.me.dc_id if hasattr(self.me, "dc_id") else "Неизвестно") async def get_stars(self): result = await self.client(GetStarsStatusRequest("me")) stars = result.balance.amount if result and result.balance else 0 return f"{stars:,}".replace(",", " ") if stars else "0" async def get_usd_to_rub(self): cache_key = "usd_rub" cached = self.cache.get(cache_key) if cached: return cached try: async with self.session.get("https://www.cbr-xml-daily.ru/daily_json.js") as resp: data = await resp.json() rate = data["Valute"]["USD"]["Value"] result = f"1 USD ≈ {rate:.2f} RUB" self.cache.set(cache_key, result) return result except: try: async with self.session.get("https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/usd.json") as resp: data = await resp.json() rate = data["usd"]["rub"] result = f"1 USD ≈ {rate:.2f} RUB" self.cache.set(cache_key, result) return result except: return "Курс USD недоступен" async def get_rub_to_usd(self): usd_rub = await self.get_usd_to_rub() if "≈" in usd_rub: try: rate = float(usd_rub.split("≈")[1].strip().split()[0]) return f"1 RUB ≈ {1/rate:.4f} USD" except: pass return "Курс RUB недоступен" async def get_usdt_to_rub(self): return await self.get_usd_to_rub() # USDT ≈ USD async def get_rub_to_usdt(self): return await self.get_rub_to_usd() async def get_ton_to_rub(self): cache_key = "ton_rub" cached = self.cache.get(cache_key) if cached: return cached try: async with self.session.get("https://api.coingecko.com/api/v3/simple/price?ids=toncoin&vs_currencies=rub") as resp: data = await resp.json() rate = data["toncoin"]["rub"] result = f"1 TON ≈ {rate:.2f} RUB" self.cache.set(cache_key, result) return result except: return "Курс TON недоступен" async def get_rub_to_ton(self): ton_rub = await self.get_ton_to_rub() if "≈" in ton_rub: try: rate = float(ton_rub.split("≈")[1].strip().split()[0]) return f"1 RUB ≈ {1/rate:.6f} TON" except: pass return "Курс недоступен" async def get_btc_to_rub(self): cache_key = "btc_rub" cached = self.cache.get(cache_key) if cached: return cached try: async with self.session.get("https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=rub") as resp: data = await resp.json() rate = data["bitcoin"]["rub"] result = f"1 BTC ≈ {rate:,.0f} RUB" self.cache.set(cache_key, result) return result except: return "Курс BTC недоступен" async def get_eth_to_rub(self): cache_key = "eth_rub" cached = self.cache.get(cache_key) if cached: return cached try: async with self.session.get("https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=rub") as resp: data = await resp.json() rate = data["ethereum"]["rub"] result = f"1 ETH ≈ {rate:,.0f} RUB" self.cache.set(cache_key, result) return result except: return "Курс ETH недоступен" async def get_stars_to_rub(self): return "1 Star ≈ 85 RUB" async def get_stars_to_ton(self): return "1 Star ≈ 0.012 TON" async def get_stars_to_usdt(self): return "1 Star ≈ 0.92 USDT" async def get_os_uptime(self): boot = datetime.fromtimestamp(psutil.boot_time()) delta = datetime.now() - boot days = delta.days hours, remainder = divmod(delta.seconds, 3600) minutes, _ = divmod(remainder, 60) if days > 0: return f"{days}d {hours}h {minutes}m" else: return f"{hours}h {minutes}m" async def get_internet_usage(self): try: net = psutil.net_io_counters() sent_gb = net.bytes_sent // (1024**3) recv_gb = net.bytes_recv // (1024**3) return f"↑ {sent_gb} GB │ ↓ {recv_gb} GB" except: return "↑ 0 GB │ ↓ 0 GB" async def get_speedtest(self): cache_key = "speedtest" cached = self.cache.get(cache_key) if cached: return cached test_urls = [ "https://proof.ovh.net/files/10Mb.dat", "http://ipv4.download.thinkbroadband.com/10MB.zip", "https://speedtest.ftp.otenet.gr/files/test10Mb.db" ] for url in test_urls: try: start = time.time() async with self.session.get(url, timeout=10) as resp: chunk_size = 1024 * 1024 total = 0 async for chunk in resp.content.iter_chunked(chunk_size): total += len(chunk) if total >= chunk_size: break duration = time.time() - start if duration > 0: speed_mbps = (total * 8) / (duration * 1024 * 1024) result = f"≈ {speed_mbps:.1f} Mbps" self.cache.set(cache_key, result) return result except: continue return "Тест скорости недоступен" async def get_host(self): return platform.node() or "Неизвестно" async def get_shell(self): return os.environ.get("SHELL", "Неизвестно").split("/")[-1] async def get_gpu(self): return "N/A (Cloud)" async def get_disk(self): try: usage = psutil.disk_usage("/") percent = (usage.used / usage.total) * 100 used_gb = usage.used // (1024**3) total_gb = usage.total // (1024**3) return f"{used_gb} GB / {total_gb} GB ({percent:.1f}%)" except: return "Диск недоступен" async def get_local_ip(self): try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(("8.8.8.8", 80)) ip = s.getsockname()[0] s.close() return ip except: return "Неизвестно" async def get_user_hostname(self): user = os.getlogin() if hasattr(os, 'getlogin') else os.environ.get("USER", "user") host = await self.get_host() return f"{user}@{host}" async def get_time(self): return datetime.now(self.tz).strftime("%H:%M:%S") async def get_date(self): return datetime.now(self.tz).strftime("%d.%m.%Y") async def get_weekday(self): return self.weekdays_ru[datetime.now(self.tz).weekday()] async def get_date_time(self): return datetime.now(self.tz).strftime("%d.%m.%Y %H:%M") async def get_full_date_time_weekday(self): now = datetime.now(self.tz) return f"{now.strftime('%d.%m.%Y %H:%M')} ({self.weekdays_ru[now.weekday()]})" async def get_weather_condition(self): data = await self._get_weather_data() return data.get("condition", "Неизвестно") async def get_temperature(self): data = await self._get_weather_data() return data.get("temp", "??°C") async def get_weather_temp(self): data = await self._get_weather_data() return data.get("weather_temp", "??") async def get_humidity(self): data = await self._get_weather_data() return data.get("humidity", "??%") async def get_pressure(self): data = await self._get_weather_data() return data.get("pressure", "?? гПа") async def get_wind_speed(self): data = await self._get_weather_data() return data.get("wind", "?? м/с") async def _get_weather_data(self): city = self.config["weather_city"] cache_key = f"weather_{city}" cached = self.cache.get(cache_key) if cached: return cached try: async with self.session.get(f"http://wttr.in/{city}?format=j1&lang=ru") as resp: if resp.status == 200: data = await resp.json() c = data["current_condition"][0] weather_data = { "condition": c["lang_ru"][0]["value"], "temp": f"{c['temp_C']}°C", "weather_temp": f"{c['lang_ru'][0]['value']} {c['temp_C']}°C", "humidity": f"{c['humidity']}%", "pressure": f"{c['pressure']} мм", "wind": f"{c['windspeedKmph']} км/ч", } self.cache.set(cache_key, weather_data) return weather_data except: pass default = { "condition": "Неизвестно", "temp": "??°C", "weather_temp": "??", "humidity": "??%", "pressure": "?? мм", "wind": "?? км/ч", } self.cache.set(cache_key, default) return default async def get_crypto_address(self): return self.config["crypto_address"] async def get_card_number(self): return self.config["card_number"] async def get_donate_site(self): val = self.config["donate_site"] if ":" in val: name, link = val.split(":", 1) return f'{name.strip()}' return val async def get_channel(self): ch = self.config["channel"] if ch.startswith("@"): return f'{ch}' return ch async def get_social(self): return self.config["social_network"] async def get_lastfm_user(self): return self.config["lastfm_user"] or "Не указан" async def get_now_playing(self): track = await self._get_current_track() if not track: return "🎵 Ничего не играет" return f"🎵 {track['name']} — {track['artist']}" async def get_user_and_playing(self): user = await self.get_lastfm_user() track = await self._get_current_track() if not track: return f"{user}: ничего не играет" return f"{user}: {track['name']} — {track['artist']}" async def get_song_name(self): track = await self._get_current_track() return track["name"] if track else "—" async def get_song_artist(self): track = await self._get_current_track() return track["artist"] if track else "—" async def get_lastfm_stats(self): user = self.config["lastfm_user"] if not user: return "Укажите Last.FM username" cache_key = f"lastfm_stats_{user}" cached = self.cache.get(cache_key) if cached: return cached api_key = "460cda35be2fbf4f28e8ea7a38580730" try: async with self.session.get( "http://ws.audioscrobbler.com/2.0/", params={ "method": "user.getinfo", "user": user, "api_key": api_key, "format": "json" } ) as resp: data = await resp.json() if "user" in data: stats = data["user"] result = f"🎵 {stats['playcount']} скробблов" self.cache.set(cache_key, result) return result except: pass return "Статистика недоступна" async def _get_current_track(self): user = self.config["lastfm_user"] if not user: return None cache_key = f"lastfm_track_{user}" cached = self.cache.get(cache_key) if cached: return cached api_key = "460cda35be2fbf4f28e8ea7a38580730" try: async with self.session.get( "http://ws.audioscrobbler.com/2.0/", params={ "method": "user.getrecenttracks", "user": user, "api_key": api_key, "format": "json", "limit": 1 } ) as resp: data = await resp.json() tracks = data.get("recenttracks", {}).get("track", []) if tracks: track = tracks[0] now_playing = "@attr" in track and "nowplaying" in track["@attr"] result = { "name": track["name"], "artist": track["artist"]["#text"], "now_playing": now_playing } self.cache.set(cache_key, result) return result except: pass return None async def on_unload(self): await self.session.close()