File Coverage

blib/lib/App/Alice/IRC.pm
Criterion Covered Total %
statement 242 363 66.6
branch 60 128 46.8
condition 16 34 47.0
subroutine 60 90 66.6
pod 1 54 1.8
total 379 669 56.6


line stmt bran cond sub pod time code
1             package App::Alice::IRC;
2              
3 4     4   22 use AnyEvent;
  4         7  
  4         112  
4 4     4   4044 use AnyEvent::IRC::Client;
  4         147342  
  4         209  
5 4     4   56 use List::Util qw/min first/;
  4         9  
  4         594  
6 4     4   5261 use List::MoreUtils qw/uniq/;
  4         5606  
  4         356  
7 4     4   31 use Digest::MD5 qw/md5_hex/;
  4         10  
  4         201  
8 4     4   23 use Any::Moose;
  4         11  
  4         40  
9 4     4   2298 use utf8;
  4         9  
  4         42  
10 4     4   111 use Encode;
  4         11  
  4         36900  
11              
12             has 'cl' => (
13             is => 'rw',
14             default => sub {AnyEvent::IRC::Client->new},
15             );
16              
17             has 'alias' => (
18             isa => 'Str',
19             is => 'ro',
20             required => 1,
21             );
22              
23             has 'nick_cached' => (
24             isa => 'Str',
25             is => 'rw',
26             lazy => 1,
27             default => sub {
28             my $self = shift;
29             return $self->config->{nick};
30             },
31             );
32              
33             sub config {
34 17     17 0 207 $_[0]->app->config->servers->{$_[0]->alias};
35             }
36              
37             has 'app' => (
38             isa => 'App::Alice',
39             is => 'ro',
40             weak_ref => 1,
41             required => 1,
42             );
43              
44             has 'reconnect_timer' => (
45             is => 'rw'
46             );
47              
48             has [qw/reconnect_count connect_time/] => (
49             is => 'rw',
50             isa => 'Int',
51             default => 0,
52             );
53              
54 1     1 0 8 sub increase_reconnect_count {$_[0]->reconnect_count($_[0]->reconnect_count + 1)}
55 1     1 0 6 sub reset_reconnect_count {$_[0]->reconnect_count(0)}
56              
57             has [qw/is_connected disabled removed/] => (
58             is => 'rw',
59             isa => 'Bool',
60             default => 0,
61             );
62              
63             has _nicks => (
64             is => 'rw',
65             isa => 'ArrayRef[HashRef|Undef]',
66             default => sub {[]},
67             );
68              
69 58     58 0 70 sub nicks {@{$_[0]->_nicks}}
  58         249  
70 0     0 0 0 sub all_nicks {[map {$_->{nick}} @{$_[0]->_nicks}]}
  0         0  
  0         0  
71 9     9 0 14 sub add_nick {push @{$_[0]->_nicks}, $_[1]}
  9         35  
72 8     8 0 19 sub remove_nick {$_[0]->_nicks([grep {$_->{nick} ne $_[1]} $_[0]->nicks])}
  20         69  
73 84     84 0 293 sub get_nick_info {first {$_->{nick} eq $_[1]} $_[0]->nicks}
  40     40   170  
74 23     23 0 69 sub includes_nick {$_[0]->get_nick_info($_[1])}
75 10     10 0 27 sub all_nick_info {$_[0]->nicks}
76 7     7 0 24 sub set_nick_info {$_[0]->remove_nick($_[1]); $_[0]->add_nick($_[2]);}
  7         32  
77 1     1 0 9 sub clear_nicks {$_[0]->_nicks([])}
78              
79             has whois_cbs => (
80             is => 'rw',
81             isa => 'HashRef[CodeRef]',
82             default => sub {{}},
83             );
84              
85             sub add_whois_cb {
86 0     0 0 0 my ($self, $nick, $cb) = @_;
87 0         0 $self->whois_cbs->{$nick} = $cb;
88 0         0 $self->send_srv(WHO => $nick);
89             }
90              
91             sub BUILD {
92 3     3 1 8 my $self = shift;
93 3 50       17 $self->cl->enable_ssl if $self->config->{ssl};
94 3 100       10 $self->disabled(1) unless $self->config->{autoconnect};
95 1     1   5 $self->cl->reg_cb(
96             registered => sub{$self->registered($_)},
97 4     4   17 channel_add => sub{$self->channel_add(@_)},
98 2     2   13 channel_remove => sub{$self->channel_remove(@_)},
99 2     2   10 channel_topic => sub{$self->channel_topic(@_)},
100 4     4   20 join => sub{$self->_join(@_)},
101 2     2   10 part => sub{$self->part(@_)},
102 1     1   6 nick_change => sub{$self->nick_change(@_)},
103 0     0   0 ctcp_action => sub{$self->ctcp_action(@_)},
104 0     0   0 publicmsg => sub{$self->publicmsg(@_)},
105 2     2   11 privatemsg => sub{$self->privatemsg(@_)},
106 1     1   10 connect => sub{$self->connected(@_)},
107 1     1   5 disconnect => sub{$self->disconnected(@_)},
108 0     0   0 irc_001 => sub{$self->log_message($_[1])},
109 6     6   27 irc_352 => sub{$self->irc_352(@_)}, # WHO info
110 0     0   0 irc_366 => sub{$self->irc_366(@_)}, # end of NAMES
111 0     0   0 irc_372 => sub{$self->log_message(mono => 1, $_[1])}, # MOTD info
112 0     0   0 irc_377 => sub{$self->log_message(mono => 1, $_[1])}, # MOTD info
113 0     0   0 irc_378 => sub{$self->log_message(mono => 1, $_[1])}, # MOTD info
114 0     0   0 irc_401 => sub{$self->irc_401(@_)},
115 0     0   0 irc_432 => sub{$self->nick; $self->log_message($_[1])}, # Bad nick
  0         0  
116 0     0   0 irc_433 => sub{$self->nick; $self->log_message($_[1])}, # Bad nick
  0         0  
117 0     0   0 irc_464 => sub{$self->disconnect("bad USER/PASS")},
118 3         168 );
119 3         2031 $self->cl->ctcp_auto_reply ('VERSION', ['VERSION', "alice $App::Alice::VERSION"]);
120 3 100       35 $self->connect unless $self->disabled;
121             }
122              
123             sub send_srv {
124 4     4 0 13 my ($self, $cmd, @params) = @_;
125 4         15 $self->cl->send_srv($cmd => map {encode_utf8($_)} @params);
  4         18  
126             }
127              
128             sub send_raw {
129 1     1 0 2 my ($self, $cmd) = @_;
130 1         8 $self->cl->send_raw(encode_utf8($cmd));
131             }
132              
133             sub broadcast {
134 20     20 0 36 my $self = shift;
135 20         114 $self->app->broadcast(@_);
136             }
137              
138             sub init_shutdown {
139 0     0 0 0 my ($self, $msg) = @_;
140 0         0 $self->disabled(1);
141 0 0       0 if ($self->is_connected) {
142 0         0 $self->disconnect($msg);
143 0         0 return;
144             }
145 0         0 $self->shutdown;
146             }
147              
148             sub shutdown {
149 0     0 0 0 my $self = shift;
150 0         0 $self->cl(undef);
151 0         0 $self->app->remove_irc($self->alias);
152 0 0       0 $self->app->shutdown if !$self->app->ircs;
153             }
154              
155             sub log {
156 7     7 0 18 my $messages = pop;
157 7 100       35 $messages = [ $messages ] unless ref $messages eq "ARRAY";
158              
159 7         19 my ($self, $level, %options) = @_;
160              
161 7         18 my @lines = map {$self->format_info($_, %options)} @$messages;
  9         36  
162 7         28 $self->broadcast(@lines);
163 7         79 $self->app->log($level => "[".$self->alias . "] $_") for @$messages;
164             }
165              
166             sub log_message {
167 0     0 0 0 my $message = pop;
168              
169 0         0 my ($self, %options) = @_;
170 0 0       0 if (@{$message->{params}}) {
  0         0  
171 0         0 $self->log("debug", %options, [ pop @{$message->{params}} ]);
  0         0  
172             }
173             }
174              
175             sub format_info {
176 9     9 0 21 my ($self, $message, %options) = @_;
177 9         62 $self->app->format_info($self->alias, $message, %options);
178             }
179              
180             sub window {
181 5     5 0 11 my ($self, $title) = @_;
182 5         31 return $self->app->find_or_create_window($title, $self);
183             }
184              
185             sub find_window {
186 10     10 0 20 my ($self, $title) = @_;
187 10         57 return $self->app->find_window($title, $self);
188             }
189              
190             sub nick {
191 20     20 0 37 my $self = shift;
192 20         94 my $nick = $self->cl->nick;
193 20 100 66     107 if ($nick and $nick ne "") {
194 18         77 $self->nick_cached($nick);
195 18         105 return $nick;
196             }
197 2   50     38 return $self->nick_cached || "Failure";
198             }
199              
200             sub windows {
201 5     5 0 10 my $self = shift;
202 14 100       90 return grep
203 5         28 {$_->type ne "info" && $_->irc->alias eq $self->alias}
204             $self->app->windows;
205             }
206              
207             sub channels {
208 1     1 0 2 my $self = shift;
209 1         5 return map {$_->title} grep {$_->is_channel} $self->windows;
  1         19  
  1         5  
210             }
211              
212             sub connect {
213 1     1 0 3 my $self = shift;
214              
215 1         4 $self->disabled(0);
216 1         5 $self->increase_reconnect_count;
217              
218 1 50       3 $self->cl->{enable_ssl} = $self->config->{ssl} ? 1 : 0;
219              
220             # some people don't set these, wtf
221 1 50 33     5 if (!$self->config->{host} or !$self->config->{port}) {
222 0         0 $self->log(info => "can't connect: missing either host or port");
223 0         0 return;
224             }
225              
226 1 50       10 $self->reconnect_count > 1 ?
227             $self->log(info => "reconnecting: attempt " . $self->reconnect_count)
228             : $self->log(debug => "connecting");
229              
230 1         9 $self->cl->connect(
231             $self->config->{host}, $self->config->{port}
232             );
233             }
234              
235             sub connected {
236 1     1 0 2 my ($self, $cl, $err) = @_;
237              
238 1 50       4 if (defined $err) {
239 0         0 $self->log(info => "connect error: $err");
240 0         0 $self->reconnect();
241 0         0 return;
242             }
243              
244 1         5 $self->log(info => "connected");
245 1         7 $self->reset_reconnect_count;
246 1         5 $self->connect_time(time);
247 1         6 $self->is_connected(1);
248              
249 1         8 $self->cl->register(
250             $self->nick, $self->config->{username},
251             $self->config->{ircname}, $self->config->{password}
252             );
253              
254 2         7 $self->broadcast({
255             type => "action",
256             event => "connect",
257             session => $self->alias,
258 1         13 windows => [map {$_->serialized} $self->windows],
259             });
260             }
261              
262             sub reconnect {
263 1     1 0 3 my ($self, $time) = @_;
264              
265 1         5 my $interval = time - $self->connect_time;
266              
267 1 50       5 if ($interval < 15) {
268 1         2 $time = 15 - $interval;
269 1         7 $self->log(debug => "last attempt was within 15 seconds, delaying $time seconds")
270             }
271              
272 1 50       5 if (!defined $time) {
273             # increase timer by 15 seconds each time, until it hits 5 minutes
274 0         0 $time = min 60 * 5, 15 * $self->reconnect_count;
275             }
276              
277 1         5 $self->log(debug => "reconnecting in $time seconds");
278             $self->reconnect_timer(
279             AnyEvent->timer(after => $time, cb => sub {
280 0 0   0   0 $self->connect unless $self->is_connected;
281             })
282 1         16 );
283             }
284              
285             sub cancel_reconnect {
286 0     0 0 0 my $self = shift;
287 0         0 $self->reconnect_timer(undef);
288 0         0 $self->reset_reconnect_count;
289             }
290              
291             sub registered {
292 1     1 0 2 my $self = shift;
293 1         2 my @log;
294              
295             $self->cl->enable_ping (300, sub {
296 0     0   0 $self->is_connected(0);
297 0         0 $self->log(debug => "ping timeout");
298 0         0 $self->reconnect(0);
299 1         12 });
300            
301 1         5 for (@{$self->config->{on_connect}}) {
  1         3  
302 1         6 push @log, "sending $_";
303 1         4 $self->send_raw($_);
304             }
305            
306             # merge auto-joined channel list with existing channels
307 1         58 my @channels = uniq @{$self->config->{channels}}, $self->channels;
  1         4  
308            
309 1         4 for (@channels) {
310 2         45 push @log, "joining $_";
311 2         11 $self->send_srv("JOIN", split /\s+/);
312             }
313            
314 1         44 $self->log(debug => \@log);
315             };
316              
317             sub disconnected {
318 1     1 0 2 my ($self, $cl, $reason) = @_;
319 1 50       18 delete $self->{disconnect_timer} if $self->{disconnect_timer};
320            
321 1 50       5 $reason = "" unless $reason;
322 1 50       4 return if $reason eq "reconnect requested.";
323 1         6 $self->log(info => "disconnected: $reason");
324            
325 2         11 $self->broadcast(map {
326 1         7 $_->format_event("disconnect", $self->nick, $reason),
327             } $self->windows);
328            
329 2         9 $self->broadcast({
330             type => "action",
331             event => "disconnect",
332             session => $self->alias,
333 1         10 windows => [map {$_->serialized} $self->windows],
334             });
335            
336 1         11 $self->is_connected(0);
337 1         4 $self->clear_nicks;
338            
339 1 50 33     22 if ($self->app->shutting_down and !$self->app->connected_ircs) {
340 0         0 $self->shutdown;
341 0         0 return;
342             }
343            
344 1 50       10 $self->reconnect(0) unless $self->disabled;
345            
346 1 50       34 if ($self->removed) {
347 0         0 $self->app->remove_irc($self->alias);
348 0         0 undef $self;
349             }
350             }
351              
352             sub disconnect {
353 0     0 0 0 my ($self, $msg) = @_;
354              
355 0         0 $self->disabled(1);
356 0 0       0 if (!$self->app->shutting_down) {
357 0         0 $self->app->remove_window($_) for $self->windows;
358             }
359              
360 0   0     0 $msg ||= $self->app->config->quitmsg;
361 0 0       0 $self->log(debug => "disconnecting: $msg") if $msg;
362 0         0 $self->send_srv(QUIT => $msg);
363             $self->{disconnect_timer} = AnyEvent->timer(
364             after => 1,
365             cb => sub {
366 0     0   0 delete $self->{disconnect_timer};
367 0         0 $self->cl->disconnect($msg);
368             }
369 0         0 );
370             }
371              
372             sub remove {
373 0     0 0 0 my $self = shift;
374 0         0 $self->removed(1);
375 0         0 $self->disconnect;
376             }
377              
378             sub publicmsg {
379 0     0 0 0 my ($self, $cl, $channel, $msg) = @_;
380 0         0 utf8::decode($channel);
381 0 0       0 if (my $window = $self->find_window($channel)) {
382 0         0 my $nick = (split '!', $msg->{prefix})[0];
383 0 0       0 return if $self->app->is_ignore($nick);
384 0         0 my $text = $msg->{params}[1];
385 0         0 utf8::decode($_) for ($text, $nick);
386 0         0 $self->app->store(nick => $nick, channel => $channel, body => $text);
387 0         0 $self->broadcast($window->format_message($nick, $text));
388             }
389             }
390              
391             sub privatemsg {
392 2     2 0 5 my ($self, $cl, $nick, $msg) = @_;
393 2         6 my $text = $msg->{params}[1];
394 2         12 utf8::decode($_) for ($nick, $text);
395 2 50       10 if ($msg->{command} eq "PRIVMSG") {
    0          
396 2         9 my $from = (split /!/, $msg->{prefix})[0];
397 2         6 utf8::decode($from);
398 2 50       14 return if $self->app->is_ignore($from);
399 2         14 my $window = $self->window($from);
400 2         18 $self->app->store(nick => $from, channel => $from, body => $text);
401 2         250 $self->broadcast($window->format_message($from, $text));
402 2 100       9 $self->send_srv(WHO => $from) unless $self->includes_nick($from);
403             }
404             elsif ($msg->{command} eq "NOTICE") {
405 0         0 $self->log(debug => $text);
406             }
407             }
408              
409             sub ctcp_action {
410 0     0 0 0 my ($self, $cl, $nick, $channel, $msg, $type) = @_;
411 0         0 utf8::decode($_) for ($nick, $msg, $channel);
412 0 0       0 return if $self->app->is_ignore($nick);
413 0 0       0 if (my $window = $self->find_window($channel)) {
414 0         0 my $text = "• $msg";
415 0         0 $self->app->store(nick => $nick, channel => $channel, body => $text);
416 0         0 $self->broadcast($window->format_message($nick, $text));
417             }
418             }
419              
420             sub nick_change {
421 1     1 0 4 my ($self, $cl, $old_nick, $new_nick, $is_self) = @_;
422 1         5 utf8::decode($_) for ($old_nick, $new_nick);
423 1 50       5 $self->nick_cached($new_nick) if $is_self;
424 1         6 $self->rename_nick($old_nick, $new_nick);
425 1         6 $self->broadcast(
426 1         6 map {$_->format_event("nick", $old_nick, $new_nick)}
427             $self->nick_windows($new_nick)
428             );
429             }
430              
431             sub _join {
432 4     4   9 my ($self, $cl, $nick, $channel, $is_self) = @_;
433 4         25 utf8::decode($_) for ($nick, $channel);
434 4 100       13 if (!$self->includes_nick($nick)) {
435 2         21 $self->add_nick({nick => $nick, real => "", channels => {$channel => ''}});
436             }
437             else {
438 2         7 $self->get_nick_info($nick)->{channels}{$channel} = '';
439             }
440 4 100       27 if ($is_self) {
    50          
441              
442             # self->window uses find_or_create, so we don't create
443             # duplicate windows here
444 3         10 my $window = $self->window($channel);
445              
446 3         20 $self->broadcast($window->join_action);
447              
448             # client library only sends WHO if the server doesn't
449             # send hostnames with NAMES list (UHNAMES), we to WHO always
450 3 50       31 $self->send_srv("WHO" => $channel) if $cl->isupport("UHNAMES");
451             }
452             elsif (my $window = $self->find_window($channel)) {
453 1         6 $self->send_srv("WHO" => $nick);
454 1         30 $self->broadcast($window->format_event("joined", $nick));
455             }
456             }
457              
458             sub channel_add {
459 4     4 0 11 my ($self, $cl, $msg, $channel, @nicks) = @_;
460 4         23 utf8::decode($_) for (@nicks, $channel);
461 4 50       16 if (my $window = $self->find_window($channel)) {
462 4         10 for (@nicks) {
463 4 50       13 if (!$self->includes_nick($_)) {
464 0         0 $self->add_nick({nick => $_, real => "", channels => {$channel => ''}});
465             }
466             else {
467 4         10 $self->get_nick_info($_)->{channels}{$channel} = '';
468             }
469             }
470             }
471             }
472              
473             sub part {
474 2     2 0 7 my ($self, $cl, $nick, $channel, $is_self, $msg) = @_;
475 2         187 utf8::decode($_) for ($channel, $nick, $msg);
476 2 100 66     17 if ($is_self and my $window = $self->find_window($channel)) {
477 1         7 $self->log(debug => "leaving $channel");
478 1         9 $self->app->close_window($window);
479 1         5 for ($self->all_nick_info) {
480 3 50       20 delete $_->{channels}{$channel} if exists $_->{channels}{$channel};
481             }
482             }
483             }
484              
485             sub channel_remove {
486 2     2 0 7 my ($self, $cl, $msg, $channel, @nicks) = @_;
487 2         14 utf8::decode($_) for ($channel, @nicks);
488            
489 2 100 66     9 return if !@nicks or grep {$_ eq $self->nick} @nicks;
  2         10  
490            
491 1 50       4 if (my $window = $self->find_window($channel)) {
492 1         3 my $body;
493 1 50 33     9 if ($msg->{command} and $msg->{command} eq "PART") {
494 1         3 for (@nicks) {
495 1 50       4 next unless $self->includes_nick($_);
496 0         0 delete $self->get_nick_info($_)->{channels}{$channel};
497 0 0       0 $self->remove_nick($_) unless $self->nick_channels($_);
498             }
499             }
500             else {
501 0         0 $self->remove_nicks(@nicks);
502 0         0 $body = $msg->{params}[0];
503 0         0 utf8::decode($body);
504             }
505 1         4 $self->broadcast(map {$window->format_event("left", $_, $body)} @nicks);
  1         5  
506             }
507             }
508              
509             sub channel_topic {
510 2     2 0 7 my ($self, $cl, $channel, $topic, $nick) = @_;
511 2         13 utf8::decode($_) for ($channel, $nick, $topic);
512 2 50       11 if (my $window = $self->find_window($channel)) {
513 2         31 $window->topic({string => $topic, author => $nick, time => time});
514 2         14 $self->broadcast($window->format_event("topic", $nick, $topic));
515             }
516             }
517              
518             sub channel_nicks {
519 9     9 0 17 my ($self, $channel) = @_;
520 9         31 return [ map {$_->{nick}} grep {exists $_->{channels}{$channel}} $self->all_nick_info ];
  20         184  
  24         56  
521             }
522              
523             sub nick_channels {
524 2     2 0 4 my ($self, $nick) = @_;
525 2         4 my $info = $self->get_nick_info($nick);
526 2 50       9 return keys %{$info->{channels}} if $info->{channels};
  2         10  
527             }
528              
529             sub nick_windows {
530 1     1 0 2 my ($self, $nick) = @_;
531 1 50       6 if ($self->nick_channels($nick)) {
532 1         3 return grep {$_} map {$self->find_window($_)} $self->nick_channels($nick);
  1         4  
  1         4  
533             }
534 0         0 return;
535             }
536              
537             sub irc_352 {
538 6     6 0 10 my ($self, $cl, $msg) = @_;
539            
540             # ignore the first param if it is our own nick, some servers include it
541 6 50       22 shift @{$msg->{params}} if $msg->{params}[0] eq $self->nick;
  6         13  
542 6         11 my ($channel, $user, $ip, $server, $nick, $flags, @real) = @{$msg->{params}};
  6         24  
543 6         14 my $real = join " ", @real;
544 6 50       15 return unless $nick;
545 6 50       29 $real =~ s/^\d // if $real;
546 6         40 utf8::decode($_) for ($channel, $user, $nick, $real);
547 6   50     78 my $info = {
      50        
      50        
      50        
548             IP => $ip || "",
549             user => $user || "",
550             server => $server || "",
551             real => $real || "",
552             channels => {$channel => $flags},
553             nick => $nick,
554             };
555            
556 6 100       18 if ($self->includes_nick($nick)) {
557 4         9 my $prev_info = $self->get_nick_info($nick);
558 4         14 $info->{channels} = {
559 4         17 %{$prev_info->{channels}},
560 4         11 %{$info->{channels}},
561             };
562              
563 4 100       19 if ($info->{real} ne $prev_info->{real}) {
564 1         5 for (grep {$_->previous_nick eq $nick} $self->windows) {
  2         9  
565 0         0 $_->reset_previous_nick;
566             }
567             }
568             }
569            
570 6         30 $self->set_nick_info($nick, $info);
571              
572 6 50       55 if ($self->whois_cbs->{$nick}) {
573 0         0 $self->whois_cbs->{$nick}->();
574 0         0 delete $self->whois_cbs->{$nick};
575             }
576             }
577              
578             sub irc_366 {
579 0     0 0 0 my ($self, $cl, $msg) = @_;
580 0         0 utf8::decode($msg->{params}[1]);
581 0 0       0 if (my $window = $self->find_window($msg->{params}[1])) {
582 0         0 $self->broadcast($window->nicks_action);
583             }
584             }
585              
586             sub irc_401 {
587 0     0 0 0 my ($self, $cl, $msg) = @_;
588 0         0 utf8::decode($msg->{params}[1]);
589 0 0       0 if (my $window = $self->find_window($msg->{params}[1])) {
590 0         0 $self->broadcast($window->format_announcement("No such nick."));
591             }
592             }
593              
594             sub rename_nick {
595 1     1 0 2 my ($self, $nick, $new_nick) = @_;
596 1 50       4 return unless $self->includes_nick($nick);
597 1         4 my $info = $self->get_nick_info($nick);
598 1         4 $info->{nick} = $new_nick;
599 1         5 $self->set_nick_info($new_nick, $info);
600 1         2 $self->remove_nick($nick);
601             }
602              
603             sub remove_nicks {
604 0     0 0 0 my ($self, @nicks) = @_;
605 0         0 $self->_nicks(
606             grep {
607 0         0 my $nick = $_;
608 0 0   0   0 first {$nick eq $_} @nicks ? 0 : 1;
  0         0  
609             } $self->nicks
610             );
611             }
612              
613             sub nick_avatar {
614 2     2 0 5 my ($self, $nick) = @_;
615 2         7 my $info = $self->get_nick_info($nick);
616 2 100 66     19 if ($info and $info->{real}) {
617 1 50       8 if ($info->{real} =~ /([^<\s]+@[^\s>]+\.[^\s>]+)/) {
    50          
618 0         0 my $email = $1;
619 0         0 return "http://www.gravatar.com/avatar/"
620             . md5_hex($email) . "?s=32&r=x";
621             }
622             elsif ($info->{real} =~ /(https?:\/\/\S+(?:jpe?g|png|gif))/) {
623 0         0 return $1;
624             }
625             else {
626 1         7 return undef;
627             }
628             }
629             }
630              
631             sub whois_table {
632 0     0 0   my ($self, $nick) = @_;
633 0           my $info = $self->get_nick_info($nick);
634 0 0         return "No info for user \"$nick\"" if !$info;
635 0           return "real: $info->{real}\nuser: $info->{user}\n" .
636             "host: $info->{IP}\nserver: $info->{server}\nchannels: " .
637 0           join " ", keys %{$info->{channels}};
638             }
639              
640             sub update_realname {
641 0     0 0   my ($self, $realname) = @_;
642 0           my $nick = $self->nick_cached;
643 0           $self->send_srv(REALNAME => $realname);
644 0 0         if (my $info = $self->get_nick_info($nick)) {
645 0           $info->{real} = $realname;
646             }
647 0           for (grep {$_->previous_nick eq $nick} $self->windows) {
  0            
648 0           $_->reset_previous_nick;
649             }
650             }
651              
652             __PACKAGE__->meta->make_immutable;
653             1;