599 lines
22 KiB
Python
599 lines
22 KiB
Python
"""Plex integration with the bot."""
|
|
from math import floor, ceil
|
|
import time
|
|
import asyncio
|
|
import requests
|
|
import imdb
|
|
import discord
|
|
import xmltodict
|
|
|
|
from discord_slash.utils.manage_components import (create_button,
|
|
create_actionrow)
|
|
from discord_slash.model import ButtonStyle
|
|
|
|
from gwendolyn.utils import encode_id
|
|
|
|
class Plex():
|
|
"""Container for Plex functions and commands."""
|
|
def __init__(self,bot):
|
|
self.bot = bot
|
|
self.credentials = self.bot.credentials
|
|
self.long_strings = self.bot.long_strings
|
|
server_ip = ["localhost", "192.168.0.40"][self.bot.options["testing"]]
|
|
|
|
self.radarr_url = "http://"+server_ip+":7878/api/v3/"
|
|
self.sonarr_url = "http://"+server_ip+":8989/api/"
|
|
self.qbittorrent_url = "http://"+server_ip+":8080/api/v2/"
|
|
self.movie_path = "/media/plex/Server/movies/"
|
|
self.show_path = "/media/plex/Server/Shows/"
|
|
|
|
|
|
async def request_movie(self, ctx, movie_name):
|
|
"""Request a movie for the Plex Server"""
|
|
await self.bot.defer(ctx)
|
|
|
|
self.bot.log("Searching for "+movie_name)
|
|
movie_list = imdb.IMDb().search_movie(movie_name)
|
|
movies = []
|
|
for movie in movie_list:
|
|
if movie["kind"] == "movie":
|
|
movies.append(movie)
|
|
if len(movies) > 5:
|
|
movies = movies[:5]
|
|
|
|
if len(movies) == 1:
|
|
message_title = "**Is it this movie?**"
|
|
else:
|
|
message_title = "**Is it any of these movies?**"
|
|
|
|
message_text = ""
|
|
imdb_ids = []
|
|
|
|
for i, movie in enumerate(movies):
|
|
try:
|
|
message_text += "\n"+str(i+1)+") "+movie["title"]
|
|
try:
|
|
message_text += " ("+str(movie["year"])+")"
|
|
except KeyError:
|
|
self.bot.log(f"{movie['title']} has no year.")
|
|
except KeyError:
|
|
message_text += "Error"
|
|
imdb_ids.append(movie.movieID)
|
|
|
|
self.bot.log(
|
|
f"Returning a list of {len(movies)} possible movies: {imdb_ids}"
|
|
)
|
|
|
|
embed = discord.Embed(
|
|
title=message_title,
|
|
description=message_text,
|
|
colour=0x00FF00
|
|
)
|
|
|
|
buttons = []
|
|
if len(movies) == 1:
|
|
buttons.append(create_button(
|
|
style=ButtonStyle.green,
|
|
label="✓",
|
|
custom_id=encode_id(["plex", "movie", str(imdb_ids[0])])
|
|
)
|
|
)
|
|
else:
|
|
for i in range(len(movies)):
|
|
buttons.append(
|
|
create_button(
|
|
style=ButtonStyle.blue,
|
|
label=str(i+1),
|
|
custom_id=encode_id(["plex", "movie", str(imdb_ids[i])])
|
|
)
|
|
)
|
|
|
|
buttons.append(create_button(
|
|
style=ButtonStyle.red,
|
|
label="X",
|
|
custom_id=encode_id(["plex", "movie", "x"])
|
|
)
|
|
)
|
|
|
|
action_rows = []
|
|
for i in range(((len(buttons)-1)//5)+1):
|
|
action_rows.append(
|
|
create_actionrow(
|
|
*buttons[(i*5):(min(len(buttons),i*5+5))]
|
|
)
|
|
)
|
|
|
|
await ctx.send(embed=embed, components=action_rows)
|
|
|
|
|
|
|
|
|
|
async def add_movie(self, message, imdb_id, edit_message = True):
|
|
"""Add a movie to Plex server."""
|
|
|
|
if not edit_message:
|
|
await message.delete()
|
|
|
|
if imdb_id == "X":
|
|
self.bot.log("Did not find what the user was searching for")
|
|
message_text = "Try searching for the IMDB id"
|
|
else:
|
|
self.bot.log("Trying to add movie "+str(imdb_id))
|
|
|
|
# Searches for the movie using the imdb id through Radarr
|
|
api_key = self.credentials["radarr_key"]
|
|
request_url = self.radarr_url+"movie/lookup/imdb?imdbId=tt"+imdb_id
|
|
request_url += "&apiKey="+api_key
|
|
response = requests.get(request_url)
|
|
|
|
# Makes the dict used for the post request
|
|
lookup_data = response.json()
|
|
post_data = {
|
|
"qualityProfileId": 1,
|
|
"rootFolderPath" : self.movie_path,
|
|
"monitored" : True,
|
|
"addOptions": {"searchForMovie": True}
|
|
}
|
|
for key in ["tmdbId","title","titleSlug","images","year"]:
|
|
post_data.update({key : lookup_data[key]})
|
|
|
|
# Makes the post request
|
|
response = requests.post(
|
|
url = self.radarr_url+"movie?apikey="+api_key,
|
|
json = post_data
|
|
)
|
|
|
|
# Deciphers the response
|
|
if response.status_code == 201:
|
|
self.bot.log("Added "+post_data["title"]+" to Plex")
|
|
message_text = "{} successfully added to Plex".format(
|
|
post_data["title"]
|
|
)
|
|
elif response.status_code == 400:
|
|
self.bot.log("The movie was already on plex")
|
|
message_text = self.long_strings["Already on Plex"].format(
|
|
post_data['title']
|
|
)
|
|
else:
|
|
self.bot.log(str(response.status_code)+" "+response.reason)
|
|
message_text = "Something went wrong",
|
|
|
|
if edit_message:
|
|
await message.edit(
|
|
embed = None,
|
|
content = message_text,
|
|
components = []
|
|
)
|
|
else:
|
|
await message.channel.send(message_text)
|
|
|
|
async def request_show(self, ctx, show_name):
|
|
"""Request a show for the Plex server."""
|
|
await self.bot.defer(ctx)
|
|
|
|
self.bot.log("Searching for "+show_name)
|
|
movies = imdb.IMDb().search_movie(show_name) # Replace with tvdb
|
|
shows = []
|
|
for movie in movies:
|
|
if movie["kind"] in ["tv series","tv miniseries"]:
|
|
shows.append(movie)
|
|
if len(shows) > 5:
|
|
shows = shows[:5]
|
|
|
|
if len(shows) == 1:
|
|
message_title = "**Is it this show?**"
|
|
else:
|
|
message_title = "**Is it any of these shows?**"
|
|
|
|
message_text = ""
|
|
imdb_ids = []
|
|
|
|
for i, show in enumerate(shows):
|
|
try:
|
|
message_text += f"\n{i+1}) {show['title']} ({show['year']})"
|
|
except KeyError:
|
|
try:
|
|
message_text += "\n"+str(i+1)+") "+show["title"]
|
|
except KeyError:
|
|
message_text += "Error"
|
|
imdb_ids.append(show.movieID)
|
|
|
|
self.bot.log(
|
|
f"Returning a list of {len(shows)} possible shows: {imdb_ids}"
|
|
)
|
|
|
|
embed = discord.Embed(
|
|
title=message_title,
|
|
description=message_text,
|
|
colour=0x00FF00
|
|
)
|
|
|
|
buttons = []
|
|
if len(shows) == 1:
|
|
buttons.append(create_button(
|
|
style=ButtonStyle.green,
|
|
label="✓",
|
|
custom_id=encode_id(["plex", "show", str(imdb_ids[0])])
|
|
)
|
|
)
|
|
else:
|
|
for i in range(len(shows)):
|
|
buttons.append(
|
|
create_button(
|
|
style=ButtonStyle.blue,
|
|
label=str(i+1),
|
|
custom_id=encode_id(["plex", "show", str(imdb_ids[i])])
|
|
)
|
|
)
|
|
|
|
buttons.append(create_button(
|
|
style=ButtonStyle.red,
|
|
label="X",
|
|
custom_id=encode_id(["plex", "show", "x"])
|
|
)
|
|
)
|
|
|
|
action_rows = []
|
|
for i in range(((len(buttons)-1)//5)+1):
|
|
action_rows.append(
|
|
create_actionrow(
|
|
*buttons[(i*5):(min(len(buttons),i*5+5))]
|
|
)
|
|
)
|
|
|
|
|
|
await ctx.send(embed=embed, components=action_rows)
|
|
|
|
async def add_show(self, message, imdb_id, edit_message = True):
|
|
"""Add the requested show to Plex."""
|
|
if imdb_id == "X":
|
|
self.bot.log("Did not find what the user was searching for")
|
|
message_text = "Try searching for the IMDB id"
|
|
else:
|
|
self.bot.log("Trying to add show "+str(imdb_id))
|
|
|
|
# Finds the tvdb id
|
|
tvdb_api_url = "https://thetvdb.com/api/"
|
|
tvdb_method = "GetSeriesByRemoteID.php"
|
|
tvdb_request_url = f"{tvdb_api_url}{tvdb_method}"
|
|
tvdb_id = xmltodict.parse(
|
|
requests.get(
|
|
tvdb_request_url+f"?imdbid=tt{imdb_id}",
|
|
headers = {"ContentType" : "application/json"}
|
|
).text
|
|
)['Data']['Series']['seriesid']
|
|
|
|
# Finds the rest of the information using Sonarr
|
|
api_key = self.credentials["sonarr_key"]
|
|
request_url = self.sonarr_url+"series/lookup?term="
|
|
request_url += f"tvdb:{tvdb_id}"
|
|
request_url += "&apiKey="+api_key
|
|
response = requests.get(request_url)
|
|
|
|
# Makes the dict used for the post request
|
|
lookup_data = response.json()[0]
|
|
post_data = {
|
|
"ProfileId" : 1,
|
|
"rootFolder_path" : self.show_path,
|
|
"monitored" : True,
|
|
"addOptions" : {"searchForMissingEpisodes" : True}
|
|
}
|
|
for key in ["tvdbId","title","titleSlug","images","seasons"]:
|
|
post_data.update({key : lookup_data[key]})
|
|
|
|
# Makes the post request
|
|
response = requests.post(
|
|
url= self.sonarr_url+"series?apikey="+api_key,
|
|
json = post_data
|
|
)
|
|
|
|
# Deciphers the response
|
|
if response.status_code == 201:
|
|
self.bot.log("Added a "+post_data["title"]+" to Plex")
|
|
message_text = post_data["title"]+" successfully added to Plex"
|
|
elif response.status_code == 400:
|
|
message_text = self.long_strings["Already on Plex"].format(
|
|
post_data['title']
|
|
)
|
|
else:
|
|
self.bot.log(str(response.status_code)+" "+response.reason)
|
|
message_text = "Something went wrong"
|
|
|
|
if edit_message:
|
|
await message.edit(
|
|
embed = None,
|
|
content = message_text,
|
|
components = []
|
|
)
|
|
else:
|
|
await message.channel.send(message_text)
|
|
|
|
async def __generate_download_list(self, show_dm, show_movies, show_shows,
|
|
episodes):
|
|
"""Generate a list of all torrents.
|
|
|
|
*Returns*
|
|
message_text: str
|
|
A formatted list of all torrents
|
|
|
|
all_downloaded: bool
|
|
Whether all torrents are downloaded
|
|
"""
|
|
self.bot.log("Generating torrent list")
|
|
title_width = 100
|
|
message = []
|
|
all_downloaded = True
|
|
|
|
if show_dm:
|
|
message.append("")
|
|
dm_section_title = "*Torrent Downloads*"
|
|
dm_section_title_line = "-"*((title_width-len(dm_section_title))//2)
|
|
message.append(
|
|
dm_section_title_line+dm_section_title+dm_section_title_line
|
|
)
|
|
login_url = self.qbittorrent_url+"auth/login"
|
|
username = self.credentials["qbittorrent_username"]
|
|
password = self.credentials["qbittorrent_password"]
|
|
login_url += f"?username={username}&password={password}"
|
|
cookie = {"SID": requests.get(login_url).cookies.values()[0]}
|
|
|
|
response = requests.get(
|
|
self.qbittorrent_url+"torrents/info",
|
|
cookies=cookie
|
|
)
|
|
torrent_list = response.json()
|
|
|
|
if len(torrent_list) > 0:
|
|
for torrent in torrent_list:
|
|
torrent_name = torrent["name"]
|
|
if len(torrent_name) > 30:
|
|
if torrent_name[26] == " ":
|
|
torrent_name = torrent_name[:26]+"...."
|
|
else:
|
|
torrent_name = torrent_name[:27]+"..."
|
|
while len(torrent_name) < 30:
|
|
torrent_name += " "
|
|
|
|
if torrent["size"] == 0:
|
|
download_ratio = 0
|
|
elif torrent["amount_left"] == 0:
|
|
download_ratio = 1
|
|
else:
|
|
download_ratio = min(
|
|
torrent["downloaded"]/torrent["size"],
|
|
1
|
|
)
|
|
progress_bar = "|"+("█"*floor(download_ratio*20))
|
|
while len(progress_bar) < 21:
|
|
progress_bar += " "
|
|
|
|
progress_bar += "| "+str(floor(download_ratio*100))+"%"
|
|
|
|
while len(progress_bar) < 27:
|
|
progress_bar += " "
|
|
|
|
eta_in_seconds = torrent["eta"]
|
|
|
|
if eta_in_seconds >= 8640000:
|
|
eta = "∞"
|
|
else:
|
|
eta = ""
|
|
if eta_in_seconds >= 86400:
|
|
eta += str(floor(eta_in_seconds/86400))+"d "
|
|
if eta_in_seconds >= 3600:
|
|
eta += str(floor((eta_in_seconds%86400)/3600))+"h "
|
|
if eta_in_seconds >= 60:
|
|
eta += str(floor((eta_in_seconds%3600)/60))+"m "
|
|
|
|
eta += str(eta_in_seconds%60)+"s"
|
|
|
|
torrent_info = f"{torrent_name} {progress_bar} "
|
|
torrent_info += f"(Eta: {eta})"
|
|
|
|
if torrent["state"] == "stalledDL":
|
|
torrent_info += " (Stalled)"
|
|
|
|
if not (download_ratio == 1 and
|
|
torrent["last_activity"] < time.time()-7200):
|
|
message.append(torrent_info)
|
|
|
|
if download_ratio < 1 and torrent["state"] != "stalledDL":
|
|
all_downloaded = False
|
|
else:
|
|
message.append("No torrents currently downloading")
|
|
|
|
if show_movies:
|
|
message.append("")
|
|
movies_section_title = "*Missing movies not downloading*"
|
|
movies_section_line = (
|
|
"-"*((title_width-len(movies_section_title))//2)
|
|
)
|
|
message.append(
|
|
movies_section_line+movies_section_title+movies_section_line
|
|
)
|
|
movie_list = requests.get(
|
|
self.radarr_url+"movie?apiKey="+self.credentials["radarr_key"]
|
|
).json()
|
|
print(
|
|
self.radarr_url+"movie?apiKey="+self.credentials["radarr_key"]
|
|
)
|
|
movie_queue = requests.get(
|
|
self.radarr_url+"queue?apiKey="+self.credentials["radarr_key"]
|
|
).json()
|
|
movie_queue_ids = []
|
|
|
|
for queue_item in movie_queue["records"]:
|
|
movie_queue_ids.append(queue_item["movieId"])
|
|
|
|
for movie in movie_list:
|
|
if (not movie["hasFile"] and
|
|
movie["id"] not in movie_queue_ids):
|
|
movie_name = movie["title"]
|
|
if len(movie_name) > 40:
|
|
if movie_name[36] == " ":
|
|
movie_name = movie_name[:36]+"...."
|
|
else:
|
|
movie_name = movie_name[:37]+"..."
|
|
|
|
while len(movie_name) < 41:
|
|
movie_name += " "
|
|
|
|
if movie["monitored"]:
|
|
movie_info = movie_name+"Could not find a torrent"
|
|
else:
|
|
movie_info = self.long_strings["No torrent"].format(
|
|
movie_name
|
|
)
|
|
|
|
message.append(movie_info)
|
|
|
|
if show_shows:
|
|
message.append("")
|
|
show_section_title = "*Missing shows not downloading*"
|
|
show_section_line = "-"*((title_width-len(show_section_title))//2)
|
|
message.append(
|
|
show_section_line+show_section_title+show_section_line
|
|
)
|
|
|
|
show_list = requests.get(
|
|
self.sonarr_url+"series?apiKey="+self.credentials["sonarr_key"]
|
|
).json()
|
|
|
|
for show in show_list:
|
|
if show["seasons"][0]["seasonNumber"] == 0:
|
|
seasons = show["seasons"][1:]
|
|
else:
|
|
seasons = show["seasons"]
|
|
if any(
|
|
(
|
|
i["statistics"]["episodeCount"] !=
|
|
i["statistics"]["totalEpisodeCount"]
|
|
) for i in seasons):
|
|
if all(
|
|
i["statistics"]["episodeCount"] == 0 for i in seasons
|
|
):
|
|
message.append(show["title"] + " (all episodes)")
|
|
else:
|
|
if episodes:
|
|
missing_episodes = sum(
|
|
(i["statistics"]["totalEpisodeCount"] -
|
|
i["statistics"]["episodeCount"])
|
|
for i in seasons)
|
|
message.append(
|
|
f"{show['title']} ({missing_episodes} episodes)"
|
|
)
|
|
|
|
message.append("-"*title_width)
|
|
|
|
message_text = "```"+"\n".join(message[1:])+"```"
|
|
if message_text == "``````":
|
|
message_text = self.long_strings["No torrents downloading"]
|
|
return message_text, all_downloaded
|
|
|
|
async def downloading(self, ctx, content):
|
|
"""Send message with list of all downloading torrents."""
|
|
async def send_long_message(ctx,message_text):
|
|
if len(message_text) <= 1994:
|
|
await ctx.send("```"+message_text+"```")
|
|
else:
|
|
cut_off_index = message_text[:1994].rfind("\n")
|
|
await ctx.send("```"+message_text[:cut_off_index]+"```")
|
|
await send_long_message(ctx,message_text[cut_off_index+1:])
|
|
|
|
await self.bot.defer(ctx)
|
|
|
|
# showDM, showMovies, showShows, episodes
|
|
parameters = [False, False, False, False]
|
|
show_dm_args = ["d", "dm", "downloading", "downloadmanager"]
|
|
show_movies_args = ["m", "movies"]
|
|
show_shows_args = ["s", "shows", "series"]
|
|
show_episode_args = ["e", "episodes"]
|
|
arg_list = [
|
|
show_dm_args, show_movies_args, show_shows_args, show_episode_args
|
|
]
|
|
input_args = []
|
|
valid_arguments = True
|
|
|
|
while content != "" and valid_arguments:
|
|
if content[0] == " ":
|
|
content = content[1:]
|
|
elif content[0] == "-":
|
|
if content[1] == "-":
|
|
arg_start = 2
|
|
if " " in content:
|
|
arg_stop = content.find(" ")
|
|
else:
|
|
arg_stop = None
|
|
else:
|
|
arg_start = 1
|
|
arg_stop = 2
|
|
|
|
input_args.append(content[arg_start:arg_stop])
|
|
if arg_stop is None:
|
|
content = ""
|
|
else:
|
|
content = content[arg_stop:]
|
|
else:
|
|
valid_arguments = False
|
|
|
|
if valid_arguments:
|
|
for arg_index, arg_aliases in enumerate(arg_list):
|
|
arg_in_input = [i in input_args for i in arg_aliases]
|
|
if any(arg_in_input):
|
|
input_args.remove(arg_aliases[arg_in_input.index(True)])
|
|
parameters[arg_index] = True
|
|
|
|
if len(input_args) != 0 or (not parameters[2] and parameters[3]):
|
|
valid_arguments = False
|
|
|
|
show_anything = any(i for i in parameters)
|
|
if not (valid_arguments and show_anything):
|
|
await ctx.send(self.long_strings["Invalid parameters"])
|
|
else:
|
|
message_text, all_downloaded = await self.__generate_download_list(
|
|
*parameters
|
|
)
|
|
if not message_text.startswith("```"):
|
|
await ctx.send(message_text)
|
|
|
|
elif len(message_text) > 2000:
|
|
message_text = message_text[3:-3]
|
|
await send_long_message(ctx,message_text)
|
|
|
|
elif all_downloaded:
|
|
await ctx.send(message_text)
|
|
|
|
else:
|
|
updates_left = 60
|
|
message_text = self.long_strings["Update"].format(
|
|
message_text[:-3], ceil(updates_left/6)
|
|
)
|
|
old_message = await ctx.send(message_text)
|
|
|
|
while ((not all_downloaded) and updates_left > 0):
|
|
await asyncio.sleep(10)
|
|
updates_left -= 1
|
|
message_text, all_downloaded = await (
|
|
self.__generate_download_list(*parameters)
|
|
)
|
|
message_text = self.long_strings["Update"].format(
|
|
message_text[:-3],
|
|
ceil(updates_left/6)
|
|
)
|
|
await old_message.edit(content = message_text)
|
|
|
|
message_text, all_downloaded = await (
|
|
self.__generate_download_list(*parameters)
|
|
)
|
|
|
|
if message_text.startswith("```"):
|
|
if all_downloaded:
|
|
self.bot.log("All torrents are downloaded")
|
|
else:
|
|
message_text = self.long_strings["No updates"].format(
|
|
message_text[:-3]
|
|
)
|
|
self.bot.log("The message updated 20 times")
|
|
|
|
await old_message.edit(content = message_text)
|