NAME

POE::Component::Client::NNTP - A component that provides access to NNTP.

SYNOPSIS

   # Connects to NNTP Server, selects a group, then downloads all current articles.
   use strict;
   use POE;
   use POE::Component::Client::NNTP;
   use Mail::Internet;
   use FileHandle;

   $|=1;

   POE::Component::Client::NNTP->spawn ( 'NNTP-Client', { NNTPServer => 'news.host' } );

   POE::Session->create(
	package_states => [
		'main' => { nntp_disconnected => '_shutdown',
			    nntp_socketerr    => '_shutdown',
			    nntp_421          => '_shutdown',
			    nntp_200	      => '_connected',
			    nntp_201	      => '_connected',
		},
		'main' => [ qw(_start nntp_211 nntp_220 nntp_223)
		],
	],
   );

   $poe_kernel->run();
   exit 0;

   sub _start {
	my ($kernel,$heap) = @_[KERNEL,HEAP];
	
	# Our session starts, register to receive all events from poco-client-nntp
	$kernel->post ( 'NNTP-Client' => register => 'all' );
	# Okay, ask it to connect to the server
	$kernel->post ( 'NNTP-Client' => 'connect' );
	undef;
   }

   sub _connected {
	my ($kernel,$heap,$text) = @_[KERNEL,HEAP,ARG0];

	print "$text\n";

	# Select a group to download from.
	$kernel->post( 'NNTP-Client' => group => 'random.group' );
	undef;
   }

   sub nntp_211 {
	my ($kernel,$heap,$text) = @_[KERNEL,HEAP,ARG0];
	print "$text\n";

	# The NNTP server sets 'current article pointer' to first article in the group.
	# Retrieve the first article
	$kernel->post( 'NNTP-Client' => 'article' );
   }

   sub nntp_220 {
	my ($kernel,$heap,$text,$article) = @_[KERNEL,HEAP,ARG0,ARG1];
	print "$text\n";

	my $message = Mail::Internet->new( $article );
	my $filename = $message->head->get( 'Message-ID' );
	my $fh = new FileHandle "> articles/$filename";
	$message->print( $fh );
	$fh->close;

	# Set 'current article pointer' to the 'next' article in the group.
	$kernel->post( 'NNTP-Client' => 'next' );
	undef;
   }

   sub nntp_223 {
	my ($kernel,$heap,$text) = @_[KERNEL,HEAP,ARG0];
	print "$text\n";

	# Server has moved to 'next' article. Retrieve it.
	# If there isn't a 'next' article an 'nntp_421' is generated
	# which will call '_shutdown'
	$kernel->post( 'NNTP-Client' => 'article' );
	undef;
   }

   sub _shutdown {
	my ($kernel,$heap) = @_[KERNEL,HEAP];

	# We got disconnected or a socketerr unregister and terminate the component.
	$kernel->post ( 'NNTP-Client' => unregister => 'all' );
	$kernel->post ( 'NNTP-Client' => 'shutdown' );
	undef;
   }

DESCRIPTION

POE::Component::Client::NNTP is a POE component that provides non-blocking NNTP access to other components and sessions. NNTP is described in RFC 977 http://www.faqs.org/rfcs/rfc977.html, please read it before doing anything else.

In your component or session, you spawn a NNTP client component, assign it an alias, and then send it a 'register' event to start receiving responses from the component.

The component takes commands in the form of events and returns the salient responses from the NNTP server.

CONSTRUCTOR

spawn

Takes two arguments, a kernel alias to christen the new component with and a hashref.

Possible values for the hashref are:

'NNTPServer', the DNS name or IP address of the NNTP host to connect to; 
'Port', the IP port on that host
'LocalAddr', an IP address on the client to connect from. 

If 'NNTPServer' is not specified, the default is 'news', unless the environment variable 'NNTPServer' is set. If 'Port' is not specified the default is 119.

  POE::Component::Client::NNTP->spawn( 'NNTP-Client', { NNTPServer => 'news', Port => 119,
		LocalAddr => '192.168.1.99' } );

INPUT

The component accepts the following events:

register

Takes N arguments: a list of event names that your session wants to listen for, minus the 'nntp_' prefix, ( this is similar to POE::Component::IRC ).

Registering for 'all' will cause it to send all NNTP-related events to you; this is the easiest way to handle it.

unregister

Takes N arguments: a list of event names which you don't want to receive. If you've previously done a 'register' for a particular event which you no longer care about, this event will tell the NNTP connection to stop sending them to you. (If you haven't, it just ignores you. No big deal).

Please ensure that you always 'unregister' with the component before asking it to 'shutdown'.

connect

Takes no arguments. Tells the NNTP component to start up a connection to the previously specified NNTP server. You will receive a 'nntp_connected' event.

disconnect

Takes no arguments. Terminates the socket connection ungracelessly.

shutdown

Takes no arguments. Terminates the component.

Always ensure that you call 'unregister' before shutting down the component.

The following are implemented NNTP commands, check RFC 977 http://www.faqs.org/rfcs/rfc977.html for the arguments accepted by each. Arguments can be passed as a single scalar or a list of arguments:

article

Takes either a valid message-ID or a numeric-ID.

body

Takes either a valid message-ID or a numeric-ID.

Takes either a valid message-ID or a numeric-ID.

stat

Takes either a valid message-ID or a numeric-ID.

group

Takes the name of a newsgroup to select.

help

Takes no arguments.

ihave

Takes one argument, a message-ID.

last

Takes no arguments.

list

Takes no arguments.

newgroups

Can take up to four arguments: a date, a time, optionally you can specify GMT and an optional list of distributions.

newnews

Can take up to five arguments: a newsgroup, a date, a time, optionally you can specify GMT and an optional list of distributions.

next

Takes no arguments.

post

Takes no arguments. Once you have sent this expect to receive an 'nntp_340' event. When you receive this send the component a 'send_post' event, see below.

send_post

Takes one argument, an array ref containing the message to be posted, one line of the message to each array element.

quit

Takes no arguments.

slave

Takes no arguments.

authinfo

Takes two arguments: first argument is either 'user' or 'pass', second argument is the user or password, respectively. Not technically part of RFC 977 http://www.faqs.org/rfcs/rfc977.html, but covered in RFC 2980 http://www.faqs.org/rfcs/rfc2980.html.

send_cmd

The catch-all event :) Anything sent to this is passed directly to the NNTP server. Use this to implement any non-RFC commands that you want, or to completely bypass all the above if you so desire.

OUTPUT

The following events are generated by the component:

nntp_connected

Generated when the component successfully makes a connection to the NNTP server. Please note, that this is only the underlying network connection. Wait for either an 'nntp_200' or 'nntp_201' before sending any commands to the server.

nntp_disconnected

Generated when the link to the NNTP server is dropped for whatever reason.

nntp_socketerr

Generated when the component fails to establish a connection to the NNTP server.

Numeric responses ( See RFC 977 )

Messages generated by NNTP servers consist of a numeric code and a text response. These will be sent to you as events with the numeric code prefixed with 'nntp_'. ARG0 is the text response.

Certain responses return following text, such as the ARTICLE command, which returns the specified article. These responses are returned in an array ref contained in ARG1.

Eg.

  $kernel->post( 'NNTP-Client' => article => $article_num );

  sub nntp_220 {
    my ($kernel,$heap,$text,$article) = @_[KERNEL,HEAP,ARG0,ARG1];

    print "$text\n";
    if ( scalar @{ $article } > 0 ) {
	foreach my $line ( @{ $article } ) {
	   print STDOUT $line;
	}
    }
    undef;
  }

Possible nntp_ values are:

100 help text follows
199 debug output

200 server ready - posting allowed
201 server ready - no posting allowed
202 slave status noted
205 closing connection - goodbye!
211 n f l s group selected
215 list of newsgroups follows
220 n <a> article retrieved - head and body follow 221 n <a> article
retrieved - head follows
222 n <a> article retrieved - body follows
223 n <a> article retrieved - request text separately 230 list of new
articles by message-id follows
231 list of new newsgroups follows
235 article transferred ok
240 article posted ok

335 send article to be transferred.  End with <CR-LF>.<CR-LF>
340 send article to be posted. End with <CR-LF>.<CR-LF>

400 service discontinued
411 no such news group
412 no newsgroup has been selected
420 no current article has been selected
421 no next article in this group
422 no previous article in this group
423 no such article number in this group
430 no such article found
435 article not wanted - do not send it
436 transfer failed - try again later
437 article rejected - do not try again.
440 posting not allowed
441 posting failed

500 command not recognized
501 command syntax error
502 access restriction or permission denied
503 program fault - command not performed

TODO

Abstract the NNTP protocol parsing into a POE::Filter.

Implement a plugin system.

CAVEATS

The group event sets the current working group on the server end. If you want to use group and numeric form of article|head|etc then you will have to spawn multiple instances of the component for each group you want to access concurrently.

AUTHOR

Chris Williams, <chris@bingosnet.co.uk<gt>

With code derived from POE::Component::IRC by Dennis Taylor.

SEE ALSO

RFC 977 http://www.faqs.org/rfcs/rfc977.html

RFC 2980 http://www.faqs.org/rfcs/rfc2980.html