import discord from discord.ext import commands import asyncio import aiohttp from typing import Optional, List, Dict, Union, Any import logging import config from commands import safe_int_convert, get_api logger = logging.getLogger('utility_commands') class UtilityCommands(commands.Cog): def __init__(self, bot): self.bot = bot self.embed_color = config.EMBED_COLOR self.tmdb_base_url = "https://api.themoviedb.org/3" @commands.command(name="help", aliases=["h"]) async def custom_help(self, ctx, command: str = None): """Show help information for all commands or a specific command""" if command: cmd = self.bot.get_command(command) if cmd: embed = discord.Embed( title=f"Help: {config.BOT_PREFIX}{cmd.name}", description=cmd.help or "No description available", color=self.embed_color ) # Add aliases if any if cmd.aliases: embed.add_field( name="Aliases", value=", ".join([f"`{config.BOT_PREFIX}{alias}`" for alias in cmd.aliases]), inline=False ) # Add usage embed.add_field( name="Usage", value=f"`{config.BOT_PREFIX}{cmd.name} {cmd.signature}`", inline=False ) await ctx.send(embed=embed) return else: await ctx.send(f"Command `{command}` not found. Try `{config.BOT_PREFIX}help` to see all commands.") return embed = discord.Embed( title="Jellyseerr Discord Bot Help", description="Here are the commands you can use:", color=self.embed_color ) # Search & Discovery Commands embed.add_field( name="🔍 Search & Discovery", value=( f"`{config.BOT_PREFIX}search ` - Search for movies and TV shows\n" f"`{config.BOT_PREFIX}info ` - Get detailed information about any media (auto-detects type)\n" f"`{config.BOT_PREFIX}trending` - Show trending movies and TV shows" ), inline=False ) # Request Commands embed.add_field( name="📝 Requests", value=( f"`{config.BOT_PREFIX}request movie ` - Request a movie\n" f"`{config.BOT_PREFIX}request tv [seasons]` - Request a TV show (all or specific seasons)\n" f"`{config.BOT_PREFIX}requests [status] [page]` - List media requests\n" ), inline=False ) # Other Commands embed.add_field( name="🔧 Other", value=( f"`{config.BOT_PREFIX}help` - Show this help message\n" f"`{config.BOT_PREFIX}status` - Check Jellyseerr server status and authentication\n" f"`{config.BOT_PREFIX}testmedia ` - Test if media exists in TMDB and Jellyseerr\n" ), inline=False ) embed.set_footer(text=f"Bot Prefix: {config.BOT_PREFIX}") await ctx.send(embed=embed) @commands.command(name="testmedia") async def test_media(self, ctx, media_type: str = None, media_id = None): """ Test if a media ID exists directly on TMDB and check correct media type Example: !testmedia movie 83936 """ if not media_type and media_id: # If only one parameter is provided, assume it's the ID media_id = media_type media_type = None await ctx.send(f"Testing both movie and TV show for ID {media_id}...") elif not media_type or not media_id: await ctx.send("Please provide a media ID. Example: `!testmedia 83936` or `!testmedia movie 550`") return if media_type and media_type.lower() not in ['movie', 'tv']: await ctx.send("Invalid media type. Use 'movie' or 'tv' or just provide the ID to test both.") return media_id = safe_int_convert(media_id) if not media_id: await ctx.send(f"Please provide a valid media ID. Example: `!testmedia 550`") return async with ctx.typing(): try: # Try to get media info from Jellyseerr api = get_api() embed = discord.Embed( title=f"Media Test: ID {media_id}", description="Testing this ID against both movie and TV endpoints", color=self.embed_color ) # Test both movie and TV endpoints directly using HTTP requests async with aiohttp.ClientSession() as session: # Test movie endpoint async with session.get(f"{self.tmdb_base_url}/movie/{media_id}?api_key=12345") as movie_response: movie_status = movie_response.status # Test TV endpoint async with session.get(f"{self.tmdb_base_url}/tv/{media_id}?api_key=12345") as tv_response: tv_status = tv_response.status # Check which type of media this ID belongs to is_movie = movie_status == 401 # 401 means it exists but auth failed is_tv = tv_status == 401 # 401 means it exists but auth failed if is_movie and not is_tv: embed.add_field( name="TMDB Status", value="✅ This ID belongs to a **MOVIE**", inline=False ) correct_type = "movie" elif is_tv and not is_movie: embed.add_field( name="TMDB Status", value="✅ This ID belongs to a **TV SHOW**", inline=False ) correct_type = "tv" elif is_movie and is_tv: embed.add_field( name="TMDB Status", value="⚠️ Unusual: This ID seems to exist as both movie and TV show", inline=False ) correct_type = "both" else: embed.add_field( name="TMDB Status", value="❌ This ID was not found in TMDB database (neither movie nor TV show)", inline=False ) correct_type = None # If media_type was specified, check if it matches if media_type and correct_type and correct_type != "both" and media_type.lower() != correct_type: embed.add_field( name="Media Type Mismatch", value=f"⚠️ You specified `{media_type}` but this ID is for a `{correct_type}`.\n" f"Use `{config.BOT_PREFIX}{correct_type} {media_id}` instead.", inline=False ) # Test Jellyseerr access for the correct media type try: if correct_type == "movie" or (media_type and media_type.lower() == 'movie'): media_details = await api.get_movie_details(media_id) title = media_details.get('title', 'Unknown') embed.add_field( name="Jellyseerr Movie Status", value=f"✅ Found in Jellyseerr: {title}", inline=False ) elif correct_type == "tv" or (media_type and media_type.lower() == 'tv'): media_details = await api.get_tv_details(media_id) title = media_details.get('name', 'Unknown') embed.add_field( name="Jellyseerr TV Status", value=f"✅ Found in Jellyseerr: {title}", inline=False ) elif correct_type == "both": # Try both endpoints try: movie_details = await api.get_movie_details(media_id) movie_title = movie_details.get('title', 'Unknown') embed.add_field( name="Jellyseerr Movie Status", value=f"✅ Found as movie in Jellyseerr: {movie_title}", inline=False ) except: embed.add_field( name="Jellyseerr Movie Status", value="❌ Not found as movie in Jellyseerr", inline=False ) try: tv_details = await api.get_tv_details(media_id) tv_title = tv_details.get('name', 'Unknown') embed.add_field( name="Jellyseerr TV Status", value=f"✅ Found as TV show in Jellyseerr: {tv_title}", inline=False ) except: embed.add_field( name="Jellyseerr TV Status", value="❌ Not found as TV show in Jellyseerr", inline=False ) else: # No correct type identified # Try both if we couldn't determine the type tv_success = movie_success = False try: movie_details = await api.get_movie_details(media_id) if movie_details.get('title'): embed.add_field( name="Jellyseerr Status (Unexpected)", value=f"✅ Found as MOVIE in Jellyseerr: {movie_details.get('title')}", inline=False ) movie_success = True except: pass try: tv_details = await api.get_tv_details(media_id) if tv_details.get('name'): embed.add_field( name="Jellyseerr Status (Unexpected)", value=f"✅ Found as TV SHOW in Jellyseerr: {tv_details.get('name')}", inline=False ) tv_success = True except: pass if not tv_success and not movie_success: embed.add_field( name="Jellyseerr Status", value="❌ Not found in Jellyseerr (neither as movie nor TV show)", inline=False ) except Exception as e: embed.add_field( name="Jellyseerr Status", value=f"❌ Error retrieving from Jellyseerr: {str(e)}", inline=False ) # Add recommendations if not correct_type: embed.add_field( name="Recommendation", value="This media ID doesn't exist in TMDB. Try searching for the correct ID with:\n" f"`{config.BOT_PREFIX}search movie name` or `{config.BOT_PREFIX}search tv name`", inline=False ) elif "Error retrieving" in ''.join([f.value for f in embed.fields if hasattr(f, 'value')]): embed.add_field( name="Recommendation", value="The media exists in TMDB but your Jellyseerr account cannot access it. This could be due to:\n" "• Permission settings in your Jellyseerr account\n" "• Content filtering in Jellyseerr\n" "• The media may be excluded from your Jellyseerr instance", inline=False ) elif correct_type == "movie": embed.add_field( name="Command to Use", value=f"Use `{config.BOT_PREFIX}movie {media_id}` to get details\n" f"Use `{config.BOT_PREFIX}request movie {media_id}` to request it", inline=False ) elif correct_type == "tv": embed.add_field( name="Command to Use", value=f"Use `{config.BOT_PREFIX}tv {media_id}` to get details\n" f"Use `{config.BOT_PREFIX}request tv {media_id}` to request it", inline=False ) await ctx.send(embed=embed) except Exception as e: logger.error(f"Error in test_media command: {str(e)}", exc_info=True) await ctx.send(f"Error testing media: {str(e)}") @commands.command(name="status") async def get_status(self, ctx): """Check Jellyseerr server status""" async with ctx.typing(): try: api = get_api() status_data = await api._request('GET', '/status') embed = discord.Embed( title="Jellyseerr Server Status", color=self.embed_color ) version = status_data.get('version', 'Unknown') update_available = status_data.get('updateAvailable', False) commits_behind = status_data.get('commitsBehind', 0) embed.add_field(name="Version", value=version, inline=True) embed.add_field(name="Update Available", value="Yes" if update_available else "No", inline=True) if update_available: embed.add_field(name="Commits Behind", value=str(commits_behind), inline=True) embed.add_field(name="Server URL", value=config.JELLYSEERR_URL, inline=False) embed.add_field(name="Bot Account", value=config.JELLYSEERR_EMAIL, inline=False) # Check if authenticated try: api = get_api() if api and api.last_login: embed.add_field(name="Authentication", value="✅ Authenticated", inline=True) else: embed.add_field(name="Authentication", value="❌ Not authenticated", inline=True) except: embed.add_field(name="Authentication", value="❌ Not authenticated", inline=True) await ctx.send(embed=embed) except Exception as e: await ctx.send(f"Error checking server status: {str(e)}")