404 lines
18 KiB
Python
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}")
|