File: Introduction.pod

package info (click to toggle)
libnet-async-irc-perl 0.12-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 200 kB
  • sloc: perl: 970; sh: 2; makefile: 2
file content (206 lines) | stat: -rw-r--r-- 7,793 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
=head1 NAME

Net::Async::IRC::Introduction - an introduction

=head1 INTRODUCTION

=head2 Hello, World

This first example is the "hello world" of IRC; a script that connects to the
server and immediately sends a hello message to a preconfigured user.

This program starts with the usual boilerplate for any L<IO::Async>-based
program; namely loading the required modules and creating a containing
L<IO::Async::Loop> instance. It then constructs the actual L<Net::Async::IRC>
object and adds it to this containing loop. As these actions are standard to
every program, they won't be repeated in later examples; just presumed to have
already taken place:

   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 this is created, we can move on to the specifics of this example. As it's
a tiny example script, we'll just hard-code the parameters for the message. A
larger program of course would read these from somewhere better - a config
file, commandline arguments, etc...

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

Finally we can 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"
   );

The program calls L<Net::Async::IRC/login>, which connects the client to the
given IRC server and logs in as the given nick. This method returns a
L<Future> instance to represent its eventual completion, giving us an easy way
to sequence further code after it. After login is complete, the next task is
simply to send the message. This is done with the C<PRIVMSG> IRC command as
wrapped by L<Net::Async::IRC/do_PRIVMSG>. This takes the message target name
and text string.

The trailing call to L<Future/get> makes the script stop here waiting for this
chain of futures to actually complete. Without this, the returned future would
simply be lost (as the L<Future/then> method appears in void context), and the
second stage of code within it would probably never get called. In later
examples we'll see other techniques, but for now every constructed future will
simply be forced by calling C<get> on it. If either of these stages fails, it
will cause the C<get> call to throw an exception instead.

Once this is sent, the script terminates, closing its connection to the server.

=head2 Receiving Messages

As a second example, lets now consider also how we handle messages that arrive
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 have used the C<configure> method to attach an event handler to the
C<on_message_PRIVMSG> event. This handler code ignores any messages except
from the user we are interested in, and simply prints the contents of those we
are interested in to the terminal.

Like all IRC message handlers, this one is passed both the plain IRC message
itself (in I<$message> as an instance of L<Protocol::IRC::Message>), and the
hints hash (in I<$hints> as a plain hash reference). While the code could
operate on the message instance itself, this requries knowing that, in this
case, the text field happens to be at C<< $message->arg(1) >>. An easier and
more powerful way to work on incoming messages is to use fields of the hint
hash. In this case, the text comes in a field named simply C<text>. In this
case for C<PRIVMSG> there aren't too many other fields of interest, but for
some message types, especially server numeric replies, the hints hash will
contain lots of additional detail parsed out of the raw message, so it's
always worth looking there first. It's entirely reasonable for a program
never to actually inspect the C<$message> object itself; it is passed only
for completeness, or in case the hints parsing has failed to recognise some
server-extended detail about it and such raw access is required.

Having established this event handler, we can then log in and send a message
to the target user, similar to the first example. Instead of stopping the
script entirely afterwards, we need to ensure that the program keeps running
after this initial start so it can continue to receive messages. To do that we
enter the main L<IO::Async::Loop/run> method, which will wait indefinitely,
processing any events that are received.

=head2 Case-folded Names

The use of the "folded" strings ensures that this code can correctly cope with
any odd case-folding rules the IRC server has. By comparison, both of the
following lines are incorrect, and may cause missed 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 does not case-fold the string at all, so will fail in the case of
C<User> vs C<user>. The second attempts to solve this, but does not take
account of the odd case-folding logic most IRC servers have, in which the
characters C<[\]> are "uppercase" versions of C<{|}>. The
L<Protocol::IRC/casefold_name> method is provided as a server-aware
alternative to C<lc()>, which handles this. A correct implementation could be
written:

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

However, since this is a very common pattern, the hints hash conveniently
supplies already-folded strings for any name or nick fields it finds.
Furthermore, as the case folded version of the target name won't change after
startup, we could store that initially to save re-calculating it at 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";
      }
   );

=head2 C<PRIVMSG> vs C<text> and CTCPs

This example has used the basic C<on_message_PRIVMSG> event. A better version
would be to use C<on_message_text> instead. This is a synthesized event
created on receipt of either C<PRIVMSG> or C<NOTICE>, and itself handles
details like C<CTCP> parsing, freeing the user code from having to handle it).
For example, the plain C<PRIVMSG> event will get quite confused by an incoming
C<CTCP ACTION>, such as is created by most IRC clients by the C</me> command.
Instead, we can handle that by attaching a handler specifically for
C<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";
      },
   );

This second handlers is invoked on receipt of a C<PRIVMSG> containing a
C<CTCP ACTION>. The first is only invoked on receipt of a plain C<PRIVMSG>
that doesn't contain a C<CTCP> subcommand.

=head1 TODO

Encodings

=head1 AUTHOR

Paul Evans <leonerd@leonerd.org.uk>

=cut