Saturday, March 15, 2008

nntpclient.py

After being notified of the NNTP server our campus has set up, I decided to write an NNTP client (even though I use Thunderbird at the moment). I already have much of the backend working, which is what I'm posting today. The GUI, however, still needs work. At the moment, the GUI displays the tree and clicking on the row of the tree brings the correct article up in the text area below. However, because of a thread-detection (as in newsgroup message threads) limitation in this backend, I'll need to fix it before I can continue on with the GUI. What's posted here is the incomplete backend, which will print a dump of all of the messages from the group when run, with proper values for 'server', 'username', 'password', and 'group'.

nntpclient.py

import nntplib
from sys import exit
import re
from email.utils import _qdecode
import datetime

class NNTPParser(object):
class NNTPArticle(object):
def __init__(self, head, body, children = None):
self.head, self.body, self.children = head, body, children
def __getitem__(self, idx):
if idx == 'body':
return self.body
else:
return self.head[idx]
def child(self, idx):
if self.children and idx in self.children:
return self.children[idx]
else:
return None
def keys(self, set = 'head'):
"Set of keys: head, all"
if set == 'head':
return self.head.keys()
else:
return self.head.keys() + ['body']
def __str__(self):
if self.body:
return str(self.body)
else:
return str(self.head)
def haschildren(self):
return self.children != None
def addchild(self, id, child):
if self.haschildren():
self.children[id] = child
else:
self.children = {id : child}
ws = re.compile('^\s+')
dte = re.compile('^\s*[A-z]+,\s+([0-9]{1,2})\s+([A-z]+)\s+([0-9]{4})\s+([0-9]{2}):([0-9]{2}):([0-9]{2})')
months = {'Jan' : 1, 'Feb' : 2, 'Mar' : 3, 'Apr' : 4, 'May' : 5, 'Jun' : 6, 'Jul' : 7, 'Aug' : 8, 'Sep' : 9, 'Oct' : 10, 'Nov' : 11, 'Dec' : 12}
def __init__(self, host, port, user = None, password = None):
self.host, self.port, self.user, self.password = host, port, user, password
self.headermatch = re.compile('^[^\s]')
def connect(self, readermode = True):
if self.user and self.password and len(self.user) and len(self.password):
print 'FOO'
self.s = nntplib.NNTP(self.host, self.port, self.user, self.password, readermode = readermode)
else:
self.s = nntplib.NNTP(self.host, self.port, readermode = readermode)
print self.s.getwelcome()
def getarticles_fromgroup(self, groupname):
resp, count, first, last, name = self.s.group(groupname)
print 'Group', name, 'has', count, 'articles, range', first, 'to', last
arts = {}
for id in range(int(first), int(last) + 1):
arts[id] = self.getarticle(id)
return arts
def getheads_fromgroup(self, groupname):
resp, count, first, last, name = self.s.group(groupname)
print 'Group', name, 'has', count, 'articles, range', first, 'to', last
heads = {}
for id in range(int(first), int(last) + 1):
heads[id] = self.NNTPArticle(self.parsehead(id = id), None)
return heads
def getheadtree_fromgroup(self, groupname):
resp, count, first, last, name = self.s.group(groupname)
print 'Group', name, 'has', count, 'articles, range', first, 'to', last
heads = {}
subjs = {}
for id in range(int(first), int(last) + 1):
head = self.parsehead(id = id)
if head['Subject'] in subjs:
heads[subjs[head['Subject']]].addchild(id, head)
else:
heads[id] = head
subjs[head['Subject']] = id
return heads
def getarticle(self, id):
return self.NNTPArticle(self.parsehead(id = id), self.parsebody(id = id))
def gethead(self, id):
resp, nr, i, head = self.s.head(str(id))
return head
def parsehead(self, head = None, id = None):
if not head:
if id != None:
head = self.gethead(id)
else:
return {}
compiledhead = {}
for i in head:
if self.headermatch.search(i):
header, sep, value = i.partition(':')
if header and value > 1:
if header == 'Date' and self.dte.search(value):
m = self.dte.search(value)
parts = m.groups()
compiledhead[header] = datetime.datetime(int(parts[2]), self.months[parts[1]], int(parts[0]), int(parts[3]), int(parts[4]), int(parts[5]))
else:
value = self.ws.sub('', value)
compiledhead[header] = value
return self.NNTPArticle(compiledhead, None)
def getbody(self, id):
resp, nr, i, body = self.s.body(str(id))
return body
def parsebody(self, body = None, id = None):
if not body:
if id != None:
body = self.getbody(id)
else:
return {}
return self.NNTPArticle(None, _qdecode('\n'.join(body)))

if __name__ == '__main__':

host, port, user, password = 'server', 119, 'user', 'password'
nntp = NNTPParser(host, port, user, password)
nntp.connect(True)
arts = nntp.getarticles_fromgroup('group')
keys = arts.keys()
keys.sort(reverse = False)
for id in keys:
print id
for key in arts[id].keys():
print '%s: %s' % (key, arts[id][key])
print '\n\n%s\n-------------------------------------------------------------\n' % arts[id]

No comments: