Bot-Cobalt

 view release on metacpan or  search on metacpan

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

}

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

  unless ($self->{DBMGR}) {
    my $cfg = core->get_plugin_cfg($self);
    my $cachekeys = $cfg->{Opts}->{CacheItems} // 30;

    my $rdbdir = File::Spec->catdir(
      core()->var,
      $cfg->{Opts}->{RDBDir} ? $cfg->{Opts}->{RDBDir} : ('db', 'rdb')
    );

    $self->{DBMGR} = Bot::Cobalt::Plugin::RDB::Database->new(
      CacheKeys => $cachekeys,
      RDBDir    => $rdbdir,
    );
  }

  $self->{DBMGR}
}

sub rand_delay {
  my ($self, $delay) = @_;
  return $self->{RANDDELAY} = $delay if defined $delay;
  $self->{RANDDELAY}
}

sub SessionID {
  my ($self, $id) = @_;
  return $self->{SESSID} = $id if defined $id;
  $self->{SESSID}
}

sub AsyncSessionID {
  my ($self, $id) = @_;
  return $self->{ASYNCID} = $id if defined $id;
  $self->{ASYNCID}
}

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

  register($self, 'SERVER',
    [
      'public_msg',
      'rdb_broadcast',
      'rdb_triggered',
    ],
  );

  ## 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");

    POE::Session->create(
      object_states => [
        $self => [
          '_start',

          'poe_post_search',

          'poe_got_result',

          'poe_got_error',
        ],
      ],
    );
  }

  logger->info("Registered, $keys_c items in main RDB");

  return PLUGIN_EAT_NONE
}

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

  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;

  my @handled = qw/
    randstuff
    randq
    rdb
  /;

  ## would be better in a public_cmd_, but eh, darkbot legacy syntax..
  return PLUGIN_EAT_NONE unless $msg->highlight;

  ## uses message_array_sp, ie spaces are preserved
  ## (so don't include them prior to rdb names, for example)
  my $msg_arr = $msg->message_array_sp;

  ## since this is a highlighted message, bot's nickname is first
  my ($cmd, @message) = @$msg_arr[1 .. (scalar @$msg_arr - 1)];
  $cmd = lc($cmd||'');

  ## ..if it's not @handled we don't care:
  return PLUGIN_EAT_NONE unless $cmd and first {; $_ eq $cmd } @handled;

  logger->debug("dispatching $cmd");

  ## dispatcher:
  my ($id, $resp);

  CMD: {
    if ($cmd eq "randstuff") {
      $resp = $self->_cmd_randstuff(\@message, $msg);
      last CMD
    }

    if ($cmd eq "randq") {
      $resp = $self->_cmd_randq(\@message, $msg, 'randq');
      last CMD
    }

    if ($cmd eq "rdb") {
      $resp = $self->_cmd_rdb(\@message, $msg);
      last CMD
    }
  }

  my $channel = $msg->channel;

  if (defined $resp) {
    logger->debug("dispatching msg -> $channel");
    broadcast( 'message', $context, $channel, $resp );
  }

  PLUGIN_EAT_NONE

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

  my (@returned, $prefix);
  if ($count > 30) {
    @returned = @$indices[0 .. 29];
    $prefix   = "Matches (30 of $count): ";
  } else {
    @returned = @$indices;
    $prefix   = "Matches: ";
  }

  return $prefix.join('  ', @returned);
}
  ### self-events ###

sub Bot_rdb_triggered {
  ## Bot_rdb_triggered $context, $channel, $nick, $rdb
  my ($self, $core) = splice @_, 0, 2;
  my $context = ${$_[0]};
  my $channel = ${$_[1]};
  my $nick    = ${$_[2]};
  my $rdb     = ${$_[3]};
  my $orig    = ${$_[4]};
  my $questionstr = ${$_[5]};

  ## event normally triggered by Info3 when a topic references a ~rdb
  ## grab a random response and throw it back at the pipeline
  ## info3 plugin can pick it up and do variable replacement on it

  logger->debug("received rdb_triggered");

  my $dbmgr = $self->DBmgr;

  ## if referenced rdb doesn't exist, send orig string
  my $send_orig;
  unless ( $dbmgr->dbexists($rdb) ) {
      ++$send_orig;
  }

  ## construct fake msg obj for _select_random
  my $new_msg = Bot::Cobalt::IRC::Message::Public->new(
    context => $context,
    src     => $nick . '!fake@host',
    targets => [ $channel ],
    message => '',
  );

  my $random = $send_orig ? $orig
               : $self->_select_random($new_msg, $rdb, 'quietfail') ;

  if (exists core()->Provided->{info_topics}) {
    broadcast( 'info3_relay_string',
      $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')
               // return PLUGIN_EAT_ALL;

  ## iterate channels cfg
  ## throw randstuffs at configured channels unless told not to
  my $servers = $core->Servers;

  SERVER: for my $context (keys %$servers) {
    my $c_obj = $core->get_irc_context($context);

    next SERVER unless $c_obj->connected;

    my $irc   = $core->get_irc_obj($context) || next SERVER;
    my $chcfg = $core->get_channels_cfg($context) || next SERVER;

    logger->debug("rdb_broadcast to $context");

    my $on_channels = $irc->channels || {};
    my $casemap  = $core->get_irc_casemap($context) || 'rfc1459';
    my @channels = map { lc_irc($_, $casemap) } keys %$on_channels;

    my $evtype;
    if ( index($random, '+') == 0 ) {
      ## action
      $random = substr($random, 1);
      $evtype = 'action';
    } else {
      $evtype = 'message';
    }

    logger->debug("rdb_broadcast; type is $evtype");

    @channels = grep {
      $chcfg->{ lc_irc($_, $casemap) }->{rdb_randstuffs} // 1
    } @channels;

    if ($evtype eq 'message') {
      my $maxtargets = $c_obj->maxtargets;
      while (my @targets = splice @channels, 0, $maxtargets) {
        my $tcount = @targets;
        my $targetstr = join ',', @targets;

        logger->debug(
          "rdb_broadcast (MSG) to $tcount targets (max $maxtargets)",
          "($context -> $targetstr)"
        );

        broadcast($evtype, $context, $targetstr, $random);
      }
    } else {
      ## FIXME

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


        my (@returned, $prefix);

        if ($count > 30) {
          @returned = (shuffle @$resultarr)[0 .. 29];
          $prefix   = "$nickname: matches (30 / $count): ";
        } else {
          @returned = @$resultarr;
          $prefix   = "$nickname: matches ($count): ";
        }

        $resp = $prefix . join('  ', @returned);
      }
      last RESPTYPE
    }

    if ($type eq 'count') {
      $dbmgr->cache_push($rdb, $glob, $resultarr)
        if @$resultarr;

      my $count = @$resultarr;
      $resp = "$nickname: Found $count matches for $glob";
      last RESPTYPE
    }

  }

  broadcast( 'message', $context, $channel, $resp )
    if defined $resp;
}

sub poe_got_error {
  my ($self, $kernel, $heap) = @_[OBJECT, KERNEL, HEAP];
  my ($error, $hints) = @_[ARG0, ARG1];

  my $glob = $hints->{Glob};
  my $rdb  = $hints->{RDB};

  logger->warn("Received error from AsyncSearch: $rdb ($glob): $error");

  my $context  = $hints->{Context};
  my $channel  = $hints->{Channel};
  my $nickname = $hints->{Nickname};

  broadcast( 'message', $context, $channel,
    "$nickname: asyncsearch error: $error ($rdb)"
  );
}

1;

=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 
L<Bot::Cobalt::Plugin::Info3> topics), to implement IRC quotebots, or just 
to fill your channel with random chatter.

The "randstuff" db is labelled "main" -- all other RDB names must be 
in the [a-z0-9] set.

Requires L<Bot::Cobalt::Plugin::Info3>.

=head1 COMMANDS

Commands are prefixed with the bot's nickname, rather than CmdChar.

This is a holdover from darkbot legacy syntax.

  <JoeUser> botnick: randq some*glob

=head2 randq

Search for a specified glob in RDB 'main' (randstuffs):

  <JoeUser> bot: randq some+string*

See L<Bot::Cobalt::Utils/glob_to_re_str> for details regarding glob syntax.

=head2 randstuff

Add a new "randstuff" to the 'main' RDB

  <JoeUser> bot: randstuff new randstuff string

A randstuff can also be an action; simply prefix the string with B<+> :

  <JoeUser> bot: randstuff +dances around

Legacy darkbot-style syntax is supported; you can add items to RDBs 
by prefixing the RDB name with B<~>, like so:

  randstuff ~myrdb some new string

The RDB must already exist; see L</"rdb dbadd">

=head2 rdb

=head3 rdb get

  rdb get <rdb> <itemID>

Retrieves the specified item from the specified RDB.

=head3 rdb info

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


  rdb del <rdb> <itemID> [itemID ...]

Deletes items from the specified RDB.

=head3 rdb dbadd

  rdb dbadd <rdb>

Creates a new, empty RDB.

=head3 rdb dbdel

  rdb dbdel <rdb>

Deletes the specified RDB entirely.

Deletion may be disabled in the plugin's configuration file via the 
B<< Opts->AllowDelete >> directive.

=head3 rdb search

  rdb search <rdb> <glob>

Search within a specific RDB. Returns a single random response from the 
result set. Also see L</randq> and L<Bot::Cobalt::Utils/glob_to_re_str> 
for more details on search syntax.

=head3 rdb searchidx

  rdb searchidx <rdb> <glob>

Returns all RDB item IDs matching the specified glob.

=head3 rdb count

  rdb count <rdb> <glob>

Returns just the total number of matches for the specified glob.

=head2 random

'random' is not actually a built-in command; however, since you must have 
L<Bot::Cobalt::Plugin::Info3>, a handy trick is to add a topic named 'random' 
that triggers RDB 'main':

  <JoeUser> bot: add random ~main

That will allow use of 'random' to pull a randomly-selected entry from the 
'randstuffs' database.


=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:

  $context, $channel, $nick, $rdb, $topic_value, $original_str

Broadcasts an L</info3_relay_string> in response, which is picked up by 
B<Info3> to perform variable replacement before relaying back to the 
calling channel.

=head2 Emitted events

=head3 info3_relay_string

Broadcast by L</rdb_triggered> to be picked up by L<Bot::Cobalt::Plugin::Info3>.

Arguments are:

  $context, $channel, $nick, $string, $original

=head1 AUTHOR

Jon Portnoy <avenj@cobaltirc.org>

=cut



( run in 1.092 second using v1.01-cache-2.11-cpan-437f7b0c052 )