Files
usk_schedule_downloader/discord_torrent.py
2026-03-30 13:43:29 +07:00

404 lines
18 KiB
Python

import discord
from discord.ext import commands
from discord import app_commands
from lib.torrent_creator import TorrentUpload
from lib.logging_data import logger
from lib.sonarr import Sonarr_API
from dotenv import load_dotenv
from typing import Optional
import os
import asyncio
from pathlib import Path
load_dotenv(".env")
# Bot configuration
intents = discord.Intents.default()
intents.message_content = True
intents.members = True
console = logger(app_name="torrent_uploader",log_dir="./log")
class DownloadBot(commands.Bot):
def __init__(self):
super().__init__(
command_prefix='!',
intents=intents,
help_command=None
)
# Initialize data storage (in-memory for this example)
# In production, you'd want to use a database
self.upload_queue = []
async def setup_hook(self):
"""Called when the bot is starting up"""
console.log(f"Logged in as {self.user} (ID: {self.user.id})")
console.log("------")
# Sync slash commands
try:
synced = await self.tree.sync()
console.log(f"Synced {len(synced)} command(s)")
# threading.Thread(target=vt_worker).start() # Start the download worker in the background
except Exception as e:
console.error(f"Failed to sync commands: {e}")
bot = DownloadBot()
console.client=bot
sonarr=Sonarr_API(os.getenv("sonarr_ip"),os.getenv("sonarr_key"))
@bot.event
async def on_ready():
console.log(f'{bot.user} has connected to Discord!')
activity = discord.Game(name="Managing upload | /help")
await bot.change_presence(activity=activity)
asyncio.create_task(torrent_worker())
@bot.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
embed = discord.Embed(
title="❌ Error",
description=f"An error occurred: {str(error)}",
color=0xff0000
)
channel = bot.get_channel(interaction.channel_id)
if interaction.response.is_done():
await interaction.followup.send(embed=embed, ephemeral=True)
else:
await channel.send(embed=embed)
console.error(error)
# /upload command
@bot.tree.command(name="sonarr_upload", description="Create and Upload a torrent file to Torrent server from Sonarr")
@app_commands.describe(
sonarr_id="Sonarr title id",
season="Sonarr season",
episode="Sonarr episode",
category="Category of the torrent (e.g., Anime, Western Series, Hi-Def Movie)",
source_type="Source type of the torrent (e.g., Blu-ray, WEB-DL, Encode)",
source="Source of the torrent (e.g., Master, Zoom, TV)",
country="Country of the torrent (e.g., Thai, Western, Korean)",
original_platform="Original platform of the torrent (e.g., DVD, Hi-def, TV)",
is_subdir="Is the torrent in a subdirectory?",
bearbit="Bearbit flag (True/False)",
torrentdd="TorrentDD flag (True/False)",
is_movie="Is this a movie upload? (True/False)",
pack="Pack flag (True/False)",
# single_season="Single Season (True/False)"
)
@app_commands.choices(category=[
### Anime
app_commands.Choice(name="Anime", value="Anime"),
### Series
app_commands.Choice(name="Western Series", value="Western Series"),
app_commands.Choice(name="Korean Series", value="Korean Series"),
app_commands.Choice(name="Japanese Series", value="Japanese Series"),
app_commands.Choice(name="Chinese Series", value="Chinese Series"),
app_commands.Choice(name="Thai Series", value="Thai Series"),
app_commands.Choice(name="Other Series", value="Other Series"),
### Movies
app_commands.Choice(name="Hi-Def Movie", value="Hi-Def Movie"),
app_commands.Choice(name="4K", value="4K"),
### Documentaries
app_commands.Choice(name="Documentary", value="สารคดี"),
])
@app_commands.choices(source_type=[
app_commands.Choice(name="Blu-ray", value="Blu-ray"),
app_commands.Choice(name="CD", value="CD"),
app_commands.Choice(name="DVD5", value="DVD5"),
app_commands.Choice(name="DVD9", value="DVD9"),
app_commands.Choice(name="Encode", value="Encode"),
app_commands.Choice(name="HD DVD", value="HD DVD"),
app_commands.Choice(name="HDTV", value="HDTV"),
app_commands.Choice(name="MiniBD", value="MiniBD"),
app_commands.Choice(name="Remux", value="Remux"),
app_commands.Choice(name="Track", value="Track"),
app_commands.Choice(name="WEB-DL", value="WEB-DL"),
app_commands.Choice(name="Image", value="Image")
])
@app_commands.choices(source=[
app_commands.Choice(name="Master", value="Master"),
app_commands.Choice(name="หนังซูม", value="Zoom"),
app_commands.Choice(name="V2D From Master", value="V2D"),
app_commands.Choice(name="From TV", value="TV"),
app_commands.Choice(name="From HD-TV", value="HD-TV"),
app_commands.Choice(name="Hi-def rip from Master", value="Hi-def"),
### Anime
app_commands.Choice(name="V2D From Master/DVD Modified", value="From Master/DVD"),
app_commands.Choice(name="อัดจาก TV", value="Rip TV"),
app_commands.Choice(name="rip from Master", value="Rip Master"),
app_commands.Choice(name="Scan", value="scan")
])
@app_commands.choices(country=[
app_commands.Choice(name="Thai", value="Thai"),
app_commands.Choice(name="Western", value="Western"),
app_commands.Choice(name="Korean", value="Korean"),
app_commands.Choice(name="Japanese", value="Japanese"),
app_commands.Choice(name="Chinese", value="Chinese"),
app_commands.Choice(name="Other", value="Other")
])
@app_commands.choices(original_platform=[
app_commands.Choice(name="DVD", value="DVD"),
app_commands.Choice(name="Hi-def", value="Hi-def"),
app_commands.Choice(name="TV", value="TV"),
app_commands.Choice(name="Books", value="Books"),
app_commands.Choice(name="Other", value="Other"),
app_commands.Choice(name="Netflix", value="Netflix")
])
@app_commands.choices(is_subdir=[
app_commands.Choice(name="True", value='True'),
app_commands.Choice(name="False", value='False')
])
@app_commands.choices(bearbit=[
app_commands.Choice(name="True", value='True'),
app_commands.Choice(name="False", value='False')
])
@app_commands.choices(torrentdd=[
app_commands.Choice(name="True", value='True'),
app_commands.Choice(name="False", value='False')
])
@app_commands.choices(is_movie=[
app_commands.Choice(name="True", value='True'),
app_commands.Choice(name="False", value='False')
])
@app_commands.choices(pack=[
app_commands.Choice(name="True", value='True'),
app_commands.Choice(name="False", value='False')
])
# @app_commands.choices(single_season=[
# app_commands.Choice(name="True", value='True'),
# app_commands.Choice(name="False", value='False')
# ])
async def sonarr_upload_command(interaction: discord.Interaction,
sonarr_id: str,
season: Optional[str],
episode: Optional[str],
category: Optional[str],
source_type: Optional[str],
source: Optional[str],
country: Optional[str],
original_platform: Optional[str],
is_subdir: Optional[str] = "False",
bearbit: Optional[str] = "True",
torrentdd: Optional[str] = "True",
is_movie: Optional[str] = "False",
pack: Optional[str] = "False",
# single_season: Optional[str] = "False",
):
embed = discord.Embed(
title="Torrent Upload",
description="Creating and uploading torrent file...",
color=discord.Color.blue()
)
embed.add_field(name="Sonarr title id", value=sonarr_id, inline=False)
embed.add_field(name="Season", value=season, inline=False)
embed.add_field(name="Episode", value=episode, inline=False)
embed.add_field(name="Category", value=category if category else "Not provided", inline=False)
embed.add_field(name="Source Type", value=source_type if source_type else "Not provided", inline=False)
embed.add_field(name="Source", value=source if source else "Not provided", inline=False)
embed.add_field(name="Country", value=country if country else "Not provided", inline=False)
embed.add_field(name="Original Platform", value=original_platform if original_platform else "Not provided", inline=False)
embed.add_field(name="Is Subdirectory", value=is_subdir if is_subdir else "Not provided", inline=False)
embed.add_field(name="BearBit", value=bearbit if bearbit else "Not provided", inline=False)
embed.add_field(name="TorrentDD", value=torrentdd if torrentdd else "Not provided", inline=False)
embed.add_field(name="Is Movie", value=is_movie if is_movie else "Not provided", inline=False)
embed.add_field(name="Pack", value=pack if pack else "Not provided", inline=False)
# embed.add_field(name="Single Season", value=pack if pack else "Not provided", inline=False)
embed.set_footer(text="This may take a while, please be patient...")
await interaction.response.send_message(embed=embed)
console.log("Starting torrent creation and upload process...")
print_detail=('Sonarr title id:', sonarr_id, '| Sonarr season:', season if season else "Not provided", '| Sonarr episode:', episode if episode else "Not provided", '| category:', category if category else "Not provided", '| source_type:', source_type if source_type else "Not provided", '| source:', source if source else "Not provided", '| country:', country if country else "Not provided", '| original_platform:', original_platform if original_platform else "Not provided", '| is_subdir:', is_subdir, '| bearbit:', bearbit, '| torrentdd:', torrentdd, '| is_movie:', is_movie, '| pack:', pack)
console.log("".join(print_detail))
if not sonarr_id:
await interaction.followup.send("❌ Sonarr id is required for torrent creation.")
return
if not category:
await interaction.followup.send("❌ Category is required for torrent creation.")
return
if not source_type:
await interaction.followup.send("❌ Source type is required for torrent creation.")
return
if not source:
await interaction.followup.send("❌ Source is required for torrent creation.")
return
if not country:
await interaction.followup.send("❌ Country is required for torrent creation.")
return
if not original_platform:
await interaction.followup.send("❌ Original platform is required for torrent creation.")
return
if is_subdir not in ['True', 'False']:
await interaction.followup.send("❌ is_subdir must be either 'True' or 'False'.")
return
if bearbit not in ['True', 'False']:
await interaction.followup.send("❌ bearbit must be either 'True' or 'False'.")
return
if torrentdd not in ['True', 'False']:
await interaction.followup.send("❌ torrentdd must be either 'True' or 'False'.")
return
if is_movie not in ['True', 'False']:
await interaction.followup.send("❌ is_movie must be either 'True' or 'False'.")
return
if pack not in ['True', 'False']:
await interaction.followup.send("❌ pack must be either 'True' or 'False'.")
return
is_subdir = is_subdir == 'True'
bearbit = bearbit == 'True'
torrentdd = torrentdd == 'True'
is_movie = is_movie == 'True'
pack = pack == 'True'
file_path=get_file_path(sonarr,sonarr_id,season=season,episode=episode)
entry= {
"file_path": file_path["path"],
"imdb_id": file_path["imdbId"],
"tmdb_id": file_path["tmdbId"],
"category": category,
"source_type": source_type,
"source": source,
"country": country,
"original_platform": original_platform,
"is_subdir": is_subdir,
"bearbit": bearbit,
"torrentdd": torrentdd,
"is_movie": is_movie,
'channel_id': interaction.channel_id,
'pack': pack,
# 'single_season' : single_season
}
# Add the entry to the upload queue
bot.upload_queue.append(entry)
console.log(f"Added to upload queue: {entry}")
# Start the torrent worker if not already running
embed = discord.Embed(
title="Torrent Upload",
description="Torrent creation and upload process has been started. You will be notified once the process is completed.",
color=discord.Color.green()
)
embed.set_footer(text="Please wait while the bot processes your request...")
await interaction.followup.send(embed=embed)
def get_file_path(sonarr:Sonarr_API,sonarr_id,season,episode):
sonarr_series=sonarr.get_series_detail(sonarr_id)
tmdbId=sonarr_series["tmdbId"]
imdbId=sonarr_series["imdbId"]
sonarr_seasons=sonarr_series["seasons"]
if season is not None and episode is None:
for ss in sonarr_seasons:
if int(season)==int(ss["seasonNumber"]):
if season>0:
file_path=Path.joinpath(Path(sonarr_series["path"]),f"S{season:02}").__str__()
return {"path":file_path,"tmdbId":tmdbId,"imdbId":imdbId}
else:
file_path=Path.joinpath(Path(sonarr_series["path"]),"Specials").__str__()
return {"path":file_path,"tmdbId":tmdbId,"imdbId":imdbId}
elif season is not None and episode is not None:
season_episode_list=sonarr.get_episode_detail_from_season(sonarr_id,season=season)
for ep in season_episode_list:
if int(ep["seasonNumber"] )==int(season) and int(ep["episodeNumber"] )==int(episode) :
ep_detail=sonarr.get_episode_detail(ep["id"])
file_path=ep_detail["episodeFile"]["path"]
return {"path":file_path,"tmdbId":tmdbId,"imdbId":imdbId}
else:
file_path=sonarr_series["path"]
return {"path":file_path,"tmdbId":tmdbId,"imdbId":imdbId}
async def torrent_worker():
"""Continuously process the upload queue in the background"""
while True:
if bot.upload_queue:
discord_webhook=os.getenv('torrent_update_webhook').split(",")
entry = bot.upload_queue.pop(0)
channel=bot.get_channel(entry['channel_id'])
torrentupload=TorrentUpload(console=console)
status = await torrentupload.upload_torrent(entry)
if not (any(item['status'] is False for item in status['bearbit']) or any(item['status'] is False for item in status['torrentdd'])):
embed = discord.Embed(
title="Torrent Upload Completed",
description="All torrents have been created and uploaded successfully.",
color=discord.Color.green()
)
bb_text=""
for staBB in status['bearbit']:
bb_text+=f"{"Uploaded" if staBB["status"] else "Fail Upload"} : [{staBB["name"]}]({staBB["url"]})"
bb_text+="\n"
# bb_text+=staBB["url"]
# bb_text+="\n"
embed.add_field(name="BearBit", value=bb_text, inline=False)
dd_text=""
for staDD in status['torrentdd']:
dd_text+=f"{"Uploaded" if staDD["status"] else "Fail Upload"} : [{staDD["name"]}]({staDD["url"]})"
dd_text+="\n"
# dd_text+=staBB["url"]
# dd_text+="\n"
embed.add_field(name="TorrentDD", value=dd_text, inline=False)
# if status['bearbit'] or status['torrentdd']:
# for i,torrent in enumerate(torrent_files['all']):
# embed.add_field(name="Title", value=os.path.basename(torrent).replace(".all.torrent",""), inline=False)
# embed.add_field(name="BearBit", value="[Click here to visit BearBit](https://bearbit.org/)", inline=False)
# embed.add_field(name="TorrentDD", value="[Click here to visit TorrentDD](https://torrentdd.com/)", inline=False)
embed.set_footer(text="Thank you for visiting!")
console.debug(status)
console.log('Torrent Upload Completed',is_discord={"channel": channel,"embed": embed,"web_hook_urls":discord_webhook})
else:
embed = discord.Embed(
title="Torrent Upload Failed",
description="Some or all torrents were fail to uploaded. Please check the logs for details.",
color=discord.Color.red() if (any(item['status'] is False for item in status['bearbit']) and any(item['status'] is False for item in status['torrentdd'])) else discord.Color.orange()
)
bb_text=""
# torrent_name=None
for staBB in status['bearbit']:
bb_text+=f"{"Uploaded" if staBB["status"] else "Fail Upload"} : {staBB["name"]}"
bb_text+="\n"
embed.add_field(name="BearBit", value=bb_text, inline=False)
dd_text=""
for staDD in status['torrentdd']:
dd_text+=f"{"Uploaded" if staDD["status"] else "Fail Upload"} : {staDD["name"]}"
dd_text+="\n"
embed.add_field(name="TorrentDD", value=dd_text, inline=False)
embed.set_footer(text="Please check the logs for more details.")
console.debug(status)
console.error('Torrent Upload Failed',is_discord={"channel": channel,"embed": embed})
else:
await asyncio.sleep(5) # Sleep briefly if queue is empty
if __name__ == "__main__":
token = os.getenv('TORRENT_DISCORD_TOKEN')
if not token:
console.error("❌ TORRENT_DISCORD_TOKEN not found in environment variables!")
console.error("Make sure you have a .env file with your bot token.")
else:
try:
bot.run(token)
except discord.LoginFailure:
console.error("❌ Invalid bot token! Please check your TORRENT_DISCORD_TOKEN in the .env file.")
except Exception as e:
console.error(f"❌ An error occurred: {e}")