3
0
mirror of https://github.com/pragma-/pbot.git synced 2026-02-15 13:57:58 +01:00
pbot/PBot/FactoidCommands.pm
Pragmatic Software d955bfa06c Add centralized configuration registry module
Allows changing of bot configuration values without needing to restart
bot instance or needing to edit pbot.pl script.

Registry will initially be populated with default values from pbot.pl,
but if a registry file exists then the registry values will take
precedence over the pbot.pl values. For instance, if you regset the
bot trigger to '%' then the trigger will be '%' even if pbot.pl has '!'
or something else explicitly set.

Some registry items can have trigger hooks associated with them.  For
instance, the irc->botnick registry entry has a change_botnick_trigger
associated with it which changes the IRC nick on the server when a new
value is set via regset/regadd.

Tons of other fixes and improvements throughout.
2014-05-17 20:08:19 +00:00

848 lines
31 KiB
Perl

# File: FactoidCommands.pm
# Author: pragma_
#
# Purpose: Administrative command subroutines.
package PBot::FactoidCommands;
use warnings;
use strict;
use Carp ();
use Time::Duration;
use Time::HiRes qw(gettimeofday);
sub new {
if(ref($_[1]) eq 'HASH') {
Carp::croak("Options to FactoidCommands should be key/value pairs, not hash reference");
}
my ($class, %conf) = @_;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
}
# TODO - move this someplace better so it can be more accessible to user-customisation
my %factoid_metadata_levels = (
created_on => 999,
enabled => 10,
last_referenced_in => 60,
last_referenced_on => 60,
modulelauncher_subpattern => 60,
owner => 60,
rate_limit => 10,
ref_count => 60,
ref_user => 60,
type => 60,
edited_by => 60,
edited_on => 60,
locked => 10,
# all others are allowed to be factset by anybody/default to level 0
);
sub initialize {
my ($self, %conf) = @_;
my $pbot = delete $conf{pbot};
if(not defined $pbot) {
Carp::croak("Missing pbot reference to FactoidCommands");
}
$self->{pbot} = $pbot;
$pbot->commands->register(sub { return $self->factadd(@_) }, "learn", 0);
$pbot->commands->register(sub { return $self->factadd(@_) }, "factadd", 0);
$pbot->commands->register(sub { return $self->factrem(@_) }, "forget", 0);
$pbot->commands->register(sub { return $self->factrem(@_) }, "factrem", 0);
$pbot->commands->register(sub { return $self->factshow(@_) }, "factshow", 0);
$pbot->commands->register(sub { return $self->factinfo(@_) }, "factinfo", 0);
$pbot->commands->register(sub { return $self->factset(@_) }, "factset", 0);
$pbot->commands->register(sub { return $self->factunset(@_) }, "factunset", 0);
$pbot->commands->register(sub { return $self->factchange(@_) }, "factchange", 0);
$pbot->commands->register(sub { return $self->factalias(@_) }, "factalias", 0);
$pbot->commands->register(sub { return $self->call_factoid(@_) }, "fact", 0);
$pbot->commands->register(sub { return $self->factfind(@_) }, "factfind", 0);
$pbot->commands->register(sub { return $self->list(@_) }, "list", 0);
$pbot->commands->register(sub { return $self->top20(@_) }, "top20", 0);
# the following commands have not yet been updated to use the new factoid structure
# DO NOT USE!! Factoid corruption may occur.
$pbot->commands->register(sub { return $self->add_regex(@_) }, "regex", 999);
$pbot->commands->register(sub { return $self->histogram(@_) }, "histogram", 999);
$pbot->commands->register(sub { return $self->count(@_) }, "count", 999);
$pbot->commands->register(sub { return $self->load_module(@_) }, "load", 999);
$pbot->commands->register(sub { return $self->unload_module(@_) }, "unload", 999);
$pbot->commands->register(sub { return $self->enable_command(@_) }, "enable", 999);
$pbot->commands->register(sub { return $self->disable_command(@_) }, "disable", 999);
}
sub call_factoid {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my ($chan, $keyword, $args) = split / /, $arguments, 3;
if(not defined $chan or not defined $keyword) {
return "Usage: fact <channel> <keyword> [arguments]";
}
my ($channel, $trigger) = $self->{pbot}->factoids->find_factoid($chan, $keyword, $args, 1);
if(not defined $trigger) {
return "No such factoid '$keyword' exists for channel '$chan'";
}
return $self->{pbot}->factoids->interpreter($channel, $nick, $user, $host, 1, $trigger, $args);
}
sub factset {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my ($channel, $trigger, $key, $value) = split / /, $arguments, 4 if defined $arguments;
if(not defined $channel or not defined $trigger) {
return "Usage: factset <channel> <factoid> [key [value]]";
}
my $admininfo = $self->{pbot}->admins->loggedin($from, "$nick!$user\@$host");
my $level = 0;
my $meta_level = 0;
if(defined $admininfo) {
$level = $admininfo->{level};
}
if(defined $key) {
if(defined $factoid_metadata_levels{$key}) {
$meta_level = $factoid_metadata_levels{$key};
}
if($meta_level > 0) {
if($level == 0) {
return "You must login to set '$key'";
} elsif($level < $meta_level) {
return "You must be at least level $meta_level to set '$key'";
}
}
}
my ($owner_channel, $owner_trigger) = $self->{pbot}->factoids->find_factoid($channel, $trigger, undef, 1);
if(defined $owner_channel) {
my $factoid = $self->{pbot}->factoids->factoids->hash->{$owner_channel}->{$owner_trigger};
my ($owner) = $factoid->{'owner'} =~ m/([^!]+)/;
if(lc $nick ne lc $owner and $level == 0) {
return "You are not the owner of $trigger.";
}
}
return $self->{pbot}->factoids->factoids->set($channel, $trigger, $key, $value);
}
sub factunset {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my ($channel, $trigger, $key) = split / /, $arguments, 3 if defined $arguments;
if(not defined $channel or not defined $trigger or not defined $key) {
return "Usage: factunset <channel> <factoid> <key>"
}
my $admininfo = $self->{pbot}->admins->loggedin($from, "$nick!$user\@$host");
my $level = 0;
my $meta_level = 0;
if(defined $admininfo) {
$level = $admininfo->{level};
}
if(defined $factoid_metadata_levels{$key}) {
$meta_level = $factoid_metadata_levels{$key};
}
if($meta_level > 0) {
if($level == 0) {
return "You must login to unset '$key'";
} elsif($level < $meta_level) {
return "You must be at least level $meta_level to unset '$key'";
}
}
my ($owner_channel, $owner_trigger) = $self->{pbot}->factoids->find_factoid($channel, $trigger, undef, 1);
if(defined $owner_channel) {
my $factoid = $self->{pbot}->factoids->factoids->hash->{$owner_channel}->{$owner_trigger};
my ($owner) = $factoid->{'owner'} =~ m/([^!]+)/;
if(lc $nick ne lc $owner and $level == 0) {
return "You are not the owner of $trigger.";
}
}
return $self->{pbot}->factoids->factoids->unset($channel, $trigger, $key);
}
sub list {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $text;
if(not defined $arguments) {
return "/msg $nick Usage: list <modules|factoids|commands|admins>";
}
# TODO - update this to use new MessageHistory API
=cut
if($arguments =~/^messages\s+(.*)$/) {
my ($mask_search, $channel_search, $text_search) = split / /, $1;
return "/msg $nick Usage: list messages <hostmask or nick regex> <channel regex> [text regex]" if not defined $channel_search;
$text_search = '.*' if not defined $text_search;
my @results = eval {
my @ret;
foreach my $history_mask (keys %{ $self->{pbot}->antiflood->message_history }) {
my $nickserv = "(undef)";
$nickserv = $self->{pbot}->antiflood->message_history->{$history_mask}->{nickserv_account} if exists $self->{pbot}->antiflood->message_history->{$history_mask}->{nickserv_account};
if($history_mask =~ m/$mask_search/i) {
my $bot_trigger = $self->{pbot}->{registry}->get_value('general', 'trigger');
foreach my $history_channel (keys %{ $self->{pbot}->antiflood->message_history->{$history_mask}->{channels} }) {
if($history_channel =~ m/$channel_search/i) {
my @messages = @{ $self->{pbot}->antiflood->message_history->{$history_mask}->{channels}->{$history_channel}{messages} };
for(my $i = 0; $i <= $#messages; $i++) {
next if $messages[$i]->{msg} =~ /^\Q$bot_trigger\E?login/; # don't reveal login passwords
print "$history_mask, $history_channel\n";
print "joinwatch: ", $self->{pbot}->antiflood->message_history->{$history_mask}->{channels}->{$history_channel}{join_watch}, "\n";
push @ret, {
offenses => $self->{pbot}->antiflood->message_history->{$history_mask}->{channels}->{$history_channel}{offenses},
last_offense_timestamp => $self->{pbot}->antiflood->message_history->{$history_mask}->{channels}->{$history_channel}{last_offense_timestamp},
join_watch => $self->{pbot}->antiflood->message_history->{$history_mask}->{channels}->{$history_channel}{join_watch},
text => $messages[$i]->{msg},
timestamp => $messages[$i]->{timestamp},
mask => $history_mask,
nickserv => $nickserv,
channel => $history_channel
} if $messages[$i]->{msg} =~ m/$text_search/i;
}
}
}
}
}
return @ret;
};
if($@) {
$self->{pbot}->logger->log("Error in search parameters: $@\n");
return "Error in search parameters: $@";
}
my $text = "";
my %seen_masks = ();
my @sorted = sort { $a->{timestamp} <=> $b->{timestamp} } @results;
foreach my $msg (@sorted) {
if(not exists $seen_masks{$msg->{mask}}) {
$seen_masks{$msg->{mask}} = 1;
$text .= "--- [$msg->{mask} [$msg->{nickserv}]: join counter: $msg->{join_watch}; offenses: $msg->{offenses}; last offense/decrease: " . ($msg->{last_offense_timestamp} > 0 ? ago(gettimeofday - $msg->{last_offense_timestamp}) : "unknown") . "]\n";
}
$text .= "[$msg->{channel}] " . localtime($msg->{timestamp}) . " <$msg->{mask}> " . $msg->{text} . "\n";
}
$self->{pbot}->logger->log($text);
return "Messages:\n\n$text";
}
=cut
if($arguments =~ /^modules$/i) {
$from = '.*' if not defined $from or $from !~ /^#/;
$text = "Loaded modules for channel $from: ";
foreach my $channel (sort keys %{ $self->{pbot}->factoids->factoids->hash }) {
foreach my $command (sort keys %{ $self->{pbot}->factoids->factoids->hash->{$channel} }) {
if($self->{pbot}->factoids->factoids->hash->{$channel}->{$command}->{type} eq 'module') {
$text .= "$command ";
}
}
}
return $text;
}
if($arguments =~ /^commands$/i) {
$text = "Registered commands: ";
foreach my $command (sort { $a->{name} cmp $b->{name} } @{ $self->{pbot}->commands->{handlers} }) {
$text .= "$command->{name} ";
$text .= "($command->{level}) " if $command->{level} > 0;
}
return $text;
}
if($arguments =~ /^factoids$/i) {
return "For a list of factoids see " . $self->{pbot}->factoids->export_site;
}
if($arguments =~ /^admins$/i) {
$text = "Admins: ";
my $last_channel = "";
my $sep = "";
foreach my $channel (sort keys %{ $self->{pbot}->admins->admins->hash }) {
if($last_channel ne $channel) {
$text .= $sep . "Channel " . ($channel eq ".*" ? "all" : $channel) . ": ";
$last_channel = $channel;
$sep = "";
}
foreach my $hostmask (sort keys %{ $self->{pbot}->admins->admins->hash->{$channel} }) {
$text .= $sep;
$text .= "*" if exists $self->{pbot}->admins->admins->hash->{$channel}->{$hostmask}->{loggedin};
$text .= $self->{pbot}->admins->admins->hash->{$channel}->{$hostmask}->{name} . " (" . $self->{pbot}->admins->admins->hash->{$channel}->{$hostmask}->{level} . ")";
$sep = "; ";
}
}
return $text;
}
return "/msg $nick Usage: list <modules|commands|factoids|admins>";
}
sub factalias {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my ($chan, $alias, $command) = split / /, $arguments, 3 if defined $arguments;
if(not defined $command) {
return "Usage: factalias <channel> <keyword> <command>";
}
$chan = '.*' if $chan !~ /^#/;
my ($channel, $alias_trigger) = $self->{pbot}->factoids->find_factoid($chan, $alias, undef, 1);
if(defined $alias_trigger) {
$self->{pbot}->logger->log("attempt to overwrite existing command\n");
return "/msg $nick '$alias_trigger' already exists for channel $channel";
}
$self->{pbot}->factoids->add_factoid('text', $chan, "$nick!$user\@$host", $alias, "/call $command");
$self->{pbot}->logger->log("$nick!$user\@$host [$chan] aliased $alias => $command\n");
$self->{pbot}->factoids->save_factoids();
return "/msg $nick '$alias' aliases '$command' for " . ($chan eq '.*' ? 'the global channel' : $chan);
}
sub add_regex {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $factoids = $self->{pbot}->factoids->factoids->hash;
my ($keyword, $text) = $arguments =~ /^(.*?)\s+(.*)$/ if defined $arguments;
$from = '.*' if not defined $from or $from !~ /^#/;
if(not defined $keyword) {
$text = "";
foreach my $trigger (sort keys %{ $factoids->{$from} }) {
if($factoids->{$from}->{$trigger}->{type} eq 'regex') {
$text .= $trigger . " ";
}
}
return "Stored regexs for channel $from: $text";
}
if(not defined $text) {
return "Usage: regex <regex> <command>";
}
my ($channel, $trigger) = $self->{pbot}->factoids->find_factoid($from, $keyword, undef, 1);
if(defined $trigger) {
$self->{pbot}->logger->log("$nick!$user\@$host attempt to overwrite $trigger\n");
return "/msg $nick $trigger already exists for channel $channel.";
}
$self->{pbot}->factoids->add_factoid('regex', $from, "$nick!$user\@$host", $keyword, $text);
$self->{pbot}->logger->log("$nick!$user\@$host added [$keyword] => [$text]\n");
return "/msg $nick $keyword added.";
}
sub factadd {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my ($from_chan, $keyword, $text) = $arguments =~ /^(\S+)\s+(\S+)\s+is\s+(.*)$/i if defined $arguments;
if(not defined $from_chan or not defined $text or not defined $keyword) {
return "/msg $nick Usage: factadd <channel> <keyword> is <factoid>";
}
$from_chan = '.*' if not $from_chan =~ m/^#/;
my ($channel, $trigger) = $self->{pbot}->factoids->find_factoid($from_chan, $keyword, undef, 1, 1);
if(defined $trigger) {
$self->{pbot}->logger->log("$nick!$user\@$host attempt to overwrite $keyword\n");
return "/msg $nick $keyword already exists for " . ($from_chan eq '.*' ? 'global channel' : $from_chan) . ".";
}
$self->{pbot}->factoids->add_factoid('text', $from_chan, "$nick!$user\@$host", $keyword, $text);
$self->{pbot}->logger->log("$nick!$user\@$host added [$from_chan] $keyword => $text\n");
return "/msg $nick '$keyword' added to " . ($from_chan eq '.*' ? 'global channel' : $from_chan) . ".";
}
sub factrem {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $factoids = $self->{pbot}->factoids->factoids->hash;
my ($from_chan, $from_trigger) = split / /, $arguments;
if(not defined $from_chan or not defined $from_trigger) {
return "/msg $nick Usage: factrem <channel> <keyword>";
}
my ($channel, $trigger) = $self->{pbot}->factoids->find_factoid($from_chan, $from_trigger, undef, 1, 1);
if(not defined $trigger) {
return "/msg $nick $from_trigger not found in channel $from_chan.";
}
if($factoids->{$channel}->{$trigger}->{type} eq 'module') {
$self->{pbot}->logger->log("$nick!$user\@$host attempted to remove $trigger [not factoid]\n");
return "/msg $nick $trigger is not a factoid.";
}
my ($owner) = $factoids->{$channel}->{$trigger}->{'owner'} =~ m/([^!]+)/;
if((lc $nick ne lc $owner) and (not $self->{pbot}->admins->loggedin($from, "$nick!$user\@$host"))) {
$self->{pbot}->logger->log("$nick!$user\@$host attempted to remove $trigger [not owner]\n");
my $chan = ($channel eq '.*' ? 'the global channel' : $channel);
return "/msg $nick You are not the owner of '$trigger' for $chan";
}
if(exists $factoids->{$channel}->{$trigger}->{'locked'} and $factoids->{$channel}->{$trigger}->{'locked'} != 0) {
return "$trigger is locked; unlock before deleting.";
}
$self->{pbot}->logger->log("$nick!$user\@$host removed [$channel][$trigger][" . $factoids->{$channel}->{$trigger}->{action} . "]\n");
$self->{pbot}->factoids->remove_factoid($channel, $trigger);
return "/msg $nick $trigger removed from " . ($channel eq '.*' ? 'the global channel' : $channel) . ".";
}
sub histogram {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $factoids = $self->{pbot}->factoids->factoids;
my %hash;
my $factoid_count = 0;
foreach my $command (keys %{ $factoids }) {
if(exists $factoids->{$command}{text}) {
$hash{$factoids->{$command}{owner}}++;
$factoid_count++;
}
}
my $text;
my $i = 0;
foreach my $owner (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
my $percent = int($hash{$owner} / $factoid_count * 100);
$percent = 1 if $percent == 0;
$text .= "$owner: $hash{$owner} ($percent". "%) ";
$i++;
last if $i >= 10;
}
return "$factoid_count factoids, top 10 submitters: $text";
}
sub factshow {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $factoids = $self->{pbot}->factoids->factoids->hash;
my ($chan, $trig) = split / /, $arguments;
if(not defined $chan or not defined $trig) {
return "Usage: factshow <channel> <trigger>";
}
my ($channel, $trigger) = $self->{pbot}->factoids->find_factoid($chan, $trig, undef, 0, 1);
if(not defined $trigger) {
return "/msg $nick '$trig' not found in channel '$chan'";
}
my $result = "$trigger: " . $factoids->{$channel}->{$trigger}->{action};
if($factoids->{$channel}->{$trigger}->{type} eq 'module') {
$result .= ' [module]';
}
return $result;
}
sub factinfo {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $factoids = $self->{pbot}->factoids->factoids->hash;
my ($chan, $trig) = split / /, $arguments;
if(not defined $chan or not defined $trig) {
return "Usage: factinfo <channel> <trigger>";
}
my ($channel, $trigger) = $self->{pbot}->factoids->find_factoid($chan, $trig, undef, 0, 1);
if(not defined $trigger) {
return "'$trig' not found in channel '$chan'";
}
my $created_ago = ago(gettimeofday - $factoids->{$channel}->{$trigger}->{created_on});
my $ref_ago = ago(gettimeofday - $factoids->{$channel}->{$trigger}->{last_referenced_on}) if defined $factoids->{$channel}->{$trigger}->{last_referenced_on};
$chan = ($channel eq '.*' ? 'global channel' : $channel);
# factoid
if($factoids->{$channel}->{$trigger}->{type} eq 'text') {
return "$trigger: Factoid submitted by " . $factoids->{$channel}->{$trigger}->{owner} . " for $chan on " . localtime($factoids->{$channel}->{$trigger}->{created_on}) . " [$created_ago], " . (defined $factoids->{$channel}->{$trigger}->{edited_by} ? "last edited by $factoids->{$channel}->{$trigger}->{edited_by} on " . localtime($factoids->{$channel}->{$trigger}->{edited_on}) . " [" . ago(gettimeofday - $factoids->{$channel}->{$trigger}->{edited_on}) . "], " : "") . "referenced " . $factoids->{$channel}->{$trigger}->{ref_count} . " times (last by " . $factoids->{$channel}->{$trigger}->{ref_user} . (exists $factoids->{$channel}->{$trigger}->{last_referenced_on} ? " on " . localtime($factoids->{$channel}->{$trigger}->{last_referenced_on}) . " [$ref_ago]" : "") . ")";
}
# module
if($factoids->{$channel}->{$trigger}->{type} eq 'module') {
return "$trigger: Module loaded by " . $factoids->{$channel}->{$trigger}->{owner} . " for $chan on " . localtime($factoids->{$channel}->{$trigger}->{created_on}) . " [$created_ago] -> http://code.google.com/p/pbot2-pl/source/browse/trunk/modules/" . $factoids->{$channel}->{$trigger}->{action} . ", used " . $factoids->{$channel}->{$trigger}->{ref_count} . " times (last by " . $factoids->{$channel}->{$trigger}->{ref_user} . (exists $factoids->{$channel}->{$trigger}->{last_referenced_on} ? " on " . localtime($factoids->{$channel}->{$trigger}->{last_referenced_on}) . " [$ref_ago]" : "") . ")";
}
# regex
if($factoids->{$channel}->{$trigger}->{type} eq 'regex') {
return "$trigger: Regex created by " . $factoids->{$channel}->{$trigger}->{owner} . " for $chan on " . localtime($factoids->{$channel}->{$trigger}->{created_on}) . " [$created_ago], " . (defined $factoids->{$channel}->{$trigger}->{edited_by} ? "last edited by $factoids->{$channel}->{$trigger}->{edited_by} on " . localtime($factoids->{$channel}->{$trigger}->{edited_on}) . " [" . ago(gettimeofday - $factoids->{$channel}->{$trigger}->{edited_on}) . "], " : "") . " used " . $factoids->{$channel}->{$trigger}->{ref_count} . " times (last by " . $factoids->{$channel}->{$trigger}->{ref_user} . (exists $factoids->{$channel}->{$trigger}->{last_referenced_on} ? " on " . localtime($factoids->{$channel}->{$trigger}->{last_referenced_on}) . " [$ref_ago]" : "") . ")";
}
return "/msg $nick $trigger is not a factoid or a module";
}
sub top20 {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $factoids = $self->{pbot}->factoids->factoids->hash;
my %hash = ();
my $text = "";
my $i = 0;
my ($channel, $args) = split / /, $arguments, 2 if defined $arguments;
if(not defined $channel) {
return "Usage: top20 <channel> [nick or 'recent']";
}
if(not defined $args) {
foreach my $chan (sort keys %{ $factoids }) {
next if lc $chan ne lc $channel;
foreach my $command (sort {$factoids->{$chan}->{$b}{ref_count} <=> $factoids->{$chan}->{$a}{ref_count}} keys %{ $factoids->{$chan} }) {
if($factoids->{$chan}->{$command}{ref_count} > 0 and $factoids->{$chan}->{$command}{type} eq 'text') {
$text .= "$command ($factoids->{$chan}->{$command}{ref_count}) ";
$i++;
last if $i >= 20;
}
}
$channel = "the global channel" if $channel eq '.*';
$text = "Top $i referenced factoids for $channel: $text" if $i > 0;
return $text;
}
} else {
if(lc $args eq "recent") {
foreach my $chan (sort keys %{ $factoids }) {
next if lc $chan ne lc $channel;
foreach my $command (sort { $factoids->{$chan}->{$b}{created_on} <=> $factoids->{$chan}->{$a}{created_on} } keys %{ $factoids->{$chan} }) {
my $ago = ago(gettimeofday - $factoids->{$chan}->{$command}->{created_on});
$text .= " $command [$ago by $factoids->{$chan}->{$command}->{owner}]\n";
$i++;
last if $i >= 50;
}
$channel = "global channel" if $channel eq '.*';
$text = "$i most recent $channel submissions:\n\n$text" if $i > 0;
return $text;
}
}
my $user = lc $args;
foreach my $chan (sort keys %{ $factoids }) {
next if lc $chan ne lc $channel;
foreach my $command (sort { ($factoids->{$chan}->{$b}{last_referenced_on} || 0) <=> ($factoids->{$chan}->{$a}{last_referenced_on} || 0) } keys %{ $factoids->{$chan} }) {
if($factoids->{$chan}->{$command}{ref_user} =~ /\Q$args\E/i) {
if($user ne lc $factoids->{$chan}->{$command}{ref_user} && not $user =~ /$factoids->{$chan}->{$command}{ref_user}/i) {
$user .= " ($factoids->{$chan}->{$command}{ref_user})";
}
my $ago = $factoids->{$chan}->{$command}{last_referenced_on} ? ago(gettimeofday - $factoids->{$chan}->{$command}{last_referenced_on}) : "unknown";
$text .= " $command [$ago]\n";
$i++;
last if $i >= 20;
}
}
$text = "$i factoids last referenced by $user:\n\n$text" if $i > 0;
return $text;
}
}
}
sub count {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $factoids = $self->{pbot}->factoids->factoids;
my $i = 0;
my $total = 0;
if(not defined $arguments) {
return "/msg $nick Usage: count <nick|factoids>";
}
$arguments = ".*" if($arguments =~ /^factoids$/);
eval {
foreach my $command (keys %{ $factoids }) {
$total++ if exists $factoids->{$command}{text};
my $regex = qr/^\Q$arguments\E$/;
if($factoids->{$command}{owner} =~ /$regex/i && exists $factoids->{$command}{text}) {
$i++;
}
}
};
return "/msg $nick $arguments: $@" if $@;
return "I have $i factoids" if($arguments eq ".*");
if($i > 0) {
my $percent = int($i / $total * 100);
$percent = 1 if $percent == 0;
return "$arguments has submitted $i factoids out of $total ($percent"."%)";
} else {
return "$arguments hasn't submitted any factoids";
}
}
sub factfind {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $factoids = $self->{pbot}->factoids->factoids->hash;
if(not defined $arguments) {
return "/msg $nick Usage: factfind [-channel channel] [-owner nick] [-by nick] [text]";
}
my ($channel, $owner, $by);
$channel = $1 if $arguments =~ s/-channel\s+([^\b\s]+)//i;
$owner = $1 if $arguments =~ s/-owner\s+([^\b\s]+)//i;
$by = $1 if $arguments =~ s/-by\s+([^\b\s]+)//i;
$owner = '.*' if not defined $owner;
$by = '.*' if not defined $by;
$arguments =~ s/^\s+//;
$arguments =~ s/\s+$//;
$arguments =~ s/\s+/ /g;
my $argtype = undef;
if($owner ne '.*') {
$argtype = "owned by $owner";
}
if($by ne '.*') {
if(not defined $argtype) {
$argtype = "last referenced by $by";
} else {
$argtype .= " and last referenced by $by";
}
}
if($arguments ne "") {
my $unquoted_args = $arguments;
$unquoted_args =~ s/(?:\\(?!\\))//g;
$unquoted_args =~ s/(?:\\\\)/\\/g;
if(not defined $argtype) {
$argtype = "with text containing '$unquoted_args'";
} else {
$argtype .= " and with text containing '$unquoted_args'";
}
}
if(not defined $argtype) {
return "/msg $nick Usage: factfind [-channel] [-owner nick] [-by nick] [text]";
}
my ($text, $last_trigger, $last_chan, $i);
$last_chan = "";
$i = 0;
eval {
foreach my $chan (sort keys %{ $factoids }) {
next if defined $channel and $chan !~ /$channel/i;
foreach my $trigger (sort keys %{ $factoids->{$chan} }) {
if($factoids->{$chan}->{$trigger}->{type} eq 'text' or $factoids->{$chan}->{$trigger}->{type} eq 'regex') {
if($factoids->{$chan}->{$trigger}->{owner} =~ /$owner/i && $factoids->{$chan}->{$trigger}->{ref_user} =~ /$by/i) {
next if($arguments ne "" && $factoids->{$chan}->{$trigger}->{action} !~ /$arguments/i && $trigger !~ /$arguments/i);
$i++;
if($chan ne $last_chan) {
$text .= $chan eq '.*' ? "[global channel] " : "[$chan] ";
$last_chan = $chan;
}
$text .= "$trigger ";
$last_trigger = $trigger;
}
}
}
}
};
return "/msg $nick $arguments: $@" if $@;
if($i == 1) {
chop $text;
return "found one factoid submitted for " . ($last_chan eq '.*' ? 'global channel' : $last_chan) . " " . $argtype . ": $last_trigger is $factoids->{$last_chan}->{$last_trigger}->{action}";
} else {
return "found $i factoids " . $argtype . ": $text" unless $i == 0;
my $chans = (defined $channel ? ($channel eq '.*' ? 'global channel' : $channel) : 'any channels');
return "No factoids " . $argtype . " submitted for $chans";
}
}
sub factchange {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $factoids = $self->{pbot}->factoids->factoids->hash;
my ($channel, $trigger, $keyword, $delim, $tochange, $changeto, $modifier);
if(defined $arguments) {
if($arguments =~ /^([^\s]+) ([^\s]+)\s+s(.)/) {
$channel = $1;
$keyword = $2;
$delim = $3;
}
if($arguments =~ /$delim(.*?)$delim(.*)$delim(.*)?$/) {
$tochange = $1;
$changeto = $2;
$modifier = $3;
}
}
if(not defined $channel or not defined $changeto) {
return "Usage: factchange <channel> <keyword> s/<pattern>/<replacement>/";
}
($channel, $trigger) = $self->{pbot}->factoids->find_factoid($channel, $keyword, undef, 0, 1);
if(not defined $trigger) {
return "/msg $nick $keyword not found in channel $from.";
}
if(not $self->{pbot}->admins->loggedin($from, "$nick!$user\@$host") and exists $factoids->{$channel}->{$trigger}->{'locked'} and $factoids->{$channel}->{$trigger}->{'locked'} != 0) {
return "$trigger is locked and cannot be changed.";
}
my $ret = eval {
use re::engine::RE2 -strict => 1;
if(not $factoids->{$channel}->{$trigger}->{action} =~ s|$tochange|$changeto|) {
$self->{pbot}->logger->log("($from) $nick!$user\@$host: failed to change '$trigger' 's$delim$tochange$delim$changeto$delim\n");
return "/msg $nick Change $trigger failed.";
} else {
$self->{pbot}->logger->log("($from) $nick!$user\@$host: changed '$trigger' 's/$tochange/$changeto/\n");
$factoids->{$channel}->{$trigger}->{edited_by} = "$nick!$user\@$host";
$factoids->{$channel}->{$trigger}->{edited_on} = gettimeofday;
$self->{pbot}->factoids->save_factoids();
return "Changed: $trigger is " . $factoids->{$channel}->{$trigger}->{action};
}
};
return "/msg $nick Change $trigger: $@" if $@;
return $ret;
}
sub load_module {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $factoids = $self->{pbot}->factoids->factoids;
my ($keyword, $module) = $arguments =~ /^(.*?)\s+(.*)$/ if defined $arguments;
if(not defined $module) {
return "/msg $nick Usage: load <command> <module>";
}
if(not exists($factoids->{$keyword})) {
$factoids->{$keyword}{module} = $module;
$factoids->{$keyword}{enabled} = 1;
$factoids->{$keyword}{owner} = $nick;
$factoids->{$keyword}{created_on} = time();
$self->{pbot}->logger->log("$nick!$user\@$host loaded $keyword => $module\n");
$self->{pbot}->factoids->save_factoids();
return "/msg $nick Loaded $keyword => $module";
} else {
return "/msg $nick There is already a command named $keyword.";
}
}
sub unload_module {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $factoids = $self->{pbot}->factoids->factoids;
if(not defined $arguments) {
return "/msg $nick Usage: unload <module>";
} elsif(not exists $factoids->{$arguments}) {
return "/msg $nick $arguments not found.";
} elsif(not exists $factoids->{$arguments}{module}) {
return "/msg $nick $arguments is not a module.";
} else {
delete $factoids->{$arguments};
$self->{pbot}->factoids->save_factoids();
$self->{pbot}->logger->log("$nick!$user\@$host unloaded module $arguments\n");
return "/msg $nick $arguments unloaded.";
}
}
sub enable_command {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $factoids = $self->{pbot}->factoids->factoids;
if(not defined $arguments) {
return "/msg $nick Usage: enable <command>";
} elsif(not exists $factoids->{$arguments}) {
return "/msg $nick $arguments not found.";
} else {
$factoids->{$arguments}{enabled} = 1;
$self->{pbot}->factoids->save_factoids();
$self->{pbot}->logger->log("$nick!$user\@$host enabled $arguments\n");
return "/msg $nick $arguments enabled.";
}
}
sub disable_command {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $factoids = $self->{pbot}->factoids->factoids;
if(not defined $arguments) {
return "/msg $nick Usage: disable <command>";
} elsif(not exists $factoids->{$arguments}) {
return "/msg $nick $arguments not found.";
} else {
$factoids->{$arguments}{enabled} = 0;
$self->{pbot}->factoids->save_factoids();
$self->{pbot}->logger->log("$nick!$user\@$host disabled $arguments\n");
return "/msg $nick $arguments disabled.";
}
}
1;