view release on metacpan or search on metacpan
- 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()
- 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,
- 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()
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;