Merge branch 'nightly' into dev

This commit is contained in:
Clinton Hall 2018-06-22 16:24:13 +12:00 committed by GitHub
commit 9ac74615af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1185 additions and 29 deletions

View file

@ -17,7 +17,7 @@ Failed download handling for SickBeard is available by using Tolstyak's fork [Si
To use this feature, in autoProcessTV.cfg set the parameter "fork=failed". Default is "fork=default" and will work with the standard version of SickBeard and just ignores failed downloads. To use this feature, in autoProcessTV.cfg set the parameter "fork=failed". Default is "fork=default" and will work with the standard version of SickBeard and just ignores failed downloads.
Development of Tolstyak's fork ended in 2013, but newer forks exist with significant feature updates such as [Mr-Orange TPB](https://github.com/coach0742/Sick-Beard) (discontinued), [SickRageTV](https://github.com/SiCKRAGETV/SickRage) and [SickRage](https://github.com/SickRage/SickRage) (active). See [SickBeard Forks](https://github.com/clinton-hall/nzbToMedia/wiki/Failed-Download-Handling-%28FDH%29#sick-beard-and-its-forks "SickBeard Forks") for a list of known forks. Development of Tolstyak's fork ended in 2013, but newer forks exist with significant feature updates such as [Mr-Orange TPB](https://github.com/coach0742/Sick-Beard) (discontinued), [SickRageTV](https://github.com/SiCKRAGETV/SickRage) and [SickRage](https://github.com/SickRage/SickRage) (active). See [SickBeard Forks](https://github.com/clinton-hall/nzbToMedia/wiki/Failed-Download-Handling-%28FDH%29#sick-beard-and-its-forks "SickBeard Forks") for a list of known forks.
Full support is provided for [SickRageTV](https://github.com/SiCKRAGETV/SickRage) and [SickRage](https://github.com/SickRage/SickRage). Full support is provided for [SickRageTV](https://github.com/SiCKRAGETV/SickRage), [SickRage](https://github.com/SickRage/SickRage), and [SickGear](https://github.com/SickGear/SickGear).
Torrent support has been added with the assistance of jkaberg and berkona. Currently supports uTorrent, Transmission, Deluge and possibly more. Torrent support has been added with the assistance of jkaberg and berkona. Currently supports uTorrent, Transmission, Deluge and possibly more.
To enable Torrent extraction, on Windows, you need to install [7-zip](http://www.7-zip.org/ "7-zip") or on *nix you need to install the following packages/commands. To enable Torrent extraction, on Windows, you need to install [7-zip](http://www.7-zip.org/ "7-zip") or on *nix you need to install the following packages/commands.

View file

@ -132,7 +132,7 @@ def processTorrent(inputDirectory, inputName, inputCategory, inputHash, inputID,
logger.debug("Scanning files in directory: {0}".format(inputDirectory)) logger.debug("Scanning files in directory: {0}".format(inputDirectory))
if sectionName == 'HeadPhones': if sectionName in ['HeadPhones', 'Lidarr']:
core.NOFLATTEN.extend( core.NOFLATTEN.extend(
inputCategory) # Make sure we preserve folder structure for HeadPhones. inputCategory) # Make sure we preserve folder structure for HeadPhones.
@ -142,7 +142,11 @@ def processTorrent(inputDirectory, inputName, inputCategory, inputHash, inputID,
inputFiles = core.listMediaFiles(inputDirectory, archives=False, other=True, otherext=extensions) inputFiles = core.listMediaFiles(inputDirectory, archives=False, other=True, otherext=extensions)
else: else:
inputFiles = core.listMediaFiles(inputDirectory, other=True, otherext=extensions) inputFiles = core.listMediaFiles(inputDirectory, other=True, otherext=extensions)
logger.debug("Found {0} files in {1}".format(len(inputFiles), inputDirectory)) if len(inputFiles) == 0 and os.path.isfile(inputDirectory):
inputFiles = [inputDirectory]
logger.debug("Found 1 file to process: {0}".format(inputDirectory))
else:
logger.debug("Found {0} files in {1}".format(len(inputFiles), inputDirectory))
for inputFile in inputFiles: for inputFile in inputFiles:
filePath = os.path.dirname(inputFile) filePath = os.path.dirname(inputFile)
fileName, fileExt = os.path.splitext(os.path.basename(inputFile)) fileName, fileExt = os.path.splitext(os.path.basename(inputFile))
@ -235,7 +239,7 @@ def processTorrent(inputDirectory, inputName, inputCategory, inputHash, inputID,
inputHash = inputHash.upper() inputHash = inputHash.upper()
result = core.autoProcessTV().processEpisode(sectionName, outputDestination, inputName, result = core.autoProcessTV().processEpisode(sectionName, outputDestination, inputName,
status, clientAgent, inputHash, inputCategory) status, clientAgent, inputHash, inputCategory)
elif sectionName == 'HeadPhones': elif sectionName in ['HeadPhones', 'Lidarr']:
result = core.autoProcessMusic().process(sectionName, outputDestination, inputName, result = core.autoProcessMusic().process(sectionName, outputDestination, inputName,
status, clientAgent, inputCategory) status, clientAgent, inputCategory)
elif sectionName == 'Mylar': elif sectionName == 'Mylar':

View file

@ -193,6 +193,33 @@
##### Set to path where download client places completed downloads locally for this category ##### Set to path where download client places completed downloads locally for this category
watch_dir = watch_dir =
[Lidarr]
#### autoProcessing for Movies
#### raCategory - category that gets called for post-processing with Radarr
[[music]]
enabled = 0
apikey =
host = localhost
port = 8686
###### ADVANCED USE - ONLY EDIT IF YOU KNOW WHAT YOU'RE DOING ######
web_root =
ssl = 0
delete_failed = 0
# Enable/Disable linking for Torrents
Torrent_NoLink = 0
keep_archive = 1
extract = 1
nzbExtractionBy = Downloader
wait_for = 6
# Set this to minimum required size to consider a media file valid (in MB)
minSize = 0
# Enable/Disable deleting ignored files (samples and invalid media files)
delete_ignored = 0
##### Enable if NzbDrone is on a remote server for this category
remote_path = 0
##### Set to path where download client places completed downloads locally for this category
watch_dir =
[Mylar] [Mylar]
#### autoProcessing for Comics #### autoProcessing for Comics
#### comics - category that gets called for post-processing with Mylar #### comics - category that gets called for post-processing with Mylar
@ -252,7 +279,7 @@
[Nzb] [Nzb]
###### clientAgent - Supported clients: sabnzbd, nzbget ###### clientAgent - Supported clients: sabnzbd, nzbget
clientAgent = sabnzbd clientAgent = sabnzbd
###### SabNZBD (You must edit this if your using nzbToMedia.py with SabNZBD) ###### SabNZBD (You must edit this if you're using nzbToMedia.py with SabNZBD)
sabnzbd_host = http://localhost sabnzbd_host = http://localhost
sabnzbd_port = 8080 sabnzbd_port = 8080
sabnzbd_apikey = sabnzbd_apikey =
@ -260,7 +287,7 @@
default_downloadDirectory = default_downloadDirectory =
[Torrent] [Torrent]
###### clientAgent - Supported clients: utorrent, transmission, deluge, rtorrent, vuze, other ###### clientAgent - Supported clients: utorrent, transmission, deluge, rtorrent, vuze, qbittorrent, other
clientAgent = other clientAgent = other
###### useLink - Set to hard for physical links, sym for symbolic links, move to move, move-sym to move and link back, and no to not use links (copy) ###### useLink - Set to hard for physical links, sym for symbolic links, move to move, move-sym to move and link back, and no to not use links (copy)
useLink = hard useLink = hard
@ -272,20 +299,25 @@
categories = music_videos,pictures,software,manual categories = music_videos,pictures,software,manual
###### A list of categories that you don't want to be flattened (i.e preserve the directory structure when copying/linking. ###### A list of categories that you don't want to be flattened (i.e preserve the directory structure when copying/linking.
noFlatten = pictures,manual noFlatten = pictures,manual
###### uTorrent Hardlink solution (You must edit this if your using TorrentToMedia.py with uTorrent) ###### uTorrent Hardlink solution (You must edit this if you're using TorrentToMedia.py with uTorrent)
uTorrentWEBui = http://localhost:8090/gui/ uTorrentWEBui = http://localhost:8090/gui/
uTorrentUSR = your username uTorrentUSR = your username
uTorrentPWD = your password uTorrentPWD = your password
###### Transmission (You must edit this if your using TorrentToMedia.py with Transmission) ###### Transmission (You must edit this if you're using TorrentToMedia.py with Transmission)
TransmissionHost = localhost TransmissionHost = localhost
TransmissionPort = 9091 TransmissionPort = 9091
TransmissionUSR = your username TransmissionUSR = your username
TransmissionPWD = your password TransmissionPWD = your password
#### Deluge (You must edit this if your using TorrentToMedia.py with deluge. Note that the host/port is for the deluge daemon, not the webui) #### Deluge (You must edit this if you're using TorrentToMedia.py with deluge. Note that the host/port is for the deluge daemon, not the webui)
DelugeHost = localhost DelugeHost = localhost
DelugePort = 58846 DelugePort = 58846
DelugeUSR = your username DelugeUSR = your username
DelugePWD = your password DelugePWD = your password
###### qBittorrent (You must edit this if you're using TorrentToMedia.py with qBittorrent)
qBittorrenHost = localhost
qBittorrentPort = 8080
qBittorrentUSR = your username
qBittorrentPWD = your password
###### ADVANCED USE - ONLY EDIT IF YOU KNOW WHAT YOU'RE DOING ###### ###### ADVANCED USE - ONLY EDIT IF YOU KNOW WHAT YOU'RE DOING ######
deleteOriginal = 0 deleteOriginal = 0
chmodDirectory = 0 chmodDirectory = 0

View file

@ -51,7 +51,7 @@ from core.databases import mainDB
# Client Agents # Client Agents
NZB_CLIENTS = ['sabnzbd', 'nzbget', 'manual'] NZB_CLIENTS = ['sabnzbd', 'nzbget', 'manual']
TORRENT_CLIENTS = ['transmission', 'deluge', 'utorrent', 'rtorrent', 'other', 'manual'] TORRENT_CLIENTS = ['transmission', 'deluge', 'utorrent', 'rtorrent', 'qbittorrent', 'other', 'manual']
# sabnzbd constants # sabnzbd constants
SABNZB_NO_OF_ARGUMENTS = 8 SABNZB_NO_OF_ARGUMENTS = 8
@ -62,11 +62,11 @@ FORKS = {}
FORK_DEFAULT = "default" FORK_DEFAULT = "default"
FORK_FAILED = "failed" FORK_FAILED = "failed"
FORK_FAILED_TORRENT = "failed-torrent" FORK_FAILED_TORRENT = "failed-torrent"
FORK_SICKRAGETV = "sickragetv" FORK_SICKRAGETV = "SickRageTV"
FORK_SICKRAGE = "sickrage" FORK_SICKRAGE = "SickRage"
FORK_SICKRAGE_API = "sickrage-api" FORK_SICKRAGE_API = "SiCKRAGE-api"
FORK_MEDUSA = "medusa" FORK_MEDUSA = "Medusa"
FORK_SICKGEAR = "sickgear" FORK_SICKGEAR = "SickGear"
FORKS[FORK_DEFAULT] = {"dir": None} FORKS[FORK_DEFAULT] = {"dir": None}
FORKS[FORK_FAILED] = {"dirName": None, "failed": None} FORKS[FORK_FAILED] = {"dirName": None, "failed": None}
FORKS[FORK_FAILED_TORRENT] = {"dir": None, "failed": None, "process_method": None} FORKS[FORK_FAILED_TORRENT] = {"dir": None, "failed": None, "process_method": None}
@ -137,6 +137,11 @@ DELUGEPORT = None
DELUGEUSR = None DELUGEUSR = None
DELUGEPWD = None DELUGEPWD = None
QBITTORRENTHOST = None
QBITTORRENTPORT = None
QBITTORRENTUSR = None
QBITTORRENTPWD = None
PLEXSSL = None PLEXSSL = None
PLEXHOST = None PLEXHOST = None
PLEXPORT = None PLEXPORT = None
@ -236,7 +241,7 @@ def initialize(section=None):
DELETE_ORIGINAL, TORRENT_CHMOD_DIRECTORY, PASSWORDSFILE, USER_DELAY, USER_SCRIPT, USER_SCRIPT_CLEAN, USER_SCRIPT_MEDIAEXTENSIONS, \ DELETE_ORIGINAL, TORRENT_CHMOD_DIRECTORY, PASSWORDSFILE, USER_DELAY, USER_SCRIPT, USER_SCRIPT_CLEAN, USER_SCRIPT_MEDIAEXTENSIONS, \
USER_SCRIPT_PARAM, USER_SCRIPT_RUNONCE, USER_SCRIPT_SUCCESSCODES, DOWNLOADINFO, CHECK_MEDIA, SAFE_MODE, \ USER_SCRIPT_PARAM, USER_SCRIPT_RUNONCE, USER_SCRIPT_SUCCESSCODES, DOWNLOADINFO, CHECK_MEDIA, SAFE_MODE, \
TORRENT_DEFAULTDIR, TORRENT_RESUME_ON_FAILURE, NZB_DEFAULTDIR, REMOTEPATHS, LOG_ENV, PID_FILE, MYAPP, ACHANNELS, ACHANNELS2, ACHANNELS3, \ TORRENT_DEFAULTDIR, TORRENT_RESUME_ON_FAILURE, NZB_DEFAULTDIR, REMOTEPATHS, LOG_ENV, PID_FILE, MYAPP, ACHANNELS, ACHANNELS2, ACHANNELS3, \
PLEXSSL, PLEXHOST, PLEXPORT, PLEXTOKEN, PLEXSEC, TORRENT_RESUME, PAR2CMD PLEXSSL, PLEXHOST, PLEXPORT, PLEXTOKEN, PLEXSEC, TORRENT_RESUME, PAR2CMD, QBITTORRENTHOST, QBITTORRENTPORT, QBITTORRENTUSR, QBITTORRENTPWD
if __INITIALIZED__: if __INITIALIZED__:
return False return False
@ -359,7 +364,7 @@ def initialize(section=None):
if GROUPS == ['']: if GROUPS == ['']:
GROUPS = None GROUPS = None
TORRENT_CLIENTAGENT = CFG["Torrent"]["clientAgent"] # utorrent | deluge | transmission | rtorrent | vuze |other TORRENT_CLIENTAGENT = CFG["Torrent"]["clientAgent"] # utorrent | deluge | transmission | rtorrent | vuze | qbittorrent |other
USELINK = CFG["Torrent"]["useLink"] # no | hard | sym USELINK = CFG["Torrent"]["useLink"] # no | hard | sym
OUTPUTDIRECTORY = CFG["Torrent"]["outputDirectory"] # /abs/path/to/complete/ OUTPUTDIRECTORY = CFG["Torrent"]["outputDirectory"] # /abs/path/to/complete/
TORRENT_DEFAULTDIR = CFG["Torrent"]["default_downloadDirectory"] TORRENT_DEFAULTDIR = CFG["Torrent"]["default_downloadDirectory"]
@ -387,6 +392,11 @@ def initialize(section=None):
DELUGEUSR = CFG["Torrent"]["DelugeUSR"] # mysecretusr DELUGEUSR = CFG["Torrent"]["DelugeUSR"] # mysecretusr
DELUGEPWD = CFG["Torrent"]["DelugePWD"] # mysecretpwr DELUGEPWD = CFG["Torrent"]["DelugePWD"] # mysecretpwr
QBITTORRENTHOST = CFG["Torrent"]["qBittorrenHost"] # localhost
QBITTORRENTPORT = int(CFG["Torrent"]["qBittorrentPort"]) # 8080
QBITTORRENTUSR = CFG["Torrent"]["qBittorrentUSR"] # mysecretusr
QBITTORRENTPWD = CFG["Torrent"]["qBittorrentPWD"] # mysecretpwr
REMOTEPATHS = CFG["Network"]["mount_points"] or [] REMOTEPATHS = CFG["Network"]["mount_points"] or []
if REMOTEPATHS: if REMOTEPATHS:
if isinstance(REMOTEPATHS, list): if isinstance(REMOTEPATHS, list):

View file

@ -75,7 +75,7 @@ class autoProcessMovie(object):
if release['status'] not in ['snatched', 'downloaded', 'done']: if release['status'] not in ['snatched', 'downloaded', 'done']:
continue continue
if download_id: if download_id:
if download_id != release['download_info']['id']: if download_id.lower() != release['download_info']['id'].lower():
continue continue
id = release['_id'] id = release['_id']
@ -185,7 +185,7 @@ class autoProcessMovie(object):
media_id = None media_id = None
downloader = None downloader = None
release_status_old = None release_status_old = None
if release and imdbid: if release:
try: try:
release_id = release.keys()[0] release_id = release.keys()[0]
media_id = release[release_id]['media_id'] media_id = release[release_id]['media_id']

View file

@ -4,6 +4,7 @@ import os
import time import time
import requests import requests
import core import core
import json
from core.nzbToMediaUtil import convert_to_ascii, remoteDir, listMediaFiles, server_responding from core.nzbToMediaUtil import convert_to_ascii, remoteDir, listMediaFiles, server_responding
from core.nzbToMediaSceneExceptions import process_all_exceptions from core.nzbToMediaSceneExceptions import process_all_exceptions
@ -13,6 +14,39 @@ requests.packages.urllib3.disable_warnings()
class autoProcessMusic(object): class autoProcessMusic(object):
def command_complete(self, url, params, headers, section):
try:
r = requests.get(url, params=params, headers=headers, stream=True, verify=False, timeout=(30, 60))
except requests.ConnectionError:
logger.error("Unable to open URL: {0}".format(url), section)
return None
if r.status_code not in [requests.codes.ok, requests.codes.created, requests.codes.accepted]:
logger.error("Server returned status {0}".format(r.status_code), section)
return None
else:
try:
return r.json()['state']
except (ValueError, KeyError):
# ValueError catches simplejson's JSONDecodeError and json's ValueError
logger.error("{0} did not return expected json data.".format(section), section)
return None
def CDH(self, url2, headers, section="MAIN"):
try:
r = requests.get(url2, params={}, headers=headers, stream=True, verify=False, timeout=(30, 60))
except requests.ConnectionError:
logger.error("Unable to open URL: {0}".format(url2), section)
return False
if r.status_code not in [requests.codes.ok, requests.codes.created, requests.codes.accepted]:
logger.error("Server returned status {0}".format(r.status_code), section)
return False
else:
try:
return r.json().get("enableCompletedDownloadHandling", False)
except ValueError:
# ValueError catches simplejson's JSONDecodeError and json's ValueError
return False
def get_status(self, url, apikey, dirName): def get_status(self, url, apikey, dirName):
logger.debug("Attempting to get current status for release:{0}".format(os.path.basename(dirName))) logger.debug("Attempting to get current status for release:{0}".format(os.path.basename(dirName)))
@ -96,7 +130,10 @@ class autoProcessMusic(object):
else: else:
extract = int(cfg.get("extract", 0)) extract = int(cfg.get("extract", 0))
url = "{0}{1}:{2}{3}/api".format(protocol, host, port, web_root) if section == "Lidarr":
url = "{0}{1}:{2}{3}/api/v1".format(protocol, host, port, web_root)
else:
url = "{0}{1}:{2}{3}/api".format(protocol, host, port, web_root)
if not server_responding(url): if not server_responding(url):
logger.error("Server did not respond. Exiting", section) logger.error("Server did not respond. Exiting", section)
return [1, "{0}: Failed to post-process - {1} did not respond.".format(section, section)] return [1, "{0}: Failed to post-process - {1} did not respond.".format(section, section)]
@ -119,11 +156,11 @@ class autoProcessMusic(object):
core.extractFiles(dirName) core.extractFiles(dirName)
inputName, dirName = convert_to_ascii(inputName, dirName) inputName, dirName = convert_to_ascii(inputName, dirName)
if listMediaFiles(dirName, media=False, audio=True, meta=False, archives=False) and status: #if listMediaFiles(dirName, media=False, audio=True, meta=False, archives=False) and status:
logger.info("Status shown as failed from Downloader, but valid video files found. Setting as successful.", section) # logger.info("Status shown as failed from Downloader, but valid video files found. Setting as successful.", section)
status = 0 # status = 0
if status == 0: if status == 0 and section == "HeadPhones":
params = { params = {
'apikey': apikey, 'apikey': apikey,
@ -149,6 +186,74 @@ class autoProcessMusic(object):
logger.warning("The music album does not appear to have changed status after {0} minutes. Please check your Logs".format(wait_for), section) logger.warning("The music album does not appear to have changed status after {0} minutes. Please check your Logs".format(wait_for), section)
return [1, "{0}: Failed to post-process - No change in wanted status".format(section)] return [1, "{0}: Failed to post-process - No change in wanted status".format(section)]
elif status == 0 and section == "Lidarr":
url = "{0}{1}:{2}{3}/api/v1/command".format(protocol, host, port, web_root)
url2 = "{0}{1}:{2}{3}/api/v1/config/downloadClient".format(protocol, host, port, web_root)
headers = {"X-Api-Key": apikey}
if remote_path:
logger.debug("remote_path: {0}".format(remoteDir(dirName)), section)
data = {"name": "DownloadedAlbumScan", "path": remoteDir(dirName), "downloadClientId": download_id, "importMode": "Move"}
else:
logger.debug("path: {0}".format(dirName), section)
data = {"name": "DownloadedAlbumScan", "path": dirName, "downloadClientId": download_id, "importMode": "Move"}
if not download_id:
data.pop("downloadClientId")
data = json.dumps(data)
try:
logger.debug("Opening URL: {0} with data: {1}".format(url, data), section)
r = requests.post(url, data=data, headers=headers, stream=True, verify=False, timeout=(30, 1800))
except requests.ConnectionError:
logger.error("Unable to open URL: {0}".format(url), section)
return [1, "{0}: Failed to post-process - Unable to connect to {1}".format(section, section)]
Success = False
Queued = False
Started = False
try:
res = json.loads(r.content)
scan_id = int(res['id'])
logger.debug("Scan started with id: {0}".format(scan_id), section)
Started = True
except Exception as e:
logger.warning("No scan id was returned due to: {0}".format(e), section)
scan_id = None
Started = False
return [1, "{0}: Failed to post-process - Unable to start scan".format(section)]
n = 0
params = {}
url = "{0}/{1}".format(url, scan_id)
while n < 6: # set up wait_for minutes to see if command completes..
time.sleep(10 * wait_for)
command_status = self.command_complete(url, params, headers, section)
if command_status and command_status in ['completed', 'failed']:
break
n += 1
if command_status:
logger.debug("The Scan command return status: {0}".format(command_status), section)
if not os.path.exists(dirName):
logger.debug("The directory {0} has been removed. Renaming was successful.".format(dirName), section)
return [0, "{0}: Successfully post-processed {1}".format(section, inputName)]
elif command_status and command_status in ['completed']:
logger.debug("The Scan command has completed successfully. Renaming was successful.", section)
return [0, "{0}: Successfully post-processed {1}".format(section, inputName)]
elif command_status and command_status in ['failed']:
logger.debug("The Scan command has failed. Renaming was not successful.", section)
# return [1, "%s: Failed to post-process %s" % (section, inputName) ]
if self.CDH(url2, headers, section=section):
logger.debug("The Scan command did not return status completed, but complete Download Handling is enabled. Passing back to {0}.".format(section), section)
return [status, "{0}: Complete DownLoad Handling is enabled. Passing back to {1}".format(section, section)]
else:
logger.warning("The Scan command did not return a valid status. Renaming was not successful.", section)
return [1, "{0}: Failed to post-process {1}".format(section, inputName)]
else: else:
logger.warning("FAILED DOWNLOAD DETECTED", section) if section == "Lidarr":
return [1, "{0}: Failed to post-process. {1} does not support failed downloads".format(section, section)] logger.postprocess("FAILED: The download failed. Sending failed download to {0} for CDH processing".format(section), section)
return [1, "{0}: Download Failed. Sending back to {1}".format(section, section)] # Return as failed to flag this in the downloader.
else:
logger.warning("FAILED DOWNLOAD DETECTED", section)
if delete_failed and os.path.isdir(dirName) and not os.path.dirname(dirName) == dirName:
logger.postprocess("Deleting failed files and folder {0}".format(dirName), section)
rmDir(dirName)
return [1, "{0}: Failed to post-process. {1} does not support failed downloads".format(section, section)] # Return as failed to flag this in the downloader.

View file

@ -285,6 +285,9 @@ class autoProcessTV(object):
if not apikey and username and password: if not apikey and username and password:
login = "{0}{1}:{2}{3}/login".format(protocol, host, port, web_root) login = "{0}{1}:{2}{3}/login".format(protocol, host, port, web_root)
login_params = {'username': username, 'password': password} login_params = {'username': username, 'password': password}
r = s.get(login, verify=False, timeout=(30,60))
if r.status_code == 401 and r.cookies.get('_xsrf'):
login_params['_xsrf'] = r.cookies.get('_xsrf')
s.post(login, data=login_params, stream=True, verify=False, timeout=(30, 60)) s.post(login, data=login_params, stream=True, verify=False, timeout=(30, 60))
r = s.get(url, auth=(username, password), params=fork_params, stream=True, verify=False, timeout=(30, 1800)) r = s.get(url, auth=(username, password), params=fork_params, stream=True, verify=False, timeout=(30, 1800))
elif section == "NzbDrone": elif section == "NzbDrone":

View file

@ -21,8 +21,10 @@ def autoFork(section, inputCategory):
apikey = cfg.get("apikey") apikey = cfg.get("apikey")
ssl = int(cfg.get("ssl", 0)) ssl = int(cfg.get("ssl", 0))
web_root = cfg.get("web_root", "") web_root = cfg.get("web_root", "")
replace = {'sickrage':'SickRage', 'sickragetv':'SickRageTV', 'sickgear':'SickGear', 'medusa':'Medusa'}
f1 = replace[cfg.get("fork", "auto")] if cfg.get("fork", "auto") in replace else cfg.get("fork", "auto")
try: try:
fork = core.FORKS.items()[core.FORKS.keys().index(cfg.get("fork", "auto"))] fork = core.FORKS.items()[core.FORKS.keys().index(f1)]
except: except:
fork = "auto" fork = "auto"
protocol = "https://" if ssl else "http://" protocol = "https://" if ssl else "http://"
@ -67,6 +69,9 @@ def autoFork(section, inputCategory):
login = "{protocol}{host}:{port}{root}/login".format( login = "{protocol}{host}:{port}{root}/login".format(
protocol=protocol, host=host, port=port, root=web_root) protocol=protocol, host=host, port=port, root=web_root)
login_params = {'username': username, 'password': password} login_params = {'username': username, 'password': password}
r = s.get(login, verify=False, timeout=(30,60))
if r.status_code == 401 and r.cookies.get('_xsrf'):
login_params['_xsrf'] = r.cookies.get('_xsrf')
s.post(login, data=login_params, stream=True, verify=False) s.post(login, data=login_params, stream=True, verify=False)
r = s.get(url, auth=(username, password), verify=False) r = s.get(url, auth=(username, password), verify=False)
except requests.ConnectionError: except requests.ConnectionError:

View file

@ -263,6 +263,11 @@ class ConfigObj(configobj.ConfigObj, Section):
logger.warning("{x} category is set for CouchPotato and Radarr. " logger.warning("{x} category is set for CouchPotato and Radarr. "
"Please check your config in NZBGet".format "Please check your config in NZBGet".format
(x=os.environ['NZBPO_RACATEGORY'])) (x=os.environ['NZBPO_RACATEGORY']))
if 'NZBPO_LICATEGORY' in os.environ and 'NZBPO_HPCATEGORY' in os.environ:
if os.environ['NZBPO_LICATEGORY'] == os.environ['NZBPO_HPCATEGORY']:
logger.warning("{x} category is set for HeadPhones and Lidarr. "
"Please check your config in NZBGet".format
(x=os.environ['NZBPO_LICATEGORY']))
section = "Nzb" section = "Nzb"
key = 'NZBOP_DESTDIR' key = 'NZBOP_DESTDIR'
if key in os.environ: if key in os.environ:
@ -413,6 +418,25 @@ class ConfigObj(configobj.ConfigObj, Section):
if os.environ[envCatKey] in CFG_NEW['CouchPotato'].sections: if os.environ[envCatKey] in CFG_NEW['CouchPotato'].sections:
CFG_NEW['CouchPotato'][envCatKey]['enabled'] = 0 CFG_NEW['CouchPotato'][envCatKey]['enabled'] = 0
section = "Lidarr"
envCatKey = 'NZBPO_LICATEGORY'
envKeys = ['ENABLED', 'HOST', 'APIKEY', 'PORT', 'SSL', 'WEB_ROOT', 'WATCH_DIR', 'FORK', 'DELETE_FAILED',
'TORRENT_NOLINK', 'NZBEXTRACTIONBY', 'WAIT_FOR', 'DELETE_FAILED', 'REMOTE_PATH']
cfgKeys = ['enabled', 'host', 'apikey', 'port', 'ssl', 'web_root', 'watch_dir', 'fork', 'delete_failed',
'Torrent_NoLink', 'nzbExtractionBy', 'wait_for', 'delete_failed', 'remote_path']
if envCatKey in os.environ:
for index in range(len(envKeys)):
key = 'NZBPO_RA{index}'.format(index=envKeys[index])
if key in os.environ:
option = cfgKeys[index]
value = os.environ[key]
if os.environ[envCatKey] not in CFG_NEW[section].sections:
CFG_NEW[section][os.environ[envCatKey]] = {}
CFG_NEW[section][os.environ[envCatKey]][option] = value
CFG_NEW[section][os.environ[envCatKey]]['enabled'] = 1
if os.environ[envCatKey] in CFG_NEW['CouchPotato'].sections:
CFG_NEW['CouchPotato'][envCatKey]['enabled'] = 0
section = "Extensions" section = "Extensions"
envKeys = ['COMPRESSEDEXTENSIONS', 'MEDIAEXTENSIONS', 'METAEXTENSIONS'] envKeys = ['COMPRESSEDEXTENSIONS', 'MEDIAEXTENSIONS', 'METAEXTENSIONS']
cfgKeys = ['compressedExtensions', 'mediaExtensions', 'metaExtensions'] cfgKeys = ['compressedExtensions', 'mediaExtensions', 'metaExtensions']

View file

@ -23,6 +23,7 @@ from core.linktastic import linktastic
from core.synchronousdeluge.client import DelugeClient from core.synchronousdeluge.client import DelugeClient
from core.utorrent.client import UTorrentClient from core.utorrent.client import UTorrentClient
from core.transmissionrpc.client import Client as TransmissionClient from core.transmissionrpc.client import Client as TransmissionClient
from core.qbittorrent.client import Client as qBittorrentClient
from core import logger, nzbToMediaDB from core import logger, nzbToMediaDB
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
@ -588,6 +589,34 @@ def parse_vuze(args):
return inputDirectory, inputName, inputCategory, inputHash, inputID return inputDirectory, inputName, inputCategory, inputHash, inputID
def parse_qbittorrent(args):
# qbittorrent usage: C:\full\path\to\nzbToMedia\TorrentToMedia.py "%D|%N|%L|%I"
try:
input = args[1].split('|')
except:
input = []
try:
inputDirectory = os.path.normpath(input[0].replace('"',''))
except:
inputDirectory = ''
try:
inputName = input[1].replace('"','')
except:
inputName = ''
try:
inputCategory = input[2].replace('"','')
except:
inputCategory = ''
try:
inputHash = input[3].replace('"','')
except:
inputHash = ''
try:
inputID = input[3].replace('"','')
except:
inputID = ''
return inputDirectory, inputName, inputCategory, inputHash, inputID
def parse_args(clientAgent, args): def parse_args(clientAgent, args):
clients = { clients = {
@ -596,6 +625,7 @@ def parse_args(clientAgent, args):
'utorrent': parse_utorrent, 'utorrent': parse_utorrent,
'deluge': parse_deluge, 'deluge': parse_deluge,
'transmission': parse_transmission, 'transmission': parse_transmission,
'qbittorrent': parse_qbittorrent,
'vuze': parse_vuze, 'vuze': parse_vuze,
} }
@ -796,6 +826,14 @@ def create_torrent_class(clientAgent):
except: except:
logger.error("Failed to connect to Deluge") logger.error("Failed to connect to Deluge")
if clientAgent == 'qbittorrent':
try:
logger.debug("Connecting to {0}: http://{1}:{2}".format(clientAgent, core.QBITTORRENTHOST, core.QBITTORRENTPORT))
tc = qBittorrentClient("http://{0}:{1}/".format(core.QBITTORRENTHOST, core.QBITTORRENTPORT))
tc.login(core.QBITTORRENTUSR, core.QBITTORRENTPWD)
except:
logger.error("Failed to connect to qBittorrent")
return tc return tc
@ -808,6 +846,8 @@ def pause_torrent(clientAgent, inputHash, inputID, inputName):
core.TORRENT_CLASS.stop_torrent(inputID) core.TORRENT_CLASS.stop_torrent(inputID)
if clientAgent == 'deluge' and core.TORRENT_CLASS != "": if clientAgent == 'deluge' and core.TORRENT_CLASS != "":
core.TORRENT_CLASS.core.pause_torrent([inputID]) core.TORRENT_CLASS.core.pause_torrent([inputID])
if clientAgent == 'qbittorrent' and core.TORRENT_CLASS != "":
core.TORRENT_CLASS.pause(inputHash)
time.sleep(5) time.sleep(5)
except: except:
logger.warning("Failed to stop torrent {0} in {1}".format(inputName, clientAgent)) logger.warning("Failed to stop torrent {0} in {1}".format(inputName, clientAgent))
@ -824,6 +864,8 @@ def resume_torrent(clientAgent, inputHash, inputID, inputName):
core.TORRENT_CLASS.start_torrent(inputID) core.TORRENT_CLASS.start_torrent(inputID)
if clientAgent == 'deluge' and core.TORRENT_CLASS != "": if clientAgent == 'deluge' and core.TORRENT_CLASS != "":
core.TORRENT_CLASS.core.resume_torrent([inputID]) core.TORRENT_CLASS.core.resume_torrent([inputID])
if clientAgent == 'qbittorrent' and core.TORRENT_CLASS != "":
core.TORRENT_CLASS.resume(inputHash)
time.sleep(5) time.sleep(5)
except: except:
logger.warning("Failed to start torrent {0} in {1}".format(inputName, clientAgent)) logger.warning("Failed to start torrent {0} in {1}".format(inputName, clientAgent))
@ -840,6 +882,8 @@ def remove_torrent(clientAgent, inputHash, inputID, inputName):
core.TORRENT_CLASS.remove_torrent(inputID, True) core.TORRENT_CLASS.remove_torrent(inputID, True)
if clientAgent == 'deluge' and core.TORRENT_CLASS != "": if clientAgent == 'deluge' and core.TORRENT_CLASS != "":
core.TORRENT_CLASS.core.remove_torrent(inputID, True) core.TORRENT_CLASS.core.remove_torrent(inputID, True)
if clientAgent == 'qbittorrent' and core.TORRENT_CLASS != "":
core.TORRENT_CLASS.delete(inputHash)
time.sleep(5) time.sleep(5)
except: except:
logger.warning("Failed to delete torrent {0} in {1}".format(inputName, clientAgent)) logger.warning("Failed to delete torrent {0} in {1}".format(inputName, clientAgent))
@ -862,6 +906,11 @@ def find_download(clientAgent, download_id):
return True return True
if clientAgent == 'deluge': if clientAgent == 'deluge':
return False return False
if clientAgent == 'qbittorrent':
torrents = core.TORRENT_CLASS.torrents()
for torrent in torrents:
if torrent['hash'] == download_id:
return True
if clientAgent == 'sabnzbd': if clientAgent == 'sabnzbd':
if "http" in core.SABNZBDHOST: if "http" in core.SABNZBDHOST:
baseURL = "{0}:{1}/api".format(core.SABNZBDHOST, core.SABNZBDPORT) baseURL = "{0}:{1}/api".format(core.SABNZBDHOST, core.SABNZBDPORT)

View file

@ -0,0 +1 @@
# coding=utf-8

633
core/qbittorrent/client.py Normal file
View file

@ -0,0 +1,633 @@
import requests
import json
class LoginRequired(Exception):
def __str__(self):
return 'Please login first.'
class Client(object):
"""class to interact with qBittorrent WEB API"""
def __init__(self, url):
if not url.endswith('/'):
url += '/'
self.url = url
session = requests.Session()
check_prefs = session.get(url+'query/preferences')
if check_prefs.status_code == 200:
self._is_authenticated = True
self.session = session
elif check_prefs.status_code == 404:
self._is_authenticated = False
raise RuntimeError("""
This wrapper only supports qBittorrent applications
with version higher than 3.1.x.
Please use the latest qBittorrent release.
""")
else:
self._is_authenticated = False
def _get(self, endpoint, **kwargs):
"""
Method to perform GET request on the API.
:param endpoint: Endpoint of the API.
:param kwargs: Other keyword arguments for requests.
:return: Response of the GET request.
"""
return self._request(endpoint, 'get', **kwargs)
def _post(self, endpoint, data, **kwargs):
"""
Method to perform POST request on the API.
:param endpoint: Endpoint of the API.
:param data: POST DATA for the request.
:param kwargs: Other keyword arguments for requests.
:return: Response of the POST request.
"""
return self._request(endpoint, 'post', data, **kwargs)
def _request(self, endpoint, method, data=None, **kwargs):
"""
Method to hanle both GET and POST requests.
:param endpoint: Endpoint of the API.
:param method: Method of HTTP request.
:param data: POST DATA for the request.
:param kwargs: Other keyword arguments.
:return: Response for the request.
"""
final_url = self.url + endpoint
if not self._is_authenticated:
raise LoginRequired
rq = self.session
if method == 'get':
request = rq.get(final_url, **kwargs)
else:
request = rq.post(final_url, data, **kwargs)
request.raise_for_status()
request.encoding = 'utf_8'
if len(request.text) == 0:
data = json.loads('{}')
else:
try:
data = json.loads(request.text)
except ValueError:
data = request.text
return data
def login(self, username='admin', password='admin'):
"""
Method to authenticate the qBittorrent Client.
Declares a class attribute named ``session`` which
stores the authenticated session if the login is correct.
Else, shows the login error.
:param username: Username.
:param password: Password.
:return: Response to login request to the API.
"""
self.session = requests.Session()
login = self.session.post(self.url+'login',
data={'username': username,
'password': password})
if login.text == 'Ok.':
self._is_authenticated = True
else:
return login.text
def logout(self):
"""
Logout the current session.
"""
response = self._get('logout')
self._is_authenticated = False
return response
@property
def qbittorrent_version(self):
"""
Get qBittorrent version.
"""
return self._get('version/qbittorrent')
@property
def api_version(self):
"""
Get WEB API version.
"""
return self._get('version/api')
@property
def api_min_version(self):
"""
Get minimum WEB API version.
"""
return self._get('version/api_min')
def shutdown(self):
"""
Shutdown qBittorrent.
"""
return self._get('command/shutdown')
def torrents(self, **filters):
"""
Returns a list of torrents matching the supplied filters.
:param filter: Current status of the torrents.
:param category: Fetch all torrents with the supplied label.
:param sort: Sort torrents by.
:param reverse: Enable reverse sorting.
:param limit: Limit the number of torrents returned.
:param offset: Set offset (if less than 0, offset from end).
:return: list() of torrent with matching filter.
"""
params = {}
for name, value in filters.items():
# make sure that old 'status' argument still works
name = 'filter' if name == 'status' else name
params[name] = value
return self._get('query/torrents', params=params)
def get_torrent(self, infohash):
"""
Get details of the torrent.
:param infohash: INFO HASH of the torrent.
"""
return self._get('query/propertiesGeneral/' + infohash.lower())
def get_torrent_trackers(self, infohash):
"""
Get trackers for the torrent.
:param infohash: INFO HASH of the torrent.
"""
return self._get('query/propertiesTrackers/' + infohash.lower())
def get_torrent_webseeds(self, infohash):
"""
Get webseeds for the torrent.
:param infohash: INFO HASH of the torrent.
"""
return self._get('query/propertiesWebSeeds/' + infohash.lower())
def get_torrent_files(self, infohash):
"""
Get list of files for the torrent.
:param infohash: INFO HASH of the torrent.
"""
return self._get('query/propertiesFiles/' + infohash.lower())
@property
def global_transfer_info(self):
"""
Get JSON data of the global transfer info of qBittorrent.
"""
return self._get('query/transferInfo')
@property
def preferences(self):
"""
Get the current qBittorrent preferences.
Can also be used to assign individual preferences.
For setting multiple preferences at once,
see ``set_preferences`` method.
Note: Even if this is a ``property``,
to fetch the current preferences dict, you are required
to call it like a bound method.
Wrong::
qb.preferences
Right::
qb.preferences()
"""
prefs = self._get('query/preferences')
class Proxy(Client):
"""
Proxy class to to allow assignment of individual preferences.
this class overrides some methods to ease things.
Because of this, settings can be assigned like::
In [5]: prefs = qb.preferences()
In [6]: prefs['autorun_enabled']
Out[6]: True
In [7]: prefs['autorun_enabled'] = False
In [8]: prefs['autorun_enabled']
Out[8]: False
"""
def __init__(self, url, prefs, auth, session):
super(Proxy, self).__init__(url)
self.prefs = prefs
self._is_authenticated = auth
self.session = session
def __getitem__(self, key):
return self.prefs[key]
def __setitem__(self, key, value):
kwargs = {key: value}
return self.set_preferences(**kwargs)
def __call__(self):
return self.prefs
return Proxy(self.url, prefs, self._is_authenticated, self.session)
def sync(self, rid=0):
"""
Sync the torrents by supplied LAST RESPONSE ID.
Read more @ http://git.io/vEgXr
:param rid: Response ID of last request.
"""
return self._get('sync/maindata', params={'rid': rid})
def download_from_link(self, link, **kwargs):
"""
Download torrent using a link.
:param link: URL Link or list of.
:param savepath: Path to download the torrent.
:param category: Label or Category of the torrent(s).
:return: Empty JSON data.
"""
# old:new format
old_arg_map = {'save_path': 'savepath'} # , 'label': 'category'}
# convert old option names to new option names
options = kwargs.copy()
for old_arg, new_arg in old_arg_map.items():
if options.get(old_arg) and not options.get(new_arg):
options[new_arg] = options[old_arg]
options['urls'] = link
# workaround to send multipart/formdata request
# http://stackoverflow.com/a/23131823/4726598
dummy_file = {'_dummy': (None, '_dummy')}
return self._post('command/download', data=options, files=dummy_file)
def download_from_file(self, file_buffer, **kwargs):
"""
Download torrent using a file.
:param file_buffer: Single file() buffer or list of.
:param save_path: Path to download the torrent.
:param label: Label of the torrent(s).
:return: Empty JSON data.
"""
if isinstance(file_buffer, list):
torrent_files = {}
for i, f in enumerate(file_buffer):
torrent_files.update({'torrents%s' % i: f})
else:
torrent_files = {'torrents': file_buffer}
data = kwargs.copy()
if data.get('save_path'):
data.update({'savepath': data['save_path']})
return self._post('command/upload', data=data, files=torrent_files)
def add_trackers(self, infohash, trackers):
"""
Add trackers to a torrent.
:param infohash: INFO HASH of torrent.
:param trackers: Trackers.
"""
data = {'hash': infohash.lower(),
'urls': trackers}
return self._post('command/addTrackers', data=data)
@staticmethod
def _process_infohash_list(infohash_list):
"""
Method to convert the infohash_list to qBittorrent API friendly values.
:param infohash_list: List of infohash.
"""
if isinstance(infohash_list, list):
data = {'hashes': '|'.join([h.lower() for h in infohash_list])}
else:
data = {'hashes': infohash_list.lower()}
return data
def pause(self, infohash):
"""
Pause a torrent.
:param infohash: INFO HASH of torrent.
"""
return self._post('command/pause', data={'hash': infohash.lower()})
def pause_all(self):
"""
Pause all torrents.
"""
return self._get('command/pauseAll')
def pause_multiple(self, infohash_list):
"""
Pause multiple torrents.
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
return self._post('command/pauseAll', data=data)
def set_label(self, infohash_list, label):
"""
Set the label on multiple torrents.
IMPORTANT: OLD API method, kept as it is to avoid breaking stuffs.
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
data['label'] = label
return self._post('command/setLabel', data=data)
def set_category(self, infohash_list, category):
"""
Set the category on multiple torrents.
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
data['category'] = category
return self._post('command/setCategory', data=data)
def resume(self, infohash):
"""
Resume a paused torrent.
:param infohash: INFO HASH of torrent.
"""
return self._post('command/resume', data={'hash': infohash.lower()})
def resume_all(self):
"""
Resume all torrents.
"""
return self._get('command/resumeAll')
def resume_multiple(self, infohash_list):
"""
Resume multiple paused torrents.
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
return self._post('command/resumeAll', data=data)
def delete(self, infohash_list):
"""
Delete torrents.
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
return self._post('command/delete', data=data)
def delete_permanently(self, infohash_list):
"""
Permanently delete torrents.
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
return self._post('command/deletePerm', data=data)
def recheck(self, infohash_list):
"""
Recheck torrents.
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
return self._post('command/recheck', data=data)
def increase_priority(self, infohash_list):
"""
Increase priority of torrents.
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
return self._post('command/increasePrio', data=data)
def decrease_priority(self, infohash_list):
"""
Decrease priority of torrents.
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
return self._post('command/decreasePrio', data=data)
def set_max_priority(self, infohash_list):
"""
Set torrents to maximum priority level.
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
return self._post('command/topPrio', data=data)
def set_min_priority(self, infohash_list):
"""
Set torrents to minimum priority level.
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
return self._post('command/bottomPrio', data=data)
def set_file_priority(self, infohash, file_id, priority):
"""
Set file of a torrent to a supplied priority level.
:param infohash: INFO HASH of torrent.
:param file_id: ID of the file to set priority.
:param priority: Priority level of the file.
"""
if priority not in [0, 1, 2, 7]:
raise ValueError("Invalid priority, refer WEB-UI docs for info.")
elif not isinstance(file_id, int):
raise TypeError("File ID must be an int")
data = {'hash': infohash.lower(),
'id': file_id,
'priority': priority}
return self._post('command/setFilePrio', data=data)
# Get-set global download and upload speed limits.
def get_global_download_limit(self):
"""
Get global download speed limit.
"""
return self._get('command/getGlobalDlLimit')
def set_global_download_limit(self, limit):
"""
Set global download speed limit.
:param limit: Speed limit in bytes.
"""
return self._post('command/setGlobalDlLimit', data={'limit': limit})
global_download_limit = property(get_global_download_limit,
set_global_download_limit)
def get_global_upload_limit(self):
"""
Get global upload speed limit.
"""
return self._get('command/getGlobalUpLimit')
def set_global_upload_limit(self, limit):
"""
Set global upload speed limit.
:param limit: Speed limit in bytes.
"""
return self._post('command/setGlobalUpLimit', data={'limit': limit})
global_upload_limit = property(get_global_upload_limit,
set_global_upload_limit)
# Get-set download and upload speed limits of the torrents.
def get_torrent_download_limit(self, infohash_list):
"""
Get download speed limit of the supplied torrents.
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
return self._post('command/getTorrentsDlLimit', data=data)
def set_torrent_download_limit(self, infohash_list, limit):
"""
Set download speed limit of the supplied torrents.
:param infohash_list: Single or list() of infohashes.
:param limit: Speed limit in bytes.
"""
data = self._process_infohash_list(infohash_list)
data.update({'limit': limit})
return self._post('command/setTorrentsDlLimit', data=data)
def get_torrent_upload_limit(self, infohash_list):
"""
Get upoload speed limit of the supplied torrents.
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
return self._post('command/getTorrentsUpLimit', data=data)
def set_torrent_upload_limit(self, infohash_list, limit):
"""
Set upload speed limit of the supplied torrents.
:param infohash_list: Single or list() of infohashes.
:param limit: Speed limit in bytes.
"""
data = self._process_infohash_list(infohash_list)
data.update({'limit': limit})
return self._post('command/setTorrentsUpLimit', data=data)
# setting preferences
def set_preferences(self, **kwargs):
"""
Set preferences of qBittorrent.
Read all possible preferences @ http://git.io/vEgDQ
:param kwargs: set preferences in kwargs form.
"""
json_data = "json={}".format(json.dumps(kwargs))
headers = {'content-type': 'application/x-www-form-urlencoded'}
return self._post('command/setPreferences', data=json_data,
headers=headers)
def get_alternative_speed_status(self):
"""
Get Alternative speed limits. (1/0)
"""
return self._get('command/alternativeSpeedLimitsEnabled')
alternative_speed_status = property(get_alternative_speed_status)
def toggle_alternative_speed(self):
"""
Toggle alternative speed limits.
"""
return self._get('command/toggleAlternativeSpeedLimits')
def toggle_sequential_download(self, infohash_list):
"""
Toggle sequential download in supplied torrents.
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
return self._post('command/toggleSequentialDownload', data=data)
def toggle_first_last_piece_priority(self, infohash_list):
"""
Toggle first/last piece priority of supplied torrents.
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
return self._post('command/toggleFirstLastPiecePrio', data=data)
def force_start(self, infohash_list, value=True):
"""
Force start selected torrents.
:param infohash_list: Single or list() of infohashes.
:param value: Force start value (bool)
"""
data = self._process_infohash_list(infohash_list)
data.update({'value': json.dumps(value)})
return self._post('command/setForceStart', data=data)

245
nzbToLidarr.py Executable file
View file

@ -0,0 +1,245 @@
#!/usr/bin/env python2
# coding=utf-8
#
##############################################################################
### NZBGET POST-PROCESSING SCRIPT ###
# Post-Process to Lidarr.
#
# This script sends the download to your automated media management servers.
#
# NOTE: This script requires Python to be installed on your system.
##############################################################################
### OPTIONS ###
## General
# Auto Update nzbToMedia (0, 1).
#
# Set to 1 if you want nzbToMedia to automatically check for and update to the latest version
#auto_update=0
# Check Media for corruption (0, 1).
#
# Enable/Disable media file checking using ffprobe.
#check_media=1
# Safe Mode protection of DestDir (0, 1).
#
# Enable/Disable a safety check to ensure we don't process all downloads in the default_downloadDirectory by mistake.
#safe_mode=1
# Disable additional extraction checks for failed (0, 1).
#
# Turn this on to disable additional extraction attempts for failed downloads. Default = 0 this will attempt to extract and verify if media is present.
#no_extract_failed = 0
## Lidarr
# Lidarr script category.
#
# category that gets called for post-processing with NzbDrone.
#liCategory=music2
# Lidarr host.
#
# The ipaddress for your Lidarr server. e.g For the Same system use localhost or 127.0.0.1
#lihost=localhost
# Lidarr port.
#liport=8686
# Lidarr API key.
#liapikey=
# Lidarr uses ssl (0, 1).
#
# Set to 1 if using ssl, else set to 0.
#lissl=0
# Lidarr web_root
#
# set this if using a reverse proxy.
#liweb_root=
# Lidarr wait_for
#
# Set the number of minutes to wait after calling the renamer, to check the episode has changed status.
#liwait_for=6
# Lidarr Delete Failed Downloads (0, 1).
#
# set to 1 to delete failed, or 0 to leave files in place.
#lidelete_failed=0
# Lidarr and NZBGet are a different system (0, 1).
#
# Enable to replace local path with the path as per the mountPoints below.
#liremote_path=0
## Network
# Network Mount Points (Needed for remote path above)
#
# Enter Mount points as LocalPath,RemotePath and separate each pair with '|'
# e.g. mountPoints=/volume1/Public/,E:\|/volume2/share/,\\NAS\
#mountPoints=
## Extensions
# Media Extensions
#
# This is a list of media extensions that are used to verify that the download does contain valid media.
#mediaExtensions=.mkv,.avi,.divx,.xvid,.mov,.wmv,.mp4,.mpg,.mpeg,.vob,.iso,.ts
## Posix
# Niceness for external tasks Extractor and Transcoder.
#
# Set the Niceness value for the nice command. These range from -20 (most favorable to the process) to 19 (least favorable to the process).
#niceness=10
# ionice scheduling class (0, 1, 2, 3).
#
# Set the ionice scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle.
#ionice_class=2
# ionice scheduling class data.
#
# Set the ionice scheduling class data. This defines the class data, if the class accepts an argument. For real time and best-effort, 0-7 is valid data.
#ionice_classdata=4
## Transcoder
# getSubs (0, 1).
#
# set to 1 to download subtitles.
#getSubs = 0
# subLanguages.
#
# subLanguages. create a list of languages in the order you want them in your subtitles.
#subLanguages = eng,spa,fra
# Transcode (0, 1).
#
# set to 1 to transcode, otherwise set to 0.
#transcode=0
# create a duplicate, or replace the original (0, 1).
#
# set to 1 to cretae a new file or 0 to replace the original
#duplicate=1
# ignore extensions.
#
# list of extensions that won't be transcoded.
#ignoreExtensions=.avi,.mkv
# outputFastStart (0,1).
#
# outputFastStart. 1 will use -movflags + faststart. 0 will disable this from being used.
#outputFastStart = 0
# outputVideoPath.
#
# outputVideoPath. Set path you want transcoded videos moved to. Leave blank to disable.
#outputVideoPath =
# processOutput (0,1).
#
# processOutput. 1 will send the outputVideoPath to SickBeard/CouchPotato. 0 will send original files.
#processOutput = 0
# audioLanguage.
#
# audioLanguage. set the 3 letter language code you want as your primary audio track.
#audioLanguage = eng
# allAudioLanguages (0,1).
#
# allAudioLanguages. 1 will keep all audio tracks (uses AudioCodec3) where available.
#allAudioLanguages = 0
# allSubLanguages (0,1).
#
# allSubLanguages. 1 will keep all exisiting sub languages. 0 will discare those not in your list above.
#allSubLanguages = 0
# embedSubs (0,1).
#
# embedSubs. 1 will embded external sub/srt subs into your video if this is supported.
#embedSubs = 1
# burnInSubtitle (0,1).
#
# burnInSubtitle. burns the default sub language into your video (needed for players that don't support subs)
#burnInSubtitle = 0
# extractSubs (0,1).
#
# extractSubs. 1 will extract subs from the video file and save these as external srt files.
#extractSubs = 0
# externalSubDir.
#
# externalSubDir. set the directory where subs should be saved (if not the same directory as the video)
#externalSubDir =
# outputDefault (None, iPad, iPad-1080p, iPad-720p, Apple-TV2, iPod, iPhone, PS3, xbox, Roku-1080p, Roku-720p, Roku-480p, mkv, mp4-scene-release, MKV-SD).
#
# outputDefault. Loads default configs for the selected device. The remaining options below are ignored.
# If you want to use your own profile, set None and set the remaining options below.
#outputDefault = None
# hwAccel (0,1).
#
# hwAccel. 1 will set ffmpeg to enable hardware acceleration (this requires a recent ffmpeg).
#hwAccel=0
# ffmpeg output settings.
#outputVideoExtension=.mp4
#outputVideoCodec=libx264
#VideoCodecAllow =
#outputVideoResolution=720:-1
#outputVideoPreset=medium
#outputVideoFramerate=24
#outputVideoBitrate=800k
#outputAudioCodec=libmp3lame
#AudioCodecAllow =
#outputAudioBitrate=128k
#outputQualityPercent = 0
#outputAudioTrack2Codec = libfaac
#AudioCodec2Allow =
#outputAudioTrack2Bitrate = 128k
#outputAudioOtherCodec = libmp3lame
#AudioOtherCodecAllow =
#outputAudioOtherBitrate = 128k
#outputSubtitleCodec =
## WakeOnLan
# use WOL (0, 1).
#
# set to 1 to send WOL broadcast to the mac and test the server (e.g. xbmc) on the host and port specified.
#wolwake=0
# WOL MAC
#
# enter the mac address of the system to be woken.
#wolmac=00:01:2e:2D:64:e1
# Set the Host and Port of a server to verify system has woken.
#wolhost=192.168.1.37
#wolport=80
### NZBGET POST-PROCESSING SCRIPT ###
##############################################################################
import sys
import nzbToMedia
section = "Lidarr"
result = nzbToMedia.main(sys.argv, section)
sys.exit(result)

View file

@ -273,6 +273,49 @@
# Enable to replace local path with the path as per the mountPoints below. # Enable to replace local path with the path as per the mountPoints below.
#hpremote_path=0 #hpremote_path=0
## Lidarr
# Lidarr script category.
#
# category that gets called for post-processing with NzbDrone.
#liCategory=music2
# Lidarr host.
#
# The ipaddress for your Lidarr server. e.g For the Same system use localhost or 127.0.0.1
#lihost=localhost
# Lidarr port.
#liport=8686
# Lidarr API key.
#liapikey=
# Lidarr uses ssl (0, 1).
#
# Set to 1 if using ssl, else set to 0.
#lissl=0
# Lidarr web_root
#
# set this if using a reverse proxy.
#liweb_root=
# Lidarr wait_for
#
# Set the number of minutes to wait after calling the renamer, to check the episode has changed status.
#liwait_for=6
# Lidarr Delete Failed Downloads (0, 1).
#
# set to 1 to delete failed, or 0 to leave files in place.
#lidelete_failed=0
# Lidarr and NZBGet are a different system (0, 1).
#
# Enable to replace local path with the path as per the mountPoints below.
#liremote_path=0
## Mylar ## Mylar
# Mylar script category. # Mylar script category.
@ -672,7 +715,7 @@ def process(inputDirectory, inputName=None, status=0, clientAgent='manual', down
elif sectionName in ["SickBeard", "NzbDrone", "Sonarr"]: elif sectionName in ["SickBeard", "NzbDrone", "Sonarr"]:
result = autoProcessTV().processEpisode(sectionName, inputDirectory, inputName, status, clientAgent, result = autoProcessTV().processEpisode(sectionName, inputDirectory, inputName, status, clientAgent,
download_id, inputCategory, failureLink) download_id, inputCategory, failureLink)
elif sectionName == "HeadPhones": elif sectionName in ["HeadPhones", "Lidarr"]:
result = autoProcessMusic().process(sectionName, inputDirectory, inputName, status, clientAgent, inputCategory) result = autoProcessMusic().process(sectionName, inputDirectory, inputName, status, clientAgent, inputCategory)
elif sectionName == "Mylar": elif sectionName == "Mylar":
result = autoProcessComics().processEpisode(sectionName, inputDirectory, inputName, status, clientAgent, result = autoProcessComics().processEpisode(sectionName, inputDirectory, inputName, status, clientAgent,
@ -690,7 +733,7 @@ def process(inputDirectory, inputName=None, status=0, clientAgent='manual', down
if clientAgent != 'manual': if clientAgent != 'manual':
# update download status in our DB # update download status in our DB
update_downloadInfoStatus(inputName, 1) update_downloadInfoStatus(inputName, 1)
if sectionName not in ['UserScript', 'NzbDrone', 'Sonarr', 'Radarr']: if sectionName not in ['UserScript', 'NzbDrone', 'Sonarr', 'Radarr', 'Lidarr']:
# cleanup our processing folders of any misc unwanted files and empty directories # cleanup our processing folders of any misc unwanted files and empty directories
cleanDir(inputDirectory, sectionName, inputCategory) cleanDir(inputDirectory, sectionName, inputCategory)
@ -763,6 +806,8 @@ def main(args, section=None):
download_id = os.environ['NZBPR_SONARR'] download_id = os.environ['NZBPR_SONARR']
elif 'NZBPR_RADARR' in os.environ: elif 'NZBPR_RADARR' in os.environ:
download_id = os.environ['NZBPR_RADARR'] download_id = os.environ['NZBPR_RADARR']
elif 'NZBPR_LIDARR' in os.environ:
download_id = os.environ['NZBPR_LIDARR']
if 'NZBPR__DNZB_FAILURE' in os.environ: if 'NZBPR__DNZB_FAILURE' in os.environ:
failureLink = os.environ['NZBPR__DNZB_FAILURE'] failureLink = os.environ['NZBPR__DNZB_FAILURE']