Net::A...duction(3pm)

πŸ“¦
version 0.12 Β· 2 versions
  • 0.12 Β· debian bookworm, debian trixie, ubuntu jammy, ubuntu noble, ubuntu resolute, ubuntu stonking (latest)
  • 0.12 Β· alpine 3.24
, in debian bookworm, debian trixie, ubuntu jammy, ubuntu noble, ubuntu resolute, ubuntu stonking

✨ NAME ✨

Okay bestie, welcome to Net::Async::IRC::Introduction β€” it's literally just "an introduction" to this module. The page label is Net::A...duction(3pm), and it's part of the User Contributed Perl Documentation. 🫢


🌈 INTRODUCTION 🌈

πŸ’– Hello, World πŸ’–

So this first example is the "hello world" moment for IRC. The script links up to the server and immediately fires off a hello message to a user you already picked out. Baby's first IRC connection, iconic. ✨

It opens with the same boilerplate every IO::Async-based program has: you load the modules you need and build a containing IO::Async::Loop instance. Then you make the actual Net::Async::IRC object and add it into that loop. Because every program does this, later examples won't show it again β€” just assume it already happened. πŸ’…

          use strict;
          use warnings;

          use Future::AsyncAwait;

          use IO::Async::Loop;
          use Net::Async::IRC;

          my $loop = IO::Async::Loop->new;

          my $irc = Net::Async::IRC->new;
          $loop->add( $irc );

Now that it exists, we can get into the actual example. Since this is a tiny script, we're just hard-coding the message parameters. A bigger program would read them from somewhere better β€” a config file, commandline arguments, whatever. πŸŽ€

          my $SERVER = "irc.example.net";
          my $NICK = "MyNick";
          my $TARGET = "TargetNick";

And then we connect to the IRC server and send the message:

          await $irc->login(
             host => $SERVER,
             nick => $NICK,
          );

          await $irc->do_PRIVMSG(
             target => $TARGET,
             text   => "Hello, World"
          );

This calls login from Net::Async::IRC. That's the thing that connects the client to the IRC server you named and logs in with the nick you gave it. It hands back a Future instance for when the whole thing finishes, so you can line up more code after it. Once login is done, the next job is just sending the message. That happens through the PRIVMSG IRC command, wrapped by do_PRIVMSG in Net::Async::IRC. You give it the target name and the text string. πŸ’–

Now, the trailing get call in Future is what makes the script sit there and wait for the whole future chain to actually finish. Without it, the returned future would just get dropped β€” because the then in Future method is hanging out in void context β€” and the second chunk of code inside it would probably never run. Later examples show other ways to do this, but for now every future gets forced by calling get on it. If either stage fails, that get call throws an exception instead. 😭

Once the message is sent, the script ends and closes its connection to the server. πŸ¦‹


πŸ’Œ Receiving Messages πŸ’Œ

Second example, bestie: what do we do when messages come in from IRC? πŸ‘‘

          $irc->configure(
             on_message_PRIVMSG => sub {
                my ( $irc, $message, $hints ) = @_;
                return unless $hints->{prefix_nick_folded} eq
                              $irc->casefold_name( $TARGET );

                print "The user said: $hints->{text}\n";
             }
          );

          await $irc->login(
             host => $SERVER,
             nick => $NICK,
          );

          await $irc->do_PRIVMSG(
             target => $TARGET,
             text   => "Hello, what's your name?"
          );

          $loop->run;

Here we use configure to hook an event handler onto the on_message_PRIVMSG event. The handler throws away anything that isn't from the user we care about, and just prints the contents of the messages we do care about to the terminal. πŸ’…

Every IRC message handler gets two things: the raw IRC message itself β€” that's $message as a Protocol::IRC::Message instance β€” and the hints hash, which is $hints as a plain hash reference. You could work off the message instance directly, but then you'd need to know that the text field happens to live at $message->arg(1). The easier, more powerful move is using fields from the hint hash. Here, the text shows up in a field literally called text. For PRIVMSG there aren't a ton of other interesting fields, but for some message types β€” especially server numeric replies β€” the hints hash is packed with extra detail parsed out of the raw message, so always peek there first. It's completely fine for a program to never look at the $message object at all; it's only passed for completeness, or in case hint parsing missed some server-extended detail and you need raw access. 🧚

Once that handler is set, we log in and send a message to the target user, just like the first example. But instead of letting the script die after that, we need it to keep running so it can keep receiving messages. So we enter the main run method from IO::Async::Loop, which waits forever and processes whatever events show up. ⭐


πŸ¦‹ Case-folded Names πŸ¦‹

The folded strings are the whole reason this code can handle whatever weird case-folding rules an IRC server has. For comparison, both lines below are wrong and can make you miss messages on some servers: πŸ’€

          return unless $hints->{prefix_name} eq $TARGET;   # don't do this

          return unless lc $hints->{prefix_name} eq lc $TARGET;  # don't do this

The first one doesn't case-fold at all, so it breaks on User vs user. The second tries to fix that, but it ignores the odd case-folding logic most IRC servers use, where the characters [\] are "uppercase" versions of {|}. The casefold_name method in Protocol::IRC is the server-aware alternative to lc(), and it handles this. A correct version could look like: 🌸

          return unless $irc->casefold_name( $hints->{prefix_name} ) eq
                        $irc->casefold_name( $TARGET );

But because this pattern is so common, the hints hash already gives you folded strings for any name or nick fields it finds. And since the folded version of the target name won't change after startup, we can store it once and skip recalculating it on every event: πŸ’–

          await $irc->login(
             host => $SERVER,
             nick => $NICK,
          );

          my $target_folded = $irc->casefold_name( $TARGET );

          $irc->configure(
             on_message_PRIVMSG => sub {
                my ( undef, $message, $hints ) = @_;
                return unless $hints->{prefix_nick_folded} eq $target_folded;

                print "The user said: $hints->{text}\n";
             }
          );

🌟 "PRIVMSG" vs "text" and CTCPs 🌟

This example used the basic on_message_PRIVMSG event. A better version would use on_message_text instead. That's a synthesized event made when either PRIVMSG or NOTICE comes in, and it handles stuff like CTCP parsing for you, so your code doesn't have to. For example, the plain PRIVMSG event gets super confused by an incoming CTCP ACTION, like the ones most IRC clients make with the /me command. Instead, we can catch that with a handler made specifically for CTCP ACTION: πŸ”₯

          $irc->configure(
             on_message_text => sub {
                my ( undef, $message, $hints ) = @_;
                return unless $hints->{prefix_nick_folded} eq $target_folded;

                print "The user said: $hints->{text}\n";
             },
             on_message_ctcp_ACTION => sub {
                my ( undef, $message, $hints ) = @_;
                return unless $hints->{prefix_nick_folded} eq $target_folded;

                print "The user acted: $hints->{ctcp_args}\n";
             },
          );

That second handler fires when a PRIVMSG contains a CTCP ACTION. The first one only fires for a plain PRIVMSG that doesn't have a CTCP subcommand. πŸ’…


πŸŽ€ TODO πŸŽ€

Encodings


πŸ‘‘ AUTHOR πŸ‘‘

Paul Evans <leonerd@leonerd.org.uk> πŸ’œ


And that's the page, bestie. It's perl v5.32.1, dated 2021-12-29, and filed under Net::A...duction(3pm). Slay, read, and go write some IRC code. πŸ’–βœ¨πŸ«Ά

πŸ“¦ where this page comes from