Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8fe3d2e5d1 | |||
| e0bbbd02f5 | |||
| d80746cd61 | |||
| f6197c144a | |||
| 2481ba778b |
5
.env
5
.env
@@ -21,6 +21,7 @@ JFA_URL=http://localhost:8056
|
|||||||
JFA_USERNAME=yourusername
|
JFA_USERNAME=yourusername
|
||||||
JFA_PASSWORD=yourpassword
|
JFA_PASSWORD=yourpassword
|
||||||
JFA_API_KEY=your_api_key_here
|
JFA_API_KEY=your_api_key_here
|
||||||
|
JFA_TOKEN=
|
||||||
|
|
||||||
# QBittorrent
|
# QBittorrent
|
||||||
ENABLE_QBITTORRENT=false
|
ENABLE_QBITTORRENT=false
|
||||||
@@ -34,6 +35,10 @@ PROXMOX_HOST=https://your-proxmox-server:8006
|
|||||||
PROXMOX_TOKEN_NAME=root@pam!yourtokenname
|
PROXMOX_TOKEN_NAME=root@pam!yourtokenname
|
||||||
PROXMOX_TOKEN_VALUE=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
PROXMOX_TOKEN_VALUE=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
PROXMOX_VERIFY_SSL=false
|
PROXMOX_VERIFY_SSL=false
|
||||||
|
PROXMOX_NODE=pve1
|
||||||
|
PROXMOX_VM_ID=
|
||||||
|
# Proxmox type can be "qemu" for VM, "lxc" for container
|
||||||
|
PROXMOX_TYPE=
|
||||||
|
|
||||||
# MySQL
|
# MySQL
|
||||||
DB_HOST=localhost
|
DB_HOST=localhost
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
# 1.0.8
|
||||||
|
|
||||||
|
- Fixed update message
|
||||||
|
- Added changelog command
|
||||||
|
- Fixed schedule loop for Jfa being enabled when JFA support is disabled
|
||||||
|
- Added metrics tracking for a Jellyfin container/vm in proxmox
|
||||||
|
|
||||||
# 1.0.7
|
# 1.0.7
|
||||||
|
|
||||||
- Fixed JFA-GO API keys expiring. The bot now schedules a key refresh
|
- Fixed JFA-GO API keys expiring. The bot now schedules a key refresh
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ Fill out values in the .env and you're good to go!
|
|||||||

|

|
||||||
|
|
||||||
***🎬 User Commands***
|
***🎬 User Commands***
|
||||||
|
|
||||||
- `!createaccount` <username> <password> - Create your Jellyfin account
|
- `!createaccount` <username> <password> - Create your Jellyfin account
|
||||||
- `!recoveraccount` <username> <newpassword> - Reset your password
|
- `!recoveraccount` <username> <newpassword> - Reset your password
|
||||||
- `!deleteaccount` <username> - Delete your Jellyfin account
|
- `!deleteaccount` <username> - Delete your Jellyfin account
|
||||||
@@ -54,6 +55,7 @@ Fill out values in the .env and you're good to go!
|
|||||||
- `!help` - Displays help command
|
- `!help` - Displays help command
|
||||||
|
|
||||||
***🛠️ Admin Commands***
|
***🛠️ Admin Commands***
|
||||||
|
|
||||||
- `!link` <jellyfin_username> @user - Manually link accounts
|
- `!link` <jellyfin_username> @user - Manually link accounts
|
||||||
- `!unlink` @user - Manually unlink accounts
|
- `!unlink` @user - Manually unlink accounts
|
||||||
- `!listvalidusers` - Show number of valid and invalid accounts
|
- `!listvalidusers` - Show number of valid and invalid accounts
|
||||||
@@ -65,10 +67,13 @@ Fill out values in the .env and you're good to go!
|
|||||||
- `!activestreams` - View all Active Jellyfin streams
|
- `!activestreams` - View all Active Jellyfin streams
|
||||||
|
|
||||||
***💾 qBittorrent Commands***
|
***💾 qBittorrent Commands***
|
||||||
|
|
||||||
- `!qbview` - View current qBittorrent downloads
|
- `!qbview` - View current qBittorrent downloads
|
||||||
|
|
||||||
***🗳️ Proxmox Commands***
|
***🗳️ Proxmox Commands***
|
||||||
|
|
||||||
- `!storage` - Show available storage pools and free space
|
- `!storage` - Show available storage pools and free space
|
||||||
|
- `!metrics` - Show Jellyfin container metrics
|
||||||
|
|
||||||
***🔑 JFA Commands***
|
***🔑 JFA Commands***
|
||||||
|
|
||||||
@@ -78,6 +83,8 @@ Fill out values in the .env and you're good to go!
|
|||||||
- `!refreshjfakey` - Refreshes the JFA API Key Forcefully
|
- `!refreshjfakey` - Refreshes the JFA API Key Forcefully
|
||||||
|
|
||||||
***⚙️ Admin Bot Commands***
|
***⚙️ Admin Bot Commands***
|
||||||
|
|
||||||
- `!setprefix` - Change the bots command prefix
|
- `!setprefix` - Change the bots command prefix
|
||||||
- `!updates` - Manually check for bot updates
|
- `!updates` - Manually check for bot updates
|
||||||
|
- `!changelog` - View changelog for current bot version
|
||||||
- `!logging` - Enable/Disable Console Event Logging
|
- `!logging` - Enable/Disable Console Event Logging
|
||||||
120
app.py
120
app.py
@@ -55,6 +55,9 @@ PROXMOX_HOST = os.getenv("PROXMOX_HOST")
|
|||||||
PROXMOX_TOKEN_NAME = os.getenv("PROXMOX_TOKEN_NAME")
|
PROXMOX_TOKEN_NAME = os.getenv("PROXMOX_TOKEN_NAME")
|
||||||
PROXMOX_TOKEN_VALUE = os.getenv("PROXMOX_TOKEN_VALUE")
|
PROXMOX_TOKEN_VALUE = os.getenv("PROXMOX_TOKEN_VALUE")
|
||||||
PROXMOX_VERIFY_SSL = os.getenv("PROXMOX_VERIFY_SSL", "False").lower() == "true"
|
PROXMOX_VERIFY_SSL = os.getenv("PROXMOX_VERIFY_SSL", "False").lower() == "true"
|
||||||
|
PROXMOX_NODE = os.getenv("PROXMOX_NODE", "pve")
|
||||||
|
PROXMOX_VM_ID = os.getenv("PROXMOX_VM_ID", None)
|
||||||
|
PROXMOX_TYPE = os.getenv("PROXMOX_TYPE", "qemu")
|
||||||
|
|
||||||
DB_HOST = get_env_var("DB_HOST")
|
DB_HOST = get_env_var("DB_HOST")
|
||||||
DB_USER = get_env_var("DB_USER")
|
DB_USER = get_env_var("DB_USER")
|
||||||
@@ -66,6 +69,7 @@ LOCAL_TZ = pytz.timezone(get_env_var("LOCAL_TZ", str, required=False) or "Americ
|
|||||||
BOT_VERSION = "1.0.7"
|
BOT_VERSION = "1.0.7"
|
||||||
VERSION_URL = "https://raw.githubusercontent.com/PenguCCN/Jellycord/main/version.txt"
|
VERSION_URL = "https://raw.githubusercontent.com/PenguCCN/Jellycord/main/version.txt"
|
||||||
RELEASES_URL = "https://github.com/PenguCCN/Jellycord/releases"
|
RELEASES_URL = "https://github.com/PenguCCN/Jellycord/releases"
|
||||||
|
CHANGELOG_URL = "https://raw.githubusercontent.com/PenguCCN/Jellycord/refs/heads/main/CHANGELOG.md"
|
||||||
|
|
||||||
# =====================
|
# =====================
|
||||||
# EVENT LOGGING
|
# EVENT LOGGING
|
||||||
@@ -1467,6 +1471,65 @@ async def qbview(ctx):
|
|||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
|
@bot.command()
|
||||||
|
async def metrics(ctx):
|
||||||
|
"""Check performance metrics for the configured Proxmox VM/Container."""
|
||||||
|
if not has_admin_role(ctx.author):
|
||||||
|
await ctx.send("❌ You don’t have permission to use this command.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not PROXMOX_VM_ID:
|
||||||
|
await ctx.send("⚠️ No Proxmox VM/Container ID is set in the .env file.")
|
||||||
|
return
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"PVEAPIToken={PROXMOX_TOKEN_NAME}={PROXMOX_TOKEN_VALUE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{PROXMOX_HOST}/api2/json/nodes/{PROXMOX_NODE}/{PROXMOX_TYPE}/{PROXMOX_VM_ID}/status/current"
|
||||||
|
r = requests.get(url, headers=headers, verify=False, timeout=10)
|
||||||
|
|
||||||
|
if r.status_code != 200:
|
||||||
|
await ctx.send(f"❌ Failed to fetch VM/Container status (status {r.status_code})")
|
||||||
|
return
|
||||||
|
|
||||||
|
data = r.json().get("data", {})
|
||||||
|
|
||||||
|
# Extract metrics
|
||||||
|
name = data.get("name", f"ID {PROXMOX_VM_ID}")
|
||||||
|
status = data.get("status", "unknown").capitalize()
|
||||||
|
cpu = round(data.get("cpu", 0) * 100, 2) # returns fraction, convert to %
|
||||||
|
maxmem = data.get("maxmem", 1)
|
||||||
|
mem = data.get("mem", 0)
|
||||||
|
mem_usage = round((mem / maxmem) * 100, 2) if maxmem > 0 else 0
|
||||||
|
maxdisk = data.get("maxdisk", 1)
|
||||||
|
disk = data.get("disk", 0)
|
||||||
|
disk_usage = round((disk / maxdisk) * 100, 2) if maxdisk > 0 else 0
|
||||||
|
maxswap = data.get("maxswap", 1)
|
||||||
|
swap = data.get("swap", 0)
|
||||||
|
swap_usage = round((swap / maxswap) * 100, 2) if maxswap > 0 else 0
|
||||||
|
uptime = data.get("uptime", 0)
|
||||||
|
|
||||||
|
# Build embed
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=f"📊 Proxmox Status: {name}",
|
||||||
|
color=discord.Color.green() if status == "Running" else discord.Color.red()
|
||||||
|
)
|
||||||
|
embed.add_field(name="Status", value=status, inline=True)
|
||||||
|
embed.add_field(name="CPU Usage", value=f"{cpu} %", inline=True)
|
||||||
|
embed.add_field(name="Memory Usage", value=f"{mem_usage} %", inline=True)
|
||||||
|
embed.add_field(name="Disk Usage", value=f"{disk_usage} %", inline=True)
|
||||||
|
embed.add_field(name="Swap Usage", value=f"{swap_usage} %", inline=True)
|
||||||
|
embed.add_field(name="Uptime", value=f"{uptime // 3600}h {(uptime % 3600) // 60}m", inline=True)
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await ctx.send(f"❌ Error fetching Proxmox VM/Container status: {e}")
|
||||||
|
print(f"[proxmoxstatus] Error: {e}")
|
||||||
|
|
||||||
|
|
||||||
@bot.command()
|
@bot.command()
|
||||||
async def storage(ctx):
|
async def storage(ctx):
|
||||||
"""Check Proxmox storage pools and ZFS pools."""
|
"""Check Proxmox storage pools and ZFS pools."""
|
||||||
@@ -1621,6 +1684,58 @@ async def updates(ctx):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
await ctx.send(f"❌ Error checking version: {e}")
|
await ctx.send(f"❌ Error checking version: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
@bot.command()
|
||||||
|
async def changelog(ctx):
|
||||||
|
log_event(f"changelog invoked by {ctx.author}")
|
||||||
|
"""Fetch and display the changelog for the current bot version."""
|
||||||
|
if not has_admin_role(ctx.author):
|
||||||
|
await ctx.send("❌ You don’t have permission to use this command.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.get(CHANGELOG_URL, timeout=10)
|
||||||
|
if r.status_code != 200:
|
||||||
|
await ctx.send(f"❌ Failed to fetch changelog (status {r.status_code})")
|
||||||
|
return
|
||||||
|
|
||||||
|
changelog_text = r.text
|
||||||
|
|
||||||
|
# Find the section for the current version
|
||||||
|
search_str = f"# {BOT_VERSION}"
|
||||||
|
start_idx = changelog_text.find(search_str)
|
||||||
|
if start_idx == -1:
|
||||||
|
await ctx.send(f"⚠️ No changelog found for version `{BOT_VERSION}`.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Find the next heading or end of file
|
||||||
|
next_idx = changelog_text.find("# ", start_idx + len(search_str))
|
||||||
|
if next_idx == -1:
|
||||||
|
section = changelog_text[start_idx:].strip()
|
||||||
|
else:
|
||||||
|
section = changelog_text[start_idx:next_idx].strip()
|
||||||
|
|
||||||
|
# Clean the section (remove the "# version" line itself)
|
||||||
|
lines = section.splitlines()
|
||||||
|
if lines and lines[0].startswith("# "):
|
||||||
|
lines = lines[1:]
|
||||||
|
section_content = "\n".join(lines).strip()
|
||||||
|
|
||||||
|
if not section_content:
|
||||||
|
section_content = "⚠️ No details provided for this version."
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=f"📜 Changelog for v{BOT_VERSION}",
|
||||||
|
description=section_content,
|
||||||
|
color=discord.Color.purple()
|
||||||
|
)
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await ctx.send(f"❌ Error fetching changelog: {e}")
|
||||||
|
print(f"[changelog] Error: {e}")
|
||||||
|
|
||||||
|
|
||||||
@bot.command()
|
@bot.command()
|
||||||
async def logging(ctx, state: str):
|
async def logging(ctx, state: str):
|
||||||
"""Admin-only: Enable or disable event logging."""
|
"""Admin-only: Enable or disable event logging."""
|
||||||
@@ -1717,6 +1832,7 @@ async def help_command(ctx):
|
|||||||
if ENABLE_PROXMOX:
|
if ENABLE_PROXMOX:
|
||||||
qb_cmds = [
|
qb_cmds = [
|
||||||
f"`{PREFIX}storage` - Show available storage pools and free space",
|
f"`{PREFIX}storage` - Show available storage pools and free space",
|
||||||
|
f"`{PREFIX}metrics` - Show Jellyfin container metrics"
|
||||||
]
|
]
|
||||||
embed.add_field(name="🗳️ Proxmox Commands", value="\n".join(qb_cmds), inline=False)
|
embed.add_field(name="🗳️ Proxmox Commands", value="\n".join(qb_cmds), inline=False)
|
||||||
|
|
||||||
@@ -1734,6 +1850,7 @@ async def help_command(ctx):
|
|||||||
admin_bot_cmds = [
|
admin_bot_cmds = [
|
||||||
f"`{PREFIX}setprefix` - Change the bot's command prefix",
|
f"`{PREFIX}setprefix` - Change the bot's command prefix",
|
||||||
f"`{PREFIX}updates` - Manually check for bot updates",
|
f"`{PREFIX}updates` - Manually check for bot updates",
|
||||||
|
f"`{PREFIX}changelog` - View changelog for current bot version",
|
||||||
f"`{PREFIX}logging` - Enable/disable console event logging"
|
f"`{PREFIX}logging` - Enable/disable console event logging"
|
||||||
]
|
]
|
||||||
embed.add_field(name="⚙️ Admin Bot Commands", value="\n".join(admin_bot_cmds), inline=False)
|
embed.add_field(name="⚙️ Admin Bot Commands", value="\n".join(admin_bot_cmds), inline=False)
|
||||||
@@ -1915,7 +2032,7 @@ async def check_for_updates():
|
|||||||
await log_channel.send(
|
await log_channel.send(
|
||||||
f"📌 Current version: `{BOT_VERSION}`\n"
|
f"📌 Current version: `{BOT_VERSION}`\n"
|
||||||
f"⬆️ Latest version: `{latest_version}`\n"
|
f"⬆️ Latest version: `{latest_version}`\n"
|
||||||
f"⚠️ **Update available for Jellyfin Bot! Get it here:**\n\n"
|
f"⚠️ **Update available for Jellycord! Get it here:**\n\n"
|
||||||
f"{RELEASES_URL}"
|
f"{RELEASES_URL}"
|
||||||
)
|
)
|
||||||
log_event(f"Latest Version:'{latest_version}', Current Version: '{BOT_VERSION}'")
|
log_event(f"Latest Version:'{latest_version}', Current Version: '{BOT_VERSION}'")
|
||||||
@@ -1947,6 +2064,7 @@ async def on_ready():
|
|||||||
if not cleanup_task.is_running():
|
if not cleanup_task.is_running():
|
||||||
cleanup_task.start()
|
cleanup_task.start()
|
||||||
|
|
||||||
|
if ENABLE_JFA:
|
||||||
if not refresh_jfa_loop.is_running():
|
if not refresh_jfa_loop.is_running():
|
||||||
refresh_jfa_loop.start()
|
refresh_jfa_loop.start()
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{ "version": "1.0.7" }
|
{ "version": "1.0.8" }
|
||||||
@@ -1 +1 @@
|
|||||||
1.0.7
|
1.0.8
|
||||||
Reference in New Issue
Block a user