#!/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.*\[PRiVATE\]-\[WtFnZb\]-)" r"\[(?P\d+)\]-\[(?P\d+)\/(?P.{3,}?)\]" r'\s+-\s+""\s+yEnc\s+', re.MULTILINE | re.UNICODE, ), re.compile( r"^(?P.*\[PRiVATE\]-\[WtFnZb\]-)" r"\[(?P.{3,}?)\]-\[(?P\d+)/(?P\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 , 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 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)