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
- Title:
HashRenamer.py
- Author(s): l3uddz
- URL: github.com/cloudbox/cloudbox
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