From c5d619d450e611a129f45f265a9f1ad806d395dd Mon Sep 17 00:00:00 2001 From: Pengu Date: Sun, 21 Sep 2025 18:13:47 -0500 Subject: [PATCH] Redid what2watch --- CHANGELOG.md | 2 + README.md | 3 +- app.py | 156 +++++++++++++++++++++++++++++++++++++++------------ version.json | 2 +- version.txt | 2 +- 5 files changed, 125 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1752b7b..f1a09ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ - Fixed JFA-GO API keys expiring. The bot now schedules a key refresh - Added Proxmox support for checking storage pool size +- !what2watch is now !movies2watch +- Added !shows2watch, now pulls 5 random shows # 1.0.6 diff --git a/README.md b/README.md index 53ed892..8d62b78 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,8 @@ Fill out values in the .env and you're good to go! - `!recoveraccount` - Reset your password - `!deleteaccount` - Delete your Jellyfin account - `!trialaccount` - Create a 24-hour trial Jellyfin account. Only if ENABLE_TRIAL_ACCOUNTS=True -- `!what2watch` - Lists 5 random movie suggestions from the Jellyfin Library +- `!movies2watch` - Lists 5 random movie suggestions from the Jellyfin Library +- `!shows2watch` - Lists 5 random show suggestions from the Jellyfin Library - `!help` - Displays help command ***๐Ÿ› ๏ธ Admin Commands*** diff --git a/app.py b/app.py index b1bc464..00c6fc6 100644 --- a/app.py +++ b/app.py @@ -63,7 +63,7 @@ DB_NAME = get_env_var("DB_NAME") LOCAL_TZ = pytz.timezone(get_env_var("LOCAL_TZ", str, required=False) or "America/Chicago") -BOT_VERSION = "1.0.6" +BOT_VERSION = "1.0.7" VERSION_URL = "https://raw.githubusercontent.com/PenguCCN/Jellycord/main/version.txt" RELEASES_URL = "https://github.com/PenguCCN/Jellycord/releases" @@ -299,6 +299,32 @@ def reset_jellyfin_password(username: str, new_password: str) -> bool: response = requests.post(f"{JELLYFIN_URL}/Users/{user_id}/Password", headers=headers, json=data) return response.status_code in (200, 204) +def create_trial_jellyfin_user(username, password): + payload = { + "Name": username, + "Password": password, + "Policy": { + "EnableDownloads": False, + "EnableSyncTranscoding": False, + "EnableRemoteControlOfOtherUsers": False, + "EnableLiveTvAccess": False, + "IsAdministrator": False, + "IsHidden": False, + "IsDisabled": False + } + } + headers = { + "X-Emby-Token": JELLYFIN_API_KEY, + "Content-Type": "application/json" + } + response = requests.post(f"{JELLYFIN_URL}/Users/New", json=payload, headers=headers) + + if response.status_code == 200: + return response.json().get("Id") + else: + print(f"[Jellyfin] Trial user creation failed. Status: {response.status_code}, Response: {response.text}") + return None + # ===================== # JELLYSEERR HELPERS # ===================== @@ -522,32 +548,6 @@ def get_metadata(key): cur.close() conn.close() return row[0] if row else None - -def create_trial_jellyfin_user(username, password): - payload = { - "Name": username, - "Password": password, - "Policy": { - "EnableDownloads": False, - "EnableSyncTranscoding": False, - "EnableRemoteControlOfOtherUsers": False, - "EnableLiveTvAccess": False, - "IsAdministrator": False, - "IsHidden": False, - "IsDisabled": False - } - } - headers = { - "X-Emby-Token": JELLYFIN_API_KEY, - "Content-Type": "application/json" - } - response = requests.post(f"{JELLYFIN_URL}/Users/New", json=payload, headers=headers) - - if response.status_code == 200: - return response.json().get("Id") - else: - print(f"[Jellyfin] Trial user creation failed. Status: {response.status_code}, Response: {response.text}") - return None def _update_env_key(key: str, value: str, env_path: str = ".env"): """Update or append key=value in .env (keeps file order).""" @@ -1049,8 +1049,9 @@ async def deleteaccount(ctx, username: str = None): await ctx.send(f"โŒ Failed to delete Jellyfin account **{username}**.") @bot.command() -async def what2watch(ctx): - """Pick 5 random movies from the Jellyfin library with embeds and posters.""" +async def movies2watch(ctx): + log_event(f"movies2watch invoked by {ctx.author}") + """Pick 5 random movies from the Jellyfin library with embeds, and IMDb links.""" member = ctx.guild.get_member(ctx.author.id) if ctx.guild else None if not member or not has_required_role(member): await ctx.send(f"โŒ {ctx.author.mention}, you donโ€™t have the required role to use this command.") @@ -1058,8 +1059,17 @@ async def what2watch(ctx): headers = {"X-Emby-Token": JELLYFIN_API_KEY} try: - # Fetch all movies - r = requests.get(f"{JELLYFIN_URL}/Items?IncludeItemTypes=Movie&Recursive=true", headers=headers, timeout=10) + # Fetch all movies (include ProviderIds explicitly!) + r = requests.get( + f"{JELLYFIN_URL}/Items", + headers=headers, + params={ + "IncludeItemTypes": "Movie", + "Recursive": "true", + "Fields": "ProviderIds" + }, + timeout=10 + ) if r.status_code != 200: await ctx.send(f"โŒ Failed to fetch movies. Status code: {r.status_code}") return @@ -1079,21 +1089,26 @@ async def what2watch(ctx): ) for movie in selection: - name = movie.get("Name") + name = movie.get("Name", "Unknown Title") year = movie.get("ProductionYear", "N/A") runtime = movie.get("RunTimeTicks", None) runtime_min = int(runtime / 10_000_000 / 60) if runtime else "N/A" - + # Poster URL if available poster_url = None if "PrimaryImageTag" in movie and movie["PrimaryImageTag"]: poster_url = f"{JELLYFIN_URL}/Items/{movie['Id']}/Images/Primary?tag={movie['PrimaryImageTag']}&quality=90" - field_value = f"Year: {year}\nRuntime: {runtime_min} min" + # IMDb link if available + imdb_id = movie.get("ProviderIds", {}).get("Imdb") + imdb_link = f"[IMDb Link](https://www.imdb.com/title/{imdb_id})" if imdb_id else "No IMDb ID available" + + # Field content + field_value = f"Year: {year}\nRuntime: {runtime_min} min\n{imdb_link}" embed.add_field(name=name, value=field_value, inline=False) - + if poster_url: - embed.set_image(url=poster_url) # Only last movie's poster will appear as main embed image + embed.set_image(url=poster_url) # Only the last poster appears as main embed image await ctx.send(embed=embed) @@ -1101,6 +1116,72 @@ async def what2watch(ctx): await ctx.send(f"โŒ Error fetching movies: {e}") print(f"[what2watch] Error: {e}") +@bot.command() +async def shows2watch(ctx): + log_event(f"shows2watch invoked by {ctx.author}") + """Pick 5 random TV shows from the Jellyfin library with embeds, and IMDb links.""" + member = ctx.guild.get_member(ctx.author.id) if ctx.guild else None + if not member or not has_required_role(member): + await ctx.send(f"โŒ {ctx.author.mention}, you donโ€™t have the required role to use this command.") + return + + headers = {"X-Emby-Token": JELLYFIN_API_KEY} + try: + # Fetch all shows (include ProviderIds explicitly!) + r = requests.get( + f"{JELLYFIN_URL}/Items", + headers=headers, + params={ + "IncludeItemTypes": "Series", + "Recursive": "true", + "Fields": "ProviderIds" + }, + timeout=10 + ) + if r.status_code != 200: + await ctx.send(f"โŒ Failed to fetch shows. Status code: {r.status_code}") + return + + shows = r.json().get("Items", []) + if not shows: + await ctx.send("โš ๏ธ No shows found in the library.") + return + + # Pick 5 random shows + selection = random.sample(shows, min(5, len(shows))) + + embed = discord.Embed( + title="๐Ÿ“บ Shows to Watch", + description="Here are 5 random TV show suggestions from the library:", + color=discord.Color.green() + ) + + for show in selection: + name = show.get("Name", "Unknown Title") + year = show.get("ProductionYear", "N/A") + + # Poster URL if available + poster_url = None + if "PrimaryImageTag" in show and show["PrimaryImageTag"]: + poster_url = f"{JELLYFIN_URL}/Items/{show['Id']}/Images/Primary?tag={show['PrimaryImageTag']}&quality=90" + + # IMDb link if available + imdb_id = show.get("ProviderIds", {}).get("Imdb") + imdb_link = f"[IMDb Link](https://www.imdb.com/title/{imdb_id})" if imdb_id else "No IMDb ID available" + + # Field content + field_value = f"Year: {year}\n{imdb_link}" + embed.add_field(name=name, value=field_value, inline=False) + + if poster_url: + embed.set_image(url=poster_url) # Only the last show's poster will appear as the embed image + + await ctx.send(embed=embed) + + except Exception as e: + await ctx.send(f"โŒ Error fetching shows: {e}") + print(f"[shows2watch] Error: {e}") + @bot.command() async def cleanup(ctx): @@ -1591,7 +1672,8 @@ async def help_command(ctx): f"`{PREFIX}createaccount ` - Create your Jellyfin account", f"`{PREFIX}recoveraccount ` - Reset your password", f"`{PREFIX}deleteaccount ` - Delete your Jellyfin account", - f"`{PREFIX}what2watch` - Lists 5 random movie suggestions from the Jellyfin Library" + f"`{PREFIX}movies2watch` - Lists 5 random movie suggestions from the Jellyfin Library", + f"`{PREFIX}shows2watch` - Lists 5 random show suggestions from the Jellyfin Library" ] if ENABLE_TRIAL_ACCOUNTS: user_cmds.append(f"`{PREFIX}trialaccount ` - Create a 24-hour trial Jellyfin account") diff --git a/version.json b/version.json index 86ad267..c27ffa9 100644 --- a/version.json +++ b/version.json @@ -1 +1 @@ -{ "version": "1.0.6" } \ No newline at end of file +{ "version": "1.0.7" } \ No newline at end of file diff --git a/version.txt b/version.txt index ece61c6..f9cbc01 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.0.6 \ No newline at end of file +1.0.7 \ No newline at end of file