Bot-Cobalt

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN


  - Drop DateTime dependency; strftime() will do.
  
  - Explicitly use JSON::XS. Saves some overhead vs. 'use JSON' and 
    performance sucks without it anyway. (We are already pulling in XS 
    bits, so 'eh')

  - Plugin::RDB; Revert to strictly random item IDs; hashing is unnecessary 
    overhead. Drops Digest::SHA dependency.
  
  - Plugin::RDB; Clean up randstuff timer in _unregister.

  - Plugin::Extras::DNS; add simplistic iptohex and hextoip command 
    handlers.

  - Core::Role::Singleton; kill is_instanced in favor of Moosey 
    'has_instance'

  - Optimized Bot::Cobalt::Utils::rplprintf()


Changes  view on Meta::CPAN

  - Fix conflated incoming/outgoing notice events:
   - Outgoing notices still go to 'send_notice' or 'notice'
   - Incoming notices to 'got_notice'

  - Bot::Cobalt::Core::Sugar subs are now prototyped.

  - Shorter lock retry delays in Bot::Cobalt::DB, Bot::Cobalt::Serializer.
    Also remove 4-arg select() statements in favor of Time::HiRes 
    (Windows doesn't like select-on-undef, I'm told).

  - Plugin::RDB bug fix; reset rdb_broadcast timer regardless of whether 
   _select_random() failed.
   
  - Plugin::Extras::CPAN; Add 'belongs' command.
  
  - Various smaller bugfixes, test expansions, POD cleanups.


0.010  2012-06-21

  - Add Bot::Cobalt::Core::Loader, a lightweight module load/unload class,

Changes  view on Meta::CPAN

  - Depend on newer POE::Component::Client::HTTP and 
    POE::Component::Resolver. Systems with older Components that happened 
    to grab newer Socket::GetAddrInfo would produce failures due to 
    Socket::GetAddrInfo API changes -- newer POE bits make use of newer 
    Socket.pm and drop the GetAddrInfo dependency entirely.
    ...Not technically "our problem" but this makes my life less annoying 
    on a couple machines :-)

  - Some POD improvements and widely expanded tests.

  - Drop 'tick' tracker for low-priority timer pool; pretty useless.

  - Various small bug/oversight fixes:
   - Conf.pm; catch Serializer failures and report more details
     Related Plugin::Rehash fix to leave current configs untouched on
     read_cfg failures.
   - IRC.pm; more consistent error messages
   - IRC::Event::Nick; fix common() when channels() has changed
   - Explicitly 'use strictures' in IRC::subclasses
   - Plugin::Auth; log to error() not emerg()

MANIFEST  view on Meta::CPAN

t/03_conf/01_file.t
t/03_conf/02_f_cobalt.t
t/03_conf/03_f_channels.t
t/03_conf/04_f_plugins.t
t/03_conf/05_conf.t
t/04_core/01_flood.t
t/04_core/02_lang.t
t/04_core/03_contextmeta.t
t/04_core/04_meta_ignore.t
t/04_core/05_meta_auth.t
t/04_core/06_timer.t
t/04_core/07_core_basic.t
t/05_irc/01_loadable.t
t/05_irc/02_irc_server.t
t/05_irc/03_irc_event.t
t/05_irc/04_irc_message.t
t/05_irc/05_irc_event_channel.t
t/05_irc/06_irc_event_kick.t
t/05_irc/07_irc_event_mode.t
t/05_irc/08_irc_event_nick.t
t/05_irc/09_irc_event_topic.t

README.mkdn  view on Meta::CPAN

    sh$ cobalt2 --nodetach --debug
    
    ## Launch in background with configured log options:
    sh$ cobalt2

## Included plugins

The example `etc/plugins.conf` installed by `cobalt2-installer` has 
most of these:

[Bot::Cobalt::Plugin::Alarmclock](https://metacpan.org/pod/Bot::Cobalt::Plugin::Alarmclock) -- IRC highlight timers

[Bot::Cobalt::Plugin::Auth](https://metacpan.org/pod/Bot::Cobalt::Plugin::Auth) -- User authentication

[Bot::Cobalt::Plugin::Games](https://metacpan.org/pod/Bot::Cobalt::Plugin::Games) -- Simple IRC games

[Bot::Cobalt::Plugin::Info3](https://metacpan.org/pod/Bot::Cobalt::Plugin::Info3) -- Flexible text-triggered responses

[Bot::Cobalt::Plugin::Master](https://metacpan.org/pod/Bot::Cobalt::Plugin::Master) -- Simple bot control from IRC

[Bot::Cobalt::Plugin::PluginMgr](https://metacpan.org/pod/Bot::Cobalt::Plugin::PluginMgr) -- Load/unload plugins from IRC

[Bot::Cobalt::Plugin::RDB](https://metacpan.org/pod/Bot::Cobalt::Plugin::RDB) -- "Random stuff" databases for quotebots 
or randomized chatter on a timer

[Bot::Cobalt::Plugin::Extras::CPAN](https://metacpan.org/pod/Bot::Cobalt::Plugin::Extras::CPAN) -- Query MetaCPAN and 
[Module::CoreList](https://metacpan.org/pod/Module::CoreList)

[Bot::Cobalt::Plugin::Extras::DNS](https://metacpan.org/pod/Bot::Cobalt::Plugin::Extras::DNS) -- DNS lookups

[Bot::Cobalt::Plugin::Extras::Karma](https://metacpan.org/pod/Bot::Cobalt::Plugin::Extras::Karma) -- Karma bot

[Bot::Cobalt::Plugin::Extras::Relay](https://metacpan.org/pod/Bot::Cobalt::Plugin::Extras::Relay) -- Cross-network relay

lib/Bot/Cobalt.pm  view on Meta::CPAN

  sh$ cobalt2 --nodetach --debug
  
  ## Launch in background with configured log options:
  sh$ cobalt2

=head2 Included plugins

The example C<etc/plugins.conf> installed by C<cobalt2-installer> has 
most of these:

L<Bot::Cobalt::Plugin::Alarmclock> -- IRC highlight timers

L<Bot::Cobalt::Plugin::Auth> -- User authentication

L<Bot::Cobalt::Plugin::Games> -- Simple IRC games

L<Bot::Cobalt::Plugin::Info3> -- Flexible text-triggered responses

L<Bot::Cobalt::Plugin::Master> -- Simple bot control from IRC

L<Bot::Cobalt::Plugin::PluginMgr> -- Load/unload plugins from IRC

L<Bot::Cobalt::Plugin::RDB> -- "Random stuff" databases for quotebots 
or randomized chatter on a timer

L<Bot::Cobalt::Plugin::Extras::CPAN> -- Query MetaCPAN and 
L<Module::CoreList>

L<Bot::Cobalt::Plugin::Extras::DNS> -- DNS lookups

L<Bot::Cobalt::Plugin::Extras::Karma> -- Karma bot

L<Bot::Cobalt::Plugin::Extras::Relay> -- Cross-network relay

lib/Bot/Cobalt/Core.pm  view on Meta::CPAN

    object_states => [
      $self => [
        'syndicator_started',
        'syndicator_stopped',

        'shutdown',
        'sighup',

        'ev_plugin_error',

        'core_timer_check_pool',
      ],
    ],
  );

}

sub syndicator_started {
  my ($kernel, $self) = @_[KERNEL, OBJECT];

  $kernel->sig(INT  => 'shutdown');

lib/Bot/Cobalt/Core.pm  view on Meta::CPAN


    ++$i;
  }

  $self->log->info("-> $i plugins loaded");

  $self->send_event('plugins_initialized', $_[ARG0]);

  $self->log->info("-> started, plugins_initialized sent");

  ## kickstart timer pool
  $kernel->yield('core_timer_check_pool');
}

sub sighup {
  my $self = $_[OBJECT];
  $self->log->warn("SIGHUP received");

  if ($self->detached) {
    ## Caught by Plugin::Rehash if present
    ## Not documented because you should be using the IRC interface
    ## (...and if the bot was run with --nodetach it will die, below)

lib/Bot/Cobalt/Core.pm  view on Meta::CPAN

  my $self = ref $_[0] eq __PACKAGE__ ? $_[0] : $_[OBJECT];

  $self->log->warn("Shutdown called, destroying syndicator");

  $self->_syndicator_destroy();
}

sub syndicator_stopped {
  my ($kernel, $self) = @_[KERNEL, OBJECT];

  $kernel->alarm('core_timer_check_pool');

  $self->log->debug("issuing: POCOIRC_SHUTDOWN, shutdown");

  $kernel->signal( $kernel, 'POCOIRC_SHUTDOWN' );
  $kernel->post( $kernel, 'shutdown' );

  $self->log->warn("Core syndicator stopped.");
}

sub ev_plugin_error {
  my ($kernel, $self, $err) = @_[KERNEL, OBJECT, ARG0];

  ## Receives the same error as 'debug => 1' (in Syndicator init)

  $self->log->error("Plugin err: $err");

  ## Bot_plugin_error
  $self->send_event( 'plugin_error', $err );
}

### Core low-pri timer

sub core_timer_check_pool {
  my ($kernel, $self) = @_[KERNEL, OBJECT];

  ## Timers are provided by Core::Role::Timers

  my $timerpool = $self->TimerPool;

  TIMER: for my $id (keys %$timerpool) {
    my $timer = $timerpool->{$id};

    unless (blessed $timer && $timer->isa('Bot::Cobalt::Timer') ) {
      ## someone's been naughty
      $self->log->warn("not a Bot::Cobalt::Timer: $id");
      delete $timerpool->{$id};
      next TIMER
    }

    if ( $timer->execute_if_ready ) {
      my $event = $timer->event;

      $self->log->debug("timer execute; $id ($event)")
        if $self->debug > 1;

      $self->send_event( 'executed_timer', $id );
      $self->timer_del($id);
    }

  } ## TIMER

  ## most definitely not a high-precision timer.
  ## checked every second or so
  $kernel->alarm('core_timer_check_pool' => time + 1);
}

1;
__END__

=pod

=head1 NAME

Bot::Cobalt::Core - Bot::Cobalt core and event syndicator

lib/Bot/Cobalt/Core/Role/Timers.pm  view on Meta::CPAN



## FIXME tests are nonexistant

has TimerPool => ( 
  is        => 'rw', 
  builder   => sub { hash },
);


sub timer_gen_unique_id {
  my ($self) = @_;
  my @p = ( 'a'..'f', 1..9 );
  my $id = join '', map { $p[rand@p] } 1 .. 4;
  $id .= $p[rand@p] while $self->TimerPool->exists($id);
  $id
}

sub timer_set {
  my ($self, $item) = @_;
  
  my ($caller_pkg, $caller_line) = (caller)[0,2];
  my $d_line = "$caller_pkg line $caller_line";
  
  my $timer = $item;
  unless (blessed $item && $item->isa('Bot::Cobalt::Timer') ) {
    ## hashref-style (we hope)
    $timer = $self->timer_set_hashref( $item, @_[2 .. $#_] );
  }

  unless ($timer->has_id) {
    $timer->id( $self->timer_gen_unique_id );
  }
  
  ## Add to our TimerPool.
  $self->TimerPool->set($timer->id => $timer);
  $self->send_event( new_timer => $timer->id );

  $self->log->debug(
    "timer_set; ".join ' ', $timer->id, $timer->delay, $timer->event
  ) if $self->debug > 1;

  $timer->id
}

sub timer_set_hashref {
  my ($self, $delay, $ev, $id) = @_;

  my ($caller_pkg, $caller_line) = (caller)[0,2];
  my $d_line = "$caller_pkg line $caller_line";
  
  unless (ref $ev eq 'HASH') {
    if (ref $ev) {
      $self->log->warn(
        "timer_set_hashref expected HASH but got $ev; $d_line"
      );
      return
    } else {
      ## Assume we were passed a simple string.
      $ev = { Event => $ev };
    }
  }
  
  my $timer = Bot::Cobalt::Timer->new;

  $timer->id($id) if $id;

  ## Try to guess type, or default to 'event'
  my $type = $ev->{Type};
  unless ($type) {
    if (defined $ev->{Text} && defined $ev->{Context}) {
      $type = 'msg'
    } else {
      $type = 'event'
    }
  }
  
  my($event_name, @event_args);
  
  TYPE: {
    if ($type eq "event") {
      unless (defined $ev->{Event}) {
        $self->log->warn(
          "timer_set_hashref no Event specified; $d_line"
        );
        return
      }

      $timer->event( $ev->{Event} );
      $timer->args( $ev->{Args}//[] );
    
      last TYPE
    }
    
    if (grep { $type eq $_ } qw/msg message privmsg action/) {
      unless (defined $ev->{Text} && defined $ev->{Target}) {
        $self->log->warn(
          "timer_set_hashref; $type needs Text and Target; $d_line"
        );

        return
      }

      $timer->context( $ev->{Context} ) if defined $ev->{Context};
      $timer->target( $ev->{Target} );
      $timer->text( $ev->{Text} );
      $timer->type( $type );

      last TYPE
    }
    
    $self->log->error("Unknown type $type passed to timer_set_hashref");
    return
  }

  ## Tag w/ __PACKAGE__ if no alias is specified
  $timer->alias( $ev->{Alias} // scalar caller );
  
  ## Start ticking.
  $timer->delay( $delay );

  $timer
}

sub timer_get {
  my ($self, $id) = @_;
  return unless $id and $self->TimerPool->exists($id);

  $self->log->debug("timer retrieved; $id")
    if $self->debug > 1;

  $self->TimerPool->get($id)
}

sub timer_get_alias {
  ## get all timerIDs for this alias
  my ($self, $alias) = @_;
  return unless $alias;

  my @timers;
  $self->TimerPool->kv->visit(sub {
    my ($id, $entry) = @$_;
    push @timers, $id if $entry->alias eq $alias
  });

  wantarray ? @timers : \@timers
}

sub timer_del {
  ## delete a timer by its ID
  ## doesn't care if the timerID actually exists or not.
  my ($self, $id) = @_;
  return unless $id;

  $self->log->debug("timer del; $id")
    if $self->debug > 1;

  return unless $self->TimerPool->exists($id);

  my $deleted = $self->TimerPool->delete($id);
  $self->send_event( 'deleted_timer', $id, $deleted->all );
  
  $deleted->all
}

sub timer_del_alias {
  my ($self, $alias) = @_;
  return unless $alias;

  my @deleted;
  $self->TimerPool->kv->visit(sub {
    my ($id, $entry) = @$_;
    if ($entry->alias eq $alias) {
      push @deleted, $id;
      my $removed = $self->TimerPool->delete($id);
      $self->send_event( deleted_timer => $id, $removed );
    }
  });

  wantarray ? @deleted : scalar @deleted 
}


1;
__END__

=pod

=head1 NAME

Bot::Cobalt::Core::Role::Timers

=head1 SYNOPSIS

  ## From a Cobalt plugin:
  my $new_id = $core->timer_set( 60,
    {
      Event => 'my_timed_event',
      Args  => [ $one, $two ],
      Alias => $core->get_plugin_alias($self),
    }
  );
  
  $core->timer_set( 60,
    {
      Event => 'my_timed_event',
      Args  => [ $one, $two ],
    },
    'MY_NAMED_TIMER'
  );
  
  $core->timer_del( $timer_id );
  $core->timer_del_alias( $core->get_plugin_alias($self) );
  
  my $timer_item = $core->timer_get( $timer_id );
  my @active = $core->timer_get_alias( $core->get_plugin_alias($self) );
    

=head1 DESCRIPTION

L<Bot::Cobalt> core interface role for managing a pool of timers living in a
TimerPool hash.

This is consumed by L<Bot::Cobalt::Core> to provide timer manipulation 
methods to the plugin pipeline.

=head1 METHODS

=head2 timer_set

The B<timer_set> method adds a new timer to the hashref 
provided by B<TimerPool> in the consuming class (usually 
L<Bot::Cobalt::Core>).

  ## Supply a Bot::Cobalt::Timer object:
  $core->timer_set( $timer_obj );
  
  ## Supply a hashref containing options:
  $core->timer_set( $secs, $opts_ref );
  $core->timer_set( $secs, $opts_ref, $timer_id );

An already-constructed L<Bot::Cobalt::Timer> object can be passed in; see the 
L<Bot::Cobalt::Timer> documentation for details on constructing a timer 
object.

More frequently, plugins pass in a hash reference containing event 
options and let B<timer_set> construct a L<Bot::Cobalt::Timer> on its own. 
This is the interface documented here.

B<timer_set> will return the new timer's ID on success; a B<send_event> 
will be called for event L</new_timer>.

=head3 Basic timers

The most basic timer is fire-and-forget with no alias tag and no 
preservation of timer ID:

  ## From a Cobalt plugin
  ## Trigger Bot_myplugin_timed_ev with no args in 30 seconds
  $core->timer_set( 30, 'myplugin_timed_ev' );
  ## Same as:
  $core->timer_set( 30, { Event => 'myplugin_timed_ev'  } );

A more sophisticated timer will probably have some arguments specified:

  $core->timer_set( 30,
    {
      Event => 'myplugin_timed_ev',
      Args  => [ $one, $two ],
    },
  );

If this is not a named timer, a unique timer ID will be created:

  my $new_id = $core->timer_set(30, { Event => 'myplugin_timed_ev' });

When used from Cobalt plugins, a timer should usually have an alias 
specified; this makes it easier to clear your pending timers from a 
B<Cobalt_unregister> event using L</timer_del_alias>, for example.

  ## From a Cobalt plugin
  ## Tag w/ our current plugin alias from Bot::Cobalt::Core
  my $new_id = $core->timer_set( 30,
    {
      Event => 'myplugin_timed_ev',
      Args  => [ $one, $two ],
      Alias => $core->get_plugin_alias($self),
    }
  );

=head3 Named timers

If a timer is intended to be globally unique within this TimerPool or 
the timer ID is generated by some other method, it can be specified in 
B<timer_set>. Existing timers with the same ID will be replaced.

  $core->timer_set( 30, 
    { 
      Event => 'myplugin_timed_ev',
      Args  => [ ],
    },
    'MY_NAMED_TIMER',
  );

(This, of course, makes life difficult if your plugin is intended to be 
instanced more than once.)

=head3 Message timers

If a timer is simply intended to send some message or action to an IRC 
context, the B<msg> and B<action> types can be used for convenience:

  $core->timer_set( 30,
    {
      Alias   => $core->get_plugin_alias($self),
      Type    => 'msg',
      Context => $context,
      Target  => $channel,
      Text    => $string,
    },
  );

=head2 timer_set_hashref

timer_set_hashref is the method called by L</timer_set> when it is not 
passed a preexisting L<Bot::Cobalt::Timer> object; you would not normally use 
this method directly.

=head2 timer_del

Deletes a timer by timer ID.

Returns the deleted timer item on success.

Calls a B<send_event> for event L</deleted_timer>.

=head2 timer_del_alias

Deletes a timer by tagged alias.

Returns the list of deleted timer IDs in list context or the number of 
deleted timers in scalar context.

A B<send_event> is called for L</deleted_timer> events for every 
removed timer.

=head2 timer_get

Retrieves the L<Bot::Cobalt::Timer> object for the specified timer ID.

This can be useful for tweaking active timers.

=head2 timer_get_alias

Returns all timer IDs in the pool belonging to the specified alias tag.

Returns a list of timer IDs. In scalar context, returns an array 
reference.

=head2 timer_gen_unique_id

Generates a unique (guaranteed not to exist in the consumer's 
TimerPool) randomized ID for a timer.

You would not normally call this method directly.

=head1 EVENTS

=head2 new_timer

Issued when a timer is set.

Only argument provided is the timer ID.

=head2 deleted_timer

Issued when a timer is deleted.

Arguments are the timer ID and the deleted item object, respectively.

=head1 AUTHOR

Jon Portnoy <avenj@cobaltirc.org>

L<http://www.cobaltirc.org>


=cut

lib/Bot/Cobalt/DB.pm  view on Meta::CPAN

  my $orig_db = tie %{ $self->_orig }, "DB_File", $path,
      $fflags, $self->perms, $DB_HASH
      or confess "failed db open: $path: $!" ;
  $orig_db->sync();

  ## dup a FH to $db->fd for _lockfh
  my $fd = $orig_db->fd;
  my $fh = IO::File->new("<&=$fd")
    or confess "failed dup in dbopen: $!";

  my $timer = 0;
  my $timeout = $self->timeout;

  ## flock _lockfh
  until ( flock $fh, $lflags ) {
    if ($timer > $timeout) {
      warn "failed lock for db $path, timeout (${timeout}s)\n";
      undef $orig_db; undef $fh;
      untie %{ $self->_orig };
      return
    }

    sleep 0.01;
    $timer += 0.01;
  }

  ## reopen DB to Tied
  my $db = tie %{ $self->tied }, "DB_File", $path,
      $fflags, $self->perms, $DB_HASH
      or confess "failed db reopen: $path: $!";

  ## preserve db obj and lock fh
  $self->is_open(1);
  $self->_lockfh($fh);

lib/Bot/Cobalt/IRC.pm  view on Meta::CPAN


## Administrative commands:
with 'Bot::Cobalt::IRC::Role::AdminCmds';

sub Cobalt_register {
  my ($self, $core) = splice @_, 0, 2;

  register($self, SERVER => 'all' );
  broadcast( 'initialize_irc' );

  ## Start a lazy cleanup timer for flood->expire
  $core->timer_set( 180,
    +{ Event => 'ircplug_chk_floodkey_expire' },
    'IRCPLUG_CHK_FLOODKEY_EXPIRE'
  );

  logger->info("Loaded");

  PLUGIN_EAT_NONE
}

sub Cobalt_unregister {

lib/Bot/Cobalt/IRC.pm  view on Meta::CPAN


sub Bot_ircplug_chk_floodkey_expire {
  my ($self, $core) = splice @_, 0, 2;

  ## Lazy flood tracker cleanup.
  ## These are just arrays of timestamps, but they gotta be cleaned up
  ## when they're stale.

  $self->flood->expire if $self->has_flood;

  $core->timer_set( 60,
    { Event => 'ircplug_chk_floodkey_expire' },
    'IRCPLUG_CHK_FLOODKEY_EXPIRE'
  );

  return PLUGIN_EAT_ALL
}

sub Bot_ircplug_flood_rem_ignore {
  my ($self, $core) = splice @_, 0, 2;
  my $context = ${ $_[0] };
  my $mask    = ${ $_[1] };
  ## Internal timer-fired event to remove temp ignores.

  logger->info("Clearing temp ignore: $mask ($context)");

  $core->ignore->del( $context, $mask );

  broadcast( 'flood_ignore_deleted', $context, $mask );

  return PLUGIN_EAT_ALL
}

sub flood_ignore {
  ## Pass me a context and a mask
  ## Set a temporary ignore and a timer to remove it
  my ($self, $context, $mask) = @_;

  my $corecf = core->get_core_cfg;
  my $ignore_time = $corecf->opts->{FloodIgnore} || 20;

  $self->flood->clear($context, $mask);

  logger->info(
    "Issuing temporary ignore due to flood: $mask ($context)"
  );

  my $added = core->ignore->add(
    $context, $mask, "flood_ignore", __PACKAGE__
  );

  broadcast( 'flood_ignore_added', $context, $mask );

  core->timer_set( $ignore_time,
    {
      Event => 'ircplug_flood_rem_ignore',
      Args  => [ $context, $mask ],
    },
  );
}

sub _reset_ajoins {
  my ($self) = @_;

lib/Bot/Cobalt/IRC/Role/AdminCmds.pm  view on Meta::CPAN

  
  ## Do we already have this context?
  if (my $ctxt_obj = irc_context($target_ctxt) ) {
    if ($ctxt_obj->connected) {
      broadcast message => $msg->context, $msg->channel,
        "Attempting reconnect for context $target_ctxt";
    }
    logger->info("Attempting reconnect for context $target_ctxt");
    broadcast ircplug_disconnect => $target_ctxt;
    broadcast ircplug_connect => $target_ctxt;
    broadcast ircplug_timer_serv_retry =>
      +{ context => $target_ctxt, delay => 300 } ;
    return PLUGIN_EAT_ALL
  }

  broadcast message => $msg->context, $msg->channel,
    "Issuing connect for context $target_ctxt";
  
  my $src_nick = $msg->src_nick;
  my $auth_usr = core->auth->username($msg->context, $src_nick);
  

lib/Bot/Cobalt/IRC/Role/AdminCmds.pm  view on Meta::CPAN

   "Issuing disconnect for context $target_ctxt",
   "(Issued by $src_nick [$auth_usr])"
  );

  broadcast ircplug_disconnect => $target_ctxt;
  
  return PLUGIN_EAT_ALL
}


sub Bot_ircplug_timer_serv_retry {
  my ($self, $core) = splice @_, 0, 2;
  my $hints = ${ $_[0] };
    
  my $context = $hints->{context};
  my $delay   = $hints->{delay} || 300;

  logger->debug("ircplug_timer_serv_retry called for $context");
  my $ctxt_obj;
  unless ($ctxt_obj = irc_context($context) && $ctxt_obj->connected) {
    logger->info("Attempting reconnect to $context . . .");
    broadcast ircplug_connect => $context;
    core->timer_set( $delay,
      +{
        Event => 'ircplug_timer_serv_retry',
        Args  => [ +{ context => $context, delay => $delay } ],
      },
    );
  }
  
  return PLUGIN_EAT_ALL
}

1;
__END__

lib/Bot/Cobalt/Logger/Output/File.pm  view on Meta::CPAN


sub _write {
  my ($self, $str) = @_;

  if ($self->_do_reopen) {
    $self->_close;
    $self->_open or warn "_open failure" and return;
  }

  ## FIXME if flock fails, buffer and try next _write up to X items ?
  my $timer = 0;
  until ( flock($self->[HANDLE], LOCK_EX | LOCK_NB) ) {
    if ($timer > FLOCK_TIMEOUT) {
      warn "flock failure for '@{[$self->file]}' ('$str')";
      return
    }
    sleep 0.01;
    $timer += 0.01;
  }

  print { $self->[HANDLE] } $str;

  flock($self->[HANDLE], LOCK_UN);
  
  $self->_close if $self->[RUNNING_IN_HELL];

  1
}

lib/Bot/Cobalt/Manual/Plugins.pod  view on Meta::CPAN

  core()->log->warn("Some error occured");

  core()->log->debug("some verbose debug output for --debug");

A plugin should at least log to B<info> when it is registered or 
unregistered; that is to say, inside B<Cobalt_register> and 
B<Cobalt_unregister> handlers.

=head3 Timers

Core timers live in B<< core()->TimerPool >>; if need be, you can 
access the timer pool directly. It is a hash keyed on timer ID.

Timer methods are provided by the L<Bot::Cobalt::Core::Role::Timers> role.
Each individual timer is a L<Bot::Cobalt::Timer> object; if you plan to 
manipulate a created timer, you'll likely want to consult that POD.

Typically most plugins will only need the following functionality; this 
only covers the hash-based interface to 
L<Bot::Cobalt::Core::Role::Timers/timer_set>, so review the aforementioned 
documentation if you'd like to use the object interface instead.

=head4 timer_set

Set up a new timer for an event or message.

  ## Object interface:
  core()->timer_set( $timer_object );

  ## Hash interface:
  core()->timer_set( $delay, $ev_hash );
  core()->timer_set( $delay, $ev_hash, $id );

Returns the timer ID on success, boolean false on failure.

Expects at least a delay (in seconds) and a hashref specifying what to 
do when the delay has elapsed.

  ## New 60 second 'msg' timer with a random unique ID:
  ## Send $string to $channel on $context
  ## (A triggered 'msg' timer broadcasts a 'message' event)
  my $id = $core->timer_set( 60,
    {
      ## The type of timer; 'msg', 'action' or 'event':
      Type => 'msg',

      ## This is a 'msg' timer; we need to know what to send
      ## 'action' carries the same syntax
      Context => $context,
      Target  => $channel,
      Text    => $string,
    }
  );

Here's the same timer, but using the pure object syntax:

  use Bot::Cobalt::Timer;
  $core->timer_set(
    Bot::Cobalt::Timer->new(
      core    => $core,
      context => $context,
      target  => $channel,
      text    => $string,
      type    => 'msg',
      delay   => 60
    );
  );

If no B<Type> is specified, I<event> is assumed:

  ## Trigger event $event in $secs with (optional) @args:
  my $id = $core->timer_set( $secs,
    {
      Event => $event,
      Args  => [ @args ],
    }
  );
  
  ## ... same thing, but object interface:
  my $id = $core->timer_set(
    Bot::Cobalt::Timer->new(
      core  => $core,
      event => $event,
      args  => \@args,
    );
  );

You can tags packages with your plugin's B<Alias>, if you'd like; 
if an Alias is set, you'll be able to clear all timers by alias via 
L</timer_del_alias> or retrieve them via L</timer_get_alias>:

  ## Alias-tagged timer
  my $id = $core->timer_set( $secs,
    {
      Event => $event,
      Args  => [ @args ],
      ## Safely retrieve our $self object's plugin alias:
      Alias => $core->get_plugin_alias( $self ),
    },
  );

(The L<Bot::Cobalt::Timer> object interface uses the B<alias> attribute.)

Additionally, L<Bot::Cobalt::Plugin::PluginMgr> automatically tries to clear 
plugin timers for unloaded plugins; this only works for Alias-tagged timers.
Without a specified Alias, a timer is essentially considered ownerless -- 
it will happily fire at their scheduled time even if the issuing plugin 
is gone.

By default, a random timer ID is chosen (and returned).

You can also specify an ID:

  ## Set a timer with specified ID 'MyTimer'
  ## Will overwrite any preexisting timers with the same ID
  $core->timer_set( 
    $secs,
    { Event => $event, Args => [ @args ] },
    'MyTimer'
  );

(The L<Bot::Cobalt::Timer> object interface uses the B<id> attribute.)

This can be used for resetting timers you've already set; grab the ID 
returned by a C<timer_set()> call and reset it to change the event or delay.

You may want C<timestr_to_secs> from L<Bot::Cobalt::Utils> for easy 
conversion of human-readable strings into seconds. This is, of course, 
included by default if you C<< use L<Bot::Cobalt::Common> >>.

If you need better accuracy, you'll need to use your own alarm()/delay() 
calls to L<POE::Kernel>; the timer pool is checked every second or so.

Arguments specified in the B<Args> array reference or B<args> object 
attribute will be relayed to plugin event handlers just like any other 
event's parameters:

  sub Bot_some_timed_event {
    ## Called by a timer_set() timer
    my ($self, $core) = splice @_, 0, 2;
    my $firstarg = ${ $_[0] };
    my $second   = ${ $_[1] };
  }

=head4 timer_del

Delete a timer by ID.

  my $deleted = $core->timer_del( $id );

Returns the deleted timer object, or nothing if there was no such 
ID.

The returned result (if there is one) can be fed back to L</timer_set> 
if needed; it will be a L<Bot::Cobalt::Timer> object:

  ## hang on to this timer for now:
   my $postponed = $core->timer_del( $id ) ;

  ## . . . situation changes . . .
   $postponed->delay(60);
  
   if ( $core->timer_set( $postponed ) ) {
     ## readding postponed timer successful
   }

=head4 timer_del_alias

Delete all timers owned by the specified alias:

  my $plugin_alias  = $core->plugin_get_alias( $self );
  my $deleted_count = $core->timer_del_alias( $plugin_alias );

Only works for timers tagged with their Alias; see L</timer_set>.
Timers with no Alias tag are considered essentially "ownerless" and left 
to their own devices; they'll fail quietly if the timed event was handled 
by an unloaded plugin.

This is also called automatically by the core plugin manager 
(L<Bot::Cobalt::Plugin::PluginMgr>) when a plugin is unloaded.

=head4 timer_get_alias

Find out which active timerIDs are owned by the specified alias:

  my $plugin_alias  = $core->plugin_get_alias( $self );
  my @active_timers = $core->timer_get_alias( $plugin_alias );

=head4 timer_get

Retrieve the L<Bot::Cobalt::Timer> for this active timer (or undef if not 
found).

  my $this_timer = $core->timer_get($id);


=head2 Syndicated core events

These are events sent by L<Bot::Cobalt::Core> when various core states 
change.

You should probably return PLUGIN_EAT_NONE on all of these, unless 
you are absolutely sure of what you are doing.

lib/Bot/Cobalt/Manual/Plugins.pod  view on Meta::CPAN


Broadcast when the syndicator reports an error from a plugin.

The only argument is the error string reported by 
L<POE::Component::Syndicator>.

These messages are also logged to 'warn' by default.

=head3 Timer related events

=head4 Bot_executed_timer

Broadcast whenever a timer ID has been executed.

The only argument is the timer ID.

=head4 Bot_deleted_timer

Broadcast whenever a timer ID has been deleted.

The first argument is the timer ID.

The second argument is the removed L<Bot::Cobalt::Timer> object.

=head3 Ignore related events

=head4 flood_ignore_added

Broadcast by L<Bot::Cobalt::IRC> when a temporary anti-flood ignore has 
been placed.

lib/Bot/Cobalt/Manual/Plugins.pod  view on Meta::CPAN

For more fine-grained control, consider running your own POE::Session; 
see L</"Spawning your own POE::Session">, below.


=head2 Spawning your own POE::Session

There's nothing preventing you from spawning your own L<POE::Session>; 
your session will run within Cobalt's L<POE::Kernel> instance and POE 
event handlers will work as-normal.

Motivations for doing so include fine-grained timer control, integration 
with POE bits such as the POE::Component and POE::Wheel namespaces
. . . and the fact that POE is pretty great ;-)

It's worth noting that many POE Components use B<get_active_session> to 
determine where to send responses. It may sometimes be necessary to use 
intermediary "proxy" methods to ensure a proper destination session is 
set in the POE::Component in use.
See L<Bot::Cobalt::Plugin::WWW> source for an example of a plugin that uses 
its own POE::Session and does this (when issuing HTTP requests to 
L<POE::Component::Client::HTTP>).

lib/Bot/Cobalt/Plugin/Alarmclock.pm  view on Meta::CPAN


use Bot::Cobalt;
use Bot::Cobalt::DB;
use Bot::Cobalt::Utils 'timestr_to_secs';

use Object::Pluggable::Constants ':ALL';


sub new { 
  bless +{ 
    # $self->timers->{$timerid} = [ $context, $username ]
    _timers => +{},
    _db     => undef,
  }, shift
}

sub timers        { shift->{_timers} }
sub clear_timers  { shift->{_timers} = +{} }

sub _init_from_db {
  my ($self) = @_;
  my $db = $self->{_db};
  unless ($db->dbopen) {
    logger->error("dbopen failure for alarmclock db in _init_from_db");
    logger->error("persistent alarms may be broken!");
    return
  }
  my $count = 0;

lib/Bot/Cobalt/Plugin/Alarmclock.pm  view on Meta::CPAN

    unless ($alarm && ref $alarm eq 'HASH') {
      logger->warn(
        defined $alarm ? 
            "Alarm '$id' not a HASH; alarmclock db may be broken!"
          : "Could not retrieve alarm '$id'; alarmclock db may be broken!"
      );
      next ID
    }
    my $expires_at = $alarm->{At};
    unless ($expires_at) {
      logger->warn("No 'At' expiry for timer id '$id', removing");
      $db->del($id);
      next ID
    }
    if ($expires_at <= time) {
      logger->debug("Expiring stale alarmclock '$id'");
      $db->del($id);
      next ID
    }
    $alarm->{Alias} = plugin_alias($self);
    my $secs = $expires_at - time;
    my $new_id = core->timer_set( $secs, $alarm );
    if ($new_id) {
      $self->timers->{$new_id} = [ $alarm->{Context}, $alarm->{User} ];
      $db->put($new_id => $alarm);
      $db->del($id);
      ++$count
    } else {
      logger->warn("Failed to readd alarmclock timer '$id'");
    }
  }
  $db->dbclose;
  $count
}

sub _delete_alarm {
  my ($self, $id) = @_;
  my $db = $self->{_db};
  unless ($db->dbopen) {

lib/Bot/Cobalt/Plugin/Alarmclock.pm  view on Meta::CPAN

  $self->{_db} = Bot::Cobalt::DB->new(
    file => $dbpath,
  );
  my $count = $self->_init_from_db;

  register( $self, SERVER => qw/
    public_cmd_alarmclock
    public_cmd_alarmdelete
    public_cmd_alarmdel
    public_cmd_alarmclear
    executed_timer
  / );

  logger->info("Loaded alarm clock ($count existing alarms loaded)");

  PLUGIN_EAT_NONE
}

sub Cobalt_unregister {
  my ($self, $core) = splice @_, 0, 2;
  logger->info("Unregistering core IRC plugin");
  core->timer_del_alias( core->get_plugin_alias($self) );
  $self->clear_timers;
  PLUGIN_EAT_NONE
}

sub Bot_deleted_timer { Bot_executed_timer(@_) }

sub Bot_executed_timer {
  my ($self, $core) = splice @_, 0, 2;
  my $timerid = ${$_[0]};

  return PLUGIN_EAT_NONE
    unless exists $self->timers->{$timerid};

  logger->debug("clearing timer state for $timerid")
    if core->debug > 1;

  delete $self->timers->{$timerid};
  $self->_delete_alarm($timerid);

  PLUGIN_EAT_NONE
}

sub Bot_public_cmd_alarmclear {
  my ($self, $core) = splice @_, 0, 2;
  my $msg = ${ $_[0] };

  my $context = $msg->context;
  my $nick    = $msg->src_nick;

lib/Bot/Cobalt/Plugin/Alarmclock.pm  view on Meta::CPAN


  my $target_ctxt = $msg->message_array->[0];

  logger->info(
    "Clearing all alarms"
    . ($target_ctxt ? " for context $target_ctxt" : "")
    . " per $nick ($auth_usr)"
  );

  my $count = 0;
  DELETE: for my $timerid (keys %{ $self->timers }) {
    if ($target_ctxt) {
      my $ctxt_set = $self->timers->{$timerid}->[0];
      next DELETE unless $target_ctxt eq $ctxt_set;
    }
    core->timer_del($timerid);
    delete $self->timers->{$timerid};
    $self->_delete_alarm($timerid);
    ++$count
  }

  broadcast( 'message', $context, $msg->channel,
    core->rpl( q{ALARMCLOCK_DELETED},
      nick    => $nick,
      timerid => (
        $target_ctxt ? "ALL [$target_ctxt] ($count)" : "ALL ($count)"
      ),
    )
  );

  PLUGIN_EAT_ALL
}

sub Bot_public_cmd_alarmdelete { Bot_public_cmd_alarmdel(@_) }

sub Bot_public_cmd_alarmdel {
  my ($self, $core) = splice @_, 0, 2;
  my $msg = ${$_[0]};

  my $context = $msg->context;
  my $nick    = $msg->src_nick;

  my $auth_usr = core->auth->username($context, $nick);
  return PLUGIN_EAT_NONE unless $auth_usr;

  my $timerid = $msg->message_array->[0];
  return PLUGIN_EAT_ALL unless $timerid;

  my $channel = $msg->channel;

  unless (exists $self->timers->{$timerid}) {
    broadcast( 'message', $context, $channel,
      core->rpl( q{ALARMCLOCK_NOSUCH},
        nick    => $nick,
        timerid => $timerid,
      )
    );

    return PLUGIN_EAT_ALL
  }

  my $thistimer = $self->timers->{$timerid};
  my ($ctxt_set, $ctxt_by) = @$thistimer;
  ## did this user set this timer?
  ## original user may've been undef if LevelRequired == 0
  unless ($ctxt_set eq $context && defined $ctxt_by && $auth_usr eq $ctxt_by) {
    my $auth_lev = core->auth->level($context, $nick);
    ## superusers can override:
    unless ($auth_lev == 9999) {
      broadcast( 'message', $context, $channel,
        core->rpl( q{ALARMCLOCK_NOTYOURS},
          nick    => $nick,
          timerid => $timerid,
        )
      );
      return PLUGIN_EAT_ALL
    }
  }

  core->timer_del($timerid);
  delete $self->timers->{$timerid};
  $self->_delete_alarm($timerid);

  broadcast( 'message', $context, $channel,
    core->rpl( q{ALARMCLOCK_DELETED},
      nick    => $nick,
      timerid => $timerid,
    )
  );

  PLUGIN_EAT_ALL
}


sub Bot_public_cmd_alarmclock {
  my ($self, $core) = splice @_, 0, 2;
  my $msg     = ${$_[0]};

lib/Bot/Cobalt/Plugin/Alarmclock.pm  view on Meta::CPAN


  ## This is the array of (format-stripped) args to the _public_cmd_
  my $args = $msg->message_array;
  ## -> f.ex.:  split ' ', !alarmclock 1h10m things and stuff
  my $timestr = shift @$args;
  ## the rest of this string is the alarm text:
  my $txtstr  = join ' ', @$args;

  $txtstr = "$setter: ALARMCLOCK: ".$txtstr ;

  ## set a timer
  my $secs = timestr_to_secs($timestr) || 1;
  my $channel = $msg->channel;

  my $alarm = +{
    Type    => 'msg',
    User    => $auth_usr,
    Context => $context,
    Target  => $channel,
    Text    => $txtstr,
    Alias   => plugin_alias($self),
    At      => time + $secs,
  };
  my $id = core->timer_set( $secs, $alarm );

  my $resp;
  if ($id) {
    $self->timers->{$id} = [ $context, $auth_usr ];
    $resp = core->rpl( q{ALARMCLOCK_SET},
        nick => $setter,
        secs => $secs,
        timerid => $id,
        timestr => $timestr,
    );
    my $db = $self->{_db};
    if ($db->dbopen) {
      $db->put($id => $alarm);
      $db->dbclose;
    } else {
      logger->error("dbopen failure for alarmclock db during add");
    }
  } else {

lib/Bot/Cobalt/Plugin/Alarmclock.pm  view on Meta::CPAN

=head1 NAME

Bot::Cobalt::Plugin::Alarmclock - Timed IRC highlights

=head1 SYNOPSIS

  # Set alarms:
  > !alarmclock 20m go do some something
  > !alarmclock 1h30m stop staring at irc

  # Remove alarms by timer ID:
  > !alarmdel a1b2c

  # Superusers can remove all alarms (version 0.21.1+):
  > !alarmclear
  # ... or all alarms for a specific context:
  > !alarmclear Main

=head1 DESCRIPTION

This plugin allows authorized users to set a time via either a time string
(see L<Bot::Cobalt::Utils/"timestr_to_secs">) or a specified number of seconds.

When the timer expires, the bot will highlight the user's nickname and
display the specified string in the channel in which the alarmclock was set.

For example:

  !alarmclock 5m check my laundry
  !alarmclock 2h15m10s remind me in 2 hours 15 mins 10 secs

(Accuracy down to the second is not guaranteed. Plus, this is IRC. Sorry.)

As of C<v0.20.1>, alarmclocks will persist between bot runs.

lib/Bot/Cobalt/Plugin/Extras/Debug.pm  view on Meta::CPAN


sub new { bless [], shift }

sub Cobalt_register {
  my ($self, $core) = splice @_, 0, 2;

  my @events = map { 'public_cmd_'.$_ } 
    qw/
      dumpcfg 
      dumpstate 
      dumptimers 
      dumpservers
      dumplangset
      dumpheap
    / ;

  register $self, SERVER => [ @events ];

  $core->log->info("Loaded Debug");

  PLUGIN_EAT_NONE

lib/Bot/Cobalt/Plugin/Extras/Debug.pm  view on Meta::CPAN

  return PLUGIN_EAT_ALL unless
    $core->auth->has_flag($context, $src_nick, 'SUPERUSER');

  broadcast message => $msg->context, $msg->channel,
    "Dumping state hash to log . . .";
  $core->log->warn("dumpstate called (debugger)");
  $core->log->warn(Dumper $core->State);
  PLUGIN_EAT_NONE
}

sub Bot_public_cmd_dumptimers {
  my ($self, $core) = splice @_, 0, 2;
  my $msg = ${ $_[0] };
  my $context  = $msg->context;
  my $src_nick = $msg->src_nick;

  return PLUGIN_EAT_ALL unless
    $core->auth->has_flag($context, $src_nick, 'SUPERUSER');

  broadcast message => $msg->context, $msg->channel,
    "Dumping timer pool to log . . .";
  $core->log->warn("dumptimers called (debugger)");
  $core->log->warn(Dumper $core->TimerPool);
  PLUGIN_EAT_NONE
}

sub Bot_public_cmd_dumpservers {
  my ($self, $core) = splice @_, 0, 2;
  my $msg = ${ $_[0] };
  my $context  = $msg->context;
  my $src_nick = $msg->src_nick;

lib/Bot/Cobalt/Plugin/Extras/Debug.pm  view on Meta::CPAN


  # Dump langset to log:
  !dumplangset

  # Dump server state to log:
  !dumpservers

  # Dump miscellaneous state (core->State) to log:
  !dumpstate

  # Dump current timer pool to log:
  !dumptimers

  # Dump memory state for inspection (requires Devel::MAT):
  !dumpheap

=head1 DESCRIPTION

This is a simple development tool allowing developers to dump the 
current contents of various core attributes to STDOUT for inspection.

All commands are restricted to superusers.

lib/Bot/Cobalt/Plugin/Extras/Karma.pm  view on Meta::CPAN

    qw/
      public_msg
      public_cmd_karma
      public_cmd_topkarma
      public_cmd_resetkarma
      
      karmaplug_sync_db
    /
  );

  $core->timer_set( 5,
    { Event => 'karmaplug_sync_db' },
    'KARMAPLUG_SYNC_DB',
  );

  logger->info("Registered");

  PLUGIN_EAT_NONE
}

sub Cobalt_unregister {

lib/Bot/Cobalt/Plugin/Extras/Karma.pm  view on Meta::CPAN

  my $current = $db->get($karma_for) || 0;
  $db->dbclose;

  $current 
}

sub Bot_karmaplug_sync_db {
  my ($self, $core) = splice @_, 0, 2;
  
  $self->_sync();
  $core->timer_set( 5,
    { Event => 'karmaplug_sync_db' },
    'KARMAPLUG_SYNC_DB',
  );

  PLUGIN_EAT_NONE  
}

sub Bot_public_msg {
  my ($self, $core) = splice @_, 0, 2;
  my $msg     = ${$_[0]};

lib/Bot/Cobalt/Plugin/Extras/Relay.pm  view on Meta::CPAN


      'public_cmd_relay',
      'public_cmd_rwhois',

      'relay_push_join_queue',
    ],
  );

  $core->log->info("Loaded relay system");

  $core->timer_set( 3,
    {
      Event => 'relay_push_join_queue',
      Alias => $core->get_plugin_alias($self),
    },
    'RELAYBOT_JOINQUEUE'
  );

  return PLUGIN_EAT_NONE
}

lib/Bot/Cobalt/Plugin/Extras/Relay.pm  view on Meta::CPAN

      } # RELAY

    } # CHAN

  }  # SERV

  $self->{JoinQueue} = {};

  broadcast('relay_push_left_queue');

  $core->timer_set( 3,
    {
      Event => 'relay_push_join_queue',
      Alias => $core->get_plugin_alias($self),
    },
    'RELAYBOT_JOINQUEUE'
  );

  return PLUGIN_EAT_ALL
}

lib/Bot/Cobalt/Plugin/Info3.pm  view on Meta::CPAN

  if ($self->{LastTriggered}->{$context}->{$channel}) {
    my $lasttrig = $self->{LastTriggered}->{$context}->{$channel};
    my ($last_match, $tries) = @$lasttrig;
    if ($str eq $last_match) {
      ++$tries;
      if ($tries > $self->{MAX_TRIGGERED}) {
        ## we've hit this topic too many times in a row
        ## plugin should EAT_NONE
        logger->debug("Over trigger limit for $str");

        ## set a timer to expire this LastTriggered
        core->timer_set( 90,
          {
            Alias => plugin_alias($self),
            Event => 'info3_expire_maxtriggered',
            Args => [ $context, $channel ],
          },
        );

        return 1
      } else {
        ## haven't hit MAX_TRIGGERED yet.

lib/Bot/Cobalt/Plugin/PluginMgr.pm  view on Meta::CPAN

      err => "Plugin $alias is marked as non-reloadable",
 ) unless Bot::Cobalt::Core::Loader->is_reloadable($plug_obj);

  logger->info("Attempting to unload $alias ($plugisa) per request");

  if ( core()->plugin_del($alias) ) {
    delete core()->PluginObjects->{$plug_obj};

    Bot::Cobalt::Core::Loader->unload($plugisa);

    ## and timers:
    core()->timer_del_alias($alias);

    return core->rpl( q{RPL_PLUGIN_UNLOAD},
        plugin => $alias
    )
  } else {
    return core->rpl( q{RPL_PLUGIN_UNLOAD_ERR},
      plugin => $alias,
      err => 'Unknown core->plugin_del failure'
    )
  }

lib/Bot/Cobalt/Plugin/RDB.pm  view on Meta::CPAN

  );

  ## if the rdbdir doesn't exist, ::Database will try to create it
  ## (it'll also handle creating 'main' for us)
  my $dbmgr = $self->DBmgr;

  ## we'll die out here if there's a problem with 'main' :
  my $keys_c = $dbmgr->get_keys('main');
  core->Provided->{randstuff_items} = $keys_c;

  ## kickstart a randstuff timer (named timer for rdb_broadcast)
  ## delay is in Opts->RandDelay as a timestr
  ## (0 turns off timer)
  my $cfg = core->get_plugin_cfg( $self );
  my $randdelay = $cfg->{Opts}->{RandDelay} // '30m';
  logger->debug("randdelay: $randdelay");

  $randdelay = timestr_to_secs($randdelay) unless $randdelay =~ /^\d+$/;

  $self->rand_delay( $randdelay );

  if ($randdelay) {
    core->timer_set( $randdelay,
      {
        Event => 'rdb_broadcast',
        Alias => core->get_plugin_alias($self)
      },
      'RANDSTUFF'
    );
  }

  if ($cfg->{Opts}->{AsyncSearch}) {
    logger->debug("spawning Session to handle AsyncSearch");

lib/Bot/Cobalt/Plugin/RDB.pm  view on Meta::CPAN

  logger->info("Unregistering RDB");

  $poe_kernel->alias_remove('sess_'. core->get_plugin_alias($self) );

  if ( $self->AsyncSessionID ) {
    $poe_kernel->post( $self->AsyncSessionID, 'shutdown' );
  }

  delete core->Provided->{randstuff_items};

  core->timer_del('RANDSTUFF');

  return PLUGIN_EAT_NONE
}


sub Bot_public_msg {
  my ($self, $core) = splice @_, 0, 2;
  my $msg     = ${$_[0]};
  my $context = $msg->context;

lib/Bot/Cobalt/Plugin/RDB.pm  view on Meta::CPAN

      $context, $channel, $nick, $random, $questionstr
    );
  } else {
    logger->warn("RDB plugin cannot trigger, Info3 is missing");
  }
  return PLUGIN_EAT_ALL
}

sub Bot_rdb_broadcast {
  my ($self, $core) = splice @_, 0, 2;
  ## our timer self-event

  ## reset timer unless randdelay is 0
  if ($self->rand_delay) {
    $core->timer_set( $self->rand_delay,
      {
        Event => 'rdb_broadcast',
        Alias => $core->get_plugin_alias($self)
      },
      'RANDSTUFF'
    );

    logger->debug("rdb_broadcast; timer reset; ".$self->rand_delay);
  }

  my $mock_msg = Bot::Cobalt::IRC::Message::Public->new(
    context => '',
    src     => '',
    targets => [],
    message => '',
  );

  my $random = $self->_select_random($mock_msg, 'main', 'quietfail')

lib/Bot/Cobalt/Plugin/RDB.pm  view on Meta::CPAN


=pod

=head1 NAME

Bot::Cobalt::Plugin::RDB - Bot::Cobalt "random" DB plugin

=head1 DESCRIPTION

Jason Hamilton's B<darkbot> came with the concept of "randstuffs," 
randomized responses broadcast to channels via a timer.

Later versions included a search interface and "RDBs" -- discrete 
'randstuff' databases that could be accessed via 'info' topic triggers 
to return a random response.

B<cobalt1> used essentially the same interface.
This B<RDB> plugin attempts to expand on that concept.

This functionality is often useful to simulate humanoid responses to 
conversation (by writing 'conversational' RDB replies triggered by 

lib/Bot/Cobalt/Plugin/RDB.pm  view on Meta::CPAN



=head1 EVENTS

=head2 Received events

=head3 rdb_broadcast

Self-triggered event.

Called on a timer to broadcast randstuffs from RDB "main."

Takes no arguments.

=head3 rdb_triggered

Triggered (usually by L<Bot::Cobalt::Plugin::Info3>) when a RDB is polled 
for a random response.

Arguments are:

lib/Bot/Cobalt/Plugin/Seen.pm  view on Meta::CPAN

      user_left
      user_quit
      
      seendb_update
      
      seenplug_deferred_list
      
    /,
  );
  
  core->timer_set( 6, 
    +{ Event => 'seendb_update' },
    'SEENDB_WRITE'
  );
  
  logger->info("Loaded");
  
  PLUGIN_EAT_NONE
}

sub Cobalt_unregister {

lib/Bot/Cobalt/Plugin/Seen.pm  view on Meta::CPAN

  $core->log->info("Unloaded");
  PLUGIN_EAT_NONE
}

sub Bot_seendb_update {
  my ($self, $core) = splice @_, 0, 2;
  my $force_flush = @_ ? ${ $_[0] } : 0;

  my $buf = $self->[BUF];
  unless (keys %$buf) {
    $core->timer_set( 2, +{ Event => 'seendb_update' } );
    return PLUGIN_EAT_ALL
  }

  my $db  = $self->[SDB];

  CONTEXT: for my $context (keys %$buf) {
    unless ($db->dbopen) {
      logger->warn("dbopen failed in update; cannot update SeenDB");
      # FIXME exponential back-off?
      $core->timer_set( 6, +{ Event => 'seendb_update' } );
      return PLUGIN_EAT_ALL
    }

    my $writes;
    NICK: for my $nickname (keys %{ $buf->{$context} }) {
      ## if we've done a lot of writes, yield back (unless we're cleaning up)
      if (!$force_flush && $writes && $writes % 50 == 0) {
        $db->dbclose;
        broadcast 'seendb_update';
        return PLUGIN_EAT_ALL

lib/Bot/Cobalt/Plugin/Seen.pm  view on Meta::CPAN

      my $thiskey = $context .'%'. $nickname;
      $db->put($thiskey, $thisbuf);
      ++$writes;
    } ## NICK
    $db->dbclose;
    
    delete $buf->{$context} unless keys %{ $buf->{$context} };
  
  } ## CONTEXT
  
  $core->timer_set( 2, +{ Event => 'seendb_update' } );  

  return PLUGIN_EAT_ALL
}

sub Bot_user_joined {
  my ($self, $core) = splice @_, 0, 2;
  my $join    = ${ $_[0] };
  my $context = $join->context;

  my $nick = $join->src_nick;

lib/Bot/Cobalt/Serializer.pm  view on Meta::CPAN


  if (defined $opts && ref $opts && reftype $opts eq 'HASH') {
    $lock    = $opts->{Locking} if defined $opts->{Locking};
    $timeout = $opts->{Timeout} if $opts->{Timeout};
  }

  open(my $out_fh, '>>:encoding(UTF-8)', $path)
    or confess "open failed for $path: $!";

  if ($lock) {
    my $timer = 0;

    until ( flock $out_fh, LOCK_EX | LOCK_NB ) {
      confess "Failed writefile lock ($path), timed out ($timeout)"
        if $timer > $timeout;

      sleep 0.01;
      $timer += 0.01;
    }

  }

  seek($out_fh, 0, 0)
    or confess "seek failed for $path: $!";
  truncate($out_fh, 0)
    or confess "truncate failed for $path";

  print $out_fh $data;

lib/Bot/Cobalt/Timer.pm  view on Meta::CPAN

package Bot::Cobalt::Timer;
$Bot::Cobalt::Timer::VERSION = '0.021003';
use strictures 2;
use Carp;

use Bot::Cobalt::Common ':types';

use Moo;

## It's possible to pass in a different core.
## (Allows timers to fire against different syndicators if needed)
has core  => (
  lazy      => 1,
  is        => 'rw',
  isa       => HasMethods['send_event'],
  builder   => sub {
    require Bot::Cobalt::Core;
    Bot::Cobalt::Core->instance 
      || die "Cannot find active Bot::Cobalt::Core instance"
  },
);

## May have a timer ID specified at construction for use by
## timer pool managers; if not, creating IDs is up to them.
## (See ::Core::Role::Timers)
has id => (
  lazy      => 1,
  is        => 'rw',
  isa       => Str,
  predicate => 'has_id'
);

## 'at' is set regardless of whether delay()/at() is used
## (or 0 if none is ever set)

lib/Bot/Cobalt/Timer.pm  view on Meta::CPAN

sub is_ready {
  my ($self) = @_;
  $self->at <= time ? 1 : ()
}

sub execute {
  my ($self) = @_;
  $self->_process_type;

  unless ( $self->event ) {
    carp "timer execute called but no event specified";
    return
  }

  my $args = $self->args;
  $self->core->send_event( $self->event, @$args );
  1
}

sub execute_if_ready { execute_ready(@_) }
sub execute_ready {

lib/Bot/Cobalt/Timer.pm  view on Meta::CPAN

}


1;
__END__

=pod

=head1 NAME

Bot::Cobalt::Timer - Cobalt timer objects

=head1 SYNOPSIS

  my $timer = Bot::Cobalt::Timer->new(
    event  => 'my_timed_event',
    args   => [ $one, $two ],
  );
  
  $timer->delay(30);
  
  ## Add this instance to Core's TimerPool, for example:
  $core->timer_set( $timer );

=head1 DESCRIPTION

A B<Bot::Cobalt::Timer> instance represents a single timed event.

These are usually constructed for use by the L<Bot::Cobalt::Core> TimerPool; 
also see L<Bot::Cobalt::Core::Role::Timers/timer_set>.

By default, timers that are executed will fire against the 
L<Bot::Cobalt::Core> singleton. You can pass in a different 'core =>' 
object; it simply needs to provide a C<send_event> method that is passed the
L</event> and L</args> when the timer fires (see L</Execution>).

=head1 METHODS

=head2 Timer settings

=head3 at

The absolute time that this timer is supposed to fire (epoch seconds).

This is normally set automatically when L</delay> is called.

(If it is tweaked manually, L</delay> is irrelevant information.)

=head3 delay

The time this timer is supposed to fire expressed in seconds from the 
time it was set.

(Sets L</at> to I<time()> + I<delay>)

=head3 event

The name of the event that should be fired via B<send_event> when this 
timer is executed.

=head3 args

A L<List::Objects::WithUtils::Array> containing any arguments attached to the
L</event>.

=head3 id

This timer's unique identifier, used as a key in timer pools.

Note that a unique random ID is added when the Timer object is passed to 
L<Bot::Cobalt::Core::Role::Timers/timer_set> if no B<id> is explicitly 
specified.

=head3 alias

The alias tag attached to this timer. Defaults to C<caller()>

=head3 type

The type of event.

Valid types as of this writing are B<msg>, B<action>, and B<event>.

B<msg> and B<action> types require L</context>, L</text>, and L</target> 
attributes be specified.

If no type has been specified for this timer, B<type()> returns our best 
guess; for timed events carrying a L</context> and L</target> the 
default is B<msg>.

This is used to set up proper event names for special timer types.

=head3 context

B<msg and action timer types only>

The server context for an outgoing B<msg> or B<action>.

See L</type>

=head3 text

B<msg and action timer types only>

The text string to send with an outgoing B<msg> or B<action>.

See L</type>

=head3 target

B<msg and action timer types only>

The target channel or nickname for an outgoing B<msg> or B<action>.

See L</type>

=head2 Execution

A timer object can be instructed to execute as long as it was provided a
proper B<core> object at construction time (normally L<Bot::Cobalt::Core>).

=head3 is_ready

Returns boolean true if the timer is ready to execute; in other words, 
if the specified L</at> is reached.

=head3 execute_if_ready

L</execute> the timer if L</is_ready> is true.

=head3 execute

Execute the timer; if our B<core> object can B<send_event>, the timer's 
event is broadcast. Otherwise the timer will warn and return.

=head1 AUTHOR

Jon Portnoy <avenj@cobaltirc.org>

=cut

lib/Bot/Cobalt/Utils.pm  view on Meta::CPAN



=head2 Date and Time

=head3 timestr_to_secs

Convert a string such as "2h10m" into seconds.

  my $delay_s = timestr_to_secs '1h33m10s';

Useful for dealing with timers.


=head3 secs_to_timestr

Turns seconds back into a timestring suitable for feeding to 
L</timestr_to_secs>:

  my $timestr = secs_to_timestr 820; ## -> 13m40s


share/etc/langs/ebonics.yml  view on Meta::CPAN

  ## RPL_PLUGIN_UNLOAD: %plugin
  RPL_PLUGIN_UNLOAD: "yo I just tossed %plugin cuz'a 5-0"

  ## RPL_PLUGIN_ERR: %plugin, %err
  RPL_PLUGIN_ERR: "that shit be fucked up: %plugin%: %err"

  ## RPL_PLUGIN_UNLOAD_ERR: %plugin, %err
  RPL_PLUGIN_UNLOAD_ERR: "tried to drop %plugin% but that shit be fucked up: %err"

  ## RPL_TIMER_ERR
  RPL_TIMER_ERR: "nigga I be hittin' too much rock to set no timer!"


 ## Bot::Cobalt::IRC:

  ## RPL_CHAN_SYNC: %chan
  RPL_CHAN_SYNC: "now about my paper on %chan"


 ## Plugin::Version:

share/etc/langs/ebonics.yml  view on Meta::CPAN


  ## RPL_INFO: %version, %plugins, %uptime, %sent, %topics, %randstuffs
  RPL_INFO: "I be runnin' wit %version%. we gots %plugins plugins in this mofugger, hustlin' fo %uptime%, puttin' in work %sent times. we kicks it wit %topics topics and %randstuffs randstuffs"

  ## RPL_OS: %os
  RPL_OS: "representin' %os"


 ## Plugin::Alarmclock:

  ## ALARMCLOCK_SET: %nick, %secs, %timestr, %timerid
  ALARMCLOCK_SET: "Alarm set to trigger in %secs%s (%nick%) [timerID: %timerid%]"
  
  ## ALARMCLOCK_NOSUCH: %nick, %timerid
  ALARMCLOCK_NOSUCH: "I ain't know about alarm %timerid"
  
  ## ALARMCLOCK_NOTYOURS: %nick, %timerid
  ALARMCLOCK_NOTYOURS: "yo %nick i ain't think %timerid be yours"
  
  ## ALARMCLOCK_DELETED: %nick, %timerid
  ALARMCLOCK_DELETED: "%nick i done forgot about %timerid"


 ## Plugin::Auth:

  ## Broken syntax RPLs, no args:
  AUTH_BADSYN_LOGIN: "dis be how you get down: LOGIN <username> <passwd>"
  AUTH_BADSYN_CHPASS: "dis be how you get down: CHPASS <oldpass> <newpass>"

  ## AUTH_SUCCESS: %context, %src, %nick, %user, %lev
  AUTH_SUCCESS: "you be in the clique now, dawg [%nick%] (%user - %lev%)"

share/etc/langs/english.yml  view on Meta::CPAN

  ## RPL_PLUGIN_UNLOAD: %plugin
  RPL_PLUGIN_UNLOAD: "Plugin removed: %plugin"

  ## RPL_PLUGIN_ERR: %plugin, %err
  RPL_PLUGIN_ERR: "Failed plugin load: %plugin%: %err"

  ## RPL_PLUGIN_UNLOAD_ERR: %plugin, %err
  RPL_PLUGIN_UNLOAD_ERR: "Failed plugin unload: %plugin%: %err"

  ## RPL_TIMER_ERR
  RPL_TIMER_ERR: "Failed to add timer; unknown timer_set failure"


 ## Bot::Cobalt::IRC:

  ## RPL_CHAN_SYNC: %chan
  RPL_CHAN_SYNC: "Sync complete on %chan"


 ## Plugin::Version:

share/etc/langs/english.yml  view on Meta::CPAN


  ## RPL_INFO: %version, %plugins, %uptime, %sent, %topics, %randstuffs
  RPL_INFO: "Running %version%. I have %plugins plugins loaded. I've been up for %uptime and sent %sent messages. I have %topics info3 topics and %randstuffs randstuffs."

  ## RPL_OS: %os
  RPL_OS: "I am running %os"


 ## Plugin::Alarmclock:

  ## ALARMCLOCK_SET: %nick, %secs, %timestr, %timerid
  ALARMCLOCK_SET: "Alarm set to trigger in %secs%s (%nick%) [timerID: %timerid%]"
  
  ## ALARMCLOCK_NOSUCH: %nick, %timerid
  ALARMCLOCK_NOSUCH: "No such alarmID: %timerid"
  
  ## ALARMCLOCK_NOTYOURS: %nick, %timerid
  ALARMCLOCK_NOTYOURS: "%nick%: alarmID %timerid doesn't appear to belong to you!"
  
  ## ALARMCLOCK_DELETED: %nick, %timerid
  ALARMCLOCK_DELETED: "%nick%: alarmID %timerid has been cleared."


 ## Plugin::Auth:

  ## Broken syntax RPLs, no args:
  AUTH_BADSYN_LOGIN: "Bad syntax. Usage: LOGIN <username> <passwd>"
  AUTH_BADSYN_CHPASS: "Bad syntax. Usage: CHPASS <oldpass> <newpass>"

  ## AUTH_SUCCESS: %context, %src, %nick, %user, %lev
  AUTH_SUCCESS: "Successful auth [%nick%] (%user - %lev%)"

t/04_core/06_timer.t  view on Meta::CPAN

{
  package 
    MockCore;
  use strict; use warnings FATAL => 'all';
  use Test::More;
  sub new { bless {}, shift }
  sub send_event { pass('send_event called') }
}


my $timer = new_ok( 'Bot::Cobalt::Timer' => [
    core  => MockCore->new,
    delay => 60,
    id    => 'mytimer',
    event => 'test',
    alias => 'Pkg::Snackulate', 
  ],
);

is( $timer->delay, 60, 'delay()' );
is( $timer->event, 'test', 'event()' );
is( $timer->alias, 'Pkg::Snackulate', 'alias()' );
ok( $timer->has_id, 'has_id()' );
is( $timer->id, 'mytimer', 'id()' );
is( $timer->type, 'event', 'type()' );

ok( $timer->at, 'delay() -> at()' );
ok( $timer->at(1), 'reset at()' );
is( $timer->at, 1, 'at() is reset' );

ok( $timer->args(['arg1', 'arg2']), 'set args()' );
is_deeply( $timer->args, ['arg1', 'arg2'], 'get args()' );

ok( $timer->is_ready, 'timer would be ready' );
ok( $timer->execute_if_ready, 'execute_if_ready()' );

my $mtimer = new_ok( 'Bot::Cobalt::Timer' => [
    core    => MockCore->new,
    context => 'Test',
    target  => 'target',
    text    => 'testing things',
  ],
);

is( $mtimer->context, 'Test', 'context()' );
is( $mtimer->target, 'target', 'target()' );
is( $mtimer->text, 'testing things', 'text()' );
is( $mtimer->type, 'msg', 'assume msg type()' );
is( $mtimer->at, 0, 'no delay set' );

ok( $mtimer->delay(900), 'set delay()' );
ok( $mtimer->at, 'at() is set' );
ok( !$mtimer->execute_if_ready, 'no execute()' );

t/04_core/07_core_basic.t  view on Meta::CPAN

  ## IRC:
  qw/
    is_connected
    get_irc_context
    get_irc_object
    get_irc_casemap
  /,
  
  ## Timers:
  qw/
    timer_set
    timer_del
    timer_del_alias
    timer_get
    timer_get_alias
  /,
  
);

ok( $core->get_core_cfg, 'get_core_cfg()' );
isa_ok( $core->get_core_cfg, 'Bot::Cobalt::Conf::File::Core' );

ok( 
  ref $core->get_channels_cfg('Main') eq 'HASH', 
  'get_channels_cfg(Main)' 

t/author-no-tabs.t  view on Meta::CPAN

    't/03_conf/01_file.t',
    't/03_conf/02_f_cobalt.t',
    't/03_conf/03_f_channels.t',
    't/03_conf/04_f_plugins.t',
    't/03_conf/05_conf.t',
    't/04_core/01_flood.t',
    't/04_core/02_lang.t',
    't/04_core/03_contextmeta.t',
    't/04_core/04_meta_ignore.t',
    't/04_core/05_meta_auth.t',
    't/04_core/06_timer.t',
    't/04_core/07_core_basic.t',
    't/05_irc/01_loadable.t',
    't/05_irc/02_irc_server.t',
    't/05_irc/03_irc_event.t',
    't/05_irc/04_irc_message.t',
    't/05_irc/05_irc_event_channel.t',
    't/05_irc/06_irc_event_kick.t',
    't/05_irc/07_irc_event_mode.t',
    't/05_irc/08_irc_event_nick.t',
    't/05_irc/09_irc_event_topic.t',

tools/cobalt2-import-info2  view on Meta::CPAN

  };
}

my $count = scalar keys %$info3ref;
say "Output path: $output_path";
say "Pushing $count topics to Info3 DB";

my $cdb = Bot::Cobalt::DB->new(
  file => $output_path,
);
my $timer0 = [gettimeofday];
$cdb->dbopen || die "failed to open db\n";
for my $glob (keys %$info3ref) {
  unless ( $cdb->put($glob, $info3ref->{$glob}) ) {
    warn "!! db put failure for $glob";
  }
}
$cdb->dbclose;
my $interval = tv_interval($timer0);


say "Done.";
say $interval if $bench;



( run in 0.982 second using v1.01-cache-2.11-cpan-49f99fa48dc )