1

I'm exploring the AnyEvent::IRC::Client library to send a list of messages to an IRC channel. I follow the sample code from its metacpan site (ref: https://metacpan.org/pod/AnyEvent::IRC::Client), except that I need to send a list of messages instead of a single message. So, I'm able to send all the messages in the array to the IRC channel successfully. The problem is that the Event loop does not close at the end (ie. I have to hit Ctrl+C to terminate the program). So any insight on how to close the event loop would be greatly appreciated. Thanks!

# Send some messages to IRC channel
use strict ;
use warnings 'all' ;
no warnings 'uninitialized' ;
use AnyEvent ;
use AnyEvent::IRC::Client ;
use Data::Dumper ;
sub say { print @_, "\n" }

my @messages = ( 'msg1','msg2','msg3','msg4','msg5','msg6','msg7','msg8','msg9','msg10' ) ;

sendIrc( 'ircServer', 6667, '#ircChannel1', 'user123', 'psswd', \@messages ) ;

sub sendIrc {#Params: ircServer(Str), port(Int), channel(Str), nick(Str), psswd(Str), messages(ArrRef<Str>) ; #Return: void ;
  my ( $server, $port, $chan, $nick, $psswd, $messages ) = @_ ;
  my $timer ;
  my $condVar = AnyEvent->condvar ;
  my $con = AnyEvent::IRC::Client->new ;
  
  $con->reg_cb( connect => sub {
    my ( $cond, $err ) = @_ ;
    if ( defined $err ) {
      warn "connect error: $err\n" ;
      return ;
    }#end if
  }) ;

  $con->reg_cb( registered => sub {
    say "User is in!" ;
  }) ;

  $con->reg_cb( disconnect => sub {
    say "User is out!" ;
  }) ;

  $con->reg_cb( sent => sub {
    my ( $con ) = @_ ;
    if ( $_[2] eq 'PRIVMSG' ) {
      say "Msg sent!" ;
      $timer = AnyEvent->timer (
        after => 1 ,
        cb    => sub {
          undef $timer ;
          $condVar->end ;
        }#end callback
      );#end timer
    }#end if
  }) ;

  $con->connect( $server, $port, { nick => $nick, password => $psswd } ) ;
  for my $msg ( @$messages ) {
    $condVar->begin ;
    $con->send_srv( PRIVMSG => ( $chan, $msg ) ) ;
  }#end for
  $condVar->wait ;
  $con->disconnect ;
  return ;
}#end sub
santa100
  • 255
  • 1
  • 5
  • You reuse the `$timer` therefore it will be called only once and you get disbalanced begin/end calls. You can reset the timer in the callback and make sure you call `->end` immediately. Or easier remove the timer from the callback and use a timer before you disconnect. – clamp Jan 09 '21 at 22:37
  • Hi @clamp, so just to clarify, I just have to delete the "my $timer;" main declaration line and use separate timer instances, ie. "my $timer = AnyEvent->timer(...)" inside the "$con->reg_cb(sent => sub{...." block, and that should be it right? – santa100 Jan 10 '21 at 01:33
  • No, that will lead to all timers running at the same time. Unfortunately `sent` fires too early and you have to give it a Second to actually get done. You need to check if the timer is still running (defined) and if so, rescedule with accumulated time. That‘s why I thought setting up a single timer with a longer time right before the disconnect would be easier. – clamp Jan 10 '21 at 09:04
  • So, if going with the single timer approach, the number of seconds to be provided to the "after" attribute will have to be proportional to the number of messages in the array, ie. if there're 10 messages, then it'll be: "after => 10 ; if there're 20 messages, it'll be "after => 20", correct? – santa100 Jan 10 '21 at 19:40
  • In my environment this seems to be the case. The messages are queued and each one takes about 1 second. – clamp Jan 10 '21 at 20:07
  • Thanks @clamp, I'll play around with it a bit more... – santa100 Jan 11 '21 at 02:42
  • Hi @clamp, I'm able to get it to work now using the single timer to disconnect. However, it's not very efficient cuz we still have to resort to a "time-based" approach, which is to provide a wait time for the "after" attribute (ie if there're 10 msgs, then the value is 10; if there're 20 msgs, then set it to 20, etc....). How to make it more efficient, ie. some kind of listener that will auto-disconnect as soon as the last message has been sent? Basically I'd like to know if there's a reg_cb() for the Last message sent. I searched the metacpan Doc but don't see such method?? – santa100 Jan 13 '21 at 04:39

1 Answers1

1

The problem is, that AnyEvent::IRC::Client enqueues the messages and immediately emits the sent event. There seems to be no event that triggers when the messages are actually sent to the Server. Also it takes a certain time for each message to process. I modified your code to keep track of the number of active jobs and reset the timer accordingly. This is not a robust solution. Expect this to fail if the estimated time per job changes.

use strict ;
use warnings 'all' ;
no warnings 'uninitialized' ;
use AnyEvent ;
use AnyEvent::IRC::Client ;
use Data::Dumper ;
sub say { print @_, "\n" }
my @messages = map {"msg$_"} 0..10 ;

sendIrc( 'ircServer', 6667, '#ircChannel1', 'user123', 'psswd', \@messages ) ;

sub sendIrc {#Params: ircServer(Str), port(Int), channel(Str), nick(Str), psswd(Str), messages(ArrRef<Str>) ; #Return: void ;
  my ( $server, $port, $chan, $nick, $psswd, $messages ) = @_ ;
  my $timer;
  my $jobs = 0;# count of jobs
  my $time_per_job = 1;# seconds
  my $condVar = AnyEvent->condvar ;
  my $con = AnyEvent::IRC::Client->new ;
  my $cb = sub {shift->send("done\n")};
  $condVar->begin($cb); # one begin/end pair to wrap everything

  $con->reg_cb( connect => sub {
    my ( $cond, $err ) = @_ ;
    if ( defined $err ) {
      warn "connect error: $err\n" ;
      return ;
    }#end if
  }) ;

  $con->reg_cb( registered => sub {
                    say "User is in!" ;
  }) ;

  $con->reg_cb( disconnect => sub {
    say "User is out!" ;
  }) ;

  $con->reg_cb( sent => sub {
    if ( $_[2] eq 'PRIVMSG' ) {
        $jobs++;
        print "jobs waiting: $jobs\n";
        $timer = AnyEvent->timer (
            after => $jobs * $time_per_job ,
            cb    => sub{ while ($jobs){ $condVar->end;
                                         $jobs--;}
                      },#end callback
         );#end timer
    }#end if
  }) ;

  $con->connect( $server, $port, { nick => $nick, password => $psswd } ) ;

  for my $msg ( @$messages ) {
      $con->send_srv( PRIVMSG => ( $chan, $msg ) ) ;
      $condVar->begin;
  }#end for
  
  $condVar->end;
  my $isDone = $condVar->recv;
  print $isDone;
  $con->disconnect ;
  return ;
}#end sub
clamp
  • 2,552
  • 1
  • 6
  • 16