Advertisement
Guest User

Untitled

a guest
Mar 11th, 2018
94
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 11.54 KB | None | 0 0
  1. from pynzb import nzb_parser
  2. import asyncore
  3. import asynchat
  4. import argparse
  5. import socket
  6. import ssl
  7. import sys
  8. import re
  9.  
  10. debug = False
  11. VERSION = 0.1
  12.  
  13. NNTP_TERMINATOR="\r\n"
  14.  
  15. class NZBParser():
  16.     """
  17.    Crude NZB Parser.
  18.    We initialize a few things for tracking stats in here, as well as making
  19.    huge arrays of things we probably don't need.
  20.    """
  21.     def __init__(self):
  22.         self.results = {
  23.             "totalBytes": 0,
  24.             "totalFiles": 0,
  25.             "totalArticles": 0,
  26.             "nzbdata": None,
  27.         }
  28.  
  29.     def parse(self, filename):
  30.         """
  31.        Open the given file and parse it.
  32.        """
  33.         try:
  34.             fh = open(filename)
  35.         except:
  36.             print(("ERROR: Could not open NZB file '{filename}'".format(
  37.                 filename=filename)))
  38.             sys.exit(1)
  39.  
  40.         nzbxml = fh.read()
  41.         fh.close()
  42.  
  43.         nzbdata = nzbparser.parse(nzbxml)
  44.         for nzb in nzbdata:
  45.             self.results["totalFiles"] += 1
  46.             for segment in nzb.segments:
  47.                 self.results["totalArticles"] += 1
  48.                 self.results["totalBytes"] += segment.bytes
  49.         self.results["nzbdata"] = nzbdata
  50.         return self.results
  51.  
  52.  
  53. class async_chat_ssl(asynchat.async_chat):
  54.     """
  55.    Class to make wrap asynchat in SSL
  56.    """
  57.     def connect(self, host, use_ssl=False):
  58.         """
  59.        If we're connecting with SSL, set the socket to blocking.
  60.        """
  61.         self.use_ssl = use_ssl
  62.         if use_ssl:
  63.             self.socket.setblocking(1)
  64.         asynchat.async_chat.connect(self, host)
  65.  
  66.     def handle_connect(self):
  67.         """
  68.        Wrap the socket with SSL when we connect.
  69.        """
  70.         if self.use_ssl:
  71.             self.ssl = ssl.wrap_socket(self.socket)
  72.             self.set_socket(self.ssl)
  73.  
  74.     def push(self, data):
  75.         """
  76.        Small wrapper for the push method, so that we can print
  77.        out what we're pushing in debug mode.
  78.        """
  79.         if debug:
  80.             print(data)
  81.         asynchat.async_chat.push(self, "{data}{terminator}".format(
  82.             data=data,
  83.             terminator=NNTP_TERMINATOR
  84.         ))
  85.  
  86.  
  87. class NZBHandler(async_chat_ssl):
  88.     def __init__(self, config, nzbdata):
  89.         # asynchat
  90.         asynchat.async_chat.__init__(self)
  91.         self.set_terminator(NNTP_TERMINATOR)
  92.         self.data = ""
  93.         # Config
  94.         self.conf = config
  95.         self.nzbdata = nzbdata["nzbdata"]
  96.         self.remaining = nzbdata["totalArticles"]
  97.         self.totalArticles = nzbdata["totalArticles"]
  98.         self.missing = 0
  99.         # Status
  100.         self.authed = False
  101.         self.curgrp = None
  102.         self.working = None
  103.         self.finished = False
  104.         self.groupname = None
  105.  
  106.     # NNTP commands that we need to send
  107.     def group(self, groupname):
  108.         """
  109.        Change to a new newsgroup
  110.        """
  111.         self.working = True
  112.         self.push("GROUP {groupname}".format(groupname=groupname))
  113.         self.changed = groupname
  114.  
  115.     def noop(self):
  116.         """
  117.        We use the "DATE" command to do nothing, since it's cheap.
  118.        """
  119.         self.working = True
  120.         self.push("DATE")
  121.  
  122.     def stat(self, article):
  123.         """
  124.        STAT an article in the active newsgroup
  125.        """
  126.         self.working = True
  127.         self.push("STAT <{article}>".format(article=article))
  128.  
  129.     def quit(self):
  130.         """
  131.        QUIT and disconnect from the server.
  132.        """
  133.         self.working = True
  134.         self.push("QUIT")
  135.  
  136.     # Methods for handling NNTP responses
  137.     # DATE response.
  138.     def response_111(self):
  139.         self.working = False
  140.  
  141.     # Welcome banner. We send our username.
  142.     def response_200(self):
  143.         if self.conf.username:
  144.             self.push("AUTHINFO USER {username}".format(
  145.                 username=self.conf.username))
  146.  
  147.     # Disconnecting.
  148.     def response_205(self):
  149.         if debug:
  150.             print("205: Disconnecting.")
  151.         self.close()
  152.         self.finished = True
  153.  
  154.     # Group switched successfully.
  155.     def response_211(self):
  156.         if debug:
  157.             print("211: Group switched.")
  158.         self.curgrp = self.changed
  159.         self.working = False
  160.  
  161.     # Article exists
  162.     def response_223(self):
  163.         if debug:
  164.             print("223: Article exists.")
  165.         self.working = False
  166.  
  167.     # Authenticated successfully.
  168.     def response_281(self):
  169.         if debug:
  170.             print("281: Authentication successful.")
  171.         self.authed = True
  172.  
  173.     # Request for further authentication, send password.
  174.     def response_381(self):
  175.         self.push("AUTHINFO PASS {password}".format(
  176.             password=self.conf.password))
  177.  
  178.     # Non-existant group
  179.     def response_411(self):
  180.         if debug:
  181.             print("411: Group does not exist.")
  182.         self.working = False
  183.  
  184.     # No such article number in this group
  185.     def response_423(self):
  186.         if debug:
  187.             print("423: No such article in this group.")
  188.         self.working = False
  189.         self.missing += 1
  190.  
  191.     # No such article found
  192.     def response_430(self):
  193.         if debug:
  194.             print("430: No such article found.")
  195.         self.working = False
  196.         self.missing += 1
  197.  
  198.     # Authentication failed. We'll quit if we hit this.
  199.     def response_452(self):
  200.         if debug:
  201.             print("452: Authentication failed.")
  202.         self.working = False
  203.         self.quit()
  204.  
  205.     # Command not recognised. We'll quit if we hit this.
  206.     def response_500(self):
  207.         if debug:
  208.             print("500: Command not recognised.")
  209.         if debug:
  210.             print(("Command was: '{command}'.".format(command=self.lastcommand)))
  211.         self.working = False
  212.         self.quit()
  213.  
  214.     # Access restriction/permission denied
  215.     def response_502(self):
  216.         if debug:
  217.             print("502: Access restriction.")
  218.         self.working = False
  219.         self.quit()
  220.  
  221.     # Get the next message_id from the nzbdata
  222.     def get_message_id(self):
  223.         message_id = None
  224.         if len(self.nzbdata[0].segments) > 0:
  225.             segment = self.nzbdata[0].segments.pop()
  226.             self.groupname = self.nzbdata[0].groups[0]
  227.             message_id = segment.message_id
  228.         else:
  229.             self.nzbdata.pop(0)
  230.             self.groupname = None
  231.             message_id = ""
  232.  
  233.     # Buffer incoming data until we get the terminator
  234.     def collect_incoming_data(self, data):
  235.         """
  236.        Buffer the incoming data.
  237.        """
  238.         self.data += data
  239.  
  240.     def found_terminator(self):
  241.         """
  242.        Called when we find the terminator in the incoming data
  243.        """
  244.         if debug:
  245.             print((self.data))
  246.         nntpcode = self.data[0:3]
  247.  
  248.         # Call the appropriate method
  249.         m = 'response_{nntpcode}'.format(nntpcode=str(nntpcode))
  250.         if hasattr(self, m):
  251.             getattr(self, m)()
  252.         else:
  253.             print(("Handler not found for response '{nntpcode}'."
  254.                   " Quitting.".format(nntpcode=nntpcode)))
  255.             self.quit()
  256.  
  257.         if self.finished:
  258.             return
  259.  
  260.         if len(self.nzbdata) == 0:
  261.             self.quit()
  262.         # OK, here we actually do the work of switching groups and
  263.         # checking articles.
  264.         if self.authed and not self.working:
  265.             message_id = None
  266.             if len(self.nzbdata[0].segments) > 0:
  267.                 segment = self.nzbdata[0].segments.pop()
  268.                 self.groupname = self.nzbdata[0].groups[0]
  269.                 message_id = segment.message_id
  270.             else:
  271.                 self.nzbdata.pop(0)
  272.                 self.noop()
  273.                 self.groupname = None
  274.  
  275.             if message_id:
  276.                 msg = "Remaining: {remaining}".format(remaining=self.remaining)
  277.                 if debug:
  278.                     print (msg)
  279.                 else:
  280.                     print((" " * (len(msg) + 1)), end=' ')
  281.                     print(("\r{msg}".format(msg=msg)), end=' ')
  282.                     sys.stdout.flush()
  283.                 self.stat(message_id)
  284.                 self.remaining -= 1
  285.  
  286.         # Clear out the data ready for the next run
  287.         self.data = ""
  288.  
  289.     def completion(self):
  290.         presentArticles = self.totalArticles - self.missing
  291.         return 100 * float(presentArticles) / float(self.totalArticles)
  292.  
  293.     def run(self, host, port, sslmode):
  294.         self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
  295.         self.connect((host, port), use_ssl=sslmode)
  296.         asyncore.loop()
  297.  
  298.  
  299. def getopts():
  300.     argparser = argparse.ArgumentParser(
  301.         description="Check article completion on an NNTP server",
  302.         epilog="Note: The checking may take a while to complete, "
  303.         "depending on the number of articles."
  304.     )
  305.     argparser.add_argument(
  306.         '-s', '--server',
  307.         type=str,
  308.         required=True,
  309.         help="Server to connect to.",
  310.     )
  311.     argparser.add_argument(
  312.         '-p', '--port',
  313.         type=int,
  314.         default=119,
  315.         help="Specify the port that the server is listening on.",
  316.     )
  317.     argparser.add_argument(
  318.         '-S', '--ssl',
  319.         type=bool,
  320.         metavar='[0,1]',
  321.         default=False,
  322.         help="Enable SSL mode.",
  323.     )
  324.     argparser.add_argument(
  325.         '-u', '--username',
  326.         type=str,
  327.         help="Username to authenticate as.",
  328.     )
  329.     argparser.add_argument(
  330.         '-P', '--password',
  331.         type=str,
  332.         help="Password to authenticate with.",
  333.     )
  334.     argparser.add_argument(
  335.         '-f', '--filename',
  336.         type=str,
  337.         required=True,
  338.         help="NZB file to check.",
  339.     )
  340.     argparser.add_argument(
  341.         '-d', '--debug',
  342.         type=bool,
  343.         metavar='[0,1]',
  344.         default=False,
  345.         help="Enable verbose output.",
  346.     )
  347.     argparser.add_argument(
  348.         '-v', '--version',
  349.         action='version',
  350.         version='%(prog)s v{version}'.format(version=str(VERSION)),
  351.         help="Show version information.",
  352.     )
  353.  
  354.     return argparser.parse_args()
  355.  
  356.  
  357. def pretty_size(size, real=True):
  358.     suffixes = {
  359.         1000: [
  360.             'KB', 'MB', 'GB',
  361.             'TB', 'PB', 'EB',
  362.             'ZB', 'YB'
  363.         ],
  364.         1024: [
  365.             'KiB', 'MiB', 'GiB',
  366.             'TiB', 'PiB', 'EiB',
  367.             'ZiB', 'YiB'
  368.         ],
  369.     }
  370.  
  371.     multiple = 1024 if real else 1000
  372.  
  373.     for suffix in suffixes[multiple]:
  374.         size /= multiple
  375.         if size < multiple:
  376.             return '{size:.1f} {suffix}'.format(size=size, suffix=suffix)
  377.  
  378. if __name__ == '__main__':
  379.    
  380.     conf = getopts()
  381.     if conf.debug:
  382.         debug = True
  383.     nzbparser = NZBParser()
  384.     results = nzbparser.parse(conf.filename)
  385.     print("")
  386.     print("NZB Parsing Results")
  387.     print("===================")
  388.     print("Totals")
  389.     print(("\tFiles: {files}".format(files=results["totalFiles"])))
  390.     print(("\tArticles: {articles}".format(articles=results["totalArticles"])))
  391.     print(("\tArticle Size: {size}".format(
  392.         size=pretty_size(results["totalBytes"]))))
  393.     print("")
  394.  
  395.     print("Be patient, this might take a while :)")
  396.     nzbhandler = NZBHandler(conf, results)
  397.     nzbhandler.run(conf.server, conf.port, conf.ssl)
  398.  
  399.     print("\n")
  400.     print("NZB Checking Results")
  401.     print("====================")
  402.     print("Totals")
  403.     print(("\tMissing Articles: {missing}".format(missing=nzbhandler.missing)))
  404.     print(("\tCompletion: {percent:.2f}%".format(
  405.         percent=nzbhandler.completion())))
  406.     print("")
  407.     print("All done!")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement