123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
#!/usr/bin/env python3
"""
Docker Install:
--
docker exec -it rutorrent apk update
docker exec -it rutorrent apk add python3
docker exec -it rutorrent python3 -m pip install requests urllib3
ruTorrent Filter Action:
--
Run Program: /scripts/arrpush.py
Run Arguments: "SONARR OR RADARR URL" "API_KEY" "$(TorrentName)" "$(TorrentUrl)" "$(TorrentSize)" "$(Tracker)"
"""
import logging
import os
import re
import string
import sys
import time
from datetime import datetime
from logging.handlers import RotatingFileHandler
from urllib.parse import urljoin
import requests
import urllib3
############################################################
# INIT
############################################################
# Logging
log_formatter = logging.Formatter(
'%(asctime)s - %(levelname)-10s - %(name)-20s - %(funcName)-30s - %(message)s')
root_logger = logging.getLogger()
root_logger.setLevel(logging.INFO)
# disable loggers
logging.getLogger('urllib3').setLevel(logging.ERROR)
urllib3.disable_warnings()
# Set console logger
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(log_formatter)
root_logger.addHandler(console_handler)
# Set file logger
file_handler = RotatingFileHandler(
os.path.join(os.path.dirname(sys.argv[0]), "arrpush.log"),
maxBytes=1024 * 1024 * 5,
backupCount=5
)
file_handler.setFormatter(log_formatter)
root_logger.addHandler(file_handler)
# Set logging level
root_logger.setLevel(logging.DEBUG)
log = root_logger.getChild('arrpush')
# Indexer Settings
parse_name_from_indexers = [
'passthepopcorn'
]
############################################################
# BDECODE
# https://github.com/utdemir/bencoder/blob/master/bencoder.py
############################################################
def decode(s):
def decode_first(s):
if s.startswith(b"i"):
match = re.match(b"i(-?\\d+)e", s)
return int(match.group(1)), s[match.span()[1]:]
elif s.startswith(b"l") or s.startswith(b"d"):
l = []
rest = s[1:]
while not rest.startswith(b"e"):
elem, rest = decode_first(rest)
l.append(elem)
rest = rest[1:]
if s.startswith(b"l"):
return l, rest
else:
return {i: j for i, j in zip(l[::2], l[1::2])}, rest
elif any(s.startswith(i.encode()) for i in string.digits):
m = re.match(b"(\\d+):", s)
length = int(m.group(1))
rest_i = m.span()[1]
start = rest_i
end = rest_i + length
return s[start:end], s[end:]
else:
raise ValueError("Malformed input.")
if isinstance(s, str):
s = s.encode("ascii")
ret, rest = decode_first(s)
if rest:
raise ValueError("Malformed input.")
return ret
############################################################
# MISC
############################################################
def get_torrent_name(torrent_link):
decoded_torrent_data = {}
try:
# retrieve torrent file data
req = requests.get(torrent_link, timeout=30, verify=False)
if not req.status_code == 200:
log.error("Failed to retrieve torrent data from %s", torrent_link)
return None
except Exception:
log.exception("Exception retrieving torrent data from %s: ", torrent_link)
return None
try:
# decode bencoded torrent file
decoded_torrent_data = decode(req.content)
if b'info' not in decoded_torrent_data or b'name' not in decoded_torrent_data[b'info']:
log.error("Failed to parse torrent name from %s", torrent_link)
return None
except Exception:
log.exception("Exception parsing torrent data from %s: ", torrent_link)
try:
parsed_torrent_name = decoded_torrent_data[b'info'][b'name'].decode()
log.info("Retrieved torrent name from %s, name: %s", torrent_link, parsed_torrent_name)
return parsed_torrent_name
except Exception:
log.error("Exception parsing torrent name from %s: ", torrent_link)
return None
# https://stackoverflow.com/a/40347452
def get_bytes(size, suffix):
size = int(float(size))
suffix = suffix.lower()
if suffix == 'kb' or suffix == 'kib':
return size << 10
elif suffix == 'mb' or suffix == 'mib':
return size << 20
elif suffix == 'gb' or suffix == 'gib':
return size << 30
return False
def push_release(arr_url, arr_api_key, download_url, title, size, indexer):
try:
tries = 0
# build payload and headers
payload = {'title': title,
'downloadUrl': download_url,
'size': str(size),
'indexer': indexer,
'downloadProtocol': 'torrent',
'publishDate': datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')}
headers = {'Content-Type': 'application/json',
'X-Api-Key': arr_api_key}
while tries < 5:
tries += 1
# make request
req = requests.post(urljoin(arr_url, 'api/release/push'), json=payload, headers=headers, timeout=60,
verify=False)
if req.status_code == 200:
log.info("Successfully pushed %s (%s) to %s", title, indexer, arr_url)
return True
log.error("Failed pushing %s (%s) to %s, Attempt %d/5. Response status_code: %d, response text:\n%s", title,
download_url, arr_url, tries, req.status_code, req.text)
time.sleep(10)
return False
except Exception:
log.error("Exception pushing %s to %s: ", download_url, arr_url)
return False
############################################################
# MAIN
############################################################
if __name__ == "__main__":
# parse args
if len(sys.argv) < 7:
log.error("Not enough arguments were supplied: %s", sys.argv)
sys.exit(1)
try:
service_url = sys.argv[1]
service_api_key = sys.argv[2]
torrent_name = sys.argv[3]
torrent_url = sys.argv[4]
torrent_tracker = sys.argv[6]
# get size in bytes
torrent_size, torrent_size_type = sys.argv[5].split()
torrent_size = get_bytes(torrent_size, torrent_size_type)
except Exception:
log.exception("Failed parsing arguments supplied %s: ", sys.argv)
sys.exit(1)
# get torrent name from torrent file for specific trackers
if torrent_tracker.lower() in parse_name_from_indexers:
tmp = get_torrent_name(torrent_url)
torrent_name = sys.argv[3] if not tmp else tmp
# send to sonarr/radarr
if push_release(service_url, service_api_key, torrent_url, torrent_name, torrent_size, torrent_tracker):
sys.exit(0)
else:
sys.exit(1)