Perl Cookbook

Perl CookbookSearch this book
Previous: 18.3. Sending MailChapter 18
Internet Services
Next: 18.5. Reading Mail with POP3
 

18.4. Reading and Posting Usenet News Messages

Problem

You want to connect to a Usenet news server to read and post messages. Your program could send a periodic posting to a newsgroup,[1] summarize a newsgroup, or identify first-time contributors in a newsgroup so you can send them a helpful welcome message.

[1] If so, be sure to check out Ian Kluft's auto-faq program at http://www.novia.net/~pschleck/auto-faq/.

Solution

Use the CPAN module Net::NNTP:

use Net::NNTP;

$server = Net::NNTP->new("news.host.dom")
    or die "Can't connect to news server: $@\n";
($narticles, $first, $last, $name) = $server->group( "misc.test" )
    or die "Can't select misc.test\n";
$headers  = $server->head($first)
    or die "Can't get headers from article $first in $name\n";
$bodytext = $server->body($first)
    or die "Can't get body from article $first in $name\n";
$article  = $server->article($first)
    or die "Can't get article $first from $name\n";

$server->postok()
    or warn "Server didn't tell me I could post.\n";

$server->post( [ @lines ] )
    or die "Can't post: $!\n";

Discussion

Usenet is a distributed news system. Servers exchange messages to ensure that each server gets all the messages for the newsgroups it carries. Each server sets its own expiration criteria to decide how long messages stay on the server. Client newsreaders connect to their designated server (usually belonging to their company, ISP, or university) and can read existing postings and contribute new ones.

Each message (or article, as they're also known) has a set of headers and a body, separated by a blank line. Articles are identified in two ways: the message ID header and an article number within a newsgroup. An article's message ID is stored in the message itself and is guaranteed to be unique no matter which news server the article was read from. When an article references others, it does so by message ID. A message ID is a string like:

<0401@jpl-devvax.JPL.NASA.GOV>

An article can also be identified by a newsgroup and an article number within the group. Each news server assigns its own article numbers to the articles it has, so they're only guaranteed to be good for the news server you got them from.

The Net::NNTP constructor connects to the specified news server. If the connection couldn't be made, it returns undef and sets $@ to an error message. If the connection was successfully made, new returns a new Net::NNTP object:

$server = Net::NNTP->new("news.mycompany.com")
    or die "Couldn't connect to news.mycompany.com: $@\n";

Once connected, you can get a list of newsgroups with the list method. This returns a reference to a hash whose keys are newsgroup names. Each value is a reference to an array consisting of the first valid article number in the group, the last valid article number in the group, and a string of flags. The flags are typically "y", meaning you may post, but could be "m" for moderated or =NAME, meaning that the group is an alias for the newsgroup NAME. There are over 17,000 newsgroups that your server might carry, so fetching a list of all the groups can take a while.

$grouplist = $server->list()
    or die "Couldn't fetch group list\n";

foreach $group (keys %$grouplist) {
    if ($grouplist->{$group}->[2] eq 'y') {
        # I can post to $group
    }
}

Much as FTP has the concept of a current directory, the Network News Transfer Protocol (NNTP) has the concept of a current group. Make a group the current group with the group method:

($narticles, $first, $last, $name) = $server->group("comp.lang.perl.misc")
    or die "Can't select comp.lang.perl.misc\n";

The group method returns a four-element list: the number of articles in the group, the first article number, the last article number, and the name of the group. If the group does not exist, it returns an empty list.

There are two ways to retrieve articles: call article with a message ID, or select a group with group and then call article with an article number. In scalar context, it returns a reference to an array of lines. In list context, article returns a list of lines. If an error occurs, article returns false:

@lines = $server->article($message_id)
    or die "Can't fetch article $message_id: $!\n";

You can fetch an article's header or body with the head and body methods. Like article, these methods take an article number or message ID, and return a list of lines or an array reference.

@group = $server->group("comp.lang.perl.misc")
    or die "Can't select group comp.lang.perl.misc\n";
@lines = $server->head($group[1])
    or die "Can't get headers from first article in comp.lang.perl.misc\n";

To post an article, use the post method. Give it a list of lines or a reference to an array of lines, and it returns true if the post succeeded, false if the article couldn't be posted.

$server->post(@message)
    or die "Can't post\n";

Use the postok method to find out whether the server said that you may post:

unless ($server->postok()) {
    warn "You may not post.\n";
}

Read the manpage for Net::NNTP for a complete list of methods.

See Also

The documentation for the Net::NNTP module from CPAN; RFC 977, Network News Transfer Protocol ; your system's trn (1) and innd (8) manpages (if you have them)


Previous: 18.3. Sending MailPerl CookbookNext: 18.5. Reading Mail with POP3
18.3. Sending MailBook Index18.5. Reading Mail with POP3