blob: a8aba4838ab4650c398baeea4f6a6050bf394044 [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()
Tim Petersdfb673b2001-01-16 07:12:46 +0000115
Thomas Wouters47adcba2001-01-16 06:35:14 +0000116 # 'mode reader' is sometimes necessary to enable 'reader' mode.
Tim Petersdfb673b2001-01-16 07:12:46 +0000117 # However, the order in which 'mode reader' and 'authinfo' need to
Thomas Wouters47adcba2001-01-16 06:35:14 +0000118 # arrive differs between some NNTP servers. Try to send
119 # 'mode reader', and if it fails with an authorization failed
120 # error, try again after sending authinfo.
121 readermode_afterauth = 0
Tim Peters2344fae2001-01-15 00:50:52 +0000122 if readermode:
123 try:
124 self.welcome = self.shortcmd('mode reader')
125 except NNTPPermanentError:
126 # error 500, probably 'not implemented'
127 pass
Thomas Wouters47adcba2001-01-16 06:35:14 +0000128 except NNTPTemporaryError, e:
129 if user and e.response[:3] == '480':
130 # Need authorization before 'mode reader'
131 readermode_afterauth = 1
132 else:
133 raise
Tim Peters2344fae2001-01-15 00:50:52 +0000134 if user:
135 resp = self.shortcmd('authinfo user '+user)
136 if resp[:3] == '381':
137 if not password:
138 raise NNTPReplyError(resp)
139 else:
140 resp = self.shortcmd(
141 'authinfo pass '+password)
142 if resp[:3] != '281':
143 raise NNTPPermanentError(resp)
Thomas Wouters47adcba2001-01-16 06:35:14 +0000144 if readermode_afterauth:
145 try:
146 self.welcome = self.shortcmd('mode reader')
147 except NNTPPermanentError:
148 # error 500, probably 'not implemented'
149 pass
Tim Petersdfb673b2001-01-16 07:12:46 +0000150
Barry Warsaw9dd78722000-02-10 20:25:53 +0000151
Tim Peters2344fae2001-01-15 00:50:52 +0000152 # Get the welcome message from the server
153 # (this is read and squirreled away by __init__()).
154 # If the response code is 200, posting is allowed;
155 # if it 201, posting is not allowed
Guido van Rossumc629d341992-11-05 10:43:02 +0000156
Tim Peters2344fae2001-01-15 00:50:52 +0000157 def getwelcome(self):
158 """Get the welcome message from the server
159 (this is read and squirreled away by __init__()).
160 If the response code is 200, posting is allowed;
161 if it 201, posting is not allowed."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000162
Tim Peters2344fae2001-01-15 00:50:52 +0000163 if self.debugging: print '*welcome*', `self.welcome`
164 return self.welcome
Guido van Rossumc629d341992-11-05 10:43:02 +0000165
Tim Peters2344fae2001-01-15 00:50:52 +0000166 def set_debuglevel(self, level):
167 """Set the debugging level. Argument 'level' means:
168 0: no debugging output (default)
169 1: print commands and responses but not body text etc.
170 2: also print raw lines read and sent before stripping CR/LF"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000171
Tim Peters2344fae2001-01-15 00:50:52 +0000172 self.debugging = level
173 debug = set_debuglevel
Guido van Rossumc629d341992-11-05 10:43:02 +0000174
Tim Peters2344fae2001-01-15 00:50:52 +0000175 def putline(self, line):
176 """Internal: send one line to the server, appending CRLF."""
177 line = line + CRLF
178 if self.debugging > 1: print '*put*', `line`
179 self.sock.send(line)
Guido van Rossumc629d341992-11-05 10:43:02 +0000180
Tim Peters2344fae2001-01-15 00:50:52 +0000181 def putcmd(self, line):
182 """Internal: send one command to the server (through putline())."""
183 if self.debugging: print '*cmd*', `line`
184 self.putline(line)
Guido van Rossumc629d341992-11-05 10:43:02 +0000185
Tim Peters2344fae2001-01-15 00:50:52 +0000186 def getline(self):
187 """Internal: return one line from the server, stripping CRLF.
188 Raise EOFError if the connection is closed."""
189 line = self.file.readline()
190 if self.debugging > 1:
191 print '*get*', `line`
192 if not line: raise EOFError
193 if line[-2:] == CRLF: line = line[:-2]
194 elif line[-1:] in CRLF: line = line[:-1]
195 return line
Guido van Rossumc629d341992-11-05 10:43:02 +0000196
Tim Peters2344fae2001-01-15 00:50:52 +0000197 def getresp(self):
198 """Internal: get a response from the server.
199 Raise various errors if the response indicates an error."""
200 resp = self.getline()
201 if self.debugging: print '*resp*', `resp`
202 c = resp[:1]
203 if c == '4':
204 raise NNTPTemporaryError(resp)
205 if c == '5':
206 raise NNTPPermanentError(resp)
207 if c not in '123':
208 raise NNTPProtocolError(resp)
209 return resp
Guido van Rossumc629d341992-11-05 10:43:02 +0000210
Tim Peters2344fae2001-01-15 00:50:52 +0000211 def getlongresp(self):
212 """Internal: get a response plus following text from the server.
213 Raise various errors if the response indicates an error."""
214 resp = self.getresp()
215 if resp[:3] not in LONGRESP:
216 raise NNTPReplyError(resp)
217 list = []
218 while 1:
219 line = self.getline()
220 if line == '.':
221 break
222 if line[:2] == '..':
223 line = line[1:]
224 list.append(line)
225 return resp, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000226
Tim Peters2344fae2001-01-15 00:50:52 +0000227 def shortcmd(self, line):
228 """Internal: send a command and get the response."""
229 self.putcmd(line)
230 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000231
Tim Peters2344fae2001-01-15 00:50:52 +0000232 def longcmd(self, line):
233 """Internal: send a command and get the response plus following text."""
234 self.putcmd(line)
235 return self.getlongresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000236
Tim Peters2344fae2001-01-15 00:50:52 +0000237 def newgroups(self, date, time):
238 """Process a NEWGROUPS command. Arguments:
239 - date: string 'yymmdd' indicating the date
240 - time: string 'hhmmss' indicating the time
241 Return:
242 - resp: server response if successful
243 - list: list of newsgroup names"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000244
Tim Peters2344fae2001-01-15 00:50:52 +0000245 return self.longcmd('NEWGROUPS ' + date + ' ' + time)
Guido van Rossumc629d341992-11-05 10:43:02 +0000246
Tim Peters2344fae2001-01-15 00:50:52 +0000247 def newnews(self, group, date, time):
248 """Process a NEWNEWS command. Arguments:
249 - group: group name or '*'
250 - date: string 'yymmdd' indicating the date
251 - time: string 'hhmmss' indicating the time
252 Return:
253 - resp: server response if successful
254 - list: list of article ids"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000255
Tim Peters2344fae2001-01-15 00:50:52 +0000256 cmd = 'NEWNEWS ' + group + ' ' + date + ' ' + time
257 return self.longcmd(cmd)
Guido van Rossumc629d341992-11-05 10:43:02 +0000258
Tim Peters2344fae2001-01-15 00:50:52 +0000259 def list(self):
260 """Process a LIST command. Return:
261 - resp: server response if successful
262 - list: list of (group, last, first, flag) (strings)"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000263
Tim Peters2344fae2001-01-15 00:50:52 +0000264 resp, list = self.longcmd('LIST')
265 for i in range(len(list)):
266 # Parse lines into "group last first flag"
267 list[i] = tuple(string.split(list[i]))
268 return resp, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000269
Tim Peters2344fae2001-01-15 00:50:52 +0000270 def group(self, name):
271 """Process a GROUP command. Argument:
272 - group: the group name
273 Returns:
274 - resp: server response if successful
275 - count: number of articles (string)
276 - first: first article number (string)
277 - last: last article number (string)
278 - name: the group name"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000279
Tim Peters2344fae2001-01-15 00:50:52 +0000280 resp = self.shortcmd('GROUP ' + name)
281 if resp[:3] != '211':
282 raise NNTPReplyError(resp)
283 words = string.split(resp)
284 count = first = last = 0
285 n = len(words)
286 if n > 1:
287 count = words[1]
288 if n > 2:
289 first = words[2]
290 if n > 3:
291 last = words[3]
292 if n > 4:
293 name = string.lower(words[4])
294 return resp, count, first, last, name
Guido van Rossumc629d341992-11-05 10:43:02 +0000295
Tim Peters2344fae2001-01-15 00:50:52 +0000296 def help(self):
297 """Process a HELP command. Returns:
298 - resp: server response if successful
299 - list: list of strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000300
Tim Peters2344fae2001-01-15 00:50:52 +0000301 return self.longcmd('HELP')
Guido van Rossumc629d341992-11-05 10:43:02 +0000302
Tim Peters2344fae2001-01-15 00:50:52 +0000303 def statparse(self, resp):
304 """Internal: parse the response of a STAT, NEXT or LAST command."""
305 if resp[:2] != '22':
306 raise NNTPReplyError(resp)
307 words = string.split(resp)
308 nr = 0
309 id = ''
310 n = len(words)
311 if n > 1:
312 nr = words[1]
313 if n > 2:
314 id = words[2]
315 return resp, nr, id
Guido van Rossumc629d341992-11-05 10:43:02 +0000316
Tim Peters2344fae2001-01-15 00:50:52 +0000317 def statcmd(self, line):
318 """Internal: process a STAT, NEXT or LAST command."""
319 resp = self.shortcmd(line)
320 return self.statparse(resp)
Guido van Rossumc629d341992-11-05 10:43:02 +0000321
Tim Peters2344fae2001-01-15 00:50:52 +0000322 def stat(self, id):
323 """Process a STAT command. Argument:
324 - id: article number or message id
325 Returns:
326 - resp: server response if successful
327 - nr: the article number
328 - id: the article id"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000329
Tim Peters2344fae2001-01-15 00:50:52 +0000330 return self.statcmd('STAT ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000331
Tim Peters2344fae2001-01-15 00:50:52 +0000332 def next(self):
333 """Process a NEXT command. No arguments. Return as for STAT."""
334 return self.statcmd('NEXT')
Guido van Rossumc629d341992-11-05 10:43:02 +0000335
Tim Peters2344fae2001-01-15 00:50:52 +0000336 def last(self):
337 """Process a LAST command. No arguments. Return as for STAT."""
338 return self.statcmd('LAST')
Guido van Rossumc629d341992-11-05 10:43:02 +0000339
Tim Peters2344fae2001-01-15 00:50:52 +0000340 def artcmd(self, line):
341 """Internal: process a HEAD, BODY or ARTICLE command."""
342 resp, list = self.longcmd(line)
343 resp, nr, id = self.statparse(resp)
344 return resp, nr, id, list
Guido van Rossumc629d341992-11-05 10:43:02 +0000345
Tim Peters2344fae2001-01-15 00:50:52 +0000346 def head(self, id):
347 """Process a HEAD command. Argument:
348 - id: article number or message id
349 Returns:
350 - resp: server response if successful
351 - nr: article number
352 - id: message id
353 - list: the lines of the article's header"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000354
Tim Peters2344fae2001-01-15 00:50:52 +0000355 return self.artcmd('HEAD ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000356
Tim Peters2344fae2001-01-15 00:50:52 +0000357 def body(self, id):
358 """Process a BODY command. Argument:
359 - id: article number or message id
360 Returns:
361 - resp: server response if successful
362 - nr: article number
363 - id: message id
364 - list: the lines of the article's body"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000365
Tim Peters2344fae2001-01-15 00:50:52 +0000366 return self.artcmd('BODY ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000367
Tim Peters2344fae2001-01-15 00:50:52 +0000368 def article(self, id):
369 """Process an ARTICLE command. Argument:
370 - id: article number or message id
371 Returns:
372 - resp: server response if successful
373 - nr: article number
374 - id: message id
375 - list: the lines of the article"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000376
Tim Peters2344fae2001-01-15 00:50:52 +0000377 return self.artcmd('ARTICLE ' + id)
Guido van Rossumc629d341992-11-05 10:43:02 +0000378
Tim Peters2344fae2001-01-15 00:50:52 +0000379 def slave(self):
380 """Process a SLAVE command. Returns:
381 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000382
Tim Peters2344fae2001-01-15 00:50:52 +0000383 return self.shortcmd('SLAVE')
Guido van Rossumc629d341992-11-05 10:43:02 +0000384
Tim Peters2344fae2001-01-15 00:50:52 +0000385 def xhdr(self, hdr, str):
386 """Process an XHDR command (optional server extension). Arguments:
387 - hdr: the header type (e.g. 'subject')
388 - str: an article nr, a message id, or a range nr1-nr2
389 Returns:
390 - resp: server response if successful
391 - list: list of (nr, value) strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000392
Tim Peters2344fae2001-01-15 00:50:52 +0000393 pat = re.compile('^([0-9]+) ?(.*)\n?')
394 resp, lines = self.longcmd('XHDR ' + hdr + ' ' + str)
395 for i in range(len(lines)):
396 line = lines[i]
397 m = pat.match(line)
398 if m:
399 lines[i] = m.group(1, 2)
400 return resp, lines
Guido van Rossumc629d341992-11-05 10:43:02 +0000401
Tim Peters2344fae2001-01-15 00:50:52 +0000402 def xover(self,start,end):
403 """Process an XOVER command (optional server extension) Arguments:
404 - start: start of range
405 - end: end of range
406 Returns:
407 - resp: server response if successful
408 - list: list of (art-nr, subject, poster, date,
409 id, references, size, lines)"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000410
Tim Peters2344fae2001-01-15 00:50:52 +0000411 resp, lines = self.longcmd('XOVER ' + start + '-' + end)
412 xover_lines = []
413 for line in lines:
414 elem = string.splitfields(line,"\t")
415 try:
416 xover_lines.append((elem[0],
417 elem[1],
418 elem[2],
419 elem[3],
420 elem[4],
421 string.split(elem[5]),
422 elem[6],
423 elem[7]))
424 except IndexError:
425 raise NNTPDataError(line)
426 return resp,xover_lines
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000427
Tim Peters2344fae2001-01-15 00:50:52 +0000428 def xgtitle(self, group):
429 """Process an XGTITLE command (optional server extension) Arguments:
430 - group: group name wildcard (i.e. news.*)
431 Returns:
432 - resp: server response if successful
433 - list: list of (name,title) strings"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000434
Tim Peters2344fae2001-01-15 00:50:52 +0000435 line_pat = re.compile("^([^ \t]+)[ \t]+(.*)$")
436 resp, raw_lines = self.longcmd('XGTITLE ' + group)
437 lines = []
438 for raw_line in raw_lines:
439 match = line_pat.search(string.strip(raw_line))
440 if match:
441 lines.append(match.group(1, 2))
442 return resp, lines
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000443
Tim Peters2344fae2001-01-15 00:50:52 +0000444 def xpath(self,id):
445 """Process an XPATH command (optional server extension) Arguments:
446 - id: Message id of article
447 Returns:
448 resp: server response if successful
449 path: directory path to article"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000450
Tim Peters2344fae2001-01-15 00:50:52 +0000451 resp = self.shortcmd("XPATH " + id)
452 if resp[:3] != '223':
453 raise NNTPReplyError(resp)
454 try:
455 [resp_num, path] = string.split(resp)
456 except ValueError:
457 raise NNTPReplyError(resp)
458 else:
459 return resp, path
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000460
Tim Peters2344fae2001-01-15 00:50:52 +0000461 def date (self):
462 """Process the DATE command. Arguments:
463 None
464 Returns:
465 resp: server response if successful
466 date: Date suitable for newnews/newgroups commands etc.
467 time: Time suitable for newnews/newgroups commands etc."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000468
Tim Peters2344fae2001-01-15 00:50:52 +0000469 resp = self.shortcmd("DATE")
470 if resp[:3] != '111':
471 raise NNTPReplyError(resp)
472 elem = string.split(resp)
473 if len(elem) != 2:
474 raise NNTPDataError(resp)
475 date = elem[1][2:8]
476 time = elem[1][-6:]
477 if len(date) != 6 or len(time) != 6:
478 raise NNTPDataError(resp)
479 return resp, date, time
Guido van Rossum8421c4e1995-09-22 00:52:38 +0000480
481
Tim Peters2344fae2001-01-15 00:50:52 +0000482 def post(self, f):
483 """Process a POST command. Arguments:
484 - f: file containing the article
485 Returns:
486 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000487
Tim Peters2344fae2001-01-15 00:50:52 +0000488 resp = self.shortcmd('POST')
489 # Raises error_??? if posting is not allowed
490 if resp[0] != '3':
491 raise NNTPReplyError(resp)
492 while 1:
493 line = f.readline()
494 if not line:
495 break
496 if line[-1] == '\n':
497 line = line[:-1]
498 if line[:1] == '.':
499 line = '.' + line
500 self.putline(line)
501 self.putline('.')
502 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000503
Tim Peters2344fae2001-01-15 00:50:52 +0000504 def ihave(self, id, f):
505 """Process an IHAVE command. Arguments:
506 - id: message-id of the article
507 - f: file containing the article
508 Returns:
509 - resp: server response if successful
510 Note that if the server refuses the article an exception is raised."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000511
Tim Peters2344fae2001-01-15 00:50:52 +0000512 resp = self.shortcmd('IHAVE ' + id)
513 # Raises error_??? if the server already has it
514 if resp[0] != '3':
515 raise NNTPReplyError(resp)
516 while 1:
517 line = f.readline()
518 if not line:
519 break
520 if line[-1] == '\n':
521 line = line[:-1]
522 if line[:1] == '.':
523 line = '.' + line
524 self.putline(line)
525 self.putline('.')
526 return self.getresp()
Guido van Rossumc629d341992-11-05 10:43:02 +0000527
Tim Peters2344fae2001-01-15 00:50:52 +0000528 def quit(self):
529 """Process a QUIT command and close the socket. Returns:
530 - resp: server response if successful"""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000531
Tim Peters2344fae2001-01-15 00:50:52 +0000532 resp = self.shortcmd('QUIT')
533 self.file.close()
534 self.sock.close()
535 del self.file, self.sock
536 return resp
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000537
538
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000539def _test():
Tim Peters2344fae2001-01-15 00:50:52 +0000540 """Minimal test function."""
541 s = NNTP('news', readermode='reader')
542 resp, count, first, last, name = s.group('comp.lang.python')
543 print resp
544 print 'Group', name, 'has', count, 'articles, range', first, 'to', last
545 resp, subs = s.xhdr('subject', first + '-' + last)
546 print resp
547 for item in subs:
548 print "%7s %s" % item
549 resp = s.quit()
550 print resp
Guido van Rossume2ed9df1997-08-26 23:26:18 +0000551
552
553# Run the test when run as a script
554if __name__ == '__main__':
Tim Peters2344fae2001-01-15 00:50:52 +0000555 _test()