Added qBittorrent Support, reformatted help command
This commit is contained in:
7
.env
7
.env
@@ -16,9 +16,16 @@ JELLYSEERR_URL=http://localhost:5055
|
|||||||
JELLYSEERR_API_KEY=your_api_key_here
|
JELLYSEERR_API_KEY=your_api_key_here
|
||||||
|
|
||||||
# JFA-Go
|
# JFA-Go
|
||||||
|
ENABLE_JFA=false
|
||||||
JFA_URL=http://localhost:8056
|
JFA_URL=http://localhost:8056
|
||||||
JFA_API_KEY=your_api_key_here
|
JFA_API_KEY=your_api_key_here
|
||||||
|
|
||||||
|
# QBittorrent
|
||||||
|
ENABLE_QBITTORRENT=false
|
||||||
|
QBIT_HOST=http://localhost:8080
|
||||||
|
QBIT_USERNAME=your_username
|
||||||
|
QBIT_PASSWORD=your_password
|
||||||
|
|
||||||
# MySQL
|
# MySQL
|
||||||
DB_HOST=localhost
|
DB_HOST=localhost
|
||||||
DB_USER=root
|
DB_USER=root
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
- Added Progress bar to Active Streams
|
- Added Progress bar to Active Streams
|
||||||
- Added JFA-Go support for external invites
|
- Added JFA-Go support for external invites
|
||||||
|
- Added qBittorrent Support
|
||||||
|
- Reformated Help Command
|
||||||
|
|
||||||
# 1.0.5
|
# 1.0.5
|
||||||
|
|
||||||
|
|||||||
21
README.md
21
README.md
@@ -44,24 +44,35 @@ 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
|
||||||
- `!trialaccount` <username> <password> - Create a 24-hour trial Jellyfin account. Only if ENABLE_TRIAL_ACCOUNTS=True
|
- `!trialaccount` <username> <password> - Create a 24-hour trial Jellyfin account. Only if ENABLE_TRIAL_ACCOUNTS=True
|
||||||
- `!what2watch` - Lists 5 random movie suggestions from the Jellyfin Library
|
- `!what2watch` - Lists 5 random movie suggestions from the Jellyfin Library
|
||||||
|
- `!help` - Displays help command
|
||||||
|
|
||||||
***Admin Commands***
|
***🛠️ Admin Commands***
|
||||||
|
- `!link` <jellyfin_username> @user - Manually link accounts
|
||||||
|
- `!unlink` @user - Manually unlink accounts
|
||||||
|
- `!listvalidusers` - Show number of valid and invalid accounts
|
||||||
- `!cleanup` - Remove Jellyfin accounts from users without roles
|
- `!cleanup` - Remove Jellyfin accounts from users without roles
|
||||||
- `!lastcleanup` - See Last cleanup time, and time remaining before next cleanup
|
- `!lastcleanup` - See Last cleanup time, and time remaining before next cleanup
|
||||||
- `!searchaccount` <jellyfin_username> - Find linked Discord user
|
- `!searchaccount` <jellyfin_username> - Find linked Discord user
|
||||||
- `!searchdiscord` @user - Find linked Jellyfin account
|
- `!searchdiscord` @user - Find linked Jellyfin account
|
||||||
- `!scanlibraries` - Scan all Jellyfin libraries
|
- `!scanlibraries` - Scan all Jellyfin libraries
|
||||||
- `!activestreams` - View all Active Jellyfin streams
|
- `!activestreams` - View all Active Jellyfin streams
|
||||||
- `!link` <jellyfin_username> @user - Manually link accounts
|
|
||||||
- `!unlink` @user - Manually unlink accounts
|
|
||||||
|
|
||||||
***Admin Bot Commands***
|
***💾 qBittorrent Commands***
|
||||||
|
- `!qbview` - View current qBittorrent downloads
|
||||||
|
|
||||||
|
***🔑 JFA Commands***
|
||||||
|
|
||||||
|
- `!createinvite` - Create a new JFA invite link
|
||||||
|
- `!listinvites` - List all active JFA invite links
|
||||||
|
- `!deleteinvite <code>` - Delete a specific JFA Invite
|
||||||
|
|
||||||
|
***⚙️ 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
|
||||||
- `!logging` - Enable/Disable Console Event Logging
|
- `!logging` - Enable/Disable Console Event Logging
|
||||||
166
app.py
166
app.py
@@ -7,6 +7,7 @@ import os
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import pytz
|
import pytz
|
||||||
import random
|
import random
|
||||||
|
import qbittorrentapi
|
||||||
|
|
||||||
# =====================
|
# =====================
|
||||||
# ENV + VALIDATION
|
# ENV + VALIDATION
|
||||||
@@ -37,9 +38,15 @@ JELLYSEERR_ENABLED = os.getenv("JELLYSEERR_ENABLED", "false").lower() == "true"
|
|||||||
JELLYSEERR_URL = os.getenv("JELLYSEERR_URL", "").rstrip("/")
|
JELLYSEERR_URL = os.getenv("JELLYSEERR_URL", "").rstrip("/")
|
||||||
JELLYSEERR_API_KEY = os.getenv("JELLYSEERR_API_KEY", "")
|
JELLYSEERR_API_KEY = os.getenv("JELLYSEERR_API_KEY", "")
|
||||||
|
|
||||||
|
ENABLE_JFA = os.getenv("ENABLE_JFA", "False").lower() == "true"
|
||||||
JFA_URL = os.getenv("JFA_URL")
|
JFA_URL = os.getenv("JFA_URL")
|
||||||
JFA_API_KEY = os.getenv("JFA_API_KEY")
|
JFA_API_KEY = os.getenv("JFA_API_KEY")
|
||||||
|
|
||||||
|
ENABLE_QBITTORRENT = os.getenv("ENABLE_QBITTORRENT", "False").lower() == "true"
|
||||||
|
QBIT_HOST = os.getenv("QBIT_HOST")
|
||||||
|
QBIT_USERNAME = os.getenv("QBIT_USERNAME")
|
||||||
|
QBIT_PASSWORD = os.getenv("QBIT_PASSWORD")
|
||||||
|
|
||||||
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")
|
||||||
DB_PASSWORD = get_env_var("DB_PASSWORD")
|
DB_PASSWORD = get_env_var("DB_PASSWORD")
|
||||||
@@ -47,7 +54,7 @@ DB_NAME = get_env_var("DB_NAME")
|
|||||||
|
|
||||||
LOCAL_TZ = pytz.timezone(get_env_var("LOCAL_TZ", str, required=False) or "America/Chicago")
|
LOCAL_TZ = pytz.timezone(get_env_var("LOCAL_TZ", str, required=False) or "America/Chicago")
|
||||||
|
|
||||||
BOT_VERSION = "1.0.5"
|
BOT_VERSION = "1.0.6"
|
||||||
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"
|
||||||
|
|
||||||
@@ -70,6 +77,20 @@ intents.members = True
|
|||||||
intents.message_content = True
|
intents.message_content = True
|
||||||
bot = commands.Bot(command_prefix=PREFIX, intents=intents, help_command=None)
|
bot = commands.Bot(command_prefix=PREFIX, intents=intents, help_command=None)
|
||||||
|
|
||||||
|
# =====================
|
||||||
|
# QBITTORRENT SETUP
|
||||||
|
# =====================
|
||||||
|
|
||||||
|
qb = qbittorrentapi.Client(
|
||||||
|
host=QBIT_HOST,
|
||||||
|
username=QBIT_USERNAME,
|
||||||
|
password=QBIT_PASSWORD
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
qb.auth_log_in()
|
||||||
|
except qbittorrentapi.LoginFailed:
|
||||||
|
print("Failed to log in to qBittorrent API")
|
||||||
|
|
||||||
# =====================
|
# =====================
|
||||||
# DATABASE SETUP
|
# DATABASE SETUP
|
||||||
# =====================
|
# =====================
|
||||||
@@ -316,6 +337,16 @@ def delete_jellyseerr_user(js_id: str) -> bool:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Jellyseerr] Failed to delete user {js_id}: {e}")
|
print(f"[Jellyseerr] Failed to delete user {js_id}: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# =====================
|
||||||
|
# QBITTORRENT HELPERS
|
||||||
|
# =====================
|
||||||
|
|
||||||
|
def progress_bar(progress: float, length: int = 20) -> str:
|
||||||
|
"""Return a textual progress bar for the embed."""
|
||||||
|
filled_length = int(length * progress)
|
||||||
|
bar = '█' * filled_length + '░' * (length - filled_length)
|
||||||
|
return f"[{bar}] {progress*100:.2f}%"
|
||||||
|
|
||||||
# =====================
|
# =====================
|
||||||
# DISCORD HELPERS
|
# DISCORD HELPERS
|
||||||
@@ -487,6 +518,10 @@ async def createaccount(ctx, username: str = None, password: str = None):
|
|||||||
@bot.command()
|
@bot.command()
|
||||||
async def createinvite(ctx):
|
async def createinvite(ctx):
|
||||||
"""Admin-only: Create a new JFA-Go invite link (create -> fetch latest invite)."""
|
"""Admin-only: Create a new JFA-Go invite link (create -> fetch latest invite)."""
|
||||||
|
if not ENABLE_JFA:
|
||||||
|
await ctx.send("❌ JFA support is not enabled in the bot configuration.")
|
||||||
|
return
|
||||||
|
|
||||||
if not has_admin_role(ctx.author):
|
if not has_admin_role(ctx.author):
|
||||||
await ctx.send("❌ You don’t have permission to use this command.")
|
await ctx.send("❌ You don’t have permission to use this command.")
|
||||||
return
|
return
|
||||||
@@ -574,6 +609,10 @@ async def createinvite(ctx):
|
|||||||
@bot.command()
|
@bot.command()
|
||||||
async def listinvites(ctx):
|
async def listinvites(ctx):
|
||||||
"""Admin-only: List all active JFA-Go invites."""
|
"""Admin-only: List all active JFA-Go invites."""
|
||||||
|
if not ENABLE_JFA:
|
||||||
|
await ctx.send("❌ JFA support is not enabled in the bot configuration.")
|
||||||
|
return
|
||||||
|
|
||||||
if not has_admin_role(ctx.author):
|
if not has_admin_role(ctx.author):
|
||||||
await ctx.send("❌ You don’t have permission to use this command.")
|
await ctx.send("❌ You don’t have permission to use this command.")
|
||||||
return
|
return
|
||||||
@@ -1130,6 +1169,61 @@ async def activestreams(ctx):
|
|||||||
await ctx.send(f"❌ Error fetching active streams: {e}")
|
await ctx.send(f"❌ Error fetching active streams: {e}")
|
||||||
print(f"[activestreams] Error: {e}")
|
print(f"[activestreams] Error: {e}")
|
||||||
|
|
||||||
|
@bot.command()
|
||||||
|
async def qbview(ctx):
|
||||||
|
"""Admin-only: View current qBittorrent downloads."""
|
||||||
|
if not ENABLE_QBITTORRENT:
|
||||||
|
await ctx.send("❌ qBittorrent support is not enabled in the bot configuration.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not has_admin_role(ctx.author):
|
||||||
|
await ctx.send("❌ You don’t have permission to use this command.")
|
||||||
|
return
|
||||||
|
|
||||||
|
torrents = qb.torrents_info()
|
||||||
|
embed = discord.Embed(title="qBittorrent Downloads", color=0x00ff00)
|
||||||
|
|
||||||
|
if not torrents:
|
||||||
|
embed.description = "No torrents found."
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Group torrents by state
|
||||||
|
state_groups = {
|
||||||
|
"Downloading / Uploading": [],
|
||||||
|
"Finished": [],
|
||||||
|
"Stalled": [],
|
||||||
|
"Checking / Metadata": [],
|
||||||
|
"Other": []
|
||||||
|
}
|
||||||
|
|
||||||
|
for t in torrents:
|
||||||
|
if t.state in ("downloading", "uploading"):
|
||||||
|
state_groups["Downloading / Uploading"].append(t)
|
||||||
|
elif t.state in ("completed", "pausedUP", "pausedDL"):
|
||||||
|
state_groups["Finished"].append(t)
|
||||||
|
elif t.state in ("stalledUP", "stalledDL"):
|
||||||
|
state_groups["Stalled"].append(t)
|
||||||
|
elif t.state in ("checkingUP", "checkingDL", "checking", "metaDL"):
|
||||||
|
state_groups["Checking / Metadata"].append(t)
|
||||||
|
else:
|
||||||
|
state_groups["Other"].append(t)
|
||||||
|
|
||||||
|
# Add torrents to embed
|
||||||
|
for group_name, torrents_list in state_groups.items():
|
||||||
|
if torrents_list:
|
||||||
|
value_text = ""
|
||||||
|
for torrent in torrents_list:
|
||||||
|
value_text += (
|
||||||
|
f"{torrent.name}\n"
|
||||||
|
f"{progress_bar(torrent.progress)}\n"
|
||||||
|
f"Peers: {torrent.num_leechs} | Seeders: {torrent.num_seeds}\n"
|
||||||
|
f"Status: {torrent.state}\n\n"
|
||||||
|
)
|
||||||
|
embed.add_field(name=group_name, value=value_text, inline=False)
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
@bot.command()
|
@bot.command()
|
||||||
async def link(ctx, jellyfin_username: str = None, user: discord.User = None, js_id: str = None):
|
async def link(ctx, jellyfin_username: str = None, user: discord.User = None, js_id: str = None):
|
||||||
@@ -1266,47 +1360,71 @@ async def help_command(ctx):
|
|||||||
color=discord.Color.blue()
|
color=discord.Color.blue()
|
||||||
)
|
)
|
||||||
|
|
||||||
# User commands
|
# --- Jellyfin User Commands ---
|
||||||
user_cmds = [
|
user_cmds = [
|
||||||
f"`{PREFIX}createaccount <username> <password>` - Create your Jellyfin account",
|
f"`{PREFIX}createaccount <username> <password>` - Create your Jellyfin account",
|
||||||
f"`{PREFIX}recoveraccount <newpassword>` - Reset your password",
|
f"`{PREFIX}recoveraccount <newpassword>` - Reset your password",
|
||||||
f"`{PREFIX}deleteaccount <username>` - Delete your Jellyfin account",
|
f"`{PREFIX}deleteaccount <username>` - Delete your Jellyfin account",
|
||||||
f"`{PREFIX}what2watch` - Lists 5 random movie suggestions from the Jellyfin Library"
|
f"`{PREFIX}what2watch` - Lists 5 random movie suggestions from the Jellyfin Library"
|
||||||
]
|
]
|
||||||
|
|
||||||
# Only show trialaccount if enabled
|
|
||||||
if ENABLE_TRIAL_ACCOUNTS:
|
if ENABLE_TRIAL_ACCOUNTS:
|
||||||
user_cmds.append(f"`{PREFIX}trialaccount <username> <password>` - Create a 24-hour trial Jellyfin account")
|
user_cmds.append(f"`{PREFIX}trialaccount <username> <password>` - Create a 24-hour trial Jellyfin account")
|
||||||
|
|
||||||
|
embed.add_field(name="🎬 Jellyfin Commands", value="\n".join(user_cmds), inline=False)
|
||||||
|
|
||||||
embed.add_field(name="User Commands", value="\n".join(user_cmds), inline=False)
|
# --- Bot Commands ---
|
||||||
|
bot_cmds = [
|
||||||
|
f"`{PREFIX}help` - Show this help message"
|
||||||
|
]
|
||||||
|
embed.add_field(name="🤖 Bot Commands", value="\n".join(bot_cmds), inline=False)
|
||||||
|
|
||||||
# Admin commands
|
# --- Admin Commands ---
|
||||||
if is_admin:
|
if is_admin:
|
||||||
# Dynamic link command line
|
# Admin Jellyfin commands
|
||||||
link_command = f"`{PREFIX}link <jellyfin_username> @user` - Manually link accounts"
|
link_command = f"`{PREFIX}link <jellyfin_username> @user` - Manually link accounts"
|
||||||
if JELLYSEERR_ENABLED:
|
if JELLYSEERR_ENABLED:
|
||||||
link_command = f"`{PREFIX}link <jellyfin_username> @user <Jellyseerr ID>` - Manually link accounts with Jellyseerr"
|
link_command = f"`{PREFIX}link <jellyfin_username> @user <Jellyseerr ID>` - Link accounts with Jellyseerr"
|
||||||
|
|
||||||
embed.add_field(name="Admin Commands", value=(
|
admin_cmds = [
|
||||||
f"`{PREFIX}cleanup` - Remove Jellyfin accounts from users without roles\n"
|
link_command,
|
||||||
f"`{PREFIX}listvalidusers` - Show number of valid and invalid accounts\n"
|
f"`{PREFIX}unlink @user` - Manually unlink accounts",
|
||||||
f"`{PREFIX}lastcleanup` - See Last cleanup time, and time remaining before next cleanup\n"
|
f"`{PREFIX}listvalidusers` - Show number of valid and invalid accounts",
|
||||||
f"`{PREFIX}searchaccount <jellyfin_username>` - Find linked Discord user\n"
|
f"`{PREFIX}cleanup` - Remove Jellyfin accounts from users without roles",
|
||||||
f"`{PREFIX}searchdiscord @user` - Find linked Jellyfin account\n"
|
f"`{PREFIX}lastcleanup` - See last cleanup time and time remaining",
|
||||||
f"`{PREFIX}scanlibraries` - Scan all Jellyfin libraries\n"
|
f"`{PREFIX}searchaccount <jellyfin_username>` - Find linked Discord user",
|
||||||
f"`{PREFIX}activestreams` - View all Active Jellyfin streams\n"
|
f"`{PREFIX}searchdiscord @user` - Find linked Jellyfin account",
|
||||||
f"{link_command}\n"
|
f"`{PREFIX}scanlibraries` - Scan all Jellyfin libraries",
|
||||||
f"`{PREFIX}unlink @user` - Manually unlink accounts\n"
|
f"`{PREFIX}activestreams` - View all active Jellyfin streams"
|
||||||
), inline=False)
|
]
|
||||||
|
embed.add_field(name="🛠️ Admin Commands", value="\n".join(admin_cmds), inline=False)
|
||||||
|
|
||||||
embed.add_field(name="Admin Bot Commands", value=(
|
# --- qBittorrent Commands ---
|
||||||
f"`{PREFIX}setprefix` - Change the bot's command prefix\n"
|
if ENABLE_QBITTORRENT:
|
||||||
f"`{PREFIX}updates` - Manually check for bot updates\n"
|
qb_cmds = [
|
||||||
f"`{PREFIX}logging` - Enable/Disable Console Event Logging\n"
|
f"`{PREFIX}qbview` - Show current qBittorrent downloads with progress, peers, and seeders",
|
||||||
), inline=False)
|
]
|
||||||
|
embed.add_field(name="💾 qBittorrent Commands", value="\n".join(qb_cmds), inline=False)
|
||||||
|
|
||||||
|
# --- JFA Commands ---
|
||||||
|
if ENABLE_JFA:
|
||||||
|
jfa_cmds = [
|
||||||
|
f"`{PREFIX}createinvite` - Create a new JFA invite link",
|
||||||
|
f"`{PREFIX}listinvites` - List all active JFA invite links",
|
||||||
|
f"`{PREFIX}deleteinvite <code>` - Delete a specific JFA invite"
|
||||||
|
]
|
||||||
|
embed.add_field(name="🔑 JFA Commands", value="\n".join(jfa_cmds), inline=False)
|
||||||
|
|
||||||
|
# Admin Bot commands
|
||||||
|
admin_bot_cmds = [
|
||||||
|
f"`{PREFIX}setprefix` - Change the bot's command prefix",
|
||||||
|
f"`{PREFIX}updates` - Manually check for bot updates",
|
||||||
|
f"`{PREFIX}logging` - Enable/disable console event logging"
|
||||||
|
]
|
||||||
|
embed.add_field(name="⚙️ Admin Bot Commands", value="\n".join(admin_bot_cmds), inline=False)
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
# =====================
|
# =====================
|
||||||
# TASKS
|
# TASKS
|
||||||
# =====================
|
# =====================
|
||||||
|
|||||||
@@ -3,4 +3,5 @@ requests==2.32.3
|
|||||||
mysql-connector-python==9.0.0
|
mysql-connector-python==9.0.0
|
||||||
python-dotenv==1.0.1
|
python-dotenv==1.0.1
|
||||||
pytz==2025.2
|
pytz==2025.2
|
||||||
apscheduler==3.11.0
|
apscheduler==3.11.0
|
||||||
|
qbittorrent-api==2025.7.0
|
||||||
@@ -1 +1 @@
|
|||||||
{ "version": "1.0.5" }
|
{ "version": "1.0.6" }
|
||||||
@@ -1 +1 @@
|
|||||||
1.0.5
|
1.0.6
|
||||||
Reference in New Issue
Block a user