package Net::IRC3; use strict; use AnyEvent; use IO::Socket::INET; =head1 NAME Net::IRC3 - An IRC Protocol module which is event system independend =head1 VERSION Version 0.01 =cut our $VERSION = '0.1'; =head1 SYNOPSIS use Net::IRC3; my $irc3 = new Net::IRC3; my $con = $irc3->connect_server ("test.not.at.irc.net", 6667); ... =head1 DESCRIPTION L itself is a simple building block for an IRC client. It manages connections and parses and constructs IRC messages. L is I simple, if you don't want to care about all the other things that a client still has to do (like replying to PINGs and remembering who is on a channel), I recommend to read the L page instead. =head1 METHODS =over 4 =item B This just creates a L object, which is a management class for creating and managing connections. =cut sub new { my $this = shift; my $class = ref($this) || $this; my $self = { }; bless $self, $class; return $self; } =item B Tries to open a socket to the host C<$host> and the port C<$port>. If successfull it will return a Net::IRC3::Connection object. If an error occured it will die (use eval to catch the exception). =cut sub connect_server { my ($self, $host, $port) = @_; defined $self->{connections}->{"$host:$port"} and return; my $sock = IO::Socket::INET->new ( PeerAddr => $host, PeerPort => $port, Proto => 'tcp', Blocking => 0 ) or die "couldn't connect to irc server '$host:$port': $!\n";; my $con = Net::IRC3::Connection->new ($self, $sock, $host, $port); $con->{rw} = AnyEvent->io (poll => 'r', fh => $sock, cb => sub { my $l = sysread $sock, my $data, 1024; $con->feed_irc_data ($data); unless ($l) { if (defined $l) { $con->disconnect_server ("EOF from IRC server '$host:$port'"); return; } else { $con->disconnect_server ("Error while reading from IRC server '$host:$port': $!"); return; } } }); return $con; } =back =head1 FUNCTIONS These are some utility functions that might come in handy when handling the IRC protocol. =over 4 =item B This method parses the C<$ircline>, which is one line of the IRC protocol without the trailing "\015\012". It returns a hash which has the following entrys: =over 4 =item prefix The message prefix. =item command The IRC command. =item params The parameters to the IRC command in a array reference, this includes the trailing parameter (the one after the ':' or the 14th parameter). =item trailing This is set if there was a trailing parameter (the one after the ':' or the 14th parameter). =back =cut sub parse_irc_msg { my ($msg) = @_; my $cmd; my $pref; my $t; my @a; my $p = $msg =~ s/^(:([^ ]+)[ ])?([A-Za-z]+|\d{3})//; $pref = $2; $cmd = $3; my $i = 0; while ($msg =~ s/^[ ]([^ :\015\012\0][^ \015\012\0]*)//) { push @a, $1 if defined $1; if (++$i > 13) { last; } } if ($i == 14) { if ($msg =~ s/^[ ]:?([^\015\012\0]*)//) { $t = $1 if $1 ne ""; } } else { if ($msg =~ s/^[ ]:([^\015\012\0]*)//) { $t = $1 if $1 ne ""; } } push @a, $t if defined $t; my $m = { prefix => $pref, command => $cmd, params => \@a, trailing => $t }; return $p ? $m : undef; } =item B This function assembles a IRC message. The generated message will look like (pseudo code!) : : Please refer to RFC 2812 how IRC messages normally look like. The prefix and the trailing string will be omitted if they are C. EXAMPLES: $con->mk_msg (undef, "PRIVMSG", "you suck!", "magnus"); # will return: "PRIVMSG magnus :you suck!\015\012" $con->mk_msg (undef, "JOIN", undef, "#test"); # will return: "JOIN #magnus\015\012" =cut sub mk_msg { my ($prefix, $command, $trail, @params) = @_; my $msg = ""; $msg .= defined $prefix ? ":$prefix " : ""; $msg .= "$command"; # FIXME: params must be counted, and if > 13 they have to be # concationated with $trail map { $msg .= " $_" } @params; $msg .= defined $trail ? " :$trail" : ""; $msg .= "\015\012"; return $msg; } =item B This function splits an IRC user prefix as described by RFC 2817 into the three parts: nickname, user and host. Which will be returned as a list with that order. C<$prefix> can also be a hash like it is returned by C. =cut sub split_prefix { my ($prfx) = @_; if (ref ($prfx) eq 'HASH') { $prfx = $prfx->{prefix}; } $prfx =~ m/^\s*([^!]*)!([^@]*)@(.*?)\s*$/; return ($1, $2, $3); } =item B A shortcut to extract the nickname from the C<$prefix>. C<$prefix> can also be a hash like it is returned by C. =cut sub prefix_nick { my ($prfx) = @_; return (split_prefix ($prfx))[0]; } =item B A shortcut to extract the username from the C<$prefix>. C<$prefix> can also be a hash like it is returned by C. =cut sub prefix_user { my ($prfx) = @_; return (split_prefix ($prfx))[1]; } =item B A shortcut to extract the hostname from the C<$prefix>. C<$prefix> can also be a hash like it is returned by C. =cut sub prefix_host { my ($self, $prfx) = @_; return (split_prefix ($prfx))[2]; } =head1 Net::IRC3::Connection The connection class. Here the actual interesting stuff can be done, such as sending and receiving IRC messages. =head2 METHODS =over 4 =cut package Net::IRC3::Connection; use strict; use AnyEvent; use IO::Socket::INET; sub new { my $this = shift; my $class = ref($this) || $this; my $self = { pirc => $_[0], s => $_[1], h => $_[2], p => $_[3], cbs => {}, heap => {} }; bless $self, $class; return $self; } =item B Unregisters the connection in the main Net::IRC3 object, closes the sockets and send a 'disconnect' event with C<$reason> as argument. =cut sub disconnect_server { my ($self, $reason) = @_; $self->event (disconnect => $reason); delete $self->{rw}; delete $self->{ww}; delete $self->{pirc}->{connections}->{$self->{h} . ":" . $self->{p}}; eval { $self->{s}->close } } =item B Returns a hash reference that is local to this connection object that lets you store any information you want. =cut sub heap { my ($self) = @_; return $self->{heap}; } =item B This function sends a message to the server. C<@ircmsg> is the argumentlist for C. =cut sub send_msg { my ($self, @msg) = @_; my $data = mk_msg (@msg); my ($host, $port) = ($self->{h}, $self->{p}); $self->{outbuf} .= $data; unless (defined $self->{ww}) { my $sock = $self->{s}; $self->{ww} = AnyEvent->io (poll => 'w', fh => $sock, cb => sub { my $l = syswrite $sock, $self->{outbuf}; substr $self->{outbuf}, 0, $l, ""; if (length ($self->{outbuf}) == 0) { delete $self->{ww} } unless ($l) { # XXX: is this behaviour correct or ok? $self->disconnect_server ("Error while writing to IRC server '$host:$port': $!"); return; } }); } } =item B This registers a callback in the connection class. These callbacks will be called by internal events and by IRC protocol commands. The first argument to the callbacks is always the connection object itself. If a callback returns a false value, it will be unregistered. NOTE: I If C<$cmd> starts with 'irc_' the callback C<$cb> will be registered for a IRC protocol command. The command is the suffix of C<$cmd> then. The second argument to the callback is the message hash reference that has the layout that is returned by C. EXAMPLE: $con->reg_cb (irc_privmsg => \&privmsg_handler); # privmsg_handler will be called if an IRC message # with the command 'PRIVMSG' arrives. If C<$cmd> is not prefixed with a 'irc_' it will be called when an event with the name C<$cmd> is emitted. The arguments to the callback depend on the event that is emitted (but remember: the first argument will always be the connection object) Following events are emitted by this module and shouldn't be emitted from a module user call to C. =over 4 =item B This event will be generated if the connection is somehow terminated. It will also be emitted when C is called. The second argument to the callback is C<$reason>, a string that contains a clue about why the connection terminated. =back =cut sub reg_cb { my ($self, $cmd, $cb) = @_; if ($cmd =~ m/^irc_(\S+)/i) { push @{$self->{cbs}->{lc $1}}, $cb; } else { push @{$self->{events}->{$cmd}}, $cb; } 1; } =item B This function emits an event with the name C<$event> and the arguments C<@args>. The registerd callback that has been registered with C will be called with the first argument being the connection object and the rest of the arguments being C<@args>. EXAMPLE $con->reg_cb (test_event => sub { print "Yay, i love $_[1]!!\n"); $con->event (test_event => "IRC"); # will print "Yay, i love IRC!!\n" =cut sub event { my ($self, $ev, @arg) = @_; my $nxt = []; for (@{$self->{events}->{lc $ev}}) { $_->($self, @arg) and push @$nxt, $_; } $self->{events}->{lc $ev} = $nxt; } # internal function, called by the read callbacks above. sub feed_irc_data { my ($self, $data) = @_; $self->{buffer} .= $data; my @msg; while ($self->{buffer} =~ s/^([^\015\012]*)\015?\012//) { push @msg, $1; } for (@msg) { my $m = parse_irc_msg ($_); my $nxt = []; for (@{$self->{cbs}->{lc $m->{command}}}) { $_->($self, $m) and push @$nxt, $_; } $self->{cbs}->{lc $m->{command}} = $nxt; $nxt = []; for (@{$self->{cbs}->{'*'}}) { $_->($self, $m) and push @$nxt, $_; } $self->{cbs}->{'*'} = $nxt; } } =back =head1 AUTHOR Robin Redeker, C<< >> =head1 SEE ALSO RFC 2812 - Internet Relay Chat: Client Protocol =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc Net::IRC3 You can also look for information at: =over 4 =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * RT: CPAN's request tracker L =item * Search CPAN L =back =head1 ACKNOWLEDGEMENTS Thanks to Marc Lehmann for the new AnyEvent module! =head1 COPYRIGHT & LICENSE Copyright 2006 Robin Redker, all rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut 1;