This commit is contained in:
2026-03-30 13:43:29 +07:00
commit b9bef86ea2
16 changed files with 7185 additions and 0 deletions

432
main.py Normal file
View File

@@ -0,0 +1,432 @@
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}")