from lib.logging_data import logger import discord from discord import app_commands from dotenv import load_dotenv from lib.usk import USK from datetime import datetime import asyncio from typing import Optional import os load_dotenv(".env") if __name__ == "__main__": console = logger(app_name="scheduler",log_dir="./log",gotify_config=os.getenv("gotify_token"),discord_config=os.getenv('DISCORD_CHANNEL_ID')) console.log("Starting Unshackle Schedule Bot...") # bot=ScheduleBot(console) # console.client=bot usk_scheduler = USK(console) console.client=usk_scheduler @usk_scheduler.event async def on_ready(): # console.log(f'{bot.user} has connected to Discord!') activity = discord.Game(name="Managing schedules | /help") await usk_scheduler.change_presence(activity=activity) @usk_scheduler.tree.command(name="help", description="Show bot commands and usage") async def help_command(interaction: discord.Interaction): embed = discord.Embed( title="Unshackle Schedule Bot Commands", description="Here are the available commands:", color=discord.Color.blue() ) embed.add_field(name="/help", value="Show this help message", inline=False) embed.add_field(name="/schedule", value="Schedule command", inline=False) embed.add_field(name="/get_watchlist", value="Show watchlist", inline=False) embed.add_field(name="/add_to_watchlist", value="Add watchlist", inline=False) embed.add_field(name="/add_schedule_overwrite", value="Add schedule overwrite", inline=False) await interaction.response.send_message(embed=embed) # Error handling @usk_scheduler.tree.error async def on_app_command_error(interaction: discord.Interaction, error: app_commands.AppCommandError): if isinstance(error, app_commands.CheckFailure): embed = discord.Embed( title="❌ Permission Denied", description="You don't have permission to use this command.", color=0xff0000 ) if not interaction.response.is_done(): await interaction.response.send_message(embed=embed, ephemeral=True) return else: embed = discord.Embed( title="❌ Error", description=f"An error occurred: {str(error)}", color=0xff0000 ) # Get line and file console.error("An error occurred") channel = usk_scheduler.get_channel(interaction.channel_id) if interaction.response.is_done(): await interaction.followup.send(embed=embed, ephemeral=True) else: await channel.send(embed=embed) # raise error @usk_scheduler.tree.command(name="schedule", description="Schedule command") @app_commands.describe( cmd="Command for schedule (e.g., start, check, stop)", ) @app_commands.choices(cmd=[ app_commands.Choice(name="Start", value="start"), app_commands.Choice(name="Check", value="check"), app_commands.Choice(name="Stop", value="stop"), ]) async def schedule_command( interaction: discord.Interaction, cmd: str, ): if cmd == "start": # await interaction.response.send_message("Starting the schedule...") # Here you would start your scheduling logic embed = discord.Embed( title="Schedule Starting", description="The schedule is starting now. Please wait...", color=discord.Color.green() ) await interaction.response.send_message(embed=embed) await usk_scheduler.set_today_schedule(interaction) console.log("Schedule started successfully.") embed = discord.Embed( title="Scheduled Jobs", description="Here are the currently scheduled jobs:", color=discord.Color.blue() ) jobs = usk_scheduler.scheduler.get_jobs() for job in jobs: if job: embed.add_field( name=job.id, value=f"Next run time: {job.next_run_time.strftime('%Y-%m-%d %H:%M:%S')}", inline=False ) await asyncio.sleep(1) await interaction.followup.send(embed=embed) elif cmd == "check": # Here you would check the current schedule embed = discord.Embed( title="Today's Schedule", description="Here is the current schedule for today:", color=discord.Color.blue() ) # schedule = bot.sonarr.get_today_schedule() queue=usk_scheduler.db.get_today_queue() # print(queue) # exit() if queue: for item in queue: embed.add_field( name=f"{item['title']} (S{item['season']:02}E{item['episode']:02}) - {item['status']}", value=f"Air Date: {datetime.fromtimestamp(item['start_timestamp']).strftime('%Y-%m-%d %H:%M:%S')}", inline=False ) else: embed.add_field(name="No Queue", value="There are no queued episodes for today.", inline=False) jobs = usk_scheduler.scheduler.get_jobs() if jobs: job_list = "\n".join([f"{job.id} - {job.next_run_time}" for job in jobs]) embed.add_field(name="Scheduled Jobs", value=job_list, inline=False) else: embed.add_field(name="No Scheduled Jobs", value="There are no scheduled jobs at the moment.", inline=False) await interaction.response.send_message(embed=embed) console.log('Checked the queue successfully.') elif cmd == "stop": await interaction.response.send_message("Stopping the schedule...") # Here you would stop your scheduling logic usk_scheduler.scheduler.shutdown(wait=False) await interaction.followup.send("Schedule stopped.") console.log('Schedule stopped successfully.') @usk_scheduler.tree.command(name="recheck", description="recheck schedule by weekday") @app_commands.describe( weekday="Day of the week (0=Monday, 6=Sunday)", ) @app_commands.choices(weekday=[ app_commands.Choice(name="Monday", value='1'), app_commands.Choice(name="Tuesday", value='2'), app_commands.Choice(name="Wednesday", value='3'), app_commands.Choice(name="Thursday", value='4'), app_commands.Choice(name="Friday", value='5'), app_commands.Choice(name="Saturday", value='6'), app_commands.Choice(name="Sunday", value='7'), app_commands.Choice(name="All", value='8'), ]) async def recheck_command( interaction: discord.Interaction, weekday: str, ): weekday=int(weekday) weekday_name = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday", "All"][weekday-1] await interaction.response.send_message(f"Rechecking schedule for {weekday_name}...") # Here you would start your scheduling logic await usk_scheduler.recheck_day(weekday,interaction) console.log(f"Rechecked the schedule for {weekday_name} successfully.") await asyncio.sleep(1) await interaction.followup.send(f"Rechecked the schedule for {weekday_name} successfully.") @usk_scheduler.tree.command(name="recheck_title", description="recheck schedule by title") @app_commands.describe( title="Title of the show" ) async def recheck_title_command( interaction: discord.Interaction, title: str, season: Optional[int] = None, episode: Optional[int] = None, ): await interaction.response.send_message(f"Rechecking schedule for {title}...") # Here you would start your scheduling logic await usk_scheduler.recheck_Title(title,interaction,season,episode) console.log(f"Rechecked the schedule for {title} successfully.") await asyncio.sleep(1) await interaction.followup.send(f"Rechecked the schedule for {title} successfully.") @usk_scheduler.tree.command(name="get_watchlist", description="Get watchlist command") async def get_watchlist_command(interaction: discord.Interaction): watchlist = usk_scheduler.db.get_watchlist() if not watchlist: await interaction.response.send_message("Your watchlist is empty.") return MAX_EMBED_CHARS = 5900 # Stay safely under 6000 char limit embeds = [] current_embed = discord.Embed( title="Your Watchlist", description="Here are the shows in your watchlist:", color=discord.Color.green() ) total_chars = len(current_embed.title) + len(current_embed.description) for show in watchlist: field_name = show['Title'] field_value = ( f"Service: {show['Service']}\n" f"URL: {show['url']}\n" f"Quality: {show['quality']}\n" f"Codec: {show['codec']}\n" f"Range: {show['range']}\n" f"Audio lang: {show['audio_lang']}\n" f"Subtitle lang: {show['sub_lang']}" ) # Check if adding this field would exceed the limit if total_chars + len(field_name) + len(field_value) > MAX_EMBED_CHARS: embeds.append(current_embed) current_embed = discord.Embed(color=discord.Color.green()) total_chars = 0 current_embed.add_field(name=field_name, value=field_value, inline=False) total_chars += len(field_name) + len(field_value) # Add the last embed embeds.append(current_embed) # Send embeds for i, embed in enumerate(embeds): if i == 0: await interaction.response.send_message(embed=embed) else: await interaction.followup.send(embed=embed) console.log('Fetched watchlist successfully.') @usk_scheduler.tree.command(name="add_to_watchlist", description="Add watchlist command") @app_commands.describe( id_="ID of the show as Sonarr ID (e.g., 12345)", service="Service (e.g., BLBL, TID, MMAX)", title="Title of the show", url="URL of the show can be ID or URL", audio_lang="Audio language (e.g., en, jp)", sub_lang="Subtitle language (e.g., en, jp)", quality="Quality of the show (e.g., 1080p, 720p)", codec="Codec used (e.g., h264, h265)", range_="Range of episodes (e.g., SDR,HDR)", audio_channel="Audio channel (e.g., 2.0,5.1)", season="Season number (e.g., 1, 2)", # for BiliBili if_dub="For BiliBili : If the show is dubbed (e.g., True, False)", url_org="For BiliBili : URL of the original show (if applicable)", title_lang="For BiliBili : Title language (e.g., en, jp)", org_lang="For BiliBili : Original language (e.g., en, jp)", ) @app_commands.choices(service=[ app_commands.Choice(name="Amazon Prime", value="AMZN"), app_commands.Choice(name="Netflix", value="NF"), app_commands.Choice(name="Hotstar", value="HS"), app_commands.Choice(name="VIU", value="VIU"), app_commands.Choice(name="TrueID", value="TID"), app_commands.Choice(name="Mono Max", value="MMAX"), app_commands.Choice(name="BiliBili", value="BLBL"), app_commands.Choice(name="FutureSkill", value="FSK"), app_commands.Choice(name="HBO Max", value="HMAX"), app_commands.Choice(name="iQIYI", value="IQ"), app_commands.Choice(name="WeTV", value="WTV"), app_commands.Choice(name="Crunchyroll", value="CR"), app_commands.Choice(name="Laftel", value="LT"), ]) @app_commands.choices(if_dub=[ app_commands.Choice(name="True", value="True"), app_commands.Choice(name="False", value="False"), ]) @app_commands.choices(quality=[ app_commands.Choice(name="2160p", value="2160"), app_commands.Choice(name="1440p", value="1440"), app_commands.Choice(name="1080p", value="1080"), app_commands.Choice(name="720p", value="720"), app_commands.Choice(name="480p", value="480"), app_commands.Choice(name="Best", value="Best"), ]) @app_commands.choices(codec=[ app_commands.Choice(name="H265", value="265"), app_commands.Choice(name="H264", value="264"), ]) @app_commands.choices(range_=[ app_commands.Choice(name="HDR", value="HDR"), app_commands.Choice(name="SDR", value="SDR"), ]) @app_commands.choices(audio_channel=[ app_commands.Choice(name="2.0", value="20"), app_commands.Choice(name="5.1", value="51"), app_commands.Choice(name="Best", value= "Best"), ]) async def add_to_watchlist_command( interaction: discord.Interaction, id_: int, service: str, title: str, url: str, audio_lang: str = "orig,th", sub_lang: str = "orig,th,en", quality:str = '1080', codec: str = '264', range_: str = 'SDR', audio_channel: str = None, #for BiliBili if_dub: str = "False", url_org: str = None, season: str = None, title_lang: str = None, org_lang: str = None, ): entry = { 'ID': id_, 'Service': service, 'Title': title, 'url': url, 'audio_lang': audio_lang, 'sub_lang': sub_lang, 'quality': int(quality), 'codec': int(codec), 'range': range_, 'audio_channel': audio_channel, 'if_dub': True if if_dub.lower() == "true" else False, 'url_org': url_org, 'season': int(season), 'title_lang': title_lang, 'org_lang': org_lang } usk_scheduler.db.add_watchlist(entry) embed = discord.Embed( title="Watchlist Entry Added", description=f"Added **{title}** to your watchlist.", color=discord.Color.green() ) await interaction.response.send_message(embed=embed) console.log(f'Added {title} to watchlist successfully.') @usk_scheduler.tree.command(name="add_schedule", description="Add schedule command") @app_commands.describe( title="Title of the show", air_time="Air time of the show (format: HHMM, e.g., 2100 for 9 PM)", day_of_week="Day of the week (Monday, Sunday or Monday,Sunday)", offset="Offset episode (e.g., 0 for no offset, 1 for next episode)", ) # @app_commands.choices(title=[ # app_commands.Choice(name=f'{t['Title'][:15]} season {t['season']}', value=t['Title'][:20]) for t in sorted(bot.db.get_watchlist(), key=lambda x: x["Title"], reverse=True) # ]) async def add_schedule_command(interaction: discord.Interaction, title: str, air_time: str, day_of_week: str, offset: int = 0): entry = { 'title': title, 'air_time': air_time, 'day_of_week': day_of_week, 'offset': offset } status=usk_scheduler.db.add_overwrite_schedule(entry) # console.log(f'Added schedule overwrite for {title} at {air_time} on {day_of_week} with offset {offset}. Status: {status}') if status == 'No changes made, entry already exists.': console.log(f'Schedule overwrite for {title} at {air_time} on {day_of_week} with offset {offset} already exists.') embed = discord.Embed( title="Schedule Overwrite Already Exists", description=f"An overwrite for **{title}** at **{air_time}** on **{day_of_week}** with offset **{offset}** already exists.", color=discord.Color.orange() ) await interaction.response.send_message(embed=embed, ephemeral=True) return elif status == 'Entry added or updated successfully.': # If the entry was added or updated successfully, send a confirmation message console.log(f'Schedule overwrite for {title} at {air_time} on {day_of_week} with offset {offset} added or updated successfully.') embed = discord.Embed( title="Schedule Overwrite Added", description=f"Added schedule overwrite for **{title}** at **{air_time}** on **{day_of_week}** with offset **{offset}**.", color=discord.Color.green() ) await interaction.response.send_message(embed=embed) return token = os.getenv("DISCORD_TOKEN") if not token: print("❌ DISCORD_TOKEN not found in environment variables!") print("Make sure you have a .env file with your bot token.") else: try: # bot.usk_scheduler.set_today_schedule() # Initialize the schedule usk_scheduler.run(token, log_handler=None) except discord.LoginFailure: print("❌ Invalid bot token! Please check your DISCORD_TOKEN in the .env file.") except Exception as e: print(f"❌ An error occurred: {e}")