# ______ ___ ___ _ _ # ____ | ___ \ | \/ | | | | | # / __ \| |_/ / _| . . | ___ __| |_ _| | ___ # / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \ # | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/ # \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___| # \____/ __/ | # |___/ # На модуль распространяется лицензия "GNU General Public License v3.0" # https://github.com/all-licenses/GNU-General-Public-License-v3.0 # meta developer: @pymodule import aiohttp import base64 import json from datetime import datetime from .. import loader, utils async def download_image(session: aiohttp.ClientSession, url: str): try: async with session.get(url, timeout=35) as resp: if resp.status == 200: return await resp.read() except Exception: pass return None @loader.tds class MinecraftPlayerInfo(loader.Module): """A module for obtaining information about a Minecraft player by nickname""" strings = { "name": "MinecraftPlayerInfo", "no_args": "❌ Specify the player's nickname", "not_found": "❌ Player with this nickname not found", "loading": "🔄 Loading information...", "no_media": "❌ Failed to load any images", "partial_media": "⚠️ Some images failed to load\n\n", "no_history": "No nickname history", "model_steve": "Classic (Steve)", "model_alex": "Slim (Alex)", "cape_yes": "Yes ✅", "cape_no": "No ❌", "cape_failed": " (render failed to load)", "history_current": "— current", "history_changed": "— changed {}", "history_original": "— original", "info": ( "🔍 Minecraft Player Information\n\n" "Nickname: {name}\n" "UUID: {uuid_dashed}\n" "Skin Model: {model}\n" "Cape: {cape}\n\n" "Nickname History:\n{history}\n\n" "🔗 Full profile on NameMC" ) } strings_ru = { "_cls_doc": "Модуль для получения информации о игроке Minecraft по никнейму", "no_args": "❌ Укажите никнейм игрока", "not_found": "❌ Игрок с таким никнеймом не найден", "loading": "🔄 Загружаю информацию...", "no_media": "❌ Не удалось загрузить ни одного изображения", "partial_media": "⚠️ Некоторые изображения не загрузились\n\n", "no_history": "Нет истории изменений", "model_steve": "Classic (Steve)", "model_alex": "Slim (Alex)", "cape_yes": "Есть ✅", "cape_no": "Нет ❌", "cape_failed": " (рендер не загрузился)", "history_current": "— текущий", "history_changed": "— изменён {}", "history_original": "— оригинальный", "info": ( "🔍 Информация о игроке Minecraft\n\n" "Никнейм: {name}\n" "UUID: {uuid_dashed}\n" "Модель скина: {model}\n" "Плащ: {cape}\n\n" "История никнеймов:\n{history}\n\n" "🔗 Полный профиль на NameMC" ) } async def client_ready(self, client, db): self.client = client @loader.command(ru_doc="<никнейм> — отображает информацию об игроке Minecraft (3D-рендеринг, история, плащ)") async def mcplayer(self, message): """ — show Minecraft player info (3D renders, history, cape)""" args = utils.get_args_raw(message) if not args: return await utils.answer(message, self.strings("no_args")) loading_msg = await utils.answer(message, self.strings("loading")) nick = args.strip() async with aiohttp.ClientSession() as session: async with session.get(f"https://api.mojang.com/users/profiles/minecraft/{nick}") as resp: if resp.status == 204 or resp.status != 200: await loading_msg.edit(self.strings("not_found")) return data = await resp.json() name = data["name"] uuid = data["id"] uuid_dashed = f"{uuid[:8]}-{uuid[8:12]}-{uuid[12:16]}-{uuid[16:20]}-{uuid[20:]}" async with session.get(f"https://api.mojang.com/user/profiles/{uuid}/names") as resp: names = await resp.json() if resp.status == 200 else [{"name": name}] history_lines = [] for i, entry in enumerate(names): uname = entry["name"] if i == len(names) - 1: history_lines.append(f"• {uname} {self.strings('history_current')}") elif "changedToAt" in entry: changed = datetime.utcfromtimestamp(entry["changedToAt"] / 1000).strftime("%d.%m.%Y") history_lines.append(f"• {uname} {self.strings('history_changed').format(changed)}") else: history_lines.append(f"• {uname} {self.strings('history_original')}") history = "\n".join(history_lines) or self.strings("no_history") async with session.get(f"https://sessionserver.mojang.com/session/minecraft/profile/{uuid}?unsigned=false") as resp: profile = await resp.json() if resp.status == 200 else {} cape_url = None model = self.strings("model_steve") if profile.get("properties"): for prop in profile["properties"]: if prop["name"] == "textures": textures_b64 = prop["value"] textures_json = json.loads(base64.b64decode(textures_b64).decode("utf-8")) textures = textures_json.get("textures", {}) skin_data = textures.get("SKIN", {}) cape_url = textures.get("CAPE", {}).get("url") if "metadata" in skin_data and skin_data["metadata"].get("model") == "slim": model = self.strings("model_alex") has_cape = bool(cape_url) cape_text = self.strings("cape_yes") if has_cape else self.strings("cape_no") body_urls = [ f"https://crafthead.net/body/{uuid}.png", f"https://api.mineatar.io/body/{uuid}?scale=12", f"https://mc-heads.net/body/{uuid}/500", f"https://cravatar.eu/helmbody/{uuid}/500.png", f"https://minotar.net/body/{uuid}/500.png", ] head_urls = [ f"https://crafthead.net/avatar/{uuid}.png", f"https://api.mineatar.io/head/{uuid}?scale=12", f"https://mc-heads.net/avatar/{uuid}/500", f"https://cravatar.eu/head/{uuid}/500.png", f"https://minotar.net/avatar/{uuid}/500.png", ] body_bytes = None for url in body_urls: body_bytes = await download_image(session, url) if body_bytes: break head_bytes = None for url in head_urls: head_bytes = await download_image(session, url) if head_bytes: break cape_bytes = await download_image(session, cape_url) if cape_url else None if not cape_bytes and has_cape: cape_fallbacks = [ f"https://crafthead.net/cape/{uuid}.png", f"https://api.mineatar.io/cape/{uuid}.png", f"https://mc-heads.net/cape/{uuid}", ] for url in cape_fallbacks: cape_bytes = await download_image(session, url) if cape_bytes: break if not cape_bytes: cape_text += self.strings("cape_failed") uploaded_media = [] if body_bytes: uploaded = await self.client.upload_file(body_bytes, file_name=f"{name}_body.png") uploaded_media.append(uploaded) if head_bytes: uploaded = await self.client.upload_file(head_bytes, file_name=f"{name}_head.png") uploaded_media.append(uploaded) if cape_bytes: uploaded = await self.client.upload_file(cape_bytes, file_name=f"{name}_cape.png") uploaded_media.append(uploaded) if not uploaded_media: await loading_msg.edit(self.strings("no_media")) return caption = self.strings("info").format( name=name, uuid_dashed=uuid_dashed, uuid_raw=uuid, model=model, cape=cape_text, history=history ) if not (body_bytes and head_bytes): caption = self.strings("partial_media") + caption await self.client.send_file( message.peer_id, file=uploaded_media, caption=caption, parse_mode="html", reply_to=message.reply_to_msg_id or message.id ) await loading_msg.delete() if message.out: await message.delete()