From 54099b85c1cb7dedb38c6aa00027fef22b3503dc Mon Sep 17 00:00:00 2001 From: Pengu Date: Fri, 12 Dec 2025 03:21:41 -0600 Subject: [PATCH] Push Stats command --- README.md | 3 ++ app.py | 117 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 3 +- 3 files changed, 122 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 60e1c3b..54364a0 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ ![Live Player Count](https://img.shields.io/badge/dynamic/json?query=$.data.result[0].value[1]&url=https%3A%2F%2Fprometheus.pengucc.com%2Fapi%2Fv1%2Fquery%3Fquery%3Dproxmox&style=for-the-badge&logo=proxmox&logoColor=white&label=Proxmox%20Enabled&color=9964c5) ![Live Player Count](https://img.shields.io/badge/dynamic/json?query=$.data.result[0].value[1]&url=https%3A%2F%2Fprometheus.pengucc.com%2Fapi%2Fv1%2Fquery%3Fquery%3Djfa&style=for-the-badge&logo=go&logoColor=white&label=JFA-GO%20Enabled&color=9964c5) ![Live Player Count](https://img.shields.io/badge/dynamic/json?query=$.data.result[0].value[1]&url=https%3A%2F%2Fprometheus.pengucc.com%2Fapi%2Fv1%2Fquery%3Fquery%3Dqbittorrent&style=for-the-badge&logo=qbittorrent&logoColor=white&label=qBittorrent%20Enabled&color=9964c5) +![Live Player Count](https://img.shields.io/badge/dynamic/json?query=$.data.result[0].value[1]&url=https%3A%2F%2Fprometheus.pengucc.com%2Fapi%2Fv1%2Fquery%3Fquery%3Dradarr&style=for-the-badge&logo=radarr&logoColor=white&label=Radarr%20Enabled&color=9964c5) +![Live Player Count](https://img.shields.io/badge/dynamic/json?query=$.data.result[0].value[1]&url=https%3A%2F%2Fprometheus.pengucc.com%2Fapi%2Fv1%2Fquery%3Fquery%3Dsonarr&style=for-the-badge&logo=sonarr&logoColor=white&label=Sonarr%20Enabled&color=9964c5) [![Online Members](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdiscordapp.com%2Fapi%2Finvites%2FEdPJAhrDq8%3Fwith_counts%3Dtrue&query=approximate_presence_count&style=for-the-badge&logo=discord&logoColor=white&label=ONLINE%20MEMBERS&labelColor=grey&color=239eda)](https://discord.gg/EdPJAhrDq8) ![Latest Version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FPenguCCN%2FJellycord%2Fmain%2Fversion.json&query=%24.version&style=for-the-badge&logo=python&logoColor=white&label=Latest%20Version%3A&color=239eda) @@ -107,6 +109,7 @@ Adding these simply allows me to see what features users are interested in, allo ***⚙️ Admin Bot Commands*** - `!setprefix` - Change the bots command prefix +- `!stats` - View Local and Global Jellycord Stats - `!update` - Download latest bot version - `!backup` - Create a backup of the bot and configurations - `!backups` - List backups of the bot diff --git a/app.py b/app.py index a11c1ef..7b0fa52 100644 --- a/app.py +++ b/app.py @@ -19,6 +19,8 @@ import tempfile import shutil import pymysql import json +import psutil +import platform # ===================== # ENV + VALIDATION @@ -94,6 +96,7 @@ RELEASES_URL = "https://github.com/PenguCCN/Jellycord/releases" CHANGELOG_URL = "https://raw.githubusercontent.com/PenguCCN/Jellycord/refs/heads/main/CHANGELOG.md" TRACKING_ENABLED = os.getenv("TRACKING_ENABLED", "False").lower() == "true" +PROMETHEUS_URL = "https://prometheus.pengucc.com/api/v1/query" POST_ENDPOINTS = { "botinstance": "https://jellycordstats.pengucc.com/api/instance", "jellyseerr": "https://jellycordstats.pengucc.com/api/jellyseerr", @@ -770,6 +773,22 @@ def restart_bot(): def build_payload(enabled: bool): return {"value": 1 if enabled else 0} +def promql(query: str): + """Run a PromQL query and return results.""" + + try: + response = requests.get( + PROMETHEUS_URL, + params={"query": query}, + timeout=10 + ) + data = response.json() + result = data.get("data", {}).get("result", []) + return result + except Exception as e: + print(f"[Prometheus] Error: {e}") + return None + # ===================== # EVENTS # ===================== @@ -1924,6 +1943,103 @@ async def unlink(ctx, discord_user: discord.User = None): delete_account(discord_user.id) await ctx.send(f"✅ Unlinked Jellyfin account **{account[0]}** from Discord user {discord_user.mention}.") +@bot.command() +async def stats(ctx): + """Show unified system and Prometheus metrics in one compact embed.""" + + # ------------------- + # Local System Stats + # ------------------- + + cpu_usage = psutil.cpu_percent(interval=1) + + mem = psutil.virtual_memory() + mem_str = f"{round(mem.used/1024**3,2)} / {round(mem.total/1024**3,2)} GB ({mem.percent}%)" + + disk = psutil.disk_usage('/') + disk_str = f"{round(disk.used/1024**3,2)} / {round(disk.total/1024**3,2)} GB ({disk.percent}%)" + + boot_time = datetime.datetime.fromtimestamp(psutil.boot_time()) + uptime = datetime.datetime.now() - boot_time + uptime_str = str(uptime).split('.')[0] + + python_version = platform.python_version() + bot_ver = BOT_VERSION if "BOT_VERSION" in globals() else "Unknown" + + + # ------------------- + # Prometheus Stats (Last 5 Minutes) + # ------------------- + + prometheus_fields = [] + + if PROMETHEUS_URL: + + metrics = { + "Instances": + "max_over_time(instance[5m])", + + "Jellyseerr Enabled": + "max_over_time(jellyseerr[5m])", + + "JFA Enabled": + "max_over_time(jfa[5m])", + + "qBittorrent Enabled": + "max_over_time(qbittorrent[5m])", + + "Radarr Enabled": + "max_over_time(radarr[5m])", + + "Sonarr Enabled": + "max_over_time(sonarr[5m])" + } + + for label, query in metrics.items(): + result = promql(query) + if result and isinstance(result, list) and len(result) > 0: + try: + value = result[0]["value"][1] + except: + value = "N/A" + else: + value = "N/A" + + prometheus_fields.append((label, value)) + + + # ------------------- + # Build Embed + # ------------------- + + embed = discord.Embed( + title="📊 Jellycord System & Tracking Statistics", + color=discord.Color.blurple() + ) + + # Local stats + embed.add_field(name="🧠 CPU", value=f"{cpu_usage}%", inline=True) + embed.add_field(name="💾 Memory", value=mem_str, inline=True) + embed.add_field(name="📀 Disk", value=disk_str, inline=True) + embed.add_field(name="⏱️ Uptime", value=uptime_str, inline=True) + embed.add_field(name="🐍 Python", value=python_version, inline=True) + embed.add_field(name="🤖 Bot Version", value=bot_ver, inline=True) + + # Prometheus stats + if prometheus_fields: + embed.add_field(name="📡 Tracking (Last 5 Minutes)", value="\u200b", inline=False) + for label, val in prometheus_fields: + embed.add_field(name=f"• {label}", value=f"`{val}`", inline=True) + else: + embed.add_field( + name="📡 Tracking", + value="Prometheus disabled or unreachable", + inline=False + ) + + embed.set_footer(text=f"Generated • {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + + await ctx.send(embed=embed) @bot.command() async def setprefix(ctx, new_prefix: str = None): @@ -2380,6 +2496,7 @@ async def help_command(ctx): # Admin Bot commands admin_bot_cmds = [ + f"`{PREFIX}stats` - View Local and Global Jellycord Stats", f"`{PREFIX}setprefix` - Change the bot's command prefix", f"`{PREFIX}update` - Download latest bot version", f"`{PREFIX}backup` - Create a backup of the bot, its database and configurations", diff --git a/requirements.txt b/requirements.txt index c0f23aa..9214f35 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ pytz==2025.2 apscheduler==3.11.0 qbittorrent-api==2025.7.0 proxmoxer==2.2.0 -pymysql==1.1.2 \ No newline at end of file +pymysql==1.1.2 +psutil==7.1.3 \ No newline at end of file