Skip to content

Scripts you can use with NZBGet

Clean

Cleans the NZB name by removing the retagged stuff (-Obfuscated, -postbox, etc).

Script
#!/usr/bin/env python3
# 

##############################################################################
### NZBGET SCAN SCRIPT                                                     ###

# Clean NZB name.
#
# Removes the following suffixes from NZB name:
# NZBgeek / Obfuscated / BUYMORE / Scrambled.
#
# NOTE: This script requires Python to be installed on your system.

### NZBGET SCAN SCRIPT                                                     ###
##############################################################################

from __future__ import print_function
import os, re, sys

# Exit codes used by NZBGet
POSTPROCESS_SUCCESS=93
POSTPROCESS_ERROR=94
POSTPROCESS_SKIP=95

# Check if the script is called from NZBGet 13.0 or later
if not 'NZBOP_SCRIPTDIR' in os.environ:
  print('*** NZBGet post-processing script ***')
  print('This script is supposed to be called from NZBGet (13.0 or later).')
  sys.exit(POSTPROCESS_ERROR)

if not 'NZBNP_NZBNAME' in os.environ:
  print('[WARN] Filename not found in environment')
  sys.exit(POSTPROCESS_ERROR)

fwp = os.environ['NZBNP_NZBNAME']
fwp = re.sub('(?i)-4P\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)-4Planet\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)-AsRequested\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)-AsRequested-xpost\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)-BUYMORE\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)-Chamele0n\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)-GEROV\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)-iNC0GNiTO\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)-NZBGeek\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)-Obfuscated\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)-postbot\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)-Rakuv\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)-Scrambled\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)-WhiteRev\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)-xpost\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)\[eztv\]\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)\[TGx\]\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)\[TGx\]-xpost\.nzb$', '.nzb', fwp)
fwp = re.sub('(?i)\[ettv\]\.nzb$', '.nzb', fwp)
if fwp:
  print('[NZB] NZBNAME=', fwp, sep='')

sys.exit(POSTPROCESS_SUCCESS)

HashRenamer

Renames hashed media files to match the source NZB.

Script
#!/usr/bin/env python3
#
##############################################################################
# Title:         HashRenamer.py                                              #
# Author(s):     l3uddz, desimaniac                                          #
# URL:           https://github.com/l3uddz/nzbgetScripts                     #
# Description:   Renames hashed media files to match the source NZB.         #
# --                                                                         #
#            Part of the Cloudbox project: https://cloudbox.works            #
##############################################################################

##############################################################################
#  Built on top of the NZBGet scripts template created by Clinton Hall       #
#    (https://github.com/clinton-hall).                                      #
#  Released under GNU General Public License v2.0                            #
##############################################################################


##############################################################################
### NZBGET POST-PROCESSING SCRIPT                                          ###

# Rename files with hashes for file name
#
# NOTE: This script requires Python to be installed on your system.
#
##############################################################################
### NZBGET POST-PROCESSING SCRIPT                                          ###
##############################################################################

import os
import re
import shutil
import sys

# NZBGet Exit Codes
NZBGET_POSTPROCESS_PARCHECK = 92
NZBGET_POSTPROCESS_SUCCESS = 93
NZBGET_POSTPROCESS_ERROR = 94
NZBGET_POSTPROCESS_NONE = 95


############################################################
# EXTENSION STUFF
############################################################

def do_check():
    if 'NZBOP_SCRIPTDIR' not in os.environ:
        print("This script can only be called from NZBGet (11.0 or later).")
        sys.exit(0)

    if os.environ['NZBOP_VERSION'][0:5] < '11.0':
        print("[ERROR] NZBGet Version %s is not supported. Please update NZBGet." % (str(os.environ['NZBOP_VERSION'])))
        sys.exit(0)

    print("Script triggered from NZBGet Version %s." % (str(os.environ['NZBOP_VERSION'])))

    status = 0
    if 'NZBPP_TOTALSTATUS' in os.environ:
        if not os.environ['NZBPP_TOTALSTATUS'] == 'SUCCESS':
            print("[ERROR] Download failed with status %s." % (os.environ['NZBPP_STATUS']))
            status = 1
    else:
        # Check par status
        if os.environ['NZBPP_PARSTATUS'] == '1' or os.environ['NZBPP_PARSTATUS'] == '4':
            print("[ERROR] Par-repair failed, setting status \"failed\".")
            status = 1

        # Check unpack status
        if os.environ['NZBPP_UNPACKSTATUS'] == '1':
            print("[ERROR] Unpack failed, setting status \"failed\".")
            status = 1

        if os.environ['NZBPP_UNPACKSTATUS'] == '0' and os.environ['NZBPP_PARSTATUS'] == '0':
            # Unpack was skipped due to nzb-file properties or due to errors during par-check

            if os.environ['NZBPP_HEALTH'] < 1000:
                print("[ERROR] Download health is compromised and Par-check/repair disabled or no .par2 files found. " \
                      "Setting status \"failed\".")
                print("[ERROR] Please check your Par-check/repair settings for future downloads.")
                status = 1

            else:
                print("[ERROR] Par-check/repair disabled or no .par2 files found, and Unpack not required. Health is " \
                      "ok so handle as though download successful.")
                print("[WARNING] Please check your Par-check/repair settings for future downloads.")

    # Check if destination directory exists (important for reprocessing of history items)
    if not os.path.isdir(os.environ['NZBPP_DIRECTORY']):
        print("[ERROR] Nothing to post-process: destination directory", os.environ[
            'NZBPP_DIRECTORY'], "doesn't exist. Setting status \"failed\".")
        status = 1

    # All checks done, now launching the script.
    if status == 1:
        sys.exit(NZBGET_POSTPROCESS_NONE)


def get_file_name(path):
    try:
        file_name = os.path.basename(path)
        extensions = re.findall(r'\.([^.]+)', file_name)
        ext = '.'.join(extensions)
        name = file_name.replace(".%s" % ext, '')
        return name, ext
    except Exception:
        pass
    return None


def is_file_hash(file_name):
    hash_regexp = [
        r'^[a-fA-F0-9]{40}$',
        r'^[a-fA-F0-9]{32}$',
        r'^[a-f0-9]{128}$',
        r'^[a-zA-Z0-9]{42}$'
    ]
    for hash in hash_regexp:
        if re.match(hash, file_name):
            return True
    return False


def find_files(folder, extension=None, depth=None):
    file_list = []
    start_count = folder.count(os.sep)
    for path, subdirs, files in os.walk(folder, topdown=True):
        for name in files:
            if depth and path.count(os.sep) - start_count >= depth:
                del subdirs[:]
                continue
            file = os.path.join(path, name)
            if not extension:
                file_list.append(file)
            else:
                if file.lower().endswith(extension.lower()):
                    file_list.append(file)

    return sorted(file_list, key=lambda x: x.count(os.path.sep), reverse=True)


############################################################
# MAIN
############################################################

# do checks
do_check()

# retrieve required variables
directory = os.path.normpath(os.environ['NZBPP_DIRECTORY'])
nzb_name = os.environ['NZBPP_NZBFILENAME']
if nzb_name is None:
    print("[ERROR] Unable to retrieve NZBPP_NZBFILENAME")
    sys.exit(NZBGET_POSTPROCESS_ERROR)
nzb_name = nzb_name.replace('.nzb', '')

print(("[INFO] Using \"%s\" for hashed filenames" % nzb_name))
print(("[INFO] Scanning \"%s\" for hashed filenames" % directory))

# scan for files
found_files = find_files(directory)
if not found_files:
    print(("[INFO] No files were found in \"%s\"" % directory))
    sys.exit(NZBGET_POSTPROCESS_NONE)
else:
    print(("[INFO] Found %d files to check for hashed filenames" % len(found_files)))
    # loop files checking for file hash
    moved_files = 0
    for found_file_path in found_files:
        # set variable
        dir_name = os.path.dirname(found_file_path)
        file_name, file_ext = get_file_name(found_file_path)

        # is this a file hash
        if is_file_hash(file_name):
            new_file_path = os.path.join(dir_name, "%s.%s" % (nzb_name, file_ext))
            print(("[INFO] Moving \"%s\" to \"%s\"" % (found_file_path, new_file_path)))
            try:
                shutil.move(found_file_path, new_file_path)
                moved_files += 1
            except Exception:
                print(("[ERROR] Failed moving \"%s\" to \"%s\"" % (found_file_path, new_file_path)))

    print(("[INFO] Finished processing \"%s\", moved %d files" % (directory, moved_files)))

sys.exit(NZBGET_POSTPROCESS_SUCCESS)

WtFnZb-Renamer

This extensions extracts obfuscated filenames from .nzb files

Script
#!/usr/bin/env python3
### NZBGET SCAN SCRIPT

# Extract filenames from subjects containing [PRiVATE]-[WtFnZb]
#
# This extensions extracts obfuscated filenames from .nzb files
# created by WtFnZb.
#
# Supported subject formats:
#
# - [PRiVATE]-[WtFnZb]-[filename]-[1/5] - "" yEnc 0 (1/1)"
#
# - [PRiVATE]-[WtFnZb]-[5]-[1/filename] - "" yEnc
#
#
# NOTE: Requires Python and lxml (sudo apt install python3-lxml python-lxml)
#

### NZBGET SCAN SCRIPT

import sys
import os
import re

# Exit codes used by NZBGet
POSTPROCESS_SUCCESS = 93
POSTPROCESS_NONE = 95
POSTPROCESS_ERROR = 94

try:
    from lxml import etree
except ImportError:
    print(u'[ERROR] Python lxml required. Please install with "sudo apt install python-lxml" or "pip install lxml".')
    sys.exit(POSTPROCESS_ERROR)

patterns = (
    re.compile(r'^(?P<prefix>.*\[PRiVATE\]-\[WtFnZb\]-)'
               r'\[(?P<total>\d+)\]-\[(?P<segment>\d+)\/(?P<filename>.{3,}?)\]'
               r'\s+-\s+""\s+yEnc\s+',
               re.MULTILINE | re.UNICODE),
    re.compile(r'^(?P<prefix>.*\[PRiVATE\]-\[WtFnZb\]-)'
               r'\[(?P<filename>.{3,}?)\]-\[(?P<segment>\d+)/(?P<total>\d+)\]'
               r'\s+-\s+""\s+yEnc\s+',
               re.MULTILINE | re.UNICODE))

nzb_dir = os.getenv('NZBNP_DIRECTORY')
nzb_filename = os.getenv('NZBNP_FILENAME')
nzb_name = os.getenv('NZBNP_NZBNAME')
nzb_file_naming = os.getenv('NZBOP_FILENAMING')

if nzb_dir is None or nzb_filename is None or nzb_name is None:
    print('Please run as NZBGet plugin')
    sys.exit(POSTPROCESS_ERROR)

if nzb_file_naming is not None and nzb_file_naming.lower() != 'nzb':
    print(u'[ERROR] NZBGet setting FileNaming (under Download Queue) '
          u'must be set to "Nzb" for this extension to work correctly, exiting.')
    sys.exit(POSTPROCESS_ERROR)

if not os.path.exists(nzb_dir):
    print('[ERROR] NZB directory doesn\'t exist, exiting')
    sys.exit(POSTPROCESS_ERROR)

if not nzb_filename.lower().endswith('.nzb'):
    print(u'[ERROR] {} is not a .nzb file.'.format(nzb_filename))
    sys.exit(POSTPROCESS_ERROR)

nzb = os.path.join(nzb_dir, nzb_filename)
if not os.path.exists(nzb):
    print('[ERROR] {nzb} doesn\'t exist, exiting'.format(nzb=nzb))
    sys.exit(POSTPROCESS_ERROR)

with open(nzb, mode='rb') as infile:
    tree = etree.parse(infile)

changed = False
file_count = 0
totals = set()
filenames = set()

for f in tree.getiterator('{http://www.newzbin.com/DTD/2003/nzb}file'):
    subject = f.get('subject')
    if subject is None:
        print(u'[DETAIL] No subject in <file>, skipping')
        continue
    file_count += 1
    result = [re.match(pattern, subject) for pattern in patterns]
    matched = [m for m in result if m is not None]
    if len(matched) == 0:
        print(u'[INFO] No pattern matching subject, exiting.')
        sys.exit(POSTPROCESS_NONE)
    elif len(matched) > 1:
        print(u'[ERROR] Multiple patterns matched, exiting.')
        sys.exit(POSTPROCESS_ERROR)
    else:
        match = matched[0].groupdict()

    if match['filename'].lower().endswith('.par2'):
        print(u'[INFO] par2 exists, exiting')
        sys.exit(POSTPROCESS_NONE)

    if int(match['segment']) > int(match['total']):
        print(u'[DETAIL] Segment index is greater then total, skipping')
        continue

    # NZBGet subject parsing changes when duplicate filenames are present
    # prefix duplicates to avoid that
    if match['filename'] in filenames:
        match['filename'] = u'{}.{}'.format(file_count, match['filename'])

    filenames.add(match['filename'])

    s = u'WtFnZb "{filename}" yEnc ({segment}/{total})'.format(
        filename = match['filename'],
        segment = match['segment'],
        total = match['total'])

    print(u'[INFO] New subject {subject}'.format(subject=s.encode('ascii', 'ignore')))
    f.set('subject', s)
    changed = True
    totals.add(int(match['total']))

if not changed:
    print(u'[WARNING] No subject changed, exiting.')
    sys.exit(POSTPROCESS_NONE)

if len(totals) != 1:
    print(u'[WARNING] Mixed values for number of total segments, exiting.')
    sys.exit(POSTPROCESS_NONE)

if totals.pop() != file_count:
    print(u'[WARNING] Listed segment count does not match <file> count, exiting.')
    sys.exit(POSTPROCESS_NONE)

org = u'{}.wtfnzb.original.processed'.format(nzb)
exists_counter = 0
while os.path.exists(org):
    exists_counter += 1
    org = u'{}.{}.wtfnzb.original.processed'.format(nzb, exists_counter)

print(u'[INFO] Preserving original nzb as {}'.format(org))
os.rename(nzb, org)

print(u'[INFO] Writing {}'.format(nzb))
with open(nzb, mode='wb') as outfile:
    outfile.write(etree.tostring(tree,
        xml_declaration=True,
        encoding=tree.docinfo.encoding,
        doctype=tree.docinfo.doctype))

sys.exit(POSTPROCESS_SUCCESS)

Last update: 2021-03-02