Skip to content

NZBGet Scripts

NZBGet development has been picked up again by new developers over at github!

A collection of community-provided and maintained scripts for NZBGet.

If you have a script you want to share, don't hesitate to create a PR for it.

Because these scripts are community-provided and maintained we can't assure that they are still 100% working

Clean

Clean NZB name
  • Title: Clean.py
  • Author(s): ???

Removes the following suffixes from NZB name: NZBgeek / Obfuscated / BUYMORE / Scrambled /etc... 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(r"(?i)-4P\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-4Planet\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-AlternativeToRequested\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-AlteZachen\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-AsRequested\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-AsRequested-xpost\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-BUYMORE\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-Chamele0n\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-GEROV\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-iNC0GNiTO\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-NZBGeek\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-Obfuscated\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-Obfuscation\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-postbot\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-Rakuv[a-z0-9]*\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-RePACKPOST\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-Scrambled\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-WhiteRev\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-WRTEAM\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-CAPTCHA\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-Z0iDS3N\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)\[eztv([ ._-]re)?\]\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)\[TGx\]\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)\[ettv\]\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)\[TGx\]-xpost\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i).mkv-xpost\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)-xpost\.nzb$", ".nzb", fwp)
fwp = re.sub(r"(?i)(-D-Z0N3|\-[^-.\n]*)(\-.{4})?\.nzb$", r"\1.nzb", fwp)
if fwp:
    print("[NZB] NZBNAME=", fwp, sep="")

sys.exit(POSTPROCESS_SUCCESS)

HashRenamer

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. Released under GNU General Public License v2.0

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)

replace_for

Replaces underscores with dots
  • Title: replace_for.py
  • Author: miker

Replaces underscores with dots in downloaded filename to prevent download loops with poorly named releases on some indexers (often HONE releases).

Install Instructions:

1. Copy script to NZBGet's script folder
1. Run: `sudo chmod +x replace_for.py`
1. In NZBGet go to `Settings` => `Extension Scripts`
1. Enable `replace_for.py` in the `Extensions` setting.
Script
#!/usr/bin/env python3
#

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

# Replace underscore with dot.
#
# Author: miker
#
#
# Copy script to NZBGet's script folder.
# Run sudo chmod +x replace_for.py
#
#
# NOTE: This script requires Python to be installed on your system.

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

from __future__ import print_function
import os, re, sys

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


directory = os.environ["NZBPP_DIRECTORY"]
print("Directory used is: ", directory)

for path, currentDirectory, files in os.walk(directory):
    for file in files:
        if file.find("_") != -1:
            dst = file.replace("_", ".")
            os.rename(os.path.join(path, file), os.path.join(path, dst))
            print("Result: ", file, " renamed to ", dst)

sys.exit(POSTPROCESS_SUCCESS)

WtFnZb-Renamer

Renames hashed media files to match the source NZB
  • Title: WtFnZb-Renamer.py
  • Author(s): WtFnZb
  • URL: ??

NZBGET SCAN SCRIPT

Extract filenames from subjects containing [PRiVATE]-[WtFnZb]

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(
        '[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(
        "[ERROR] NZBGet setting FileNaming (under Download Queue) "
        '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("[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("[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("[INFO] No pattern matching subject, exiting.")
        sys.exit(POSTPROCESS_NONE)
    elif len(matched) > 1:
        print("[ERROR] Multiple patterns matched, exiting.")
        sys.exit(POSTPROCESS_ERROR)
    else:
        match = matched[0].groupdict()

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

    if int(match["segment"]) > int(match["total"]):
        print("[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"] = "{}.{}".format(file_count, match["filename"])

    filenames.add(match["filename"])

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

    print("[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("[WARNING] No subject changed, exiting.")
    sys.exit(POSTPROCESS_NONE)

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

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

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

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

print("[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)

Tip

This script doesn't always work and is often needed if you use a certain indexer.

It might be better to use the following Sonarr Regex in your release profile

su season pack issue