1. Hey Guest, are you looking for a place to host your IRC related project? Then check out the Resource Manager on IRCForums! Click the resources tab above this notice!
  2. Hello Guest, are you new to IRCForums? Why not introduce yourself in the introduction forum!
  3. We have updated the layout of our forums. Please see this thread for more information.

Teaching Devs to Fish: Getting started with Perl and IRC

Discussion in 'IRC Tutorials & Guides' started by avenj, Dec 18, 2011.

  1. Offline

    avenj Recruit

    Member Since:
    Dec 18, 2011
    Message Count:
    58
    Likes Received:
    27
    Trophy Points:
    21
    When developing client applications, including bots: Don't parse IRC with a regex!

    Why not? Well, study http://tools.ietf.org/html/rfc1459#section-2.3 closely. The regex to do this properly is actually quite complicated. Rather, use an appropriate IRC library for your language.

    For Perl, your best options are Bot::BasicBot (simple sugar over POE::Component::IRC), POE::Component::IRC (tried and true), or Net::Async::IRC (uses IO::Async rather than POE, also more simplistic).

    This example specifically pertains to POE::Component::IRC.
    POE provides an event loop and a lot of nifty features for asynchronous programming. It has a large ecosystem containing several entire client and server systems (as Components).

    For more on what you can accomplish with POE::Component::IRC and the State plugin, see POE::Component::IRC and POE::Component::IRC::State

    For more information on POE, see http://poe.perl.org (and especially the POE Cookbook)

    A slightly braindead example follows. It should be enough to get an amateur started, with the help of a basic understanding of Perl fundamentals and some quality time spent with perldoc.

    Code (perl):

    #!/usr/bin/env perl
     
    ## a silly POE::Component::IRC example
    ## also see perldocs for:
    ##  POE::Session
    ##  POE::Component::IRC
    ##  POE::Component::IRC::State
    ##  IRC::Utils
    ## and http://poe.perl.org
     
    use strict;
    use warnings;
     
    ## some stupid globals.
    ## in reality you'd want conf files, clearly
    ## (YAML makes them pretty easy, for what it's worth)
    my $nick = "simplepocoexample";
     
    my $server = "irc.cobaltirc.org";
    my $port = 6667;
     
    my $username = "pocoexample";
    my $ircname = "example pocoirc bot";
     
    my @channels = (
      '#unix',
      '#guns',
    );
     
     
    use feature "switch";  # perl5.10: given/when
     
    use Carp;
     
    use POE;
    use POE::Component::IRC::State;
    use POE::Component::IRC::Plugin::CTCP;
    use POE::Component::IRC::Plugin::AutoJoin;
    use POE::Component::IRC::Plugin::Connector;
    #use POE::Component::IRC::Plugin::NickServID;
    use POE::Component::IRC::Plugin::NickReclaim;
     
    ## see IRC::Utils docs for more information on these.
    ## in particular note that IRC upper->lower case rules are "odd"
    ## hence, eq_irc uc_irc lc_irc as opposed to uc()/lc()
    ## see http://tools.ietf.org/html/rfc1459#section-2.2
    use IRC::Utils qw/
      parse_user
      eq_irc uc_irc lc_irc
      strip_color strip_formatting /;
     
     
    ## set up a POE session.
    ## this session is "ours" ...
    ## we could specify our own events, handle irc_ events, etc.
    ## see POE documentation
    POE::Session->create(
      ## package_states defines event handlers by package
      ## for this example we could just as easily use inline_states
      ## for a bigger app we might perhaps use object_states
      ## see POE perldocs for more info.
      package_states => [
        main => [
          '_start',
          'irc_chan_sync',
    #      'irc_join',
    #      'irc_part',
          'irc_public',
    #      'irc_msg',
        ],
      ],
      heap => { },
    );
     
    ## run POE:
    $poe_kernel->run();
     
     
    ## Event handlers:
    sub _start {
      ## POE's heap is just a convenient data store
      ## access it via the HEAP constant
      ## here we grab our KERNEL and HEAP:
      my ($kernel, $heap) = @_[KERNEL, HEAP];
     
      print "Spawning IRC ($nick) $server $port\n";
     
      ## we use ::State here as an example, but it's actually very nice
      ## it'll handle most basic state tracking for you.
      ## see http://search.cpan.org/perldoc?POE::Component::IRC::State
      my $irc = POE::Component::IRC::State->spawn(
        nick    => $nick,
        username => $username,
        ircname  => $ircname,
        server  => $server,
        port    => $port,
        raw => 0,
      ) or croak "poco-irc error: $!";
     
      ## automagically maintain connections to servers:
      $irc->plugin_add('Connector' =>
        POE::Component::IRC::Plugin::Connector->new);
     
      ## if our nickname is unavailable, keep trying for it:
      $irc->plugin_add('NickReclaim' =>
        POE::Component::IRC::Plugin::NickReclaim->new( poll => 30 ) );
     
      ## you could specify a NickServ passwd to autoidentify to NickServ
      ## (be sure to uncomment the 'use' line for NickServID above)
    #  $irc->plugin_add('NickServID' =>
    #    POE::Component::IRC::Plugin::NickServID->new (
    #      Password => $nickserv_pass // '',
    #    ),
    #  );
     
      ## automatically join & attempt to rejoin @channels:
      $irc->plugin_add('AutoJoin' =>
        POE::Component::IRC::Plugin::AutoJoin->new(
          Channels => [ @channels ],
          RejoinOnKick => 1,
          Rejoin_delay => 5,  ## these could be configurable
          NickServ_delay => 1,
          Retry_when_banned => 60,
        ),
      );
     
      ## ..you could set up some custom ctcp replies:
      $irc->plugin_add('CTCP' =>
        POE::Component::IRC::Plugin::CTCP->new(
          version => "pocoirc example (perl $^V)",
        ),
      );
     
      ## save a reference to the IRC obj in our heap
      ## makes it extremely easy to get to later
      $heap->{IRC} = $irc;
     
      ## register for all events
      ## (we could just register for specific events)
      $irc->yield(register => 'all');
     
      ## could specify opts to connect
      ## (same as spawn)
      $irc->yield(connect => { });
    }
     
    sub irc_public {
      ## public message handler
     
      ## get our heap:
      my ($heap) = $_[HEAP];
     
      ## ...and ARG0 (source), ARG1 (array of targets), ARG2 (string)
      ## note that ARG0 is full prefix form, ie nick!user@host
      my ($src, $target, $txt) = @_[ARG0 .. ARG2];
      ## ..that being the case, maybe we want to split it up:
      my ($nick, $user, $host) = parse_user($src);
     
      my $irc = $heap->{IRC};  ## irc object
     
      ## $target is an array
      ## (messages can have multiple targets)
      ## maybe we only want to respond to the first channel:
      my $channel = $target->[0];
     
      ## perhaps we want to disregard colors/formatting?
      $txt = strip_color( strip_formatting($txt) );
     
      ## now you could maybe parse it for commands or something...
      my ($cmd, @args) = split ' ', $txt;
      given ($cmd)  ## perl5.10+: given/when case statement
      {
        when ("os") {
          my $response = "I am running $^O";
          $irc->yield(privmsg => $channel => $response);
        }
     
        when ("unixtime") {
          $irc->yield(privmsg => $channel => time);
        }
      }
     
    }
     
    sub irc_chan_sync {
      ## successfully joined channel
      my ($heap, $chan) = @_[HEAP, ARG0];
      my $irc = $heap->{IRC};
      ## ..etc..
    }
     
    Bertrum likes this.
  2. Offline

    Trixar_za GigIRC

    Member Since:
    Dec 18, 2011
    Message Count:
    1,295
    Likes Received:
    182
    Trophy Points:
    178
  3. Offline

    avenj Recruit

    Member Since:
    Dec 18, 2011
    Message Count:
    58
    Likes Received:
    27
    Trophy Points:
    21
    Indeed, this assumes you already know enough Perl to mostly get a grasp of what's happening here.

    Hmm ... A lot has changed since 2003 ... I see a few inaccuracies in that particular guide (for example, -w rather than 'use warnings' is a Bad Idea, as it won't apply to modules ... 2-arg open is long since deprecated, bareword filehandles are a bad idea ... plus some weirdness like referring to man pages when perl docs exist primarily in perldoc) although overall it doesn't look awful ...

    Might I also perhaps suggest:
    http://learn.perl.org/
    http://www.perl.org/books/beginning-perl/

    Additionally, after learning some fundamentals, Modern Perl: http://onyxneon.com/books/modern_perl/
  4. Offline

    Trixar_za GigIRC

    Member Since:
    Dec 18, 2011
    Message Count:
    1,295
    Likes Received:
    182
    Trophy Points:
    178
    I learned the latter the long way around, especially use warning;.
    This is how my template file looks now:
    Code (text):
    #!/usr/bin/perl
    use strict;
    use warnings;
    Never really got far in beginning-perl myself. To download it manually is huge too :p
    Best thing I ever learned was how to use perldoc - so learn it and use it people ;)
    avenj likes this.
  5. Offline

    avenj Recruit

    Member Since:
    Dec 18, 2011
    Message Count:
    58
    Likes Received:
    27
    Trophy Points:
    21
    Your template is lacking in animals and new features! ;)

    Code (perl):

    use strict;
    use warnings;
    use 5.10.1;  ## can has given/when, say() etc
     
    given ("string") {
      ## case w/ smart-match
      ## see 'Smart matching' in perldoc perlsyn
      when ("something") { ... } ## smartmatch: 'eq'
      when ([qw/some stuff/]) { ... } ## smartmatch: is this element in this array?
      when (/^regex$/) { ... } ## pattern match
      do_something when /^regex$/;  ## also legit
      default { ... }  ## some default to fall through to
    }
     
    ## even better ... can has better reference coping skills:
    use 5.14.1;
    my $things = { stuff => [ ] };
    push $things->{stuff}, 'a', 'b', 'c';  ## no need for @{ }
     
    ## animals help:
    use Carp;
    carp "something bad happened"; ## more informative warn()
    croak "something extra bad happened";  ## more informative die()
    confess "something really very bad happened"; ## callstack
     
    package Some::Module;
    use Moose;
    use MooseX::NonMoose;
    extends 'POE::Component::Syndicator';  ## I'm an event syndicator!
     
    has 'cfg' => (
      is => 'rw',
      isa => 'HashRef',
      default => sub { {} },
    );
    ## see perldoc Moose, way beyond the scope of this bored post 8)
     

Share This Page