mirror of
https://github.com/StuffAnThings/qbit_manage.git
synced 2025-11-10 00:10:46 +08:00
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:
parent
c00f1d44b0
commit
9414f3c2d1
2 changed files with 155 additions and 42 deletions
13
config.yml
13
config.yml
|
|
@ -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
|
||||||
184
qbit_manage.py
184
qbit_manage.py
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue