blob: 16716ec5f9ff9de672e834b80284ad3ee2d51884 [file] [log] [blame]
Guido van Rossum54f22ed2000-02-04 15:10:34 +00001"""An NNTP client class based on RFC 977: Network News Transfer Protocol.
Guido van Rossumc629d341992-11-05 10:43:02 +00002
Guido van Rossum54f22ed2000-02-04 15:10:34 +00003Example:
Guido van Rossumc629d341992-11-05 10:43:02 +00004
Guido van Rossum54f22ed2000-02-04 15:10:34 +00005>>> from nntplib import NNTP
6>>> s = NNTP('news')
7>>> resp, count, first, last, name = s.group('comp.lang.python')
8>>> print 'Group', name, 'has', count, 'articles, range', first, 'to', last
9Group comp.lang.python has 51 articles, range 5770 to 5821
10>>> resp, subs = s.xhdr('subject', first + '-' + last)
11>>> resp = s.quit()
12>>>
Guido van Rossumc629d341992-11-05 10:43:02 +000013
Guido van Rossum54f22ed2000-02-04 15:10:34 +000014Here 'resp' is the server response line.
15Error responses are turned into exceptions.
16
17To post an article from a file:
18>>> f = open(filename, 'r') # file containing article, including header
19>>> resp = s.post(f)
20>>>
21
22For descriptions of all methods, read the comments in the code below.
23Note that all arguments and return values representing article numbers
24are strings, not numbers, since they are rarely used for calculations.
25"""
26
27# RFC 977 by Brian Kantor and Phil Lapsley.
28# xover, xgtitle, xpath, date methods by Kevan Heydon
Guido van Rossum8421c4e1995-09-22 00:52:38 +000029
Guido van Rossumc629d341992-11-05 10:43:02 +000030
31# Imports
Guido van Rossum9694fca1997-10-22 21:00:49 +000032import re
Guido van Rossumc629d341992-11-05 10:43:02 +000033import socket
34import string
35
36
Tim Peters2344fae2001-01-15 00:50:52 +000037
Barry Warsaw9dd78722000-02-10 20:25:53 +000038# Exceptions raised when an error or invalid response is received
39class NNTPError(Exception):
Tim Peters2344fae2001-01-15 00:50:52 +000040 """Base class for all nntplib exceptions"""
41 def __init__(self, *args):
42 apply(Exception.__init__, (self,)+args)
43 try:
44 self.response = args[0]
45 except IndexError:
46 self.response = 'No response given'
Barry Warsaw9dd78722000-02-10 20:25:53 +000047
48class NNTPReplyError(NNTPError):
Tim Peters2344fae2001-01-15 00:50:52 +000049 """Unexpected [123]xx reply"""
50 pass
Barry Warsaw9dd78722000-02-10 20:25:53 +000051
52class NNTPTemporaryError(NNTPError):
Tim Peters2344fae2001-01-15 00:50:52 +000053 """4xx errors"""
54 pass
Barry Warsaw9dd78722000-02-10 20:25:53 +000055
56class NNTPPermanentError(NNTPError):
Tim Peters2344fae2001-01-15 00:50:52 +000057 """5xx errors"""
58 pass
Barry Warsaw9dd78722000-02-10 20:25:53 +000059
60class NNTPProtocolError(NNTPError):
Tim Peters2344fae2001-01-15 00:50:52 +000061 """Response does not begin with [1-5]"""
62 pass
Barry Warsaw9dd78722000-02-10 20:25:53 +000063
64class NNTPDataError(NNTPError):
Tim Peters2344fae2001-01-15 00:50:52 +000065 """Error in response data"""
66 pass
Barry Warsaw9dd78722000-02-10 20:25:53 +000067
68# for backwards compatibility
69error_reply = NNTPReplyError
70error_temp = NNTPTemporaryError
71error_perm = NNTPPermanentError
72error_proto = NNTPProtocolError
73error_data = NNTPDataError
Guido van Rossumc629d341992-11-05 10:43:02 +000074
75
Tim Peters2344fae2001-01-15 00:50:52 +000076
Guido van Rossumc629d341992-11-05 10:43:02 +000077# Standard port used by NNTP servers
78NNTP_PORT = 119
79
80
81# Response numbers that are followed by additional text (e.g. article)
Guido van Rossum8421c4e1995-09-22 00:52:38 +000082LONGRESP = ['100', '215', '220', '221', '222', '224', '230', '231', '282']
Guido van Rossumc629d341992-11-05 10:43:02 +000083
84
85# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
86CRLF = '\r\n'
87
88
Tim Peters2344fae2001-01-15 00:50:52 +000089
Guido van Rossumc629d341992-11-05 10:43:02 +000090# The class itself
Guido van Rossumc629d341992-11-05 10:43:02 +000091class NNTP:
Tim Peters2344fae2001-01-15 00:50:52 +000092 def __init__(self, host, port=NNTP_PORT, user=None, password=None,
93 readermode=None):
94 """Initialize an instance. Arguments:
95 - host: hostname to connect to
96 - port: port to connect to (default the standard NNTP port)
97 - user: username to authenticate with
98 - password: password to use with username
99 - readermode: if true, send 'mode reader' command after
100 connecting.
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000101
Tim Peters2344fae2001-01-15 00:50:52 +0000102 readermode is sometimes necessary if you are connecting to an
103 NNTP server on the local machine and intend to call
104 reader-specific comamnds, such as `group'. If you get
105 unexpected NNTPPermanentErrors, you might need to set
106 readermode.
107 """
108 self.host = host
109 self.port = port
110 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
111 self.sock.connect((self.host, self.port))
112 self.file = self.sock.makefile('rb')
113 self.debugging = 0
114 self.welcome = self.getresp()
115 if readermode:
116 try:
117 self.welcome = self.shortcmd('mode reader')
118 except NNTPPermanentError:
119 # error 500, probably 'not implemented'
120 pass
121 if user:
122 resp = self.shortcmd('authinfo user '+user)
123 if resp[:3] == '381':
124 if not password:
125 raise NNTPReplyError(resp)
126 else:
127 resp = self.shortcmd(
128 'authinfo pass '+password)
129 if resp[:3] != '281':
130 raise NNTPPermanentError(resp)
Barry Warsaw9dd78722000-02-10 20:25:53 +0000131
Tim Peters2344fae2001-01-15 00:50:52 +0000132 # Get the welcome message from the server
133 # (this is read and squirreled away by __init__()).
134 # If the response code is 200, posting is allowed;
135 # if it 201, posting is not allowed
Guido van Rossumc629d341992-11-05 10:43:02 +0000136
Tim Peters2344fae2001-01-15 00:50:52 +0000137 def getwelcome(self):
138 """Get the welcome message from the server
139 (this is read and squirreled away by __init__()).
140 If the response code is 200, posting is allowed;
141 if it 201, posting is not allowed."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000142
Tim Peters2344fae2001-01-15 00:50:52 +0000143 if self.debugging: print '*welcome*', `self.welcome`
144 return self.welcome
Guido van Rossumc629d341992-11-05 10:43:02 +0000145
Tim Peters2344fae2001-01-15 00:50:52 +0000146 def set_debuglevel(self, level):
147 """Set the debugging level. Argument 'level' means:
148 0: no debugging output (default)
149 1: print commands and responses but not body text etc.
150 2: also print raw lines read and sent before stripping CR/LF"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000151
Tim Peters2344fae2001-01-15 00:50:52 +0000152 self.debugging = level
153 debug = set_debuglevel
Guido van Rossumc629d341992-11-05 10:43:02 +0000154
Tim Peters2344fae2001-01-15 00:50:52 +0000155 def putline(self, line):
156 """Internal: send one line to the server, appending CRLF."""
157 line = line + CRLF
158 if self.debugging > 1: print '*put*', `line`
159 self.sock.send(line)
Guido van Rossumc629d341992-11-05 10:43:02 +0000160
Tim Peters2344fae2001-01-15 00:50:52 +0000161 def putcmd(self, line):
162 """Internal: send one command to the server (through putline())."""
163 if self.debugging: print '*cmd*', `line`
164 self.putline(line)
Guido van Rossumc629d341992-11-05 10:43:02 +0000165
Tim Peters2344fae2001-01-15 00:50:52 +0000166 def getline(self):
167 """Internal: return one line from the server, stripping CRLF.
168 Raise EOFError if the connection is closed."""
169 line = self.file.readline()
170 if self.debugging > 1:
171 print '*get*', `line`
172 if not line: raise EOFError
173 if line[-2:] == CRLF: line = line[:-2]
174 elif line[-1:] in CRLF: line = line[:-1]
175 return line
Guido van Rossumc629d341992-11-05 10:43:02 +0000176
Tim Peters2344fae2001-01-15 00:50:52 +0000177 def getresp(self):
178 """Internal: get a response from the server.
179 Raise various errors if the response indicates an error."""
180 resp = self.getline()
181 if self.debugging: print '*resp*', `resp`
182 c = resp[:1]
183 if c == '4':
184 raise NNTPTemporaryError(resp)
185 if c == '5':
186 raise NNTPPermanentError(resp)
187 if c not in '123':
188 raise NNTPProtocolError(resp)
189 return resp
Guido van Rossumc629d341992-11-05 10:43:02 +0000190
Tim Peters2344fae2001-01-15 00:50:52 +0000191 def getlongresp(self):
192 """Internal: get a response plus following text from the server.
193 Raise various errors if the response indicates an error."""
194 resp = self.getresp()
195 if resp[:3] not in LONGRESP:
196 raise NNTPReplyError(resp)
197 list = []
198 while 1:
199 line = self.getline()
200 if line == '.':
201 break
202 if line[:2] == '..':
203 line = line[1:]
204 list.append(line)
205 return resp, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000206
Tim Peters2344fae2001-01-15 00:50:52 +0000207 def shortcmd(self, line):
208 """Internal: send a command and get the response."""
209 self.putcmd(line)
210 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000211
Tim Peters2344fae2001-01-15 00:50:52 +0000212 def longcmd(self, line):
213 """Internal: send a command and get the response plus following text."""
214 self.putcmd(line)
215 return self.getlongresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000216
Tim Peters2344fae2001-01-15 00:50:52 +0000217 def newgroups(self, date, time):
218 """Process a NEWGROUPS command. Arguments:
219 - date: string 'yymmdd' indicating the date
220 - time: string 'hhmmss' indicating the time
221 Return:
222 - resp: server response if successful
223 - list: list of newsgroup names"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000224
Tim Peters2344fae2001-01-15 00:50:52 +0000225 return self.longcmd('NEWGROUPS ' + date + ' ' + time)
Guido van Rossumc629d341992-11-05 10:43:02 +0000226
Tim Peters2344fae2001-01-15 00:50:52 +0000227 def newnews(self, group, date, time):
228 """Process a NEWNEWS command. Arguments:
229 - group: group name or '*'
230 - date: string 'yymmdd' indicating the date
231 - time: string 'hhmmss' indicating the time
232 Return:
233 - resp: server response if successful
234 - list: list of article ids"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000235
Tim Peters2344fae2001-01-15 00:50:52 +0000236 cmd = 'NEWNEWS ' + group + ' ' + date + ' ' + time
237 return self.longcmd(cmd)
Guido van Rossumc629d341992-11-05 10:43:02 +0000238
Tim Peters2344fae2001-01-15 00:50:52 +0000239 def list(self):
240 """Process a LIST command. Return:
241 - resp: server response if successful
242 - list: list of (group, last, first, flag) (strings)"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000243
Tim Peters2344fae2001-01-15 00:50:52 +0000244 resp, list = self.longcmd('LIST')
245 for i in range(len(list)):
246 # Parse lines into "group last first flag"
247 list[i] = tuple(string.split(list[i]))
248 return resp, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000249
Tim Peters2344fae2001-01-15 00:50:52 +0000250 def group(self, name):
251 """Process a GROUP command. Argument:
252 - group: the group name
253 Returns:
254 - resp: server response if successful
255 - count: number of articles (string)
256 - first: first article number (string)
257 - last: last article number (string)
258 - name: the group name"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000259
Tim Peters2344fae2001-01-15 00:50:52 +0000260 resp = self.shortcmd('GROUP ' + name)
261 if resp[:3] != '211':
262 raise NNTPReplyError(resp)
263 words = string.split(resp)
264 count = first = last = 0
265 n = len(words)
266 if n > 1:
267 count = words[1]
268 if n > 2:
269 first = words[2]
270 if n > 3:
271 last = words[3]
272 if n > 4:
273 name = string.lower(words[4])
274 return resp, count, first, last, name
Guido van Rossumc629d341992-11-05 10:43:02 +0000275
Tim Peters2344fae2001-01-15 00:50:52 +0000276 def help(self):
277 """Process a HELP command. Returns:
278 - resp: server response if successful
279 - list: list of strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000280
Tim Peters2344fae2001-01-15 00:50:52 +0000281 return self.longcmd('HELP')
Guido van Rossumc629d341992-11-05 10:43:02 +0000282
Tim Peters2344fae2001-01-15 00:50:52 +0000283 def statparse(self, resp):
284 """Internal: parse the response of a STAT, NEXT or LAST command."""
285 if resp[:2] != '22':
286 raise NNTPReplyError(resp)
287 words = string.split(resp)
288 nr = 0
289 id = ''
290 n = len(words)
291 if n > 1:
292 nr = words[1]
293 if n > 2:
294 id = words[2]
295 return resp, nr, id
Guido van Rossumc629d341992-11-05 10:43:02 +0000296
Tim Peters2344fae2001-01-15 00:50:52 +0000297 def statcmd(self, line):
298 """Internal: process a STAT, NEXT or LAST command."""
299 resp = self.shortcmd(line)
300 return self.statparse(resp)
Guido van Rossumc629d341992-11-05 10:43:02 +0000301
Tim Peters2344fae2001-01-15 00:50:52 +0000302 def stat(self, id):
303 """Process a STAT command. Argument:
304 - id: article number or message id
305 Returns:
306 - resp: server response if successful
307 - nr: the article number
308 - id: the article id"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000309
Tim Peters2344fae2001-01-15 00:50:52 +0000310 return self.statcmd('STAT ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000311
Tim Peters2344fae2001-01-15 00:50:52 +0000312 def next(self):
313 """Process a NEXT command. No arguments. Return as for STAT."""
314 return self.statcmd('NEXT')
Guido van Rossumc629d341992-11-05 10:43:02 +0000315
Tim Peters2344fae2001-01-15 00:50:52 +0000316 def last(self):
317 """Process a LAST command. No arguments. Return as for STAT."""
318 return self.statcmd('LAST')
Guido van Rossumc629d341992-11-05 10:43:02 +0000319
Tim Peters2344fae2001-01-15 00:50:52 +0000320 def artcmd(self, line):
321 """Internal: process a HEAD, BODY or ARTICLE command."""
322 resp, list = self.longcmd(line)
323 resp, nr, id = self.statparse(resp)
324 return resp, nr, id, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000325
Tim Peters2344fae2001-01-15 00:50:52 +0000326 def head(self, id):
327 """Process a HEAD command. Argument:
328 - id: article number or message id
329 Returns:
330 - resp: server response if successful
331 - nr: article number
332 - id: message id
333 - list: the lines of the article's header"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000334
Tim Peters2344fae2001-01-15 00:50:52 +0000335 return self.artcmd('HEAD ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000336
Tim Peters2344fae2001-01-15 00:50:52 +0000337 def body(self, id):
338 """Process a BODY command. Argument:
339 - id: article number or message id
340 Returns:
341 - resp: server response if successful
342 - nr: article number
343 - id: message id
344 - list: the lines of the article's body"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000345
Tim Peters2344fae2001-01-15 00:50:52 +0000346 return self.artcmd('BODY ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000347
Tim Peters2344fae2001-01-15 00:50:52 +0000348 def article(self, id):
349 """Process an ARTICLE command. Argument:
350 - id: article number or message id
351 Returns:
352 - resp: server response if successful
353 - nr: article number
354 - id: message id
355 - list: the lines of the article"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000356
Tim Peters2344fae2001-01-15 00:50:52 +0000357 return self.artcmd('ARTICLE ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000358
Tim Peters2344fae2001-01-15 00:50:52 +0000359 def slave(self):
360 """Process a SLAVE command. Returns:
361 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000362
Tim Peters2344fae2001-01-15 00:50:52 +0000363 return self.shortcmd('SLAVE')
Guido van Rossumc629d341992-11-05 10:43:02 +0000364
Tim Peters2344fae2001-01-15 00:50:52 +0000365 def xhdr(self, hdr, str):
366 """Process an XHDR command (optional server extension). Arguments:
367 - hdr: the header type (e.g. 'subject')
368 - str: an article nr, a message id, or a range nr1-nr2
369 Returns:
370 - resp: server response if successful
371 - list: list of (nr, value) strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000372
Tim Peters2344fae2001-01-15 00:50:52 +0000373 pat = re.compile('^([0-9]+) ?(.*)\n?')
374 resp, lines = self.longcmd('XHDR ' + hdr + ' ' + str)
375 for i in range(len(lines)):
376 line = lines[i]
377 m = pat.match(line)
378 if m:
379 lines[i] = m.group(1, 2)
380 return resp, lines
Guido van Rossumc629d341992-11-05 10:43:02 +0000381
Tim Peters2344fae2001-01-15 00:50:52 +0000382 def xover(self,start,end):
383 """Process an XOVER command (optional server extension) Arguments:
384 - start: start of range
385 - end: end of range
386 Returns:
387 - resp: server response if successful
388 - list: list of (art-nr, subject, poster, date,
389 id, references, size, lines)"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000390
Tim Peters2344fae2001-01-15 00:50:52 +0000391 resp, lines = self.longcmd('XOVER ' + start + '-' + end)
392 xover_lines = []
393 for line in lines:
394 elem = string.splitfields(line,"\t")
395 try:
396 xover_lines.append((elem[0],
397 elem[1],
398 elem[2],
399 elem[3],
400 elem[4],
401 string.split(elem[5]),
402 elem[6],
403 elem[7]))
404 except IndexError:
405 raise NNTPDataError(line)
406 return resp,xover_lines
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000407
Tim Peters2344fae2001-01-15 00:50:52 +0000408 def xgtitle(self, group):
409 """Process an XGTITLE command (optional server extension) Arguments:
410 - group: group name wildcard (i.e. news.*)
411 Returns:
412 - resp: server response if successful
413 - list: list of (name,title) strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000414
Tim Peters2344fae2001-01-15 00:50:52 +0000415 line_pat = re.compile("^([^ \t]+)[ \t]+(.*)$")
416 resp, raw_lines = self.longcmd('XGTITLE ' + group)
417 lines = []
418 for raw_line in raw_lines:
419 match = line_pat.search(string.strip(raw_line))
420 if match:
421 lines.append(match.group(1, 2))
422 return resp, lines
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000423
Tim Peters2344fae2001-01-15 00:50:52 +0000424 def xpath(self,id):
425 """Process an XPATH command (optional server extension) Arguments:
426 - id: Message id of article
427 Returns:
428 resp: server response if successful
429 path: directory path to article"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000430
Tim Peters2344fae2001-01-15 00:50:52 +0000431 resp = self.shortcmd("XPATH " + id)
432 if resp[:3] != '223':
433 raise NNTPReplyError(resp)
434 try:
435 [resp_num, path] = string.split(resp)
436 except ValueError:
437 raise NNTPReplyError(resp)
438 else:
439 return resp, path
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000440
Tim Peters2344fae2001-01-15 00:50:52 +0000441 def date (self):
442 """Process the DATE command. Arguments:
443 None
444 Returns:
445 resp: server response if successful
446 date: Date suitable for newnews/newgroups commands etc.
447 time: Time suitable for newnews/newgroups commands etc."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000448
Tim Peters2344fae2001-01-15 00:50:52 +0000449 resp = self.shortcmd("DATE")
450 if resp[:3] != '111':
451 raise NNTPReplyError(resp)
452 elem = string.split(resp)
453 if len(elem) != 2:
454 raise NNTPDataError(resp)
455 date = elem[1][2:8]
456 time = elem[1][-6:]
457 if len(date) != 6 or len(time) != 6:
458 raise NNTPDataError(resp)
459 return resp, date, time
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000460
461
Tim Peters2344fae2001-01-15 00:50:52 +0000462 def post(self, f):
463 """Process a POST command. Arguments:
464 - f: file containing the article
465 Returns:
466 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000467
Tim Peters2344fae2001-01-15 00:50:52 +0000468 resp = self.shortcmd('POST')
469 # Raises error_??? if posting is not allowed
470 if resp[0] != '3':
471 raise NNTPReplyError(resp)
472 while 1:
473 line = f.readline()
474 if not line:
475 break
476 if line[-1] == '\n':
477 line = line[:-1]
478 if line[:1] == '.':
479 line = '.' + line
480 self.putline(line)
481 self.putline('.')
482 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000483
Tim Peters2344fae2001-01-15 00:50:52 +0000484 def ihave(self, id, f):
485 """Process an IHAVE command. Arguments:
486 - id: message-id of the article
487 - f: file containing the article
488 Returns:
489 - resp: server response if successful
490 Note that if the server refuses the article an exception is raised."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000491
Tim Peters2344fae2001-01-15 00:50:52 +0000492 resp = self.shortcmd('IHAVE ' + id)
493 # Raises error_??? if the server already has it
494 if resp[0] != '3':
495 raise NNTPReplyError(resp)
496 while 1:
497 line = f.readline()
498 if not line:
499 break
500 if line[-1] == '\n':
501 line = line[:-1]
502 if line[:1] == '.':
503 line = '.' + line
504 self.putline(line)
505 self.putline('.')
506 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000507
Tim Peters2344fae2001-01-15 00:50:52 +0000508 def quit(self):
509 """Process a QUIT command and close the socket. Returns:
510 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000511
Tim Peters2344fae2001-01-15 00:50:52 +0000512 resp = self.shortcmd('QUIT')
513 self.file.close()
514 self.sock.close()
515 del self.file, self.sock
516 return resp
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000517
518
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000519def _test():
Tim Peters2344fae2001-01-15 00:50:52 +0000520 """Minimal test function."""
521 s = NNTP('news', readermode='reader')
522 resp, count, first, last, name = s.group('comp.lang.python')
523 print resp
524 print 'Group', name, 'has', count, 'articles, range', first, 'to', last
525 resp, subs = s.xhdr('subject', first + '-' + last)
526 print resp
527 for item in subs:
528 print "%7s %s" % item
529 resp = s.quit()
530 print resp
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000531
532
533# Run the test when run as a script
534if __name__ == '__main__':
Tim Peters2344fae2001-01-15 00:50:52 +0000535 _test()