qbit_manage/qbit_manage.py
Visorask 46cac6beb4 Update
- [Fixed erroneous dryrun logging when it should be info.
2021-02-26 15:32:45 -06:00

315 lines
12 KiB
Python

#!/usr/bin/python3
import os
import shutil
import yaml
import argparse
import logging
import logging.handlers
from qbittorrentapi import Client
import urllib3
# import apprise
parser = argparse.ArgumentParser("qBittorrent Manager.",
description='A mix of scripts combined for managing qBittorrent.')
parser.add_argument('-c', '--config-file',
dest='config',
action='store',
default='config.yml',
help='This is used if you want to use a different name for your config.yml. Example: tv.yml')
parser.add_argument('-l', '--log-file',
dest='logfile',
action='store',
default='activity.log',
help='This is used if you want to use a different name for your log file. Example: tv.log')
parser.add_argument('-m', '--manage',
dest='manage',
action='store_const',
const='manage',
help='Use this if you would like to update your tags AND'
' 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',
dest='cat_update',
action='store_const',
const='cat_update',
help='Use this if you would like to update your categories.')
parser.add_argument('-t', '--tag-update',
dest='tag_update',
action='store_const',
const='tag_update',
help='Use this if you would like to update your tags.')
parser.add_argument('-r', '--rem-unregistered',
dest='rem_unregistered',
action='store_const',
const='rem_unregistered',
help='Use this if you would like to remove unregistered torrents.')
parser.add_argument('--dry-run',
dest='dry_run',
action='store_const',
const='dry_run',
help='If you would like to see what is gonna happen but not actually delete or '
'tag/categorize anything.')
parser.add_argument('--log',
dest='loglevel',
action='store',
default='INFO',
help='Change your log level. ')
args = parser.parse_args()
with open(args.config, "r") as cfg_file:
cfg = yaml.load(cfg_file, Loader=yaml.FullLoader)
urllib3.disable_warnings()
file_name_format = args.logfile
msg_format = '%(asctime)s - %(levelname)s: %(message)s'
max_bytes = 1024 * 1024 * 2
backup_count = 5
logger = logging.getLogger('qBit Manage')
logging.DRYRUN = 25
logging.addLevelName(logging.DRYRUN, 'DRY-RUN')
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.
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 = ''
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):
cat_path = cfg["cat"]
for i, f in cat_path.items():
if i in path:
category = f
return category
else:
category = ''
logger.warning('No categories matched. Check your config.yml file. - Setting tag to NULL')
return category
def get_tags(url):
tag_path = cfg["tags"]
for i, f in tag_path.items():
if i in url:
tag = f
return tag
else:
tag = ''
logger.warning('No tags matched. Check your config.yml file. Setting category to NULL')
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():
if args.manage == 'manage' or args.cat_update == 'cat_update':
num_cat = 0
torrent_list = client.torrents.info()
for torrent in torrent_list:
if torrent.category == '':
for x in torrent.trackers:
if x.url.startswith('http'):
t_url = trunc_val(x.url, '/')
new_cat = get_category(torrent.save_path)
if args.dry_run == 'dry_run':
logger.dryrun('\n - Torrent Name: %s \n - New Category: %s \n - Tracker: %s',
torrent.name, new_cat, t_url)
num_cat += 1
else:
logger.info('\n - Torrent Name: %s \n - New Category: %s \n - Tracker: %s',
torrent.name, new_cat, t_url)
torrent.set_category(category=new_cat)
num_cat += 1
if args.dry_run == 'dry_run':
if num_cat >= 1:
logger.dryrun('Did not update %s new categories.', num_cat)
else:
logger.dryrun('No new torrents to categorize.')
else:
if num_cat >= 1:
logger.info('Updated %s new categories.', num_cat)
else:
logger.info('No new torrents to categorize.')
def update_tags():
if args.manage == 'manage' or args.tag_update == 'tag_update':
num_tags = 0
torrent_list = client.torrents.info()
for torrent in torrent_list:
if torrent.tags == '':
for x in torrent.trackers:
if x.url.startswith('http'):
t_url = trunc_val(x.url, '/')
new_tag = get_tags(x.url)
if args.dry_run == 'dry_run':
logger.dryrun('\n - Torrent Name: %s \n - New Tag: %s \n - Tracker: %s',
torrent.name, new_tag, t_url)
num_tags += 1
else:
logger.info('\n - Torrent Name: %s \n - New Tag: %s \n - Tracker: %s',
torrent.name, new_tag, t_url)
torrent.add_tags(tags=new_tag)
num_tags += 1
if args.dry_run == 'dry_run':
if num_tags >= 1:
logger.dryrun('Did not update %s new tags.', num_tags)
else:
logger.dryrun('No new torrents to tag.')
else:
if num_tags >= 1:
logger.info('Updated %s new tags.', num_tags)
else:
logger.info('No new torrents to tag.')
def rem_unregistered():
if args.manage == 'manage' or args.rem_unregistered == 'rem_unregistered':
torrent_list = client.torrents.info()
dupes, no_dupes = get_name(torrent_list)
rem_unr = 0
del_tor = 0
for torrent in torrent_list:
for status in torrent.trackers:
for x in torrent.trackers:
if x.url.startswith('http'):
t_url = trunc_val(x.url, '/')
if 'Unregistered torrent' in status.msg or 'Torrent is not found' in status.msg:
if torrent.name in dupes:
if args.dry_run == 'dry_run':
logger.dryrun('\n - Torrent Name: %s \n - Status: %s \n - Tracker: %s \n '
'- Deleted .torrent but not content files.',
torrent.name, status.msg, t_url)
rem_unr += 1
else:
logger.info('\n - Torrent Name: %s \n - Status: %s \n - Tracker: %s \n '
'- 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 rem_unr >= 1 or del_tor >= 1:
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:
logger.dryrun('No unregistered torrents found.')
else:
if rem_unr >= 1 or del_tor >= 1:
logger.info('Deleted %s .torrents(s) but not content files.', rem_unr)
logger.info('Deleted %s .torrents(s) AND content files.', del_tor)
else:
logger.info('No unregistered torrents found.')
def run():
cross_seed()
update_category()
update_tags()
rem_unregistered()
if __name__ == "__main__":
run()