Big update

- [Added] Cross-seed torrent mover into proper watched folders.
- [Added] Tracker passkeys will no longer show up in stdout or log files.
- [Added] New keyword for catching unregistered torrents for BHD.
- [Added] Added default user/pass for anyone not using authentication or removes it from the config file.
- [Fixed] Unregistered torrents deleting content files if you are cross-seeding.
- [Fixed] Double logging should no longer happen.
This commit is contained in:
Visorask 2021-02-26 14:59:52 -06:00
parent c00f1d44b0
commit 9414f3c2d1
2 changed files with 155 additions and 42 deletions

View file

@ -3,14 +3,18 @@ qbt:
host: 'localhost:8080' host: 'localhost:8080'
user: 'username' user: 'username'
pass: 'password' pass: 'password'
directory:
# Category Parameters # Do not remove these
# Cross-seed var: </your/path/here/> Ensure there is a / at the end or it won't work properly
cross_seed: '/your/path/here/'
movies: '/your/path/here/'
tv: '/your/path/here/'
unknown: '/your/path/here/'
# Category/Pathing Parameters
cat: cat:
# <File path keyword>: <Category Name> # <File path keyword>: <Category Name>
anime: anime
movies: movies movies: movies
tv: tv tv: tv
# Tag Parameters # Tag Parameters
tags: tags:
# <Tracker URL Keyword>: <Tag Name> # <Tracker URL Keyword>: <Tag Name>
@ -26,3 +30,4 @@ tags:
blutopia: Blutopia blutopia: Blutopia
hdts: HDTorrents hdts: HDTorrents
tv-vault: TV-Vault tv-vault: TV-Vault
cartoonchaos: CartoonChaos

View file

@ -1,14 +1,14 @@
#!/usr/bin/python3 #!/usr/bin/python3
import sys import os
import shutil
import yaml import yaml
import argparse import argparse
import logging import logging
from logging.handlers import RotatingFileHandler import logging.handlers
from qbittorrentapi import Client from qbittorrentapi import Client
import urllib3 import urllib3
# import apprise # import apprise
parser = argparse.ArgumentParser("qBittorrent Manager.", parser = argparse.ArgumentParser("qBittorrent Manager.",
@ -29,6 +29,12 @@ parser.add_argument('-m', '--manage',
const='manage', const='manage',
help='Use this if you would like to update your tags AND' help='Use this if you would like to update your tags AND'
' categories AND remove unregistered torrents.') ' categories AND remove unregistered torrents.')
parser.add_argument('-s', '--cross-seed',
dest='cross_seed',
action='store_const',
const='cross_seed',
help='Use this after running cross-seed script to organize your torrents into specified '
'watch folders.')
parser.add_argument('-g', '--cat-update', parser.add_argument('-g', '--cat-update',
dest='cat_update', dest='cat_update',
action='store_const', action='store_const',
@ -60,26 +66,52 @@ args = parser.parse_args()
with open(args.config, "r") as cfg_file: with open(args.config, "r") as cfg_file:
cfg = yaml.load(cfg_file, Loader=yaml.FullLoader) cfg = yaml.load(cfg_file, Loader=yaml.FullLoader)
# Logging urllib3.disable_warnings()
log_lev = getattr(logging, args.loglevel.upper())
file_handler = logging.FileHandler(filename=args.logfile) file_name_format = args.logfile
stdout_handler = logging.StreamHandler(sys.stderr) msg_format = '%(asctime)s - %(levelname)s: %(message)s'
rotate_handler = RotatingFileHandler(filename=args.logfile, maxBytes=1024 * 1024 * 2, backupCount=5) max_bytes = 1024 * 1024 * 2
handlers = [file_handler, stdout_handler, rotate_handler] backup_count = 5
# noinspection PyArgumentList
logging.basicConfig(level=log_lev, format='%(asctime)s - %(levelname)s: %(message)s', handlers=handlers)
logger = logging.getLogger('qBit Manage') logger = logging.getLogger('qBit Manage')
# Add dry-run to logging.
logging.DRYRUN = 25 logging.DRYRUN = 25
logging.addLevelName(logging.DRYRUN, 'DRY-RUN') logging.addLevelName(logging.DRYRUN, 'DRY-RUN')
setattr(logger, 'dryrun', lambda message, *args: logger._log(logging.DRYRUN, message, args)) setattr(logger, 'dryrun', lambda dryrun, *args: logger._log(logging.DRYRUN, dryrun, args))
log_lev = getattr(logging, args.loglevel.upper())
logger.setLevel(log_lev)
file_handler = logging.handlers.RotatingFileHandler(filename=file_name_format,
maxBytes=max_bytes,
backupCount=backup_count)
file_handler.setLevel(log_lev)
file_formatter = logging.Formatter(msg_format)
file_handler.setFormatter(file_formatter)
logger.addHandler(file_handler)
stream_handler = logging.StreamHandler()
stream_handler.setLevel(log_lev)
stream_formatter = logging.Formatter(msg_format)
stream_handler.setFormatter(stream_formatter)
logger.addHandler(stream_handler)
# Actual API call to connect to qbt. # Actual API call to connect to qbt.
client = Client(host=cfg["qbt"]["host"], username=cfg["qbt"]["user"], password=cfg["qbt"]["pass"]) host = cfg["qbt"]["host"]
if 'user' in cfg["qbt"]:
username = cfg["qbt"]["user"]
else:
username = ''
if 'pass' in cfg["qbt"]:
password = cfg["qbt"]["pass"]
else:
password = ''
urllib3.disable_warnings() client = Client(host=host,
username=username,
password=password)
def trunc_val(s, d, n=3):
return d.join(s.split(d, n)[:n])
def get_category(path): def get_category(path):
@ -106,6 +138,60 @@ def get_tags(url):
return tag return tag
def get_name(t_list):
dupes = []
no_dupes = []
t_name = []
for torrent in t_list:
n = torrent.name
t_name.append(n)
for s in t_name:
if t_name.count(s) > 1:
if s not in dupes:
dupes.append(s)
if t_name.count(s) == 1:
if s not in no_dupes:
no_dupes.append(s)
return dupes, no_dupes
# def check_cs_cat():
def cross_seed():
if args.cross_seed == 'cross_seed':
num_cs_tv = 0
num_cs_movie = 0
num_cs_unknown = 0
cs_files = os.listdir(cfg["directory"]["cross_seed"])
dir_cs = cfg["directory"]["cross_seed"]
dir_tv = cfg["directory"]["tv"]
dir_movie = cfg["directory"]["movies"]
dir_unknown = cfg["directory"]["unknown"]
for file in cs_files:
if '[episode]' in file or '[pack]' in file:
src = dir_cs + file
dest = dir_tv + file
shutil.move(src, dest)
logger.info('Moving %s to %s', src, dest)
num_cs_tv += 1
elif '[movie]' in file:
src = dir_cs + file
dest = dir_movie + file
shutil.move(src, dest)
logger.info('Moving %s to %s', src, dest)
num_cs_movie += 1
elif '[unknown]' in file:
src = dir_cs + file
dest = dir_unknown + file
shutil.move(src, dest)
logger.info('Moving %s to %s', src, dest)
num_cs_unknown += 1
total = num_cs_tv + num_cs_movie + num_cs_unknown
logger.info('\n - TV .torrents moved: %s \n - Movie .torrents moved: %s \n - Unknown .torrents moved: %s '
'\n -- Total .torrents moved: %s', num_cs_tv, num_cs_movie, num_cs_unknown, total)
def update_category(): def update_category():
if args.manage == 'manage' or args.cat_update == 'cat_update': if args.manage == 'manage' or args.cat_update == 'cat_update':
num_cat = 0 num_cat = 0
@ -114,14 +200,15 @@ def update_category():
if torrent.category == '': if torrent.category == '':
for x in torrent.trackers: for x in torrent.trackers:
if x.url.startswith('http'): if x.url.startswith('http'):
t_url = trunc_val(x.url, '/')
new_cat = get_category(torrent.save_path) new_cat = get_category(torrent.save_path)
if args.dry_run == 'dry_run': if args.dry_run == 'dry_run':
logger.dryrun('\n - Torrent Name: %s \n - New Category: %s \n - Tracker: %s', logger.dryrun('\n - Torrent Name: %s \n - New Category: %s \n - Tracker: %s',
torrent.name, new_cat, x.url) torrent.name, new_cat, t_url)
num_cat += 1 num_cat += 1
else: else:
logger.info('\n - Torrent Name: %s \n - New Category: %s \n - Tracker: %s', logger.info('\n - Torrent Name: %s \n - New Category: %s \n - Tracker: %s',
torrent.name, new_cat, x.url) torrent.name, new_cat, t_url)
torrent.set_category(category=new_cat) torrent.set_category(category=new_cat)
num_cat += 1 num_cat += 1
if args.dry_run == 'dry_run': if args.dry_run == 'dry_run':
@ -144,14 +231,15 @@ def update_tags():
if torrent.tags == '': if torrent.tags == '':
for x in torrent.trackers: for x in torrent.trackers:
if x.url.startswith('http'): if x.url.startswith('http'):
t_url = trunc_val(x.url, '/')
new_tag = get_tags(x.url) new_tag = get_tags(x.url)
if args.dry_run == 'dry_run': if args.dry_run == 'dry_run':
logger.dryrun('\n - Torrent Name: %s \n - New Tag: %s \n - Tracker: %s', logger.dryrun('\n - Torrent Name: %s \n - New Tag: %s \n - Tracker: %s',
torrent.name, new_tag, x.url) torrent.name, new_tag, t_url)
num_tags += 1 num_tags += 1
else: else:
logger.info('\n - Torrent Name: %s \n - New Tag: %s \n - Tracker: %s', logger.info('\n - Torrent Name: %s \n - New Tag: %s \n - Tracker: %s',
torrent.name, new_tag, x.url) torrent.name, new_tag, t_url)
torrent.add_tags(tags=new_tag) torrent.add_tags(tags=new_tag)
num_tags += 1 num_tags += 1
if args.dry_run == 'dry_run': if args.dry_run == 'dry_run':
@ -168,40 +256,60 @@ def update_tags():
def rem_unregistered(): def rem_unregistered():
if args.manage == 'manage' or args.rem_unregistered == 'rem_unregistered': if args.manage == 'manage' or args.rem_unregistered == 'rem_unregistered':
rem_unr = 0
torrent_list = client.torrents.info() torrent_list = client.torrents.info()
dupes, no_dupes = get_name(torrent_list)
rem_unr = 0
del_tor = 0
for torrent in torrent_list: for torrent in torrent_list:
for status in torrent.trackers: for status in torrent.trackers:
for x in torrent.trackers: for x in torrent.trackers:
if x.url.startswith('http'): if x.url.startswith('http'):
if 'Unregistered torrent' in status.msg: t_url = trunc_val(x.url, '/')
if args.dry_run == 'dry_run': if 'Unregistered torrent' in status.msg or 'Torrent is not found' in status.msg:
logger.dryrun('\n - Torrent Name: %s \n - Status: %s \n - Tracker: %s \n ' if torrent.name in dupes:
'- File NOT Deleted', torrent.name, status.msg, x.url) if args.dry_run == 'dry_run':
rem_unr += 1 logger.dryrun('\n - Torrent Name: %s \n - Status: %s \n - Tracker: %s \n '
else: '- Deleted .torrent but not content files.',
logger.info('\n - Torrent Name: %s \n - Status: %s \n - Tracker: %s \n ' torrent.name, status.msg, t_url)
'- Deleted', torrent.name, status.msg, x.url) rem_unr += 1
torrent.delete(hash=torrent.hash, else:
delete_files=True) logger.info('\n - Torrent Name: %s \n - Status: %s \n - Tracker: %s \n '
rem_unr += 1 '- Deleted .torrent but not content files.',
torrent.name, status.msg, t_url)
torrent.delete(hash=torrent.hash, delete_files=False)
rem_unr += 1
elif torrent.name in no_dupes:
if args.dry_run == 'dry_run':
logger.dryrun('\n - Torrent Name: %s \n - Status: %s \n - Tracker: %s \n '
'- Deleted .torrent AND content files.',
torrent.name, status.msg, t_url)
del_tor += 1
else:
logger.info('\n - Torrent Name: %s \n - Status: %s \n - Tracker: %s \n '
'- Deleted .torrent AND content files.',
torrent.name, status.msg, t_url)
torrent.delete(hash=torrent.hash, delete_files=True)
del_tor += 1
if args.dry_run == 'dry_run': if args.dry_run == 'dry_run':
if rem_unr >= 1: if rem_unr >= 1 or del_tor >= 1:
logger.dryrun('Did not delete %s torrents.', rem_unr) logger.dryrun('Did not delete %s .torrents(s) but not content files.', rem_unr)
logger.dryrun('Did not delete %s .torrents(s) AND content files.', del_tor)
else: else:
logger.dryrun('No unregistered torrents found.') logger.dryrun('No unregistered torrents found.')
else: else:
if rem_unr >= 1: if rem_unr >= 1 or del_tor >= 1:
logger.info('Deleted %s torrents.', rem_unr) logger.dryrun('Deleted %s .torrents(s) but not content files.', rem_unr)
logger.dryrun('Deleted %s .torrents(s) AND content files.', del_tor)
else: else:
logger.info('No unregistered torrents found.') logger.info('No unregistered torrents found.')
def main(): def run():
cross_seed()
update_category() update_category()
update_tags() update_tags()
rem_unregistered() rem_unregistered()
if __name__ == "__main__": if __name__ == "__main__":
main() run()