# ©️ Dan Gazizullin, 2021-2023 # This file is a part of Hikka Userbot # 🌐 https://github.com/hikariatama/Hikka # You can redistribute it and/or modify it under the terms of the GNU AGPLv3 # 🔑 https://www.gnu.org/licenses/agpl-3.0.html # ©️ Codrago, 2024-2025 # This file is a part of Heroku Userbot # 🌐 https://github.com/coddrago/Heroku # You can redistribute it and/or modify it under the terms of the GNU AGPLv3 # 🔑 https://www.gnu.org/licenses/agpl-3.0.html import git import time import git import psutil import os import glob import requests import re import emoji from bs4 import BeautifulSoup from typing import Optional from pathlib import Path from PIL import Image, ImageDraw, ImageFont from io import BytesIO from herokutl.tl.types import Message from herokutl.utils import get_display_name from .. import loader, utils, version import platform as lib_platform import getpass @loader.tds class HerokuInfoMod(loader.Module): """Show userbot info""" strings = {"name": "HerokuInfo"} def __init__(self): self.config = loader.ModuleConfig( loader.ConfigValue( "custom_message", doc=lambda: self.strings("_cfg_cst_msg"), ), loader.ConfigValue( "banner_url", "https://imgur.com/a/7LBPJiq.png", lambda: self.strings("_cfg_banner"), ), loader.ConfigValue( "show_heroku", True, validator=loader.validators.Boolean(), ), loader.ConfigValue( "ping_emoji", "🪐", lambda: self.strings["ping_emoji"], validator=loader.validators.String(), ), loader.ConfigValue( "switchInfo", False, "Switch info to mode photo", validator=loader.validators.Boolean(), ), loader.ConfigValue( "imgSettings", ["Лапокапканот", 30, '#000', '0|0', "mm", 0, '#000'], "Image settings\n1. Дополнительный ник (если прежний ник не отображается)\n2. Размер шрифта\n3. Цвет шрифта в HEX формате '#000'\n4. Координаты текста '100|100', по умолчания в центре фотографии\n5. Якорь текста -> https://pillow.readthedocs.io/en/stable/_images/anchor_horizontal.svg\n6. Размер обводки, по умолчанию 0\n7. Цвет обводки в HEX формате '#000'", validator=loader.validators.Series( fixed_len=7, ), ), ) def _get_os_name(self): try: with open("/etc/os-release", "r") as f: for line in f: if line.startswith("PRETTY_NAME"): return line.split("=")[1].strip().strip('"') except FileNotFoundError: return self.strings['non_detectable'] def remove_emoji_and_html(self, text: str) -> str: reg = r'<[^<]+?>' text = f"{re.sub(reg, '', text)}" allchars = [str for str in text] emoji_list = [c for c in allchars if c in emoji.EMOJI_DATA] clean_text = ''.join([str for str in text if not any(i in str for i in emoji_list)]) return clean_text def imgurpidor(self, url: str) -> str: page = requests.get(url, stream=True, headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"}) soup = BeautifulSoup(page.text, 'html.parser') metatag = soup.find("meta", property="og:image") return metatag['content'] def _render_info(self, start: float) -> str: try: repo = git.Repo(search_parent_directories=True) diff = repo.git.log([f"HEAD..origin/{version.branch}", "--oneline"]) upd = ( self.strings("update_required").format(prefix=self.get_prefix()) if diff else self.strings("up-to-date") ) except Exception: upd = "" me = self.config['imgSettings'][0] if (self.config['imgSettings'][0] != "Лапокапканот") and self.config['switchInfo'] else '{}'.format( self._client.heroku_me.id, utils.escape_html(get_display_name(self._client.heroku_me)), ).replace('{', '').replace('}', '') build = utils.get_commit_url() _version = f'{".".join(list(map(str, list(version.__version__))))}' prefix = f"«{utils.escape_html(self.get_prefix())}»" platform = utils.get_named_platform() for emoji, icon in [ ("🍊", "🧡"), ("🍇", "💜"), ("😶‍🌫️", "😶‍🌫️"), ("❓", "📱"), ("🍀", "🍀"), ("🦾", "🦾"), ("🚂", "🚂"), ("🐳", "🐳"), ("🕶", "📱"), ("🐈‍⬛", "🐈‍⬛"), ("✌️", "✌️"), ("💎", "💎"), ("🛡", "🌩"), ("🌼", "❤️"), ("🎡", "🎡"), ("🐧", "🐧"), ("🧃", "🧃") ]: platform = platform.replace(emoji, icon) return ( ( "🪐 Heroku\n" if self.config["show_heroku"] else "" ) + self.config["custom_message"].format( me=me, version=_version, build=build, prefix=prefix, platform=platform, upd=upd, uptime=utils.formatted_uptime(), cpu_usage=utils.get_cpu_usage(), ram_usage=f"{utils.get_ram_usage()} MB", branch=version.branch, hostname=lib_platform.node(), user=getpass.getuser(), os=self._get_os_name() or self.strings('non_detectable'), kernel=lib_platform.release(), cpu=f"{psutil.cpu_count(logical=False)} ({psutil.cpu_count()}) core(-s); {psutil.cpu_percent()}% total", ping=round((time.perf_counter_ns() - start) / 10**6, 3) ) if self.config["custom_message"] else ( f'{{}}\n\n{{}} {self.strings("owner")}: {me}\n\n{{}}' f' {self.strings("version")}: {_version} {build}\n{{}}' f' {self.strings("branch")}:' f" {version.branch}\n{upd}\n\n{{}}" f' {self.strings("prefix")}: {prefix}\n{{}}' f' {self.strings("uptime")}:' f" {utils.formatted_uptime()}\n\n{{}}" f' {self.strings("cpu_usage")}:' f" ~{utils.get_cpu_usage()} %\n{{}}" f' {self.strings("ram_usage")}:' f" ~{utils.get_ram_usage()} MB\n{{}}" ).format( ( utils.get_platform_emoji() if self._client.heroku_me.premium and self.config["show_heroku"] else "" ), "😎", "💫", "🌳", "⌨️", "⌛️", "⚡️", "💼", platform, ) ) def _get_info_photo(self, start: float) -> Optional[Path]: imgform = self.config['banner_url'].split('.')[-1] imgset = self.config['imgSettings'] if imgform in ['jpg', 'jpeg', 'png', 'bmp', 'webp']: response = requests.get(self.config['banner_url'] if not self.config['banner_url'].startswith('https://imgur') else self.imgurpidor(self.config['banner_url']), stream=True, headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"}) img = Image.open(BytesIO(response.content)) font = ImageFont.truetype( glob.glob(f'{os.getcwd()}/assets/font.*')[0], size=int(imgset[1]), encoding='unic' ) w, h = img.size draw = ImageDraw.Draw(img) draw.text( (int(w/2), int(h/2)) if imgset[3] == '0|0' else tuple([int(i) for i in imgset[3].split('|')]), f'{utils.remove_html(self._render_info(start))}', anchor=imgset[4], font=font, fill=imgset[2] if imgset[2].startswith('#') else '#000', stroke_width=int(imgset[5]), stroke_fill=imgset[6] if imgset[6].startswith('#') else '#000', embedded_color=True ) path = f'{os.getcwd()}/assets/imginfo.{imgform}' img.save(path) return Path(path).absolute() return None @loader.command() async def insfont(self, message: Message): " - Install font" if message.is_reply: reply = await message.get_reply_message() fontform = reply.document.mime_type.split("/")[1] if not fontform in ['ttf', 'otf']: await utils.answer( message, self.strings["incorrect_format_font"] ) return origpath = glob.glob(f'{os.getcwd()}/assets/font.*')[0] ptf = f'{os.getcwd()}/font.{fontform}' os.rename(origpath, ptf) photo = await reply.download_media(origpath) if photo is None: os.rename(ptf, origpath) await utils.answer( message, self.strings["no_font"] ) return os.remove(ptf) elif utils.check_url(utils.get_args_raw(message)): fontform = utils.get_args_raw(message).split('.')[-1] if not fontform in ['ttf', 'otf']: await utils.answer( message, self.strings["incorrect_format_font"] ) return response = requests.get(utils.get_args_raw(message), stream=True) os.remove(glob.glob(f'{os.getcwd()}/assets/font.*')[0]) with open(f'{os.getcwd()}/assets/font.{fontform}', 'wb') as file: file.write(response.content) else: await utils.answer( message, self.strings["no_font"] ) return await utils.answer( message, self.string["font_installed"] ) @loader.command() async def infocmd(self, message: Message): start = time.perf_counter_ns() if self.config['switchInfo']: if self._get_info_photo(start) is None: await utils.answer( message, self.strings["incorrect_img_format"] ) return await utils.answer_file( message, self._get_info_photo(start), reply_to=getattr(message, "reply_to_msg_id", None), ) elif self.config["custom_message"] is None: await utils.answer_file( message, self.config["banner_url"], self._render_info(start), reply_to=getattr(message, "reply_to_msg_id", None), ) else: if '{ping}' in self.config["custom_message"]: message = await utils.answer(message, self.config["ping_emoji"]) await utils.answer_file( message, self.config["banner_url"], self._render_info(start), reply_to=getattr(message, "reply_to_msg_id", None), ) @loader.command() async def herokuinfo(self, message: Message): await utils.answer(message, self.strings("desc")) @loader.command() async def setinfo(self, message: Message): if not (args := utils.get_args_html(message)): return await utils.answer(message, self.strings("setinfo_no_args")) self.config["custom_message"] = args await utils.answer(message, self.strings("setinfo_success"))