File Coverage

blib/lib/Circle/Net/IRC/Channel.pm
Criterion Covered Total %
statement 65 473 13.7
branch 0 72 0.0
condition 0 22 0.0
subroutine 22 79 27.8
pod 0 44 0.0
total 87 690 12.6


line stmt bran cond sub pod time code
1             # You may distribute under the terms of the GNU General Public License
2             #
3             # (C) Paul Evans, 2008-2017 -- leonerd@leonerd.org.uk
4              
5             package Circle::Net::IRC::Channel;
6              
7 4     4   24 use strict;
  4         6  
  4         93  
8 4     4   17 use warnings;
  4         7  
  4         72  
9 4     4   59 use 5.010; # //
  4         12  
10 4     4   17 use base qw( Circle::Net::IRC::Target );
  4         7  
  4         1782  
11              
12             our $VERSION = '0.173320';
13              
14 4     4   23 use Carp;
  4         9  
  4         180  
15              
16 4     4   20 use Circle::TaggedString;
  4         6  
  4         62  
17              
18 4     4   16 use Circle::Widget::Box;
  4         7  
  4         62  
19 4     4   16 use Circle::Widget::Entry;
  4         14  
  4         57  
20 4     4   1453 use Circle::Widget::Label;
  4         9  
  4         101  
21              
22 4     4   21 use POSIX qw( strftime );
  4         7  
  4         30  
23              
24             sub init_prop_occupant_summary
25             {
26 0     0 0   return { total => 0 };
27             }
28              
29             sub on_connected
30             {
31 0     0 0   my $self = shift;
32 0           $self->SUPER::on_connected;
33              
34 0 0         if( $self->{autojoin} ) {
35 0     0     $self->join( on_joined => sub { } );
36             }
37             }
38              
39             sub join
40             {
41 0     0 0   my $self = shift;
42 0           my %args = @_;
43              
44 0           my $on_joined = $args{on_joined};
45 0 0         ref $on_joined eq "CODE" or croak "Expected 'on_joined' as CODE ref";
46              
47 0   0       my $key = $args{key} // $self->{key};
48              
49 0           my $net = $self->{net};
50 0           $net->do_join( $self->get_prop_name, $key );
51              
52 0           $self->{on_joined} = $on_joined;
53 0           $self->{on_join_error} = $args{on_join_error};
54             }
55              
56             sub invite
57             {
58 0     0 0   my $self = shift;
59 0           my ( $nick ) = @_;
60              
61 0           my $irc = $self->{irc};
62             # INVITE user #channel
63 0           $irc->send_message( "INVITE", undef, $nick, $self->get_prop_name );
64             }
65              
66             sub kick
67             {
68 0     0 0   my $self = shift;
69 0           my ( $nick, $message ) = @_;
70              
71 0           my $irc = $self->{irc};
72 0           $irc->send_message( "KICK", undef, $self->get_prop_name, $nick, $message );
73             }
74              
75             sub mode
76             {
77 0     0 0   my $self = shift;
78 0           my ( $modestr, @args ) = @_;
79              
80 0           my $irc = $self->{irc};
81 0           $irc->send_message( "MODE", undef, $self->get_prop_name, $modestr, @args );
82             }
83              
84             sub method_mode
85             {
86 0     0 0   my $self = shift; my $ctx = shift;
  0            
87 0           my ( $modestr, $argsarray ) = @_;
88 0           $self->mode( $modestr, @$argsarray );
89             }
90              
91             sub part
92             {
93 0     0 0   my $self = shift;
94 0           my %args = @_;
95              
96 0           my $on_parted = $args{on_parted};
97 0 0         ref $on_parted eq "CODE" or croak "Expected 'on_parted' as CODE ref";
98              
99 0           my $irc = $self->{irc};
100 0 0         $irc->send_message( "PART", undef, $self->get_prop_name, defined $args{message} ? $args{message} : ( "" ) );
101              
102 0           $self->{on_parted} = $on_parted;
103             }
104              
105             sub topic
106             {
107 0     0 0   my $self = shift;
108 0           my ( $topic ) = @_;
109              
110 0           my $irc = $self->{irc};
111 0           $irc->send_message( "TOPIC", undef, $self->get_prop_name, $topic );
112             }
113              
114             sub method_topic
115             {
116 0     0 0   my $self = shift; my $ctx = shift;
  0            
117 0           $self->topic( @_ );
118             }
119              
120             sub user_leave
121             {
122 0     0 0   my $self = shift;
123 0           my ( $nick_folded ) = @_;
124              
125 0           $self->del_prop_occupants( $nick_folded );
126 0           $self->post_update_occupants;
127             }
128              
129             sub gen_modestr
130             {
131 0     0 0   my $self = shift;
132              
133             # This is a dynamic property
134              
135 0           my $mode = $self->get_prop_mode;
136              
137             # Order the mode as the server declares
138              
139 0           my $irc = $self->{irc};
140 0           my $channelmodes = $irc->server_info( "channelmodes" );
141              
142 0           my @modes = sort { index( $channelmodes, $a ) <=> index( $channelmodes, $b ) } keys %$mode;
  0            
143              
144 0           my $str = "+";
145 0           my @args;
146              
147 0           foreach my $modechar ( @modes ) {
148 0           $str .= $modechar;
149 0 0         push @args, $mode->{$modechar} if length $mode->{$modechar};
150             }
151              
152 0           return CORE::join( " ", $str, @args );
153             }
154              
155             sub apply_modes
156             {
157 0     0 0   my $self = shift;
158 0           my ( $modes ) = @_;
159              
160 0           my @mode_added;
161             my @mode_deleted;
162              
163 0           my $irc = $self->{irc};
164 0           my $PREFIX_FLAGS = $irc->isupport( "prefix_flags" );
165              
166 0           foreach my $m ( @$modes ) {
167 0           my ( $type, $sense, $mode ) = @{$m}{qw( type sense mode )};
  0            
168              
169 0 0         my $pm = $sense > 0 ? "+" :
    0          
170             $sense < 0 ? "-" :
171             "";
172              
173 0 0         if( !defined $type ) {
    0          
    0          
    0          
    0          
174 0           print STDERR "TODO: Undefined type for chanmode $mode\n";
175             }
176             elsif( $type eq 'list' ) {
177 0           print STDERR "TODO: A list chanmode $pm$mode $m->{value}\n";
178             }
179             elsif( $type eq 'occupant' ) {
180 0           my $flag = $m->{flag};
181 0           my $nick_folded = $m->{nick_folded};
182              
183 0           my $occupant = $self->get_prop_occupants->{$nick_folded};
184              
185 0 0         if( $sense > 0 ) {
186 0           my $flags = $occupant->{flag} . $flag;
187             # Now sort by PREFIX_FLAGS order
188 0           $flags = CORE::join( "", sort { index( $PREFIX_FLAGS, $a ) <=> index( $PREFIX_FLAGS, $b ) } split( m//, $flags ) );
  0            
189 0           $occupant->{flag} = $flags;
190             }
191             else {
192 0           $occupant->{flag} =~ s/\Q$flag//g;
193             }
194              
195             # We're not adding it, we're changing it
196 0           $self->add_prop_occupants( $nick_folded => $occupant );
197 0           $self->post_update_occupants;
198             }
199             elsif( $type eq 'value' ) {
200 0 0         if( $sense > 0 ) {
201 0           push @mode_added, [ $mode, $m->{value} ];
202             }
203             else {
204 0           push @mode_deleted, $mode;
205             }
206             }
207             elsif( $type eq 'bool' ) {
208 0 0         if( $sense > 0 ) {
209 0           push @mode_added, [ $mode, "" ];
210             }
211             else {
212 0           push @mode_deleted, $mode;
213             }
214             }
215             }
216              
217 0 0         if( @mode_added ) {
218             # TODO: Allow CHANGE_ADD messages to add multiple key/value pairs
219 0           foreach my $m ( @mode_added ) {
220 0           $self->add_prop_mode( $m->[0] => $m->[1] );
221             }
222             }
223              
224 0 0         if( @mode_deleted ) {
225 0           $self->del_prop_mode( $_ ) for @mode_deleted;
226             }
227              
228 0 0 0       if( @mode_added or @mode_deleted or !defined $self->get_prop_modestr ) {
      0        
229 0           $self->set_prop_modestr( $self->gen_modestr );
230             }
231             }
232              
233             sub post_update_occupants
234             {
235 0     0 0   my $self = shift;
236              
237 0           my $irc = $self->{irc};
238              
239 0           my %count = map { $_ => 0 } "total", "", split( m//, $irc->isupport( "prefix_flags" ) );
  0            
240              
241 0           my $myflag;
242              
243 0           foreach my $occ ( values %{ $self->get_prop_occupants } ) {
  0            
244 0 0         unless( defined $occ->{nick} ) {
245 0           warn "Have an undefined nick in $occ in $self\n";
246 0           next;
247             }
248 0 0         unless( defined $occ->{flag} ) {
249 0           warn "Have an undefined flag for nick $occ->{nick} in $occ in $self\n";
250 0           next;
251             }
252              
253 0 0         my $flag = $occ->{flag} =~ m/^(.)/ ? $1 : "";
254              
255 0           $count{total}++;
256 0           $count{$flag}++;
257              
258 0 0         $myflag = $flag if $irc->is_nick_me( $occ->{nick} );
259             }
260              
261 0           $self->set_prop_occupant_summary( \%count );
262              
263             # Efficient application of property change
264 0           my $old_myflag = $self->get_prop_my_flag;
265              
266 0 0 0       $self->set_prop_my_flag( $myflag ) if !defined $old_myflag or $old_myflag ne $myflag;
267             }
268              
269             sub on_message_JOIN
270             {
271 0     0 0   my $self = shift;
272 0           my ( $message, $hints ) = @_;
273              
274 0           my $nick = $hints->{prefix_nick};
275              
276 0           my $userhost = "$hints->{prefix_user}\@$hints->{prefix_host}";
277              
278 0 0         if( $hints->{prefix_is_me} ) {
279 0           $self->{on_joined}->( $self );
280              
281 0           $self->fire_event( "self_joined" );
282 0           $self->push_displayevent( "irc.join", { channel => $self->get_prop_name, nick => $nick, userhost => $userhost } );
283 0           $self->bump_level( 1 );
284              
285             # Request the initial mode
286 0           my $irc = $self->{irc};
287 0           $irc->send_message( "MODE", undef, $self->get_prop_name );
288             }
289             else {
290 0           $self->fire_event( "join", $nick );
291 0           $self->push_displayevent( "irc.join", { channel => $self->get_prop_name, nick => $nick, userhost => $userhost } );
292 0           $self->bump_level( 1 );
293              
294 0           my $nick_folded = $hints->{prefix_nick_folded};
295 0           my $newocc = { nick => $nick, flag => "" };
296              
297 0           $self->add_prop_occupants( $nick_folded => $newocc );
298 0           $self->post_update_occupants;
299             }
300              
301 0           return 1;
302             }
303              
304             sub on_message_KICK
305             {
306 0     0 0   my $self = shift;
307 0           my ( $message, $hints ) = @_;
308              
309 0           my $kicker = $hints->{kicker_nick};
310 0           my $kicked = $hints->{kicked_nick};
311 0           my $kickmsg = $hints->{text};
312              
313 0 0         defined $kickmsg or $kickmsg = "";
314              
315 0           my $net = $self->{net};
316 0           my $kickmsg_formatted = $net->format_text( $kickmsg );
317              
318 0           my $irc = $self->{irc};
319 0 0         if( $irc->is_nick_me( $kicked ) ) {
320 0           $self->fire_event( "self_parted" );
321 0           $self->push_displayevent( "irc.kick", { channel => $self->get_prop_name, kicker => $kicker, kicked => $kicked, kickmsg => $kickmsg_formatted } );
322 0           $self->bump_level( 1 );
323             }
324             else {
325 0           $self->fire_event( "kick", $kicker, $kicked, $kickmsg );
326 0           $self->push_displayevent( "irc.kick", { channel => $self->get_prop_name, kicker => $kicker, kicked => $kicked, kickmsg => $kickmsg_formatted } );
327 0           $self->bump_level( 1 );
328              
329 0           $self->user_leave( $hints->{kicked_nick_folded} );
330             }
331              
332 0           return 1;
333             }
334              
335             sub on_message_MODE
336             {
337 0     0 0   my $self = shift;
338 0           my ( $message, $hints ) = @_;
339              
340 0           my $modes = $hints->{modes};
341              
342 0           my $nick;
343             my $userhost;
344              
345 0 0         if( defined $hints->{prefix_nick} ) {
346 0           $nick = $hints->{prefix_nick};
347 0           $userhost = "$hints->{prefix_user}\@$hints->{prefix_host}";
348             }
349             else {
350 0           $nick = $userhost = $hints->{prefix_host};
351             }
352              
353 0           $self->apply_modes( $hints->{modes} );
354              
355 0           my $modestr = CORE::join( " ", $hints->{modechars}, @{ $hints->{modeargs} } );
  0            
356              
357             # 'nick' for legacy purposes, 'moder' for new
358 0           $self->push_displayevent( "irc.mode", {
359             channel => $self->get_prop_name,
360             nick => $nick, moder => $nick,
361             userhost => $userhost,
362             mode => $modestr,
363             } );
364 0           $self->bump_level( 1 );
365              
366 0           return 1;
367             }
368              
369             sub on_message_NICK
370             {
371 0     0 0   my $self = shift;
372 0           my ( $message, $hints ) = @_;
373              
374 0           my $oldnick_folded = $hints->{old_nick_folded};
375              
376 0 0         return 0 unless my $occ = $self->get_prop_occupants->{$oldnick_folded};
377              
378 0           my $oldnick = $hints->{old_nick};
379 0           my $newnick = $hints->{new_nick};
380              
381 0           $self->push_displayevent( "irc.nick", { channel => $self->get_prop_name, oldnick => $oldnick, newnick => $newnick } );
382 0           $self->bump_level( 1 );
383              
384 0           my $newnick_folded = $hints->{new_nick_folded};
385              
386 0           $self->del_prop_occupants( $oldnick_folded );
387              
388 0           $occ->{nick} = $newnick;
389 0           $self->add_prop_occupants( $newnick_folded => $occ );
390              
391 0           $self->post_update_occupants;
392              
393 0           return 1;
394             }
395              
396             sub on_message_PART
397             {
398 0     0 0   my $self = shift;
399 0           my ( $message, $hints ) = @_;
400              
401 0           my $nick = $hints->{prefix_nick};
402 0           my $partmsg = $hints->{text};
403              
404 0 0         defined $partmsg or $partmsg = "";
405              
406 0           my $net = $self->{net};
407 0           my $partmsg_formatted = $net->format_text( $partmsg );
408              
409 0           my $userhost = "$hints->{prefix_user}\@$hints->{prefix_host}";
410              
411 0 0         if( $hints->{prefix_is_me} ) {
412 0           $self->fire_event( "self_parted" );
413 0           $self->push_displayevent( "irc.part", { channel => $self->get_prop_name, nick => $nick, userhost => $userhost, partmsg => $partmsg_formatted } );
414 0           $self->bump_level( 1 );
415              
416 0           $self->{on_parted}->( $self );
417             }
418             else {
419 0           $self->fire_event( "part", $nick, $partmsg );
420 0           $self->push_displayevent( "irc.part", { channel => $self->get_prop_name, nick => $nick, userhost => $userhost, partmsg => $partmsg_formatted } );
421 0           $self->bump_level( 1 );
422              
423 0           $self->user_leave( $hints->{prefix_nick_folded} );
424             }
425              
426 0           return 1;
427             }
428              
429             sub on_message_QUIT
430             {
431 0     0 0   my $self = shift;
432 0           my ( $message, $hints ) = @_;
433              
434 0           my $nick_folded = $hints->{prefix_nick_folded};
435              
436 0 0         return 0 unless $self->get_prop_occupants->{$nick_folded};
437              
438 0           my $nick = $hints->{prefix_nick};
439 0           my $quitmsg = $hints->{text};
440              
441 0 0         defined $quitmsg or $quitmsg = "";
442              
443 0           my $net = $self->{net};
444 0           my $quitmsg_formatted = $net->format_text( $quitmsg );
445              
446 0           my $userhost = "$hints->{prefix_user}\@$hints->{prefix_host}";
447              
448 0           $self->push_displayevent( "irc.quit", { channel => $self->get_prop_name, nick => $nick, userhost => $userhost, quitmsg => $quitmsg_formatted } );
449 0           $self->bump_level( 1 );
450              
451 0           $self->user_leave( $nick_folded );
452              
453 0           return 1;
454             }
455              
456             sub on_message_TOPIC
457             {
458 0     0 0   my $self = shift;
459 0           my ( $message, $hints ) = @_;
460              
461 0           my $topic = $hints->{text};
462              
463 0           $self->set_prop_topic( $topic );
464              
465 0           my $nick = $hints->{prefix_name};
466              
467 0           my $userhost = "$hints->{prefix_user}\@$hints->{prefix_host}";
468              
469 0           $self->fire_event( "topic", $nick, $topic );
470 0           $self->push_displayevent( "irc.topic", { channel => $self->get_prop_name, nick => $nick, userhost => $userhost, topic => $topic } );
471 0           $self->bump_level( 1 );
472              
473 0           return 1;
474             }
475              
476             sub on_message_RPL_CHANNELMODEIS
477             {
478 0     0 0   my $self = shift;
479 0           my ( $message, $hints ) = @_;
480              
481 0           $self->apply_modes( $hints->{modes} );
482              
483 0           my $modestr = CORE::join( " ", $hints->{modechars}, @{ $hints->{modeargs} } );
  0            
484              
485 0           $self->push_displayevent( "irc.mode_is", { channel => $self->get_prop_name, mode => $modestr } );
486 0           $self->bump_level( 1 );
487              
488 0           return 1;
489             }
490              
491             sub on_message_RPL_NOTOPIC
492             {
493 0     0 0   my $self = shift;
494 0           my ( $message, $hints ) = @_;
495              
496 0           $self->set_prop_topic( "" );
497              
498 0           return 1;
499             }
500              
501             sub on_message_RPL_TOPIC
502             {
503 0     0 0   my $self = shift;
504 0           my ( $message, $hints ) = @_;
505              
506 0           my $topic = $hints->{text};
507              
508 0           $self->set_prop_topic( $topic );
509              
510 0           $self->fire_event( "topic", undef, $topic );
511 0           $self->push_displayevent( "irc.topic_is", { channel => $self->get_prop_name, topic => $topic } );
512 0           $self->bump_level( 1 );
513              
514 0           return 1;
515             }
516              
517             sub on_message_RPL_TOPICWHOTIME
518             {
519 0     0 0   my $self = shift;
520 0           my ( $message, $hints ) = @_;
521              
522 0           my $timestr = strftime "%Y/%m/%d %H:%M:%S", localtime $hints->{timestamp};
523              
524 0           $self->push_displayevent( "irc.topic_by", { channel => $self->get_prop_name, topic_by => $hints->{topic_nick}, timestamp => $timestr } );
525 0           $self->bump_level( 1 );
526              
527 0           return 1;
528             }
529              
530             sub on_message_RPL_CHANNEL_URL
531             {
532 0     0 0   my $self = shift;
533 0           my ( $message, $hints ) = @_;
534              
535 0           $self->push_displayevent( "irc.text", { server => $hints->{prefix_host}, text => "URL: $hints->{text}" } );
536 0           $self->bump_level( 1 );
537              
538 0           return 1;
539             }
540              
541             sub on_message_RPL_CHANNELCREATED
542             {
543 0     0 0   my $self = shift;
544 0           my ( $message, $hints ) = @_;
545              
546 0           my $timestr = strftime "%Y/%m/%d %H:%M:%S", localtime $hints->{timestamp};
547              
548 0           $self->push_displayevent( "irc.text", { server => $hints->{prefix_host}, text => "Channel created $timestr" } );
549 0           $self->bump_level( 1 );
550              
551 0           return 1;
552             }
553              
554             sub on_message_names
555             {
556 0     0 0   my $self = shift;
557 0           my ( $message, $hints ) = @_;
558              
559 0           $self->set_prop_occupants( $hints->{names} );
560 0           $self->post_update_occupants;
561              
562 0           return 1;
563             }
564              
565             sub command_part
566             : Command_description("Part the channel")
567             : Command_arg('message?', eatall => 1)
568             {
569 0     0 0 0 my $self = shift;
570 0         0 my ( $message, $cinv ) = @_;
571              
572             $self->part(
573             message => $message,
574              
575             on_parted => sub {
576 0     0   0 $cinv->respond( "Parted", level => 1 );
577 0         0 $self->destroy;
578             },
579             on_part_error => sub {
580 0     0   0 $cinv->responderr( "Cannot part - $_[0]", level => 3 );
581             },
582 0         0 );
583              
584 0         0 return;
585 4     4   11128 }
  4         9  
  4         31  
586              
587             sub command_mode
588             : Command_description("Change a MODE")
589             : Command_arg('mode')
590             : Command_arg('args', collect => 1)
591             {
592 0     0 0 0 my $self = shift;
593 0         0 my ( $mode, $args ) = @_;
594              
595 0         0 $self->mode( $mode, @$args );
596              
597 0         0 return;
598 4     4   915 }
  4         7  
  4         13  
599              
600             sub command_topic
601             : Command_description("Change the TOPIC")
602             : Command_arg('topic?', eatall => 1)
603             {
604 0     0 0 0 my $self = shift;
605 0         0 my ( $topic ) = @_;
606              
607 0 0       0 if( length $topic ) {
608 0         0 $self->topic( $topic );
609             }
610             else {
611 0         0 $self->push_displayevent( "irc.topic_is", { channel => $self->get_prop_name, topic => $self->get_prop_topic } );
612             }
613              
614 0         0 return;
615 4     4   904 }
  4         6  
  4         15  
616              
617             sub command_names
618             : Command_description("Print a list of users in the channel")
619             : Command_opt('flat=+', desc => "all types of users in one flat list")
620             {
621 0     0 0 0 my $self = shift;
622 0         0 my ( $opts, $cinv ) = @_;
623              
624 0         0 my $occ = $self->get_prop_occupants;
625              
626 0 0       0 if( $opts->{flat} ) {
627 0         0 my @names = map { "$occ->{$_}{flag}$occ->{$_}{nick}" } sort keys %$occ;
  0         0  
628              
629 0         0 $cinv->respond( "Names: " . CORE::join( " ", @names ) );
630 0         0 return;
631             }
632              
633             # Split into groups per flag
634 0         0 my %occgroups;
635 0         0 for my $nick_folded ( keys %$occ ) {
636 0         0 my $flag = substr( $occ->{$nick_folded}{flag}, 0, 1 ); # In case user has several
637 0         0 push @{ $occgroups{ $flag } }, $nick_folded;
  0         0  
638             }
639              
640             # TODO: Ought to obtain this from somewhere - NaIRC maybe?
641 0         0 my %flag_to_desc = (
642             '~' => "Founder",
643             '&' => "Admin",
644             '@' => "Operator",
645             '%' => "Halfop",
646             '+' => "Voice",
647             '' => "User",
648             );
649              
650 0         0 my $irc = $self->{irc};
651 0         0 foreach my $flag ( sort { $irc->cmp_prefix_flags( $b, $a ) } keys %occgroups ) {
  0         0  
652 0         0 my @names = map { "$flag$occ->{$_}{nick}" } sort @{ $occgroups{$flag} };
  0         0  
  0         0  
653              
654 0         0 my $text = Circle::TaggedString->new( $flag_to_desc{$flag} . ": " );
655 0         0 $text->append_tagged( CORE::join( " ", @names ), indent => 1 );
656              
657 0         0 $cinv->respond( $text );
658             }
659              
660 0         0 return;
661 4     4   1486 }
  4         9  
  4         30  
662              
663             sub command_op
664             : Command_description("Give channel operator status to users")
665             : Command_arg('users', collect => 1)
666             {
667 0     0 0 0 my $self = shift;
668 0         0 my ( $users ) = @_;
669              
670 0         0 my @users = @$users;
671 0         0 $self->mode( "+".("o"x@users), @users );
672              
673 0         0 return;
674 4     4   829 }
  4         9  
  4         12  
675              
676             sub command_deop
677             : Command_description("Remove channel operator status from users")
678             : Command_arg('users', collect => 1)
679             {
680 0     0 0 0 my $self = shift;
681 0         0 my ( $users ) = @_;
682              
683 0         0 my @users = @$users;
684 0         0 $self->mode( "-".("o"x@users), @users );
685              
686 0         0 return;
687 4     4   658 }
  4         8  
  4         13  
688              
689             sub command_halfop
690             : Command_description("Give channel half-operator status to users")
691             : Command_arg('users', collect => 1)
692             {
693 0     0 0 0 my $self = shift;
694 0         0 my ( $users ) = @_;
695              
696 0         0 my @users = @$users;
697 0         0 $self->mode( "+".("h"x@users), @users );
698              
699 0         0 return;
700 4     4   729 }
  4         15  
  4         15  
701              
702             sub command_dehalfop
703             : Command_description("Remove channel half-operator status from users")
704             : Command_arg('users', collect => 1)
705             {
706 0     0 0 0 my $self = shift;
707 0         0 my ( $users ) = @_;
708              
709 0         0 my @users = @$users;
710 0         0 $self->mode( "-".("h"x@users), @users );
711              
712 0         0 return;
713 4     4   723 }
  4         6  
  4         15  
714              
715             sub command_voice
716             : Command_description("Give channel voice status to users")
717             : Command_arg('users', collect => 1)
718             {
719 0     0 0 0 my $self = shift;
720 0         0 my ( $users ) = @_;
721              
722 0         0 my @users = @$users;
723 0         0 $self->mode( "+".("v"x@users), @users );
724              
725 0         0 return;
726 4     4   674 }
  4         8  
  4         16  
727              
728             sub command_devoice
729             : Command_description("Remove channel voice status from users")
730             : Command_arg('users', collect => 1)
731             {
732 0     0 0 0 my $self = shift;
733 0         0 my ( $users ) = @_;
734              
735 0         0 my @users = @$users;
736 0         0 $self->mode( "-".("v"x@users), @users );
737              
738 0         0 return;
739 4     4   816 }
  4         9  
  4         15  
740              
741             sub command_invite
742             : Command_description("Invite a new user to the channel")
743             : Command_arg('user')
744             {
745 0     0 0 0 my $self = shift;
746 0         0 my ( $nick ) = @_;
747              
748 0         0 $self->invite( $nick );
749              
750 0         0 return;
751 4     4   711 }
  4         8  
  4         15  
752              
753             sub command_kick
754             : Command_description("Kick a user from the channel")
755             : Command_arg('user')
756             : Command_arg('message?', eatall => 1 )
757             {
758 0     0 0   my $self = shift;
759 0           my ( $nick, $message ) = @_;
760              
761 0 0         $message = "" if !defined $message;
762              
763 0           $self->kick( $nick, $message );
764              
765 0           return;
766 4     4   797 }
  4         9  
  4         13  
767              
768             ###
769             # Widget tree
770             ###
771              
772             sub get_widget_statusbar
773             {
774 0     0 0   my $self = shift;
775              
776 0           my $registry = $self->{registry};
777 0           my $net = $self->{net};
778              
779 0           my $statusbar = $registry->construct(
780             "Circle::Widget::Box",
781             classes => [qw( status )],
782             orientation => "horizontal",
783             );
784              
785 0           $statusbar->add( $net->get_widget_netname );
786              
787 0           my $nicklabel = $registry->construct(
788             "Circle::Widget::Label",
789             classes => [qw( nick )],
790             );
791              
792             # TODO: This is hideous...
793 0   0       my $nick = $net->get_prop_nick || $net->{configured_nick};
794 0           my $my_flag = "";
795 0     0     my $updatenicklabel = sub { $nicklabel->set_prop_text( $my_flag . $nick ) };
  0            
796             $net->watch_property( "nick",
797 0     0     on_set => sub { $nick = $_[1]; goto &$updatenicklabel }
  0            
798 0           );
799             $self->watch_property( "my_flag",
800 0     0     on_set => sub { $my_flag = $_[1]; goto &$updatenicklabel }
  0            
801 0           );
802 0           $updatenicklabel->();
803              
804 0           $statusbar->add( $nicklabel );
805              
806 0           my $modestrlabel = $registry->construct(
807             "Circle::Widget::Label",
808             classes => [qw( mode )],
809             );
810             $self->watch_property( "modestr",
811 0   0 0     on_updated => sub { $modestrlabel->set_prop_text( $_[1] || "" ) }
812 0           );
813              
814 0           $statusbar->add( $modestrlabel );
815              
816 0           $statusbar->add_spacer( expand => 1 );
817              
818 0           my $countlabel = $registry->construct(
819             "Circle::Widget::Label",
820             classes => [qw( occupants )],
821             );
822             $self->watch_property( "occupant_summary",
823             on_updated => sub {
824 0     0     my ( $self, $summary ) = @_;
825              
826 0           my $irc = $self->{irc};
827 0   0       my $PREFIX_FLAGS = $irc->isupport( "prefix_flags" ) || "";
828              
829             my $str = "$summary->{total} users [" .
830 0 0         CORE::join( " ", map { "$_$summary->{$_}" } grep { $summary->{$_}||0 > 0 } split( m//, $PREFIX_FLAGS ), "" ) .
  0            
  0            
831             "]";
832              
833 0           $countlabel->set_prop_text( $str );
834             }
835 0           );
836              
837 0           $statusbar->add( $countlabel );
838              
839 0           return $statusbar;
840             }
841              
842             sub get_widget_occupants_completegroup
843             {
844 0     0 0   my $self = shift;
845              
846 0   0       return $self->{widget_occupants_completegroup} ||= do {
847 0           my $registry = $self->{registry};
848              
849 0           my $widget = $registry->construct(
850             "Circle::Widget::Entry::CompleteGroup",
851             suffix_sol => ": ",
852             );
853              
854 0           my %key_to_nick;
855             $self->watch_property( "occupants",
856             on_set => sub {
857 0     0     my ( undef, $occupants ) = @_;
858 0           $widget->set( map { $key_to_nick{$_} = $occupants->{$_}{nick} } keys %$occupants );
  0            
859             },
860             on_add => sub {
861 0     0     my ( undef, $key, $occ ) = @_;
862 0           $widget->add( $key_to_nick{$key} = $occ->{nick} );
863             },
864             on_del => sub {
865 0     0     my ( undef, $key ) = @_;
866 0           $widget->remove( delete $key_to_nick{$key} );
867             },
868 0           );
869              
870 0           my $occupants = $self->get_prop_occupants;
871 0           $widget->set( map { $key_to_nick{$_} = $occupants->{$_}{nick} } keys %$occupants );
  0            
872              
873 0           $widget;
874             };
875             }
876              
877             sub get_widget_commandentry
878             {
879 0     0 0   my $self = shift;
880 0           my $widget = $self->SUPER::get_widget_commandentry;
881              
882 0           $widget->add_prop_completions( $self->get_widget_occupants_completegroup );
883              
884 0           return $widget;
885             }
886              
887             sub make_widget_pre_scroller
888             {
889 0     0 0   my $self = shift;
890 0           my ( $box ) = @_;
891              
892 0           my $registry = $self->{registry};
893              
894             my $topicentry = $registry->construct(
895             "Circle::Widget::Entry",
896             classes => [qw( topic )],
897 0     0     on_enter => sub { $self->topic( $_[0] ) },
898 0           );
899             $self->watch_property( "topic",
900 0     0     on_updated => sub { $topicentry->set_prop_text( $_[1] ) }
901 0           );
902              
903 0           $box->add( $topicentry );
904             }
905              
906             0x55AA;