Tidy things up

This commit is contained in:
Pragmatic Software 2020-02-15 14:38:32 -08:00
parent c14402dd04
commit 5c4e10a35c
119 changed files with 21787 additions and 23105 deletions

File diff suppressed because it is too large Load Diff

View File

@ -20,140 +20,125 @@ use Time::HiRes qw(gettimeofday);
use POSIX qw/strftime/;
sub initialize {
my ($self, %conf) = @_;
my $filename = $conf{spamkeywords_file} // $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/spam_keywords';
$self->{keywords} = PBot::DualIndexHashObject->new(name => 'SpamKeywords', filename => $filename, pbot => $self->{pbot});
$self->{keywords}->load;
my ($self, %conf) = @_;
my $filename = $conf{spamkeywords_file} // $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/spam_keywords';
$self->{keywords} = PBot::DualIndexHashObject->new(name => 'SpamKeywords', filename => $filename, pbot => $self->{pbot});
$self->{keywords}->load;
$self->{pbot}->{registry}->add_default('text', 'antispam', 'enforce', $conf{enforce_antispam} // 1);
$self->{pbot}->{commands}->register(sub { $self->antispam_cmd(@_) }, "antispam", 1);
$self->{pbot}->{capabilities}->add('admin', 'can-antispam', 1);
$self->{pbot}->{registry}->add_default('text', 'antispam', 'enforce', $conf{enforce_antispam} // 1);
$self->{pbot}->{commands}->register(sub { $self->antispam_cmd(@_) }, "antispam", 1);
$self->{pbot}->{capabilities}->add('admin', 'can-antispam', 1);
}
sub is_spam {
my ($self, $namespace, $text, $all_namespaces) = @_;
my $lc_namespace = lc $namespace;
my ($self, $namespace, $text, $all_namespaces) = @_;
my $lc_namespace = lc $namespace;
return 0 if not $self->{pbot}->{registry}->get_value('antispam', 'enforce');
return 0 if $self->{pbot}->{registry}->get_value($namespace, 'dont_enforce_antispam');
return 0 if not $self->{pbot}->{registry}->get_value('antispam', 'enforce');
return 0 if $self->{pbot}->{registry}->get_value($namespace, 'dont_enforce_antispam');
my $ret = eval {
foreach my $space ($self->{keywords}->get_keys) {
if ($all_namespaces or $lc_namespace eq $space) {
foreach my $keyword ($self->{keywords}->get_keys($space)) {
return 1 if $text =~ m/$keyword/i;
my $ret = eval {
foreach my $space ($self->{keywords}->get_keys) {
if ($all_namespaces or $lc_namespace eq $space) {
foreach my $keyword ($self->{keywords}->get_keys($space)) { return 1 if $text =~ m/$keyword/i; }
}
}
}
}
return 0;
};
return 0;
};
if ($@) {
$self->{pbot}->{logger}->log("Error in is_spam: $@");
return 0;
}
$self->{pbot}->{logger}->log("AntiSpam: spam detected!\n") if $ret;
return $ret;
if ($@) {
$self->{pbot}->{logger}->log("Error in is_spam: $@");
return 0;
}
$self->{pbot}->{logger}->log("AntiSpam: spam detected!\n") if $ret;
return $ret;
}
sub antispam_cmd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $arglist = $stuff->{arglist};
my $arglist = $stuff->{arglist};
my $command = $self->{pbot}->{interpreter}->shift_arg($arglist);
my $command = $self->{pbot}->{interpreter}->shift_arg($arglist);
return "Usage: antispam <command>, where commands are: list/show, add, remove, set, unset" if not defined $command;
return "Usage: antispam <command>, where commands are: list/show, add, remove, set, unset" if not defined $command;
given ($command) {
when ($_ eq "list" or $_ eq "show") {
my $text = "Spam keywords:\n";
my $entries = 0;
foreach my $namespace ($self->{keywords}->get_keys) {
$text .= ' ' . $self->{keywords}->get_data($namespace, '_name') . ":\n";
foreach my $keyword ($self->{keywords}->get_keys($namespace)) {
$text .= ' ' . $self->{keywords}->get_data($namespace, $keyword, '_name') . ",\n";
$entries++;
given ($command) {
when ($_ eq "list" or $_ eq "show") {
my $text = "Spam keywords:\n";
my $entries = 0;
foreach my $namespace ($self->{keywords}->get_keys) {
$text .= ' ' . $self->{keywords}->get_data($namespace, '_name') . ":\n";
foreach my $keyword ($self->{keywords}->get_keys($namespace)) {
$text .= ' ' . $self->{keywords}->get_data($namespace, $keyword, '_name') . ",\n";
$entries++;
}
}
$text .= "none" if $entries == 0;
return $text;
}
}
$text .= "none" if $entries == 0;
return $text;
}
when ("set") {
my ($namespace, $keyword, $flag, $value) = $self->{pbot}->{interpreter}->split_args($arglist, 4);
return "Usage: antispam set <namespace> <regex> [flag [value]]" if not defined $namespace or not defined $keyword;
when ("set") {
my ($namespace, $keyword, $flag, $value) = $self->{pbot}->{interpreter}->split_args($arglist, 4);
return "Usage: antispam set <namespace> <regex> [flag [value]]" if not defined $namespace or not defined $keyword;
if (not $self->{keywords}->exists($namespace)) {
return "There is no such namespace `$namespace`.";
}
if (not $self->{keywords}->exists($namespace)) { return "There is no such namespace `$namespace`."; }
if (not $self->{keywords}->exists($namespace, $keyword)) {
return "There is no such regex `$keyword` for namespace `" . $self->{keywords}->get_data($namespace, '_name') . '`.';
}
if (not $self->{keywords}->exists($namespace, $keyword)) {
return "There is no such regex `$keyword` for namespace `" . $self->{keywords}->get_data($namespace, '_name') . '`.';
}
if (not defined $flag) {
my $text = "Flags:\n";
my $comma = '';
foreach $flag ($self->{keywords}->get_keys($namespace, $keyword)) {
if ($flag eq 'created_on') {
my $timestamp = strftime "%a %b %e %H:%M:%S %Z %Y", localtime $self->{keywords}->get_data($namespace, $keyword, $flag);
$text .= $comma . "created_on: $timestamp";
} else {
$value = $self->{keywords}->get_data($namespace, $keyword, $flag);
$text .= $comma . "$flag: $value";
}
$comma = ",\n ";
if (not defined $flag) {
my $text = "Flags:\n";
my $comma = '';
foreach $flag ($self->{keywords}->get_keys($namespace, $keyword)) {
if ($flag eq 'created_on') {
my $timestamp = strftime "%a %b %e %H:%M:%S %Z %Y", localtime $self->{keywords}->get_data($namespace, $keyword, $flag);
$text .= $comma . "created_on: $timestamp";
} else {
$value = $self->{keywords}->get_data($namespace, $keyword, $flag);
$text .= $comma . "$flag: $value";
}
$comma = ",\n ";
}
return $text;
}
if (not defined $value) {
$value = $self->{keywords}->get_data($namespace, $keyword, $flag);
if (not defined $value) { return "/say $flag is not set."; }
else { return "/say $flag is set to $value"; }
}
$self->{keywords}->set($namespace, $keyword, $flag, $value);
return "Flag set.";
}
return $text;
}
when ("unset") {
my ($namespace, $keyword, $flag) = $self->{pbot}->{interpreter}->split_args($arglist, 3);
return "Usage: antispam unset <namespace> <regex> <flag>" if not defined $namespace or not defined $keyword or not defined $flag;
if (not defined $value) {
$value = $self->{keywords}->get_data($namespace, $keyword, $flag);
if (not defined $value) {
return "/say $flag is not set.";
} else {
return "/say $flag is set to $value";
if (not $self->{keywords}->exists($namespace)) { return "There is no such namespace `$namespace`."; }
if (not $self->{keywords}->exists($namespace, $keyword)) { return "There is no such keyword `$keyword` for namespace `$namespace`."; }
if (not $self->{keywords}->exists($namespace, $keyword, $flag)) { return "There is no such flag `$flag` for regex `$keyword` for namespace `$namespace`."; }
return $self->{keywords}->remove($namespace, $keyword, $flag);
}
}
$self->{keywords}->set($namespace, $keyword, $flag, $value);
return "Flag set.";
when ("add") {
my ($namespace, $keyword) = $self->{pbot}->{interpreter}->split_args($arglist, 2);
return "Usage: antispam add <namespace> <regex>" if not defined $namespace or not defined $keyword;
my $data = {
owner => "$nick!$user\@$host",
created_on => scalar gettimeofday
};
$self->{keywords}->add($namespace, $keyword, $data);
return "/say Added `$keyword`.";
}
when ("remove") {
my ($namespace, $keyword) = $self->{pbot}->{interpreter}->split_args($arglist, 2);
return "Usage: antispam remove <namespace> <regex>" if not defined $namespace or not defined $keyword;
return $self->{keywords}->remove($namespace, $keyword);
}
default { return "Unknown command '$command'; commands are: list/show, add, remove"; }
}
when ("unset") {
my ($namespace, $keyword, $flag) = $self->{pbot}->{interpreter}->split_args($arglist, 3);
return "Usage: antispam unset <namespace> <regex> <flag>" if not defined $namespace or not defined $keyword or not defined $flag;
if (not $self->{keywords}->exists($namespace)) {
return "There is no such namespace `$namespace`.";
}
if (not $self->{keywords}->exists($namespace, $keyword)) {
return "There is no such keyword `$keyword` for namespace `$namespace`.";
}
if (not $self->{keywords}->exists($namespace, $keyword, $flag)) {
return "There is no such flag `$flag` for regex `$keyword` for namespace `$namespace`.";
}
return $self->{keywords}->remove($namespace, $keyword, $flag);
}
when ("add") {
my ($namespace, $keyword) = $self->{pbot}->{interpreter}->split_args($arglist, 2);
return "Usage: antispam add <namespace> <regex>" if not defined $namespace or not defined $keyword;
my $data = {
owner => "$nick!$user\@$host",
created_on => scalar gettimeofday
};
$self->{keywords}->add($namespace, $keyword, $data);
return "/say Added `$keyword`.";
}
when ("remove") {
my ($namespace, $keyword) = $self->{pbot}->{interpreter}->split_args($arglist, 2);
return "Usage: antispam remove <namespace> <regex>" if not defined $namespace or not defined $keyword;
return $self->{keywords}->remove($namespace, $keyword);
}
default {
return "Unknown command '$command'; commands are: list/show, add, remove";
}
}
}
1;

View File

@ -11,6 +11,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::BanTracker;
use parent 'PBot::Class';
use warnings; use strict;
@ -19,213 +20,208 @@ use feature 'unicode_strings';
use Time::HiRes qw/gettimeofday/;
use Time::Duration;
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{registry}->add_default('text', 'bantracker', 'chanserv_ban_timeout', '604800');
$self->{pbot}->{registry}->add_default('text', 'bantracker', 'mute_timeout', '604800');
$self->{pbot}->{registry}->add_default('text', 'bantracker', 'debug', '0');
my ($self, %conf) = @_;
$self->{pbot}->{registry}->add_default('text', 'bantracker', 'chanserv_ban_timeout', '604800');
$self->{pbot}->{registry}->add_default('text', 'bantracker', 'mute_timeout', '604800');
$self->{pbot}->{registry}->add_default('text', 'bantracker', 'debug', '0');
$self->{pbot}->{commands}->register(sub { $self->dumpbans(@_) }, "dumpbans", 1);
$self->{pbot}->{commands}->register(sub { $self->dumpbans(@_) }, "dumpbans", 1);
$self->{pbot}->{event_dispatcher}->register_handler('irc.endofnames', sub { $self->get_banlist(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.banlist', sub { $self->on_banlist_entry(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.quietlist', sub { $self->on_quietlist_entry(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.endofnames', sub { $self->get_banlist(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.banlist', sub { $self->on_banlist_entry(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.quietlist', sub { $self->on_quietlist_entry(@_) });
$self->{banlist} = {};
$self->{banlist} = {};
}
sub dumpbans {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $bans = Dumper($self->{banlist});
return $bans;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $bans = Dumper($self->{banlist});
return $bans;
}
sub get_banlist {
my ($self, $event_type, $event) = @_;
my $channel = lc $event->{event}->{args}[1];
return 0 if not $self->{pbot}->{chanops}->can_gain_ops($channel);
delete $self->{banlist}->{$channel};
$self->{pbot}->{logger}->log("Retrieving banlist for $channel.\n");
$event->{conn}->sl("mode $channel +bq");
return 0;
my ($self, $event_type, $event) = @_;
my $channel = lc $event->{event}->{args}[1];
return 0 if not $self->{pbot}->{chanops}->can_gain_ops($channel);
delete $self->{banlist}->{$channel};
$self->{pbot}->{logger}->log("Retrieving banlist for $channel.\n");
$event->{conn}->sl("mode $channel +bq");
return 0;
}
sub on_banlist_entry {
my ($self, $event_type, $event) = @_;
my $channel = lc $event->{event}->{args}[1];
my $target = lc $event->{event}->{args}[2];
my $source = lc $event->{event}->{args}[3];
my $timestamp = $event->{event}->{args}[4];
my $ago = ago(gettimeofday - $timestamp);
my ($self, $event_type, $event) = @_;
$self->{pbot}->{logger}->log("ban-tracker: [banlist entry] $channel: $target banned by $source $ago.\n");
$self->{banlist}->{$channel}->{'+b'}->{$target} = [ $source, $timestamp ];
my $channel = lc $event->{event}->{args}[1];
my $target = lc $event->{event}->{args}[2];
my $source = lc $event->{event}->{args}[3];
my $timestamp = $event->{event}->{args}[4];
if ($target =~ m/^\*!\*@/ or $target =~ m/^\*!.*\@gateway\/web/i) {
my $timeout = 60 * 60 * 24 * 7;
my $ago = ago(gettimeofday - $timestamp);
if ($target =~ m/\// and $target !~ m/\@gateway/) {
$timeout = 0; # permanent bans for cloaks that aren't gateway
$self->{pbot}->{logger}->log("ban-tracker: [banlist entry] $channel: $target banned by $source $ago.\n");
$self->{banlist}->{$channel}->{'+b'}->{$target} = [$source, $timestamp];
if ($target =~ m/^\*!\*@/ or $target =~ m/^\*!.*\@gateway\/web/i) {
my $timeout = 60 * 60 * 24 * 7;
if ($target =~ m/\// and $target !~ m/\@gateway/) {
$timeout = 0; # permanent bans for cloaks that aren't gateway
}
if ($timeout && $self->{pbot}->{chanops}->can_gain_ops($channel)) {
if (not $self->{pbot}->{chanops}->{unban_timeout}->exists($channel, $target)) {
$self->{pbot}->{logger}->log("Temp ban for $target in $channel.\n");
my $data = {
timeout => gettimeofday + $timeout,
owner => $source,
reason => 'Temp ban on *!*@... or *!...@gateway/web'
};
$self->{pbot}->{chanops}->{unban_timeout}->add($channel, $target, $data);
}
}
}
if ($timeout && $self->{pbot}->{chanops}->can_gain_ops($channel)) {
if (not $self->{pbot}->{chanops}->{unban_timeout}->exists($channel, $target)) {
$self->{pbot}->{logger}->log("Temp ban for $target in $channel.\n");
my $data = {
timeout => gettimeofday + $timeout,
owner => $source,
reason => 'Temp ban on *!*@... or *!...@gateway/web'
};
$self->{pbot}->{chanops}->{unban_timeout}->add($channel, $target, $data);
}
}
}
return 0;
return 0;
}
sub on_quietlist_entry {
my ($self, $event_type, $event) = @_;
my $channel = lc $event->{event}->{args}[1];
my $target = lc $event->{event}->{args}[3];
my $source = lc $event->{event}->{args}[4];
my $timestamp = $event->{event}->{args}[5];
my $ago = ago(gettimeofday - $timestamp);
my ($self, $event_type, $event) = @_;
$self->{pbot}->{logger}->log("ban-tracker: [quietlist entry] $channel: $target quieted by $source $ago.\n");
$self->{banlist}->{$channel}->{'+q'}->{$target} = [ $source, $timestamp ];
return 0;
my $channel = lc $event->{event}->{args}[1];
my $target = lc $event->{event}->{args}[3];
my $source = lc $event->{event}->{args}[4];
my $timestamp = $event->{event}->{args}[5];
my $ago = ago(gettimeofday - $timestamp);
$self->{pbot}->{logger}->log("ban-tracker: [quietlist entry] $channel: $target quieted by $source $ago.\n");
$self->{banlist}->{$channel}->{'+q'}->{$target} = [$source, $timestamp];
return 0;
}
sub get_baninfo {
my ($self, $mask, $channel, $account) = @_;
my ($bans, $ban_account);
my ($self, $mask, $channel, $account) = @_;
my ($bans, $ban_account);
$account = undef if not length $account;
$account = lc $account if defined $account;
$account = undef if not length $account;
$account = lc $account if defined $account;
if ($self->{pbot}->{registry}->get_value('bantracker', 'debug')) {
$self->{pbot}->{logger}->log("[get-baninfo] Getting baninfo for $mask in $channel using account " . (defined $account ? $account : "[undefined]") . "\n");
}
my ($nick, $user, $host) = $mask =~ m/([^!]+)!([^@]+)@(.*)/;
foreach my $mode (keys %{ $self->{banlist}->{$channel} }) {
foreach my $banmask (keys %{ $self->{banlist}->{$channel}->{$mode} }) {
if ($banmask =~ m/^\$a:(.*)/) {
$ban_account = lc $1;
} else {
$ban_account = "";
}
my $banmask_key = $banmask;
$banmask = quotemeta $banmask;
$banmask =~ s/\\\*/.*?/g;
$banmask =~ s/\\\?/./g;
my $banned;
$banned = 1 if defined $account and $account eq $ban_account;
$banned = 1 if $mask =~ m/^$banmask$/i;
if ($banmask_key =~ m{\@gateway/web/irccloud.com} and $host =~ m{^gateway/web/irccloud.com}) {
my ($bannick, $banuser, $banhost) = $banmask_key =~ m/([^!]+)!([^@]+)@(.*)/;
if (lc $user eq lc $banuser) {
$banned = 1;
}
}
if ($banned) {
if (not defined $bans) {
$bans = [];
}
my $baninfo = {};
$baninfo->{banmask} = $banmask_key;
$baninfo->{channel} = $channel;
$baninfo->{owner} = $self->{banlist}->{$channel}->{$mode}->{$banmask_key}->[0];
$baninfo->{when} = $self->{banlist}->{$channel}->{$mode}->{$banmask_key}->[1];
$baninfo->{type} = $mode;
#$self->{pbot}->{logger}->log("get-baninfo: dump: " . Dumper($baninfo) . "\n");
#$self->{pbot}->{logger}->log("get-baninfo: $baninfo->{banmask} $baninfo->{type} in $baninfo->{channel} by $baninfo->{owner} on $baninfo->{when}\n");
push @$bans, $baninfo;
}
if ($self->{pbot}->{registry}->get_value('bantracker', 'debug')) {
$self->{pbot}->{logger}->log("[get-baninfo] Getting baninfo for $mask in $channel using account " . (defined $account ? $account : "[undefined]") . "\n");
}
}
return $bans;
my ($nick, $user, $host) = $mask =~ m/([^!]+)!([^@]+)@(.*)/;
foreach my $mode (keys %{$self->{banlist}->{$channel}}) {
foreach my $banmask (keys %{$self->{banlist}->{$channel}->{$mode}}) {
if ($banmask =~ m/^\$a:(.*)/) { $ban_account = lc $1; }
else { $ban_account = ""; }
my $banmask_key = $banmask;
$banmask = quotemeta $banmask;
$banmask =~ s/\\\*/.*?/g;
$banmask =~ s/\\\?/./g;
my $banned;
$banned = 1 if defined $account and $account eq $ban_account;
$banned = 1 if $mask =~ m/^$banmask$/i;
if ($banmask_key =~ m{\@gateway/web/irccloud.com} and $host =~ m{^gateway/web/irccloud.com}) {
my ($bannick, $banuser, $banhost) = $banmask_key =~ m/([^!]+)!([^@]+)@(.*)/;
if (lc $user eq lc $banuser) { $banned = 1; }
}
if ($banned) {
if (not defined $bans) { $bans = []; }
my $baninfo = {};
$baninfo->{banmask} = $banmask_key;
$baninfo->{channel} = $channel;
$baninfo->{owner} = $self->{banlist}->{$channel}->{$mode}->{$banmask_key}->[0];
$baninfo->{when} = $self->{banlist}->{$channel}->{$mode}->{$banmask_key}->[1];
$baninfo->{type} = $mode;
#$self->{pbot}->{logger}->log("get-baninfo: dump: " . Dumper($baninfo) . "\n");
#$self->{pbot}->{logger}->log("get-baninfo: $baninfo->{banmask} $baninfo->{type} in $baninfo->{channel} by $baninfo->{owner} on $baninfo->{when}\n");
push @$bans, $baninfo;
}
}
}
return $bans;
}
sub is_banned {
my ($self, $nick, $user, $host, $channel) = @_;
my ($self, $nick, $user, $host, $channel) = @_;
my $message_account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
my @nickserv_accounts = $self->{pbot}->{messagehistory}->{database}->get_nickserv_accounts($message_account);
push @nickserv_accounts, undef;
my $message_account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
my @nickserv_accounts = $self->{pbot}->{messagehistory}->{database}->get_nickserv_accounts($message_account);
push @nickserv_accounts, undef;
my $banned = undef;
my $banned = undef;
foreach my $nickserv_account (@nickserv_accounts) {
my $baninfos = $self->get_baninfo("$nick!$user\@$host", $channel, $nickserv_account);
foreach my $nickserv_account (@nickserv_accounts) {
my $baninfos = $self->get_baninfo("$nick!$user\@$host", $channel, $nickserv_account);
if (defined $baninfos) {
foreach my $baninfo (@$baninfos) {
my $u = $self->{pbot}->{users}->loggedin($channel, "$nick!$user\@$host");
my $whitelisted = $self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted');
if ($self->{pbot}->{antiflood}->ban_exempted($baninfo->{channel}, $baninfo->{banmask}) || $whitelisted) {
$self->{pbot}->{logger}->log("[BanTracker] is_banned: $nick!$user\@$host banned as $baninfo->{banmask} in $baninfo->{channel}, but allowed through whitelist\n");
} else {
if ($channel eq lc $baninfo->{channel}) {
my $mode = $baninfo->{type} eq "+b" ? "banned" : "quieted";
$self->{pbot}->{logger}->log("[BanTracker] is_banned: $nick!$user\@$host $mode as $baninfo->{banmask} in $baninfo->{channel} by $baninfo->{owner}\n");
$banned = $baninfo;
last;
}
if (defined $baninfos) {
foreach my $baninfo (@$baninfos) {
my $u = $self->{pbot}->{users}->loggedin($channel, "$nick!$user\@$host");
my $whitelisted = $self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted');
if ($self->{pbot}->{antiflood}->ban_exempted($baninfo->{channel}, $baninfo->{banmask}) || $whitelisted) {
$self->{pbot}->{logger}->log("[BanTracker] is_banned: $nick!$user\@$host banned as $baninfo->{banmask} in $baninfo->{channel}, but allowed through whitelist\n");
} else {
if ($channel eq lc $baninfo->{channel}) {
my $mode = $baninfo->{type} eq "+b" ? "banned" : "quieted";
$self->{pbot}->{logger}->log("[BanTracker] is_banned: $nick!$user\@$host $mode as $baninfo->{banmask} in $baninfo->{channel} by $baninfo->{owner}\n");
$banned = $baninfo;
last;
}
}
}
}
}
}
}
return $banned;
return $banned;
}
sub track_mode {
my $self = shift;
my ($source, $mode, $target, $channel) = @_;
my $self = shift;
my ($source, $mode, $target, $channel) = @_;
$mode = lc $mode;
$target = lc $target;
$channel = lc $channel;
$mode = lc $mode;
$target = lc $target;
$channel = lc $channel;
if ($mode eq "+b" or $mode eq "+q") {
$self->{pbot}->{logger}->log("ban-tracker: $target " . ($mode eq '+b' ? 'banned' : 'quieted') . " by $source in $channel.\n");
$self->{banlist}->{$channel}->{$mode}->{$target} = [ $source, gettimeofday ];
$self->{pbot}->{antiflood}->devalidate_accounts($target, $channel);
}
elsif ($mode eq "-b" or $mode eq "-q") {
$self->{pbot}->{logger}->log("ban-tracker: $target " . ($mode eq '-b' ? 'unbanned' : 'unquieted') . " by $source in $channel.\n");
delete $self->{banlist}->{$channel}->{$mode eq "-b" ? "+b" : "+q"}->{$target};
if ($mode eq "+b" or $mode eq "+q") {
$self->{pbot}->{logger}->log("ban-tracker: $target " . ($mode eq '+b' ? 'banned' : 'quieted') . " by $source in $channel.\n");
$self->{banlist}->{$channel}->{$mode}->{$target} = [$source, gettimeofday];
$self->{pbot}->{antiflood}->devalidate_accounts($target, $channel);
} elsif ($mode eq "-b" or $mode eq "-q") {
$self->{pbot}->{logger}->log("ban-tracker: $target " . ($mode eq '-b' ? 'unbanned' : 'unquieted') . " by $source in $channel.\n");
delete $self->{banlist}->{$channel}->{$mode eq "-b" ? "+b" : "+q"}->{$target};
if ($mode eq "-b") {
if ($self->{pbot}->{chanops}->{unban_timeout}->exists($channel, $target)) {
$self->{pbot}->{chanops}->{unban_timeout}->remove($channel, $target);
} elsif ($self->{pbot}->{chanops}->{unban_timeout}->exists($channel, "$target\$##stop_join_flood")) {
# freenode strips channel forwards from unban result if no ban exists with a channel forward
$self->{pbot}->{chanops}->{unban_timeout}->remove($channel, "$target\$##stop_join_flood");
}
if ($mode eq "-b") {
if ($self->{pbot}->{chanops}->{unban_timeout}->exists($channel, $target)) { $self->{pbot}->{chanops}->{unban_timeout}->remove($channel, $target); }
elsif ($self->{pbot}->{chanops}->{unban_timeout}->exists($channel, "$target\$##stop_join_flood")) {
# freenode strips channel forwards from unban result if no ban exists with a channel forward
$self->{pbot}->{chanops}->{unban_timeout}->remove($channel, "$target\$##stop_join_flood");
}
} elsif ($mode eq "-q") {
if ($self->{pbot}->{chanops}->{unmute_timeout}->exists($channel, $target)) { $self->{pbot}->{chanops}->{unmute_timeout}->remove($channel, $target); }
}
} else {
$self->{pbot}->{logger}->log("BanTracker: Unknown mode '$mode'\n");
}
elsif ($mode eq "-q") {
if ($self->{pbot}->{chanops}->{unmute_timeout}->exists($channel, $target)) {
$self->{pbot}->{chanops}->{unmute_timeout}->remove($channel, $target);
}
}
} else {
$self->{pbot}->{logger}->log("BanTracker: Unknown mode '$mode'\n");
}
}
1;

View File

@ -19,134 +19,128 @@ no if $] >= 5.018, warnings => "experimental::smartmatch";
use Time::HiRes qw(gettimeofday);
sub initialize {
my ($self, %conf) = @_;
$self->{filename} = $conf{filename};
$self->{blacklist} = {};
$self->{pbot}->{commands}->register(sub { $self->blacklist(@_) }, "blacklist", 1);
$self->{pbot}->{capabilities}->add('admin', 'can-blacklist', 1);
$self->load_blacklist;
my ($self, %conf) = @_;
$self->{filename} = $conf{filename};
$self->{blacklist} = {};
$self->{pbot}->{commands}->register(sub { $self->blacklist(@_) }, "blacklist", 1);
$self->{pbot}->{capabilities}->add('admin', 'can-blacklist', 1);
$self->load_blacklist;
}
sub add {
my ($self, $channel, $hostmask) = @_;
$self->{blacklist}->{lc $channel}->{lc $hostmask} = 1;
$self->save_blacklist();
my ($self, $channel, $hostmask) = @_;
$self->{blacklist}->{lc $channel}->{lc $hostmask} = 1;
$self->save_blacklist();
}
sub remove {
my $self = shift;
my ($channel, $hostmask) = @_;
my $self = shift;
my ($channel, $hostmask) = @_;
$channel = lc $channel;
$hostmask = lc $hostmask;
$channel = lc $channel;
$hostmask = lc $hostmask;
if (exists $self->{blacklist}->{$channel}) {
delete $self->{blacklist}->{$channel}->{$hostmask};
if (exists $self->{blacklist}->{$channel}) {
delete $self->{blacklist}->{$channel}->{$hostmask};
if (keys %{ $self->{blacklist}->{$channel} } == 0) {
delete $self->{blacklist}->{$channel};
if (keys %{$self->{blacklist}->{$channel}} == 0) { delete $self->{blacklist}->{$channel}; }
}
}
$self->save_blacklist();
$self->save_blacklist();
}
sub clear_blacklist {
my $self = shift;
$self->{blacklist} = {};
my $self = shift;
$self->{blacklist} = {};
}
sub load_blacklist {
my $self = shift;
my $filename;
if (@_) { $filename = shift; } else { $filename = $self->{filename}; }
my $self = shift;
my $filename;
if (@_) { $filename = shift; }
else { $filename = $self->{filename}; }
if (not defined $filename) {
$self->{pbot}->{logger}->log("No blacklist path specified -- skipping loading of blacklist");
return;
}
$self->{pbot}->{logger}->log("Loading blacklist from $filename ...\n");
open(FILE, "< $filename") or Carp::croak "Couldn't open $filename: $!\n";
my @contents = <FILE>;
close(FILE);
my $i = 0;
foreach my $line (@contents) {
chomp $line;
$i++;
my ($channel, $hostmask) = split(/\s+/, $line);
if (not defined $hostmask || not defined $channel) {
Carp::croak "Syntax error around line $i of $filename\n";
if (not defined $filename) {
$self->{pbot}->{logger}->log("No blacklist path specified -- skipping loading of blacklist");
return;
}
if (exists $self->{blacklist}->{$channel}->{$hostmask}) {
Carp::croak "Duplicate blacklist entry [$hostmask][$channel] found in $filename around line $i\n";
$self->{pbot}->{logger}->log("Loading blacklist from $filename ...\n");
open(FILE, "< $filename") or Carp::croak "Couldn't open $filename: $!\n";
my @contents = <FILE>;
close(FILE);
my $i = 0;
foreach my $line (@contents) {
chomp $line;
$i++;
my ($channel, $hostmask) = split(/\s+/, $line);
if (not defined $hostmask || not defined $channel) { Carp::croak "Syntax error around line $i of $filename\n"; }
if (exists $self->{blacklist}->{$channel}->{$hostmask}) { Carp::croak "Duplicate blacklist entry [$hostmask][$channel] found in $filename around line $i\n"; }
$self->{blacklist}->{$channel}->{$hostmask} = 1;
}
$self->{blacklist}->{$channel}->{$hostmask} = 1;
}
$self->{pbot}->{logger}->log(" $i entries in blacklist\n");
$self->{pbot}->{logger}->log(" $i entries in blacklist\n");
}
sub save_blacklist {
my $self = shift;
my $filename;
my $self = shift;
my $filename;
if (@_) { $filename = shift; } else { $filename = $self->{filename}; }
if (@_) { $filename = shift; }
else { $filename = $self->{filename}; }
if (not defined $filename) {
$self->{pbot}->{logger}->log("No blacklist path specified -- skipping saving of blacklist\n");
return;
}
open(FILE, "> $filename") or die "Couldn't open $filename: $!\n";
foreach my $channel (keys %{ $self->{blacklist} }) {
foreach my $hostmask (keys %{ $self->{blacklist}->{$channel} }) {
print FILE "$channel $hostmask\n";
if (not defined $filename) {
$self->{pbot}->{logger}->log("No blacklist path specified -- skipping saving of blacklist\n");
return;
}
}
close(FILE);
open(FILE, "> $filename") or die "Couldn't open $filename: $!\n";
foreach my $channel (keys %{$self->{blacklist}}) {
foreach my $hostmask (keys %{$self->{blacklist}->{$channel}}) { print FILE "$channel $hostmask\n"; }
}
close(FILE);
}
sub check_blacklist {
my $self = shift;
my ($hostmask, $channel, $nickserv, $gecos) = @_;
my $self = shift;
my ($hostmask, $channel, $nickserv, $gecos) = @_;
return 0 if not defined $channel;
return 0 if not defined $channel;
foreach my $black_channel (keys %{ $self->{blacklist} }) {
foreach my $black_hostmask (keys %{ $self->{blacklist}->{$black_channel} }) {
my $flag = '';
$flag = $1 if $black_hostmask =~ s/^\$(.)://;
foreach my $black_channel (keys %{$self->{blacklist}}) {
foreach my $black_hostmask (keys %{$self->{blacklist}->{$black_channel}}) {
my $flag = '';
$flag = $1 if $black_hostmask =~ s/^\$(.)://;
my $black_channel_escaped = quotemeta $black_channel;
my $black_hostmask_escaped = quotemeta $black_hostmask;
my $black_channel_escaped = quotemeta $black_channel;
my $black_hostmask_escaped = quotemeta $black_hostmask;
$black_channel_escaped =~ s/\\(\.|\*)/$1/g;
$black_hostmask_escaped =~ s/\\(\.|\*)/$1/g;
$black_channel_escaped =~ s/\\(\.|\*)/$1/g;
$black_hostmask_escaped =~ s/\\(\.|\*)/$1/g;
next if $channel !~ /^$black_channel_escaped$/;
next if $channel !~ /^$black_channel_escaped$/;
if ($flag eq 'a' && defined $nickserv && $nickserv =~ /^$black_hostmask_escaped$/i) {
$self->{pbot}->{logger}->log("$hostmask nickserv $nickserv blacklisted in channel $channel (matches [\$a:$black_hostmask] host and [$black_channel] channel)\n");
return 1;
} elsif ($flag eq 'r' && defined $gecos && $gecos =~ /^$black_hostmask_escaped$/i) {
$self->{pbot}->{logger}->log("$hostmask GECOS $gecos blacklisted in channel $channel (matches [\$r:$black_hostmask] host and [$black_channel] channel)\n");
return 1;
} elsif ($flag eq '' && $hostmask =~ /^$black_hostmask_escaped$/i) {
$self->{pbot}->{logger}->log("$hostmask blacklisted in channel $channel (matches [$black_hostmask] host and [$black_channel] channel)\n");
return 1;
}
if ($flag eq 'a' && defined $nickserv && $nickserv =~ /^$black_hostmask_escaped$/i) {
$self->{pbot}->{logger}->log("$hostmask nickserv $nickserv blacklisted in channel $channel (matches [\$a:$black_hostmask] host and [$black_channel] channel)\n");
return 1;
} elsif ($flag eq 'r' && defined $gecos && $gecos =~ /^$black_hostmask_escaped$/i) {
$self->{pbot}->{logger}->log("$hostmask GECOS $gecos blacklisted in channel $channel (matches [\$r:$black_hostmask] host and [$black_channel] channel)\n");
return 1;
} elsif ($flag eq '' && $hostmask =~ /^$black_hostmask_escaped$/i) {
$self->{pbot}->{logger}->log("$hostmask blacklisted in channel $channel (matches [$black_hostmask] host and [$black_channel] channel)\n");
return 1;
}
}
}
}
return 0;
return 0;
}
sub blacklist {
@ -161,18 +155,15 @@ sub blacklist {
given ($command) {
when ($_ eq "list" or $_ eq "show") {
my $text = "Blacklist:\n";
my $text = "Blacklist:\n";
my $entries = 0;
foreach my $channel (sort keys %{ $self->{blacklist} }) {
if ($channel eq '.*') {
$text .= " all channels:\n";
} else {
$text .= " $channel:\n";
}
foreach my $mask (sort keys %{ $self->{blacklist}->{$channel} }) {
$text .= " $mask,\n";
$entries++;
}
foreach my $channel (sort keys %{$self->{blacklist}}) {
if ($channel eq '.*') { $text .= " all channels:\n"; }
else { $text .= " $channel:\n"; }
foreach my $mask (sort keys %{$self->{blacklist}->{$channel}}) {
$text .= " $mask,\n";
$entries++;
}
}
$text .= "none" if $entries == 0;
return "/msg $nick $text";
@ -194,17 +185,15 @@ sub blacklist {
$channel = '.*' if not defined $channel;
if (exists $self->{blacklist}->{$channel} and not exists $self->{blacklist}->{$channel}->{$mask}) {
$self->{pbot}->{logger}->log("$nick attempt to remove nonexistent [$mask][$channel] from blacklist\n");
return "/say $mask not found in blacklist for channel $channel (use `blacklist list` to display blacklist)";
$self->{pbot}->{logger}->log("$nick attempt to remove nonexistent [$mask][$channel] from blacklist\n");
return "/say $mask not found in blacklist for channel $channel (use `blacklist list` to display blacklist)";
}
$self->remove($channel, $mask);
$self->{pbot}->{logger}->log("$nick!$user\@$host removed [$mask] from blacklist for channel [$channel]\n");
return "/say $mask removed from blacklist for channel $channel";
}
default {
return "Unknown command '$command'; commands are: list/show, add, remove";
}
default { return "Unknown command '$command'; commands are: list/show, add, remove"; }
}
}

View File

@ -17,261 +17,234 @@ use feature 'switch';
no if $] >= 5.018, warnings => "experimental::smartmatch";
sub initialize {
my ($self, %conf) = @_;
my $filename = $conf{filename} // $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/capabilities';
$self->{caps} = PBot::HashObject->new(name => 'Capabilities', filename => $filename, pbot => $self->{pbot});
$self->{caps}->load;
# 'cap' command registered in PBot.pm because $self->{pbot}->{commands} is not yet loaded.
my ($self, %conf) = @_;
my $filename = $conf{filename} // $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/capabilities';
$self->{caps} = PBot::HashObject->new(name => 'Capabilities', filename => $filename, pbot => $self->{pbot});
$self->{caps}->load;
# add some capabilities used in this file
$self->add('can-modify-capabilities', undef, 1);
$self->add('can-group-capabilities', undef, 1);
$self->add('can-ungroup-capabilities', undef, 1);
# 'cap' command registered in PBot.pm because $self->{pbot}->{commands} is not yet loaded.
# add some useful capabilities
$self->add('is-whitelisted', undef, 1);
# add some capabilities used in this file
$self->add('can-modify-capabilities', undef, 1);
$self->add('can-group-capabilities', undef, 1);
$self->add('can-ungroup-capabilities', undef, 1);
# add some useful capabilities
$self->add('is-whitelisted', undef, 1);
}
sub has {
my ($self, $cap, $subcap, $depth) = @_;
my $cap_data = $self->{caps}->get_data($cap);
return 0 if not defined $cap_data;
return 1 if $cap eq $subcap and $cap_data->{$subcap};
my ($self, $cap, $subcap, $depth) = @_;
my $cap_data = $self->{caps}->get_data($cap);
return 0 if not defined $cap_data;
return 1 if $cap eq $subcap and $cap_data->{$subcap};
$depth //= 10;
if (--$depth <= 0) {
$self->{pbot}->{logger}->log("Max recursion reached for PBot::Capabilities->has($cap, $subcap)\n");
$depth //= 10;
if (--$depth <= 0) {
$self->{pbot}->{logger}->log("Max recursion reached for PBot::Capabilities->has($cap, $subcap)\n");
return 0;
}
foreach my $c ($self->{caps}->get_keys($cap)) {
return 1 if $c eq $subcap and $cap_data->{$c};
return 1 if $self->has($c, $subcap, $depth);
}
return 0;
}
foreach my $c ($self->{caps}->get_keys($cap)) {
return 1 if $c eq $subcap and $cap_data->{$c};
return 1 if $self->has($c, $subcap, $depth);
}
return 0;
}
sub userhas {
my ($self, $user, $cap) = @_;
return 0 if not defined $user;
return 1 if $user->{$cap};
foreach my $key (keys %$user) {
next if $key eq '_name';
next if not $user->{$key};
return 1 if $self->has($key, $cap);
}
return 0;
my ($self, $user, $cap) = @_;
return 0 if not defined $user;
return 1 if $user->{$cap};
foreach my $key (keys %$user) {
next if $key eq '_name';
next if not $user->{$key};
return 1 if $self->has($key, $cap);
}
return 0;
}
sub exists {
my ($self, $cap) = @_;
$cap = lc $cap;
foreach my $c ($self->{caps}->get_keys) {
return 1 if $c eq $cap;
foreach my $sub_cap ($self->{caps}->get_keys($c)) {
return 1 if $sub_cap eq $cap;
my ($self, $cap) = @_;
$cap = lc $cap;
foreach my $c ($self->{caps}->get_keys) {
return 1 if $c eq $cap;
foreach my $sub_cap ($self->{caps}->get_keys($c)) { return 1 if $sub_cap eq $cap; }
}
}
return 0;
return 0;
}
sub add {
my ($self, $cap, $subcap, $dontsave) = @_;
if (not defined $subcap) {
if (not $self->{caps}->exists($cap)) {
$self->{caps}->add($cap, {}, $dontsave);
}
} else {
if ($self->{caps}->exists($cap)) {
$self->{caps}->set($cap, $subcap, 1, $dontsave);
my ($self, $cap, $subcap, $dontsave) = @_;
if (not defined $subcap) {
if (not $self->{caps}->exists($cap)) { $self->{caps}->add($cap, {}, $dontsave); }
} else {
$self->{caps}->add($cap, { $subcap => 1 }, $dontsave);
if ($self->{caps}->exists($cap)) { $self->{caps}->set($cap, $subcap, 1, $dontsave); }
else { $self->{caps}->add($cap, {$subcap => 1}, $dontsave); }
}
}
}
sub remove {
my ($self, $cap, $subcap) = @_;
$cap = lc $cap;
if (not defined $subcap) {
foreach my $c ($self->{caps}->get_keys) {
foreach my $sub_cap ($self->{caps}->get_keys($c)) {
$self->{caps}->remove($c, $sub_cap, 1) if $sub_cap eq $cap;
}
$self->{caps}->remove($c, undef, 1) if $c eq $cap;
my ($self, $cap, $subcap) = @_;
$cap = lc $cap;
if (not defined $subcap) {
foreach my $c ($self->{caps}->get_keys) {
foreach my $sub_cap ($self->{caps}->get_keys($c)) { $self->{caps}->remove($c, $sub_cap, 1) if $sub_cap eq $cap; }
$self->{caps}->remove($c, undef, 1) if $c eq $cap;
}
} else {
$self->{caps}->remove($cap, $subcap, 1) if $self->{caps}->exists($cap);
}
} else {
$self->{caps}->remove($cap, $subcap, 1) if $self->{caps}->exists($cap);
}
$self->{caps}->save;
$self->{caps}->save;
}
sub rebuild_botowner_capabilities {
my ($self) = @_;
$self->{caps}->remove('botowner', undef, 1);
foreach my $cap ($self->{caps}->get_keys) {
$self->add('botowner', $cap, 1);
}
my ($self) = @_;
$self->{caps}->remove('botowner', undef, 1);
foreach my $cap ($self->{caps}->get_keys) { $self->add('botowner', $cap, 1); }
}
sub list {
my ($self, $capability) = @_;
return "No such capability $capability." if defined $capability and not $self->{caps}->exists($capability);
my ($self, $capability) = @_;
return "No such capability $capability." if defined $capability and not $self->{caps}->exists($capability);
my @caps;
my @groups;
my @standalones;
my $result;
my @caps;
my @groups;
my @standalones;
my $result;
if (not defined $capability) {
@caps = sort $self->{caps}->get_keys;
$result = 'Capabilities: ';
} else {
@caps = sort $self->{caps}->get_keys($capability);
return "Capability $capability has no grouped capabilities." if not @caps;
$result = "Grouped capabilities for $capability: ";
}
# first list all capabilities that have sub-capabilities (i.e. grouped capabilities)
# then list stand-alone capabilities
foreach my $cap (@caps) {
my $count = $self->{caps}->get_keys($cap);
if ($count > 0) {
push @groups, "$cap ($count cap" . ($count == 1 ? '' : 's') . ")" if $count;
if (not defined $capability) {
@caps = sort $self->{caps}->get_keys;
$result = 'Capabilities: ';
} else {
push @standalones, $cap;
@caps = sort $self->{caps}->get_keys($capability);
return "Capability $capability has no grouped capabilities." if not @caps;
$result = "Grouped capabilities for $capability: ";
}
}
$result .= join ', ', @groups, @standalones;
return $result;
# first list all capabilities that have sub-capabilities (i.e. grouped capabilities)
# then list stand-alone capabilities
foreach my $cap (@caps) {
my $count = $self->{caps}->get_keys($cap);
if ($count > 0) { push @groups, "$cap ($count cap" . ($count == 1 ? '' : 's') . ")" if $count; }
else { push @standalones, $cap; }
}
$result .= join ', ', @groups, @standalones;
return $result;
}
sub capcmd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $command = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
my $result;
given ($command) {
when ('list') {
my $cap = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
return $self->list($cap);
}
when ('whohas') {
my $cap = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
return "Usage: cap whohas <capability>; Lists all users who have <capability>" if not defined $cap;
return "No such capability $cap." if not $self->exists($cap);
my $result = "Users with capability $cap: ";
my $matched = 0;
my $users = $self->{pbot}->{users}->{users};
foreach my $channel (sort $users->get_keys) {
my @matches;
foreach my $hostmask (sort $users->get_keys($channel)) {
my $u = $users->get_data($channel, $hostmask);
push @matches, $u->{name} if $self->userhas($u, $cap);
my $command = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
my $result;
given ($command) {
when ('list') {
my $cap = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
return $self->list($cap);
}
if (@matches) {
$result .= '; ' if $matched;
my $global = $matched ? 'global: ' : '';
$result .= $users->get_data($channel, '_name') eq '.*' ? $global : $users->get_data($channel, '_name') . ': ';
$result .= join ', ', @matches;
$matched = 1;
when ('whohas') {
my $cap = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
return "Usage: cap whohas <capability>; Lists all users who have <capability>" if not defined $cap;
return "No such capability $cap." if not $self->exists($cap);
my $result = "Users with capability $cap: ";
my $matched = 0;
my $users = $self->{pbot}->{users}->{users};
foreach my $channel (sort $users->get_keys) {
my @matches;
foreach my $hostmask (sort $users->get_keys($channel)) {
my $u = $users->get_data($channel, $hostmask);
push @matches, $u->{name} if $self->userhas($u, $cap);
}
if (@matches) {
$result .= '; ' if $matched;
my $global = $matched ? 'global: ' : '';
$result .= $users->get_data($channel, '_name') eq '.*' ? $global : $users->get_data($channel, '_name') . ': ';
$result .= join ', ', @matches;
$matched = 1;
}
}
$result .= 'nobody' if not $matched;
return $result;
}
}
$result .= 'nobody' if not $matched;
return $result;
}
when ('userhas') {
my ($hostmask, $cap) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
return "Usage: cap userhas <user> [capability]; Lists capabilities belonging to <user>" if not defined $hostmask;
$cap = lc $cap if defined $cap;
when ('userhas') {
my ($hostmask, $cap) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
return "Usage: cap userhas <user> [capability]; Lists capabilities belonging to <user>" if not defined $hostmask;
$cap = lc $cap if defined $cap;
my $u = $self->{pbot}->{users}->find_user($from, $hostmask, 1);
if (not defined $u) {
$from = 'global' if $from !~ /^#/;
return "No such user $hostmask in $from."
}
my $u = $self->{pbot}->{users}->find_user($from, $hostmask, 1);
if (not defined $u) {
$from = 'global' if $from !~ /^#/;
return "No such user $hostmask in $from.";
}
if (defined $cap) {
return "Try again. No such capability $cap." if not $self->exists($cap);
if ($self->userhas($u, $cap)) {
return "Yes. User $u->{name} has capability $cap.";
} else {
return "No. User $u->{name} does not have capability $cap.";
if (defined $cap) {
return "Try again. No such capability $cap." if not $self->exists($cap);
if ($self->userhas($u, $cap)) { return "Yes. User $u->{name} has capability $cap."; }
else { return "No. User $u->{name} does not have capability $cap."; }
} else {
my $result = "User $u->{name} has capabilities: ";
my @groups;
my @single;
foreach my $key (sort keys %{$u}) {
next if $key eq '_name';
next if not $self->exists($key);
my $count = $self->{caps}->get_keys;
if ($count > 0) { push @groups, "$key ($count cap" . ($count == 1 ? '' : 's') . ")"; }
else { push @single, $key; }
}
if (@groups or @single) { $result .= join ', ', @groups, @single; }
else { $result = "User $u->{name} has no capabilities."; }
return $result;
}
}
} else {
my $result = "User $u->{name} has capabilities: ";
my @groups;
my @single;
foreach my $key (sort keys %{$u}) {
next if $key eq '_name';
next if not $self->exists($key);
my $count = $self->{caps}->get_keys;
if ($count > 0) {
push @groups, "$key ($count cap" . ($count == 1 ? '' : 's') . ")";
} else {
push @single, $key;
}
when ('group') {
my ($cap, $subcaps) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
return "Usage: cap group <existing or new capability> <existing capabilities...>" if not defined $cap or not defined $subcaps;
my $u = $self->{pbot}->{users}->loggedin($from, "$nick!$user\@$host");
return "You must be logged into your user account to group capabilities together." if not defined $u;
return "You must have the can-group-capabilities capability to group capabilities together." if not $self->userhas($u, 'can-group-capabilities');
my @caps = split /\s+|,/, $subcaps;
foreach my $c (@caps) {
return "No such capability $c." if not $self->exists($c);
return "You cannot group a capability with itself." if lc $cap eq lc $c;
$self->add($cap, $c);
}
if (@caps > 1) { return "Capabilities " . join(', ', @caps) . " added to the $cap capability group."; }
else { return "Capability $subcaps added to the $cap capability group."; }
}
if (@groups or @single) {
$result .= join ', ', @groups, @single;
} else {
$result = "User $u->{name} has no capabilities.";
when ('ungroup') {
my ($cap, $subcaps) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
return "Usage: cap ungroup <existing capability group> <grouped capabilities...>" if not defined $cap or not defined $subcaps;
return "No such capability $cap." if not $self->exists($cap);
my $u = $self->{pbot}->{users}->loggedin($from, "$nick!$user\@$host");
return "You must be logged into your user account to remove capabilities from groups." if not defined $u;
return "You must have the can-ungroup-capabilities capability to remove capabilities from groups." if not $self->userhas($u, 'can-ungroup-capabilities');
my @caps = split /\s+|,/, $subcaps;
foreach my $c (@caps) {
return "No such capability $c." if not $self->exists($c);
return "Capability $c does not belong to the $cap capability group." if not $self->has($cap, $c);
$self->remove($cap, $c);
}
if (@caps > 1) { return "Capabilities " . join(', ', @caps) . " removed from the $cap capability group."; }
else { return "Capability $subcaps removed from the $cap capability group."; }
}
default {
$result =
"Usage: cap list [capability] | cap group <existing or new capability group> <existing capabilities...> | cap ungroup <existing capability group> <grouped capabilities...> | cap userhas <user> [capability] | cap whohas <capability>";
}
return $result;
}
}
when ('group') {
my ($cap, $subcaps) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
return "Usage: cap group <existing or new capability> <existing capabilities...>" if not defined $cap or not defined $subcaps;
my $u = $self->{pbot}->{users}->loggedin($from, "$nick!$user\@$host");
return "You must be logged into your user account to group capabilities together." if not defined $u;
return "You must have the can-group-capabilities capability to group capabilities together." if not $self->userhas($u, 'can-group-capabilities');
my @caps = split /\s+|,/, $subcaps;
foreach my $c (@caps) {
return "No such capability $c." if not $self->exists($c);
return "You cannot group a capability with itself." if lc $cap eq lc $c;
$self->add($cap, $c);
}
if (@caps > 1) {
return "Capabilities " . join(', ', @caps) . " added to the $cap capability group.";
} else {
return "Capability $subcaps added to the $cap capability group.";
}
}
when ('ungroup') {
my ($cap, $subcaps) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
return "Usage: cap ungroup <existing capability group> <grouped capabilities...>" if not defined $cap or not defined $subcaps;
return "No such capability $cap." if not $self->exists($cap);
my $u = $self->{pbot}->{users}->loggedin($from, "$nick!$user\@$host");
return "You must be logged into your user account to remove capabilities from groups." if not defined $u;
return "You must have the can-ungroup-capabilities capability to remove capabilities from groups." if not $self->userhas($u, 'can-ungroup-capabilities');
my @caps = split /\s+|,/, $subcaps;
foreach my $c (@caps) {
return "No such capability $c." if not $self->exists($c);
return "Capability $c does not belong to the $cap capability group." if not $self->has($cap, $c);
$self->remove($cap, $c);
}
if (@caps > 1) {
return "Capabilities " . join(', ', @caps) . " removed from the $cap capability group.";
} else {
return "Capability $subcaps removed from the $cap capability group.";
}
}
default {
$result = "Usage: cap list [capability] | cap group <existing or new capability group> <existing capabilities...> | cap ungroup <existing capability group> <grouped capabilities...> | cap userhas <user> [capability] | cap whohas <capability>";
}
}
return $result;
return $result;
}
1;

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::ChanOps;
use parent 'PBot::Class';
use warnings; use strict;
@ -18,496 +19,475 @@ use Time::HiRes qw(gettimeofday);
use Time::Duration qw(concise duration);
sub initialize {
my ($self, %conf) = @_;
my ($self, %conf) = @_;
$self->{unban_timeout} = PBot::DualIndexHashObject->new(
pbot => $self->{pbot},
name => 'Unban Timeouts',
filename => $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/unban_timeouts'
);
$self->{unban_timeout} = PBot::DualIndexHashObject->new(
pbot => $self->{pbot},
name => 'Unban Timeouts',
filename => $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/unban_timeouts'
);
$self->{unban_timeout}->load;
$self->{unban_timeout}->load;
$self->{unmute_timeout} = PBot::DualIndexHashObject->new(
pbot => $self->{pbot},
name => 'Unmute Timeouts',
filename => $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/unmute_timeouts'
);
$self->{unmute_timeout} = PBot::DualIndexHashObject->new(
pbot => $self->{pbot},
name => 'Unmute Timeouts',
filename => $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/unmute_timeouts'
);
$self->{unmute_timeout}->load;
$self->{unmute_timeout}->load;
$self->{ban_queue} = {};
$self->{unban_queue} = {};
$self->{ban_queue} = {};
$self->{unban_queue} = {};
$self->{op_commands} = {};
$self->{is_opped} = {};
$self->{op_requested} = {};
$self->{op_commands} = {};
$self->{is_opped} = {};
$self->{op_requested} = {};
$self->{commands} = PBot::ChanOpCommands->new(pbot => $self->{pbot});
$self->{commands} = PBot::ChanOpCommands->new(pbot => $self->{pbot});
$self->{pbot}->{registry}->add_default('text', 'general', 'deop_timeout', 300);
$self->{pbot}->{registry}->add_default('text', 'general', 'deop_timeout', 300);
$self->{pbot}->{timer}->register(sub { $self->check_opped_timeouts }, 10);
$self->{pbot}->{timer}->register(sub { $self->check_unban_timeouts }, 10);
$self->{pbot}->{timer}->register(sub { $self->check_unmute_timeouts }, 10);
$self->{pbot}->{timer}->register(sub { $self->check_unban_queue }, 30);
$self->{pbot}->{timer}->register(sub { $self->check_opped_timeouts }, 10);
$self->{pbot}->{timer}->register(sub { $self->check_unban_timeouts }, 10);
$self->{pbot}->{timer}->register(sub { $self->check_unmute_timeouts }, 10);
$self->{pbot}->{timer}->register(sub { $self->check_unban_queue }, 30);
}
sub can_gain_ops {
my ($self, $channel) = @_;
$channel = lc $channel;
return $self->{pbot}->{channels}->{channels}->exists($channel)
&& $self->{pbot}->{channels}->{channels}->get_data($channel, 'chanop')
&& $self->{pbot}->{channels}->{channels}->get_data($channel, 'enabled');
my ($self, $channel) = @_;
$channel = lc $channel;
return
$self->{pbot}->{channels}->{channels}->exists($channel)
&& $self->{pbot}->{channels}->{channels}->get_data($channel, 'chanop')
&& $self->{pbot}->{channels}->{channels}->get_data($channel, 'enabled');
}
sub gain_ops {
my $self = shift;
my $channel = shift;
$channel = lc $channel;
my $self = shift;
my $channel = shift;
$channel = lc $channel;
return if exists $self->{op_requested}->{$channel};
return if not $self->can_gain_ops($channel);
return if exists $self->{op_requested}->{$channel};
return if not $self->can_gain_ops($channel);
my $op_nick = $self->{pbot}->{registry}->get_value($channel, 'op_nick') //
$self->{pbot}->{registry}->get_value('general', 'op_nick') // 'chanserv';
my $op_nick = $self->{pbot}->{registry}->get_value($channel, 'op_nick') // $self->{pbot}->{registry}->get_value('general', 'op_nick') // 'chanserv';
my $op_command = $self->{pbot}->{registry}->get_value($channel, 'op_command') //
$self->{pbot}->{registry}->get_value('general', 'op_command') // "op $channel";
my $op_command = $self->{pbot}->{registry}->get_value($channel, 'op_command') // $self->{pbot}->{registry}->get_value('general', 'op_command') // "op $channel";
$op_command =~ s/\$channel\b/$channel/g;
$op_command =~ s/\$channel\b/$channel/g;
if (not exists $self->{is_opped}->{$channel}) {
$self->{pbot}->{conn}->privmsg($op_nick, $op_command);
$self->{op_requested}->{$channel} = scalar gettimeofday;
} else {
$self->perform_op_commands($channel);
}
if (not exists $self->{is_opped}->{$channel}) {
$self->{pbot}->{conn}->privmsg($op_nick, $op_command);
$self->{op_requested}->{$channel} = scalar gettimeofday;
} else {
$self->perform_op_commands($channel);
}
}
sub lose_ops {
my $self = shift;
my $channel = shift;
$channel = lc $channel;
$self->{pbot}->{conn}->mode($channel, '-o ' . $self->{pbot}->{registry}->get_value('irc', 'botnick'));
my $self = shift;
my $channel = shift;
$channel = lc $channel;
$self->{pbot}->{conn}->mode($channel, '-o ' . $self->{pbot}->{registry}->get_value('irc', 'botnick'));
}
sub add_op_command {
my ($self, $channel, $command) = @_;
$channel = lc $channel;
return if not $self->can_gain_ops($channel);
push @{ $self->{op_commands}->{$channel} }, $command;
my ($self, $channel, $command) = @_;
$channel = lc $channel;
return if not $self->can_gain_ops($channel);
push @{$self->{op_commands}->{$channel}}, $command;
}
sub perform_op_commands {
my $self = shift;
my $channel = shift;
$channel = lc $channel;
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
my $self = shift;
my $channel = shift;
$channel = lc $channel;
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
$self->{pbot}->{logger}->log("Performing op commands...\n");
while (my $command = shift @{ $self->{op_commands}->{$channel} }) {
if ($command =~ /^mode (.*?) (.*)/i) {
$self->{pbot}->{conn}->mode($1, $2);
$self->{pbot}->{logger}->log(" executing mode $1 $2\n");
} elsif ($command =~ /^kick (.*?) (.*?) (.*)/i) {
$self->{pbot}->{conn}->kick($1, $2, $3) unless $1 =~ /^\Q$botnick\E$/i;
$self->{pbot}->{logger}->log(" executing kick on $1 $2 $3\n");
} elsif ($command =~ /^sl (.*)/i) {
$self->{pbot}->{conn}->sl($1);
$self->{pbot}->{logger}->log(" executing sl $1\n");
$self->{pbot}->{logger}->log("Performing op commands...\n");
while (my $command = shift @{$self->{op_commands}->{$channel}}) {
if ($command =~ /^mode (.*?) (.*)/i) {
$self->{pbot}->{conn}->mode($1, $2);
$self->{pbot}->{logger}->log(" executing mode $1 $2\n");
} elsif ($command =~ /^kick (.*?) (.*?) (.*)/i) {
$self->{pbot}->{conn}->kick($1, $2, $3) unless $1 =~ /^\Q$botnick\E$/i;
$self->{pbot}->{logger}->log(" executing kick on $1 $2 $3\n");
} elsif ($command =~ /^sl (.*)/i) {
$self->{pbot}->{conn}->sl($1);
$self->{pbot}->{logger}->log(" executing sl $1\n");
}
}
}
$self->{pbot}->{logger}->log("Done.\n");
$self->{pbot}->{logger}->log("Done.\n");
}
sub ban_user {
my $self = shift;
my ($mask, $channel, $immediately) = @_;
$self->add_to_ban_queue($channel, 'b', $mask);
if (not defined $immediately or $immediately != 0) {
$self->check_ban_queue;
}
my $self = shift;
my ($mask, $channel, $immediately) = @_;
$self->add_to_ban_queue($channel, 'b', $mask);
if (not defined $immediately or $immediately != 0) { $self->check_ban_queue; }
}
sub get_bans {
my ($self, $mask, $channel) = @_;
my $masks;
my ($self, $mask, $channel) = @_;
my $masks;
if ($mask !~ m/[!@\$]/) {
my ($message_account, $hostmask) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($mask);
if ($mask !~ m/[!@\$]/) {
my ($message_account, $hostmask) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($mask);
if (defined $hostmask) {
my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($message_account);
$masks = $self->{pbot}->{bantracker}->get_baninfo($hostmask, $channel, $nickserv);
if (defined $hostmask) {
my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($message_account);
$masks = $self->{pbot}->{bantracker}->get_baninfo($hostmask, $channel, $nickserv);
}
my %akas = $self->{pbot}->{messagehistory}->{database}->get_also_known_as($mask);
foreach my $aka (keys %akas) {
next if $akas{$aka}->{type} == $self->{pbot}->{messagehistory}->{database}->{alias_type}->{WEAK};
next if $akas{$aka}->{nickchange} == 1;
my $b = $self->{pbot}->{bantracker}->get_baninfo($aka, $channel);
if (defined $b) {
$masks = {} if not defined $masks;
push @$masks, @$b;
}
}
}
my %akas = $self->{pbot}->{messagehistory}->{database}->get_also_known_as($mask);
foreach my $aka (keys %akas) {
next if $akas{$aka}->{type} == $self->{pbot}->{messagehistory}->{database}->{alias_type}->{WEAK};
next if $akas{$aka}->{nickchange} == 1;
my $b = $self->{pbot}->{bantracker}->get_baninfo($aka, $channel);
if (defined $b) {
$masks = {} if not defined $masks;
push @$masks, @$b;
}
}
}
return $masks
return $masks;
}
sub unmode_user {
my ($self, $mask, $channel, $immediately, $mode) = @_;
my ($self, $mask, $channel, $immediately, $mode) = @_;
$mask = lc $mask;
$channel = lc $channel;
$self->{pbot}->{logger}->log("Removing mode $mode from $mask in $channel\n");
$mask = lc $mask;
$channel = lc $channel;
$self->{pbot}->{logger}->log("Removing mode $mode from $mask in $channel\n");
my $bans = $self->get_bans($mask, $channel);
my %unbanned;
my $bans = $self->get_bans($mask, $channel);
my %unbanned;
if (not defined $bans) {
my $baninfo = {};
$baninfo->{banmask} = $mask;
$baninfo->{type} = '+' . $mode;
push @$bans, $baninfo;
}
if (not defined $bans) {
my $baninfo = {};
$baninfo->{banmask} = $mask;
$baninfo->{type} = '+' . $mode;
push @$bans, $baninfo;
}
foreach my $baninfo (@$bans) {
next if $baninfo->{type} ne '+' . $mode;
next if exists $unbanned{$baninfo->{banmask}};
$unbanned{$baninfo->{banmask}} = 1;
$self->add_to_unban_queue($channel, $mode, $baninfo->{banmask});
}
$self->check_unban_queue if $immediately;
foreach my $baninfo (@$bans) {
next if $baninfo->{type} ne '+' . $mode;
next if exists $unbanned{$baninfo->{banmask}};
$unbanned{$baninfo->{banmask}} = 1;
$self->add_to_unban_queue($channel, $mode, $baninfo->{banmask});
}
$self->check_unban_queue if $immediately;
}
sub nick_to_banmask {
my ($self, $mask) = @_;
my ($self, $mask) = @_;
if ($mask !~ m/[!@\$]/) {
my ($message_account, $hostmask) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($mask);
if (defined $hostmask) {
my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($message_account);
if (defined $nickserv && length $nickserv) {
$mask = '$a:' . $nickserv;
} else {
my ($nick, $user, $host) = $hostmask =~ m/([^!]+)!([^@]+)@(.*)/;
$mask = "*!$user\@" .$self->{pbot}->{antiflood}->address_to_mask($host);
}
} else {
$mask .= '!*@*';
if ($mask !~ m/[!@\$]/) {
my ($message_account, $hostmask) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($mask);
if (defined $hostmask) {
my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($message_account);
if (defined $nickserv && length $nickserv) { $mask = '$a:' . $nickserv; }
else {
my ($nick, $user, $host) = $hostmask =~ m/([^!]+)!([^@]+)@(.*)/;
$mask = "*!$user\@" . $self->{pbot}->{antiflood}->address_to_mask($host);
}
} else {
$mask .= '!*@*';
}
}
}
return $mask;
return $mask;
}
sub unban_user {
my ($self, $mask, $channel, $immediately) = @_;
$mask = lc $mask;
$channel = lc $channel;
$self->{pbot}->{logger}->log("Unbanning $channel $mask\n");
$self->unmode_user($mask, $channel, $immediately, 'b');
my ($self, $mask, $channel, $immediately) = @_;
$mask = lc $mask;
$channel = lc $channel;
$self->{pbot}->{logger}->log("Unbanning $channel $mask\n");
$self->unmode_user($mask, $channel, $immediately, 'b');
}
sub ban_user_timed {
my $self = shift;
my ($owner, $reason, $mask, $channel, $length, $immediately) = @_;
my $self = shift;
my ($owner, $reason, $mask, $channel, $length, $immediately) = @_;
$channel = lc $channel;
$mask = lc $mask;
$channel = lc $channel;
$mask = lc $mask;
$mask = $self->nick_to_banmask($mask);
$self->ban_user($mask, $channel, $immediately);
$mask = $self->nick_to_banmask($mask);
$self->ban_user($mask, $channel, $immediately);
if ($length > 0) {
my $data = {
timeout => gettimeofday + $length
};
$data->{owner} = $owner if defined $owner;
$data->{reason} = $reason if defined $reason;
$self->{unban_timeout}->add($channel, $mask, $data);
} else {
if ($self->{unban_timeout}->exists($channel, $mask)) {
$self->{unban_timeout}->remove($channel, $mask);
if ($length > 0) {
my $data = {timeout => gettimeofday + $length};
$data->{owner} = $owner if defined $owner;
$data->{reason} = $reason if defined $reason;
$self->{unban_timeout}->add($channel, $mask, $data);
} else {
if ($self->{unban_timeout}->exists($channel, $mask)) { $self->{unban_timeout}->remove($channel, $mask); }
}
}
}
sub checkban {
my ($self, $channel, $target) = @_;
my $mask = $self->nick_to_banmask($target);
my ($self, $channel, $target) = @_;
my $mask = $self->nick_to_banmask($target);
if ($self->{unban_timeout}->exists($channel, $mask)) {
my $timeout = $self->{unban_timeout}->get_data($channel, $mask, 'timeout');
my $owner = $self->{unban_timeout}->get_data($channel, $mask, 'owner');
my $reason = $self->{unban_timeout}->get_data($channel, $mask, 'reason');
my $duration = concise duration($timeout - gettimeofday);
if ($self->{unban_timeout}->exists($channel, $mask)) {
my $result = "$mask banned in $channel ";
$result .= "by $owner " if defined $owner;
$result .= "for $reason " if defined $reason;
$result .= "($duration remaining)";
return $result;
} else {
return "$mask has no ban timeout.";
}
my $timeout = $self->{unban_timeout}->get_data($channel, $mask, 'timeout');
my $owner = $self->{unban_timeout}->get_data($channel, $mask, 'owner');
my $reason = $self->{unban_timeout}->get_data($channel, $mask, 'reason');
my $duration = concise duration($timeout - gettimeofday);
my $result = "$mask banned in $channel ";
$result .= "by $owner " if defined $owner;
$result .= "for $reason " if defined $reason;
$result .= "($duration remaining)";
return $result;
} else {
return "$mask has no ban timeout.";
}
}
sub mute_user {
my $self = shift;
my ($mask, $channel, $immediately) = @_;
$self->add_to_ban_queue($channel, 'q', $mask);
if (not defined $immediately or $immediately != 0) {
$self->check_ban_queue;
}
my $self = shift;
my ($mask, $channel, $immediately) = @_;
$self->add_to_ban_queue($channel, 'q', $mask);
if (not defined $immediately or $immediately != 0) { $self->check_ban_queue; }
}
sub unmute_user {
my ($self, $mask, $channel, $immediately) = @_;
$mask = lc $mask;
$channel = lc $channel;
$self->{pbot}->{logger}->log("Unmuting $channel $mask\n");
$self->unmode_user($mask, $channel, $immediately, 'q');
my ($self, $mask, $channel, $immediately) = @_;
$mask = lc $mask;
$channel = lc $channel;
$self->{pbot}->{logger}->log("Unmuting $channel $mask\n");
$self->unmode_user($mask, $channel, $immediately, 'q');
}
sub mute_user_timed {
my $self = shift;
my ($owner, $reason, $mask, $channel, $length, $immediately) = @_;
my $self = shift;
my ($owner, $reason, $mask, $channel, $length, $immediately) = @_;
$mask = $self->nick_to_banmask($mask);
$self->mute_user($mask, $channel, $immediately);
$mask = $self->nick_to_banmask($mask);
$self->mute_user($mask, $channel, $immediately);
if ($length > 0) {
my $data = {
timeout => gettimeofday + $length
};
$data->{owner} = $owner if defined $owner;
$data->{reason} = $reason if defined $reason;
$self->{unmute_timeout}->add($channel, $mask, $data);
} else {
if ($self->{unmute_timeout}->exists($channel, $mask)) {
$self->{unmute_timeout}->remove($channel, $mask);
if ($length > 0) {
my $data = {timeout => gettimeofday + $length};
$data->{owner} = $owner if defined $owner;
$data->{reason} = $reason if defined $reason;
$self->{unmute_timeout}->add($channel, $mask, $data);
} else {
if ($self->{unmute_timeout}->exists($channel, $mask)) { $self->{unmute_timeout}->remove($channel, $mask); }
}
}
}
sub checkmute {
my ($self, $channel, $target) = @_;
my $mask = $self->nick_to_banmask($target);
my ($self, $channel, $target) = @_;
my $mask = $self->nick_to_banmask($target);
if ($self->{unmute_timeout}->exists($channel, $mask)) {
my $timeout = $self->{unmute_timeout}->get_data($channel, $mask, 'timeout');
my $owner = $self->{unmute_timeout}->get_data($channel, $mask, 'owner');
my $reason = $self->{unmute_timeout}->get_data($channel, $mask, 'reason');
my $duration = concise duration($timeout - gettimeofday);
if ($self->{unmute_timeout}->exists($channel, $mask)) {
my $result = "$mask muted in $channel ";
$result .= "by $owner " if defined $owner;
$result .= "for $reason " if defined $reason;
$result .= "($duration remaining)";
my $timeout = $self->{unmute_timeout}->get_data($channel, $mask, 'timeout');
my $owner = $self->{unmute_timeout}->get_data($channel, $mask, 'owner');
my $reason = $self->{unmute_timeout}->get_data($channel, $mask, 'reason');
my $duration = concise duration($timeout - gettimeofday);
return $result;
} else {
return "$mask has no mute timeout.";
}
my $result = "$mask muted in $channel ";
$result .= "by $owner " if defined $owner;
$result .= "for $reason " if defined $reason;
$result .= "($duration remaining)";
return $result;
} else {
return "$mask has no mute timeout.";
}
}
sub join_channel {
my ($self, $channels) = @_;
my ($self, $channels) = @_;
$self->{pbot}->{conn}->join($channels);
$self->{pbot}->{conn}->join($channels);
foreach my $channel (split /,/, $channels) {
$channel = lc $channel;
$self->{pbot}->{event_dispatcher}->dispatch_event('pbot.join', { channel => $channel });
foreach my $channel (split /,/, $channels) {
$channel = lc $channel;
$self->{pbot}->{event_dispatcher}->dispatch_event('pbot.join', {channel => $channel});
delete $self->{is_opped}->{$channel};
delete $self->{op_requested}->{$channel};
delete $self->{is_opped}->{$channel};
delete $self->{op_requested}->{$channel};
if ($self->{pbot}->{channels}->{channels}->exists($channel)
and $self->{pbot}->{channels}->{channels}->get_data($channel, 'permop')) {
$self->gain_ops($channel);
if ($self->{pbot}->{channels}->{channels}->exists($channel) and $self->{pbot}->{channels}->{channels}->get_data($channel, 'permop')) { $self->gain_ops($channel); }
$self->{pbot}->{conn}->mode($channel);
}
$self->{pbot}->{conn}->mode($channel);
}
}
sub part_channel {
my ($self, $channel) = @_;
$channel = lc $channel;
$self->{pbot}->{event_dispatcher}->dispatch_event('pbot.part', { channel => $channel });
$self->{pbot}->{conn}->part($channel);
delete $self->{is_opped}->{$channel};
delete $self->{op_requested}->{$channel};
my ($self, $channel) = @_;
$channel = lc $channel;
$self->{pbot}->{event_dispatcher}->dispatch_event('pbot.part', {channel => $channel});
$self->{pbot}->{conn}->part($channel);
delete $self->{is_opped}->{$channel};
delete $self->{op_requested}->{$channel};
}
sub has_ban_timeout {
my ($self, $channel, $mask) = @_;
return $self->{unban_timeout}->exists($channel, $mask);
my ($self, $channel, $mask) = @_;
return $self->{unban_timeout}->exists($channel, $mask);
}
sub has_mute_timeout {
my ($self, $channel, $mask) = @_;
return $self->{unmute_timeout}->exists($channel, $mask);
my ($self, $channel, $mask) = @_;
return $self->{unmute_timeout}->exists($channel, $mask);
}
sub add_to_ban_queue {
my ($self, $channel, $mode, $target) = @_;
push @{$self->{ban_queue}->{$channel}->{$mode}}, $target;
$self->{pbot}->{logger}->log("Added +$mode $target for $channel to ban queue.\n");
my ($self, $channel, $mode, $target) = @_;
push @{$self->{ban_queue}->{$channel}->{$mode}}, $target;
$self->{pbot}->{logger}->log("Added +$mode $target for $channel to ban queue.\n");
}
sub check_ban_queue {
my $self = shift;
my $self = shift;
my $MAX_COMMANDS = 4;
my $commands = 0;
my $MAX_COMMANDS = 4;
my $commands = 0;
foreach my $channel (keys %{$self->{ban_queue}}) {
my $done = 0;
while (not $done) {
my ($list, $count, $modes);
$list = '';
$modes = '+';
$count = 0;
foreach my $channel (keys %{$self->{ban_queue}}) {
my $done = 0;
while (not $done) {
my ($list, $count, $modes);
$list = '';
$modes = '+';
$count = 0;
foreach my $mode (keys %{$self->{ban_queue}->{$channel}}) {
while (@{$self->{ban_queue}->{$channel}->{$mode}}) {
my $target = pop @{$self->{ban_queue}->{$channel}->{$mode}};
$list .= " $target";
$modes .= $mode;
last if ++$count >= $self->{pbot}->{ircd}->{MODES};
foreach my $mode (keys %{$self->{ban_queue}->{$channel}}) {
while (@{$self->{ban_queue}->{$channel}->{$mode}}) {
my $target = pop @{$self->{ban_queue}->{$channel}->{$mode}};
$list .= " $target";
$modes .= $mode;
last if ++$count >= $self->{pbot}->{ircd}->{MODES};
}
if (not @{$self->{ban_queue}->{$channel}->{$mode}}) { delete $self->{ban_queue}->{$channel}->{$mode}; }
last if $count >= $self->{pbot}->{ircd}->{MODES};
}
if (not keys %{$self->{ban_queue}->{$channel}}) {
delete $self->{ban_queue}->{$channel};
$done = 1;
}
if ($count) {
$self->add_op_command($channel, "mode $channel $modes $list");
$self->gain_ops($channel);
return if ++$commands >= $MAX_COMMANDS;
}
}
if (not @{$self->{ban_queue}->{$channel}->{$mode}}) {
delete $self->{ban_queue}->{$channel}->{$mode};
}
last if $count >= $self->{pbot}->{ircd}->{MODES};
}
if (not keys %{ $self->{ban_queue}->{$channel} }) {
delete $self->{ban_queue}->{$channel};
$done = 1;
}
if ($count) {
$self->add_op_command($channel, "mode $channel $modes $list");
$self->gain_ops($channel);
return if ++$commands >= $MAX_COMMANDS;
}
}
}
}
sub add_to_unban_queue {
my ($self, $channel, $mode, $target) = @_;
push @{$self->{unban_queue}->{$channel}->{$mode}}, $target;
$self->{pbot}->{logger}->log("Added -$mode $target for $channel to unban queue.\n");
my ($self, $channel, $mode, $target) = @_;
push @{$self->{unban_queue}->{$channel}->{$mode}}, $target;
$self->{pbot}->{logger}->log("Added -$mode $target for $channel to unban queue.\n");
}
sub check_unban_queue {
my $self = shift;
my $self = shift;
my $MAX_COMMANDS = 4;
my $commands = 0;
my $MAX_COMMANDS = 4;
my $commands = 0;
foreach my $channel (keys %{$self->{unban_queue}}) {
my $done = 0;
while (not $done) {
my ($list, $count, $modes);
$list = '';
$modes = '-';
$count = 0;
foreach my $channel (keys %{$self->{unban_queue}}) {
my $done = 0;
while (not $done) {
my ($list, $count, $modes);
$list = '';
$modes = '-';
$count = 0;
foreach my $mode (keys %{$self->{unban_queue}->{$channel}}) {
while (@{$self->{unban_queue}->{$channel}->{$mode}}) {
my $target = pop @{$self->{unban_queue}->{$channel}->{$mode}};
$list .= " $target";
$modes .= $mode;
last if ++$count >= $self->{pbot}->{ircd}->{MODES};
foreach my $mode (keys %{$self->{unban_queue}->{$channel}}) {
while (@{$self->{unban_queue}->{$channel}->{$mode}}) {
my $target = pop @{$self->{unban_queue}->{$channel}->{$mode}};
$list .= " $target";
$modes .= $mode;
last if ++$count >= $self->{pbot}->{ircd}->{MODES};
}
if (not @{$self->{unban_queue}->{$channel}->{$mode}}) { delete $self->{unban_queue}->{$channel}->{$mode}; }
last if $count >= $self->{pbot}->{ircd}->{MODES};
}
if (not keys %{$self->{unban_queue}->{$channel}}) {
delete $self->{unban_queue}->{$channel};
$done = 1;
}
if ($count) {
$self->add_op_command($channel, "mode $channel $modes $list");
$self->gain_ops($channel);
return if ++$commands >= $MAX_COMMANDS;
}
}
if (not @{$self->{unban_queue}->{$channel}->{$mode}}) {
delete $self->{unban_queue}->{$channel}->{$mode};
}
last if $count >= $self->{pbot}->{ircd}->{MODES};
}
if (not keys %{ $self->{unban_queue}->{$channel} }) {
delete $self->{unban_queue}->{$channel};
$done = 1;
}
if ($count) {
$self->add_op_command($channel, "mode $channel $modes $list");
$self->gain_ops($channel);
return if ++$commands >= $MAX_COMMANDS;
}
}
}
}
sub check_unban_timeouts {
my $self = shift;
return if not $self->{pbot}->{joined_channels};
my $now = gettimeofday();
foreach my $channel ($self->{unban_timeout}->get_keys) {
foreach my $mask ($self->{unban_timeout}->get_keys($channel)) {
if ($self->{unban_timeout}->get_data($channel, $mask, 'timeout') < $now) {
$self->{unban_timeout}->set($channel, $mask, 'timeout', $now + 7200);
$self->unban_user($mask, $channel);
}
my $self = shift;
return if not $self->{pbot}->{joined_channels};
my $now = gettimeofday();
foreach my $channel ($self->{unban_timeout}->get_keys) {
foreach my $mask ($self->{unban_timeout}->get_keys($channel)) {
if ($self->{unban_timeout}->get_data($channel, $mask, 'timeout') < $now) {
$self->{unban_timeout}->set($channel, $mask, 'timeout', $now + 7200);
$self->unban_user($mask, $channel);
}
}
}
}
}
sub check_unmute_timeouts {
my $self = shift;
return if not $self->{pbot}->{joined_channels};
my $now = gettimeofday();
foreach my $channel ($self->{unmute_timeout}->get_keys) {
foreach my $mask ($self->{unmute_timeout}->get_keys($channel)) {
if ($self->{unmute_timeout}->get_data($channel, $mask, 'timeout') < $now) {
$self->{unmute_timeout}->set($channel, $mask, 'timeout', $now + 7200);
$self->unmute_user($mask, $channel);
}
my $self = shift;
return if not $self->{pbot}->{joined_channels};
my $now = gettimeofday();
foreach my $channel ($self->{unmute_timeout}->get_keys) {
foreach my $mask ($self->{unmute_timeout}->get_keys($channel)) {
if ($self->{unmute_timeout}->get_data($channel, $mask, 'timeout') < $now) {
$self->{unmute_timeout}->set($channel, $mask, 'timeout', $now + 7200);
$self->unmute_user($mask, $channel);
}
}
}
}
}
sub check_opped_timeouts {
my $self = shift;
my $now = gettimeofday();
foreach my $channel (keys %{ $self->{is_opped} }) {
if ($self->{is_opped}->{$channel}{timeout} < $now) {
unless ($self->{pbot}->{channels}->{channels}->exists($channel)
and $self->{pbot}->{channels}->{channels}->get_data($channel, 'permop')) {
$self->lose_ops($channel);
}
my $self = shift;
my $now = gettimeofday();
foreach my $channel (keys %{$self->{is_opped}}) {
if ($self->{is_opped}->{$channel}{timeout} < $now) {
unless ($self->{pbot}->{channels}->{channels}->exists($channel) and $self->{pbot}->{channels}->{channels}->get_data($channel, 'permop')) { $self->lose_ops($channel); }
}
}
}
foreach my $channel (keys %{ $self->{op_requested} }) {
if ($now - $self->{op_requested}->{$channel} > 60 * 5) {
if ($self->{pbot}->{channels}->{channels}->exists($channel)
and $self->{pbot}->{channels}->{channels}->get_data($channel, 'enabled')) {
$self->{pbot}->{logger}->log("5 minutes since OP request for $channel and no OP yet; trying again ...\n");
delete $self->{op_requested}->{$channel};
$self->gain_ops($channel);
} else {
$self->{pbot}->{logger}->log("Disregarding OP request for $channel (channel is disabled)\n");
delete $self->{op_requested}->{$channel};
}
foreach my $channel (keys %{$self->{op_requested}}) {
if ($now - $self->{op_requested}->{$channel} > 60 * 5) {
if ($self->{pbot}->{channels}->{channels}->exists($channel) and $self->{pbot}->{channels}->{channels}->get_data($channel, 'enabled')) {
$self->{pbot}->{logger}->log("5 minutes since OP request for $channel and no OP yet; trying again ...\n");
delete $self->{op_requested}->{$channel};
$self->gain_ops($channel);
} else {
$self->{pbot}->{logger}->log("Disregarding OP request for $channel (channel is disabled)\n");
delete $self->{op_requested}->{$channel};
}
}
}
}
}
1;

View File

@ -14,130 +14,125 @@ use warnings; use strict;
use feature 'unicode_strings';
sub initialize {
my ($self, %conf) = @_;
$self->{channels} = PBot::HashObject->new(pbot => $self->{pbot}, name => 'Channels', filename => $conf{filename});
$self->{channels}->load;
my ($self, %conf) = @_;
$self->{channels} = PBot::HashObject->new(pbot => $self->{pbot}, name => 'Channels', filename => $conf{filename});
$self->{channels}->load;
$self->{pbot}->{commands}->register(sub { $self->join(@_) }, "join", 1);
$self->{pbot}->{commands}->register(sub { $self->part(@_) }, "part", 1);
$self->{pbot}->{commands}->register(sub { $self->set(@_) }, "chanset", 1);
$self->{pbot}->{commands}->register(sub { $self->unset(@_) }, "chanunset", 1);
$self->{pbot}->{commands}->register(sub { $self->add(@_) }, "chanadd", 1);
$self->{pbot}->{commands}->register(sub { $self->remove(@_) }, "chanrem", 1);
$self->{pbot}->{commands}->register(sub { $self->list(@_) }, "chanlist", 1);
$self->{pbot}->{commands}->register(sub { $self->join(@_) }, "join", 1);
$self->{pbot}->{commands}->register(sub { $self->part(@_) }, "part", 1);
$self->{pbot}->{commands}->register(sub { $self->set(@_) }, "chanset", 1);
$self->{pbot}->{commands}->register(sub { $self->unset(@_) }, "chanunset", 1);
$self->{pbot}->{commands}->register(sub { $self->add(@_) }, "chanadd", 1);
$self->{pbot}->{commands}->register(sub { $self->remove(@_) }, "chanrem", 1);
$self->{pbot}->{commands}->register(sub { $self->list(@_) }, "chanlist", 1);
$self->{pbot}->{capabilities}->add('admin', 'can-join', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-part', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-chanlist', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-join', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-part', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-chanlist', 1);
}
sub join {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
foreach my $channel (split /[\s+,]/, $arguments) {
$self->{pbot}->{logger}->log("$nick!$user\@$host made me join $channel\n");
$self->{pbot}->{chanops}->join_channel($channel);
}
return "/msg $nick Joining $arguments";
my ($self, $from, $nick, $user, $host, $arguments) = @_;
foreach my $channel (split /[\s+,]/, $arguments) {
$self->{pbot}->{logger}->log("$nick!$user\@$host made me join $channel\n");
$self->{pbot}->{chanops}->join_channel($channel);
}
return "/msg $nick Joining $arguments";
}
sub part {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
$arguments = $from if not $arguments;
foreach my $channel (split /[\s+,]/, $arguments) {
$self->{pbot}->{logger}->log("$nick!$user\@$host made me part $channel\n");
$self->{pbot}->{chanops}->part_channel($channel);
}
return "/msg $nick Parting $arguments";
my ($self, $from, $nick, $user, $host, $arguments) = @_;
$arguments = $from if not $arguments;
foreach my $channel (split /[\s+,]/, $arguments) {
$self->{pbot}->{logger}->log("$nick!$user\@$host made me part $channel\n");
$self->{pbot}->{chanops}->part_channel($channel);
}
return "/msg $nick Parting $arguments";
}
sub set {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($channel, $key, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 3);
return "Usage: chanset <channel> [key [value]]" if not defined $channel;
return $self->{channels}->set($channel, $key, $value);
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($channel, $key, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 3);
return "Usage: chanset <channel> [key [value]]" if not defined $channel;
return $self->{channels}->set($channel, $key, $value);
}
sub unset {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($channel, $key) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
return "Usage: chanunset <channel> <key>" if not defined $channel or not defined $key;
return $self->{channels}->unset($channel, $key);
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($channel, $key) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
return "Usage: chanunset <channel> <key>" if not defined $channel or not defined $key;
return $self->{channels}->unset($channel, $key);
}
sub add {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
return "Usage: chanadd <channel>" if not defined $arguments or not length $arguments;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
return "Usage: chanadd <channel>" if not defined $arguments or not length $arguments;
my $data = {
enabled => 1,
chanop => 0,
permop => 0
};
my $data = {
enabled => 1,
chanop => 0,
permop => 0
};
return $self->{channels}->add($arguments, $data);
return $self->{channels}->add($arguments, $data);
}
sub remove {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
return "Usage: chanrem <channel>" if not defined $arguments or not length $arguments;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
return "Usage: chanrem <channel>" if not defined $arguments or not length $arguments;
# clear unban timeouts
if ($self->{pbot}->{chanops}->{unban_timeout}->exists($arguments)) {
$self->{pbot}->{chanops}->{unban_timeout}->remove($arguments);
}
# clear unban timeouts
if ($self->{pbot}->{chanops}->{unban_timeout}->exists($arguments)) { $self->{pbot}->{chanops}->{unban_timeout}->remove($arguments); }
# clear unmute timeouts
if ($self->{pbot}->{chanops}->{unmute_timeout}->exists($arguments)) {
$self->{pbot}->{chanops}->{unmute_timeout}->remove($arguments);
}
# clear unmute timeouts
if ($self->{pbot}->{chanops}->{unmute_timeout}->exists($arguments)) { $self->{pbot}->{chanops}->{unmute_timeout}->remove($arguments); }
# TODO: ignores, etc?
return $self->{channels}->remove($arguments);
# TODO: ignores, etc?
return $self->{channels}->remove($arguments);
}
sub list {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $result;
foreach my $channel (sort $self->{channels}->get_keys) {
$result .= $self->{channels}->get_data($channel, '_name') . ': {';
my $comma = ' ';
foreach my $key (sort $self->{channels}->get_keys($channel)) {
$result .= "$comma$key => " . $self->{channels}->get_data($channel, $key);
$comma = ', ';
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $result;
foreach my $channel (sort $self->{channels}->get_keys) {
$result .= $self->{channels}->get_data($channel, '_name') . ': {';
my $comma = ' ';
foreach my $key (sort $self->{channels}->get_keys($channel)) {
$result .= "$comma$key => " . $self->{channels}->get_data($channel, $key);
$comma = ', ';
}
$result .= " }\n";
}
$result .= " }\n";
}
return $result;
return $result;
}
sub autojoin {
my ($self) = @_;
return if $self->{pbot}->{joined_channels};
my $channels;
foreach my $channel ($self->{channels}->get_keys) {
if ($self->{channels}->get_data($channel, 'enabled')) {
$channels .= $self->{channels}->get_data($channel, '_name') . ',';
my ($self) = @_;
return if $self->{pbot}->{joined_channels};
my $channels;
foreach my $channel ($self->{channels}->get_keys) {
if ($self->{channels}->get_data($channel, 'enabled')) { $channels .= $self->{channels}->get_data($channel, '_name') . ','; }
}
}
$self->{pbot}->{logger}->log("Joining channels: $channels\n");
$self->{pbot}->{chanops}->join_channel($channels);
$self->{pbot}->{joined_channels} = 1;
$self->{pbot}->{logger}->log("Joining channels: $channels\n");
$self->{pbot}->{chanops}->join_channel($channels);
$self->{pbot}->{joined_channels} = 1;
}
sub is_active {
my ($self, $channel) = @_;
# returns undef if channel doesn't exist; otherwise, the value of 'enabled'
return $self->{channels}->get_data($channel, 'enabled');
my ($self, $channel) = @_;
# returns undef if channel doesn't exist; otherwise, the value of 'enabled'
return $self->{channels}->get_data($channel, 'enabled');
}
sub is_active_op {
my ($self, $channel) = @_;
return $self->is_active($channel) && $self->{channels}->get_data($channel, 'chanop');
my ($self, $channel) = @_;
return $self->is_active($channel) && $self->{channels}->get_data($channel, 'chanop');
}
sub get_meta {
my ($self, $channel, $key) = @_;
return $self->{channels}->get_data($channel, $key);
my ($self, $channel, $key) = @_;
return $self->{channels}->get_data($channel, $key);
}
1;

View File

@ -13,26 +13,26 @@ use warnings;
use strict;
sub new {
my ($proto, %conf) = @_;
my $class = ref($proto) || $proto;
my $self = bless {}, $class;
my ($proto, %conf) = @_;
my $class = ref($proto) || $proto;
my $self = bless {}, $class;
if (not exists $conf{pbot}) {
my ($package, $filename, $line) = caller(0);
my (undef, undef, undef, $subroutine) = caller(1);
Carp::croak("Missing pbot reference to " . $class . ", created by $subroutine at $filename:$line");
}
if (not exists $conf{pbot}) {
my ($package, $filename, $line) = caller(0);
my (undef, undef, undef, $subroutine) = caller(1);
Carp::croak("Missing pbot reference to " . $class . ", created by $subroutine at $filename:$line");
}
$self->{pbot} = $conf{pbot};
$self->{pbot}->{logger}->log("Initializing $class\n");
$self->initialize(%conf);
return $self;
$self->{pbot} = $conf{pbot};
$self->{pbot}->{logger}->log("Initializing $class\n");
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($package, $filename, $line) = caller(0);
my (undef, undef, undef, $subroutine) = caller(1);
Carp::croak("Missing initialize subroutine in $subroutine at $filename:$line");
my ($package, $filename, $line) = caller(0);
my (undef, undef, undef, $subroutine) = caller(1);
Carp::croak("Missing initialize subroutine in $subroutine at $filename:$line");
}
1;

View File

@ -18,249 +18,235 @@ use feature 'unicode_strings';
use Time::Duration qw/duration/;
sub initialize {
my ($self, %conf) = @_;
$self->PBot::Registerable::initialize(%conf);
my ($self, %conf) = @_;
$self->PBot::Registerable::initialize(%conf);
$self->{metadata} = PBot::HashObject->new(pbot => $self->{pbot}, name => 'Commands', filename => $conf{filename});
$self->{metadata}->load;
$self->{metadata} = PBot::HashObject->new(pbot => $self->{pbot}, name => 'Commands', filename => $conf{filename});
$self->{metadata}->load;
$self->register(sub { $self->cmdset(@_) }, "cmdset", 1);
$self->register(sub { $self->cmdunset(@_) }, "cmdunset", 1);
$self->register(sub { $self->help(@_) }, "help", 0);
$self->register(sub { $self->uptime(@_) }, "uptime", 0);
$self->register(sub { $self->in_channel(@_) }, "in", 1);
$self->register(sub { $self->cmdset(@_) }, "cmdset", 1);
$self->register(sub { $self->cmdunset(@_) }, "cmdunset", 1);
$self->register(sub { $self->help(@_) }, "help", 0);
$self->register(sub { $self->uptime(@_) }, "uptime", 0);
$self->register(sub { $self->in_channel(@_) }, "in", 1);
$self->{pbot}->{capabilities}->add('admin', 'can-in', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-in', 1);
}
sub register {
my ($self, $subref, $name, $requires_cap) = @_;
Carp::croak("Missing parameters to Commands::register") if not defined $subref or not defined $name;
my ($self, $subref, $name, $requires_cap) = @_;
Carp::croak("Missing parameters to Commands::register") if not defined $subref or not defined $name;
my $ref = $self->PBot::Registerable::register($subref);
$ref->{name} = lc $name;
$ref->{requires_cap} = $requires_cap // 0;
my $ref = $self->PBot::Registerable::register($subref);
$ref->{name} = lc $name;
$ref->{requires_cap} = $requires_cap // 0;
if (not $self->{metadata}->exists($name)) {
$self->{metadata}->add($name, { requires_cap => $requires_cap, help => '' }, 1);
} else {
if (not defined $self->get_meta($name, 'requires_cap')) {
$self->{metadata}->set($name, 'requires_cap', $requires_cap, 1);
if (not $self->{metadata}->exists($name)) { $self->{metadata}->add($name, {requires_cap => $requires_cap, help => ''}, 1); }
else {
if (not defined $self->get_meta($name, 'requires_cap')) { $self->{metadata}->set($name, 'requires_cap', $requires_cap, 1); }
}
}
# add can-cmd capability
$self->{pbot}->{capabilities}->add("can-$name", undef, 1) if $requires_cap;
return $ref;
# add can-cmd capability
$self->{pbot}->{capabilities}->add("can-$name", undef, 1) if $requires_cap;
return $ref;
}
sub unregister {
my ($self, $name) = @_;
Carp::croak("Missing name parameter to Commands::unregister") if not defined $name;
$name = lc $name;
@{ $self->{handlers} } = grep { $_->{name} ne $name } @{ $self->{handlers} };
my ($self, $name) = @_;
Carp::croak("Missing name parameter to Commands::unregister") if not defined $name;
$name = lc $name;
@{$self->{handlers}} = grep { $_->{name} ne $name } @{$self->{handlers}};
}
sub exists {
my ($self, $keyword) = @_;
$keyword = lc $keyword;
foreach my $ref (@{ $self->{handlers} }) {
return 1 if $ref->{name} eq $keyword;
}
return 0;
my ($self, $keyword) = @_;
$keyword = lc $keyword;
foreach my $ref (@{$self->{handlers}}) { return 1 if $ref->{name} eq $keyword; }
return 0;
}
sub interpreter {
my ($self, $stuff) = @_;
my $result;
my ($self, $stuff) = @_;
my $result;
if ($self->{pbot}->{registry}->get_value('general', 'debugcontext')) {
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
$self->{pbot}->{logger}->log("Commands::interpreter\n");
$self->{pbot}->{logger}->log(Dumper $stuff);
}
my $keyword = lc $stuff->{keyword};
my $from = $stuff->{from};
my ($cmd_channel) = $stuff->{arguments} =~ m/\B(#[^ ]+)/; # assume command is invoked in regards to first channel-like argument
$cmd_channel = $from if not defined $cmd_channel; # otherwise command is invoked in regards to the channel the user is in
my $user = $self->{pbot}->{users}->find_user($cmd_channel, "$stuff->{nick}!$stuff->{user}\@$stuff->{host}");
my $cap_override;
if (exists $stuff->{'cap-override'}) {
$self->{pbot}->{logger}->log("Override cap to $stuff->{'cap-override'}\n");
$cap_override = $stuff->{'cap-override'};
}
foreach my $ref (@{ $self->{handlers} }) {
if ($ref->{name} eq $keyword) {
my $requires_cap = $self->get_meta($keyword, 'requires_cap') // $ref->{requires_cap};
if ($requires_cap) {
if (defined $cap_override) {
if (not $self->{pbot}->{capabilities}->has($cap_override, "can-$keyword")) {
return "/msg $stuff->{nick} The $keyword command requires the can-$keyword capability, which cap-override $cap_override does not have.";
}
} else {
if (not defined $user) {
my ($found_chan, $found_mask) = $self->{pbot}->{users}->find_user_account($cmd_channel, "$stuff->{nick}!$stuff->{user}\@$stuff->{host}", 1);
if (not defined $found_chan) {
return "/msg $stuff->{nick} You must have a user account to use $keyword.";
} else {
return "/msg $stuff->{nick} You must have a user account in $cmd_channel to use $keyword. (You have an account in $found_chan.)";
}
} elsif (not $user->{loggedin}) {
return "/msg $stuff->{nick} You must be logged into your user account to use $keyword.";
}
if (not $self->{pbot}->{capabilities}->userhas($user, "can-$keyword")) {
return "/msg $stuff->{nick} The $keyword command requires the can-$keyword capability, which your user account does not have.";
}
}
}
$stuff->{no_nickoverride} = 1;
if ($self->get_meta($keyword, 'background-process')) {
my $timeout = $self->get_meta($keyword, 'process-timeout') // $self->{pbot}->{registry}->get_value('processmanager', 'default_timeout');
$self->{pbot}->{process_manager}->execute_process(
$stuff,
sub { $stuff->{result} = $ref->{subref}->($stuff->{from}, $stuff->{nick}, $stuff->{user}, $stuff->{host}, $stuff->{arguments}, $stuff) },
$timeout
);
return "";
} else {
my $result = $ref->{subref}->($stuff->{from}, $stuff->{nick}, $stuff->{user}, $stuff->{host}, $stuff->{arguments}, $stuff);
return undef if $stuff->{referenced} and $result =~ m/(?:usage:|no results)/i;
return $result;
}
if ($self->{pbot}->{registry}->get_value('general', 'debugcontext')) {
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
$self->{pbot}->{logger}->log("Commands::interpreter\n");
$self->{pbot}->{logger}->log(Dumper $stuff);
}
}
return undef;
my $keyword = lc $stuff->{keyword};
my $from = $stuff->{from};
my ($cmd_channel) = $stuff->{arguments} =~ m/\B(#[^ ]+)/; # assume command is invoked in regards to first channel-like argument
$cmd_channel = $from if not defined $cmd_channel; # otherwise command is invoked in regards to the channel the user is in
my $user = $self->{pbot}->{users}->find_user($cmd_channel, "$stuff->{nick}!$stuff->{user}\@$stuff->{host}");
my $cap_override;
if (exists $stuff->{'cap-override'}) {
$self->{pbot}->{logger}->log("Override cap to $stuff->{'cap-override'}\n");
$cap_override = $stuff->{'cap-override'};
}
foreach my $ref (@{$self->{handlers}}) {
if ($ref->{name} eq $keyword) {
my $requires_cap = $self->get_meta($keyword, 'requires_cap') // $ref->{requires_cap};
if ($requires_cap) {
if (defined $cap_override) {
if (not $self->{pbot}->{capabilities}->has($cap_override, "can-$keyword")) {
return "/msg $stuff->{nick} The $keyword command requires the can-$keyword capability, which cap-override $cap_override does not have.";
}
} else {
if (not defined $user) {
my ($found_chan, $found_mask) = $self->{pbot}->{users}->find_user_account($cmd_channel, "$stuff->{nick}!$stuff->{user}\@$stuff->{host}", 1);
if (not defined $found_chan) { return "/msg $stuff->{nick} You must have a user account to use $keyword."; }
else { return "/msg $stuff->{nick} You must have a user account in $cmd_channel to use $keyword. (You have an account in $found_chan.)"; }
} elsif (not $user->{loggedin}) {
return "/msg $stuff->{nick} You must be logged into your user account to use $keyword.";
}
if (not $self->{pbot}->{capabilities}->userhas($user, "can-$keyword")) {
return "/msg $stuff->{nick} The $keyword command requires the can-$keyword capability, which your user account does not have.";
}
}
}
$stuff->{no_nickoverride} = 1;
if ($self->get_meta($keyword, 'background-process')) {
my $timeout = $self->get_meta($keyword, 'process-timeout') // $self->{pbot}->{registry}->get_value('processmanager', 'default_timeout');
$self->{pbot}->{process_manager}->execute_process(
$stuff,
sub { $stuff->{result} = $ref->{subref}->($stuff->{from}, $stuff->{nick}, $stuff->{user}, $stuff->{host}, $stuff->{arguments}, $stuff) },
$timeout
);
return "";
} else {
my $result = $ref->{subref}->($stuff->{from}, $stuff->{nick}, $stuff->{user}, $stuff->{host}, $stuff->{arguments}, $stuff);
return undef if $stuff->{referenced} and $result =~ m/(?:usage:|no results)/i;
return $result;
}
}
}
return undef;
}
sub set_meta {
my ($self, $command, $key, $value, $save) = @_;
return undef if not $self->{metadata}->exists($command);
$self->{metadata}->set($command, $key, $value, !$save);
return 1;
my ($self, $command, $key, $value, $save) = @_;
return undef if not $self->{metadata}->exists($command);
$self->{metadata}->set($command, $key, $value, !$save);
return 1;
}
sub get_meta {
my ($self, $command, $key) = @_;
return $self->{metadata}->get_data($command, $key);
my ($self, $command, $key) = @_;
return $self->{metadata}->get_data($command, $key);
}
sub cmdset {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($command, $key, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 3);
return "Usage: cmdset <command> [key [value]]" if not defined $command;
return $self->{metadata}->set($command, $key, $value);
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($command, $key, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 3);
return "Usage: cmdset <command> [key [value]]" if not defined $command;
return $self->{metadata}->set($command, $key, $value);
}
sub cmdunset {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($command, $key) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
return "Usage: cmdunset <command> <key>" if not defined $command or not defined $key;
return $self->{metadata}->unset($command, $key);
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($command, $key) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
return "Usage: cmdunset <command> <key>" if not defined $command or not defined $key;
return $self->{metadata}->unset($command, $key);
}
sub help {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
if (not length $arguments) {
return "For general help, see <https://github.com/pragma-/pbot/tree/master/doc>. For help about a specific command or factoid, use `help <keyword> [channel]`.";
}
my $keyword = lc $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
# check built-in commands first
if ($self->exists($keyword)) {
if ($self->{metadata}->exists($keyword)) {
my $name = $self->{metadata}->get_data($keyword, '_name');
my $requires_cap = $self->{metadata}->get_data($keyword, 'requires_cap');
my $help = $self->{metadata}->get_data($keyword, 'help');
my $result = "/say $name: ";
$result .= "[Requires can-$keyword] " if $requires_cap;
if (not defined $help or not length $help) {
$result .= "I have no help for this command yet.";
} else {
$result .= $help;
}
return $result;
if (not length $arguments) {
return "For general help, see <https://github.com/pragma-/pbot/tree/master/doc>. For help about a specific command or factoid, use `help <keyword> [channel]`.";
}
return "$keyword is a built-in command, but I have no help for it yet.";
}
# then factoids
my $channel_arg = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
$channel_arg = $from if not defined $channel_arg or not length $channel_arg;
$channel_arg = '.*' if $channel_arg !~ m/^#/;
my $keyword = lc $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
my @factoids = $self->{pbot}->{factoids}->find_factoid($channel_arg, $keyword, exact_trigger => 1);
# check built-in commands first
if ($self->exists($keyword)) {
if ($self->{metadata}->exists($keyword)) {
my $name = $self->{metadata}->get_data($keyword, '_name');
my $requires_cap = $self->{metadata}->get_data($keyword, 'requires_cap');
my $help = $self->{metadata}->get_data($keyword, 'help');
my $result = "/say $name: ";
$result .= "[Requires can-$keyword] " if $requires_cap;
if (not @factoids or not $factoids[0]) {
return "I don't know anything about $keyword.";
}
my ($channel, $trigger);
if (@factoids > 1) {
if (not grep { $_->[0] eq $channel_arg } @factoids) {
return "/say $keyword found in multiple channels: " . (join ', ', sort map { $_->[0] eq '.*' ? 'global' : $_->[0] } @factoids) . "; use `help $keyword <channel>` to disambiguate.";
} else {
foreach my $factoid (@factoids) {
if ($factoid->[0] eq $channel_arg) {
($channel, $trigger) = ($factoid->[0], $factoid->[1]);
last;
if (not defined $help or not length $help) { $result .= "I have no help for this command yet."; }
else { $result .= $help; }
return $result;
}
}
return "$keyword is a built-in command, but I have no help for it yet.";
}
} else {
($channel, $trigger) = ($factoids[0]->[0], $factoids[0]->[1]);
}
my $channel_name = $self->{pbot}->{factoids}->{factoids}->get_data($channel, '_name');
my $trigger_name = $self->{pbot}->{factoids}->{factoids}->get_data($channel, $trigger, '_name');
$channel_name = 'global channel' if $channel_name eq '.*';
$trigger_name = "\"$trigger_name\"" if $trigger_name =~ / /;
# then factoids
my $channel_arg = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
$channel_arg = $from if not defined $channel_arg or not length $channel_arg;
$channel_arg = '.*' if $channel_arg !~ m/^#/;
my $result = "/say ";
$result .= "[$channel_name] " if $channel ne $from and $channel ne '.*';
$result .= "$trigger_name: ";
my @factoids = $self->{pbot}->{factoids}->find_factoid($channel_arg, $keyword, exact_trigger => 1);
my $help = $self->{pbot}->{factoids}->{factoids}->get_data($channel, $trigger, 'help');
if (not @factoids or not $factoids[0]) { return "I don't know anything about $keyword."; }
if (not defined $help or not length $help) {
return "/say $trigger_name is a factoid for $channel_name, but I have no help for it yet.";
}
my ($channel, $trigger);
$result .= $help;
return $result;
if (@factoids > 1) {
if (not grep { $_->[0] eq $channel_arg } @factoids) {
return
"/say $keyword found in multiple channels: "
. (join ', ', sort map { $_->[0] eq '.*' ? 'global' : $_->[0] } @factoids)
. "; use `help $keyword <channel>` to disambiguate.";
} else {
foreach my $factoid (@factoids) {
if ($factoid->[0] eq $channel_arg) {
($channel, $trigger) = ($factoid->[0], $factoid->[1]);
last;
}
}
}
} else {
($channel, $trigger) = ($factoids[0]->[0], $factoids[0]->[1]);
}
my $channel_name = $self->{pbot}->{factoids}->{factoids}->get_data($channel, '_name');
my $trigger_name = $self->{pbot}->{factoids}->{factoids}->get_data($channel, $trigger, '_name');
$channel_name = 'global channel' if $channel_name eq '.*';
$trigger_name = "\"$trigger_name\"" if $trigger_name =~ / /;
my $result = "/say ";
$result .= "[$channel_name] " if $channel ne $from and $channel ne '.*';
$result .= "$trigger_name: ";
my $help = $self->{pbot}->{factoids}->{factoids}->get_data($channel, $trigger, 'help');
if (not defined $help or not length $help) { return "/say $trigger_name is a factoid for $channel_name, but I have no help for it yet."; }
$result .= $help;
return $result;
}
sub uptime {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
return localtime ($self->{pbot}->{startup_timestamp}) . " [" . duration (time - $self->{pbot}->{startup_timestamp}) . "]";
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
return localtime($self->{pbot}->{startup_timestamp}) . " [" . duration(time - $self->{pbot}->{startup_timestamp}) . "]";
}
sub in_channel {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "Usage: in <channel> <command>";
return $usage if not $arguments;
my $usage = "Usage: in <channel> <command>";
return $usage if not $arguments;
my ($channel, $command) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2, 0, 1);
return $usage if not defined $channel or not defined $command;
my ($channel, $command) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2, 0, 1);
return $usage if not defined $channel or not defined $command;
if (not $self->{pbot}->{nicklist}->is_present($channel, $nick)) {
return "You must be present in $channel to do this.";
}
if (not $self->{pbot}->{nicklist}->is_present($channel, $nick)) { return "You must be present in $channel to do this."; }
$stuff->{from} = $channel;
$stuff->{command} = $command;
return $self->{pbot}->{interpreter}->interpret($stuff);
$stuff->{from} = $channel;
$stuff->{command} = $command;
return $self->{pbot}->{interpreter}->interpret($stuff);
}
1;

View File

@ -20,364 +20,347 @@ use Text::Levenshtein qw(fastdistance);
use JSON;
sub new {
my ($proto, %conf) = @_;
my $class = ref($proto) || $proto;
my $self = bless {}, $class;
Carp::croak("Missing pbot reference to " . __FILE__) unless exists $conf{pbot};
$self->{pbot} = $conf{pbot};
$self->initialize(%conf);
return $self;
my ($proto, %conf) = @_;
my $class = ref($proto) || $proto;
my $self = bless {}, $class;
Carp::croak("Missing pbot reference to " . __FILE__) unless exists $conf{pbot};
$self->{pbot} = $conf{pbot};
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($self, %conf) = @_;
$self->{name} = $conf{name} // 'Dual Index hash object';
$self->{filename} = $conf{filename} // Carp::carp("Missing filename to DualIndexHashObject, will not be able to save to or load from file.");
$self->{hash} = {};
my ($self, %conf) = @_;
$self->{name} = $conf{name} // 'Dual Index hash object';
$self->{filename} = $conf{filename} // Carp::carp("Missing filename to DualIndexHashObject, will not be able to save to or load from file.");
$self->{hash} = {};
}
sub load {
my ($self, $filename) = @_;
$filename = $self->{filename} if not defined $filename;
my ($self, $filename) = @_;
$filename = $self->{filename} if not defined $filename;
if (not defined $filename) {
Carp::carp "No $self->{name} filename specified -- skipping loading from file";
return;
}
$self->{pbot}->{logger}->log("Loading $self->{name} from $filename ...\n");
if (not open(FILE, "< $filename")) {
Carp::carp "Skipping loading from file: Couldn't open $filename: $!\n";
return;
}
my $contents = do {
local $/;
<FILE>;
};
$self->{hash} = decode_json $contents if length $contents;
close FILE;
# update existing entries to use _name to preserve case
# and lowercase any non-lowercased entries
foreach my $primary_index (keys %{ $self->{hash} }) {
if (not exists $self->{hash}->{$primary_index}->{_name}) {
if (lc $primary_index eq $primary_index) {
$self->{hash}->{$primary_index}->{_name} = $primary_index;
} else {
if (exists $self->{hash}->{lc $primary_index}) {
Carp::croak "Cannot update $self->{name} primary index $primary_index; duplicate object found";
}
my $data = delete $self->{hash}->{$primary_index};
$data->{_name} = $primary_index;
$primary_index = lc $primary_index;
$self->{hash}->{$primary_index} = $data;
}
if (not defined $filename) {
Carp::carp "No $self->{name} filename specified -- skipping loading from file";
return;
}
foreach my $secondary_index (keys %{ $self->{hash}->{$primary_index} }) {
next if $secondary_index eq '_name';
if (not exists $self->{hash}->{$primary_index}->{$secondary_index}->{_name}) {
if (lc $secondary_index eq $secondary_index) {
$self->{hash}->{$primary_index}->{$secondary_index}->{_name} = $secondary_index;
} else {
if (exists $self->{hash}->{$primary_index}->{lc $secondary_index}) {
Carp::croak "Cannot update $self->{name} $primary_index sub-object $secondary_index; duplicate object found";
}
$self->{pbot}->{logger}->log("Loading $self->{name} from $filename ...\n");
my $data = delete $self->{hash}->{$primary_index}->{$secondary_index};
$data->{_name} = $secondary_index;
$secondary_index = lc $secondary_index;
$self->{hash}->{$primary_index}->{$secondary_index} = $data;
}
}
if (not open(FILE, "< $filename")) {
$self->{pbot}->{logger}->log("Skipping loading from file: Couldn't open $filename: $!\n");
return;
}
my $contents = do {
local $/;
<FILE>;
};
$self->{hash} = decode_json $contents if length $contents;
close FILE;
# update existing entries to use _name to preserve case
# and lowercase any non-lowercased entries
foreach my $primary_index (keys %{$self->{hash}}) {
if (not exists $self->{hash}->{$primary_index}->{_name}) {
if (lc $primary_index eq $primary_index) { $self->{hash}->{$primary_index}->{_name} = $primary_index; }
else {
if (exists $self->{hash}->{lc $primary_index}) { Carp::croak "Cannot update $self->{name} primary index $primary_index; duplicate object found"; }
my $data = delete $self->{hash}->{$primary_index};
$data->{_name} = $primary_index;
$primary_index = lc $primary_index;
$self->{hash}->{$primary_index} = $data;
}
}
foreach my $secondary_index (keys %{$self->{hash}->{$primary_index}}) {
next if $secondary_index eq '_name';
if (not exists $self->{hash}->{$primary_index}->{$secondary_index}->{_name}) {
if (lc $secondary_index eq $secondary_index) { $self->{hash}->{$primary_index}->{$secondary_index}->{_name} = $secondary_index; }
else {
if (exists $self->{hash}->{$primary_index}->{lc $secondary_index}) {
Carp::croak "Cannot update $self->{name} $primary_index sub-object $secondary_index; duplicate object found";
}
my $data = delete $self->{hash}->{$primary_index}->{$secondary_index};
$data->{_name} = $secondary_index;
$secondary_index = lc $secondary_index;
$self->{hash}->{$primary_index}->{$secondary_index} = $data;
}
}
}
}
}
}
sub save {
my $self = shift;
my $filename;
if (@_) { $filename = shift; } else { $filename = $self->{filename}; }
my $self = shift;
my $filename;
if (@_) { $filename = shift; }
else { $filename = $self->{filename}; }
if (not defined $filename) {
Carp::carp "No $self->{name} filename specified -- skipping saving to file.\n";
return;
}
if (not defined $filename) {
Carp::carp "No $self->{name} filename specified -- skipping saving to file.\n";
return;
}
$self->{pbot}->{logger}->log("Saving $self->{name} to $filename\n");
$self->{pbot}->{logger}->log("Saving $self->{name} to $filename\n");
my $json = JSON->new;
my $json_text = $json->pretty->canonical->utf8->encode($self->{hash});
my $json = JSON->new;
my $json_text = $json->pretty->canonical->utf8->encode($self->{hash});
open(FILE, "> $filename") or die "Couldn't open $filename: $!\n";
print FILE "$json_text\n";
close FILE;
open(FILE, "> $filename") or die "Couldn't open $filename: $!\n";
print FILE "$json_text\n";
close FILE;
}
sub clear {
my $self = shift;
$self->{hash} = {};
my $self = shift;
$self->{hash} = {};
}
sub levenshtein_matches {
my ($self, $primary_index, $secondary_index, $distance, $strictnamespace) = @_;
my $comma = '';
my $result = "";
my ($self, $primary_index, $secondary_index, $distance, $strictnamespace) = @_;
my $comma = '';
my $result = "";
$distance = 0.60 if not defined $distance;
$distance = 0.60 if not defined $distance;
$primary_index = '.*' if not defined $primary_index;
$primary_index = '.*' if not defined $primary_index;
if (not $secondary_index) {
foreach my $index (sort keys %{ $self->{hash} }) {
my $distance_result = fastdistance($primary_index, $index);
my $length = (length $primary_index > length $index) ? length $primary_index : length $index;
if (not $secondary_index) {
foreach my $index (sort keys %{$self->{hash}}) {
my $distance_result = fastdistance($primary_index, $index);
my $length = (length $primary_index > length $index) ? length $primary_index : length $index;
if ($distance_result / $length < $distance) {
my $name = $self->{hash}->{$index}->{_name};
if ($name =~ / /) {
$result .= $comma . "\"$name\"";
} else {
$result .= $comma . $name;
if ($distance_result / $length < $distance) {
my $name = $self->{hash}->{$index}->{_name};
if ($name =~ / /) { $result .= $comma . "\"$name\""; }
else { $result .= $comma . $name; }
$comma = ", ";
}
}
$comma = ", ";
}
}
} else {
my $lc_primary_index = lc $primary_index;
if (not exists $self->{hash}->{$lc_primary_index}) {
return 'none';
}
} else {
my $lc_primary_index = lc $primary_index;
if (not exists $self->{hash}->{$lc_primary_index}) { return 'none'; }
my $last_header = "";
my $header = "";
my $last_header = "";
my $header = "";
foreach my $index1 (sort keys %{ $self->{hash} }) {
$header = "[$self->{hash}->{$index1}->{_name}] ";
$header = '[global] ' if $header eq '[.*] ';
foreach my $index1 (sort keys %{$self->{hash}}) {
$header = "[$self->{hash}->{$index1}->{_name}] ";
$header = '[global] ' if $header eq '[.*] ';
if ($strictnamespace) {
next unless $index1 eq '.*' or $index1 eq $lc_primary_index;
$header = "" unless $header eq '[global] ';
}
if ($strictnamespace) {
next unless $index1 eq '.*' or $index1 eq $lc_primary_index;
$header = "" unless $header eq '[global] ';
}
foreach my $index2 (sort keys %{ $self->{hash}->{$index1} }) {
my $distance_result = fastdistance($secondary_index, $index2);
my $length = (length $secondary_index > length $index2) ? length $secondary_index : length $index2;
foreach my $index2 (sort keys %{$self->{hash}->{$index1}}) {
my $distance_result = fastdistance($secondary_index, $index2);
my $length = (length $secondary_index > length $index2) ? length $secondary_index : length $index2;
if ($distance_result / $length < $distance) {
my $name = $self->{hash}->{$index1}->{$index2}->{_name};
$header = "" if $last_header eq $header;
$last_header = $header;
$comma = '; ' if $comma ne '' and $header ne '';
if ($name =~ / /) {
$result .= $comma . $header . "\"$name\"";
} else {
$result .= $comma . $header . $name;
}
$comma = ", ";
if ($distance_result / $length < $distance) {
my $name = $self->{hash}->{$index1}->{$index2}->{_name};
$header = "" if $last_header eq $header;
$last_header = $header;
$comma = '; ' if $comma ne '' and $header ne '';
if ($name =~ / /) { $result .= $comma . $header . "\"$name\""; }
else { $result .= $comma . $header . $name; }
$comma = ", ";
}
}
}
}
}
}
$result =~ s/(.*), /$1 or /;
$result = 'none' if $comma eq '';
return $result;
$result =~ s/(.*), /$1 or /;
$result = 'none' if $comma eq '';
return $result;
}
sub set {
my ($self, $primary_index, $secondary_index, $key, $value, $dont_save) = @_;
my $lc_primary_index = lc $primary_index;
my $lc_secondary_index = lc $secondary_index;
my ($self, $primary_index, $secondary_index, $key, $value, $dont_save) = @_;
my $lc_primary_index = lc $primary_index;
my $lc_secondary_index = lc $secondary_index;
if (not exists $self->{hash}->{$lc_primary_index}) {
my $result = "$self->{name}: $primary_index not found; similiar matches: ";
$result .= $self->levenshtein_matches($primary_index);
return $result;
}
if (not exists $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}) {
my $secondary_text = $secondary_index =~ / / ? "\"$secondary_index\"" : $secondary_index;
my $result = "$self->{name}: [$self->{hash}->{$lc_primary_index}->{_name}] $secondary_text not found; similiar matches: ";
$result .= $self->levenshtein_matches($primary_index, $secondary_index);
return $result;
}
my $name1 = $self->{hash}->{$lc_primary_index}->{_name};
my $name2 = $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{_name};
$name1 = 'global' if $name1 eq '.*';
$name2 = "\"$name2\"" if $name2 =~ / /;
if (not defined $key) {
my $result = "[$name1] $name2 keys:\n";
my $comma = '';
foreach my $key (sort keys %{ $self->{hash}->{$lc_primary_index}->{$lc_secondary_index} }) {
next if $key eq '_name';
$result .= $comma . "$key => " . $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{$key};
$comma = ";\n";
if (not exists $self->{hash}->{$lc_primary_index}) {
my $result = "$self->{name}: $primary_index not found; similiar matches: ";
$result .= $self->levenshtein_matches($primary_index);
return $result;
}
$result .= "none" if ($comma eq '');
return $result;
}
if (not defined $value) {
$value = $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{$key};
} else {
$self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{$key} = $value;
$self->save unless $dont_save;
}
if (not exists $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}) {
my $secondary_text = $secondary_index =~ / / ? "\"$secondary_index\"" : $secondary_index;
my $result = "$self->{name}: [$self->{hash}->{$lc_primary_index}->{_name}] $secondary_text not found; similiar matches: ";
$result .= $self->levenshtein_matches($primary_index, $secondary_index);
return $result;
}
return "[$name1] $name2: $key " . (defined $value ? "set to $value" : "is not set.");
my $name1 = $self->{hash}->{$lc_primary_index}->{_name};
my $name2 = $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{_name};
$name1 = 'global' if $name1 eq '.*';
$name2 = "\"$name2\"" if $name2 =~ / /;
if (not defined $key) {
my $result = "[$name1] $name2 keys:\n";
my $comma = '';
foreach my $key (sort keys %{$self->{hash}->{$lc_primary_index}->{$lc_secondary_index}}) {
next if $key eq '_name';
$result .= $comma . "$key => " . $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{$key};
$comma = ";\n";
}
$result .= "none" if ($comma eq '');
return $result;
}
if (not defined $value) { $value = $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{$key}; }
else {
$self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{$key} = $value;
$self->save unless $dont_save;
}
return "[$name1] $name2: $key " . (defined $value ? "set to $value" : "is not set.");
}
sub unset {
my ($self, $primary_index, $secondary_index, $key) = @_;
my $lc_primary_index = lc $primary_index;
my $lc_secondary_index = lc $secondary_index;
my ($self, $primary_index, $secondary_index, $key) = @_;
my $lc_primary_index = lc $primary_index;
my $lc_secondary_index = lc $secondary_index;
if (not exists $self->{hash}->{$lc_primary_index}) {
my $result = "$self->{name}: $primary_index not found; similiar matches: ";
$result .= $self->levenshtein_matches($primary_index);
return $result;
}
if (not exists $self->{hash}->{$lc_primary_index}) {
my $result = "$self->{name}: $primary_index not found; similiar matches: ";
$result .= $self->levenshtein_matches($primary_index);
return $result;
}
my $name1 = $self->{hash}->{$lc_primary_index}->{_name};
$name1 = 'global' if $name1 eq '.*';
my $name1 = $self->{hash}->{$lc_primary_index}->{_name};
$name1 = 'global' if $name1 eq '.*';
if (not exists $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}) {
my $result = "$self->{name}: [$name1] $secondary_index not found; similiar matches: ";
$result .= $self->levenshtein_matches($primary_index, $secondary_index);
return $result;
}
if (not exists $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}) {
my $result = "$self->{name}: [$name1] $secondary_index not found; similiar matches: ";
$result .= $self->levenshtein_matches($primary_index, $secondary_index);
return $result;
}
my $name2 = $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{_name};
$name2 = "\"$name2\"" if $name2 =~ / /;
my $name2 = $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{_name};
$name2 = "\"$name2\"" if $name2 =~ / /;
if (defined delete $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{$key}) {
if (defined delete $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{$key}) {
$self->save;
return "$self->{name}: [$name1] $name2: $key unset.";
} else {
return "$self->{name}: [$name1] $name2: $key does not exist.";
}
$self->save;
return "$self->{name}: [$name1] $name2: $key unset.";
} else {
return "$self->{name}: [$name1] $name2: $key does not exist.";
}
$self->save;
}
sub exists {
my ($self, $primary_index, $secondary_index, $data_index) = @_;
return 0 if not defined $primary_index;
$primary_index = lc $primary_index;
return 0 if not exists $self->{hash}->{$primary_index};
return 1 if not defined $secondary_index;
$secondary_index = lc $secondary_index;
return 0 if not exists $self->{hash}->{$primary_index}->{$secondary_index};
return 1 if not defined $data_index;
return exists $self->{hash}->{$primary_index}->{$secondary_index}->{$data_index};
my ($self, $primary_index, $secondary_index, $data_index) = @_;
return 0 if not defined $primary_index;
$primary_index = lc $primary_index;
return 0 if not exists $self->{hash}->{$primary_index};
return 1 if not defined $secondary_index;
$secondary_index = lc $secondary_index;
return 0 if not exists $self->{hash}->{$primary_index}->{$secondary_index};
return 1 if not defined $data_index;
return exists $self->{hash}->{$primary_index}->{$secondary_index}->{$data_index};
}
sub get_keys {
my ($self, $primary_index, $secondary_index) = @_;
return keys %{$self->{hash}} if not defined $primary_index;
my ($self, $primary_index, $secondary_index) = @_;
return keys %{$self->{hash}} if not defined $primary_index;
if (not defined $secondary_index) {
return grep { $_ ne '_name' } keys %{$self->{hash}->{lc $primary_index}};
}
if (not defined $secondary_index) {
return grep { $_ ne '_name' } keys %{$self->{hash}->{lc $primary_index}};
}
return grep { $_ ne '_name' } keys %{$self->{hash}->{lc $primary_index}->{lc $secondary_index}};
return grep { $_ ne '_name' } keys %{$self->{hash}->{lc $primary_index}->{lc $secondary_index}};
}
sub get_data {
my ($self, $primary_index, $secondary_index, $data_index) = @_;
$primary_index = lc $primary_index if defined $primary_index;
$secondary_index = lc $secondary_index if defined $secondary_index;
return undef if not exists $self->{hash}->{$primary_index};
return $self->{hash}->{$primary_index} if not defined $secondary_index;
return $self->{hash}->{$primary_index}->{$secondary_index} if not defined $data_index;
return $self->{hash}->{$primary_index}->{$secondary_index}->{$data_index};
my ($self, $primary_index, $secondary_index, $data_index) = @_;
$primary_index = lc $primary_index if defined $primary_index;
$secondary_index = lc $secondary_index if defined $secondary_index;
return undef if not exists $self->{hash}->{$primary_index};
return $self->{hash}->{$primary_index} if not defined $secondary_index;
return $self->{hash}->{$primary_index}->{$secondary_index} if not defined $data_index;
return $self->{hash}->{$primary_index}->{$secondary_index}->{$data_index};
}
sub add {
my ($self, $primary_index, $secondary_index, $data, $dont_save, $quiet) = @_;
my $lc_primary_index = lc $primary_index;
my $lc_secondary_index = lc $secondary_index;
my ($self, $primary_index, $secondary_index, $data, $dont_save, $quiet) = @_;
my $lc_primary_index = lc $primary_index;
my $lc_secondary_index = lc $secondary_index;
if (not exists $self->{hash}->{$lc_primary_index}) {
$self->{hash}->{$lc_primary_index}->{_name} = $primary_index; # preserve case
}
if (not exists $self->{hash}->{$lc_primary_index}) {
$self->{hash}->{$lc_primary_index}->{_name} = $primary_index; # preserve case
}
$data->{_name} = $secondary_index; # preserve case
$self->{hash}->{$lc_primary_index}->{$lc_secondary_index} = $data;
$self->save() unless $dont_save;
$data->{_name} = $secondary_index; # preserve case
$self->{hash}->{$lc_primary_index}->{$lc_secondary_index} = $data;
$self->save() unless $dont_save;
my $name1 = $self->{hash}->{$lc_primary_index}->{_name};
my $name2 = $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{_name};
$name1 = 'global' if $name1 eq '.*';
$name2 = "\"$name2\"" if $name2 =~ / /;
$self->{pbot}->{logger}->log("$self->{name}: [$name1]: $name2 added.\n") unless $dont_save or $quiet;
return "$self->{name}: [$name1]: $name2 added.";
my $name1 = $self->{hash}->{$lc_primary_index}->{_name};
my $name2 = $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{_name};
$name1 = 'global' if $name1 eq '.*';
$name2 = "\"$name2\"" if $name2 =~ / /;
$self->{pbot}->{logger}->log("$self->{name}: [$name1]: $name2 added.\n") unless $dont_save or $quiet;
return "$self->{name}: [$name1]: $name2 added.";
}
sub remove {
my ($self, $primary_index, $secondary_index, $data_index, $dont_save) = @_;
my $lc_primary_index = lc $primary_index;
my $lc_secondary_index = lc $secondary_index;
my ($self, $primary_index, $secondary_index, $data_index, $dont_save) = @_;
my $lc_primary_index = lc $primary_index;
my $lc_secondary_index = lc $secondary_index;
if (not exists $self->{hash}->{$lc_primary_index}) {
my $result = "$self->{name}: $primary_index not found; similiar matches: ";
$result .= $self->levenshtein_matches($primary_index);
return $result;
}
if (not defined $secondary_index) {
my $data = delete $self->{hash}->{$lc_primary_index};
if (defined $data) {
my $name = $data->{_name};
$name = 'global' if $name eq '.*';
$self->save unless $dont_save;
return "$self->{name}: $name removed.";
} else {
return "$self->{name}: $primary_index does not exist.";
if (not exists $self->{hash}->{$lc_primary_index}) {
my $result = "$self->{name}: $primary_index not found; similiar matches: ";
$result .= $self->levenshtein_matches($primary_index);
return $result;
}
}
my $name1 = $self->{hash}->{$lc_primary_index}->{_name};
$name1 = 'global' if $name1 eq '.*';
if (not exists $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}) {
my $result = "$self->{name}: [$name1] $secondary_index not found; similiar matches: ";
$result .= $self->levenshtein_matches($primary_index, $secondary_index);
return $result;
}
if (not defined $data_index) {
my $data = delete $self->{hash}->{$lc_primary_index}->{$lc_secondary_index};
if (defined $data) {
my $name2 = $data->{_name};
$name2 = "\"$name2\"" if $name2 =~ / /;
# remove primary group if no more secondaries (only key left should be the _name key)
if (keys %{ $self->{hash}->{$lc_primary_index} } == 1) {
delete $self->{hash}->{$lc_primary_index};
}
$self->save unless $dont_save;
return "$self->{name}: [$name1] $name2 removed.";
} else {
return "$self->{name}: [$name1] $secondary_index does not exist.";
if (not defined $secondary_index) {
my $data = delete $self->{hash}->{$lc_primary_index};
if (defined $data) {
my $name = $data->{_name};
$name = 'global' if $name eq '.*';
$self->save unless $dont_save;
return "$self->{name}: $name removed.";
} else {
return "$self->{name}: $primary_index does not exist.";
}
}
}
my $name2 = $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{_name};
if (defined delete $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{$data_index}) {
return "$self->{name}: [$name1] $name2.$data_index removed.";
} else {
return "$self->{name}: [$name1] $name2.$data_index does not exist.";
}
my $name1 = $self->{hash}->{$lc_primary_index}->{_name};
$name1 = 'global' if $name1 eq '.*';
if (not exists $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}) {
my $result = "$self->{name}: [$name1] $secondary_index not found; similiar matches: ";
$result .= $self->levenshtein_matches($primary_index, $secondary_index);
return $result;
}
if (not defined $data_index) {
my $data = delete $self->{hash}->{$lc_primary_index}->{$lc_secondary_index};
if (defined $data) {
my $name2 = $data->{_name};
$name2 = "\"$name2\"" if $name2 =~ / /;
# remove primary group if no more secondaries (only key left should be the _name key)
if (keys %{$self->{hash}->{$lc_primary_index}} == 1) { delete $self->{hash}->{$lc_primary_index}; }
$self->save unless $dont_save;
return "$self->{name}: [$name1] $name2 removed.";
} else {
return "$self->{name}: [$name1] $secondary_index does not exist.";
}
}
my $name2 = $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{_name};
if (defined delete $self->{hash}->{$lc_primary_index}->{$lc_secondary_index}->{$data_index}) { return "$self->{name}: [$name1] $name2.$data_index removed."; }
else { return "$self->{name}: [$name1] $name2.$data_index does not exist."; }
}
1;

View File

@ -11,79 +11,77 @@ use feature 'unicode_strings';
use IO::Select;
sub initialize {
my ($self, %conf) = @_;
$self->{handlers} = { any => [] };
my ($self, %conf) = @_;
$self->{handlers} = {any => []};
}
sub register_handler {
my ($self, $event_type, $sub, $package_override) = @_;
my ($package) = caller(0);
$package = $package_override if defined $package_override;
my $info = "$package\-\>$event_type";
$self->{pbot}->{logger}->log("Adding handler: $info\n") if $self->{pbot}->{registry}->get_value('eventdispatcher', 'debug');
push @{$self->{handlers}->{$event_type}}, [$sub, $info];
my ($self, $event_type, $sub, $package_override) = @_;
my ($package) = caller(0);
$package = $package_override if defined $package_override;
my $info = "$package\-\>$event_type";
$self->{pbot}->{logger}->log("Adding handler: $info\n") if $self->{pbot}->{registry}->get_value('eventdispatcher', 'debug');
push @{$self->{handlers}->{$event_type}}, [$sub, $info];
}
sub remove_handler {
my ($self, $event_type, $package_override) = @_;
my ($package) = caller(0);
$package = $package_override if defined $package_override;
my $info = "$package\-\>$event_type";
my ($self, $event_type, $package_override) = @_;
my ($package) = caller(0);
$package = $package_override if defined $package_override;
my $info = "$package\-\>$event_type";
if (exists $self->{handlers}->{$event_type}) {
for (my $i = 0; $i < @{$self->{handlers}->{$event_type}}; $i++) {
my $ref = @{$self->{handlers}->{$event_type}}[$i];
if ($info eq $ref->[1]) {
$self->{pbot}->{logger}->log("Removing handler: $info\n") if $self->{pbot}->{registry}->get_value('eventdispatcher', 'debug');
splice @{$self->{handlers}->{$event_type}}, $i--, 1;
}
if (exists $self->{handlers}->{$event_type}) {
for (my $i = 0; $i < @{$self->{handlers}->{$event_type}}; $i++) {
my $ref = @{$self->{handlers}->{$event_type}}[$i];
if ($info eq $ref->[1]) {
$self->{pbot}->{logger}->log("Removing handler: $info\n") if $self->{pbot}->{registry}->get_value('eventdispatcher', 'debug');
splice @{$self->{handlers}->{$event_type}}, $i--, 1;
}
}
}
}
}
sub dispatch_event {
my ($self, $event_type, $event_data) = @_;
my $ret = undef;
my ($self, $event_type, $event_data) = @_;
my $ret = undef;
if (exists $self->{handlers}->{$event_type}) {
for (my $i = 0; $i < @{$self->{handlers}->{$event_type}}; $i++) {
my $ref = @{$self->{handlers}->{$event_type}}[$i];
my ($handler, $info) = ($ref->[0], $ref->[1]);
my $debug = $self->{pbot}->{registry}->get_value('eventdispatcher', 'debug') // 0;
$self->{pbot}->{logger}->log("Dispatching $event_type to handler $info\n") if $debug > 1;
if (exists $self->{handlers}->{$event_type}) {
for (my $i = 0; $i < @{$self->{handlers}->{$event_type}}; $i++) {
my $ref = @{$self->{handlers}->{$event_type}}[$i];
my ($handler, $info) = ($ref->[0], $ref->[1]);
my $debug = $self->{pbot}->{registry}->get_value('eventdispatcher', 'debug') // 0;
$self->{pbot}->{logger}->log("Dispatching $event_type to handler $info\n") if $debug > 1;
eval {
$ret = $handler->($event_type, $event_data);
};
eval { $ret = $handler->($event_type, $event_data); };
if ($@) {
chomp $@;
$self->{pbot}->{logger}->log("Error in event handler: $@\n");
#$self->{pbot}->{logger}->log("Removing handler.\n");
#splice @{$self->{handlers}->{$event_type}}, $i--, 1;
}
return $ret if $ret;
if ($@) {
chomp $@;
$self->{pbot}->{logger}->log("Error in event handler: $@\n");
#$self->{pbot}->{logger}->log("Removing handler.\n");
#splice @{$self->{handlers}->{$event_type}}, $i--, 1;
}
return $ret if $ret;
}
}
}
for (my $i = 0; $i < @{$self->{handlers}->{any}}; $i++) {
my $ref = @{$self->{handlers}->{any}}[$i];
my ($handler, $info) = ($ref->[0], $ref->[1]);
$self->{pbot}->{logger}->log("Dispatching any to handler $info\n") if $self->{pbot}->{registry}->get_value('eventdispatcher', 'debug');
for (my $i = 0; $i < @{$self->{handlers}->{any}}; $i++) {
my $ref = @{$self->{handlers}->{any}}[$i];
my ($handler, $info) = ($ref->[0], $ref->[1]);
$self->{pbot}->{logger}->log("Dispatching any to handler $info\n") if $self->{pbot}->{registry}->get_value('eventdispatcher', 'debug');
eval {
$ret = $handler->($event_type, $event_data);
};
eval { $ret = $handler->($event_type, $event_data); };
if ($@) {
chomp $@;
$self->{pbot}->{logger}->log("Error in event handler: $@\n");
#$self->{pbot}->{logger}->log("Removing handler.\n");
#splice @{$self->{handlers}->{any}}, $i--, 1;
if ($@) {
chomp $@;
$self->{pbot}->{logger}->log("Error in event handler: $@\n");
#$self->{pbot}->{logger}->log("Removing handler.\n");
#splice @{$self->{handlers}->{any}}, $i--, 1;
}
return $ret if $ret;
}
return $ret if $ret;
}
return $ret;
return $ret;
}
1;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -26,89 +26,82 @@ use warnings; use strict;
use feature 'unicode_strings';
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->do_func(@_) }, 'func', 0);
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->do_func(@_) }, 'func', 0);
$self->register(
'help',
{
desc => 'provides help about a func',
usage => 'help [func]',
subref => sub { $self->func_help(@_) }
}
);
$self->register(
'help',
{
desc => 'provides help about a func',
usage => 'help [func]',
subref => sub { $self->func_help(@_) }
}
);
$self->register(
'list',
{
desc => 'lists available funcs',
usage => 'list [regex]',
subref => sub { $self->func_list(@_) }
}
);
$self->register(
'list',
{
desc => 'lists available funcs',
usage => 'list [regex]',
subref => sub { $self->func_list(@_) }
}
);
}
sub register {
my ($self, $func, $data) = @_;
$self->{funcs}->{$func} = $data;
my ($self, $func, $data) = @_;
$self->{funcs}->{$func} = $data;
}
sub unregister {
my ($self, $func) = @_;
delete $self->{funcs}->{$func};
my ($self, $func) = @_;
delete $self->{funcs}->{$func};
}
sub do_func {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $func = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
return "Usage: func <keyword> [arguments]; see also: func help" if not defined $func;
return "[No such func '$func']" if not exists $self->{funcs}->{$func};
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $func = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
return "Usage: func <keyword> [arguments]; see also: func help" if not defined $func;
return "[No such func '$func']" if not exists $self->{funcs}->{$func};
my @params;
while (my $param = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist})) {
push @params, $param;
}
my @params;
while (my $param = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist})) { push @params, $param; }
my $result = $self->{funcs}->{$func}->{subref}->(@params);
$result =~ s/\x1/1/g;
return $result;
my $result = $self->{funcs}->{$func}->{subref}->(@params);
$result =~ s/\x1/1/g;
return $result;
}
sub func_help {
my ($self, $func) = @_;
return "func: invoke built-in functions; usage: func <keyword> [arguments]; to list available functions: func list [regex]" if not length $func;
return "No such func '$func'." if not exists $self->{funcs}->{$func};
return "$func: $self->{funcs}->{$func}->{desc}; usage: $self->{funcs}->{$func}->{usage}";
my ($self, $func) = @_;
return "func: invoke built-in functions; usage: func <keyword> [arguments]; to list available functions: func list [regex]" if not length $func;
return "No such func '$func'." if not exists $self->{funcs}->{$func};
return "$func: $self->{funcs}->{$func}->{desc}; usage: $self->{funcs}->{$func}->{usage}";
}
sub func_list {
my ($self, $regex) = @_;
$regex = '.*' if not defined $regex;
my $result = eval {
my $text = '';
foreach my $func (sort keys %{$self->{funcs}}) {
if ($func =~ m/$regex/i or $self->{funcs}->{$func}->{desc} =~ m/$regex/i) {
$text .= "$func, ";
}
}
my ($self, $regex) = @_;
$regex = '.*' if not defined $regex;
my $result = eval {
my $text = '';
foreach my $func (sort keys %{$self->{funcs}}) {
if ($func =~ m/$regex/i or $self->{funcs}->{$func}->{desc} =~ m/$regex/i) { $text .= "$func, "; }
}
$text =~ s/,\s+$//;
if (not length $text) {
if ($regex eq '.*') {
$text = "No funcs yet.";
} else {
$text = "No matching func.";
}
}
return "Available funcs: $text; see also: func help <keyword>";
};
$text =~ s/,\s+$//;
if (not length $text) {
if ($regex eq '.*') { $text = "No funcs yet."; }
else { $text = "No matching func."; }
}
return "Available funcs: $text; see also: func help <keyword>";
};
if ($@) {
my $error = $@;
$error =~ s/at PBot.Functions.*$//;
return "Error: $error\n";
}
return $result;
if ($@) {
my $error = $@;
$error =~ s/at PBot.Functions.*$//;
return "Error: $error\n";
}
return $result;
}
1;

View File

@ -19,218 +19,216 @@ use Text::Levenshtein qw(fastdistance);
use JSON;
sub new {
my ($proto, %conf) = @_;
my $class = ref($proto) || $proto;
my $self = bless {}, $class;
Carp::croak("Missing pbot reference to " . __FILE__) unless exists $conf{pbot};
$self->{pbot} = $conf{pbot};
$self->initialize(%conf);
return $self;
my ($proto, %conf) = @_;
my $class = ref($proto) || $proto;
my $self = bless {}, $class;
Carp::croak("Missing pbot reference to " . __FILE__) unless exists $conf{pbot};
$self->{pbot} = $conf{pbot};
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($self, %conf) = @_;
$self->{name} = $conf{name} // 'hash object';
$self->{filename} = $conf{filename} // Carp::carp("Missing filename to HashObject, will not be able to save to or load from file.");
$self->{hash} = {};
my ($self, %conf) = @_;
$self->{name} = $conf{name} // 'hash object';
$self->{filename} = $conf{filename} // Carp::carp("Missing filename to HashObject, will not be able to save to or load from file.");
$self->{hash} = {};
}
sub load {
my $self = shift;
my $filename;
if (@_) { $filename = shift; } else { $filename = $self->{filename}; }
my $self = shift;
my $filename;
if (@_) { $filename = shift; }
else { $filename = $self->{filename}; }
$self->clear;
$self->clear;
if (not defined $filename) {
Carp::carp "No $self->{name} filename specified -- skipping loading from file";
return;
}
$self->{pbot}->{logger}->log("Loading $self->{name} from $filename ...\n");
if (not open(FILE, "< $filename")) {
Carp::carp "Skipping loading from file: Couldn't open $filename: $!\n";
return;
}
my $contents = do {
local $/;
<FILE>;
};
$self->{hash} = decode_json $contents;
close FILE;
# update existing entries to use _name to preserve case
# and lowercase any non-lowercased entries
foreach my $index (keys %{$self->{hash}}) {
if (not exists $self->{hash}->{$index}->{_name}) {
if (lc $index eq $index) {
$self->{hash}->{$index}->{_name} = $index;
} else {
if (exists $self->{hash}->{lc $index}) {
Carp::croak "Cannot update $self->{name} object $index; duplicate object found";
}
my $data = delete $self->{hash}->{$index};
$data->{_name} = $index;
$self->{hash}->{lc $index} = $data;
}
if (not defined $filename) {
Carp::carp "No $self->{name} filename specified -- skipping loading from file";
return;
}
$self->{pbot}->{logger}->log("Loading $self->{name} from $filename ...\n");
if (not open(FILE, "< $filename")) {
$self->{pbot}->{logger}->log("Skipping loading from file: Couldn't open $filename: $!\n");
return;
}
my $contents = do {
local $/;
<FILE>;
};
$self->{hash} = decode_json $contents;
close FILE;
# update existing entries to use _name to preserve case
# and lowercase any non-lowercased entries
foreach my $index (keys %{$self->{hash}}) {
if (not exists $self->{hash}->{$index}->{_name}) {
if (lc $index eq $index) { $self->{hash}->{$index}->{_name} = $index; }
else {
if (exists $self->{hash}->{lc $index}) { Carp::croak "Cannot update $self->{name} object $index; duplicate object found"; }
my $data = delete $self->{hash}->{$index};
$data->{_name} = $index;
$self->{hash}->{lc $index} = $data;
}
}
}
}
}
sub save {
my $self = shift;
my $filename;
if (@_) { $filename = shift; } else { $filename = $self->{filename}; }
my $self = shift;
my $filename;
if (@_) { $filename = shift; }
else { $filename = $self->{filename}; }
if (not defined $filename) {
Carp::carp "No $self->{name} filename specified -- skipping saving to file.\n";
return;
}
if (not defined $filename) {
Carp::carp "No $self->{name} filename specified -- skipping saving to file.\n";
return;
}
$self->{pbot}->{logger}->log("Saving $self->{name} to $filename\n");
$self->{pbot}->{logger}->log("Saving $self->{name} to $filename\n");
my $json = JSON->new;
my $json_text = $json->pretty->canonical->utf8->encode($self->{hash});
my $json = JSON->new;
my $json_text = $json->pretty->canonical->utf8->encode($self->{hash});
open(FILE, "> $filename") or die "Couldn't open $filename: $!\n";
print FILE "$json_text\n";
close(FILE);
open(FILE, "> $filename") or die "Couldn't open $filename: $!\n";
print FILE "$json_text\n";
close(FILE);
}
sub clear {
my $self = shift;
$self->{hash} = {};
my $self = shift;
$self->{hash} = {};
}
sub levenshtein_matches {
my ($self, $keyword) = @_;
my $comma = '';
my $result = "";
my ($self, $keyword) = @_;
my $comma = '';
my $result = "";
foreach my $index (sort keys %{$self->{hash}}) {
my $distance = fastdistance($keyword, $index);
my $length = (length $keyword > length $index) ? length $keyword : length $index;
foreach my $index (sort keys %{$self->{hash}}) {
my $distance = fastdistance($keyword, $index);
my $length = (length $keyword > length $index) ? length $keyword : length $index;
if ($length != 0 && $distance / $length < 0.50) {
$result .= $comma . $index;
$comma = ", ";
if ($length != 0 && $distance / $length < 0.50) {
$result .= $comma . $index;
$comma = ", ";
}
}
}
$result =~ s/(.*), /$1 or /;
$result = "none" if $comma eq '';
return $result;
$result =~ s/(.*), /$1 or /;
$result = "none" if $comma eq '';
return $result;
}
sub set {
my ($self, $index, $key, $value, $dont_save) = @_;
my $lc_index = lc $index;
my ($self, $index, $key, $value, $dont_save) = @_;
my $lc_index = lc $index;
if (not exists $self->{hash}->{$lc_index}) {
my $result = "$self->{name}: $index not found; similiar matches: ";
$result .= $self->levenshtein_matches($index);
return $result;
}
if (not defined $key) {
my $result = "[$self->{name}] $self->{hash}->{$lc_index}->{_name} keys: ";
my $comma = '';
foreach my $k (sort keys %{$self->{hash}->{$lc_index}}) {
next if $k eq '_name';
$result .= $comma . "$k => " . $self->{hash}->{$lc_index}->{$k};
$comma = "; ";
if (not exists $self->{hash}->{$lc_index}) {
my $result = "$self->{name}: $index not found; similiar matches: ";
$result .= $self->levenshtein_matches($index);
return $result;
}
$result .= "none" if ($comma eq '');
return $result;
}
if (not defined $value) {
$value = $self->{hash}->{$lc_index}->{$key};
} else {
$self->{hash}->{$lc_index}->{$key} = $value;
$self->save unless $dont_save;
}
return "[$self->{name}] $self->{hash}->{$lc_index}->{_name}: $key " . (defined $value ? "set to $value" : "is not set.");
if (not defined $key) {
my $result = "[$self->{name}] $self->{hash}->{$lc_index}->{_name} keys: ";
my $comma = '';
foreach my $k (sort keys %{$self->{hash}->{$lc_index}}) {
next if $k eq '_name';
$result .= $comma . "$k => " . $self->{hash}->{$lc_index}->{$k};
$comma = "; ";
}
$result .= "none" if ($comma eq '');
return $result;
}
if (not defined $value) { $value = $self->{hash}->{$lc_index}->{$key}; }
else {
$self->{hash}->{$lc_index}->{$key} = $value;
$self->save unless $dont_save;
}
return "[$self->{name}] $self->{hash}->{$lc_index}->{_name}: $key " . (defined $value ? "set to $value" : "is not set.");
}
sub unset {
my ($self, $index, $key) = @_;
my $lc_index = lc $index;
my ($self, $index, $key) = @_;
my $lc_index = lc $index;
if (not exists $self->{hash}->{$lc_index}) {
my $result = "$self->{name}: $index not found; similiar matches: ";
$result .= $self->levenshtein_matches($index);
return $result;
}
if (not exists $self->{hash}->{$lc_index}) {
my $result = "$self->{name}: $index not found; similiar matches: ";
$result .= $self->levenshtein_matches($index);
return $result;
}
if (defined delete $self->{hash}->{$lc_index}->{$key}) {
$self->save;
return "[$self->{name}] $self->{hash}->{$lc_index}->{_name}: $key unset.";
} else {
return "[$self->{name}] $self->{hash}->{$lc_index}->{_name}: $key does not exist.";
}
if (defined delete $self->{hash}->{$lc_index}->{$key}) {
$self->save;
return "[$self->{name}] $self->{hash}->{$lc_index}->{_name}: $key unset.";
} else {
return "[$self->{name}] $self->{hash}->{$lc_index}->{_name}: $key does not exist.";
}
}
sub exists {
my ($self, $index, $data_index) = @_;
return exists $self->{hash}->{lc $index} if not defined $data_index;
return exists $self->{hash}->{lc $index}->{$data_index};
my ($self, $index, $data_index) = @_;
return exists $self->{hash}->{lc $index} if not defined $data_index;
return exists $self->{hash}->{lc $index}->{$data_index};
}
sub get_keys {
my ($self, $index) = @_;
return keys %{$self->{hash}} if not defined $index;
return grep { $_ ne '_name' } keys %{$self->{hash}->{lc $index}};
my ($self, $index) = @_;
return keys %{$self->{hash}} if not defined $index;
return grep { $_ ne '_name' } keys %{$self->{hash}->{lc $index}};
}
sub get_data {
my ($self, $index, $data_index) = @_;
my $lc_index = lc $index;
return undef if not exists $self->{hash}->{$lc_index};
return $self->{hash}->{$lc_index} if not defined $data_index;
return $self->{hash}->{$lc_index}->{$data_index};
my ($self, $index, $data_index) = @_;
my $lc_index = lc $index;
return undef if not exists $self->{hash}->{$lc_index};
return $self->{hash}->{$lc_index} if not defined $data_index;
return $self->{hash}->{$lc_index}->{$data_index};
}
sub add {
my ($self, $index, $data, $dont_save) = @_;
my $lc_index = lc $index;
$data->{_name} = $index; # preserve case of index
$self->{hash}->{$lc_index} = $data;
$self->save unless $dont_save;
return "$index added to $self->{name}.";
my ($self, $index, $data, $dont_save) = @_;
my $lc_index = lc $index;
$data->{_name} = $index; # preserve case of index
$self->{hash}->{$lc_index} = $data;
$self->save unless $dont_save;
return "$index added to $self->{name}.";
}
sub remove {
my ($self, $index, $data_index, $dont_save) = @_;
my $lc_index = lc $index;
my ($self, $index, $data_index, $dont_save) = @_;
my $lc_index = lc $index;
if (not exists $self->{hash}->{$lc_index}) {
my $result = "$self->{name}: $index not found; similiar matches: ";
$result .= $self->levenshtein_matches($lc_index);
return $result;
}
if (defined $data_index) {
if (defined delete $self->{hash}->{$lc_index}->{$data_index}) {
delete $self->{hash}->{$lc_index} if keys(%{$self->{hash}->{$lc_index}}) == 1;
$self->save unless $dont_save;
return "$self->{hash}->{$lc_index}->{_name}.$data_index removed from $self->{name}";
} else {
return "$self->{name}: $self->{hash}->{$lc_index}->{_name}.$data_index does not exist.";
if (not exists $self->{hash}->{$lc_index}) {
my $result = "$self->{name}: $index not found; similiar matches: ";
$result .= $self->levenshtein_matches($lc_index);
return $result;
}
}
my $data = delete $self->{hash}->{$lc_index};
if (defined $data) {
$self->save unless $dont_save;
return "$data->{_name} removed from $self->{name}.";
} else {
return "$self->{name}: $data_index does not exist.";
}
if (defined $data_index) {
if (defined delete $self->{hash}->{$lc_index}->{$data_index}) {
delete $self->{hash}->{$lc_index} if keys(%{$self->{hash}->{$lc_index}}) == 1;
$self->save unless $dont_save;
return "$self->{hash}->{$lc_index}->{_name}.$data_index removed from $self->{name}";
} else {
return "$self->{name}: $self->{hash}->{$lc_index}->{_name}.$data_index does not exist.";
}
}
my $data = delete $self->{hash}->{$lc_index};
if (defined $data) {
$self->save unless $dont_save;
return "$data->{_name} removed from $self->{name}.";
} else {
return "$self->{name}: $data_index does not exist.";
}
}
1;

View File

@ -13,23 +13,19 @@
#####################################################################
# $Id: IRC.pm,v 1.10 2004/04/30 18:02:51 jmuhlich Exp $
package PBot::IRC; # pragma_ 2011/01/21
package PBot::IRC; # pragma_ 2011/01/21
BEGIN { require 5.004; } # needs IO::* and $coderef->(@args) syntax
use PBot::IRC::Connection; # pragma_ 2011/01/21
use PBot::IRC::EventQueue; # pragma_ 2011/01/21
use PBot::IRC::Connection; # pragma_ 2011/01/21
use PBot::IRC::EventQueue; # pragma_ 2011/01/21
use IO::Select;
use Carp;
use feature 'unicode_strings';
# grab the drop-in replacement for time() from Time::HiRes, if it's available
BEGIN {
Time::HiRes->import('time') if eval "require Time::HiRes";
}
BEGIN { Time::HiRes->import('time') if eval "require Time::HiRes"; }
use strict;
use vars qw($VERSION);
@ -37,42 +33,42 @@ use vars qw($VERSION);
$VERSION = "0.79";
sub new {
my $proto = shift;
my $proto = shift;
my $self = {
'_conn' => [],
'_connhash' => {},
'_error' => IO::Select->new(),
'_debug' => 0,
'_schedulequeue' => new PBot::IRC::EventQueue(), # pragma_ 2011/01/21
'_outputqueue' => new PBot::IRC::EventQueue(), # pragma_ 2011/01/21
'_read' => IO::Select->new(),
'_timeout' => 1,
'_write' => IO::Select->new(),
};
my $self = {
'_conn' => [],
'_connhash' => {},
'_error' => IO::Select->new(),
'_debug' => 0,
'_schedulequeue' => new PBot::IRC::EventQueue(), # pragma_ 2011/01/21
'_outputqueue' => new PBot::IRC::EventQueue(), # pragma_ 2011/01/21
'_read' => IO::Select->new(),
'_timeout' => 1,
'_write' => IO::Select->new(),
};
bless $self, $proto;
bless $self, $proto;
return $self;
return $self;
}
sub outputqueue {
my $self = shift;
return $self->{_outputqueue};
my $self = shift;
return $self->{_outputqueue};
}
sub schedulequeue {
my $self = shift;
return $self->{_schedulequeue};
my $self = shift;
return $self->{_schedulequeue};
}
# Front end to addfh(), below. Sets it to read by default.
# Takes at least 1 arg: an object to add to the select loop.
# (optional) a flag string to pass to addfh() (see below)
sub addconn {
my ($self, $conn) = @_;
my ($self, $conn) = @_;
$self->addfh( $conn->socket, $conn->can('parse'), ($_[2] || 'r'), $conn);
$self->addfh($conn->socket, $conn->can('parse'), ($_[2] || 'r'), $conn);
}
# Adds a filehandle to the select loop. Tasty and flavorful.
@ -83,197 +79,182 @@ sub addconn {
# except that you can combine flags (i.e., "rw").
# (optional) an object that the coderef is a method of
sub addfh {
my ($self, $fh, $code, $flag, $obj) = @_;
my ($letter);
my ($self, $fh, $code, $flag, $obj) = @_;
my ($letter);
die "Not enough arguments to IRC->addfh()" unless defined $code;
die "Not enough arguments to IRC->addfh()" unless defined $code;
if ($flag) {
foreach $letter (split(//, lc $flag)) {
if ($letter eq 'r') {
$self->{_read}->add( $fh );
} elsif ($letter eq 'w') {
$self->{_write}->add( $fh );
} elsif ($letter eq 'e') {
$self->{_error}->add( $fh );
}
if ($flag) {
foreach $letter (split(//, lc $flag)) {
if ($letter eq 'r') { $self->{_read}->add($fh); }
elsif ($letter eq 'w') { $self->{_write}->add($fh); }
elsif ($letter eq 'e') { $self->{_error}->add($fh); }
}
} else {
$self->{_read}->add($fh);
}
} else {
$self->{_read}->add( $fh );
}
$self->{_connhash}->{$fh} = [ $code, $obj ];
$self->{_connhash}->{$fh} = [$code, $obj];
}
# Sets or returns the debugging flag for this object.
# Takes 1 optional arg: a new boolean value for the flag.
sub debug {
my $self = shift;
my $self = shift;
if (@_) {
$self->{_debug} = $_[0];
}
return $self->{_debug};
if (@_) { $self->{_debug} = $_[0]; }
return $self->{_debug};
}
# Goes through one iteration of the main event loop. Useful for integrating
# other event-based systems (Tk, etc.) with Net::IRC.
# Takes no args.
sub do_one_loop {
my $self = shift;
my ($ev, $sock, $time, $nexttimer, $timeout);
my (undef, undef, undef, $caller) = caller(1);
my $self = shift;
my ($ev, $sock, $time, $nexttimer, $timeout);
my (undef, undef, undef, $caller) = caller(1);
$time = time(); # no use calling time() all the time.
$time = time(); # no use calling time() all the time.
if (!$self->outputqueue->is_empty) {
my $outputevent = undef;
while (defined($outputevent = $self->outputqueue->head)
&& $outputevent->time <= $time) {
$outputevent = $self->outputqueue->dequeue();
$outputevent->content->{coderef}->(@{$outputevent->content->{args}});
if (!$self->outputqueue->is_empty) {
my $outputevent = undef;
while (defined($outputevent = $self->outputqueue->head) && $outputevent->time <= $time) {
$outputevent = $self->outputqueue->dequeue();
$outputevent->content->{coderef}->(@{$outputevent->content->{args}});
}
$nexttimer = $self->outputqueue->head->time if !$self->outputqueue->is_empty();
}
$nexttimer = $self->outputqueue->head->time if !$self->outputqueue->is_empty();
}
# we don't want to bother waiting on input or running
# scheduled events if we're just flushing the output queue
# so we bail out here
return if $caller eq 'PBot::IRC::flush_output_queue'; # pragma_ 2011/01/21
# we don't want to bother waiting on input or running
# scheduled events if we're just flushing the output queue
# so we bail out here
return if $caller eq 'PBot::IRC::flush_output_queue'; # pragma_ 2011/01/21
# Check the queue for scheduled events to run.
if (!$self->schedulequeue->is_empty) {
my $scheduledevent = undef;
while (defined($scheduledevent = $self->schedulequeue->head) && $scheduledevent->time <= $time) {
$scheduledevent = $self->schedulequeue->dequeue();
$scheduledevent->content->{coderef}->(@{$scheduledevent->content->{args}});
# Check the queue for scheduled events to run.
if (!$self->schedulequeue->is_empty) {
my $scheduledevent = undef;
while (defined($scheduledevent = $self->schedulequeue->head) && $scheduledevent->time <= $time) {
$scheduledevent = $self->schedulequeue->dequeue();
$scheduledevent->content->{coderef}->(@{$scheduledevent->content->{args}});
}
if (!$self->schedulequeue->is_empty() && $nexttimer && $self->schedulequeue->head->time < $nexttimer) { $nexttimer = $self->schedulequeue->head->time; }
}
if (!$self->schedulequeue->is_empty()
&& $nexttimer
&& $self->schedulequeue->head->time < $nexttimer) {
$nexttimer = $self->schedulequeue->head->time;
# Block until input arrives, then hand the filehandle over to the
# user-supplied coderef. Look! It's a freezer full of government cheese!
if ($nexttimer) { $timeout = $nexttimer - $time < $self->{_timeout} ? $nexttimer - $time : $self->{_timeout}; }
else { $timeout = $self->{_timeout}; }
foreach $ev (
IO::Select->select(
$self->{_read},
$self->{_write},
$self->{_error},
$timeout
)
)
{
foreach $sock (@{$ev}) {
my $conn = $self->{_connhash}->{$sock};
$conn or next;
# $conn->[0] is a code reference to a handler sub.
# $conn->[1] is optionally an object which the
# handler sub may be a method of.
$conn->[0]->($conn->[1] ? ($conn->[1], $sock) : $sock);
}
}
}
# Block until input arrives, then hand the filehandle over to the
# user-supplied coderef. Look! It's a freezer full of government cheese!
if ($nexttimer) {
$timeout = $nexttimer - $time < $self->{_timeout}
? $nexttimer - $time : $self->{_timeout};
} else {
$timeout = $self->{_timeout};
}
foreach $ev (IO::Select->select($self->{_read},
$self->{_write},
$self->{_error},
$timeout)) {
foreach $sock (@{$ev}) {
my $conn = $self->{_connhash}->{$sock};
$conn or next;
# $conn->[0] is a code reference to a handler sub.
# $conn->[1] is optionally an object which the
# handler sub may be a method of.
$conn->[0]->($conn->[1] ? ($conn->[1], $sock) : $sock);
}
}
}
sub flush_output_queue {
my $self = shift;
my $self = shift;
while (!$self->outputqueue->is_empty()) {
$self->do_one_loop();
}
while (!$self->outputqueue->is_empty()) { $self->do_one_loop(); }
}
# Creates and returns a new Connection object.
# Any args here get passed to Connection->connect().
sub newconn {
my $self = shift;
my $conn = PBot::IRC::Connection->new($self, @_); # pragma_ 2011/01/21
my $self = shift;
my $conn = PBot::IRC::Connection->new($self, @_); # pragma_ 2011/01/21
return if $conn->error;
return $conn;
return if $conn->error;
return $conn;
}
# Takes the args passed to it by Connection->schedule()... see it for details.
sub enqueue_scheduled_event {
my $self = shift;
my $time = shift;
my $coderef = shift;
my @args = @_;
my $self = shift;
my $time = shift;
my $coderef = shift;
my @args = @_;
return $self->schedulequeue->enqueue($time, { coderef => $coderef, args => \@args });
return $self->schedulequeue->enqueue($time, {coderef => $coderef, args => \@args});
}
# Takes a scheduled event ID to remove from the queue.
# Returns the deleted coderef, if you actually care.
sub dequeue_scheduled_event {
my ($self, $id) = @_;
$self->schedulequeue->dequeue($id);
my ($self, $id) = @_;
$self->schedulequeue->dequeue($id);
}
# Takes the args passed to it by Connection->schedule()... see it for details.
sub enqueue_output_event {
my $self = shift;
my $time = shift;
my $coderef = shift;
my @args = @_;
my $self = shift;
my $time = shift;
my $coderef = shift;
my @args = @_;
return $self->outputqueue->enqueue($time, { coderef => $coderef, args => \@args });
return $self->outputqueue->enqueue($time, {coderef => $coderef, args => \@args});
}
# Takes a scheduled event ID to remove from the queue.
# Returns the deleted coderef, if you actually care.
sub dequeue_output_event {
my ($self, $id) = @_;
$self->outputqueue->dequeue($id);
my ($self, $id) = @_;
$self->outputqueue->dequeue($id);
}
# Front-end for removefh(), below.
# Takes 1 arg: a Connection (or DCC or whatever) to remove.
sub removeconn {
my ($self, $conn) = @_;
my ($self, $conn) = @_;
$self->removefh( $conn->socket );
$self->removefh($conn->socket);
}
# Given a filehandle, removes it from all select lists. You get the picture.
sub removefh {
my ($self, $fh) = @_;
my ($self, $fh) = @_;
$self->{_read}->remove( $fh );
$self->{_write}->remove( $fh );
$self->{_error}->remove( $fh );
delete $self->{_connhash}->{$fh};
$self->{_read}->remove($fh);
$self->{_write}->remove($fh);
$self->{_error}->remove($fh);
delete $self->{_connhash}->{$fh};
}
# Begin the main loop. Wheee. Hope you remembered to set up your handlers
# first... (takes no args, of course)
sub start {
my $self = shift;
my $self = shift;
while (1) {
$self->do_one_loop();
}
while (1) { $self->do_one_loop(); }
}
# Sets or returns the current timeout, in seconds, for the select loop.
# Takes 1 optional arg: the new value for the timeout, in seconds.
# Fractional timeout values are just fine, as per the core select().
sub timeout {
my $self = shift;
my $self = shift;
if (@_) { $self->{_timeout} = $_[0] }
return $self->{_timeout};
if (@_) { $self->{_timeout} = $_[0] }
return $self->{_timeout};
}
1;
__END__

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@
#####################################################################
# $Id: DCC.pm,v 1.1.1.1 2002/11/14 17:32:15 jmuhlich Exp $
package PBot::IRC::DCC; # pragma_ 2011/21/01
package PBot::IRC::DCC; # pragma_ 2011/21/01
use strict;
@ -32,52 +32,37 @@ use feature 'unicode_strings';
# \merlyn: but he seems like a nice trucker guy...
# archon: you offered to shower with a random guy?
# Methods that can be shared between the various DCC classes.
package PBot::IRC::DCC::Connection; # pragma_ 2011/21/01
package PBot::IRC::DCC::Connection; # pragma_ 2011/21/01
use Carp;
use Socket; # need inet_ntoa...
use Socket; # need inet_ntoa...
use strict;
sub fixaddr {
my ($address) = @_;
chomp $address; # just in case, sigh.
if ($address =~ /^\d+$/) {
return inet_ntoa(pack "N", $address);
} elsif ($address =~ /^[12]?\d{1,2}\.[12]?\d{1,2}\.[12]?\d{1,2}\.[12]?\d{1,2}$/) {
return $address;
} elsif ($address =~ tr/a-zA-Z//) { # Whee! Obfuscation!
chomp $address; # just in case, sigh.
if ($address =~ /^\d+$/) { return inet_ntoa(pack "N", $address); }
elsif ($address =~ /^[12]?\d{1,2}\.[12]?\d{1,2}\.[12]?\d{1,2}\.[12]?\d{1,2}$/) { return $address; }
elsif ($address =~ tr/a-zA-Z//) { # Whee! Obfuscation!
return inet_ntoa(((gethostbyname($address))[4])[0]);
} else {
return;
}
}
sub bytes_in {
return shift->{_bin};
}
sub bytes_in { return shift->{_bin}; }
sub bytes_out {
return shift->{_bout};
}
sub bytes_out { return shift->{_bout}; }
sub nick {
return shift->{_nick};
}
sub nick { return shift->{_nick}; }
sub socket {
return shift->{_socket};
}
sub socket { return shift->{_socket}; }
sub time {
return time - shift->{_time};
}
sub time { return time - shift->{_time}; }
sub debug {
return shift->{_debug};
}
sub debug { return shift->{_debug}; }
# Changes here 1998-04-01 by MJD
# Optional third argument `$block'.
@ -88,54 +73,60 @@ sub _getline {
my $frag = $self->{_frag};
if (defined $sock->recv($input, 10240)) {
$frag .= $input;
if (length($frag) > 0) {
$frag .= $input;
if (length($frag) > 0) {
warn "Got ". length($frag) ." bytes from $sock\n"
if $self->{_debug};
warn "Got " . length($frag) . " bytes from $sock\n" if $self->{_debug};
if ($block) { # Block mode (GET)
return $input;
if ($block) { # Block mode (GET)
return $input;
} else { # Line mode (CHAT)
# We're returning \n's 'cause DCC's need 'em
my @lines = split /\012/, $frag, -1;
$lines[-1] .= "\012";
$self->{_frag} = ($frag !~ /\012$/) ? pop @lines : '';
return (@lines);
}
}
else {
# um, if we can read, i say we should read more than 0
# besides, recv isn't returning undef on closed
# sockets. getting rid of this connection...
} else { # Line mode (CHAT)
# We're returning \n's 'cause DCC's need 'em
my @lines = split /\012/, $frag, -1;
$lines[-1] .= "\012";
$self->{_frag} = ($frag !~ /\012$/) ? pop @lines : '';
return (@lines);
}
} else {
warn "recv() received 0 bytes in _getline, closing connection.\n"
if $self->{_debug};
# um, if we can read, i say we should read more than 0
# besides, recv isn't returning undef on closed
# sockets. getting rid of this connection...
$self->{_parent}->handler(PBot::IRC::Event->new('dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}));
$self->{_parent}->parent->removefh($sock);
$self->{_socket}->close;
$self->{_fh}->close if $self->{_fh};
return;
}
warn "recv() received 0 bytes in _getline, closing connection.\n" if $self->{_debug};
$self->{_parent}->handler(
PBot::IRC::Event->new(
'dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}
)
);
$self->{_parent}->parent->removefh($sock);
$self->{_socket}->close;
$self->{_fh}->close if $self->{_fh};
return;
}
} else {
# Error, lets scrap this connection
warn "recv() returned undef, socket error in _getline()\n"
if $self->{_debug};
# Error, lets scrap this connection
$self->{_parent}->handler(PBot::IRC::Event->new('dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}));
$self->{_parent}->parent->removefh($sock);
$self->{_socket}->close;
$self->{_fh}->close if $self->{_fh};
return;
warn "recv() returned undef, socket error in _getline()\n" if $self->{_debug};
$self->{_parent}->handler(
PBot::IRC::Event->new(
'dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}
)
);
$self->{_parent}->parent->removefh($sock);
$self->{_socket}->close;
$self->{_fh}->close if $self->{_fh};
return;
}
}
@ -146,20 +137,22 @@ sub DESTROY {
# live. Duplicate dcc_close events would be a Bad Thing.
if ($self->{_socket}->opened) {
$self->{_parent}->handler(PBot::IRC::Event->new('dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}));
$self->{_socket}->close;
close $self->{_fh} if $self->{_fh};
$self->{_parent}->{_parent}->parent->removeconn($self);
$self->{_parent}->handler(
PBot::IRC::Event->new(
'dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}
)
);
$self->{_socket}->close;
close $self->{_fh} if $self->{_fh};
$self->{_parent}->{_parent}->parent->removeconn($self);
}
}
sub peer {
return ( $_[0]->{_nick}, "DCC " . $_[0]->{_type} );
}
sub peer { return ($_[0]->{_nick}, "DCC " . $_[0]->{_type}); }
# -- #perl was here! --
# orev: hehe...
@ -168,48 +161,56 @@ sub peer {
# tmtowtdi: \merlyn will be hacked to death by a psycho
# archon: yeah, but with is much more amusing
# Connection handling GETs
package PBot::IRC::DCC::GET; # pragma_ 2011/21/01
package PBot::IRC::DCC::GET; # pragma_ 2011/21/01
use IO::Socket;
use Carp;
use strict;
@PBot::IRC::DCC::GET::ISA = qw(Net::IRC::DCC::Connection); # pragma_ 2011/21/01
@PBot::IRC::DCC::GET::ISA = qw(Net::IRC::DCC::Connection); # pragma_ 2011/21/01
sub new {
my ($class, $container, $nick, $address,
$port, $size, $filename, $handle, $offset) = @_;
my (
$class, $container, $nick, $address,
$port, $size, $filename, $handle, $offset
) = @_;
my ($sock, $fh);
# get the address into a dotted quad
$address = &PBot::IRC::DCC::Connection::fixaddr($address); # pragma_ 2011/21/01
$address = &PBot::IRC::DCC::Connection::fixaddr($address); # pragma_ 2011/21/01
return if $port < 1024 or not defined $address or $size < 1;
$fh = defined $handle ? $handle : IO::File->new(">$filename");
unless (defined $fh) {
carp "Can't open $filename for writing: $!";
$sock = new IO::Socket::INET( Proto => "tcp",
PeerAddr => "$address:$port" ) and
$sock->close();
$sock = new IO::Socket::INET(
Proto => "tcp",
PeerAddr => "$address:$port"
) and $sock->close();
return;
}
binmode $fh; # I love this next line. :-)
binmode $fh; # I love this next line. :-)
ref $fh eq 'GLOB' ? select((select($fh), $|++)[0]) : $fh->autoflush(1);
$sock = new IO::Socket::INET( Proto => "tcp",
PeerAddr => "$address:$port" );
$sock = new IO::Socket::INET(
Proto => "tcp",
PeerAddr => "$address:$port"
);
if (defined $sock) {
$container->handler(PBot::IRC::Event->new('dcc_open', # pragma_ 2011/21/01
$nick,
$sock,
'get',
'get', $sock));
$container->handler(
PBot::IRC::Event->new(
'dcc_open', # pragma_ 2011/21/01
$nick,
$sock,
'get',
'get', $sock
)
);
} else {
carp "Can't connect to $address: $!";
@ -220,20 +221,20 @@ sub new {
$sock->autoflush(1);
my $self = {
_bin => defined $offset ? $offset : 0, # bytes recieved so far
_bout => 0, # Bytes we've sent
_connected => 1,
_debug => $container->debug,
_fh => $fh, # FileHandle we will be writing to.
_filename => $filename,
_frag => '',
_nick => $nick, # Nick of person on other end
_parent => $container,
_size => $size, # Expected size of file
_socket => $sock, # Socket we're reading from
_time => time,
_type => 'GET',
};
_bin => defined $offset ? $offset : 0, # bytes recieved so far
_bout => 0, # Bytes we've sent
_connected => 1,
_debug => $container->debug,
_fh => $fh, # FileHandle we will be writing to.
_filename => $filename,
_frag => '',
_nick => $nick, # Nick of person on other end
_parent => $container,
_size => $size, # Expected size of file
_socket => $sock, # Socket we're reading from
_time => time,
_type => 'GET',
};
bless $self, $class;
@ -255,31 +256,38 @@ sub parse {
next unless defined $line;
unless (print {$self->{_fh}} $line) {
carp ("Error writing to " . $self->{_filename} . ": $!");
close $self->{_fh};
$self->{_parent}->parent->removeconn($self);
$self->{_parent}->handler(PBot::IRC::Event->new('dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}));
$self->{_socket}->close;
return;
carp("Error writing to " . $self->{_filename} . ": $!");
close $self->{_fh};
$self->{_parent}->parent->removeconn($self);
$self->{_parent}->handler(
PBot::IRC::Event->new(
'dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}
)
);
$self->{_socket}->close;
return;
}
$self->{_bin} += length($line);
# confirm the packet we've just recieved
unless ( $self->{_socket}->send( pack("N", $self->{_bin}) ) ) {
carp "Error writing to DCC GET socket: $!";
close $self->{_fh};
$self->{_parent}->parent->removeconn($self);
$self->{_parent}->handler(PBot::IRC::Event->new('dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}));
$self->{_socket}->close;
return;
unless ($self->{_socket}->send(pack("N", $self->{_bin}))) {
carp "Error writing to DCC GET socket: $!";
close $self->{_fh};
$self->{_parent}->parent->removeconn($self);
$self->{_parent}->handler(
PBot::IRC::Event->new(
'dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}
)
);
$self->{_socket}->close;
return;
}
$self->{_bout} += 4;
@ -287,40 +295,48 @@ sub parse {
# The file is done.
# If we close the socket, the select loop gets screwy because
# it won't remove its reference to the socket.
if ( $self->{_size} and $self->{_size} <= $self->{_bin} ) {
if ($self->{_size} and $self->{_size} <= $self->{_bin}) {
close $self->{_fh};
$self->{_parent}->parent->removeconn($self);
$self->{_parent}->handler(PBot::IRC::Event->new('dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}));
$self->{_socket}->close;
$self->{_parent}->handler(
PBot::IRC::Event->new(
'dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}
)
);
$self->{_socket}->close;
return;
}
$self->{_parent}->handler(PBot::IRC::Event->new('dcc_update', # pragma_ 2011/21/01
$self->{_nick},
$self,
$self->{_type},
$self ));
$self->{_parent}->handler(
PBot::IRC::Event->new(
'dcc_update', # pragma_ 2011/21/01
$self->{_nick},
$self,
$self->{_type},
$self
)
);
}
sub filename {
return shift->{_filename};
}
sub filename { return shift->{_filename}; }
sub size {
return shift->{_size};
}
sub size { return shift->{_size}; }
sub close {
my ($self, $sock) = @_;
$self->{_fh}->close;
$self->{_parent}->parent->removeconn($self);
$self->{_parent}->handler(PBot::IRC::Event->new('dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}));
$self->{_parent}->handler(
PBot::IRC::Event->new(
'dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}
)
);
$self->{_socket}->close;
return;
}
@ -335,10 +351,9 @@ sub close {
# Silmaril: AWWWWwwww yeeeaAAHH.
# archon: waka chica waka chica
# Connection handling SENDs
package PBot::IRC::DCC::SEND; # pragma_ 2011/21/01
@PBot::IRC::DCC::SEND::ISA = qw(Net::IRC::DCC::Connection); # pragma_ 2011/21/01
package PBot::IRC::DCC::SEND; # pragma_ 2011/21/01
@PBot::IRC::DCC::SEND::ISA = qw(Net::IRC::DCC::Connection); # pragma_ 2011/21/01
use IO::File;
use IO::Socket;
@ -359,7 +374,7 @@ sub new {
unless (defined $fh) {
carp "Couldn't open $filename for reading: $!";
return;
return;
}
binmode $fh;
@ -367,8 +382,10 @@ sub new {
$size = $fh->tell;
$fh->seek(0, SEEK_SET);
$sock = new IO::Socket::INET( Proto => "tcp",
Listen => 1);
$sock = new IO::Socket::INET(
Proto => "tcp",
Listen => 1
);
unless (defined $sock) {
carp "Couldn't open DCC SEND socket: $!";
@ -376,31 +393,33 @@ sub new {
return;
}
$container->ctcp('DCC SEND', $nick, $filename,
unpack("N",inet_aton($container->hostname())),
$sock->sockport(), $size);
$container->ctcp(
'DCC SEND', $nick, $filename,
unpack("N", inet_aton($container->hostname())),
$sock->sockport(), $size
);
$sock->autoflush(1);
my $self = {
_bin => 0, # Bytes we've recieved thus far
_blocksize => $blocksize,
_bout => 0, # Bytes we've sent
_debug => $container->debug,
_fh => $fh, # FileHandle we will be reading from.
_filename => $filename,
_frag => '',
_nick => $nick,
_parent => $container,
_size => $size, # Size of file
_socket => $sock, # Socket we're writing to
_time => 0, # This gets set by Accept->parse()
_type => 'SEND',
_bin => 0, # Bytes we've recieved thus far
_blocksize => $blocksize,
_bout => 0, # Bytes we've sent
_debug => $container->debug,
_fh => $fh, # FileHandle we will be reading from.
_filename => $filename,
_frag => '',
_nick => $nick,
_parent => $container,
_size => $size, # Size of file
_socket => $sock, # Socket we're writing to
_time => 0, # This gets set by Accept->parse()
_type => 'SEND',
};
bless $self, $class;
$sock = PBot::IRC::DCC::Accept->new($sock, $self); # pragma_ 2011/21/01
$sock = PBot::IRC::DCC::Accept->new($sock, $self); # pragma_ 2011/21/01
unless (defined $sock) {
carp "Error in accept: $!";
@ -431,35 +450,44 @@ sub parse {
$self->{_bin} += 4;
unless (defined $size) {
# Dang! The other end unexpectedly canceled.
carp (($self->peer)[1] . " connection to " .
($self->peer)[0] . " lost");
$self->{_fh}->close;
$self->{_parent}->parent->removefh($sock);
$self->{_parent}->handler(PBot::IRC::Event->new('dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}));
$self->{_socket}->close;
return;
# Dang! The other end unexpectedly canceled.
carp(($self->peer)[1] . " connection to " . ($self->peer)[0] . " lost");
$self->{_fh}->close;
$self->{_parent}->parent->removefh($sock);
$self->{_parent}->handler(
PBot::IRC::Event->new(
'dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}
)
);
$self->{_socket}->close;
return;
}
$size = unpack("N", $size);
if ($size >= $self->{_size}) {
if ($self->{_debug}) {
warn "Other end acknowledged entire file ($size >= ",
$self->{_size}, ")";
}
if ($self->{_debug}) {
warn "Other end acknowledged entire file ($size >= ",
$self->{_size}, ")";
}
# they've acknowledged the whole file, we outtie
$self->{_fh}->close;
$self->{_parent}->parent->removeconn($self);
$self->{_parent}->handler(PBot::IRC::Event->new('dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}));
$self->{_socket}->close;
$self->{_parent}->handler(
PBot::IRC::Event->new(
'dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}
)
);
$self->{_socket}->close;
return;
}
@ -467,43 +495,51 @@ sub parse {
# better not send any more
return if $size < $self->{_bout};
unless (defined $self->{_fh}->read($buf,$self->{_blocksize})) {
unless (defined $self->{_fh}->read($buf, $self->{_blocksize})) {
if ($self->{_debug}) {
warn "Failed to read from source file in DCC SEND!";
}
$self->{_fh}->close;
if ($self->{_debug}) { warn "Failed to read from source file in DCC SEND!"; }
$self->{_fh}->close;
$self->{_parent}->parent->removeconn($self);
$self->{_parent}->handler(PBot::IRC::Event->new('dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}));
$self->{_socket}->close;
$self->{_parent}->handler(
PBot::IRC::Event->new(
'dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}
)
);
$self->{_socket}->close;
return;
}
unless ($self->{_socket}->send($buf)) {
if ($self->{_debug}) {
warn "send() failed horribly in DCC SEND"
}
if ($self->{_debug}) { warn "send() failed horribly in DCC SEND" }
$self->{_fh}->close;
$self->{_parent}->parent->removeconn($self);
$self->{_parent}->handler(PBot::IRC::Event->new('dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}));
$self->{_socket}->close;
return;
$self->{_parent}->handler(
PBot::IRC::Event->new(
'dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}
)
);
$self->{_socket}->close;
return;
}
$self->{_bout} += length($buf);
$self->{_parent}->handler(PBot::IRC::Event->new('dcc_update', # pragma_ 2011/21/01
$self->{_nick},
$self,
$self->{_type},
$self ));
$self->{_parent}->handler(
PBot::IRC::Event->new(
'dcc_update', # pragma_ 2011/21/01
$self->{_nick},
$self,
$self->{_type},
$self
)
);
return 1;
}
@ -517,10 +553,9 @@ sub parse {
# archon: she's in league with the guy in your shower
# archon: she gets you drunk and he takes your wallet!
# handles CHAT connections
package PBot::IRC::DCC::CHAT; # pragma_ 2011/21/01
@PBot::IRC::DCC::CHAT::ISA = qw(Net::IRC::DCC::Connection); # pragma_ 2011/21/01
package PBot::IRC::DCC::CHAT; # pragma_ 2011/21/01
@PBot::IRC::DCC::CHAT::ISA = qw(Net::IRC::DCC::Connection); # pragma_ 2011/21/01
use IO::Socket;
use Carp;
@ -532,79 +567,92 @@ sub new {
my ($sock, $self);
if ($type) {
# we're initiating
$sock = new IO::Socket::INET( Proto => "tcp",
Listen => 1);
$sock = new IO::Socket::INET(
Proto => "tcp",
Listen => 1
);
unless (defined $sock) {
carp "Couldn't open DCC CHAT socket: $!";
return;
}
$sock->autoflush(1);
$container->ctcp('DCC CHAT', $nick, 'chat',
unpack("N",inet_aton($container->hostname)),
$sock->sockport());
$sock->autoflush(1);
$container->ctcp(
'DCC CHAT', $nick, 'chat',
unpack("N", inet_aton($container->hostname)),
$sock->sockport()
);
$self = {
_bin => 0, # Bytes we've recieved thus far
_bout => 0, # Bytes we've sent
_connected => 1,
_debug => $container->debug,
_frag => '',
_nick => $nick, # Nick of the client on the other end
_parent => $container,
_socket => $sock, # Socket we're reading from
_time => 0, # This gets set by Accept->parse()
_type => 'CHAT',
};
$self = {
_bin => 0, # Bytes we've recieved thus far
_bout => 0, # Bytes we've sent
_connected => 1,
_debug => $container->debug,
_frag => '',
_nick => $nick, # Nick of the client on the other end
_parent => $container,
_socket => $sock, # Socket we're reading from
_time => 0, # This gets set by Accept->parse()
_type => 'CHAT',
};
bless $self, $class;
bless $self, $class;
$sock = PBot::IRC::DCC::Accept->new($sock, $self); # pragma_ 2011/21/01
$sock = PBot::IRC::DCC::Accept->new($sock, $self); # pragma_ 2011/21/01
unless (defined $sock) {
carp "Error in DCC CHAT connect: $!";
return;
}
unless (defined $sock) {
carp "Error in DCC CHAT connect: $!";
return;
}
} else { # we're connecting
} else { # we're connecting
$address = &PBot::IRC::DCC::Connection::fixaddr($address); # pragma_ 2011/21/01
return if $port < 1024 or not defined $address;
$address = &PBot::IRC::DCC::Connection::fixaddr($address); # pragma_ 2011/21/01
return if $port < 1024 or not defined $address;
$sock = new IO::Socket::INET( Proto => "tcp",
PeerAddr => "$address:$port");
$sock = new IO::Socket::INET(
Proto => "tcp",
PeerAddr => "$address:$port"
);
if (defined $sock) {
$container->handler(PBot::IRC::Event->new('dcc_open', # pragma_ 2011/21/01
$nick,
$sock,
'chat',
'chat', $sock));
} else {
carp "Error in DCC CHAT connect: $!";
return;
}
$container->handler(
PBot::IRC::Event->new(
'dcc_open', # pragma_ 2011/21/01
$nick,
$sock,
'chat',
'chat', $sock
)
);
} else {
carp "Error in DCC CHAT connect: $!";
return;
}
$sock->autoflush(1);
$sock->autoflush(1);
$self = {
_bin => 0, # Bytes we've recieved thus far
_bout => 0, # Bytes we've sent
_connected => 1,
_nick => $nick, # Nick of the client on the other end
_parent => $container,
_socket => $sock, # Socket we're reading from
_time => time,
_type => 'CHAT',
};
$self = {
_bin => 0, # Bytes we've recieved thus far
_bout => 0, # Bytes we've sent
_connected => 1,
_nick => $nick, # Nick of the client on the other end
_parent => $container,
_socket => $sock, # Socket we're reading from
_time => time,
_type => 'CHAT',
};
bless $self, $class;
bless $self, $class;
$self->{_parent}->parent->addfh($self->socket,
$self->can('parse'), 'r', $self);
$self->{_parent}->parent->addfh(
$self->socket,
$self->can('parse'), 'r', $self
);
}
return $self;
@ -623,24 +671,32 @@ sub parse {
my ($self, $sock) = @_;
foreach my $line ($self->_getline($sock)) {
return unless defined $line;
return unless defined $line;
$self->{_bin} += length($line);
$self->{_bin} += length($line);
return undef if $line eq "\012";
$self->{_bout} += length($line);
return undef if $line eq "\012";
$self->{_bout} += length($line);
$self->{_parent}->handler(PBot::IRC::Event->new('chat', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
'chat',
$line));
$self->{_parent}->handler(
PBot::IRC::Event->new(
'chat', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
'chat',
$line
)
);
$self->{_parent}->handler(PBot::IRC::Event->new('dcc_update', # pragma_ 2011/21/01
$self->{_nick},
$self,
$self->{_type},
$self ));
$self->{_parent}->handler(
PBot::IRC::Event->new(
'dcc_update', # pragma_ 2011/21/01
$self->{_nick},
$self,
$self->{_type},
$self
)
);
}
}
@ -650,15 +706,12 @@ sub parse {
sub privmsg {
my ($self) = shift;
unless (@_) {
croak 'Not enough arguments to privmsg()';
}
unless (@_) { croak 'Not enough arguments to privmsg()'; }
# Don't send a CR over DCC CHAT -- it's not wanted.
$self->socket->send(join('', @_) . "\012");
}
# -- #perl was here! --
# \merlyn: this girl carly at the bar is aBABE
# archon: are you sure? you don't sound like you're in a condition to
@ -668,26 +721,25 @@ sub privmsg {
# tmtowtdi: uh, yeah...
# \merlyn: good topic
# Sockets waiting for accept() use this to shoehorn into the select loop.
package PBot::IRC::DCC::Accept; # pragma_ 2011/21/01
package PBot::IRC::DCC::Accept; # pragma_ 2011/21/01
@PBot::IRC::DCC::Accept::ISA = qw(Net::IRC::DCC::Connection); # pragma_ 2011/21/01
@PBot::IRC::DCC::Accept::ISA = qw(Net::IRC::DCC::Connection); # pragma_ 2011/21/01
use Carp;
use Socket; # we use a lot of Socket functions in parse()
use Socket; # we use a lot of Socket functions in parse()
use strict;
sub new {
my ($class, $sock, $parent) = @_;
my ($self);
$self = { _debug => $parent->debug,
_nonblock => 1,
_socket => $sock,
_parent => $parent,
_type => 'accept',
};
$self = {
_debug => $parent->debug,
_nonblock => 1,
_socket => $sock,
_parent => $parent,
_type => 'accept',
};
bless $self, $class;
@ -703,48 +755,50 @@ sub parse {
my ($self) = shift;
my ($sock);
$sock = $self->{_socket}->accept;
$sock = $self->{_socket}->accept;
$self->{_parent}->{_socket} = $sock;
$self->{_parent}->{_time} = time;
$self->{_parent}->{_time} = time;
if ($self->{_parent}->{_type} eq 'SEND') {
# ok, to get the ball rolling, we send them the first packet.
my $buf;
unless (defined $self->{_parent}->{_fh}->
read($buf, $self->{_parent}->{_blocksize})) {
return;
}
unless (defined $sock->send($buf)) {
$sock->close;
$self->{_parent}->{_fh}->close;
$self->{_parent}->{_parent}->parent->removefh($sock);
$self->{_parent}->handler(PBot::IRC::Event->new('dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}));
$self->{_socket}->close;
return;
}
# ok, to get the ball rolling, we send them the first packet.
my $buf;
unless (defined $self->{_parent}->{_fh}->read($buf, $self->{_parent}->{_blocksize})) { return; }
unless (defined $sock->send($buf)) {
$sock->close;
$self->{_parent}->{_fh}->close;
$self->{_parent}->{_parent}->parent->removefh($sock);
$self->{_parent}->handler(
PBot::IRC::Event->new(
'dcc_close', # pragma_ 2011/21/01
$self->{_nick},
$self->{_socket},
$self->{_type}
)
);
$self->{_socket}->close;
return;
}
}
$self->{_parent}->{_parent}->parent->addconn($self->{_parent});
$self->{_parent}->{_parent}->parent->removeconn($self);
$self->{_parent}->{_parent}->handler(PBot::IRC::Event-> # pragma_ 2011/21/01
new('dcc_open',
$self->{_parent}->{_nick},
$self->{_parent}->{_socket},
$self->{_parent}->{_type},
$self->{_parent}->{_type},
$self->{_parent}->{_socket})
);
$self->{_parent}->{_parent}->handler(
PBot::IRC::Event-> # pragma_ 2011/21/01
new(
'dcc_open',
$self->{_parent}->{_nick},
$self->{_parent}->{_socket},
$self->{_parent}->{_type},
$self->{_parent}->{_type},
$self->{_parent}->{_socket}
)
);
}
1;
__END__
=head1 NAME

View File

@ -22,7 +22,7 @@
# Well, welcome to the real world, guys, where code needs to be
# maintainable and sane.
package PBot::IRC::Event; # pragma_ 2011/21/01
package PBot::IRC::Event; # pragma_ 2011/21/01
use feature 'unicode_strings';
@ -36,73 +36,70 @@ our %_names;
# the name of the format string for the event
# (optional) any number of arguments provided by the event
sub new {
my $class = shift;
my $type = shift;
my $from = shift;
my $to = shift;
my $format = shift;
my $args = \@_;
my $self = {
'type' => $type,
'from' => undef,
'to' => ref($to) eq 'ARRAY' ? $to : [ $to ],
'format' => $format,
'args' => [],
};
my $class = shift;
bless $self, $class;
my $type = shift;
my $from = shift;
my $to = shift;
my $format = shift;
my $args = \@_;
if ($self->type !~ /\D/) {
$self->type($self->trans($self->type));
} else {
$self->type(lc($self->type));
}
my $self = {
'type' => $type,
'from' => undef,
'to' => ref($to) eq 'ARRAY' ? $to : [$to],
'format' => $format,
'args' => [],
};
$self->from($from); # sets nick, user, and host
$self->args($args); # strips colons from args
bless $self, $class;
return $self;
if ($self->type !~ /\D/) { $self->type($self->trans($self->type)); }
else { $self->type(lc($self->type)); }
$self->from($from); # sets nick, user, and host
$self->args($args); # strips colons from args
return $self;
}
# Sets or returns an argument list for this event.
# Takes any number of args: the arguments for the event.
sub args {
my $self = shift;
my $args = shift;
my $self = shift;
my $args = shift;
if ($args) {
my (@q, $i, $ct) = @{$args}; # This line is solemnly dedicated to \mjd.
if ($args) {
my (@q, $i, $ct) = @{$args}; # This line is solemnly dedicated to \mjd.
$self->{'args'} = [ ];
while (@q) {
$i = shift @q;
next unless defined $i;
$self->{'args'} = [];
while (@q) {
$i = shift @q;
next unless defined $i;
if ($i =~ /^:/ and $ct) { # Concatenate :-args.
$i = join ' ', (substr($i, 1), @q);
push @{$self->{'args'}}, $i;
last;
}
push @{$self->{'args'}}, $i;
$ct++;
if ($i =~ /^:/ and $ct) { # Concatenate :-args.
$i = join ' ', (substr($i, 1), @q);
push @{$self->{'args'}}, $i;
last;
}
push @{$self->{'args'}}, $i;
$ct++;
}
}
}
return @{$self->{'args'}};
return @{$self->{'args'}};
}
# Dumps the contents of an event to STDERR so you can see what's inside.
# Takes no args.
sub dump {
my ($self, $arg, $counter) = (shift, undef, 0); # heh heh!
my ($self, $arg, $counter) = (shift, undef, 0); # heh heh!
printf STDERR "TYPE: %-30s FORMAT: %-30s\n", $self->type, $self->format;
print STDERR "FROM: ", $self->from, "\n";
print STDERR "TO: ", join(", ", @{$self->to}), "\n";
foreach $arg ($self->args) {
print STDERR "Arg ", $counter++, ": ", $arg, "\n";
}
foreach $arg ($self->args) { print STDERR "Arg ", $counter++, ": ", $arg, "\n"; }
}
# Sets or returns the format string for this event.
@ -121,18 +118,18 @@ sub from {
my @part;
if (@_) {
# avoid certain irritating and spurious warnings from this line...
{ local $^W;
@part = split /[\@!]/, $_[0], 3;
}
$self->nick(defined $part[0] ? $part[0] : '');
$self->user(defined $part[1] ? $part[1] : '');
$self->host(defined $part[2] ? $part[2] : '');
defined $self->user ?
$self->userhost($self->user . '@' . $self->host) :
$self->userhost($self->host);
$self->{'from'} = $_[0];
# avoid certain irritating and spurious warnings from this line...
{
local $^W;
@part = split /[\@!]/, $_[0], 3;
}
$self->nick(defined $part[0] ? $part[0] : '');
$self->user(defined $part[1] ? $part[1] : '');
$self->host(defined $part[2] ? $part[2] : '');
defined $self->user ? $self->userhost($self->user . '@' . $self->host) : $self->userhost($self->host);
$self->{'from'} = $_[0];
}
return $self->{'from'};
@ -161,7 +158,7 @@ sub nick {
sub to {
my $self = shift;
$self->{'to'} = [ @_ ] if @_;
$self->{'to'} = [@_] if @_;
return wantarray ? @{$self->{'to'}} : $self->{'to'};
}
@ -194,292 +191,291 @@ sub userhost {
# Simple sub for translating server numerics to their appropriate names.
# Takes one arg: the number to be translated.
sub trans {
shift if (ref($_[0]) || $_[0]) =~ /^PBot::IRC/; # pragma_ 2011/21/01
shift if (ref($_[0]) || $_[0]) =~ /^PBot::IRC/; # pragma_ 2011/21/01
my $ev = shift;
return (exists $_names{$ev} ? $_names{$ev} : undef);
}
%_names = (
# suck! these aren't treated as strings --
# 001 ne 1 for the purpose of hash keying, apparently.
'001' => "welcome",
'002' => "yourhost",
'003' => "created",
'004' => "myinfo",
'005' => "map", # Undernet Extension, Kajetan@Hinner.com, 17/11/98
'006' => "mapmore", # Undernet Extension, Kajetan@Hinner.com, 17/11/98
'007' => "mapend", # Undernet Extension, Kajetan@Hinner.com, 17/11/98
'008' => "snomask", # Undernet Extension, Kajetan@Hinner.com, 17/11/98
'009' => "statmemtot", # Undernet Extension, Kajetan@Hinner.com, 17/11/98
'010' => "statmem", # Undernet Extension, Kajetan@Hinner.com, 17/11/98
200 => "tracelink",
201 => "traceconnecting",
202 => "tracehandshake",
203 => "traceunknown",
204 => "traceoperator",
205 => "traceuser",
206 => "traceserver",
208 => "tracenewtype",
209 => "traceclass",
211 => "statslinkinfo",
212 => "statscommands",
213 => "statscline",
214 => "statsnline",
215 => "statsiline",
216 => "statskline",
217 => "statsqline",
218 => "statsyline",
219 => "endofstats",
220 => "statsbline", # UnrealIrcd, Hendrik Frenzel
221 => "umodeis",
222 => "sqline_nick", # UnrealIrcd, Hendrik Frenzel
223 => "statsgline", # UnrealIrcd, Hendrik Frenzel
224 => "statstline", # UnrealIrcd, Hendrik Frenzel
225 => "statseline", # UnrealIrcd, Hendrik Frenzel
226 => "statsnline", # UnrealIrcd, Hendrik Frenzel
227 => "statsvline", # UnrealIrcd, Hendrik Frenzel
231 => "serviceinfo",
232 => "endofservices",
233 => "service",
234 => "servlist",
235 => "servlistend",
241 => "statslline",
242 => "statsuptime",
243 => "statsoline",
244 => "statshline",
245 => "statssline", # Reserved, Kajetan@Hinner.com, 17/10/98
246 => "statstline", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
247 => "statsgline", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
# suck! these aren't treated as strings --
# 001 ne 1 for the purpose of hash keying, apparently.
'001' => "welcome",
'002' => "yourhost",
'003' => "created",
'004' => "myinfo",
'005' => "map", # Undernet Extension, Kajetan@Hinner.com, 17/11/98
'006' => "mapmore", # Undernet Extension, Kajetan@Hinner.com, 17/11/98
'007' => "mapend", # Undernet Extension, Kajetan@Hinner.com, 17/11/98
'008' => "snomask", # Undernet Extension, Kajetan@Hinner.com, 17/11/98
'009' => "statmemtot", # Undernet Extension, Kajetan@Hinner.com, 17/11/98
'010' => "statmem", # Undernet Extension, Kajetan@Hinner.com, 17/11/98
200 => "tracelink",
201 => "traceconnecting",
202 => "tracehandshake",
203 => "traceunknown",
204 => "traceoperator",
205 => "traceuser",
206 => "traceserver",
208 => "tracenewtype",
209 => "traceclass",
211 => "statslinkinfo",
212 => "statscommands",
213 => "statscline",
214 => "statsnline",
215 => "statsiline",
216 => "statskline",
217 => "statsqline",
218 => "statsyline",
219 => "endofstats",
220 => "statsbline", # UnrealIrcd, Hendrik Frenzel
221 => "umodeis",
222 => "sqline_nick", # UnrealIrcd, Hendrik Frenzel
223 => "statsgline", # UnrealIrcd, Hendrik Frenzel
224 => "statstline", # UnrealIrcd, Hendrik Frenzel
225 => "statseline", # UnrealIrcd, Hendrik Frenzel
226 => "statsnline", # UnrealIrcd, Hendrik Frenzel
227 => "statsvline", # UnrealIrcd, Hendrik Frenzel
231 => "serviceinfo",
232 => "endofservices",
233 => "service",
234 => "servlist",
235 => "servlistend",
241 => "statslline",
242 => "statsuptime",
243 => "statsoline",
244 => "statshline",
245 => "statssline", # Reserved, Kajetan@Hinner.com, 17/10/98
246 => "statstline", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
247 => "statsgline", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
### TODO: need numerics to be able to map to multiple strings
### 247 => "statsxline", # UnrealIrcd, Hendrik Frenzel
248 => "statsuline", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
249 => "statsdebug", # Unspecific Extension, Kajetan@Hinner.com, 17/10/98
250 => "luserconns", # 1998-03-15 -- tkil
251 => "luserclient",
252 => "luserop",
253 => "luserunknown",
254 => "luserchannels",
255 => "luserme",
256 => "adminme",
257 => "adminloc1",
258 => "adminloc2",
259 => "adminemail",
261 => "tracelog",
262 => "endoftrace", # 1997-11-24 -- archon
265 => "n_local", # 1997-10-16 -- tkil
266 => "n_global", # 1997-10-16 -- tkil
271 => "silelist", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
272 => "endofsilelist", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
275 => "statsdline", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
280 => "glist", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
281 => "endofglist", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
290 => "helphdr", # UnrealIrcd, Hendrik Frenzel
291 => "helpop", # UnrealIrcd, Hendrik Frenzel
292 => "helptlr", # UnrealIrcd, Hendrik Frenzel
293 => "helphlp", # UnrealIrcd, Hendrik Frenzel
294 => "helpfwd", # UnrealIrcd, Hendrik Frenzel
295 => "helpign", # UnrealIrcd, Hendrik Frenzel
248 => "statsuline", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
249 => "statsdebug", # Unspecific Extension, Kajetan@Hinner.com, 17/10/98
250 => "luserconns", # 1998-03-15 -- tkil
251 => "luserclient",
252 => "luserop",
253 => "luserunknown",
254 => "luserchannels",
255 => "luserme",
256 => "adminme",
257 => "adminloc1",
258 => "adminloc2",
259 => "adminemail",
261 => "tracelog",
262 => "endoftrace", # 1997-11-24 -- archon
265 => "n_local", # 1997-10-16 -- tkil
266 => "n_global", # 1997-10-16 -- tkil
271 => "silelist", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
272 => "endofsilelist", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
275 => "statsdline", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
280 => "glist", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
281 => "endofglist", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
290 => "helphdr", # UnrealIrcd, Hendrik Frenzel
291 => "helpop", # UnrealIrcd, Hendrik Frenzel
292 => "helptlr", # UnrealIrcd, Hendrik Frenzel
293 => "helphlp", # UnrealIrcd, Hendrik Frenzel
294 => "helpfwd", # UnrealIrcd, Hendrik Frenzel
295 => "helpign", # UnrealIrcd, Hendrik Frenzel
300 => "none",
301 => "away",
302 => "userhost",
303 => "ison",
304 => "rpl_text", # Bahamut IRCD
305 => "unaway",
306 => "nowaway",
307 => "userip", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
308 => "rulesstart", # UnrealIrcd, Hendrik Frenzel
309 => "endofrules", # UnrealIrcd, Hendrik Frenzel
310 => "whoishelp", # (July01-01)Austnet Extension, found by Andypoo <andypoo@secret.com.au>
311 => "whoisuser",
312 => "whoisserver",
313 => "whoisoperator",
314 => "whowasuser",
315 => "endofwho",
316 => "whoischanop",
317 => "whoisidle",
318 => "endofwhois",
319 => "whoischannels",
320 => "whoisvworld", # (July01-01)Austnet Extension, found by Andypoo <andypoo@secret.com.au>
321 => "liststart",
322 => "list",
323 => "listend",
324 => "channelmodeis",
329 => "channelcreate", # 1997-11-24 -- archon
330 => "whoisaccount", # 2011-02-10 pragma_ for freenode
331 => "notopic",
332 => "topic",
333 => "topicinfo", # 1997-11-24 -- archon
334 => "listusage", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
335 => "whoisbot", # UnrealIrcd, Hendrik Frenzel
341 => "inviting",
342 => "summoning",
346 => "invitelist", # UnrealIrcd, Hendrik Frenzel
347 => "endofinvitelist", # UnrealIrcd, Hendrik Frenzel
348 => "exlist", # UnrealIrcd, Hendrik Frenzel
349 => "endofexlist", # UnrealIrcd, Hendrik Frenzel
351 => "version",
352 => "whoreply",
353 => "namreply",
354 => "whospcrpl", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
361 => "killdone",
362 => "closing",
363 => "closeend",
364 => "links",
365 => "endoflinks",
366 => "endofnames",
367 => "banlist",
368 => "endofbanlist",
369 => "endofwhowas",
371 => "info",
372 => "motd",
373 => "infostart",
374 => "endofinfo",
375 => "motdstart",
376 => "endofmotd",
377 => "motd2", # 1997-10-16 -- tkil
378 => "austmotd", # (July01-01)Austnet Extension, found by Andypoo <andypoo@secret.com.au>
379 => "whoismodes", # UnrealIrcd, Hendrik Frenzel
381 => "youreoper",
382 => "rehashing",
383 => "youreservice", # UnrealIrcd, Hendrik Frenzel
384 => "myportis",
385 => "notoperanymore", # Unspecific Extension, Kajetan@Hinner.com, 17/10/98
386 => "qlist", # UnrealIrcd, Hendrik Frenzel
387 => "endofqlist", # UnrealIrcd, Hendrik Frenzel
388 => "alist", # UnrealIrcd, Hendrik Frenzel
389 => "endofalist", # UnrealIrcd, Hendrik Frenzel
391 => "time",
392 => "usersstart",
393 => "users",
394 => "endofusers",
395 => "nousers",
300 => "none",
301 => "away",
302 => "userhost",
303 => "ison",
304 => "rpl_text", # Bahamut IRCD
305 => "unaway",
306 => "nowaway",
307 => "userip", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
308 => "rulesstart", # UnrealIrcd, Hendrik Frenzel
309 => "endofrules", # UnrealIrcd, Hendrik Frenzel
310 => "whoishelp", # (July01-01)Austnet Extension, found by Andypoo <andypoo@secret.com.au>
311 => "whoisuser",
312 => "whoisserver",
313 => "whoisoperator",
314 => "whowasuser",
315 => "endofwho",
316 => "whoischanop",
317 => "whoisidle",
318 => "endofwhois",
319 => "whoischannels",
320 => "whoisvworld", # (July01-01)Austnet Extension, found by Andypoo <andypoo@secret.com.au>
321 => "liststart",
322 => "list",
323 => "listend",
324 => "channelmodeis",
329 => "channelcreate", # 1997-11-24 -- archon
330 => "whoisaccount", # 2011-02-10 pragma_ for freenode
331 => "notopic",
332 => "topic",
333 => "topicinfo", # 1997-11-24 -- archon
334 => "listusage", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
335 => "whoisbot", # UnrealIrcd, Hendrik Frenzel
341 => "inviting",
342 => "summoning",
346 => "invitelist", # UnrealIrcd, Hendrik Frenzel
347 => "endofinvitelist", # UnrealIrcd, Hendrik Frenzel
348 => "exlist", # UnrealIrcd, Hendrik Frenzel
349 => "endofexlist", # UnrealIrcd, Hendrik Frenzel
351 => "version",
352 => "whoreply",
353 => "namreply",
354 => "whospcrpl", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
361 => "killdone",
362 => "closing",
363 => "closeend",
364 => "links",
365 => "endoflinks",
366 => "endofnames",
367 => "banlist",
368 => "endofbanlist",
369 => "endofwhowas",
371 => "info",
372 => "motd",
373 => "infostart",
374 => "endofinfo",
375 => "motdstart",
376 => "endofmotd",
377 => "motd2", # 1997-10-16 -- tkil
378 => "austmotd", # (July01-01)Austnet Extension, found by Andypoo <andypoo@secret.com.au>
379 => "whoismodes", # UnrealIrcd, Hendrik Frenzel
381 => "youreoper",
382 => "rehashing",
383 => "youreservice", # UnrealIrcd, Hendrik Frenzel
384 => "myportis",
385 => "notoperanymore", # Unspecific Extension, Kajetan@Hinner.com, 17/10/98
386 => "qlist", # UnrealIrcd, Hendrik Frenzel
387 => "endofqlist", # UnrealIrcd, Hendrik Frenzel
388 => "alist", # UnrealIrcd, Hendrik Frenzel
389 => "endofalist", # UnrealIrcd, Hendrik Frenzel
391 => "time",
392 => "usersstart",
393 => "users",
394 => "endofusers",
395 => "nousers",
401 => "nosuchnick",
402 => "nosuchserver",
403 => "nosuchchannel",
404 => "cannotsendtochan",
405 => "toomanychannels",
406 => "wasnosuchnick",
407 => "toomanytargets",
408 => "nosuchservice", # UnrealIrcd, Hendrik Frenzel
409 => "noorigin",
411 => "norecipient",
412 => "notexttosend",
413 => "notoplevel",
414 => "wildtoplevel",
416 => "querytoolong", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
421 => "unknowncommand",
422 => "nomotd",
423 => "noadmininfo",
424 => "fileerror",
425 => "noopermotd", # UnrealIrcd, Hendrik Frenzel
431 => "nonicknamegiven",
432 => "erroneusnickname", # This iz how its speld in thee RFC.
433 => "nicknameinuse",
434 => "norules", # UnrealIrcd, Hendrik Frenzel
435 => "serviceconfused", # UnrealIrcd, Hendrik Frenzel
436 => "nickcollision",
437 => "bannickchange", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
438 => "nicktoofast", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
439 => "targettoofast", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
440 => "servicesdown", # Bahamut IRCD
441 => "usernotinchannel",
442 => "notonchannel",
443 => "useronchannel",
444 => "nologin",
445 => "summondisabled",
446 => "usersdisabled",
447 => "nonickchange", # UnrealIrcd, Hendrik Frenzel
451 => "notregistered",
455 => "hostilename", # UnrealIrcd, Hendrik Frenzel
459 => "nohiding", # UnrealIrcd, Hendrik Frenzel
460 => "notforhalfops", # UnrealIrcd, Hendrik Frenzel
461 => "needmoreparams",
462 => "alreadyregistered",
463 => "nopermforhost",
464 => "passwdmismatch",
465 => "yourebannedcreep", # I love this one...
466 => "youwillbebanned",
467 => "keyset",
468 => "invalidusername", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
469 => "linkset", # UnrealIrcd, Hendrik Frenzel
470 => "linkchannel", # UnrealIrcd, Hendrik Frenzel
471 => "channelisfull",
472 => "unknownmode",
473 => "inviteonlychan",
474 => "bannedfromchan",
475 => "badchannelkey",
476 => "badchanmask",
477 => "needreggednick", # Bahamut IRCD
478 => "banlistfull", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
479 => "secureonlychannel", # pircd
401 => "nosuchnick",
402 => "nosuchserver",
403 => "nosuchchannel",
404 => "cannotsendtochan",
405 => "toomanychannels",
406 => "wasnosuchnick",
407 => "toomanytargets",
408 => "nosuchservice", # UnrealIrcd, Hendrik Frenzel
409 => "noorigin",
411 => "norecipient",
412 => "notexttosend",
413 => "notoplevel",
414 => "wildtoplevel",
416 => "querytoolong", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
421 => "unknowncommand",
422 => "nomotd",
423 => "noadmininfo",
424 => "fileerror",
425 => "noopermotd", # UnrealIrcd, Hendrik Frenzel
431 => "nonicknamegiven",
432 => "erroneusnickname", # This iz how its speld in thee RFC.
433 => "nicknameinuse",
434 => "norules", # UnrealIrcd, Hendrik Frenzel
435 => "serviceconfused", # UnrealIrcd, Hendrik Frenzel
436 => "nickcollision",
437 => "bannickchange", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
438 => "nicktoofast", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
439 => "targettoofast", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
440 => "servicesdown", # Bahamut IRCD
441 => "usernotinchannel",
442 => "notonchannel",
443 => "useronchannel",
444 => "nologin",
445 => "summondisabled",
446 => "usersdisabled",
447 => "nonickchange", # UnrealIrcd, Hendrik Frenzel
451 => "notregistered",
455 => "hostilename", # UnrealIrcd, Hendrik Frenzel
459 => "nohiding", # UnrealIrcd, Hendrik Frenzel
460 => "notforhalfops", # UnrealIrcd, Hendrik Frenzel
461 => "needmoreparams",
462 => "alreadyregistered",
463 => "nopermforhost",
464 => "passwdmismatch",
465 => "yourebannedcreep", # I love this one...
466 => "youwillbebanned",
467 => "keyset",
468 => "invalidusername", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
469 => "linkset", # UnrealIrcd, Hendrik Frenzel
470 => "linkchannel", # UnrealIrcd, Hendrik Frenzel
471 => "channelisfull",
472 => "unknownmode",
473 => "inviteonlychan",
474 => "bannedfromchan",
475 => "badchannelkey",
476 => "badchanmask",
477 => "needreggednick", # Bahamut IRCD
478 => "banlistfull", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
479 => "secureonlychannel", # pircd
### TODO: see above todo
### 479 => "linkfail", # UnrealIrcd, Hendrik Frenzel
480 => "cannotknock", # UnrealIrcd, Hendrik Frenzel
481 => "noprivileges",
482 => "chanoprivsneeded",
483 => "cantkillserver",
484 => "ischanservice", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
485 => "killdeny", # UnrealIrcd, Hendrik Frenzel
486 => "htmdisabled", # UnrealIrcd, Hendrik Frenzel
489 => "secureonlychan", # UnrealIrcd, Hendrik Frenzel
491 => "nooperhost",
492 => "noservicehost",
480 => "cannotknock", # UnrealIrcd, Hendrik Frenzel
481 => "noprivileges",
482 => "chanoprivsneeded",
483 => "cantkillserver",
484 => "ischanservice", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
485 => "killdeny", # UnrealIrcd, Hendrik Frenzel
486 => "htmdisabled", # UnrealIrcd, Hendrik Frenzel
489 => "secureonlychan", # UnrealIrcd, Hendrik Frenzel
491 => "nooperhost",
492 => "noservicehost",
501 => "umodeunknownflag",
502 => "usersdontmatch",
501 => "umodeunknownflag",
502 => "usersdontmatch",
511 => "silelistfull", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
513 => "nosuchgline", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
513 => "badping", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
518 => "noinvite", # UnrealIrcd, Hendrik Frenzel
519 => "admonly", # UnrealIrcd, Hendrik Frenzel
520 => "operonly", # UnrealIrcd, Hendrik Frenzel
521 => "listsyntax", # UnrealIrcd, Hendrik Frenzel
524 => "operspverify", # UnrealIrcd, Hendrik Frenzel
511 => "silelistfull", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
513 => "nosuchgline", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
513 => "badping", # Undernet Extension, Kajetan@Hinner.com, 17/10/98
518 => "noinvite", # UnrealIrcd, Hendrik Frenzel
519 => "admonly", # UnrealIrcd, Hendrik Frenzel
520 => "operonly", # UnrealIrcd, Hendrik Frenzel
521 => "listsyntax", # UnrealIrcd, Hendrik Frenzel
524 => "operspverify", # UnrealIrcd, Hendrik Frenzel
600 => "rpl_logon", # Bahamut IRCD
601 => "rpl_logoff", # Bahamut IRCD
602 => "rpl_watchoff", # UnrealIrcd, Hendrik Frenzel
603 => "rpl_watchstat", # UnrealIrcd, Hendrik Frenzel
604 => "rpl_nowon", # Bahamut IRCD
605 => "rpl_nowoff", # Bahamut IRCD
606 => "rpl_watchlist", # UnrealIrcd, Hendrik Frenzel
607 => "rpl_endofwatchlist", # UnrealIrcd, Hendrik Frenzel
610 => "mapmore", # UnrealIrcd, Hendrik Frenzel
640 => "rpl_dumping", # UnrealIrcd, Hendrik Frenzel
641 => "rpl_dumprpl", # UnrealIrcd, Hendrik Frenzel
642 => "rpl_eodump", # UnrealIrcd, Hendrik Frenzel
728 => "quietlist", # freenode +q, pragma_ 12/12/2011
600 => "rpl_logon", # Bahamut IRCD
601 => "rpl_logoff", # Bahamut IRCD
602 => "rpl_watchoff", # UnrealIrcd, Hendrik Frenzel
603 => "rpl_watchstat", # UnrealIrcd, Hendrik Frenzel
604 => "rpl_nowon", # Bahamut IRCD
605 => "rpl_nowoff", # Bahamut IRCD
606 => "rpl_watchlist", # UnrealIrcd, Hendrik Frenzel
607 => "rpl_endofwatchlist", # UnrealIrcd, Hendrik Frenzel
610 => "mapmore", # UnrealIrcd, Hendrik Frenzel
640 => "rpl_dumping", # UnrealIrcd, Hendrik Frenzel
641 => "rpl_dumprpl", # UnrealIrcd, Hendrik Frenzel
642 => "rpl_eodump", # UnrealIrcd, Hendrik Frenzel
728 => "quietlist", # freenode +q, pragma_ 12/12/2011
999 => "numericerror", # Bahamut IRCD
# add these events so that default handlers will kick in and handle them
# pragma_ 10/30/2014
'notice' => 'notice',
'public' => 'public',
'kick' => 'kick',
'mode' => 'mode',
'msg' => 'msg',
'disconnect' => 'disconnect',
'part' => 'part',
'join' => 'join',
'caction' => 'caction',
'quit' => 'quit',
'nick' => 'nick',
'pong' => 'pong',
'invite' => 'invite',
'cap' => 'cap',
'account' => 'account',
);
999 => "numericerror", # Bahamut IRCD
# add these events so that default handlers will kick in and handle them
# pragma_ 10/30/2014
'notice' => 'notice',
'public' => 'public',
'kick' => 'kick',
'mode' => 'mode',
'msg' => 'msg',
'disconnect' => 'disconnect',
'part' => 'part',
'join' => 'join',
'caction' => 'caction',
'quit' => 'quit',
'nick' => 'nick',
'pong' => 'pong',
'invite' => 'invite',
'cap' => 'cap',
'account' => 'account',
);
1;
__END__
=head1 NAME

View File

@ -1,75 +1,75 @@
package PBot::IRC::EventQueue; # pragma_ 2011/21/01
package PBot::IRC::EventQueue; # pragma_ 2011/21/01
use feature 'unicode_strings';
use PBot::IRC::EventQueue::Entry; # pragma_ 2011/21/01
use PBot::IRC::EventQueue::Entry; # pragma_ 2011/21/01
use strict;
sub new {
my $class = shift;
my $class = shift;
my $self = {
'queue' => {},
};
my $self = {
'queue' => {},
};
bless $self, $class;
bless $self, $class;
}
sub queue {
my $self = shift;
return $self->{'queue'};
my $self = shift;
return $self->{'queue'};
}
sub enqueue {
my $self = shift;
my $time = shift;
my $content = shift;
my $self = shift;
my $time = shift;
my $content = shift;
my $entry = new PBot::IRC::EventQueue::Entry($time, $content); # pragma_ 2011/21/01
$self->queue->{$entry->id} = $entry;
return $entry->id;
my $entry = new PBot::IRC::EventQueue::Entry($time, $content); # pragma_ 2011/21/01
$self->queue->{$entry->id} = $entry;
return $entry->id;
}
sub dequeue {
my $self = shift;
my $event = shift;
my $result;
my $self = shift;
my $event = shift;
my $result;
if (!$event) { # we got passed nothing, so return the first event
$event = $self->head();
delete $self->queue->{$event->id};
$result = $event;
} elsif (!ref($event)) { # we got passed an id
$result = $self->queue->{$event};
delete $self->queue->{$event};
} else { # we got passed an actual event object
ref($event) eq 'PBot::IRC::EventQueue::Entry' # pragma_ 2011/21/01
or die "Cannot delete event type of " . ref($event) . "!";
if (!$event) { # we got passed nothing, so return the first event
$event = $self->head();
delete $self->queue->{$event->id};
$result = $event;
} elsif (!ref($event)) { # we got passed an id
$result = $self->queue->{$event};
delete $self->queue->{$event};
} else { # we got passed an actual event object
ref($event) eq 'PBot::IRC::EventQueue::Entry' # pragma_ 2011/21/01
or die "Cannot delete event type of " . ref($event) . "!";
$result = $self->queue->{$event->id};
delete $self->queue->{$event->id};
}
$result = $self->queue->{$event->id};
delete $self->queue->{$event->id};
}
return $result;
return $result;
}
sub head {
my $self = shift;
my $self = shift;
return undef if $self->is_empty;
return undef if $self->is_empty;
no warnings; # because we want to numerically sort strings...
my $headkey = (sort {$a <=> $b} (keys(%{$self->queue})))[0];
use warnings;
no warnings; # because we want to numerically sort strings...
my $headkey = (sort { $a <=> $b } (keys(%{$self->queue})))[0];
use warnings;
return $self->queue->{$headkey};
return $self->queue->{$headkey};
}
sub is_empty {
my $self = shift;
my $self = shift;
return keys(%{$self->queue}) ? 0 : 1;
return keys(%{$self->queue}) ? 0 : 1;
}
1;

View File

@ -1,4 +1,4 @@
package PBot::IRC::EventQueue::Entry; # pragma_ 2011/21/01
package PBot::IRC::EventQueue::Entry; # pragma_ 2011/21/01
use strict;
@ -7,35 +7,35 @@ use feature 'unicode_strings';
my $id = 0;
sub new {
my $class = shift;
my $time = shift;
my $content = shift;
my $class = shift;
my $time = shift;
my $content = shift;
my $self = {
'time' => $time,
'content' => $content,
'id' => "$time:" . $id++,
};
my $self = {
'time' => $time,
'content' => $content,
'id' => "$time:" . $id++,
};
bless $self, $class;
return $self;
bless $self, $class;
return $self;
}
sub id {
my $self = shift;
return $self->{'id'};
my $self = shift;
return $self->{'id'};
}
sub time {
my $self = shift;
$self->{'time'} = $_[0] if @_;
return $self->{'time'};
my $self = shift;
$self->{'time'} = $_[0] if @_;
return $self->{'time'};
}
sub content {
my $self = shift;
$self->{'content'} = $_[0] if @_;
return $self->{'content'};
my $self = shift;
$self->{'content'} = $_[0] if @_;
return $self->{'content'};
}
1;

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::IgnoreList;
use parent 'PBot::Class';
use warnings; use strict;
@ -17,135 +18,123 @@ use PBot::IgnoreListCommands;
use Time::HiRes qw(gettimeofday);
sub initialize {
my ($self, %conf) = @_;
$self->{filename} = $conf{filename};
my ($self, %conf) = @_;
$self->{filename} = $conf{filename};
$self->{ignore_list} = {};
$self->{ignore_flood_counter} = {};
$self->{last_timestamp} = {};
$self->{ignore_list} = {};
$self->{ignore_flood_counter} = {};
$self->{last_timestamp} = {};
$self->{commands} = PBot::IgnoreListCommands->new(pbot => $self->{pbot});
$self->load_ignores();
$self->{commands} = PBot::IgnoreListCommands->new(pbot => $self->{pbot});
$self->load_ignores();
$self->{pbot}->{timer}->register(sub { $self->check_ignore_timeouts }, 10);
$self->{pbot}->{timer}->register(sub { $self->check_ignore_timeouts }, 10);
}
sub add {
my $self = shift;
my ($hostmask, $channel, $length) = @_;
my $self = shift;
my ($hostmask, $channel, $length) = @_;
if ($length < 0) {
$self->{ignore_list}->{$hostmask}->{$channel} = -1;
} else {
$self->{ignore_list}->{$hostmask}->{$channel} = gettimeofday + $length;
}
if ($length < 0) { $self->{ignore_list}->{$hostmask}->{$channel} = -1; }
else { $self->{ignore_list}->{$hostmask}->{$channel} = gettimeofday + $length; }
$self->save_ignores();
$self->save_ignores();
}
sub remove {
my $self = shift;
my ($hostmask, $channel) = @_;
my $self = shift;
my ($hostmask, $channel) = @_;
delete $self->{ignore_list}->{$hostmask}->{$channel};
delete $self->{ignore_list}->{$hostmask}->{$channel};
if (not keys %{ $self->{ignore_list}->{$hostmask} }) {
delete $self->{ignore_list}->{$hostmask};
}
if (not keys %{$self->{ignore_list}->{$hostmask}}) { delete $self->{ignore_list}->{$hostmask}; }
$self->save_ignores();
$self->save_ignores();
}
sub clear_ignores {
my $self = shift;
$self->{ignore_list} = {};
my $self = shift;
$self->{ignore_list} = {};
}
sub load_ignores {
my $self = shift;
my $filename;
my $self = shift;
my $filename;
if (@_) { $filename = shift; } else { $filename = $self->{filename}; }
if (@_) { $filename = shift; }
else { $filename = $self->{filename}; }
if (not defined $filename) {
Carp::carp "No ignorelist path specified -- skipping loading of ignorelist";
return;
}
$self->{pbot}->{logger}->log("Loading ignorelist from $filename ...\n");
open(FILE, "< $filename") or Carp::croak "Couldn't open $filename: $!\n";
my @contents = <FILE>;
close(FILE);
my $i = 0;
foreach my $line (@contents) {
chomp $line;
$i++;
my ($hostmask, $channel, $length) = split(/\s+/, $line);
if (not defined $hostmask || not defined $channel || not defined $length) {
Carp::croak "Syntax error around line $i of $filename\n";
if (not defined $filename) {
Carp::carp "No ignorelist path specified -- skipping loading of ignorelist";
return;
}
if (exists ${ $self->{ignore_list} }{$hostmask}{$channel}) {
Carp::croak "Duplicate ignore [$hostmask][$channel] found in $filename around line $i\n";
$self->{pbot}->{logger}->log("Loading ignorelist from $filename ...\n");
open(FILE, "< $filename") or Carp::croak "Couldn't open $filename: $!\n";
my @contents = <FILE>;
close(FILE);
my $i = 0;
foreach my $line (@contents) {
chomp $line;
$i++;
my ($hostmask, $channel, $length) = split(/\s+/, $line);
if (not defined $hostmask || not defined $channel || not defined $length) { Carp::croak "Syntax error around line $i of $filename\n"; }
if (exists ${$self->{ignore_list}}{$hostmask}{$channel}) { Carp::croak "Duplicate ignore [$hostmask][$channel] found in $filename around line $i\n"; }
$self->{ignore_list}->{$hostmask}->{$channel} = $length;
}
$self->{ignore_list}->{$hostmask}->{$channel} = $length;
}
$self->{pbot}->{logger}->log(" $i entries in ignorelist\n");
$self->{pbot}->{logger}->log(" $i entries in ignorelist\n");
}
sub save_ignores {
my $self = shift;
my $filename;
my $self = shift;
my $filename;
if (@_) { $filename = shift; } else { $filename = $self->{filename}; }
if (@_) { $filename = shift; }
else { $filename = $self->{filename}; }
if (not defined $filename) {
Carp::carp "No ignorelist path specified -- skipping saving of ignorelist\n";
return;
}
open(FILE, "> $filename") or die "Couldn't open $filename: $!\n";
foreach my $hostmask (keys %{ $self->{ignore_list} }) {
foreach my $channel (keys %{ $self->{ignore_list}->{$hostmask} }) {
my $length = $self->{ignore_list}->{$hostmask}->{$channel};
print FILE "$hostmask $channel $length\n";
if (not defined $filename) {
Carp::carp "No ignorelist path specified -- skipping saving of ignorelist\n";
return;
}
}
close(FILE);
open(FILE, "> $filename") or die "Couldn't open $filename: $!\n";
foreach my $hostmask (keys %{$self->{ignore_list}}) {
foreach my $channel (keys %{$self->{ignore_list}->{$hostmask}}) {
my $length = $self->{ignore_list}->{$hostmask}->{$channel};
print FILE "$hostmask $channel $length\n";
}
}
close(FILE);
}
sub check_ignore {
my $self = shift;
my ($nick, $user, $host, $channel, $silent) = @_;
my $pbot = $self->{pbot};
$channel = lc $channel;
my $self = shift;
my ($nick, $user, $host, $channel, $silent) = @_;
my $pbot = $self->{pbot};
$channel = lc $channel;
my $hostmask = "$nick!$user\@$host";
my $hostmask = "$nick!$user\@$host";
my $now = gettimeofday;
my $now = gettimeofday;
if (defined $channel) { # do not execute following if text is coming from STDIN ($channel undef)
if ($channel =~ /^#/) {
$self->{ignore_flood_counter}->{$channel}++;
}
if (defined $channel) { # do not execute following if text is coming from STDIN ($channel undef)
if ($channel =~ /^#/) { $self->{ignore_flood_counter}->{$channel}++; }
if (not exists $self->{last_timestamp}->{$channel}) {
$self->{last_timestamp}->{$channel} = $now;
} elsif ($now - $self->{last_timestamp}->{$channel} >= 30) {
$self->{last_timestamp}->{$channel} = $now;
if (exists $self->{ignore_flood_counter}->{$channel} and $self->{ignore_flood_counter}->{$channel} > 0) {
$self->{ignore_flood_counter}->{$channel} = 0;
}
}
if (not exists $self->{last_timestamp}->{$channel}) { $self->{last_timestamp}->{$channel} = $now; }
elsif ($now - $self->{last_timestamp}->{$channel} >= 30) {
$self->{last_timestamp}->{$channel} = $now;
if (exists $self->{ignore_flood_counter}->{$channel} and $self->{ignore_flood_counter}->{$channel} > 0) { $self->{ignore_flood_counter}->{$channel} = 0; }
}
=cut
if (exists $self->{ignore_flood_counter}->{$channel} and $self->{ignore_flood_counter}->{$channel} > 5) {
@ -158,42 +147,41 @@ sub check_ignore {
}
}
=cut
}
foreach my $ignored (keys %{ $self->{ignore_list} }) {
foreach my $ignored_channel (keys %{ $self->{ignore_list}->{$ignored} }) {
my $ignored_channel_escaped = quotemeta $ignored_channel;
my $ignored_escaped = quotemeta $ignored;
$ignored_channel_escaped =~ s/\\(\.|\*)/$1/g;
$ignored_escaped =~ s/\\(\.|\*)/$1/g;
if (($channel =~ /$ignored_channel_escaped/i) && ($hostmask =~ /$ignored_escaped/i)) {
$self->{pbot}->{logger}->log("$nick!$user\@$host message ignored in channel $channel (matches [$ignored] host and [$ignored_channel] channel)\n") unless $silent;
return 1;
}
}
}
return 0;
foreach my $ignored (keys %{$self->{ignore_list}}) {
foreach my $ignored_channel (keys %{$self->{ignore_list}->{$ignored}}) {
my $ignored_channel_escaped = quotemeta $ignored_channel;
my $ignored_escaped = quotemeta $ignored;
$ignored_channel_escaped =~ s/\\(\.|\*)/$1/g;
$ignored_escaped =~ s/\\(\.|\*)/$1/g;
if (($channel =~ /$ignored_channel_escaped/i) && ($hostmask =~ /$ignored_escaped/i)) {
$self->{pbot}->{logger}->log("$nick!$user\@$host message ignored in channel $channel (matches [$ignored] host and [$ignored_channel] channel)\n") unless $silent;
return 1;
}
}
}
return 0;
}
sub check_ignore_timeouts {
my $self = shift;
my $now = gettimeofday();
my $self = shift;
my $now = gettimeofday();
foreach my $hostmask (keys %{ $self->{ignore_list} }) {
foreach my $channel (keys %{ $self->{ignore_list}->{$hostmask} }) {
next if ($self->{ignore_list}->{$hostmask}->{$channel} == -1); #permanent ignore
foreach my $hostmask (keys %{$self->{ignore_list}}) {
foreach my $channel (keys %{$self->{ignore_list}->{$hostmask}}) {
next if ($self->{ignore_list}->{$hostmask}->{$channel} == -1); #permanent ignore
if ($self->{ignore_list}->{$hostmask}->{$channel} < $now) {
$self->{pbot}->{logger}->log("Unignoring $hostmask in channel $channel.\n");
$self->remove($hostmask, $channel);
if ($hostmask eq ".*") {
$self->{pbot}->{conn}->me($channel, "awakens.");
if ($self->{ignore_list}->{$hostmask}->{$channel} < $now) {
$self->{pbot}->{logger}->log("Unignoring $hostmask in channel $channel.\n");
$self->remove($hostmask, $channel);
if ($hostmask eq ".*") { $self->{pbot}->{conn}->me($channel, "awakens."); }
}
}
}
}
}
}
1;

View File

@ -8,6 +8,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::IgnoreListCommands;
use parent 'PBot::Class';
use warnings; use strict;
@ -17,83 +18,81 @@ use Time::HiRes qw(gettimeofday);
use Time::Duration;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->ignore_user(@_) }, "ignore", 1);
$self->{pbot}->{commands}->register(sub { $self->unignore_user(@_) }, "unignore", 1);
$self->{pbot}->{capabilities}->add('admin', 'can-ignore', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-unignore', 1);
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->ignore_user(@_) }, "ignore", 1);
$self->{pbot}->{commands}->register(sub { $self->unignore_user(@_) }, "unignore", 1);
$self->{pbot}->{capabilities}->add('admin', 'can-ignore', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-unignore', 1);
}
sub ignore_user {
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
return "Usage: ignore <hostmask> [channel [timeout]]" if not defined $arguments;
return "Usage: ignore <hostmask> [channel [timeout]]" if not defined $arguments;
my ($target, $channel, $length) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 3);
my ($target, $channel, $length) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 3);
if (not defined $target) {
return "Usage: ignore <hostmask> [channel [timeout]]";
}
if (not defined $target) { return "Usage: ignore <hostmask> [channel [timeout]]"; }
if ($target =~ /^list$/i) {
my $text = "Ignored: ";
my $sep = "";
if ($target =~ /^list$/i) {
my $text = "Ignored: ";
my $sep = "";
foreach my $ignored (sort keys %{ $self->{pbot}->{ignorelist}->{ignore_list} }) {
foreach my $channel (sort keys %{ ${ $self->{pbot}->{ignorelist}->{ignore_list} }{$ignored} }) {
$text .= $sep . "$ignored [$channel] " . ($self->{pbot}->{ignorelist}->{ignore_list}->{$ignored}->{$channel} < 0 ? "perm" : duration($self->{pbot}->{ignorelist}->{ignore_list}->{$ignored}->{$channel} - gettimeofday));
$sep = ";\n";
}
foreach my $ignored (sort keys %{$self->{pbot}->{ignorelist}->{ignore_list}}) {
foreach my $channel (sort keys %{${$self->{pbot}->{ignorelist}->{ignore_list}}{$ignored}}) {
$text .=
$sep
. "$ignored [$channel] "
. (
$self->{pbot}->{ignorelist}->{ignore_list}->{$ignored}->{$channel} < 0
? "perm"
: duration($self->{pbot}->{ignorelist}->{ignore_list}->{$ignored}->{$channel} - gettimeofday)
);
$sep = ";\n";
}
}
return "/msg $nick $text";
}
return "/msg $nick $text";
}
if (not defined $channel) {
$channel = ".*"; # all channels
}
if (not defined $channel) {
$channel = ".*"; # all channels
}
if (not defined $length) {
$length = -1; # permanently
} else {
my $error;
($length, $error) = $self->{pbot}->{parsedate}->parsedate($length);
return $error if defined $error;
}
if (not defined $length) {
$length = -1; # permanently
} else {
my $error;
($length, $error) = $self->{pbot}->{parsedate}->parsedate($length);
return $error if defined $error;
}
$self->{pbot}->{ignorelist}->add($target, $channel, $length);
$self->{pbot}->{ignorelist}->add($target, $channel, $length);
if ($length >= 0) {
$length = "for " . duration($length);
} else {
$length = "permanently";
}
if ($length >= 0) { $length = "for " . duration($length); }
else { $length = "permanently"; }
$self->{pbot}->{logger}->log("$nick added [$target][$channel] to ignore list $length\n");
return "/msg $nick [$target][$channel] added to ignore list $length";
$self->{pbot}->{logger}->log("$nick added [$target][$channel] to ignore list $length\n");
return "/msg $nick [$target][$channel] added to ignore list $length";
}
sub unignore_user {
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($target, $channel) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($target, $channel) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
if (not defined $target) {
return "Usage: unignore <hostmask> [channel]";
}
if (not defined $target) { return "Usage: unignore <hostmask> [channel]"; }
if (not defined $channel) {
$channel = ".*";
}
if (not defined $channel) { $channel = ".*"; }
if (exists $self->{pbot}->{ignorelist}->{ignore_list}->{$target} and not exists $self->{pbot}->{ignorelist}->{ignore_list}->{$target}->{$channel}) {
$self->{pbot}->{logger}->log("$nick attempt to remove nonexistent [$target][$channel] from ignore list\n");
return "/msg $nick [$target][$channel] not found in ignore list (use `ignore list` to list ignores)";
}
if (exists $self->{pbot}->{ignorelist}->{ignore_list}->{$target} and not exists $self->{pbot}->{ignorelist}->{ignore_list}->{$target}->{$channel}) {
$self->{pbot}->{logger}->log("$nick attempt to remove nonexistent [$target][$channel] from ignore list\n");
return "/msg $nick [$target][$channel] not found in ignore list (use `ignore list` to list ignores)";
}
$self->{pbot}->{ignorelist}->remove($target, $channel);
$self->{pbot}->{logger}->log("$nick removed [$target][$channel] from ignore list\n");
return "/msg $nick [$target][$channel] unignored";
$self->{pbot}->{ignorelist}->remove($target, $channel);
$self->{pbot}->{logger}->log("$nick removed [$target][$channel] from ignore list\n");
return "/msg $nick [$target][$channel] unignored";
}
1;

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::LagChecker;
use parent 'PBot::Class';
use warnings; use strict;
@ -18,127 +19,131 @@ use Time::HiRes qw(gettimeofday tv_interval);
use Time::Duration;
sub initialize {
my ($self, %conf) = @_;
$self->{lag_average} = undef; # average of entries in lag history, in seconds
$self->{lag_string} = undef; # string representation of lag history and lag average
$self->{lag_history} = []; # history of previous PING/PONG timings
$self->{pong_received} = undef; # tracks pong replies; undef if no ping sent; 0 if ping sent but no pong reply yet; 1 if ping/pong completed
$self->{ping_send_time} = undef; # when last ping was sent
my ($self, %conf) = @_;
$self->{lag_average} = undef; # average of entries in lag history, in seconds
$self->{lag_string} = undef; # string representation of lag history and lag average
$self->{lag_history} = []; # history of previous PING/PONG timings
$self->{pong_received} = undef; # tracks pong replies; undef if no ping sent; 0 if ping sent but no pong reply yet; 1 if ping/pong completed
$self->{ping_send_time} = undef; # when last ping was sent
# maximum number of lag history entries to retain
$self->{pbot}->{registry}->add_default('text', 'lagchecker', 'lag_history_max', $conf{lag_history_max} // 3);
# lagging is true if lag_average reaches or exceeds this threshold, in milliseconds
$self->{pbot}->{registry}->add_default('text', 'lagchecker', 'lag_threshold', $conf{lag_threshhold} // 2000);
# how often to send PING, in seconds
$self->{pbot}->{registry}->add_default('text', 'lagchecker', 'lag_history_interval', $conf{lag_history_interval} // 10);
# maximum number of lag history entries to retain
$self->{pbot}->{registry}->add_default('text', 'lagchecker', 'lag_history_max', $conf{lag_history_max} // 3);
$self->{pbot}->{registry}->add_trigger('lagchecker', 'lag_history_interval', sub { $self->lag_history_interval_trigger(@_) });
# lagging is true if lag_average reaches or exceeds this threshold, in milliseconds
$self->{pbot}->{registry}->add_default('text', 'lagchecker', 'lag_threshold', $conf{lag_threshhold} // 2000);
$self->{pbot}->{timer}->register(
sub { $self->send_ping },
$self->{pbot}->{registry}->get_value('lagchecker', 'lag_history_interval'),
'lag_history_interval'
);
# how often to send PING, in seconds
$self->{pbot}->{registry}->add_default('text', 'lagchecker', 'lag_history_interval', $conf{lag_history_interval} // 10);
$self->{pbot}->{commands}->register(sub { $self->lagcheck(@_) }, "lagcheck", 0);
$self->{pbot}->{event_dispatcher}->register_handler('irc.pong', sub { $self->on_pong(@_) });
$self->{pbot}->{registry}->add_trigger('lagchecker', 'lag_history_interval', sub { $self->lag_history_interval_trigger(@_) });
$self->{pbot}->{timer}->register(
sub { $self->send_ping },
$self->{pbot}->{registry}->get_value('lagchecker', 'lag_history_interval'),
'lag_history_interval'
);
$self->{pbot}->{commands}->register(sub { $self->lagcheck(@_) }, "lagcheck", 0);
$self->{pbot}->{event_dispatcher}->register_handler('irc.pong', sub { $self->on_pong(@_) });
}
sub lag_history_interval_trigger {
my ($self, $section, $item, $newvalue) = @_;
$self->{pbot}->{timer}->update_interval('lag_history_interval', $newvalue);
my ($self, $section, $item, $newvalue) = @_;
$self->{pbot}->{timer}->update_interval('lag_history_interval', $newvalue);
}
sub send_ping {
my $self = shift;
return unless defined $self->{pbot}->{conn};
$self->{ping_send_time} = [gettimeofday];
$self->{pong_received} = 0;
$self->{pbot}->{conn}->sl("PING :lagcheck");
my $self = shift;
return unless defined $self->{pbot}->{conn};
$self->{ping_send_time} = [gettimeofday];
$self->{pong_received} = 0;
$self->{pbot}->{conn}->sl("PING :lagcheck");
}
sub on_pong {
my $self = shift;
my $self = shift;
$self->{pong_received} = 1;
$self->{pong_received} = 1;
my $elapsed = tv_interval($self->{ping_send_time});
push @{$self->{lag_history}}, [ $self->{ping_send_time}[0], $elapsed * 1000];
my $elapsed = tv_interval($self->{ping_send_time});
push @{$self->{lag_history}}, [$self->{ping_send_time}[0], $elapsed * 1000];
my $len = @{$self->{lag_history}};
my $len = @{$self->{lag_history}};
my $lag_history_max = $self->{pbot}->{registry}->get_value('lagchecker', 'lag_history_max');
my $lag_history_max = $self->{pbot}->{registry}->get_value('lagchecker', 'lag_history_max');
while ($len > $lag_history_max) {
shift @{$self->{lag_history}};
$len--;
}
while ($len > $lag_history_max) {
shift @{$self->{lag_history}};
$len--;
}
$self->{lag_string} = "";
my $comma = "";
$self->{lag_string} = "";
my $comma = "";
my $lag_total = 0;
foreach my $entry (@{$self->{lag_history}}) {
my ($send_time, $lag_result) = @$entry;
my $lag_total = 0;
foreach my $entry (@{$self->{lag_history}}) {
my ($send_time, $lag_result) = @$entry;
$lag_total += $lag_result;
my $ago = concise ago(gettimeofday - $send_time);
$self->{lag_string} .= $comma . "[$ago] " . sprintf "%.1f ms", $lag_result;
$comma = "; ";
}
$lag_total += $lag_result;
my $ago = concise ago(gettimeofday - $send_time);
$self->{lag_string} .= $comma . "[$ago] " . sprintf "%.1f ms", $lag_result;
$comma = "; ";
}
$self->{lag_average} = $lag_total / $len;
$self->{lag_string} .= "; average: " . sprintf "%.1f ms", $self->{lag_average};
return 0;
$self->{lag_average} = $lag_total / $len;
$self->{lag_string} .= "; average: " . sprintf "%.1f ms", $self->{lag_average};
return 0;
}
sub lagging {
my $self = shift;
my $self = shift;
if (defined $self->{pong_received} and $self->{pong_received} == 0) {
# a ping has been sent (pong_received is not undef) and no pong has been received yet
my $elapsed = tv_interval($self->{ping_send_time});
return $elapsed >= $self->{pbot}->{registry}->get_value('lagchecker', 'lag_threshold');
}
if (defined $self->{pong_received} and $self->{pong_received} == 0) {
return 0 if not defined $self->{lag_average};
return $self->{lag_average} >= $self->{pbot}->{registry}->get_value('lagchecker', 'lag_threshold');
# a ping has been sent (pong_received is not undef) and no pong has been received yet
my $elapsed = tv_interval($self->{ping_send_time});
return $elapsed >= $self->{pbot}->{registry}->get_value('lagchecker', 'lag_threshold');
}
return 0 if not defined $self->{lag_average};
return $self->{lag_average} >= $self->{pbot}->{registry}->get_value('lagchecker', 'lag_threshold');
}
sub lagstring {
my $self = shift;
my $lag = $self->{lag_string} || "initializing";
return $lag;
my $self = shift;
my $lag = $self->{lag_string} || "initializing";
return $lag;
}
sub lagcheck {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
if (defined $self->{pong_received} and $self->{pong_received} == 0) {
# a ping has been sent (pong_received is not undef) and no pong has been received yet
my $elapsed = tv_interval($self->{ping_send_time});
my $lag_total = $elapsed;
my $len = @{$self->{lag_history}};
if (defined $self->{pong_received} and $self->{pong_received} == 0) {
my $lagstring = "";
my $comma = "";
# a ping has been sent (pong_received is not undef) and no pong has been received yet
my $elapsed = tv_interval($self->{ping_send_time});
my $lag_total = $elapsed;
my $len = @{$self->{lag_history}};
foreach my $entry (@{$self->{lag_history}}) {
my ($send_time, $lag_result) = @$entry;
$lag_total += $lag_result;
my $ago = concise ago(gettimeofday - $send_time);
$lagstring .= $comma . "[$ago] " . sprintf "%.1f ms", $lag_result;
$comma = "; ";
}
my $lagstring = "";
my $comma = "";
$lagstring .= $comma . "[waiting for pong] $elapsed";
foreach my $entry (@{$self->{lag_history}}) {
my ($send_time, $lag_result) = @$entry;
$lag_total += $lag_result;
my $ago = concise ago(gettimeofday - $send_time);
$lagstring .= $comma . "[$ago] " . sprintf "%.1f ms", $lag_result;
$comma = "; ";
}
my $average = $lag_total / ($len + 1);
$lagstring .= "; average: " . sprintf "%.1f ms", $average;
return $lagstring;
}
$lagstring .= $comma . "[waiting for pong] $elapsed";
return "My lag: " . $self->lagstring;
my $average = $lag_total / ($len + 1);
$lagstring .= "; average: " . sprintf "%.1f ms", $average;
return $lagstring;
}
return "My lag: " . $self->lagstring;
}
1;

View File

@ -11,55 +11,56 @@ use Scalar::Util qw/openhandle/;
use File::Basename;
sub new {
my ($proto, %conf) = @_;
my $class = ref($proto) || $proto;
my $self = bless {}, $class;
Carp::croak("Missing pbot reference to " . __FILE__) unless exists $conf{pbot};
$self->{pbot} = $conf{pbot};
print "Initializing " . __PACKAGE__ . "\n" unless $self->{pbot}->{overrides}->{'general.daemon'};
$self->initialize(%conf);
return $self;
my ($proto, %conf) = @_;
my $class = ref($proto) || $proto;
my $self = bless {}, $class;
Carp::croak("Missing pbot reference to " . __FILE__) unless exists $conf{pbot};
$self->{pbot} = $conf{pbot};
print "Initializing " . __PACKAGE__ . "\n" unless $self->{pbot}->{overrides}->{'general.daemon'};
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($self, %conf) = @_;
$self->{logfile} = $conf{filename} // Carp::croak "Missing logfile parameter in " . __FILE__;
$self->{start} = time;
my ($self, %conf) = @_;
$self->{logfile} = $conf{filename} // Carp::croak "Missing logfile parameter in " . __FILE__;
$self->{start} = time;
my $path = dirname $self->{logfile};
if (not -d $path) {
print "Creating new logfile path: $path\n" unless $self->{pbot}->{overrides}->{'general.daemon'};
mkdir $path or Carp::croak "Couldn't create logfile path: $!\n";
}
my $path = dirname $self->{logfile};
if (not -d $path) {
print "Creating new logfile path: $path\n" unless $self->{pbot}->{overrides}->{'general.daemon'};
mkdir $path or Carp::croak "Couldn't create logfile path: $!\n";
}
open LOGFILE, ">>$self->{logfile}" or Carp::croak "Couldn't open logfile $self->{logfile}: $!\n";
LOGFILE->autoflush(1);
open LOGFILE, ">>$self->{logfile}" or Carp::croak "Couldn't open logfile $self->{logfile}: $!\n";
LOGFILE->autoflush(1);
$self->{pbot}->{atexit}->register(sub { $self->rotate_log; return; });
return $self;
$self->{pbot}->{atexit}->register(sub { $self->rotate_log; return; });
return $self;
}
sub log {
my ($self, $text) = @_;
my $time = localtime;
$text =~ s/(\P{PosixGraph})/my $ch = $1; if ($ch =~ m{[\s]}) { $ch } else { sprintf "\\x%02X", ord $ch }/ge;
print LOGFILE "$time :: $text" if openhandle *LOGFILE;
print "$time :: $text" unless $self->{pbot}->{overrides}->{'general.daemon'};
my ($self, $text) = @_;
my $time = localtime;
$text =~ s/(\P{PosixGraph})/my $ch = $1; if ($ch =~ m{[\s]}) { $ch } else { sprintf "\\x%02X", ord $ch }/ge;
print LOGFILE "$time :: $text" if openhandle * LOGFILE;
print "$time :: $text" unless $self->{pbot}->{overrides}->{'general.daemon'};
}
sub rotate_log {
my ($self) = @_;
my $time = localtime $self->{start};
$time =~ s/\s+/_/g;
my ($self) = @_;
my $time = localtime $self->{start};
$time =~ s/\s+/_/g;
$self->log("Rotating log to $self->{logfile}-$time\n");
# logfile has to be closed first for maximum compatibility with `rename`
close LOGFILE;
rename $self->{logfile}, $self->{logfile} . '-' . $time;
$self->log("Rotating log to $self->{logfile}-$time\n");
# reopen renamed logfile to resume any needed logging
open LOGFILE, ">>$self->{logfile}-$time" or Carp::carp "Couldn't re-open logfile $self->{logfile}-$time: $!\n";
LOGFILE->autoflush(1) if openhandle *LOGFILE;
# logfile has to be closed first for maximum compatibility with `rename`
close LOGFILE;
rename $self->{logfile}, $self->{logfile} . '-' . $time;
# reopen renamed logfile to resume any needed logging
open LOGFILE, ">>$self->{logfile}-$time" or Carp::carp "Couldn't re-open logfile $self->{logfile}-$time: $!\n";
LOGFILE->autoflush(1) if openhandle * LOGFILE;
}
1;

View File

@ -12,6 +12,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::MessageHistory;
use parent 'PBot::Class';
use warnings; use strict;
@ -24,392 +25,355 @@ use Time::Duration;
use PBot::MessageHistory_SQLite;
sub initialize {
my ($self, %conf) = @_;
$self->{filename} = $conf{filename} // $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/message_history.sqlite3';
my ($self, %conf) = @_;
$self->{filename} = $conf{filename} // $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/message_history.sqlite3';
$self->{database} = PBot::MessageHistory_SQLite->new(pbot => $self->{pbot}, filename => $self->{filename});
$self->{database}->begin();
$self->{database}->devalidate_all_channels();
$self->{database} = PBot::MessageHistory_SQLite->new(pbot => $self->{pbot}, filename => $self->{filename});
$self->{database}->begin();
$self->{database}->devalidate_all_channels();
$self->{MSG_CHAT} = 0; # PRIVMSG, ACTION
$self->{MSG_JOIN} = 1; # JOIN
$self->{MSG_DEPARTURE} = 2; # PART, QUIT, KICK
$self->{MSG_NICKCHANGE} = 3; # CHANGED NICK
$self->{MSG_CHAT} = 0; # PRIVMSG, ACTION
$self->{MSG_JOIN} = 1; # JOIN
$self->{MSG_DEPARTURE} = 2; # PART, QUIT, KICK
$self->{MSG_NICKCHANGE} = 3; # CHANGED NICK
$self->{pbot}->{registry}->add_default('text', 'messagehistory', 'max_recall_time', $conf{max_recall_time} // 0);
$self->{pbot}->{registry}->add_default('text', 'messagehistory', 'max_messages', 32);
$self->{pbot}->{registry}->add_default('text', 'messagehistory', 'max_recall_time', $conf{max_recall_time} // 0);
$self->{pbot}->{registry}->add_default('text', 'messagehistory', 'max_messages', 32);
$self->{pbot}->{commands}->register(sub { $self->recall_message(@_) }, "recall", 0);
$self->{pbot}->{commands}->register(sub { $self->list_also_known_as(@_) }, "aka", 0);
$self->{pbot}->{commands}->register(sub { $self->rebuild_aliases(@_) }, "rebuildaliases", 1);
$self->{pbot}->{commands}->register(sub { $self->aka_link(@_) }, "akalink", 1);
$self->{pbot}->{commands}->register(sub { $self->aka_unlink(@_) }, "akaunlink", 1);
$self->{pbot}->{commands}->register(sub { $self->recall_message(@_) }, "recall", 0);
$self->{pbot}->{commands}->register(sub { $self->list_also_known_as(@_) }, "aka", 0);
$self->{pbot}->{commands}->register(sub { $self->rebuild_aliases(@_) }, "rebuildaliases", 1);
$self->{pbot}->{commands}->register(sub { $self->aka_link(@_) }, "akalink", 1);
$self->{pbot}->{commands}->register(sub { $self->aka_unlink(@_) }, "akaunlink", 1);
$self->{pbot}->{capabilities}->add('admin', 'can-akalink', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-akaunlink', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-akalink', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-akaunlink', 1);
$self->{pbot}->{atexit}->register(sub { $self->{database}->end(); return; });
$self->{pbot}->{atexit}->register(sub { $self->{database}->end(); return; });
}
sub get_message_account {
my ($self, $nick, $user, $host) = @_;
return $self->{database}->get_message_account($nick, $user, $host);
my ($self, $nick, $user, $host) = @_;
return $self->{database}->get_message_account($nick, $user, $host);
}
sub add_message {
my ($self, $account, $mask, $channel, $text, $mode) = @_;
$self->{database}->add_message($account, $mask, $channel, { timestamp => scalar gettimeofday, msg => $text, mode => $mode });
my ($self, $account, $mask, $channel, $text, $mode) = @_;
$self->{database}->add_message($account, $mask, $channel, {timestamp => scalar gettimeofday, msg => $text, mode => $mode});
}
sub rebuild_aliases {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
$self->{database}->rebuild_aliases_table;
$self->{database}->rebuild_aliases_table;
}
sub aka_link {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my ($id, $alias, $type) = split /\s+/, $arguments;
my ($id, $alias, $type) = split /\s+/, $arguments;
$type = $self->{database}->{alias_type}->{STRONG} if not defined $type;
$type = $self->{database}->{alias_type}->{STRONG} if not defined $type;
if (not $id or not $alias) {
return "Usage: link <target id> <alias id> [type]";
}
if (not $id or not $alias) { return "Usage: link <target id> <alias id> [type]"; }
my $source = $self->{database}->find_most_recent_hostmask($id);
my $target = $self->{database}->find_most_recent_hostmask($alias);
my $source = $self->{database}->find_most_recent_hostmask($id);
my $target = $self->{database}->find_most_recent_hostmask($alias);
if (not $source) {
return "No such id $id found.";
}
if (not $source) { return "No such id $id found."; }
if (not $target) {
return "No such id $alias found.";
}
if (not $target) { return "No such id $alias found."; }
if ($self->{database}->link_alias($id, $alias, $type)) {
return "/say $source " . ($type == $self->{database}->{alias_type}->{WEAK} ? "weakly" : "strongly") . " linked to $target.";
} else {
return "Link failed.";
}
if ($self->{database}->link_alias($id, $alias, $type)) {
return "/say $source " . ($type == $self->{database}->{alias_type}->{WEAK} ? "weakly" : "strongly") . " linked to $target.";
} else {
return "Link failed.";
}
}
sub aka_unlink {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my ($id, $alias) = split /\s+/, $arguments;
my ($id, $alias) = split /\s+/, $arguments;
if (not $id or not $alias) {
return "Usage: unlink <target id> <alias id>";
}
if (not $id or not $alias) { return "Usage: unlink <target id> <alias id>"; }
my $source = $self->{database}->find_most_recent_hostmask($id);
my $target = $self->{database}->find_most_recent_hostmask($alias);
my $source = $self->{database}->find_most_recent_hostmask($id);
my $target = $self->{database}->find_most_recent_hostmask($alias);
if (not $source) {
return "No such id $id found.";
}
if (not $source) { return "No such id $id found."; }
if (not $target) {
return "No such id $alias found.";
}
if (not $target) { return "No such id $alias found."; }
if ($self->{database}->unlink_alias($id, $alias)) {
return "/say $source unlinked from $target.";
} else {
return "Unlink failed.";
}
if ($self->{database}->unlink_alias($id, $alias)) { return "/say $source unlinked from $target."; }
else { return "Unlink failed."; }
}
sub list_also_known_as {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $usage = "Usage: aka [-hingr] <nick>; -h show hostmasks; -i show ids; -n show nickserv accounts; -g show gecos, -r show relationships";
my $usage = "Usage: aka [-hingr] <nick>; -h show hostmasks; -i show ids; -n show nickserv accounts; -g show gecos, -r show relationships";
if (not length $arguments) {
return $usage;
}
if (not length $arguments) { return $usage; }
my $getopt_error;
local $SIG{__WARN__} = sub {
$getopt_error = shift;
chomp $getopt_error;
};
my $getopt_error;
local $SIG{__WARN__} = sub {
$getopt_error = shift;
chomp $getopt_error;
};
Getopt::Long::Configure ("bundling");
Getopt::Long::Configure("bundling");
my ($show_hostmasks, $show_gecos, $show_nickserv, $show_id, $show_relationship, $show_weak, $dont_use_aliases_table);
my ($ret, $args) = GetOptionsFromString($arguments,
'h' => \$show_hostmasks,
'n' => \$show_nickserv,
'r' => \$show_relationship,
'g' => \$show_gecos,
'w' => \$show_weak,
'nt' => \$dont_use_aliases_table,
'i' => \$show_id);
my ($show_hostmasks, $show_gecos, $show_nickserv, $show_id, $show_relationship, $show_weak, $dont_use_aliases_table);
my ($ret, $args) = GetOptionsFromString(
$arguments,
'h' => \$show_hostmasks,
'n' => \$show_nickserv,
'r' => \$show_relationship,
'g' => \$show_gecos,
'w' => \$show_weak,
'nt' => \$dont_use_aliases_table,
'i' => \$show_id
);
return "/say $getopt_error -- $usage" if defined $getopt_error;
return "Too many arguments -- $usage" if @$args > 1;
return "Missing argument -- $usage" if @$args != 1;
return "/say $getopt_error -- $usage" if defined $getopt_error;
return "Too many arguments -- $usage" if @$args > 1;
return "Missing argument -- $usage" if @$args != 1;
my %akas = $self->{database}->get_also_known_as(@$args[0], $dont_use_aliases_table);
my %akas = $self->{database}->get_also_known_as(@$args[0], $dont_use_aliases_table);
if (%akas) {
my $result = "@$args[0] also known as:\n";
if (%akas) {
my $result = "@$args[0] also known as:\n";
my %nicks;
my $sep = "";
foreach my $aka (sort keys %akas) {
next if $aka =~ /^Guest\d+(?:!.*)?$/;
next if $akas{$aka}->{type} == $self->{database}->{alias_type}->{WEAK} && not $show_weak;
my %nicks;
my $sep = "";
foreach my $aka (sort keys %akas) {
next if $aka =~ /^Guest\d+(?:!.*)?$/;
next if $akas{$aka}->{type} == $self->{database}->{alias_type}->{WEAK} && not $show_weak;
if (not $show_hostmasks) {
my ($nick) = $aka =~ m/([^!]+)/;
next if exists $nicks{$nick};
$nicks{$nick}->{id} = $akas{$aka}->{id};
$result .= "$sep$nick";
} else {
$result .= "$sep$aka";
}
if (not $show_hostmasks) {
my ($nick) = $aka =~ m/([^!]+)/;
next if exists $nicks{$nick};
$nicks{$nick}->{id} = $akas{$aka}->{id};
$result .= "$sep$nick";
} else {
$result .= "$sep$aka";
}
$result .= "?" if $akas{$aka}->{nickchange} == 1;
$result .= " ($akas{$aka}->{nickserv})" if $show_nickserv and exists $akas{$aka}->{nickserv};
$result .= " {$akas{$aka}->{gecos}}" if $show_gecos and exists $akas{$aka}->{gecos};
$result .= "?" if $akas{$aka}->{nickchange} == 1;
$result .= " ($akas{$aka}->{nickserv})" if $show_nickserv and exists $akas{$aka}->{nickserv};
$result .= " {$akas{$aka}->{gecos}}" if $show_gecos and exists $akas{$aka}->{gecos};
if ($show_relationship) {
if ($akas{$aka}->{id} == $akas{$aka}->{alias}) {
$result .= " [$akas{$aka}->{id}]";
} else {
$result .= " [$akas{$aka}->{id} -> $akas{$aka}->{alias}]";
if ($show_relationship) {
if ($akas{$aka}->{id} == $akas{$aka}->{alias}) { $result .= " [$akas{$aka}->{id}]"; }
else { $result .= " [$akas{$aka}->{id} -> $akas{$aka}->{alias}]"; }
} elsif ($show_id) {
$result .= " [$akas{$aka}->{id}]";
}
$result .= " [WEAK]" if $akas{$aka}->{type} == $self->{database}->{alias_type}->{WEAK};
if ($show_hostmasks or $show_nickserv or $show_gecos or $show_id or $show_relationship) { $sep = ",\n"; }
else { $sep = ", "; }
}
} elsif ($show_id) {
$result .= " [$akas{$aka}->{id}]";
}
$result .= " [WEAK]" if $akas{$aka}->{type} == $self->{database}->{alias_type}->{WEAK};
if ($show_hostmasks or $show_nickserv or $show_gecos or $show_id or $show_relationship) {
$sep = ",\n";
} else {
$sep = ", ";
}
return $result;
} else {
return "I don't know anybody named @$args[0].";
}
return $result;
} else {
return "I don't know anybody named @$args[0].";
}
}
sub recall_message {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
if (not defined $from) {
$self->{pbot}->{logger}->log("Command missing ~from parameter!\n");
return "";
}
my $usage = 'Usage: recall [nick [history [channel]]] [-c,channel <channel>] [-t,text,h,history <history>] [-b,before <context before>] [-a,after <context after>] [-x,context <nick>] [-n,count <count>] [+ ...]';
if (not defined $arguments or not length $arguments) {
return $usage;
}
$arguments = lc $arguments;
my @recalls = split /\s\+\s/, $arguments;
my $getopt_error;
local $SIG{__WARN__} = sub {
$getopt_error = shift;
chomp $getopt_error;
};
my $recall_text = '';
Getopt::Long::Configure ("bundling");
foreach my $recall (@recalls) {
my ($recall_nick, $recall_history, $recall_channel, $recall_before, $recall_after, $recall_context, $recall_count);
my ($ret, $args) = GetOptionsFromString($recall,
'channel|c:s' => \$recall_channel,
'text|t|history|h:s' => \$recall_history,
'before|b:i' => \$recall_before,
'after|a:i' => \$recall_after,
'count|n:i' => \$recall_count,
'context|x:s' => \$recall_context);
return "/say $getopt_error -- $usage" if defined $getopt_error;
my $channel_arg = 1 if defined $recall_channel;
my $history_arg = 1 if defined $recall_history;
$recall_nick = shift @$args if @$args;
$recall_history = shift @$args if @$args and not defined $recall_history;
$recall_channel = "@$args" if @$args and not defined $recall_channel;
$recall_count = 1 if (not defined $recall_count) || ($recall_count <= 0);
return "You may only select a count of up to 50 messages." if $recall_count > 50;
$recall_before = 0 if not defined $recall_before;
$recall_after = 0 if not defined $recall_after;
# imply -x if -n > 1 and no history
if ($recall_count > 1 and not defined $recall_history) {
$recall_context = $recall_nick;
if (not defined $from) {
$self->{pbot}->{logger}->log("Command missing ~from parameter!\n");
return "";
}
# make -n behave like -b if -n > 1 and nick is context
if ((defined $recall_context or not defined $recall_history) and $recall_count > 1) {
$recall_before = $recall_count - 1;
$recall_count = 0;
}
my $usage =
'Usage: recall [nick [history [channel]]] [-c,channel <channel>] [-t,text,h,history <history>] [-b,before <context before>] [-a,after <context after>] [-x,context <nick>] [-n,count <count>] [+ ...]';
if ($recall_before + $recall_after > 200) {
return "You may only select up to 200 lines of surrounding context.";
}
if (not defined $arguments or not length $arguments) { return $usage; }
if ($recall_count > 1 and ($recall_before > 0 or $recall_after > 0)) {
return "The `count` and `context before/after` options cannot be used together.";
}
$arguments = lc $arguments;
# swap nick and channel if recall nick looks like channel and channel wasn't specified
if (not $channel_arg and $recall_nick =~ m/^#/) {
my $temp = $recall_nick;
$recall_nick = $recall_channel;
$recall_channel = $temp;
}
my @recalls = split /\s\+\s/, $arguments;
$recall_history = 1 if not defined $recall_history;
my $getopt_error;
local $SIG{__WARN__} = sub {
$getopt_error = shift;
chomp $getopt_error;
};
# swap history and channel if history looks like a channel and neither history or channel were specified
if (not $channel_arg and not $history_arg and $recall_history =~ m/^#/) {
my $temp = $recall_history;
$recall_history = $recall_channel;
$recall_channel = $temp;
}
my $recall_text = '';
Getopt::Long::Configure("bundling");
# skip recall command if recalling self without arguments
$recall_history = $nick eq $recall_nick ? 2 : 1 if defined $recall_nick and not defined $recall_history;
foreach my $recall (@recalls) {
my ($recall_nick, $recall_history, $recall_channel, $recall_before, $recall_after, $recall_context, $recall_count);
# set history to most recent message if not specified
$recall_history = '1' if not defined $recall_history;
my ($ret, $args) = GetOptionsFromString(
$recall,
'channel|c:s' => \$recall_channel,
'text|t|history|h:s' => \$recall_history,
'before|b:i' => \$recall_before,
'after|a:i' => \$recall_after,
'count|n:i' => \$recall_count,
'context|x:s' => \$recall_context
);
# set channel to current channel if not specified
$recall_channel = $from if not defined $recall_channel;
return "/say $getopt_error -- $usage" if defined $getopt_error;
# another sanity check for people using it wrong
if ($recall_channel !~ m/^#/) {
$recall_history = "$recall_history $recall_channel";
$recall_channel = $from;
}
my $channel_arg = 1 if defined $recall_channel;
my $history_arg = 1 if defined $recall_history;
if (not defined $recall_nick and defined $recall_context) {
$recall_nick = $recall_context;
}
$recall_nick = shift @$args if @$args;
$recall_history = shift @$args if @$args and not defined $recall_history;
$recall_channel = "@$args" if @$args and not defined $recall_channel;
my ($account, $found_nick);
$recall_count = 1 if (not defined $recall_count) || ($recall_count <= 0);
return "You may only select a count of up to 50 messages." if $recall_count > 50;
if (defined $recall_nick) {
($account, $found_nick) = $self->{database}->find_message_account_by_nick($recall_nick);
$recall_before = 0 if not defined $recall_before;
$recall_after = 0 if not defined $recall_after;
if (not defined $account) {
return "I don't know anybody named $recall_nick.";
}
# imply -x if -n > 1 and no history
if ($recall_count > 1 and not defined $recall_history) { $recall_context = $recall_nick; }
$found_nick =~ s/!.*$//;
}
my $message;
if ($recall_history =~ /^\d+$/) {
# integral history
if (defined $account) {
my $max_messages = $self->{database}->get_max_messages($account, $recall_channel);
if ($recall_history < 1 || $recall_history > $max_messages) {
if ($max_messages == 0) {
my @channels = $self->{database}->get_channels($account);
my $result = "No messages for $recall_nick in $recall_channel; I have messages for them in ";
my $comma = '';
my $count = 0;
foreach my $channel (sort @channels) {
next if $channel !~ /^#/;
$result .= "$comma$channel";
$comma = ', ';
$count++;
}
if ($count == 0) {
return "I have no messages for $recall_nick.";
} else {
return "/say $result.";
}
} else {
return "Please choose a history between 1 and $max_messages";
}
# make -n behave like -b if -n > 1 and nick is context
if ((defined $recall_context or not defined $recall_history) and $recall_count > 1) {
$recall_before = $recall_count - 1;
$recall_count = 0;
}
}
$recall_history--;
$message = $self->{database}->recall_message_by_count($account, $recall_channel, $recall_history, '(?:recall|mock|ftfy|fix)');
if ($recall_before + $recall_after > 200) { return "You may only select up to 200 lines of surrounding context."; }
if (not defined $message) {
return "No message found at index $recall_history in channel $recall_channel.";
}
} else {
# regex history
$message = $self->{database}->recall_message_by_text($account, $recall_channel, $recall_history, '(?:recall|mock|ftfy|fix)');
if ($recall_count > 1 and ($recall_before > 0 or $recall_after > 0)) { return "The `count` and `context before/after` options cannot be used together."; }
if (not defined $message) {
if (defined $account) {
return "No message for nick $found_nick in channel $recall_channel containing \"$recall_history\"";
# swap nick and channel if recall nick looks like channel and channel wasn't specified
if (not $channel_arg and $recall_nick =~ m/^#/) {
my $temp = $recall_nick;
$recall_nick = $recall_channel;
$recall_channel = $temp;
}
$recall_history = 1 if not defined $recall_history;
# swap history and channel if history looks like a channel and neither history or channel were specified
if (not $channel_arg and not $history_arg and $recall_history =~ m/^#/) {
my $temp = $recall_history;
$recall_history = $recall_channel;
$recall_channel = $temp;
}
# skip recall command if recalling self without arguments
$recall_history = $nick eq $recall_nick ? 2 : 1 if defined $recall_nick and not defined $recall_history;
# set history to most recent message if not specified
$recall_history = '1' if not defined $recall_history;
# set channel to current channel if not specified
$recall_channel = $from if not defined $recall_channel;
# another sanity check for people using it wrong
if ($recall_channel !~ m/^#/) {
$recall_history = "$recall_history $recall_channel";
$recall_channel = $from;
}
if (not defined $recall_nick and defined $recall_context) { $recall_nick = $recall_context; }
my ($account, $found_nick);
if (defined $recall_nick) {
($account, $found_nick) = $self->{database}->find_message_account_by_nick($recall_nick);
if (not defined $account) { return "I don't know anybody named $recall_nick."; }
$found_nick =~ s/!.*$//;
}
my $message;
if ($recall_history =~ /^\d+$/) {
# integral history
if (defined $account) {
my $max_messages = $self->{database}->get_max_messages($account, $recall_channel);
if ($recall_history < 1 || $recall_history > $max_messages) {
if ($max_messages == 0) {
my @channels = $self->{database}->get_channels($account);
my $result = "No messages for $recall_nick in $recall_channel; I have messages for them in ";
my $comma = '';
my $count = 0;
foreach my $channel (sort @channels) {
next if $channel !~ /^#/;
$result .= "$comma$channel";
$comma = ', ';
$count++;
}
if ($count == 0) { return "I have no messages for $recall_nick."; }
else { return "/say $result."; }
} else {
return "Please choose a history between 1 and $max_messages";
}
}
}
$recall_history--;
$message = $self->{database}->recall_message_by_count($account, $recall_channel, $recall_history, '(?:recall|mock|ftfy|fix)');
if (not defined $message) { return "No message found at index $recall_history in channel $recall_channel."; }
} else {
return "No message in channel $recall_channel containing \"$recall_history\".";
# regex history
$message = $self->{database}->recall_message_by_text($account, $recall_channel, $recall_history, '(?:recall|mock|ftfy|fix)');
if (not defined $message) {
if (defined $account) { return "No message for nick $found_nick in channel $recall_channel containing \"$recall_history\""; }
else { return "No message in channel $recall_channel containing \"$recall_history\"."; }
}
}
my $context_account;
if (defined $recall_context) {
($context_account) = $self->{database}->find_message_account_by_nick($recall_context);
if (not defined $context_account) { return "I don't know anybody named $recall_context."; }
}
my $messages = $self->{database}->get_message_context($message, $recall_before, $recall_after, $recall_count, $recall_history, $context_account);
my $max_recall_time = $self->{pbot}->{registry}->get_value('messagehistory', 'max_recall_time');
foreach my $msg (@$messages) {
$self->{pbot}->{logger}->log("$nick ($from) recalled <$msg->{nick}/$msg->{channel}> $msg->{msg}\n");
if ($max_recall_time && gettimeofday - $msg->{timestamp} > $max_recall_time && not $self->{pbot}->{users}->loggedin_admin($from, "$nick!$user\@$host")) {
$max_recall_time = duration($max_recall_time);
$recall_text .= "Sorry, you can not recall messages older than $max_recall_time.";
return $recall_text;
}
my $text = $msg->{msg};
my $ago = concise ago(gettimeofday - $msg->{timestamp});
if ( $text =~ s/^(NICKCHANGE)\b/changed nick to/
or $text =~ s/^(KICKED|QUIT)\b/lc "$1"/e
or $text =~ s/^MODE ([^ ]+) (.*)/set mode $1 on $2/
or $text =~ s/^(JOIN|PART)\b/lc "$1ed"/e)
{
$text =~ s/^(quit) (.*)/$1 ($2)/; # fix ugly "[nick] quit Quit: Leaving."
$recall_text .= "[$ago] $msg->{nick} $text\n";
} elsif ($text =~ s/^\/me\s+//) {
$recall_text .= "[$ago] * $msg->{nick} $text\n";
} else {
$recall_text .= "[$ago] <$msg->{nick}> $text\n";
}
}
}
}
my $context_account;
if (defined $recall_context) {
($context_account) = $self->{database}->find_message_account_by_nick($recall_context);
if (not defined $context_account) {
return "I don't know anybody named $recall_context.";
}
}
my $messages = $self->{database}->get_message_context($message, $recall_before, $recall_after, $recall_count, $recall_history, $context_account);
my $max_recall_time = $self->{pbot}->{registry}->get_value('messagehistory', 'max_recall_time');
foreach my $msg (@$messages) {
$self->{pbot}->{logger}->log("$nick ($from) recalled <$msg->{nick}/$msg->{channel}> $msg->{msg}\n");
if ($max_recall_time && gettimeofday - $msg->{timestamp} > $max_recall_time && not $self->{pbot}->{users}->loggedin_admin($from, "$nick!$user\@$host")) {
$max_recall_time = duration($max_recall_time);
$recall_text .= "Sorry, you can not recall messages older than $max_recall_time.";
return $recall_text;
}
my $text = $msg->{msg};
my $ago = concise ago(gettimeofday - $msg->{timestamp});
if ($text =~ s/^(NICKCHANGE)\b/changed nick to/ or
$text =~ s/^(KICKED|QUIT)\b/lc "$1"/e or
$text =~ s/^MODE ([^ ]+) (.*)/set mode $1 on $2/ or
$text =~ s/^(JOIN|PART)\b/lc "$1ed"/e) {
$text =~ s/^(quit) (.*)/$1 ($2)/; # fix ugly "[nick] quit Quit: Leaving."
$recall_text .= "[$ago] $msg->{nick} $text\n";
} elsif ($text =~ s/^\/me\s+//) {
$recall_text .= "[$ago] * $msg->{nick} $text\n";
} else {
$recall_text .= "[$ago] <$msg->{nick}> $text\n";
}
}
}
return $recall_text;
return $recall_text;
}
1;

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Modules;
use parent 'PBot::Class';
use warnings; use strict;
@ -15,124 +16,117 @@ use IPC::Run qw/run timeout/;
use Encode;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->load_cmd(@_) }, "load", 1);
$self->{pbot}->{commands}->register(sub { $self->unload_cmd(@_) }, "unload", 1);
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->load_cmd(@_) }, "load", 1);
$self->{pbot}->{commands}->register(sub { $self->unload_cmd(@_) }, "unload", 1);
}
sub load_cmd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $factoids = $self->{pbot}->{factoids}->{factoids};
my ($keyword, $module) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
return "Usage: load <keyword> <module>" if not defined $module;
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $factoids = $self->{pbot}->{factoids}->{factoids};
my ($keyword, $module) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
return "Usage: load <keyword> <module>" if not defined $module;
if ($factoids->exists('.*', $keyword)) {
return 'There is already a keyword named ' . $factoids->get_data('.*', $keyword, '_name') . '.';
}
if ($factoids->exists('.*', $keyword)) { return 'There is already a keyword named ' . $factoids->get_data('.*', $keyword, '_name') . '.'; }
$self->{pbot}->{factoids}->add_factoid('module', '.*', "$nick!$user\@$host", $keyword, $module, 1);
$factoids->set('.*', $keyword, 'add_nick', 1, 1);
$factoids->set('.*', $keyword, 'nooverride', 1);
$self->{pbot}->{logger}->log("$nick!$user\@$host loaded module $keyword => $module\n");
return "Loaded module $keyword => $module";
$self->{pbot}->{factoids}->add_factoid('module', '.*', "$nick!$user\@$host", $keyword, $module, 1);
$factoids->set('.*', $keyword, 'add_nick', 1, 1);
$factoids->set('.*', $keyword, 'nooverride', 1);
$self->{pbot}->{logger}->log("$nick!$user\@$host loaded module $keyword => $module\n");
return "Loaded module $keyword => $module";
}
sub unload_cmd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $module = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
return "Usage: unload <keyword>" if not defined $module;
my $factoids = $self->{pbot}->{factoids}->{factoids};
return "/say $module not found." if not $factoids->exists('.*', $module);
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $module = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
return "Usage: unload <keyword>" if not defined $module;
my $factoids = $self->{pbot}->{factoids}->{factoids};
return "/say $module not found." if not $factoids->exists('.*', $module);
if ($factoids->get_data('.*', $module, 'type') ne 'module') {
return "/say " . $factoids->get_data('.*', $module, '_name') . ' is not a module.';
}
if ($factoids->get_data('.*', $module, 'type') ne 'module') { return "/say " . $factoids->get_data('.*', $module, '_name') . ' is not a module.'; }
my $name = $factoids->get_data('.*', $module, '_name');
$factoids->remove('.*', $module);
$self->{pbot}->{logger}->log("$nick!$user\@$host unloaded module $module\n");
return "/say $name unloaded.";
my $name = $factoids->get_data('.*', $module, '_name');
$factoids->remove('.*', $module);
$self->{pbot}->{logger}->log("$nick!$user\@$host unloaded module $module\n");
return "/say $name unloaded.";
}
sub execute_module {
my ($self, $stuff) = @_;
my $text;
my ($self, $stuff) = @_;
my $text;
if ($self->{pbot}->{registry}->get_value('general', 'debugcontext')) {
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
$self->{pbot}->{logger}->log("execute_module\n");
$self->{pbot}->{logger}->log(Dumper $stuff);
}
if ($self->{pbot}->{registry}->get_value('general', 'debugcontext')) {
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
$self->{pbot}->{logger}->log("execute_module\n");
$self->{pbot}->{logger}->log(Dumper $stuff);
}
$self->{pbot}->{process_manager}->execute_process($stuff, sub { $self->launch_module(@_) });
$self->{pbot}->{process_manager}->execute_process($stuff, sub { $self->launch_module(@_) });
}
sub launch_module {
my ($self, $stuff) = @_;
$stuff->{arguments} = "" if not defined $stuff->{arguments};
my @factoids = $self->{pbot}->{factoids}->find_factoid($stuff->{from}, $stuff->{keyword}, exact_channel => 2, exact_trigger => 2);
if (not @factoids or not $factoids[0]) {
$stuff->{checkflood} = 1;
$self->{pbot}->{interpreter}->handle_result($stuff, "/msg $stuff->{nick} Failed to find module for '$stuff->{keyword}' in channel $stuff->{from}\n");
return;
}
my ($channel, $trigger) = ($factoids[0]->[0], $factoids[0]->[1]);
$stuff->{channel} = $channel;
$stuff->{keyword} = $trigger;
$stuff->{trigger} = $trigger;
my $module = $self->{pbot}->{factoids}->{factoids}->get_data($channel, $trigger, 'action');
$self->{pbot}->{logger}->log("(" . (defined $stuff->{from} ? $stuff->{from} : "(undef)") . "): $stuff->{nick}!$stuff->{user}\@$stuff->{host}: Executing module [$stuff->{command}] $module $stuff->{arguments}\n");
$stuff->{arguments} = $self->{pbot}->{factoids}->expand_special_vars($stuff->{from}, $stuff->{nick}, $stuff->{root_keyword}, $stuff->{arguments});
my $module_dir = $self->{pbot}->{registry}->get_value('general', 'module_dir');
if (not chdir $module_dir) {
$self->{pbot}->{logger}->log("Could not chdir to '$module_dir': $!\n");
Carp::croak("Could not chdir to '$module_dir': $!");
}
if ($self->{pbot}->{factoids}->{factoids}->exists($channel, $trigger, 'workdir')) {
chdir $self->{pbot}->{factoids}->{factoids}->get_data($channel, $trigger, 'workdir');
}
# FIXME -- add check to ensure $module exists
my ($exitval, $stdout, $stderr) = eval {
my $args = $stuff->{arguments};
if (not $stuff->{args_utf8}) {
$args = encode('UTF-8', $args);
my ($self, $stuff) = @_;
$stuff->{arguments} = "" if not defined $stuff->{arguments};
my @factoids = $self->{pbot}->{factoids}->find_factoid($stuff->{from}, $stuff->{keyword}, exact_channel => 2, exact_trigger => 2);
if (not @factoids or not $factoids[0]) {
$stuff->{checkflood} = 1;
$self->{pbot}->{interpreter}->handle_result($stuff, "/msg $stuff->{nick} Failed to find module for '$stuff->{keyword}' in channel $stuff->{from}\n");
return;
}
my @cmdline = ("./$module", $self->{pbot}->{interpreter}->split_line($args));
my $timeout = $self->{pbot}->{registry}->get_value('general', 'module_timeout') // 30;
my ($stdin, $stdout, $stderr);
run \@cmdline, \$stdin, \$stdout, \$stderr, timeout($timeout);
my $exitval = $? >> 8;
utf8::decode($stdout);
utf8::decode($stderr);
return ($exitval, $stdout, $stderr);
};
if ($@) {
my $error = $@;
if ($error =~ m/timeout on timer/) {
($exitval, $stdout, $stderr) = (-1, "$stuff->{trigger}: timed-out", '');
} else {
($exitval, $stdout, $stderr) = (-1, '', $error);
my ($channel, $trigger) = ($factoids[0]->[0], $factoids[0]->[1]);
$stuff->{channel} = $channel;
$stuff->{keyword} = $trigger;
$stuff->{trigger} = $trigger;
my $module = $self->{pbot}->{factoids}->{factoids}->get_data($channel, $trigger, 'action');
$self->{pbot}->{logger}->log("("
. (defined $stuff->{from} ? $stuff->{from} : "(undef)")
. "): $stuff->{nick}!$stuff->{user}\@$stuff->{host}: Executing module [$stuff->{command}] $module $stuff->{arguments}\n");
$stuff->{arguments} = $self->{pbot}->{factoids}->expand_special_vars($stuff->{from}, $stuff->{nick}, $stuff->{root_keyword}, $stuff->{arguments});
my $module_dir = $self->{pbot}->{registry}->get_value('general', 'module_dir');
if (not chdir $module_dir) {
$self->{pbot}->{logger}->log("Could not chdir to '$module_dir': $!\n");
Carp::croak("Could not chdir to '$module_dir': $!");
}
}
if (length $stderr) {
if (open(my $fh, '>>', "$module-stderr")) {
print $fh $stderr;
close $fh;
} else {
$self->{pbot}->{logger}->log("Failed to open $module-stderr: $!\n");
if ($self->{pbot}->{factoids}->{factoids}->exists($channel, $trigger, 'workdir')) {
chdir $self->{pbot}->{factoids}->{factoids}->get_data($channel, $trigger, 'workdir');
}
}
$stuff->{result} = $stdout;
chomp $stuff->{result};
# FIXME -- add check to ensure $module exists
my ($exitval, $stdout, $stderr) = eval {
my $args = $stuff->{arguments};
if (not $stuff->{args_utf8}) { $args = encode('UTF-8', $args); }
my @cmdline = ("./$module", $self->{pbot}->{interpreter}->split_line($args));
my $timeout = $self->{pbot}->{registry}->get_value('general', 'module_timeout') // 30;
my ($stdin, $stdout, $stderr);
run \@cmdline, \$stdin, \$stdout, \$stderr, timeout($timeout);
my $exitval = $? >> 8;
utf8::decode($stdout);
utf8::decode($stderr);
return ($exitval, $stdout, $stderr);
};
if ($@) {
my $error = $@;
if ($error =~ m/timeout on timer/) { ($exitval, $stdout, $stderr) = (-1, "$stuff->{trigger}: timed-out", ''); }
else { ($exitval, $stdout, $stderr) = (-1, '', $error); }
}
if (length $stderr) {
if (open(my $fh, '>>', "$module-stderr")) {
print $fh $stderr;
close $fh;
} else {
$self->{pbot}->{logger}->log("Failed to open $module-stderr: $!\n");
}
}
$stuff->{result} = $stdout;
chomp $stuff->{result};
}
1;

View File

@ -10,6 +10,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::NickList;
use parent 'PBot::Class';
use warnings; use strict;
@ -17,342 +18,315 @@ use feature 'unicode_strings';
use Text::Levenshtein qw/fastdistance/;
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
use Time::HiRes qw/gettimeofday/;
sub initialize {
my ($self, %conf) = @_;
$self->{nicklist} = {};
$self->{pbot}->{registry}->add_default('text', 'nicklist', 'debug', '0');
my ($self, %conf) = @_;
$self->{nicklist} = {};
$self->{pbot}->{registry}->add_default('text', 'nicklist', 'debug', '0');
$self->{pbot}->{commands}->register(sub { $self->show_nicklist(@_) }, "nicklist", 0);
$self->{pbot}->{commands}->register(sub { $self->show_nicklist(@_) }, "nicklist", 0);
$self->{pbot}->{event_dispatcher}->register_handler('irc.namreply', sub { $self->on_namreply(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.join', sub { $self->on_join(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.part', sub { $self->on_part(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.quit', sub { $self->on_quit(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.kick', sub { $self->on_kick(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.nick', sub { $self->on_nickchange(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_activity(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.caction', sub { $self->on_activity(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.namreply', sub { $self->on_namreply(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.join', sub { $self->on_join(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.part', sub { $self->on_part(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.quit', sub { $self->on_quit(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.kick', sub { $self->on_kick(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.nick', sub { $self->on_nickchange(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_activity(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.caction', sub { $self->on_activity(@_) });
# handlers for the bot itself joining/leaving channels
$self->{pbot}->{event_dispatcher}->register_handler('pbot.join', sub { $self->on_join_channel(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('pbot.part', sub { $self->on_part_channel(@_) });
# handlers for the bot itself joining/leaving channels
$self->{pbot}->{event_dispatcher}->register_handler('pbot.join', sub { $self->on_join_channel(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('pbot.part', sub { $self->on_part_channel(@_) });
}
sub show_nicklist {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $nicklist;
return "Usage: nicklist <channel> [nick]" if not length $arguments;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $nicklist;
return "Usage: nicklist <channel> [nick]" if not length $arguments;
my @args = split / /, $arguments;
my @args = split / /, $arguments;
if (@args == 1) {
if (not exists $self->{nicklist}->{lc $arguments}) {
return "No nicklist for $arguments.";
if (@args == 1) {
if (not exists $self->{nicklist}->{lc $arguments}) { return "No nicklist for $arguments."; }
$nicklist = Dumper($self->{nicklist}->{lc $arguments});
} else {
if (not exists $self->{nicklist}->{lc $args[0]}) { return "No nicklist for $args[0]."; }
elsif (not exists $self->{nicklist}->{lc $args[0]}->{lc $args[1]}) { return "No such nick $args[1] in channel $args[0]."; }
$nicklist = Dumper($self->{nicklist}->{lc $args[0]}->{lc $args[1]});
}
$nicklist = Dumper($self->{nicklist}->{lc $arguments});
} else {
if (not exists $self->{nicklist}->{lc $args[0]}) {
return "No nicklist for $args[0].";
} elsif (not exists $self->{nicklist}->{lc $args[0]}->{lc $args[1]}) {
return "No such nick $args[1] in channel $args[0].";
}
$nicklist = Dumper($self->{nicklist}->{lc $args[0]}->{lc $args[1]});
}
return $nicklist;
return $nicklist;
}
sub update_timestamp {
my ($self, $channel, $nick) = @_;
my $orig_nick = $nick;
$channel = lc $channel;
$nick = lc $nick;
my ($self, $channel, $nick) = @_;
my $orig_nick = $nick;
$channel = lc $channel;
$nick = lc $nick;
if (exists $self->{nicklist}->{$channel} and exists $self->{nicklist}->{$channel}->{$nick}) {
$self->{nicklist}->{$channel}->{$nick}->{timestamp} = gettimeofday;
} else {
$self->{pbot}->{logger}->log("Adding nick '$orig_nick' to channel '$channel'\n") if $self->{pbot}->{registry}->get_value('nicklist', 'debug');
$self->{nicklist}->{$channel}->{$nick} = { nick => $orig_nick, timestamp => scalar gettimeofday };
}
if (exists $self->{nicklist}->{$channel} and exists $self->{nicklist}->{$channel}->{$nick}) { $self->{nicklist}->{$channel}->{$nick}->{timestamp} = gettimeofday; }
else {
$self->{pbot}->{logger}->log("Adding nick '$orig_nick' to channel '$channel'\n") if $self->{pbot}->{registry}->get_value('nicklist', 'debug');
$self->{nicklist}->{$channel}->{$nick} = {nick => $orig_nick, timestamp => scalar gettimeofday};
}
}
sub remove_channel {
my ($self, $channel) = @_;
delete $self->{nicklist}->{lc $channel};
my ($self, $channel) = @_;
delete $self->{nicklist}->{lc $channel};
}
sub add_nick {
my ($self, $channel, $nick) = @_;
if (not exists $self->{nicklist}->{lc $channel}->{lc $nick}) {
$self->{pbot}->{logger}->log("Adding nick '$nick' to channel '$channel'\n") if $self->{pbot}->{registry}->get_value('nicklist', 'debug');
$self->{nicklist}->{lc $channel}->{lc $nick} = { nick => $nick, timestamp => 0 };
}
my ($self, $channel, $nick) = @_;
if (not exists $self->{nicklist}->{lc $channel}->{lc $nick}) {
$self->{pbot}->{logger}->log("Adding nick '$nick' to channel '$channel'\n") if $self->{pbot}->{registry}->get_value('nicklist', 'debug');
$self->{nicklist}->{lc $channel}->{lc $nick} = {nick => $nick, timestamp => 0};
}
}
sub remove_nick {
my ($self, $channel, $nick) = @_;
$self->{pbot}->{logger}->log("Removing nick '$nick' from channel '$channel'\n") if $self->{pbot}->{registry}->get_value('nicklist', 'debug');
delete $self->{nicklist}->{lc $channel}->{lc $nick};
my ($self, $channel, $nick) = @_;
$self->{pbot}->{logger}->log("Removing nick '$nick' from channel '$channel'\n") if $self->{pbot}->{registry}->get_value('nicklist', 'debug');
delete $self->{nicklist}->{lc $channel}->{lc $nick};
}
sub get_channels {
my ($self, $nick) = @_;
my @channels;
my ($self, $nick) = @_;
my @channels;
$nick = lc $nick;
$nick = lc $nick;
foreach my $channel (keys %{ $self->{nicklist} }) {
if (exists $self->{nicklist}->{$channel}->{$nick}) {
push @channels, $channel;
foreach my $channel (keys %{$self->{nicklist}}) {
if (exists $self->{nicklist}->{$channel}->{$nick}) { push @channels, $channel; }
}
}
return \@channels;
return \@channels;
}
sub get_nicks {
my ($self, $channel) = @_;
$channel = lc $channel;
my @nicks;
return @nicks if not exists $self->{nicklist}->{$channel};
foreach my $nick (keys %{ $self->{nicklist}->{$channel} }) {
push @nicks, $self->{nicklist}->{$channel}->{$nick}->{nick};
}
return @nicks;
my ($self, $channel) = @_;
$channel = lc $channel;
my @nicks;
return @nicks if not exists $self->{nicklist}->{$channel};
foreach my $nick (keys %{$self->{nicklist}->{$channel}}) { push @nicks, $self->{nicklist}->{$channel}->{$nick}->{nick}; }
return @nicks;
}
sub set_meta {
my ($self, $channel, $nick, $key, $value) = @_;
my ($self, $channel, $nick, $key, $value) = @_;
$channel = lc $channel;
$nick = lc $nick;
$channel = lc $channel;
$nick = lc $nick;
if (not exists $self->{nicklist}->{$channel} or not exists $self->{nicklist}->{$channel}->{$nick}) {
if (exists $self->{nicklist}->{$channel} and $nick =~ m/[*?]/) {
my $regex = quotemeta $nick;
$regex =~ s/\\\*/.*?/g;
$regex =~ s/\\\?/./g;
if (not exists $self->{nicklist}->{$channel} or not exists $self->{nicklist}->{$channel}->{$nick}) {
if (exists $self->{nicklist}->{$channel} and $nick =~ m/[*?]/) {
my $regex = quotemeta $nick;
$regex =~ s/\\\*/.*?/g;
$regex =~ s/\\\?/./g;
my $found = 0;
foreach my $n (keys %{$self->{nicklist}->{$channel}}) {
if (exists $self->{nicklist}->{$channel}->{$n}->{hostmask} and $self->{nicklist}->{$channel}->{$n}->{hostmask} =~ m/$regex/i) {
$self->{nicklist}->{$channel}->{$n}->{$key} = $value;
$found++;
my $found = 0;
foreach my $n (keys %{$self->{nicklist}->{$channel}}) {
if (exists $self->{nicklist}->{$channel}->{$n}->{hostmask} and $self->{nicklist}->{$channel}->{$n}->{hostmask} =~ m/$regex/i) {
$self->{nicklist}->{$channel}->{$n}->{$key} = $value;
$found++;
}
}
return $found;
} else {
$self->{pbot}->{logger}->log("Nicklist: Attempt to set invalid meta ($key => $value) for $nick in $channel.\n");
return 0;
}
}
return $found;
} else {
$self->{pbot}->{logger}->log("Nicklist: Attempt to set invalid meta ($key => $value) for $nick in $channel.\n");
return 0;
}
}
$self->{nicklist}->{$channel}->{$nick}->{$key} = $value;
return 1;
$self->{nicklist}->{$channel}->{$nick}->{$key} = $value;
return 1;
}
sub delete_meta {
my ($self, $channel, $nick, $key) = @_;
my ($self, $channel, $nick, $key) = @_;
$channel = lc $channel;
$nick = lc $nick;
$channel = lc $channel;
$nick = lc $nick;
if (not exists $self->{nicklist}->{$channel}
or not exists $self->{nicklist}->{$channel}->{$nick}
or not exists $self->{nicklist}->{$channel}->{$nick}->{$key}) {
return undef;
}
return delete $self->{nicklist}->{$channel}->{$nick}->{$key};
if (not exists $self->{nicklist}->{$channel} or not exists $self->{nicklist}->{$channel}->{$nick} or not exists $self->{nicklist}->{$channel}->{$nick}->{$key}) {
return undef;
}
return delete $self->{nicklist}->{$channel}->{$nick}->{$key};
}
sub get_meta {
my ($self, $channel, $nick, $key) = @_;
my ($self, $channel, $nick, $key) = @_;
$channel = lc $channel;
$nick = lc $nick;
$channel = lc $channel;
$nick = lc $nick;
if (not exists $self->{nicklist}->{$channel}
or not exists $self->{nicklist}->{$channel}->{$nick}
or not exists $self->{nicklist}->{$channel}->{$nick}->{$key}) {
return undef;
}
if (not exists $self->{nicklist}->{$channel} or not exists $self->{nicklist}->{$channel}->{$nick} or not exists $self->{nicklist}->{$channel}->{$nick}->{$key}) {
return undef;
}
return $self->{nicklist}->{$channel}->{$nick}->{$key};
return $self->{nicklist}->{$channel}->{$nick}->{$key};
}
sub is_present_any_channel {
my ($self, $nick) = @_;
my ($self, $nick) = @_;
$nick = lc $nick;
$nick = lc $nick;
foreach my $channel (keys %{ $self->{nicklist} }) {
if (exists $self->{nicklist}->{$channel}->{$nick}) {
return $self->{nicklist}->{$channel}->{$nick}->{nick};
foreach my $channel (keys %{$self->{nicklist}}) {
if (exists $self->{nicklist}->{$channel}->{$nick}) { return $self->{nicklist}->{$channel}->{$nick}->{nick}; }
}
}
return 0;
return 0;
}
sub is_present {
my ($self, $channel, $nick) = @_;
my ($self, $channel, $nick) = @_;
$channel = lc $channel;
$nick = lc $nick;
$channel = lc $channel;
$nick = lc $nick;
if (exists $self->{nicklist}->{$channel} and exists $self->{nicklist}->{$channel}->{$nick}) {
return $self->{nicklist}->{$channel}->{$nick}->{nick};
} else {
return 0;
}
if (exists $self->{nicklist}->{$channel} and exists $self->{nicklist}->{$channel}->{$nick}) { return $self->{nicklist}->{$channel}->{$nick}->{nick}; }
else { return 0; }
}
sub is_present_similar {
my ($self, $channel, $nick, $similar) = @_;
my ($self, $channel, $nick, $similar) = @_;
$channel = lc $channel;
$nick = lc $nick;
$channel = lc $channel;
$nick = lc $nick;
return 0 if not exists $self->{nicklist}->{$channel};
return $self->{nicklist}->{$channel}->{$nick}->{nick} if $self->is_present($channel, $nick);
return 0 if $nick =~ m/(?:^\$|\s)/; # not nick-like
return 0 if not exists $self->{nicklist}->{$channel};
return $self->{nicklist}->{$channel}->{$nick}->{nick} if $self->is_present($channel, $nick);
return 0 if $nick =~ m/(?:^\$|\s)/; # not nick-like
my $percentage = $self->{pbot}->{registry}->get_value('interpreter', 'nick_similarity');
$percentage = 0.20 if not defined $percentage;
my $percentage = $self->{pbot}->{registry}->get_value('interpreter', 'nick_similarity');
$percentage = 0.20 if not defined $percentage;
$percentage = $similar if defined $similar;
$percentage = $similar if defined $similar;
my $now = gettimeofday;
foreach my $person (sort { $self->{nicklist}->{$channel}->{$b}->{timestamp} <=> $self->{nicklist}->{$channel}->{$a}->{timestamp} } keys %{ $self->{nicklist}->{$channel} }) {
return 0 if $now - $self->{nicklist}->{$channel}->{$person}->{timestamp} > 3600; # 1 hour
my $distance = fastdistance($nick, $person);
my $length = length $nick > length $person ? length $nick : length $person;
my $now = gettimeofday;
foreach my $person (sort { $self->{nicklist}->{$channel}->{$b}->{timestamp} <=> $self->{nicklist}->{$channel}->{$a}->{timestamp} } keys %{$self->{nicklist}->{$channel}})
{
return 0 if $now - $self->{nicklist}->{$channel}->{$person}->{timestamp} > 3600; # 1 hour
my $distance = fastdistance($nick, $person);
my $length = length $nick > length $person ? length $nick : length $person;
if ($length != 0 && $distance / $length <= $percentage) {
return $self->{nicklist}->{$channel}->{$person}->{nick};
if ($length != 0 && $distance / $length <= $percentage) { return $self->{nicklist}->{$channel}->{$person}->{nick}; }
}
}
return 0;
return 0;
}
sub random_nick {
my ($self, $channel) = @_;
my ($self, $channel) = @_;
$channel = lc $channel;
$channel = lc $channel;
if (exists $self->{nicklist}->{$channel}) {
my $now = gettimeofday;
my @nicks = grep { $now - $self->{nicklist}->{$channel}->{$_}->{timestamp} < 3600 * 2 } keys %{ $self->{nicklist}->{$channel} };
if (exists $self->{nicklist}->{$channel}) {
my $now = gettimeofday;
my @nicks = grep { $now - $self->{nicklist}->{$channel}->{$_}->{timestamp} < 3600 * 2 } keys %{$self->{nicklist}->{$channel}};
my $nick = $nicks[rand @nicks];
return $self->{nicklist}->{$channel}->{$nick}->{nick};
} else {
return undef;
}
my $nick = $nicks[rand @nicks];
return $self->{nicklist}->{$channel}->{$nick}->{nick};
} else {
return undef;
}
}
sub on_namreply {
my ($self, $event_type, $event) = @_;
my ($channel, $nicks) = ($event->{event}->{args}[2], $event->{event}->{args}[3]);
my ($self, $event_type, $event) = @_;
my ($channel, $nicks) = ($event->{event}->{args}[2], $event->{event}->{args}[3]);
foreach my $nick (split ' ', $nicks) {
my $stripped_nick = $nick;
$stripped_nick =~ s/^[@+%]//g; # remove OP/Voice/etc indicator from nick
$self->add_nick($channel, $stripped_nick);
foreach my $nick (split ' ', $nicks) {
my $stripped_nick = $nick;
$stripped_nick =~ s/^[@+%]//g; # remove OP/Voice/etc indicator from nick
$self->add_nick($channel, $stripped_nick);
my ($account_id, $hostmask) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($stripped_nick);
my ($account_id, $hostmask) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($stripped_nick);
if (defined $hostmask) {
my ($user, $host) = $hostmask =~ m/[^!]+!([^@]+)@(.*)/;
$self->set_meta($channel, $stripped_nick, 'hostmask', $hostmask);
$self->set_meta($channel, $stripped_nick, 'user', $user);
$self->set_meta($channel, $stripped_nick, 'host', $host);
if (defined $hostmask) {
my ($user, $host) = $hostmask =~ m/[^!]+!([^@]+)@(.*)/;
$self->set_meta($channel, $stripped_nick, 'hostmask', $hostmask);
$self->set_meta($channel, $stripped_nick, 'user', $user);
$self->set_meta($channel, $stripped_nick, 'host', $host);
}
if ($nick =~ m/\@/) { $self->set_meta($channel, $stripped_nick, '+o', 1); }
if ($nick =~ m/\+/) { $self->set_meta($channel, $stripped_nick, '+v', 1); }
if ($nick =~ m/\%/) { $self->set_meta($channel, $stripped_nick, '+h', 1); }
}
if ($nick =~ m/\@/) {
$self->set_meta($channel, $stripped_nick, '+o', 1);
}
if ($nick =~ m/\+/) {
$self->set_meta($channel, $stripped_nick, '+v', 1);
}
if ($nick =~ m/\%/) {
$self->set_meta($channel, $stripped_nick, '+h', 1);
}
}
return 0;
return 0;
}
sub on_activity {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->{to}[0]);
$self->update_timestamp($channel, $nick);
return 0;
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->{to}[0]);
$self->update_timestamp($channel, $nick);
return 0;
}
sub on_join {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to);
$self->add_nick($channel, $nick);
$self->set_meta($channel, $nick, 'hostmask', "$nick!$user\@$host");
$self->set_meta($channel, $nick, 'user', $user);
$self->set_meta($channel, $nick, 'host', $host);
return 0;
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to);
$self->add_nick($channel, $nick);
$self->set_meta($channel, $nick, 'hostmask', "$nick!$user\@$host");
$self->set_meta($channel, $nick, 'user', $user);
$self->set_meta($channel, $nick, 'host', $host);
return 0;
}
sub on_part {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to);
$self->remove_nick($channel, $nick);
return 0;
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to);
$self->remove_nick($channel, $nick);
return 0;
}
sub on_quit {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host);
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host);
foreach my $channel (keys %{ $self->{nicklist} }) {
if ($self->is_present($channel, $nick)) {
$self->remove_nick($channel, $nick);
foreach my $channel (keys %{$self->{nicklist}}) {
if ($self->is_present($channel, $nick)) { $self->remove_nick($channel, $nick); }
}
}
return 0;
return 0;
}
sub on_kick {
my ($self, $event_type, $event) = @_;
my ($nick, $channel) = ($event->{event}->to, $event->{event}->{args}[0]);
$self->remove_nick($channel, $nick);
return 0;
my ($self, $event_type, $event) = @_;
my ($nick, $channel) = ($event->{event}->to, $event->{event}->{args}[0]);
$self->remove_nick($channel, $nick);
return 0;
}
sub on_nickchange {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $newnick) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $newnick) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
foreach my $channel (keys %{ $self->{nicklist} }) {
if ($self->is_present($channel, $nick)) {
my $meta = delete $self->{nicklist}->{$channel}->{lc $nick};
$meta->{nick} = $newnick;
$meta->{timestamp} = gettimeofday;
$self->{nicklist}->{$channel}->{lc $newnick} = $meta;
foreach my $channel (keys %{$self->{nicklist}}) {
if ($self->is_present($channel, $nick)) {
my $meta = delete $self->{nicklist}->{$channel}->{lc $nick};
$meta->{nick} = $newnick;
$meta->{timestamp} = gettimeofday;
$self->{nicklist}->{$channel}->{lc $newnick} = $meta;
}
}
}
return 0;
return 0;
}
sub on_join_channel {
my ($self, $event_type, $event) = @_;
$self->remove_channel($event->{channel}); # clear nicklist to remove any stale nicks before repopulating with namreplies
return 0;
my ($self, $event_type, $event) = @_;
$self->remove_channel($event->{channel}); # clear nicklist to remove any stale nicks before repopulating with namreplies
return 0;
}
sub on_part_channel {
my ($self, $event_type, $event) = @_;
$self->remove_channel($event->{channel});
return 0;
my ($self, $event_type, $event) = @_;
$self->remove_channel($event->{channel});
return 0;
}
1;

View File

@ -51,451 +51,449 @@ use PBot::Modules;
use PBot::ProcessManager;
sub new {
my ($proto, %conf) = @_;
my $class = ref($proto) || $proto;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
my ($proto, %conf) = @_;
my $class = ref($proto) || $proto;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($self, %conf) = @_;
$self->{startup_timestamp} = time;
my ($self, %conf) = @_;
$self->{startup_timestamp} = time;
my $data_dir = $conf{data_dir};
my $module_dir = $conf{module_dir};
my $plugin_dir = $conf{plugin_dir};
my $data_dir = $conf{data_dir};
my $module_dir = $conf{module_dir};
my $plugin_dir = $conf{plugin_dir};
# check command-line arguments for directory overrides
foreach my $arg (@ARGV) {
if ($arg =~ m/^-?(?:general\.)?((?:data|module|plugin)_dir)=(.*)$/) {
my $override = $1;
my $value = $2;
$data_dir = $value if $override eq 'data_dir';
$module_dir = $value if $override eq 'module_dir';
$plugin_dir = $value if $override eq 'plugin_dir';
}
}
# check command-line arguments for registry overrides
foreach my $arg (@ARGV) {
next if $arg =~ m/^-?(?:general\.)?(?:config|data|module|plugin)_dir=.*$/; # already processed
my ($item, $value) = split /=/, $arg, 2;
if (not defined $item or not defined $value) {
print STDERR "Fatal error: unknown argument `$arg`; arguments must be in the form of `section.key=value` (e.g.: irc.botnick=newnick)\n";
exit;
# check command-line arguments for directory overrides
foreach my $arg (@ARGV) {
if ($arg =~ m/^-?(?:general\.)?((?:data|module|plugin)_dir)=(.*)$/) {
my $override = $1;
my $value = $2;
$data_dir = $value if $override eq 'data_dir';
$module_dir = $value if $override eq 'module_dir';
$plugin_dir = $value if $override eq 'plugin_dir';
}
}
my ($section, $key) = split /\./, $item, 2;
if (not defined $section or not defined $key) {
print STDERR "Fatal error: bad argument `$arg`; registry entries must be in the form of section.key (e.g.: irc.botnick)\n";
exit;
# check command-line arguments for registry overrides
foreach my $arg (@ARGV) {
next if $arg =~ m/^-?(?:general\.)?(?:config|data|module|plugin)_dir=.*$/; # already processed
my ($item, $value) = split /=/, $arg, 2;
if (not defined $item or not defined $value) {
print STDERR "Fatal error: unknown argument `$arg`; arguments must be in the form of `section.key=value` (e.g.: irc.botnick=newnick)\n";
exit;
}
my ($section, $key) = split /\./, $item, 2;
if (not defined $section or not defined $key) {
print STDERR "Fatal error: bad argument `$arg`; registry entries must be in the form of section.key (e.g.: irc.botnick)\n";
exit;
}
$section =~ s/^-//; # remove a leading - to allow arguments like -irc.botnick due to habitual use of -args
$self->{overrides}->{"$section.$key"} = $value;
}
$section =~ s/^-//; # remove a leading - to allow arguments like -irc.botnick due to habitual use of -args
$self->{overrides}->{"$section.$key"} = $value;
}
# let modules register signal handlers
$self->{atexit} = PBot::Registerable->new(%conf, pbot => $self);
$self->register_signal_handlers;
# let modules register signal handlers
$self->{atexit} = PBot::Registerable->new(%conf, pbot => $self);
$self->register_signal_handlers;
# create logger
$self->{logger} = PBot::Logger->new(pbot => $self, filename => "$data_dir/log/log", %conf);
# create logger
$self->{logger} = PBot::Logger->new(pbot => $self, filename => "$data_dir/log/log", %conf);
# make sure the environment is sane
if (not -d $data_dir) {
$self->{logger}->log("Data directory ($data_dir) does not exist; aborting...\n");
exit;
}
# make sure the environment is sane
if (not -d $data_dir) {
$self->{logger}->log("Data directory ($data_dir) does not exist; aborting...\n");
exit;
}
if (not -d $module_dir) {
$self->{logger}->log("Modules directory ($module_dir) does not exist; aborting...\n");
exit;
}
if (not -d $module_dir) {
$self->{logger}->log("Modules directory ($module_dir) does not exist; aborting...\n");
exit;
}
if (not -d $plugin_dir) {
$self->{logger}->log("Plugins directory ($plugin_dir) does not exist; aborting...\n");
exit;
}
if (not -d $plugin_dir) {
$self->{logger}->log("Plugins directory ($plugin_dir) does not exist; aborting...\n");
exit;
}
# then capabilities so commands can add new capabilities
$self->{capabilities} = PBot::Capabilities->new(pbot => $self, filename => "$data_dir/capabilities", %conf);
# then capabilities so commands can add new capabilities
$self->{capabilities} = PBot::Capabilities->new(pbot => $self, filename => "$data_dir/capabilities", %conf);
# then commands so the modules can register new commands
$self->{commands} = PBot::Commands->new(pbot => $self, filename => "$data_dir/commands", %conf);
# then commands so the modules can register new commands
$self->{commands} = PBot::Commands->new(pbot => $self, filename => "$data_dir/commands", %conf);
# add some commands
$self->{commands}->register(sub { $self->listcmd(@_) }, "list");
$self->{commands}->register(sub { $self->ack_die(@_) }, "die", 1);
$self->{commands}->register(sub { $self->export(@_) }, "export", 1);
$self->{commands}->register(sub { $self->reload(@_) }, "reload", 1);
$self->{commands}->register(sub { $self->evalcmd(@_) }, "eval", 1);
$self->{commands}->register(sub { $self->sl(@_) }, "sl", 1);
# add some commands
$self->{commands}->register(sub { $self->listcmd(@_) }, "list");
$self->{commands}->register(sub { $self->ack_die(@_) }, "die", 1);
$self->{commands}->register(sub { $self->export(@_) }, "export", 1);
$self->{commands}->register(sub { $self->reload(@_) }, "reload", 1);
$self->{commands}->register(sub { $self->evalcmd(@_) }, "eval", 1);
$self->{commands}->register(sub { $self->sl(@_) }, "sl", 1);
# add 'cap' capability command
$self->{commands}->register(sub { $self->{capabilities}->capcmd(@_) }, "cap");
# add 'cap' capability command
$self->{commands}->register(sub { $self->{capabilities}->capcmd(@_) }, "cap");
# prepare the version
$self->{version} = PBot::VERSION->new(pbot => $self, %conf);
$self->{logger}->log($self->{version}->version . "\n");
$self->{logger}->log("Args: @ARGV\n") if @ARGV;
# prepare the version
$self->{version} = PBot::VERSION->new(pbot => $self, %conf);
$self->{logger}->log($self->{version}->version . "\n");
$self->{logger}->log("Args: @ARGV\n") if @ARGV;
# log the configured paths
$self->{logger}->log("data_dir: $data_dir\n");
$self->{logger}->log("module_dir: $module_dir\n");
$self->{logger}->log("plugin_dir: $plugin_dir\n");
# log the configured paths
$self->{logger}->log("data_dir: $data_dir\n");
$self->{logger}->log("module_dir: $module_dir\n");
$self->{logger}->log("plugin_dir: $plugin_dir\n");
$self->{timer} = PBot::Timer->new(pbot => $self, timeout => 10, %conf);
$self->{modules} = PBot::Modules->new(pbot => $self, %conf);
$self->{functions} = PBot::Functions->new(pbot => $self, %conf);
$self->{refresher} = PBot::Refresher->new(pbot => $self);
$self->{timer} = PBot::Timer->new(pbot => $self, timeout => 10, %conf);
$self->{modules} = PBot::Modules->new(pbot => $self, %conf);
$self->{functions} = PBot::Functions->new(pbot => $self, %conf);
$self->{refresher} = PBot::Refresher->new(pbot => $self);
# create registry and set some defaults
$self->{registry} = PBot::Registry->new(pbot => $self, filename => "$data_dir/registry", %conf);
# create registry and set some defaults
$self->{registry} = PBot::Registry->new(pbot => $self, filename => "$data_dir/registry", %conf);
$self->{registry}->add_default('text', 'general', 'data_dir', $data_dir);
$self->{registry}->add_default('text', 'general', 'module_dir', $module_dir);
$self->{registry}->add_default('text', 'general', 'plugin_dir', $plugin_dir);
$self->{registry}->add_default('text', 'general', 'trigger', $conf{trigger} // '!');
$self->{registry}->add_default('text', 'general', 'data_dir', $data_dir);
$self->{registry}->add_default('text', 'general', 'module_dir', $module_dir);
$self->{registry}->add_default('text', 'general', 'plugin_dir', $plugin_dir);
$self->{registry}->add_default('text', 'general', 'trigger', $conf{trigger} // '!');
$self->{registry}->add_default('text', 'irc', 'debug', $conf{irc_debug} // 0);
$self->{registry}->add_default('text', 'irc', 'show_motd', $conf{show_motd} // 1);
$self->{registry}->add_default('text', 'irc', 'max_msg_len', $conf{max_msg_len} // 425);
$self->{registry}->add_default('text', 'irc', 'server', $conf{server} // "irc.freenode.net");
$self->{registry}->add_default('text', 'irc', 'port', $conf{port} // 6667);
$self->{registry}->add_default('text', 'irc', 'SSL', $conf{SSL} // 0);
$self->{registry}->add_default('text', 'irc', 'SSL_ca_file', $conf{SSL_ca_file} // 'none');
$self->{registry}->add_default('text', 'irc', 'SSL_ca_path', $conf{SSL_ca_path} // 'none');
$self->{registry}->add_default('text', 'irc', 'botnick', $conf{botnick} // "");
$self->{registry}->add_default('text', 'irc', 'username', $conf{username} // "pbot3");
$self->{registry}->add_default('text', 'irc', 'realname', $conf{realname} // "https://github.com/pragma-/pbot");
$self->{registry}->add_default('text', 'irc', 'identify_password', $conf{identify_password} // '');
$self->{registry}->add_default('text', 'irc', 'log_default_handler', 1);
$self->{registry}->add_default('text', 'irc', 'debug', $conf{irc_debug} // 0);
$self->{registry}->add_default('text', 'irc', 'show_motd', $conf{show_motd} // 1);
$self->{registry}->add_default('text', 'irc', 'max_msg_len', $conf{max_msg_len} // 425);
$self->{registry}->add_default('text', 'irc', 'server', $conf{server} // "irc.freenode.net");
$self->{registry}->add_default('text', 'irc', 'port', $conf{port} // 6667);
$self->{registry}->add_default('text', 'irc', 'SSL', $conf{SSL} // 0);
$self->{registry}->add_default('text', 'irc', 'SSL_ca_file', $conf{SSL_ca_file} // 'none');
$self->{registry}->add_default('text', 'irc', 'SSL_ca_path', $conf{SSL_ca_path} // 'none');
$self->{registry}->add_default('text', 'irc', 'botnick', $conf{botnick} // "");
$self->{registry}->add_default('text', 'irc', 'username', $conf{username} // "pbot3");
$self->{registry}->add_default('text', 'irc', 'realname', $conf{realname} // "https://github.com/pragma-/pbot");
$self->{registry}->add_default('text', 'irc', 'identify_password', $conf{identify_password} // '');
$self->{registry}->add_default('text', 'irc', 'log_default_handler', 1);
$self->{registry}->set_default('irc', 'SSL_ca_file', 'private', 1);
$self->{registry}->set_default('irc', 'SSL_ca_path', 'private', 1);
$self->{registry}->set_default('irc', 'identify_password', 'private', 1);
$self->{registry}->set_default('irc', 'SSL_ca_file', 'private', 1);
$self->{registry}->set_default('irc', 'SSL_ca_path', 'private', 1);
$self->{registry}->set_default('irc', 'identify_password', 'private', 1);
# load existing registry entries from file (if exists) to overwrite defaults
if (-e $self->{registry}->{registry}->{filename}) { $self->{registry}->load; }
# load existing registry entries from file (if exists) to overwrite defaults
if (-e $self->{registry}->{registry}->{filename}) {
$self->{registry}->load;
}
# update important paths
$self->{registry}->set('general', 'data_dir', 'value', $data_dir, 0, 1);
$self->{registry}->set('general', 'module_dir', 'value', $module_dir, 0, 1);
$self->{registry}->set('general', 'plugin_dir', 'value', $plugin_dir, 0, 1);
# update important paths
$self->{registry}->set('general', 'data_dir', 'value', $data_dir, 0, 1);
$self->{registry}->set('general', 'module_dir', 'value', $module_dir, 0, 1);
$self->{registry}->set('general', 'plugin_dir', 'value', $plugin_dir, 0, 1);
# override registry entries with command-line arguments, if any
foreach my $override (keys %{$self->{overrides}}) {
my ($section, $key) = split /\./, $override;
my $value = $self->{overrides}->{$override};
$self->{logger}->log("Overriding $section.$key to $value\n");
$self->{registry}->set($section, $key, 'value', $value, 0, 1);
}
# override registry entries with command-line arguments, if any
foreach my $override (keys %{$self->{overrides}}) {
my ($section, $key) = split /\./, $override;
my $value = $self->{overrides}->{$override};
$self->{logger}->log("Overriding $section.$key to $value\n");
$self->{registry}->set($section, $key, 'value', $value, 0, 1);
}
# registry triggers fire when value changes
$self->{registry}->add_trigger('irc', 'botnick', sub { $self->change_botnick_trigger(@_) });
$self->{registry}->add_trigger('irc', 'debug', sub { $self->irc_debug_trigger(@_) });
# registry triggers fire when value changes
$self->{registry}->add_trigger('irc', 'botnick', sub { $self->change_botnick_trigger(@_) });
$self->{registry}->add_trigger('irc', 'debug', sub { $self->irc_debug_trigger(@_) });
# ensure user has attempted to configure the bot
if (not length $self->{registry}->get_value('irc', 'botnick')) {
$self->{logger}->log("Fatal error: IRC nickname not defined; please set registry key irc.botnick in $data_dir/registry to continue.\n");
exit;
}
# ensure user has attempted to configure the bot
if (not length $self->{registry}->get_value('irc', 'botnick')) {
$self->{logger}->log("Fatal error: IRC nickname not defined; please set registry key irc.botnick in $data_dir/registry to continue.\n");
exit;
}
$self->{event_dispatcher} = PBot::EventDispatcher->new(pbot => $self, %conf);
$self->{process_manager} = PBot::ProcessManager->new(pbot => $self, %conf);
$self->{irchandlers} = PBot::IRCHandlers->new(pbot => $self, %conf);
$self->{select_handler} = PBot::SelectHandler->new(pbot => $self, %conf);
$self->{users} = PBot::Users->new(pbot => $self, filename => "$data_dir/users", %conf);
$self->{stdin_reader} = PBot::StdinReader->new(pbot => $self, %conf);
$self->{bantracker} = PBot::BanTracker->new(pbot => $self, %conf);
$self->{lagchecker} = PBot::LagChecker->new(pbot => $self, %conf);
$self->{messagehistory} = PBot::MessageHistory->new(pbot => $self, filename => "$data_dir/message_history.sqlite3", %conf);
$self->{antiflood} = PBot::AntiFlood->new(pbot => $self, %conf);
$self->{antispam} = PBot::AntiSpam->new(pbot => $self, %conf);
$self->{ignorelist} = PBot::IgnoreList->new(pbot => $self, filename => "$data_dir/ignorelist", %conf);
$self->{blacklist} = PBot::BlackList->new(pbot => $self, filename => "$data_dir/blacklist", %conf);
$self->{irc} = PBot::IRC->new();
$self->{channels} = PBot::Channels->new(pbot => $self, filename => "$data_dir/channels", %conf);
$self->{chanops} = PBot::ChanOps->new(pbot => $self, %conf);
$self->{nicklist} = PBot::NickList->new(pbot => $self, %conf);
$self->{webpaste} = PBot::WebPaste->new(pbot => $self, %conf);
$self->{parsedate} = PBot::Utils::ParseDate->new(pbot => $self, %conf);
$self->{event_dispatcher} = PBot::EventDispatcher->new(pbot => $self, %conf);
$self->{process_manager} = PBot::ProcessManager->new(pbot => $self, %conf);
$self->{irchandlers} = PBot::IRCHandlers->new(pbot => $self, %conf);
$self->{select_handler} = PBot::SelectHandler->new(pbot => $self, %conf);
$self->{users} = PBot::Users->new(pbot => $self, filename => "$data_dir/users", %conf);
$self->{stdin_reader} = PBot::StdinReader->new(pbot => $self, %conf);
$self->{bantracker} = PBot::BanTracker->new(pbot => $self, %conf);
$self->{lagchecker} = PBot::LagChecker->new(pbot => $self, %conf);
$self->{messagehistory} = PBot::MessageHistory->new(pbot => $self, filename => "$data_dir/message_history.sqlite3", %conf);
$self->{antiflood} = PBot::AntiFlood->new(pbot => $self, %conf);
$self->{antispam} = PBot::AntiSpam->new(pbot => $self, %conf);
$self->{ignorelist} = PBot::IgnoreList->new(pbot => $self, filename => "$data_dir/ignorelist", %conf);
$self->{blacklist} = PBot::BlackList->new(pbot => $self, filename => "$data_dir/blacklist", %conf);
$self->{irc} = PBot::IRC->new();
$self->{channels} = PBot::Channels->new(pbot => $self, filename => "$data_dir/channels", %conf);
$self->{chanops} = PBot::ChanOps->new(pbot => $self, %conf);
$self->{nicklist} = PBot::NickList->new(pbot => $self, %conf);
$self->{webpaste} = PBot::WebPaste->new(pbot => $self, %conf);
$self->{parsedate} = PBot::Utils::ParseDate->new(pbot => $self, %conf);
$self->{interpreter} = PBot::Interpreter->new(pbot => $self, %conf);
$self->{interpreter}->register(sub { $self->{commands}->interpreter(@_) });
$self->{interpreter}->register(sub { $self->{factoids}->interpreter(@_) });
$self->{interpreter} = PBot::Interpreter->new(pbot => $self, %conf);
$self->{interpreter}->register(sub { $self->{commands}->interpreter(@_) });
$self->{interpreter}->register(sub { $self->{factoids}->interpreter(@_) });
$self->{factoids} = PBot::Factoids->new(pbot => $self, filename => "$data_dir/factoids", %conf);
$self->{factoids} = PBot::Factoids->new(pbot => $self, filename => "$data_dir/factoids", %conf);
$self->{plugins} = PBot::Plugins->new(pbot => $self, %conf);
$self->{plugins} = PBot::Plugins->new(pbot => $self, %conf);
# load available plugins
$self->{plugins}->autoload(%conf);
# load available plugins
$self->{plugins}->autoload(%conf);
# give botowner all capabilities
$self->{capabilities}->rebuild_botowner_capabilities();
# give botowner all capabilities
$self->{capabilities}->rebuild_botowner_capabilities();
}
sub random_nick {
my ($self, $length) = @_;
$length //= 9;
my @chars = ("A".."Z", "a".."z", "0".."9");
my $nick = $chars[rand @chars - 10]; # nicks cannot start with a digit
$nick .= $chars[rand @chars] for 1..$length;
return $nick;
my ($self, $length) = @_;
$length //= 9;
my @chars = ("A" .. "Z", "a" .. "z", "0" .. "9");
my $nick = $chars[rand @chars - 10]; # nicks cannot start with a digit
$nick .= $chars[rand @chars] for 1 .. $length;
return $nick;
}
# TODO: add disconnect subroutine
sub connect {
my ($self, $server) = @_;
my ($self, $server) = @_;
if ($self->{connected}) {
# TODO: disconnect, clean-up, etc
}
if ($self->{connected}) {
$server = $self->{registry}->get_value('irc', 'server') if not defined $server;
# TODO: disconnect, clean-up, etc
}
$self->{logger}->log("Connecting to $server ...\n");
$server = $self->{registry}->get_value('irc', 'server') if not defined $server;
while (not $self->{conn} = $self->{irc}->newconn(
Nick => $self->{registry}->get_value('irc', 'randomize_nick') ? $self->random_nick : $self->{registry}->get_value('irc', 'botnick'),
Username => $self->{registry}->get_value('irc', 'username'),
Ircname => $self->{registry}->get_value('irc', 'realname'),
Server => $server,
Pacing => 1,
UTF8 => 1,
SSL => $self->{registry}->get_value('irc', 'SSL'),
SSL_ca_file => $self->{registry}->get_value('irc', 'SSL_ca_file'),
SSL_ca_path => $self->{registry}->get_value('irc', 'SSL_ca_path'),
Port => $self->{registry}->get_value('irc', 'port'))) {
$self->{logger}->log("$0: Can't connect to $server:" . $self->{registry}->get_value('irc', 'port') . ". Retrying in 15 seconds...\n");
sleep 15;
}
$self->{logger}->log("Connecting to $server ...\n");
$self->{connected} = 1;
while (
not $self->{conn} = $self->{irc}->newconn(
Nick => $self->{registry}->get_value('irc', 'randomize_nick') ? $self->random_nick : $self->{registry}->get_value('irc', 'botnick'),
Username => $self->{registry}->get_value('irc', 'username'),
Ircname => $self->{registry}->get_value('irc', 'realname'),
Server => $server,
Pacing => 1,
UTF8 => 1,
SSL => $self->{registry}->get_value('irc', 'SSL'),
SSL_ca_file => $self->{registry}->get_value('irc', 'SSL_ca_file'),
SSL_ca_path => $self->{registry}->get_value('irc', 'SSL_ca_path'),
Port => $self->{registry}->get_value('irc', 'port')
)
)
{
$self->{logger}->log("$0: Can't connect to $server:" . $self->{registry}->get_value('irc', 'port') . ". Retrying in 15 seconds...\n");
sleep 15;
}
# start timer once connected
$self->{timer}->start;
$self->{connected} = 1;
# set up handlers for the IRC engine
$self->{conn}->add_default_handler(sub { $self->{irchandlers}->default_handler(@_) }, 1);
$self->{conn}->add_handler([ 251,252,253,254,255,302 ], sub { $self->{irchandlers}->on_init(@_) });
# start timer once connected
$self->{timer}->start;
# ignore these events
$self->{conn}->add_handler(['whoisserver',
'whoiscountry',
'whoischannels',
'whoisidle',
'motdstart',
'endofmotd',
'away',
'endofbanlist'], sub {});
# set up handlers for the IRC engine
$self->{conn}->add_default_handler(sub { $self->{irchandlers}->default_handler(@_) }, 1);
$self->{conn}->add_handler([251, 252, 253, 254, 255, 302], sub { $self->{irchandlers}->on_init(@_) });
# ignore these events
$self->{conn}->add_handler(
[
'whoisserver',
'whoiscountry',
'whoischannels',
'whoisidle',
'motdstart',
'endofmotd',
'away',
'endofbanlist'
],
sub { }
);
}
#main loop
sub do_one_loop {
my $self = shift;
$self->{irc}->do_one_loop();
$self->{select_handler}->do_select();
my $self = shift;
$self->{irc}->do_one_loop();
$self->{select_handler}->do_select();
}
sub start {
my $self = shift;
while (1) {
$self->connect() if not $self->{connected};
$self->do_one_loop() if $self->{connected};
}
my $self = shift;
while (1) {
$self->connect() if not $self->{connected};
$self->do_one_loop() if $self->{connected};
}
}
sub register_signal_handlers {
my $self = shift;
$SIG{INT} = sub { $self->atexit; exit 0; };
my $self = shift;
$SIG{INT} = sub { $self->atexit; exit 0; };
}
sub atexit {
my $self = shift;
$self->{atexit}->execute_all;
my $self = shift;
$self->{atexit}->execute_all;
}
sub irc_debug_trigger {
my ($self, $section, $item, $newvalue) = @_;
$self->{irc}->debug($newvalue);
$self->{conn}->debug($newvalue) if $self->{connected};
my ($self, $section, $item, $newvalue) = @_;
$self->{irc}->debug($newvalue);
$self->{conn}->debug($newvalue) if $self->{connected};
}
sub change_botnick_trigger {
my ($self, $section, $item, $newvalue) = @_;
$self->{conn}->nick($newvalue) if $self->{connected};
my ($self, $section, $item, $newvalue) = @_;
$self->{conn}->nick($newvalue) if $self->{connected};
}
sub listcmd {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $text;
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $text;
my $usage = "Usage: list <modules|commands>";
my $usage = "Usage: list <modules|commands>";
if (not defined $arguments) {
return $usage;
}
if (not defined $arguments) { return $usage; }
if ($arguments =~ /^modules$/i) {
$text = "Loaded modules: ";
foreach my $channel (sort $self->{factoids}->{factoids}->get_keys) {
foreach my $command (sort $self->{factoids}->{factoids}->get_keys($channel)) {
next if $command eq '_name';
if ($self->{factoids}->{factoids}->get_data($channel, $command, 'type') eq 'module') {
$text .= $self->{factoids}->{factoids}->get_data($channel, $command, '_name') . ' ';
if ($arguments =~ /^modules$/i) {
$text = "Loaded modules: ";
foreach my $channel (sort $self->{factoids}->{factoids}->get_keys) {
foreach my $command (sort $self->{factoids}->{factoids}->get_keys($channel)) {
next if $command eq '_name';
if ($self->{factoids}->{factoids}->get_data($channel, $command, 'type') eq 'module') {
$text .= $self->{factoids}->{factoids}->get_data($channel, $command, '_name') . ' ';
}
}
}
}
return $text;
}
return $text;
}
if ($arguments =~ /^commands$/i) {
$text = "Registered commands: ";
foreach my $command (sort { $a->{name} cmp $b->{name} } @{ $self->{commands}->{handlers} }) {
if ($command->{requires_cap}) {
$text .= "+$command->{name} ";
} else {
$text .= "$command->{name} ";
}
if ($arguments =~ /^commands$/i) {
$text = "Registered commands: ";
foreach my $command (sort { $a->{name} cmp $b->{name} } @{$self->{commands}->{handlers}}) {
if ($command->{requires_cap}) { $text .= "+$command->{name} "; }
else { $text .= "$command->{name} "; }
}
return $text;
}
return $text;
}
return $usage;
return $usage;
}
sub sl {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
return "Usage: sl <ircd command>" if not length $arguments;
$self->{conn}->sl($arguments);
return "";
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
return "Usage: sl <ircd command>" if not length $arguments;
$self->{conn}->sl($arguments);
return "";
}
sub ack_die {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
$self->{logger}->log("$nick!$user\@$host made me exit.\n");
$self->atexit();
$self->{conn}->privmsg($from, "Good-bye.") if defined $from;
$self->{conn}->quit("Departure requested.");
exit 0;
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
$self->{logger}->log("$nick!$user\@$host made me exit.\n");
$self->atexit();
$self->{conn}->privmsg($from, "Good-bye.") if defined $from;
$self->{conn}->quit("Departure requested.");
exit 0;
}
sub export {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
return "Usage: export <factoids>" if not defined $arguments;
return "Usage: export <factoids>" if not defined $arguments;
if ($arguments =~ /^factoids$/i) {
return $self->{factoids}->export_factoids;
}
if ($arguments =~ /^factoids$/i) { return $self->{factoids}->export_factoids; }
}
sub evalcmd {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
$self->{logger}->log("[$from] $nick!$user\@$host Evaluating [$arguments]\n");
$self->{logger}->log("[$from] $nick!$user\@$host Evaluating [$arguments]\n");
my $ret = '';
my $result = eval $arguments;
if ($@) {
if (length $result) {
$ret .= "[Error: $@] ";
} else {
$ret .= "Error: $@";
my $ret = '';
my $result = eval $arguments;
if ($@) {
if (length $result) { $ret .= "[Error: $@] "; }
else { $ret .= "Error: $@"; }
$ret =~ s/ at \(eval \d+\) line 1.//;
}
$ret =~ s/ at \(eval \d+\) line 1.//;
}
$result = 'Undefined.' if not defined $result;
$result = 'No output.' if not length $result;
return "/say $ret $result";
$result = 'Undefined.' if not defined $result;
$result = 'No output.' if not length $result;
return "/say $ret $result";
}
sub reload {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my %reloadables = (
'capabilities' => sub {
$self->{capabilities}->{caps}->load;
return "Capabilities reloaded.";
},
my %reloadables = (
'capabilities' => sub {
$self->{capabilities}->{caps}->load;
return "Capabilities reloaded.";
},
'commands' => sub {
$self->{commands}->{metadata}->load;
return "Commands metadata reloaded.";
},
'commands' => sub {
$self->{commands}->{metadata}->load;
return "Commands metadata reloaded.";
},
'blacklist' => sub {
$self->{blacklist}->clear_blacklist;
$self->{blacklist}->load_blacklist;
return "Blacklist reloaded.";
},
'blacklist' => sub {
$self->{blacklist}->clear_blacklist;
$self->{blacklist}->load_blacklist;
return "Blacklist reloaded.";
},
'ban-exemptions' => sub {
$self->{antiflood}->{'ban-exemptions'}->clear;
$self->{antiflood}->{'ban-exemptions'}->load;
return "Ban exemptions reloaded.";
},
'ban-exemptions' => sub {
$self->{antiflood}->{'ban-exemptions'}->clear;
$self->{antiflood}->{'ban-exemptions'}->load;
return "Ban exemptions reloaded.";
},
'ignores' => sub {
$self->{ignorelist}->clear_ignores;
$self->{ignorelist}->load_ignores;
return "Ignore list reloaded.";
},
'ignores' => sub {
$self->{ignorelist}->clear_ignores;
$self->{ignorelist}->load_ignores;
return "Ignore list reloaded.";
},
'users' => sub {
$self->{users}->load;
return "Users reloaded.";
},
'users' => sub {
$self->{users}->load;
return "Users reloaded.";
},
'channels' => sub {
$self->{channels}->{channels}->load;
return "Channels reloaded.";
},
'channels' => sub {
$self->{channels}->{channels}->load;
return "Channels reloaded.";
},
'bantimeouts' => sub {
$self->{chanops}->{unban_timeout}->clear;
$self->{chanops}->{unban_timeout}->load;
return "Ban timeouts reloaded.";
},
'bantimeouts' => sub {
$self->{chanops}->{unban_timeout}->clear;
$self->{chanops}->{unban_timeout}->load;
return "Ban timeouts reloaded.";
},
'mutetimeouts' => sub {
$self->{chanops}->{unmute_timeout}->clear;
$self->{chanops}->{unmute_timeout}->load;
return "Mute timeouts reloaded.";
},
'mutetimeouts' => sub {
$self->{chanops}->{unmute_timeout}->clear;
$self->{chanops}->{unmute_timeout}->load;
return "Mute timeouts reloaded.";
},
'registry' => sub {
$self->{registry}->{registry}->clear;
$self->{registry}->load;
return "Registry reloaded.";
},
'registry' => sub {
$self->{registry}->{registry}->clear;
$self->{registry}->load;
return "Registry reloaded.";
},
'factoids' => sub {
$self->{factoids}->{factoids}->clear;
$self->{factoids}->load_factoids;
return "Factoids reloaded.";
'factoids' => sub {
$self->{factoids}->{factoids}->clear;
$self->{factoids}->load_factoids;
return "Factoids reloaded.";
}
);
if (not length $arguments or not exists $reloadables{$arguments}) {
my $usage = 'Usage: reload <';
$usage .= join '|', sort keys %reloadables;
$usage .= '>';
return $usage;
}
);
if (not length $arguments or not exists $reloadables{$arguments}) {
my $usage = 'Usage: reload <';
$usage .= join '|', sort keys %reloadables;
$usage .= '>';
return $usage;
}
return $reloadables{$arguments}();
return $reloadables{$arguments}();
}
1;

View File

@ -16,169 +16,157 @@ use feature 'unicode_strings';
use File::Basename;
sub initialize {
my ($self, %conf) = @_;
$self->{plugins} = {};
$self->{pbot}->{commands}->register(sub { $self->load_cmd(@_) }, "plug", 1);
$self->{pbot}->{commands}->register(sub { $self->unload_cmd(@_) }, "unplug", 1);
$self->{pbot}->{commands}->register(sub { $self->reload_cmd(@_) }, "replug", 1);
$self->{pbot}->{commands}->register(sub { $self->list_cmd(@_) }, "pluglist", 0);
my ($self, %conf) = @_;
$self->{plugins} = {};
$self->{pbot}->{commands}->register(sub { $self->load_cmd(@_) }, "plug", 1);
$self->{pbot}->{commands}->register(sub { $self->unload_cmd(@_) }, "unplug", 1);
$self->{pbot}->{commands}->register(sub { $self->reload_cmd(@_) }, "replug", 1);
$self->{pbot}->{commands}->register(sub { $self->list_cmd(@_) }, "pluglist", 0);
}
sub autoload {
my ($self, %conf) = @_;
return if $self->{pbot}->{registry}->get_value('plugins', 'noautoload');
my ($self, %conf) = @_;
return if $self->{pbot}->{registry}->get_value('plugins', 'noautoload');
my $path = $self->{pbot}->{registry}->get_value('general', 'plugin_dir') // 'Plugins';
my $data_dir = $self->{pbot}->{registry}->get_value('general', 'data_dir');
my $path = $self->{pbot}->{registry}->get_value('general', 'plugin_dir') // 'Plugins';
my $data_dir = $self->{pbot}->{registry}->get_value('general', 'data_dir');
$self->{pbot}->{logger}->log("Loading plugins ...\n");
my $plugin_count = 0;
$self->{pbot}->{logger}->log("Loading plugins ...\n");
my $plugin_count = 0;
my $fh;
if (not open $fh, "<$data_dir/plugin_autoload") {
$self->{pbot}->{logger}->log("warning: file $data_dir/plugin_autoload does not exist; skipping autoloading of Plugins\n");
return;
}
my $fh;
if (not open $fh, "<$data_dir/plugin_autoload") {
$self->{pbot}->{logger}->log("warning: file $data_dir/plugin_autoload does not exist; skipping autoloading of Plugins\n");
return;
}
chomp(my @plugins = <$fh>);
close $fh;
chomp(my @plugins = <$fh>);
close $fh;
foreach my $plugin (sort @plugins) {
$plugin = basename $plugin;
$plugin =~ s/.pm$//;
foreach my $plugin (sort @plugins) {
$plugin = basename $plugin;
$plugin =~ s/.pm$//;
# do not load plugins that begin with a comment
next if $plugin =~ m/^\s*#/;
# do not load plugins that begin with a comment
next if $plugin =~ m/^\s*#/;
$plugin_count++ if $self->load($plugin, %conf)
}
$self->{pbot}->{logger}->log("$plugin_count plugin" . ($plugin_count == 1 ? '' : 's') . " loaded.\n");
$plugin_count++ if $self->load($plugin, %conf);
}
$self->{pbot}->{logger}->log("$plugin_count plugin" . ($plugin_count == 1 ? '' : 's') . " loaded.\n");
}
sub load {
my ($self, $plugin, %conf) = @_;
my ($self, $plugin, %conf) = @_;
$self->unload($plugin);
$self->unload($plugin);
return if $self->{pbot}->{registry}->get_value('plugins', 'disabled');
return if $self->{pbot}->{registry}->get_value('plugins', 'disabled');
my $path = $self->{pbot}->{registry}->get_value('general', 'plugin_dir') // 'Plugins';
my $path = $self->{pbot}->{registry}->get_value('general', 'plugin_dir') // 'Plugins';
if (not grep { $_ eq $path } @INC) {
unshift @INC, $path;
}
$self->{pbot}->{refresher}->{refresher}->refresh_module("$path/$plugin.pm");
my $ret = eval {
require "$path/$plugin.pm";
if ($@) {
chomp $@;
$self->{pbot}->{logger}->log("Error loading $plugin: $@\n");
return 0;
if (not grep { $_ eq $path } @INC) {
unshift @INC, $path;
}
$self->{pbot}->{logger}->log("Loading $plugin\n");
my $class = "Plugins::$plugin";
$self->{plugins}->{$plugin} = $class->new(pbot => $self->{pbot}, %conf);
$self->{pbot}->{refresher}->{refresher}->update_cache("$path/$plugin.pm");
return 1;
};
$self->{pbot}->{refresher}->{refresher}->refresh_module("$path/$plugin.pm");
if ($@) {
chomp $@;
$self->{pbot}->{logger}->log("Error loading $plugin: $@\n");
return 0;
}
return $ret;
my $ret = eval {
require "$path/$plugin.pm";
if ($@) {
chomp $@;
$self->{pbot}->{logger}->log("Error loading $plugin: $@\n");
return 0;
}
$self->{pbot}->{logger}->log("Loading $plugin\n");
my $class = "Plugins::$plugin";
$self->{plugins}->{$plugin} = $class->new(pbot => $self->{pbot}, %conf);
$self->{pbot}->{refresher}->{refresher}->update_cache("$path/$plugin.pm");
return 1;
};
if ($@) {
chomp $@;
$self->{pbot}->{logger}->log("Error loading $plugin: $@\n");
return 0;
}
return $ret;
}
sub unload {
my ($self, $plugin) = @_;
my ($self, $plugin) = @_;
if (exists $self->{plugins}->{$plugin}) {
eval {
$self->{plugins}->{$plugin}->unload;
delete $self->{plugins}->{$plugin};
};
if ($@) {
chomp $@;
$self->{pbot}->{logger}->log("Warning: got error unloading plugin $plugin: $@\n");
if (exists $self->{plugins}->{$plugin}) {
eval {
$self->{plugins}->{$plugin}->unload;
delete $self->{plugins}->{$plugin};
};
if ($@) {
chomp $@;
$self->{pbot}->{logger}->log("Warning: got error unloading plugin $plugin: $@\n");
}
my $path = $self->{pbot}->{registry}->get_value('general', 'plugin_dir') // 'Plugins';
my $class = $path;
$class =~ s,[/\\],::,g;
$self->{pbot}->{refresher}->{refresher}->unload_module($class . '::' . $plugin);
$self->{pbot}->{refresher}->{refresher}->unload_subs("$path/$plugin.pm");
$self->{pbot}->{logger}->log("Plugin $plugin unloaded.\n");
return 1;
} else {
return 0;
}
my $path = $self->{pbot}->{registry}->get_value('general', 'plugin_dir') // 'Plugins';
my $class = $path;
$class =~ s,[/\\],::,g;
$self->{pbot}->{refresher}->{refresher}->unload_module($class . '::' . $plugin);
$self->{pbot}->{refresher}->{refresher}->unload_subs("$path/$plugin.pm");
$self->{pbot}->{logger}->log("Plugin $plugin unloaded.\n");
return 1;
} else {
return 0;
}
}
sub reload_cmd {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
if (not length $arguments) {
return "Usage: replug <plugin>";
}
if (not length $arguments) { return "Usage: replug <plugin>"; }
my $unload_result = $self->unload_cmd($from, $nick, $user, $host, $arguments);
my $load_result = $self->load_cmd($from, $nick, $user, $host, $arguments);
my $unload_result = $self->unload_cmd($from, $nick, $user, $host, $arguments);
my $load_result = $self->load_cmd($from, $nick, $user, $host, $arguments);
my $result = "";
$result .= "$unload_result " if $unload_result =~ m/^Unloaded/;
$result .= $load_result;
return $result;
my $result = "";
$result .= "$unload_result " if $unload_result =~ m/^Unloaded/;
$result .= $load_result;
return $result;
}
sub load_cmd {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
if (not length $arguments) {
return "Usage: plug <plugin>";
}
if (not length $arguments) { return "Usage: plug <plugin>"; }
if ($self->load($arguments)) {
return "Loaded $arguments plugin.";
} else {
return "Plugin $arguments failed to load.";
}
if ($self->load($arguments)) { return "Loaded $arguments plugin."; }
else { return "Plugin $arguments failed to load."; }
}
sub unload_cmd {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
if (not length $arguments) {
return "Usage: unplug <plugin>";
}
if (not length $arguments) { return "Usage: unplug <plugin>"; }
if ($self->unload($arguments)) {
return "Unloaded $arguments plugin.";
} else {
return "Plugin $arguments is not loaded.";
}
if ($self->unload($arguments)) { return "Unloaded $arguments plugin."; }
else { return "Plugin $arguments is not loaded."; }
}
sub list_cmd {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $result = "Loaded plugins: ";
my $count = 0;
my $comma = '';
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $result = "Loaded plugins: ";
my $count = 0;
my $comma = '';
foreach my $plugin (sort keys %{ $self->{plugins} }) {
$result .= $comma . $plugin;
$count++;
$comma = ', ';
}
foreach my $plugin (sort keys %{$self->{plugins}}) {
$result .= $comma . $plugin;
$count++;
$comma = ', ';
}
$result .= 'none' if $count == 0;
return $result;
$result .= 'none' if $count == 0;
return $result;
}
1;

View File

@ -8,6 +8,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::ProcessManager;
use parent 'PBot::Class';
use warnings; use strict;
@ -17,174 +18,180 @@ use POSIX qw(WNOHANG);
use JSON;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->ps_cmd(@_) }, 'ps', 0);
$self->{pbot}->{commands}->register(sub { $self->kill_cmd(@_) }, 'kill', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-kill');
$self->{processes} = {};
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->ps_cmd(@_) }, 'ps', 0);
$self->{pbot}->{commands}->register(sub { $self->kill_cmd(@_) }, 'kill', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-kill');
$self->{processes} = {};
# automatically reap children processes in background
$SIG{CHLD} = sub { my $pid; do { $pid = waitpid(-1, WNOHANG); $self->remove_process($pid) if $pid > 0; } while $pid > 0; };
# automatically reap children processes in background
$SIG{CHLD} = sub {
my $pid; do { $pid = waitpid(-1, WNOHANG); $self->remove_process($pid) if $pid > 0; } while $pid > 0;
};
}
sub ps_cmd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my @processes;
foreach my $pid (sort keys %{$self->{processes}}) {
push @processes, "$pid: $self->{processes}->{$pid}->{commands}->[0]";
}
if (@processes) {
return "Running processes: " . join '; ', @processes;
} else {
return "No running processes.";
}
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my @processes;
foreach my $pid (sort keys %{$self->{processes}}) { push @processes, "$pid: $self->{processes}->{$pid}->{commands}->[0]"; }
if (@processes) { return "Running processes: " . join '; ', @processes; }
else { return "No running processes."; }
}
sub kill_cmd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "Usage: kill <pids...>";
my @pids;
while (1) {
my $pid = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}) // last;
return "No such pid $pid." if not exists $self->{processes}->{$pid};
push @pids, $pid;
}
return $usage if not @pids;
kill 'INT', @pids;
return "Killed.";
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "Usage: kill <pids...>";
my @pids;
while (1) {
my $pid = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}) // last;
return "No such pid $pid." if not exists $self->{processes}->{$pid};
push @pids, $pid;
}
return $usage if not @pids;
kill 'INT', @pids;
return "Killed.";
}
sub add_process {
my ($self, $pid, $stuff) = @_;
$self->{processes}->{$pid} = $stuff;
my ($self, $pid, $stuff) = @_;
$self->{processes}->{$pid} = $stuff;
}
sub remove_process {
my ($self, $pid) = @_;
delete $self->{processes}->{$pid};
my ($self, $pid) = @_;
delete $self->{processes}->{$pid};
}
sub execute_process {
my ($self, $stuff, $subref, $timeout) = @_;
$timeout //= 30;
my ($self, $stuff, $subref, $timeout) = @_;
$timeout //= 30;
if (not exists $stuff->{commands}) {
$stuff->{commands} = [ $stuff->{command} ];
}
if (not exists $stuff->{commands}) { $stuff->{commands} = [$stuff->{command}]; }
pipe(my $reader, my $writer);
$stuff->{pid} = fork;
pipe(my $reader, my $writer);
$stuff->{pid} = fork;
if (not defined $stuff->{pid}) {
$self->{pbot}->{logger}->log("Could not fork process: $!\n");
close $reader;
close $writer;
$stuff->{checkflood} = 1;
$self->{pbot}->{interpreter}->handle_result($stuff, "/me groans loudly.\n");
return;
}
if ($stuff->{pid} == 0) {
# child
close $reader;
# don't quit the IRC client when the child dies
no warnings;
*PBot::IRC::Connection::DESTROY = sub { return; };
use warnings;
# remove atexit handlers
$self->{pbot}->{atexit}->unregister_all;
# execute the provided subroutine, results are stored in $stuff
eval {
local $SIG{ALRM} = sub { die "PBot::Process `$stuff->{commands}->[0]` timed-out" };
alarm $timeout;
$subref->($stuff);
die if $@;
};
alarm 0;
# check for errors
if ($@) {
$stuff->{result} = $@;
$self->{pbot}->{logger}->log("Error executing process: $stuff->{result}\n");
$stuff->{result} =~ s/ at PBot.*$//ms;
if (not defined $stuff->{pid}) {
$self->{pbot}->{logger}->log("Could not fork process: $!\n");
close $reader;
close $writer;
$stuff->{checkflood} = 1;
$self->{pbot}->{interpreter}->handle_result($stuff, "/me groans loudly.\n");
return;
}
# print $stuff to pipe
my $json = encode_json $stuff;
print $writer "$json\n";
if ($stuff->{pid} == 0) {
# end child
exit 0;
} else {
# parent
close $writer;
$self->add_process($stuff->{pid}, $stuff);
$self->{pbot}->{select_handler}->add_reader($reader, sub { $self->process_pipe_reader($stuff->{pid}, @_) });
# return empty string since reader will handle the output when child is finished
return "";
}
# child
close $reader;
# don't quit the IRC client when the child dies
no warnings;
*PBot::IRC::Connection::DESTROY = sub { return; };
use warnings;
# remove atexit handlers
$self->{pbot}->{atexit}->unregister_all;
# execute the provided subroutine, results are stored in $stuff
eval {
local $SIG{ALRM} = sub { die "PBot::Process `$stuff->{commands}->[0]` timed-out" };
alarm $timeout;
$subref->($stuff);
die if $@;
};
alarm 0;
# check for errors
if ($@) {
$stuff->{result} = $@;
$self->{pbot}->{logger}->log("Error executing process: $stuff->{result}\n");
$stuff->{result} =~ s/ at PBot.*$//ms;
}
# print $stuff to pipe
my $json = encode_json $stuff;
print $writer "$json\n";
# end child
exit 0;
} else {
# parent
close $writer;
$self->add_process($stuff->{pid}, $stuff);
$self->{pbot}->{select_handler}->add_reader($reader, sub { $self->process_pipe_reader($stuff->{pid}, @_) });
# return empty string since reader will handle the output when child is finished
return "";
}
}
sub process_pipe_reader {
my ($self, $pid, $buf) = @_;
my $stuff = decode_json $buf or do {
$self->{pbot}->{logger}->log("Failed to decode bad json: [$buf]\n");
return;
};
my ($self, $pid, $buf) = @_;
my $stuff = decode_json $buf or do {
$self->{pbot}->{logger}->log("Failed to decode bad json: [$buf]\n");
return;
};
if (not defined $stuff->{result} or not length $stuff->{result}) {
$self->{pbot}->{logger}->log("No result from process.\n");
return;
}
if ($stuff->{referenced}) {
return if $stuff->{result} =~ m/(?:no results)/i;
}
if (exists $stuff->{special} and $stuff->{special} eq 'code-factoid') {
$stuff->{result} =~ s/\s+$//g;
$self->{pbot}->{logger}->log("No text result from code-factoid.\n") and return if not length $stuff->{result};
$stuff->{original_keyword} = $stuff->{root_keyword};
$stuff->{result} = $self->{pbot}->{factoids}->handle_action($stuff, $stuff->{result});
}
$stuff->{checkflood} = 0;
if (defined $stuff->{nickoverride}) {
$self->{pbot}->{interpreter}->handle_result($stuff, $stuff->{result});
} else {
# don't override nick if already set
if (exists $stuff->{special} and $stuff->{special} ne 'code-factoid' and $self->{pbot}->{factoids}->{factoids}->exists($stuff->{channel}, $stuff->{trigger}, 'add_nick') and $self->{pbot}->{factoids}->{factoids}->get_data($stuff->{channel}, $stuff->{trigger}, 'add_nick') != 0) {
$stuff->{nickoverride} = $stuff->{nick};
$stuff->{no_nickoverride} = 0;
$stuff->{force_nickoverride} = 1;
} else {
# extract nick-like thing from module result
if ($stuff->{result} =~ s/^(\S+): //) {
my $nick = $1;
if (lc $nick eq "usage") {
# put it back on result if it's a usage message
$stuff->{result} = "$nick: $stuff->{result}";
} else {
my $present = $self->{pbot}->{nicklist}->is_present($stuff->{channel}, $nick);
if ($present) {
# nick is present in channel
$stuff->{nickoverride} = $present;
} else {
# nick not present, put it back on result
$stuff->{result} = "$nick: $stuff->{result}";
}
}
}
if (not defined $stuff->{result} or not length $stuff->{result}) {
$self->{pbot}->{logger}->log("No result from process.\n");
return;
}
$self->{pbot}->{interpreter}->handle_result($stuff, $stuff->{result});
}
my $text = $self->{pbot}->{interpreter}->truncate_result($stuff->{channel}, $self->{pbot}->{registry}->get_value('irc', 'botnick'), 'undef', $stuff->{result}, $stuff->{result}, 0);
$self->{pbot}->{antiflood}->check_flood($stuff->{from}, $self->{pbot}->{registry}->get_value('irc', 'botnick'), $self->{pbot}->{registry}->get_value('irc', 'username'), 'pbot', $text, 0, 0, 0);
if ($stuff->{referenced}) { return if $stuff->{result} =~ m/(?:no results)/i; }
if (exists $stuff->{special} and $stuff->{special} eq 'code-factoid') {
$stuff->{result} =~ s/\s+$//g;
$self->{pbot}->{logger}->log("No text result from code-factoid.\n") and return if not length $stuff->{result};
$stuff->{original_keyword} = $stuff->{root_keyword};
$stuff->{result} = $self->{pbot}->{factoids}->handle_action($stuff, $stuff->{result});
}
$stuff->{checkflood} = 0;
if (defined $stuff->{nickoverride}) { $self->{pbot}->{interpreter}->handle_result($stuff, $stuff->{result}); }
else {
# don't override nick if already set
if ( exists $stuff->{special}
and $stuff->{special} ne 'code-factoid'
and $self->{pbot}->{factoids}->{factoids}->exists($stuff->{channel}, $stuff->{trigger}, 'add_nick')
and $self->{pbot}->{factoids}->{factoids}->get_data($stuff->{channel}, $stuff->{trigger}, 'add_nick') != 0)
{
$stuff->{nickoverride} = $stuff->{nick};
$stuff->{no_nickoverride} = 0;
$stuff->{force_nickoverride} = 1;
} else {
# extract nick-like thing from module result
if ($stuff->{result} =~ s/^(\S+): //) {
my $nick = $1;
if (lc $nick eq "usage") {
# put it back on result if it's a usage message
$stuff->{result} = "$nick: $stuff->{result}";
} else {
my $present = $self->{pbot}->{nicklist}->is_present($stuff->{channel}, $nick);
if ($present) {
# nick is present in channel
$stuff->{nickoverride} = $present;
} else {
# nick not present, put it back on result
$stuff->{result} = "$nick: $stuff->{result}";
}
}
}
}
$self->{pbot}->{interpreter}->handle_result($stuff, $stuff->{result});
}
my $text = $self->{pbot}->{interpreter}
->truncate_result($stuff->{channel}, $self->{pbot}->{registry}->get_value('irc', 'botnick'), 'undef', $stuff->{result}, $stuff->{result}, 0);
$self->{pbot}->{antiflood}
->check_flood($stuff->{from}, $self->{pbot}->{registry}->get_value('irc', 'botnick'), $self->{pbot}->{registry}->get_value('irc', 'username'), 'pbot', $text, 0, 0, 0);
}
1;

View File

@ -18,31 +18,31 @@ use feature 'unicode_strings';
use Module::Refresh;
sub initialize {
my ($self, %conf) = @_;
$self->{refresher} = Module::Refresh->new;
$self->{pbot}->{commands}->register(sub { $self->refresh(@_) }, "refresh", 1);
my ($self, %conf) = @_;
$self->{refresher} = Module::Refresh->new;
$self->{pbot}->{commands}->register(sub { $self->refresh(@_) }, "refresh", 1);
}
sub refresh {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $result = eval {
if (not $arguments) {
$self->{pbot}->{logger}->log("Refreshing all modified modules\n");
$self->{refresher}->refresh;
return "Refreshed all modified modules.\n";
} else {
$self->{pbot}->{logger}->log("Refreshing module $arguments\n");
$self->{refresher}->refresh_module($arguments);
$self->{pbot}->{logger}->log("Refreshed module.\n");
return "Refreshed module.\n";
}
};
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $result = eval {
if (not $arguments) {
$self->{pbot}->{logger}->log("Refreshing all modified modules\n");
$self->{refresher}->refresh;
return "Refreshed all modified modules.\n";
} else {
$self->{pbot}->{logger}->log("Refreshing module $arguments\n");
$self->{refresher}->refresh_module($arguments);
$self->{pbot}->{logger}->log("Refreshed module.\n");
return "Refreshed module.\n";
}
};
if ($@) {
$self->{pbot}->{logger}->log("Error refreshing: $@\n");
return $@;
}
return $result;
if ($@) {
$self->{pbot}->{logger}->log("Error refreshing: $@\n");
return $@;
}
return $result;
}
1;

View File

@ -13,58 +13,56 @@ use warnings; use strict;
use feature 'unicode_strings';
sub new {
my ($proto, %conf) = @_;
my $class = ref($proto) || $proto;
my $self = bless {}, $class;
Carp::croak("Missing pbot reference to " . __FILE__) unless exists $conf{pbot};
$self->{pbot} = $conf{pbot};
$self->initialize(%conf);
return $self;
my ($proto, %conf) = @_;
my $class = ref($proto) || $proto;
my $self = bless {}, $class;
Carp::croak("Missing pbot reference to " . __FILE__) unless exists $conf{pbot};
$self->{pbot} = $conf{pbot};
$self->initialize(%conf);
return $self;
}
sub initialize {
my $self = shift;
$self->{handlers} = [];
my $self = shift;
$self->{handlers} = [];
}
sub execute_all {
my $self = shift;
foreach my $func (@{ $self->{handlers} }) {
my $result = &{ $func->{subref} }(@_);
return $result if defined $result;
}
return undef;
my $self = shift;
foreach my $func (@{$self->{handlers}}) {
my $result = &{$func->{subref}}(@_);
return $result if defined $result;
}
return undef;
}
sub execute {
my $self = shift;
my $ref = shift;
Carp::croak("Missing reference parameter to Registerable::execute") if not defined $ref;
foreach my $func (@{ $self->{handlers} }) {
if ($ref == $func || $ref == $func->{subref}) {
return &{ $func->{subref} }(@_);
my $self = shift;
my $ref = shift;
Carp::croak("Missing reference parameter to Registerable::execute") if not defined $ref;
foreach my $func (@{$self->{handlers}}) {
if ($ref == $func || $ref == $func->{subref}) { return &{$func->{subref}}(@_); }
}
}
return undef;
return undef;
}
sub register {
my ($self, $subref) = @_;
Carp::croak("Must pass subroutine reference to register()") if not defined $subref;
my $ref = { subref => $subref };
push @{ $self->{handlers} }, $ref;
return $ref;
my ($self, $subref) = @_;
Carp::croak("Must pass subroutine reference to register()") if not defined $subref;
my $ref = {subref => $subref};
push @{$self->{handlers}}, $ref;
return $ref;
}
sub unregister {
my ($self, $ref) = @_;
Carp::croak("Must pass reference to unregister()") if not defined $ref;
@{ $self->{handlers} } = grep { $_ != $ref } @{ $self->{handlers} };
my ($self, $ref) = @_;
Carp::croak("Must pass reference to unregister()") if not defined $ref;
@{$self->{handlers}} = grep { $_ != $ref } @{$self->{handlers}};
}
sub unregister_all {
my ($self) = @_;
$self->{handlers} = [];
my ($self) = @_;
$self->{handlers} = [];
}
1;

View File

@ -9,6 +9,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Registry;
use parent 'PBot::Class';
use warnings; use strict;
@ -18,158 +19,141 @@ use Time::HiRes qw(gettimeofday);
use PBot::RegistryCommands;
sub initialize {
my ($self, %conf) = @_;
my $filename = $conf{filename} // Carp::croak("Missing filename reference in " . __FILE__);
$self->{registry} = PBot::DualIndexHashObject->new(name => 'Registry', filename => $filename, pbot => $self->{pbot});
$self->{triggers} = {};
$self->{pbot}->{atexit}->register(sub { $self->save; return; });
PBot::RegistryCommands->new(pbot => $self->{pbot});
my ($self, %conf) = @_;
my $filename = $conf{filename} // Carp::croak("Missing filename reference in " . __FILE__);
$self->{registry} = PBot::DualIndexHashObject->new(name => 'Registry', filename => $filename, pbot => $self->{pbot});
$self->{triggers} = {};
$self->{pbot}->{atexit}->register(sub { $self->save; return; });
PBot::RegistryCommands->new(pbot => $self->{pbot});
}
sub load {
my $self = shift;
$self->{registry}->load;
foreach my $section ($self->{registry}->get_keys) {
foreach my $item ($self->{registry}->get_keys($section)) {
$self->process_trigger($section, $item, $self->{registry}->get_data($section, $item, 'value'));
my $self = shift;
$self->{registry}->load;
foreach my $section ($self->{registry}->get_keys) {
foreach my $item ($self->{registry}->get_keys($section)) { $self->process_trigger($section, $item, $self->{registry}->get_data($section, $item, 'value')); }
}
}
}
sub save {
my $self = shift;
$self->{registry}->save;
my $self = shift;
$self->{registry}->save;
}
sub add_default {
my ($self, $type, $section, $item, $value) = @_;
$self->add($type, $section, $item, $value, 1);
my ($self, $type, $section, $item, $value) = @_;
$self->add($type, $section, $item, $value, 1);
}
sub add {
my $self = shift;
my ($type, $section, $item, $value, $is_default) = @_;
$type = lc $type;
my $self = shift;
my ($type, $section, $item, $value, $is_default) = @_;
$type = lc $type;
if ($is_default) {
return if $self->{registry}->exists($section, $item);
}
if ($is_default) { return if $self->{registry}->exists($section, $item); }
if (not $self->{registry}->exists($section, $item)) {
my $data = {
value => $value,
type => $type,
};
$self->{registry}->add($section, $item, $data, 1);
} else {
$self->{registry}->set($section, $item, 'value', $value, 1);
$self->{registry}->set($section, $item, 'type', $type, 1) unless $self->{registry}->exists($section, $item, 'type');
}
$self->process_trigger($section, $item, $value) unless $is_default;
$self->save unless $is_default;
if (not $self->{registry}->exists($section, $item)) {
my $data = {
value => $value,
type => $type,
};
$self->{registry}->add($section, $item, $data, 1);
} else {
$self->{registry}->set($section, $item, 'value', $value, 1);
$self->{registry}->set($section, $item, 'type', $type, 1) unless $self->{registry}->exists($section, $item, 'type');
}
$self->process_trigger($section, $item, $value) unless $is_default;
$self->save unless $is_default;
}
sub remove {
my $self = shift;
my ($section, $item) = @_;
$self->{registry}->remove($section, $item);
my $self = shift;
my ($section, $item) = @_;
$self->{registry}->remove($section, $item);
}
sub set_default {
my ($self, $section, $item, $key, $value) = @_;
$self->set($section, $item, $key, $value, 1);
my ($self, $section, $item, $key, $value) = @_;
$self->set($section, $item, $key, $value, 1);
}
sub set {
my ($self, $section, $item, $key, $value, $is_default, $dont_save) = @_;
$key = lc $key if defined $key;
my ($self, $section, $item, $key, $value, $is_default, $dont_save) = @_;
$key = lc $key if defined $key;
if ($is_default) {
return if $self->{registry}->exists($section, $item, $key);
}
if ($is_default) { return if $self->{registry}->exists($section, $item, $key); }
my $oldvalue = $self->get_value($section, $item, 1) if defined $value;
$oldvalue = '' if not defined $oldvalue;
my $oldvalue = $self->get_value($section, $item, 1) if defined $value;
$oldvalue = '' if not defined $oldvalue;
my $result = $self->{registry}->set($section, $item, $key, $value, 1);
my $result = $self->{registry}->set($section, $item, $key, $value, 1);
if (defined $key and $key eq 'value' and defined $value and $oldvalue ne $value) {
$self->process_trigger($section, $item, $value);
}
if (defined $key and $key eq 'value' and defined $value and $oldvalue ne $value) { $self->process_trigger($section, $item, $value); }
$self->save if !$dont_save && $result =~ m/set to/ && not $is_default;
return $result;
$self->save if !$dont_save && $result =~ m/set to/ && not $is_default;
return $result;
}
sub unset {
my ($self, $section, $item, $key) = @_;
$key = lc $key;
return $self->{registry}->unset($section, $item, $key);
my ($self, $section, $item, $key) = @_;
$key = lc $key;
return $self->{registry}->unset($section, $item, $key);
}
sub get_value {
my ($self, $section, $item, $as_text, $stuff) = @_;
$section = lc $section;
$item = lc $item;
my $key = $item;
my ($self, $section, $item, $as_text, $stuff) = @_;
$section = lc $section;
$item = lc $item;
my $key = $item;
# TODO: use user-metadata for this
if (defined $stuff and exists $stuff->{nick}) {
my $stuff_nick = lc $stuff->{nick};
if ($self->{registry}->exists($section, "$item.nick.$stuff_nick")) {
$key = "$item.nick.$stuff_nick";
# TODO: use user-metadata for this
if (defined $stuff and exists $stuff->{nick}) {
my $stuff_nick = lc $stuff->{nick};
if ($self->{registry}->exists($section, "$item.nick.$stuff_nick")) { $key = "$item.nick.$stuff_nick"; }
}
}
if ($self->{registry}->exists($section, $key)) {
if (not $as_text and $self->{registry}->get_data($section, $key, 'type') eq 'array') {
return split /\s*,\s*/, $self->{registry}->get_data($section, $key, 'value');
} else {
return $self->{registry}->get_data($section, $key, 'value');
if ($self->{registry}->exists($section, $key)) {
if (not $as_text and $self->{registry}->get_data($section, $key, 'type') eq 'array') { return split /\s*,\s*/, $self->{registry}->get_data($section, $key, 'value'); }
else { return $self->{registry}->get_data($section, $key, 'value'); }
}
}
return undef;
return undef;
}
sub get_array_value {
my ($self, $section, $item, $index, $stuff) = @_;
$section = lc $section;
$item = lc $item;
my $key = $item;
my ($self, $section, $item, $index, $stuff) = @_;
$section = lc $section;
$item = lc $item;
my $key = $item;
# TODO: use user-metadata for this
if (defined $stuff and exists $stuff->{nick}) {
my $stuff_nick = lc $stuff->{nick};
if ($self->{registry}->exists($section, "$item.nick.$stuff_nick")) {
$key = "$item.nick.$stuff_nick";
# TODO: use user-metadata for this
if (defined $stuff and exists $stuff->{nick}) {
my $stuff_nick = lc $stuff->{nick};
if ($self->{registry}->exists($section, "$item.nick.$stuff_nick")) { $key = "$item.nick.$stuff_nick"; }
}
}
if ($self->{registry}->exists($section, $key)) {
if ($self->{registry}->get_data($section, $key, 'type') eq 'array') {
my @array = split /\s*,\s*/, $self->{registry}->get_data($section, $key, 'value');
return $array[$index >= $#array ? $#array : $index];
} else {
return $self->{registry}->get_data($section, $key, 'value');
if ($self->{registry}->exists($section, $key)) {
if ($self->{registry}->get_data($section, $key, 'type') eq 'array') {
my @array = split /\s*,\s*/, $self->{registry}->get_data($section, $key, 'value');
return $array[$index >= $#array ? $#array : $index];
} else {
return $self->{registry}->get_data($section, $key, 'value');
}
}
}
return undef;
return undef;
}
sub add_trigger {
my ($self, $section, $item, $subref) = @_;
$self->{triggers}->{lc $section}->{lc $item} = $subref;
my ($self, $section, $item, $subref) = @_;
$self->{triggers}->{lc $section}->{lc $item} = $subref;
}
sub process_trigger {
my $self = shift;
my ($section, $item) = @_;
$section = lc $section;
$item = lc $item;
if (exists $self->{triggers}->{$section} and exists $self->{triggers}->{$section}->{$item}) {
return &{ $self->{triggers}->{$section}->{$item} }(@_);
}
return undef;
my $self = shift;
my ($section, $item) = @_;
$section = lc $section;
$item = lc $item;
if (exists $self->{triggers}->{$section} and exists $self->{triggers}->{$section}->{$item}) { return &{$self->{triggers}->{$section}->{$item}}(@_); }
return undef;
}
1;

View File

@ -14,283 +14,249 @@ use warnings; use strict;
use feature 'unicode_strings';
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->regset(@_) }, "regset", 1);
$self->{pbot}->{commands}->register(sub { $self->regunset(@_) }, "regunset", 1);
$self->{pbot}->{commands}->register(sub { $self->regshow(@_) }, "regshow", 0);
$self->{pbot}->{commands}->register(sub { $self->regsetmeta(@_) }, "regsetmeta", 1);
$self->{pbot}->{commands}->register(sub { $self->regunsetmeta(@_) }, "regunsetmeta", 1);
$self->{pbot}->{commands}->register(sub { $self->regchange(@_) }, "regchange", 1);
$self->{pbot}->{commands}->register(sub { $self->regfind(@_) }, "regfind", 0);
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->regset(@_) }, "regset", 1);
$self->{pbot}->{commands}->register(sub { $self->regunset(@_) }, "regunset", 1);
$self->{pbot}->{commands}->register(sub { $self->regshow(@_) }, "regshow", 0);
$self->{pbot}->{commands}->register(sub { $self->regsetmeta(@_) }, "regsetmeta", 1);
$self->{pbot}->{commands}->register(sub { $self->regunsetmeta(@_) }, "regunsetmeta", 1);
$self->{pbot}->{commands}->register(sub { $self->regchange(@_) }, "regchange", 1);
$self->{pbot}->{commands}->register(sub { $self->regfind(@_) }, "regfind", 0);
}
sub regset {
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "Usage: regset <section>.<item> [value]";
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "Usage: regset <section>.<item> [value]";
# support "<section>.<key>" syntax in addition to "<section> <key>"
my $section = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}) // return $usage;
my ($item, $value);
if ($section =~ m/^(.+?)\.(.+)$/) {
($section, $item) = ($1, $2);
($value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 1);
} else {
($item, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
}
# support "<section>.<key>" syntax in addition to "<section> <key>"
my $section = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}) // return $usage;
my ($item, $value);
if ($section =~ m/^(.+?)\.(.+)$/) {
($section, $item) = ($1, $2);
($value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 1);
} else {
($item, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
}
if (not defined $section or not defined $item) {
return $usage;
}
if (not defined $section or not defined $item) { return $usage; }
if (defined $value) {
$self->{pbot}->{registry}->add('text', $section, $item, $value);
} else {
return $self->{pbot}->{registry}->set($section, $item, 'value');
}
if (defined $value) { $self->{pbot}->{registry}->add('text', $section, $item, $value); }
else { return $self->{pbot}->{registry}->set($section, $item, 'value'); }
$self->{pbot}->{logger}->log("$nick!$user\@$host set registry entry [$section] $item => $value\n");
return "$section.$item set to $value";
$self->{pbot}->{logger}->log("$nick!$user\@$host set registry entry [$section] $item => $value\n");
return "$section.$item set to $value";
}
sub regunset {
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "Usage: regunset <section>.<item>";
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "Usage: regunset <section>.<item>";
# support "<section>.<key>" syntax in addition to "<section> <key>"
my $section = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}) // return $usage;
my $item;
if ($section =~ m/^(.+?)\.(.+)$/) {
($section, $item) = ($1, $2);
} else {
($item) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 1);
}
# support "<section>.<key>" syntax in addition to "<section> <key>"
my $section = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}) // return $usage;
my $item;
if ($section =~ m/^(.+?)\.(.+)$/) { ($section, $item) = ($1, $2); }
else { ($item) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 1); }
if (not defined $section or not defined $item) {
return $usage;
}
if (not defined $section or not defined $item) { return $usage; }
if (not $self->{pbot}->{registry}->{registry}->exits($section)) {
return "No such registry section $section.";
}
if (not $self->{pbot}->{registry}->{registry}->exits($section)) { return "No such registry section $section."; }
if (not $self->{pbot}->{registry}->{registry}->exists($section, $item)) {
return "No such item $item in section $section.";
}
if (not $self->{pbot}->{registry}->{registry}->exists($section, $item)) { return "No such item $item in section $section."; }
$self->{pbot}->{logger}->log("$nick!$user\@$host removed registry entry $section.$item\n");
$self->{pbot}->{registry}->remove($section, $item);
return "$section.$item deleted from registry";
$self->{pbot}->{logger}->log("$nick!$user\@$host removed registry entry $section.$item\n");
$self->{pbot}->{registry}->remove($section, $item);
return "$section.$item deleted from registry";
}
sub regsetmeta {
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "Usage: regsetmeta <section>.<item> [key [value]]";
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "Usage: regsetmeta <section>.<item> [key [value]]";
# support "<section>.<key>" syntax in addition to "<section> <key>"
my $section = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}) // return $usage;
my ($item, $key, $value);
if ($section =~ m/^(.+?)\.(.+)$/) {
($section, $item) = ($1, $2);
($key, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
} else {
($item, $key, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 3);
}
# support "<section>.<key>" syntax in addition to "<section> <key>"
my $section = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}) // return $usage;
my ($item, $key, $value);
if ($section =~ m/^(.+?)\.(.+)$/) {
($section, $item) = ($1, $2);
($key, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
} else {
($item, $key, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 3);
}
if (not defined $section or not defined $item) {
return $usage;
}
if (not defined $section or not defined $item) { return $usage; }
$key = undef if not length $key;
$value = undef if not length $value;
return $self->{pbot}->{registry}->set($section, $item, $key, $value);
$key = undef if not length $key;
$value = undef if not length $value;
return $self->{pbot}->{registry}->set($section, $item, $key, $value);
}
sub regunsetmeta {
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "Usage: regunsetmeta <section>.<item> <key>";
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "Usage: regunsetmeta <section>.<item> <key>";
# support "<section>.<key>" syntax in addition to "<section> <key>"
my $section = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}) // return $usage;
my ($item, $key);
if ($section =~ m/^(.+?)\.(.+)$/) {
($section, $item) = ($1, $2);
($key) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 1);
} else {
($item, $key) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
}
# support "<section>.<key>" syntax in addition to "<section> <key>"
my $section = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}) // return $usage;
my ($item, $key);
if ($section =~ m/^(.+?)\.(.+)$/) {
($section, $item) = ($1, $2);
($key) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 1);
} else {
($item, $key) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
}
if (not defined $section or not defined $item or not defined $key) {
return $usage;
}
return $self->{pbot}->{registry}->unset($section, $item, $key);
if (not defined $section or not defined $item or not defined $key) { return $usage; }
return $self->{pbot}->{registry}->unset($section, $item, $key);
}
sub regshow {
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my $registry = $self->{pbot}->{registry}->{registry};
my $usage = "Usage: regshow <section>.<item>";
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my $registry = $self->{pbot}->{registry}->{registry};
my $usage = "Usage: regshow <section>.<item>";
# support "<section>.<key>" syntax in addition to "<section> <key>"
my $section = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}) // return $usage;
my $item;
if ($section =~ m/^(.+?)\.(.+)$/) {
($section, $item) = ($1, $2);
} else {
($item) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 1);
}
# support "<section>.<key>" syntax in addition to "<section> <key>"
my $section = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}) // return $usage;
my $item;
if ($section =~ m/^(.+?)\.(.+)$/) { ($section, $item) = ($1, $2); }
else { ($item) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 1); }
if (not defined $section or not defined $item) {
return $usage;
}
if (not defined $section or not defined $item) { return $usage; }
if (not $registry->exists($section)) {
return "No such registry section $section.";
}
if (not $registry->exists($section)) { return "No such registry section $section."; }
if (not $registry->exists($section, $item)) {
return "No such registry item $item in section $section.";
}
if (not $registry->exists($section, $item)) { return "No such registry item $item in section $section."; }
if ($registry->get_data($section, $item, 'private')) {
return "$section.$item: <private>";
}
if ($registry->get_data($section, $item, 'private')) { return "$section.$item: <private>"; }
my $result = "$section.$item: " . $registry->get_data($section, $item, 'value');
my $result = "$section.$item: " . $registry->get_data($section, $item, 'value');
if ($registry->get_data($section, $item, 'type') eq 'array') {
$result .= ' [array]';
}
return $result;
if ($registry->get_data($section, $item, 'type') eq 'array') { $result .= ' [array]'; }
return $result;
}
sub regfind {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $registry = $self->{pbot}->{registry}->{registry};
my $usage = "Usage: regfind [-showvalues] [-section section] <regex>";
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $registry = $self->{pbot}->{registry}->{registry};
my $usage = "Usage: regfind [-showvalues] [-section section] <regex>";
return $usage if not defined $arguments;
return $usage if not defined $arguments;
my ($section, $showvalues);
$section = $1 if $arguments =~ s/-section\s+([^\b\s]+)//i;
$showvalues = 1 if $arguments =~ s/-showvalues?//i;
my ($section, $showvalues);
$section = $1 if $arguments =~ s/-section\s+([^\b\s]+)//i;
$showvalues = 1 if $arguments =~ s/-showvalues?//i;
$arguments =~ s/^\s+//;
$arguments =~ s/\s+$//;
$arguments =~ s/\s+/ /g;
$arguments =~ s/^\s+//;
$arguments =~ s/\s+$//;
$arguments =~ s/\s+/ /g;
return $usage if $arguments eq "";
return $usage if $arguments eq "";
$section = lc $section if defined $section;;
$section = lc $section if defined $section;
my ($text, $last_item, $last_section, $i);
$last_section = "";
$i = 0;
eval {
use re::engine::RE2 -strict => 1;
foreach my $section_key (sort $registry->get_keys) {
next if defined $section and $section_key ne $section;
foreach my $item_key (sort $registry->get_keys($section_key)) {
next if $item_key eq '_name';
if ($registry->get_data($section_key, $item_key, 'private')) {
# do not match on value if private
next if $item_key !~ /$arguments/i;
} else {
next if $registry->get_data($section_key, $item_key, 'value') !~ /$arguments/i and $item_key !~ /$arguments/i;
my ($text, $last_item, $last_section, $i);
$last_section = "";
$i = 0;
eval {
use re::engine::RE2 -strict => 1;
foreach my $section_key (sort $registry->get_keys) {
next if defined $section and $section_key ne $section;
foreach my $item_key (sort $registry->get_keys($section_key)) {
next if $item_key eq '_name';
if ($registry->get_data($section_key, $item_key, 'private')) {
# do not match on value if private
next if $item_key !~ /$arguments/i;
} else {
next if $registry->get_data($section_key, $item_key, 'value') !~ /$arguments/i and $item_key !~ /$arguments/i;
}
$i++;
if ($section_key ne $last_section) {
$text .= "[$section_key]\n";
$last_section = $section_key;
}
if ($showvalues) {
if ($registry->get_data($section_key, $item_key, 'private')) { $text .= " $item_key = <private>\n"; }
else {
$text .=
" $item_key = " . $registry->get_data($section_key, $item_key, 'value') . ($registry->get_data($section_key, $item_key, 'type') eq 'array' ? " [array]\n" : "\n");
}
} else {
$text .= " $item_key\n";
}
$last_item = $item_key;
}
}
};
$i++;
return "/msg $nick $arguments: $@" if $@;
if ($section_key ne $last_section) {
$text .= "[$section_key]\n";
$last_section = $section_key;
if ($i == 1) {
chop $text;
if ($registry->get_data($last_section, $last_item, 'private')) { return "Found one registry entry: [$last_section] $last_item: <private>"; }
else {
return
"Found one registry entry: [$last_section] $last_item: "
. $registry->get_data($last_section, $last_item, 'value')
. ($registry->get_data($last_section, $last_item, 'type') eq 'array' ? ' [array]' : '');
}
if ($showvalues) {
if ($registry->get_data($section_key, $item_key, 'private')) {
$text .= " $item_key = <private>\n";
} else {
$text .= " $item_key = " . $registry->get_data($section_key, $item_key, 'value') . ($registry->get_data($section_key, $item_key, 'type') eq 'array' ? " [array]\n" : "\n");
}
} else {
$text .= " $item_key\n";
}
$last_item = $item_key;
}
}
};
return "/msg $nick $arguments: $@" if $@;
if ($i == 1) {
chop $text;
if ($registry->get_data($last_section, $last_item, 'private')) {
return "Found one registry entry: [$last_section] $last_item: <private>";
} else {
return "Found one registry entry: [$last_section] $last_item: " . $registry->get_data($last_section, $last_item, 'value') . ($registry->get_data($last_section, $last_item, 'type') eq 'array' ? ' [array]' : '');
}
} else {
return "Found $i registry entries:\n$text" unless $i == 0;
return "Found $i registry entries:\n$text" unless $i == 0;
my $sections = (defined $section ? "section $section" : 'any sections');
return "No matching registry entries found in $sections.";
}
my $sections = (defined $section ? "section $section" : 'any sections');
return "No matching registry entries found in $sections.";
}
}
sub regchange {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my ($section, $item, $delim, $tochange, $changeto, $modifier);
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my ($section, $item, $delim, $tochange, $changeto, $modifier);
if (defined $arguments) {
if ($arguments =~ /^(.+?)\.([^\s]+)\s+s(.)/ or $arguments =~ /^([^\s]+) ([^\s]+)\s+s(.)/) {
$section = $1;
$item = $2;
$delim = $3;
if (defined $arguments) {
if ($arguments =~ /^(.+?)\.([^\s]+)\s+s(.)/ or $arguments =~ /^([^\s]+) ([^\s]+)\s+s(.)/) {
$section = $1;
$item = $2;
$delim = $3;
}
if ($arguments =~ /$delim(.*?)$delim(.*)$delim(.*)?$/) {
$tochange = $1;
$changeto = $2;
$modifier = $3;
}
}
if ($arguments =~ /$delim(.*?)$delim(.*)$delim(.*)?$/) {
$tochange = $1;
$changeto = $2;
$modifier = $3;
}
}
if (not defined $section or not defined $item or not defined $changeto) { return "Usage: regchange <section>.<item> s/<pattern>/<replacement>/"; }
if (not defined $section or not defined $item or not defined $changeto) {
return "Usage: regchange <section>.<item> s/<pattern>/<replacement>/";
}
$section = lc $section;
$item = lc $item;
$section = lc $section;
$item = lc $item;
my $registry = $self->{pbot}->{registry}->{registry};
my $registry = $self->{pbot}->{registry}->{registry};
if (not $registry->exists($section)) { return "No such registry section $section."; }
if (not $registry->exists($section)) {
return "No such registry section $section.";
}
if (not $registry->exists($section, $item)) { return "No such registry item $item in section $section."; }
if (not $registry->exists($section, $item)) {
return "No such registry item $item in section $section.";
}
my $ret = eval {
use re::engine::RE2 -strict => 1;
if (not $registry->get_data($section, $item, 'value') =~ s|$tochange|$changeto|) {
$self->{pbot}->{logger}->log("($from) $nick!$user\@$host: failed to change $section.$item 's$delim$tochange$delim$changeto$delim$modifier\n");
return "/msg $nick Change $section.$item failed.";
} else {
$self->{pbot}->{logger}->log("($from) $nick!$user\@$host: changed $section.$item 's/$tochange/$changeto/\n");
$self->{pbot}->{registry}->process_trigger($section, $item, 'value', $registry->get_data($section, $item, 'value'));
$self->{pbot}->{registry}->save;
return "$section.$item set to " . $registry->get_data($section, $item, 'value');
}
};
return "/msg $nick Failed to change $section.$item: $@" if $@;
return $ret;
my $ret = eval {
use re::engine::RE2 -strict => 1;
if (not $registry->get_data($section, $item, 'value') =~ s|$tochange|$changeto|) {
$self->{pbot}->{logger}->log("($from) $nick!$user\@$host: failed to change $section.$item 's$delim$tochange$delim$changeto$delim$modifier\n");
return "/msg $nick Change $section.$item failed.";
} else {
$self->{pbot}->{logger}->log("($from) $nick!$user\@$host: changed $section.$item 's/$tochange/$changeto/\n");
$self->{pbot}->{registry}->process_trigger($section, $item, 'value', $registry->get_data($section, $item, 'value'));
$self->{pbot}->{registry}->save;
return "$section.$item set to " . $registry->get_data($section, $item, 'value');
}
};
return "/msg $nick Failed to change $section.$item: $@" if $@;
return $ret;
}
1;

View File

@ -16,42 +16,41 @@ use feature 'unicode_strings';
use Time::HiRes qw(gettimeofday);
sub new {
my ($class, %conf) = @_;
my $self = {};
$self->{buf} = '';
$self->{timestamp} = gettimeofday;
return bless $self, $class;
my ($class, %conf) = @_;
my $self = {};
$self->{buf} = '';
$self->{timestamp} = gettimeofday;
return bless $self, $class;
}
sub log {
my $self = shift;
$self->{buf} .= shift;
# DBI feeds us pieces at a time, so accumulate a complete line
# before outputing
if ($self->{buf} =~ tr/\n//) {
$self->log_message;
$self->{buf} = '';
}
my $self = shift;
$self->{buf} .= shift;
# DBI feeds us pieces at a time, so accumulate a complete line
# before outputing
if ($self->{buf} =~ tr/\n//) {
$self->log_message;
$self->{buf} = '';
}
}
sub log_message {
my $self = shift;
my $now = gettimeofday;
my $elapsed = $now - $self->{timestamp};
if ($elapsed >= 0.100) {
$self->{pbot}->{logger}->log("^^^ SLOW SQL ^^^\n");
}
$elapsed = sprintf '%10.3f', $elapsed;
$self->{pbot}->{logger}->log("$elapsed : $self->{buf}");
$self->{timestamp} = $now;
my $self = shift;
my $now = gettimeofday;
my $elapsed = $now - $self->{timestamp};
if ($elapsed >= 0.100) { $self->{pbot}->{logger}->log("^^^ SLOW SQL ^^^\n"); }
$elapsed = sprintf '%10.3f', $elapsed;
$self->{pbot}->{logger}->log("$elapsed : $self->{buf}");
$self->{timestamp} = $now;
}
sub close {
my $self = shift;
if ($self->{buf}) {
$self->log_message;
$self->{buf} = '';
}
my $self = shift;
if ($self->{buf}) {
$self->log_message;
$self->{buf} = '';
}
}
1;

View File

@ -15,28 +15,29 @@ use warnings;
use feature 'unicode_strings';
sub PUSHED {
my ($class, $mode, $fh) = @_;
my $logger;
return bless \$logger, $class;
my ($class, $mode, $fh) = @_;
my $logger;
return bless \$logger, $class;
}
sub OPEN {
my ($self, $path, $mode, $fh) = @_;
# $path is our logger object
$$self = $path;
return 1;
my ($self, $path, $mode, $fh) = @_;
# $path is our logger object
$$self = $path;
return 1;
}
sub WRITE {
my ($self, $buf, $fh) = @_;
$$self->log($buf);
return length($buf);
my ($self, $buf, $fh) = @_;
$$self->log($buf);
return length($buf);
}
sub CLOSE {
my $self = shift;
$$self->close();
return 0;
my $self = shift;
$$self->close();
return 0;
}
1;

View File

@ -11,58 +11,55 @@ use feature 'unicode_strings';
use IO::Select;
sub initialize {
my ($self, %conf) = @_;
$self->{select} = IO::Select->new();
$self->{readers} = {};
$self->{buffers} = {};
my ($self, %conf) = @_;
$self->{select} = IO::Select->new();
$self->{readers} = {};
$self->{buffers} = {};
}
sub add_reader {
my ($self, $handle, $sub) = @_;
$self->{select}->add($handle);
$self->{readers}->{$handle} = $sub;
$self->{buffers}->{$handle} = "";
my ($self, $handle, $sub) = @_;
$self->{select}->add($handle);
$self->{readers}->{$handle} = $sub;
$self->{buffers}->{$handle} = "";
}
sub remove_reader {
my ($self, $handle) = @_;
$self->{select}->remove($handle);
delete $self->{readers}->{$handle};
delete $self->{buffers}->{$handle};
my ($self, $handle) = @_;
$self->{select}->remove($handle);
delete $self->{readers}->{$handle};
delete $self->{buffers}->{$handle};
}
sub do_select {
my ($self) = @_;
my $length = 8192;
my @ready = $self->{select}->can_read(0);
foreach my $fh (@ready) {
my $ret = sysread($fh, my $buf, $length);
my ($self) = @_;
my $length = 8192;
my @ready = $self->{select}->can_read(0);
foreach my $fh (@ready) {
my $ret = sysread($fh, my $buf, $length);
if (not defined $ret) {
$self->{pbot}->{logger}->log("Error with $fh: $!\n");
$self->remove_reader($fh);
next;
if (not defined $ret) {
$self->{pbot}->{logger}->log("Error with $fh: $!\n");
$self->remove_reader($fh);
next;
}
if ($ret == 0) {
if (length $self->{buffers}->{$fh}) { $self->{readers}->{$fh}->($self->{buffers}->{$fh}); }
$self->remove_reader($fh);
next;
}
$self->{buffers}->{$fh} .= $buf;
if (not exists $self->{readers}->{$fh}) { $self->{pbot}->{logger}->log("Error: no reader for $fh\n"); }
else {
if ($ret < $length) {
$self->{readers}->{$fh}->($self->{buffers}->{$fh});
$self->{buffers}->{$fh} = "";
}
}
}
if ($ret == 0) {
if (length $self->{buffers}->{$fh}) {
$self->{readers}->{$fh}->($self->{buffers}->{$fh});
}
$self->remove_reader($fh);
next;
}
$self->{buffers}->{$fh} .= $buf;
if (not exists $self->{readers}->{$fh}) {
$self->{pbot}->{logger}->log("Error: no reader for $fh\n");
} else {
if ($ret < $length) {
$self->{readers}->{$fh}->($self->{buffers}->{$fh});
$self->{buffers}->{$fh} = "";
}
}
}
}
1;

View File

@ -8,51 +8,52 @@ use parent 'PBot::Class';
use warnings; use strict;
use feature 'unicode_strings';
use POSIX qw(tcgetpgrp getpgrp); # to check whether process is in background or foreground
use POSIX qw(tcgetpgrp getpgrp); # to check whether process is in background or foreground
sub initialize {
my ($self, %conf) = @_;
# create implicit bot-admin account for bot
my $user = $self->{pbot}->{users}->find_user('.*', '*!stdin@pbot');
if (not defined $user or not $self->{pbot}->{capabilities}->userhas($user, 'botowner')) {
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
$self->{pbot}->{logger}->log("Adding stdin botowner *!stdin\@pbot...\n");
$self->{pbot}->{users}->add_user($botnick, '.*', '*!stdin@pbot', 'botowner', undef, 1);
$self->{pbot}->{users}->login($botnick, "$botnick!stdin\@pbot", undef);
$self->{pbot}->{users}->save;
}
my ($self, %conf) = @_;
# used to check whether process is in background or foreground, for stdin reading
if (not $self->{pbot}->{registry}->get_value('general', 'daemon')) {
open TTY, "</dev/tty" or die $!;
$self->{tty_fd} = fileno(TTY);
$self->{pbot}->{select_handler}->add_reader(\*STDIN, sub { $self->stdin_reader(@_) });
} else {
$self->{pbot}->{logger}->log("Starting in daemon mode.\n");
}
# create implicit bot-admin account for bot
my $user = $self->{pbot}->{users}->find_user('.*', '*!stdin@pbot');
if (not defined $user or not $self->{pbot}->{capabilities}->userhas($user, 'botowner')) {
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
$self->{pbot}->{logger}->log("Adding stdin botowner *!stdin\@pbot...\n");
$self->{pbot}->{users}->add_user($botnick, '.*', '*!stdin@pbot', 'botowner', undef, 1);
$self->{pbot}->{users}->login($botnick, "$botnick!stdin\@pbot", undef);
$self->{pbot}->{users}->save;
}
# used to check whether process is in background or foreground, for stdin reading
if (not $self->{pbot}->{registry}->get_value('general', 'daemon')) {
open TTY, "</dev/tty" or die $!;
$self->{tty_fd} = fileno(TTY);
$self->{pbot}->{select_handler}->add_reader(\*STDIN, sub { $self->stdin_reader(@_) });
} else {
$self->{pbot}->{logger}->log("Starting in daemon mode.\n");
}
}
sub stdin_reader {
my ($self, $input) = @_;
chomp $input;
my ($self, $input) = @_;
chomp $input;
# make sure we're in the foreground first
$self->{foreground} = (tcgetpgrp($self->{tty_fd}) == getpgrp()) ? 1 : 0;
return if not $self->{foreground};
# make sure we're in the foreground first
$self->{foreground} = (tcgetpgrp($self->{tty_fd}) == getpgrp()) ? 1 : 0;
return if not $self->{foreground};
$self->{pbot}->{logger}->log("---------------------------------------------\n");
$self->{pbot}->{logger}->log("Got STDIN: $input\n");
$self->{pbot}->{logger}->log("---------------------------------------------\n");
$self->{pbot}->{logger}->log("Got STDIN: $input\n");
my ($from, $text);
my ($from, $text);
if ($input =~ m/^~([^ ]+)\s+(.*)/) {
$from = $1;
$text = $self->{pbot}->{registry}->get_value('irc', 'botnick') . " $2";
} else {
$from = 'stdin@pbot';
$text = $self->{pbot}->{registry}->get_value('irc', 'botnick') . " $input";
}
return $self->{pbot}->{interpreter}->process_line($from, $self->{pbot}->{registry}->get_value('irc', 'botnick'), "stdin", "pbot", $text);
if ($input =~ m/^~([^ ]+)\s+(.*)/) {
$from = $1;
$text = $self->{pbot}->{registry}->get_value('irc', 'botnick') . " $2";
} else {
$from = 'stdin@pbot';
$text = $self->{pbot}->{registry}->get_value('irc', 'botnick') . " $input";
}
return $self->{pbot}->{interpreter}->process_line($from, $self->{pbot}->{registry}->get_value('irc', 'botnick'), "stdin", "pbot", $text);
}
1;

View File

@ -10,6 +10,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Timer;
use parent 'PBot::Class';
use warnings; use strict;
@ -17,135 +18,134 @@ use feature 'unicode_strings';
our $min_timeout = 1;
our $max_seconds = 1000000;
our $seconds = 0;
our $seconds = 0;
our @timer_funcs;
$SIG{ALRM} = sub {
$seconds += $min_timeout;
alarm $min_timeout;
$seconds += $min_timeout;
alarm $min_timeout;
# call timer func subroutines
foreach my $func (@timer_funcs) { &$func; }
# call timer func subroutines
foreach my $func (@timer_funcs) { &$func; }
# prevent $seconds over-flow
$seconds -= $max_seconds if $seconds > $max_seconds;
# prevent $seconds over-flow
$seconds -= $max_seconds if $seconds > $max_seconds;
};
sub initialize {
my ($self, %conf) = @_;
my $timeout = $conf{timeout} // 10;
$min_timeout = $timeout if $timeout < $min_timeout;
$self->{name} = $conf{name} // "Unnamed $timeout Second Timer";
$self->{handlers} = [];
$self->{enabled} = 0;
# alarm signal handler (poor-man's timer)
$self->{timer_func} = sub { on_tick_handler($self) };
return $self;
my ($self, %conf) = @_;
my $timeout = $conf{timeout} // 10;
$min_timeout = $timeout if $timeout < $min_timeout;
$self->{name} = $conf{name} // "Unnamed $timeout Second Timer";
$self->{handlers} = [];
$self->{enabled} = 0;
# alarm signal handler (poor-man's timer)
$self->{timer_func} = sub { on_tick_handler($self) };
return $self;
}
sub start {
my $self = shift;
$self->{enabled} = 1;
push @timer_funcs, $self->{timer_func};
alarm $min_timeout;
my $self = shift;
$self->{enabled} = 1;
push @timer_funcs, $self->{timer_func};
alarm $min_timeout;
}
sub stop {
my $self = shift;
$self->{enabled} = 0;
@timer_funcs = grep { $_ != $self->{timer_func} } @timer_funcs;
my $self = shift;
$self->{enabled} = 0;
@timer_funcs = grep { $_ != $self->{timer_func} } @timer_funcs;
}
sub on_tick_handler {
my $self = shift;
my $elapsed = 0;
my $self = shift;
my $elapsed = 0;
if ($self->{enabled}) {
if ($#{ $self->{handlers} } > -1) {
# call handlers supplied via register() if timeout for each has elapsed
foreach my $func (@{ $self->{handlers} }) {
if (defined $func->{last}) {
$func->{last} -= $max_seconds if $seconds < $func->{last}; # handle wrap-around of $seconds
if ($self->{enabled}) {
if ($#{$self->{handlers}} > -1) {
if ($seconds - $func->{last} >= $func->{timeout}) {
$func->{last} = $seconds;
$elapsed = 1;
}
# call handlers supplied via register() if timeout for each has elapsed
foreach my $func (@{$self->{handlers}}) {
if (defined $func->{last}) {
$func->{last} -= $max_seconds if $seconds < $func->{last}; # handle wrap-around of $seconds
if ($seconds - $func->{last} >= $func->{timeout}) {
$func->{last} = $seconds;
$elapsed = 1;
}
} else {
$func->{last} = $seconds;
$elapsed = 1;
}
if ($elapsed) {
&{$func->{subref}}($self);
$elapsed = 0;
}
}
} else {
$func->{last} = $seconds;
$elapsed = 1;
}
if ($elapsed) {
&{ $func->{subref} }($self);
$elapsed = 0;
}
}
} else {
# call default overridable handler if timeout has elapsed
if (defined $self->{last}) {
$self->{last} -= $max_seconds if $seconds < $self->{last}; # handle wrap-around
# call default overridable handler if timeout has elapsed
if (defined $self->{last}) {
$self->{last} -= $max_seconds if $seconds < $self->{last}; # handle wrap-around
if ($seconds - $self->{last} >= $self->{timeout}) {
$elapsed = 1;
$self->{last} = $seconds;
}
} else {
$elapsed = 1;
$self->{last} = $seconds;
}
if ($seconds - $self->{last} >= $self->{timeout}) {
$elapsed = 1;
$self->{last} = $seconds;
}
} else {
$elapsed = 1;
$self->{last} = $seconds;
}
if ($elapsed) {
$self->on_tick();
$elapsed = 0;
}
if ($elapsed) {
$self->on_tick();
$elapsed = 0;
}
}
}
}
}
# overridable method, executed whenever timeout is triggered
sub on_tick {
my $self = shift;
print "Tick! $self->{name} $self->{timeout} $self->{last} $seconds\n";
my $self = shift;
print "Tick! $self->{name} $self->{timeout} $self->{last} $seconds\n";
}
sub register {
my $self = shift;
my ($ref, $timeout, $id) = @_;
my $self = shift;
my ($ref, $timeout, $id) = @_;
Carp::croak("Must pass subroutine reference to register()") if not defined $ref;
Carp::croak("Must pass subroutine reference to register()") if not defined $ref;
# TODO: Check if subref already exists in handlers?
$timeout = 300 if not defined $timeout; # set default value of 5 minutes if not defined
$id = 'timer' if not defined $id;
# TODO: Check if subref already exists in handlers?
$timeout = 300 if not defined $timeout; # set default value of 5 minutes if not defined
$id = 'timer' if not defined $id;
my $h = { subref => $ref, timeout => $timeout, id => $id };
push @{ $self->{handlers} }, $h;
my $h = {subref => $ref, timeout => $timeout, id => $id};
push @{$self->{handlers}}, $h;
if ($timeout < $min_timeout) {
$min_timeout = $timeout;
}
if ($timeout < $min_timeout) { $min_timeout = $timeout; }
if ($self->{enabled}) {
alarm $min_timeout;
}
if ($self->{enabled}) { alarm $min_timeout; }
}
sub unregister {
my ($self, $id) = @_;
Carp::croak("Must pass timer id to unregister()") if not defined $id;
@{ $self->{handlers} } = grep { $_->{id} ne $id } @{ $self->{handlers} };
my ($self, $id) = @_;
Carp::croak("Must pass timer id to unregister()") if not defined $id;
@{$self->{handlers}} = grep { $_->{id} ne $id } @{$self->{handlers}};
}
sub update_interval {
my ($self, $id, $interval) = @_;
my ($self, $id, $interval) = @_;
foreach my $h (@{ $self->{handlers} }) {
if ($h->{id} eq $id) {
$h->{timeout} = $interval;
last;
foreach my $h (@{$self->{handlers}}) {
if ($h->{id} eq $id) {
$h->{timeout} = $interval;
last;
}
}
}
}
1;

View File

@ -14,579 +14,553 @@ use warnings; use strict;
use feature 'unicode_strings';
sub initialize {
my ($self, %conf) = @_;
$self->{users} = PBot::DualIndexHashObject->new(name => 'Users', filename => $conf{filename}, pbot => $conf{pbot});
$self->load;
my ($self, %conf) = @_;
$self->{users} = PBot::DualIndexHashObject->new(name => 'Users', filename => $conf{filename}, pbot => $conf{pbot});
$self->load;
$self->{pbot}->{commands}->register(sub { $self->logincmd(@_) }, "login", 0);
$self->{pbot}->{commands}->register(sub { $self->logoutcmd(@_) }, "logout", 0);
$self->{pbot}->{commands}->register(sub { $self->useradd(@_) }, "useradd", 1);
$self->{pbot}->{commands}->register(sub { $self->userdel(@_) }, "userdel", 1);
$self->{pbot}->{commands}->register(sub { $self->userset(@_) }, "userset", 1);
$self->{pbot}->{commands}->register(sub { $self->userunset(@_) }, "userunset", 1);
$self->{pbot}->{commands}->register(sub { $self->users(@_) }, "users", 0);
$self->{pbot}->{commands}->register(sub { $self->mycmd(@_) }, "my", 0);
$self->{pbot}->{commands}->register(sub { $self->logincmd(@_) }, "login", 0);
$self->{pbot}->{commands}->register(sub { $self->logoutcmd(@_) }, "logout", 0);
$self->{pbot}->{commands}->register(sub { $self->useradd(@_) }, "useradd", 1);
$self->{pbot}->{commands}->register(sub { $self->userdel(@_) }, "userdel", 1);
$self->{pbot}->{commands}->register(sub { $self->userset(@_) }, "userset", 1);
$self->{pbot}->{commands}->register(sub { $self->userunset(@_) }, "userunset", 1);
$self->{pbot}->{commands}->register(sub { $self->users(@_) }, "users", 0);
$self->{pbot}->{commands}->register(sub { $self->mycmd(@_) }, "my", 0);
$self->{pbot}->{capabilities}->add('admin', 'can-useradd', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-userdel', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-userset', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-userunset', 1);
$self->{pbot}->{capabilities}->add('can-modify-admins', undef, 1);
$self->{pbot}->{capabilities}->add('admin', 'can-useradd', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-userdel', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-userset', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-userunset', 1);
$self->{pbot}->{capabilities}->add('can-modify-admins', undef, 1);
$self->{pbot}->{event_dispatcher}->register_handler('irc.join', sub { $self->on_join(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.join', sub { $self->on_join(@_) });
}
sub on_join {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to);
($nick, $user, $host) = $self->{pbot}->{irchandlers}->normalize_hostmask($nick, $user, $host);
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to);
($nick, $user, $host) = $self->{pbot}->{irchandlers}->normalize_hostmask($nick, $user, $host);
my $u = $self->find_user($channel, "$nick!$user\@$host");
my $u = $self->find_user($channel, "$nick!$user\@$host");
if (defined $u) {
if ($self->{pbot}->{chanops}->can_gain_ops($channel)) {
my $modes = '+';
my $targets = '';
if (defined $u) {
if ($self->{pbot}->{chanops}->can_gain_ops($channel)) {
my $modes = '+';
my $targets = '';
if ($u->{autoop}) {
$self->{pbot}->{logger}->log("$nick!$user\@$host autoop in $channel\n");
$modes .= 'o';
$targets .= "$nick ";
}
if ($u->{autoop}) {
$self->{pbot}->{logger}->log("$nick!$user\@$host autoop in $channel\n");
$modes .= 'o';
$targets .= "$nick ";
}
if ($u->{autovoice}) {
$self->{pbot}->{logger}->log("$nick!$user\@$host autovoice in $channel\n");
$modes .= 'v';
$targets .= "$nick ";
}
if ($u->{autovoice}) {
$self->{pbot}->{logger}->log("$nick!$user\@$host autovoice in $channel\n");
$modes .= 'v';
$targets .= "$nick ";
}
if (length $modes > 1) {
$self->{pbot}->{chanops}->add_op_command($channel, "mode $channel $modes $targets");
$self->{pbot}->{chanops}->gain_ops($channel);
}
if (length $modes > 1) {
$self->{pbot}->{chanops}->add_op_command($channel, "mode $channel $modes $targets");
$self->{pbot}->{chanops}->gain_ops($channel);
}
}
if ($u->{autologin}) {
$self->{pbot}->{logger}->log("$nick!$user\@$host autologin to $u->{name} for $channel\n");
$u->{loggedin} = 1;
}
}
if ($u->{autologin}) {
$self->{pbot}->{logger}->log("$nick!$user\@$host autologin to $u->{name} for $channel\n");
$u->{loggedin} = 1;
}
}
return 0;
return 0;
}
sub add_user {
my ($self, $name, $channel, $hostmask, $capabilities, $password, $dont_save) = @_;
$channel = '.*' if $channel !~ m/^#/;
my ($self, $name, $channel, $hostmask, $capabilities, $password, $dont_save) = @_;
$channel = '.*' if $channel !~ m/^#/;
$capabilities //= 'none';
$password //= $self->{pbot}->random_nick(16);
$capabilities //= 'none';
$password //= $self->{pbot}->random_nick(16);
my $data = {
name => $name,
password => $password
};
my $data = {
name => $name,
password => $password
};
foreach my $cap (split /\s*,\s*/, lc $capabilities) {
next if $cap eq 'none';
$data->{$cap} = 1;
}
foreach my $cap (split /\s*,\s*/, lc $capabilities) {
next if $cap eq 'none';
$data->{$cap} = 1;
}
$self->{pbot}->{logger}->log("Adding new user (caps: $capabilities): name: $name hostmask: $hostmask channel: $channel\n");
$self->{users}->add($channel, $hostmask, $data, $dont_save);
return $data;
$self->{pbot}->{logger}->log("Adding new user (caps: $capabilities): name: $name hostmask: $hostmask channel: $channel\n");
$self->{users}->add($channel, $hostmask, $data, $dont_save);
return $data;
}
sub remove_user {
my ($self, $channel, $hostmask) = @_;
return $self->{users}->remove($channel, $hostmask);
my ($self, $channel, $hostmask) = @_;
return $self->{users}->remove($channel, $hostmask);
}
sub load {
my $self = shift;
my $filename;
if (@_) { $filename = shift; } else { $filename = $self->{users}->{filename}; }
my $self = shift;
my $filename;
if (@_) { $filename = shift; }
else { $filename = $self->{users}->{filename}; }
if (not defined $filename) {
Carp::carp "No users path specified -- skipping loading of users";
return;
}
$self->{users}->load;
my $i = 0;
foreach my $channel (sort $self->{users}->get_keys) {
foreach my $hostmask (sort $self->{users}->get_keys($channel)) {
$i++;
my $name = $self->{users}->get_data($channel, $hostmask, 'name');
my $password = $self->{users}->get_data($channel, $hostmask, 'password');
if (not defined $name or not defined $password) {
Carp::croak "A user in $filename is missing critical data\n";
}
if (not defined $filename) {
Carp::carp "No users path specified -- skipping loading of users";
return;
}
}
$self->{pbot}->{logger}->log(" $i users loaded.\n");
$self->{users}->load;
my $i = 0;
foreach my $channel (sort $self->{users}->get_keys) {
foreach my $hostmask (sort $self->{users}->get_keys($channel)) {
$i++;
my $name = $self->{users}->get_data($channel, $hostmask, 'name');
my $password = $self->{users}->get_data($channel, $hostmask, 'password');
if (not defined $name or not defined $password) { Carp::croak "A user in $filename is missing critical data\n"; }
}
}
$self->{pbot}->{logger}->log(" $i users loaded.\n");
}
sub save {
my ($self) = @_;
$self->{users}->save;
my ($self) = @_;
$self->{users}->save;
}
sub find_user_account {
my ($self, $channel, $hostmask, $any_channel) = @_;
$channel = lc $channel;
$hostmask = lc $hostmask;
$any_channel //= 0;
my ($self, $channel, $hostmask, $any_channel) = @_;
$channel = lc $channel;
$hostmask = lc $hostmask;
$any_channel //= 0;
my $sort;
if ($channel =~ m/^#/) {
$sort = sub { $a cmp $b };
} else {
$sort = sub { $b cmp $a };
}
foreach my $chan (sort $sort $self->{users}->get_keys) {
if (($channel !~ m/^#/ and $any_channel) or $channel =~ m/^$chan$/i) {
if (not $self->{users}->exists($chan, $hostmask)) {
# find hostmask by account name or wildcard
foreach my $mask ($self->{users}->get_keys($chan)) {
if (lc $self->{users}->get_data($chan, $mask, 'name') eq $hostmask) {
return ($chan, $mask);
}
if ($mask =~ /[*?]/) {
# contains * or ? so it's converted to a regex
my $mask_quoted = quotemeta $mask;
$mask_quoted =~ s/\\\*/.*?/g;
$mask_quoted =~ s/\\\?/./g;
if ($hostmask =~ m/^$mask_quoted$/i) {
return ($chan, $mask);
}
}
}
} else {
return ($chan, $hostmask);
}
my $sort;
if ($channel =~ m/^#/) {
$sort = sub { $a cmp $b };
} else {
$sort = sub { $b cmp $a };
}
}
return (undef, $hostmask);
foreach my $chan (sort $sort $self->{users}->get_keys) {
if (($channel !~ m/^#/ and $any_channel) or $channel =~ m/^$chan$/i) {
if (not $self->{users}->exists($chan, $hostmask)) {
# find hostmask by account name or wildcard
foreach my $mask ($self->{users}->get_keys($chan)) {
if (lc $self->{users}->get_data($chan, $mask, 'name') eq $hostmask) { return ($chan, $mask); }
if ($mask =~ /[*?]/) {
# contains * or ? so it's converted to a regex
my $mask_quoted = quotemeta $mask;
$mask_quoted =~ s/\\\*/.*?/g;
$mask_quoted =~ s/\\\?/./g;
if ($hostmask =~ m/^$mask_quoted$/i) { return ($chan, $mask); }
}
}
} else {
return ($chan, $hostmask);
}
}
}
return (undef, $hostmask);
}
sub find_user {
my ($self, $channel, $hostmask, $any_channel) = @_;
$any_channel //= 0;
($channel, $hostmask) = $self->find_user_account($channel, $hostmask, $any_channel);
return undef if not $any_channel and not defined $channel;
my ($self, $channel, $hostmask, $any_channel) = @_;
$any_channel //= 0;
($channel, $hostmask) = $self->find_user_account($channel, $hostmask, $any_channel);
return undef if not $any_channel and not defined $channel;
$channel = '.*' if not defined $channel;
$hostmask = '.*' if not defined $hostmask;
$hostmask = lc $hostmask;
$channel = '.*' if not defined $channel;
$hostmask = '.*' if not defined $hostmask;
$hostmask = lc $hostmask;
my $sort;
if ($channel =~ m/^#/) {
$sort = sub { $a cmp $b };
} else {
$sort = sub { $b cmp $a };
}
my $user = eval {
foreach my $channel_regex (sort $sort $self->{users}->get_keys) {
if (($channel !~ m/^#/ and $any_channel) or $channel =~ m/^$channel_regex$/i) {
foreach my $hostmask_regex ($self->{users}->get_keys($channel_regex)) {
if ($hostmask_regex =~ m/[*?]/) {
# contains * or ? so it's converted to a regex
my $hostmask_quoted = quotemeta $hostmask_regex;
$hostmask_quoted =~ s/\\\*/.*?/g;
$hostmask_quoted =~ s/\\\?/./g;
if ($hostmask =~ m/^$hostmask_quoted$/i) {
return $self->{users}->get_data($channel_regex, $hostmask_regex);
}
} else {
# direct comparison
if ($hostmask eq lc $hostmask_regex) {
return $self->{users}->get_data($channel_regex, $hostmask_regex);
}
}
}
}
my $sort;
if ($channel =~ m/^#/) {
$sort = sub { $a cmp $b };
} else {
$sort = sub { $b cmp $a };
}
return undef;
};
if ($@) {
$self->{pbot}->{logger}->log("Error in find_user parameters: $@\n");
}
return $user;
my $user = eval {
foreach my $channel_regex (sort $sort $self->{users}->get_keys) {
if (($channel !~ m/^#/ and $any_channel) or $channel =~ m/^$channel_regex$/i) {
foreach my $hostmask_regex ($self->{users}->get_keys($channel_regex)) {
if ($hostmask_regex =~ m/[*?]/) {
# contains * or ? so it's converted to a regex
my $hostmask_quoted = quotemeta $hostmask_regex;
$hostmask_quoted =~ s/\\\*/.*?/g;
$hostmask_quoted =~ s/\\\?/./g;
if ($hostmask =~ m/^$hostmask_quoted$/i) { return $self->{users}->get_data($channel_regex, $hostmask_regex); }
} else {
# direct comparison
if ($hostmask eq lc $hostmask_regex) { return $self->{users}->get_data($channel_regex, $hostmask_regex); }
}
}
}
}
return undef;
};
if ($@) { $self->{pbot}->{logger}->log("Error in find_user parameters: $@\n"); }
return $user;
}
sub find_admin {
my ($self, $from, $hostmask) = @_;
my $user = $self->find_user($from, $hostmask);
return undef if not defined $user;
return undef if not $self->{pbot}->{capabilities}->userhas($user, 'admin');
return $user;
my ($self, $from, $hostmask) = @_;
my $user = $self->find_user($from, $hostmask);
return undef if not defined $user;
return undef if not $self->{pbot}->{capabilities}->userhas($user, 'admin');
return $user;
}
sub loggedin {
my ($self, $channel, $hostmask) = @_;
my $user = $self->find_user($channel, $hostmask);
return $user if defined $user and $user->{loggedin};
return undef;
my ($self, $channel, $hostmask) = @_;
my $user = $self->find_user($channel, $hostmask);
return $user if defined $user and $user->{loggedin};
return undef;
}
sub loggedin_admin {
my ($self, $channel, $hostmask) = @_;
my $user = $self->loggedin($channel, $hostmask);
return $user if defined $user and $self->{pbot}->{capabilities}->userhas($user, 'admin');
return undef;
my ($self, $channel, $hostmask) = @_;
my $user = $self->loggedin($channel, $hostmask);
return $user if defined $user and $self->{pbot}->{capabilities}->userhas($user, 'admin');
return undef;
}
sub login {
my ($self, $channel, $hostmask, $password) = @_;
my $user = $self->find_user($channel, $hostmask);
my $channel_text = $channel eq '.*' ? '' : " for $channel";
my ($self, $channel, $hostmask, $password) = @_;
my $user = $self->find_user($channel, $hostmask);
my $channel_text = $channel eq '.*' ? '' : " for $channel";
if (not defined $user) {
$self->{pbot}->{logger}->log("Attempt to login non-existent [$channel][$hostmask] failed\n");
return "You do not have a user account$channel_text.";
}
if (not defined $user) {
$self->{pbot}->{logger}->log("Attempt to login non-existent [$channel][$hostmask] failed\n");
return "You do not have a user account$channel_text.";
}
if (defined $password and $user->{password} ne $password) {
$self->{pbot}->{logger}->log("Bad login password for [$channel][$hostmask]\n");
return "I don't think so.";
}
if (defined $password and $user->{password} ne $password) {
$self->{pbot}->{logger}->log("Bad login password for [$channel][$hostmask]\n");
return "I don't think so.";
}
$user->{loggedin} = 1;
$self->{pbot}->{logger}->log("$hostmask logged into $user->{name} ($hostmask)$channel_text.\n");
return "Logged into $user->{name} ($hostmask)$channel_text.";
$user->{loggedin} = 1;
$self->{pbot}->{logger}->log("$hostmask logged into $user->{name} ($hostmask)$channel_text.\n");
return "Logged into $user->{name} ($hostmask)$channel_text.";
}
sub logout {
my ($self, $channel, $hostmask) = @_;
my $user = $self->find_user($channel, $hostmask);
delete $user->{loggedin} if defined $user;
my ($self, $channel, $hostmask) = @_;
my $user = $self->find_user($channel, $hostmask);
delete $user->{loggedin} if defined $user;
}
sub get_user_metadata {
my ($self, $channel, $hostmask, $key) = @_;
my $user = $self->find_user($channel, $hostmask, 1);
return $user->{lc $key} if $user;
return undef;
my ($self, $channel, $hostmask, $key) = @_;
my $user = $self->find_user($channel, $hostmask, 1);
return $user->{lc $key} if $user;
return undef;
}
sub get_loggedin_user_metadata {
my ($self, $channel, $hostmask, $key) = @_;
my $user = $self->loggedin($channel, $hostmask);
return $user->{lc $key} if $user;
return undef;
my ($self, $channel, $hostmask, $key) = @_;
my $user = $self->loggedin($channel, $hostmask);
return $user->{lc $key} if $user;
return undef;
}
sub logincmd {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $channel = $from;
return "Usage: login [channel] password" if not $arguments;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $channel = $from;
return "Usage: login [channel] password" if not $arguments;
if ($arguments =~ m/^([^ ]+)\s+(.+)/) {
$channel = $1;
$arguments = $2;
}
if ($arguments =~ m/^([^ ]+)\s+(.+)/) {
$channel = $1;
$arguments = $2;
}
my ($user_channel, $user_hostmask) = $self->find_user_account($channel, "$nick!$user\@$host");
return "/msg $nick You do not have a user account." if not defined $user_channel;
my ($user_channel, $user_hostmask) = $self->find_user_account($channel, "$nick!$user\@$host");
return "/msg $nick You do not have a user account." if not defined $user_channel;
my $u = $self->{users}->get_data($user_channel, $user_hostmask);
my $channel_text = $user_channel eq '.*' ? '' : " for $user_channel";
my $u = $self->{users}->get_data($user_channel, $user_hostmask);
my $channel_text = $user_channel eq '.*' ? '' : " for $user_channel";
if ($u->{loggedin}) {
return "/msg $nick You are already logged into $u->{name} ($user_hostmask)$channel_text.";
}
if ($u->{loggedin}) { return "/msg $nick You are already logged into $u->{name} ($user_hostmask)$channel_text."; }
my $result = $self->login($user_channel, $user_hostmask, $arguments);
return "/msg $nick $result";
my $result = $self->login($user_channel, $user_hostmask, $arguments);
return "/msg $nick $result";
}
sub logoutcmd {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
$from = $arguments if length $arguments;
my ($user_channel, $user_hostmask) = $self->find_user_account($from, "$nick!$user\@$host");
return "/msg $nick You do not have a user account." if not defined $user_channel;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
$from = $arguments if length $arguments;
my ($user_channel, $user_hostmask) = $self->find_user_account($from, "$nick!$user\@$host");
return "/msg $nick You do not have a user account." if not defined $user_channel;
my $u = $self->{users}->get_data($user_channel, $user_hostmask);
my $channel_text = $user_channel eq '.*' ? '' : " for $user_channel";
return "/msg $nick You are not logged into $u->{name} ($user_hostmask)$channel_text." if not $u->{loggedin};
my $u = $self->{users}->get_data($user_channel, $user_hostmask);
my $channel_text = $user_channel eq '.*' ? '' : " for $user_channel";
return "/msg $nick You are not logged into $u->{name} ($user_hostmask)$channel_text." if not $u->{loggedin};
$self->logout($user_channel, $user_hostmask);
return "/msg $nick Logged out of $u->{name} ($user_hostmask)$channel_text.";
$self->logout($user_channel, $user_hostmask);
return "/msg $nick Logged out of $u->{name} ($user_hostmask)$channel_text.";
}
sub users {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $channel = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $channel = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
my $include_global = '';
if (not defined $channel) {
$channel = $from;
$include_global = '.*';
} else {
$channel = '.*' if $channel !~ /^#/;
}
my $text = "Users: ";
my $last_channel = "";
my $sep = "";
foreach my $chan (sort $self->{users}->get_keys) {
next if $from =~ m/^#/ and $chan ne $channel and $chan ne $include_global;
next if $from !~ m/^#/ and $channel =~ m/^#/ and $chan ne $channel;
if ($last_channel ne $chan) {
$text .= $sep . ($chan eq ".*" ? "global" : $chan) . ": ";
$last_channel = $chan;
$sep = "";
my $include_global = '';
if (not defined $channel) {
$channel = $from;
$include_global = '.*';
} else {
$channel = '.*' if $channel !~ /^#/;
}
foreach my $hostmask (sort { return 0 if $a eq '_name' or $b eq '_name'; $self->{users}->get_data($chan, $a, 'name') cmp $self->{users}->get_data($chan, $b, 'name') } $self->{users}->get_keys($chan)) {
$text .= $sep;
my $has_cap = 0;
foreach my $key ($self->{users}->get_keys($chan, $hostmask)) {
if ($self->{pbot}->{capabilities}->exists($key)) {
$has_cap = 1;
last;
my $text = "Users: ";
my $last_channel = "";
my $sep = "";
foreach my $chan (sort $self->{users}->get_keys) {
next if $from =~ m/^#/ and $chan ne $channel and $chan ne $include_global;
next if $from !~ m/^#/ and $channel =~ m/^#/ and $chan ne $channel;
if ($last_channel ne $chan) {
$text .= $sep . ($chan eq ".*" ? "global" : $chan) . ": ";
$last_channel = $chan;
$sep = "";
}
}
$text .= '+' if $has_cap;
$text .= $self->{users}->get_data($chan, $hostmask, 'name');
$sep = " ";
foreach my $hostmask (sort { return 0 if $a eq '_name' or $b eq '_name'; $self->{users}->get_data($chan, $a, 'name') cmp $self->{users}->get_data($chan, $b, 'name') }
$self->{users}->get_keys($chan))
{
$text .= $sep;
my $has_cap = 0;
foreach my $key ($self->{users}->get_keys($chan, $hostmask)) {
if ($self->{pbot}->{capabilities}->exists($key)) {
$has_cap = 1;
last;
}
}
$text .= '+' if $has_cap;
$text .= $self->{users}->get_data($chan, $hostmask, 'name');
$sep = " ";
}
$sep = "; ";
}
$sep = "; ";
}
return $text;
return $text;
}
sub useradd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($name, $channel, $hostmask, $capabilities, $password) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 5);
$capabilities //= 'none';
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($name, $channel, $hostmask, $capabilities, $password) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 5);
$capabilities //= 'none';
if (not defined $name or not defined $channel or not defined $hostmask) {
return "Usage: useradd <account name> <channel> <hostmask> [capabilities [password]]";
}
if (not defined $name or not defined $channel or not defined $hostmask) { return "Usage: useradd <account name> <channel> <hostmask> [capabilities [password]]"; }
$channel = '.*' if $channel !~ /^#/;
$channel = '.*' if $channel !~ /^#/;
my $u = $self->{pbot}->{users}->find_user($channel, "$nick!$user\@$host");
my $u = $self->{pbot}->{users}->find_user($channel, "$nick!$user\@$host");
if (not defined $u) {
$channel = 'global' if $channel eq '.*';
return "You do not have a user account for $channel; cannot add users to that channel.\n";
}
if ($capabilities ne 'none' and not $self->{pbot}->{capabilities}->userhas($u, 'can-modify-capabilities')) {
return "Your user account does not have the can-modify-capabilities capability. You cannot create user accounts with capabilities.";
}
foreach my $cap (split /\s*,\s*/, lc $capabilities) {
next if $cap eq 'none';
return "There is no such capability $cap." if not $self->{pbot}->{capabilities}->exists($cap);
if (not $self->{pbot}->{capabilities}->userhas($u, $cap)) {
return "To set the $cap capability your user account must also have it.";
if (not defined $u) {
$channel = 'global' if $channel eq '.*';
return "You do not have a user account for $channel; cannot add users to that channel.\n";
}
if ($self->{pbot}->{capabilities}->has($cap, 'admin') and not $self->{pbot}->{capabilities}->userhas($u, 'can-modify-admins')) {
return "To set the $cap capability your user account must have the can-modify-admins capability.";
if ($capabilities ne 'none' and not $self->{pbot}->{capabilities}->userhas($u, 'can-modify-capabilities')) {
return "Your user account does not have the can-modify-capabilities capability. You cannot create user accounts with capabilities.";
}
}
$self->{pbot}->{users}->add_user($name, $channel, $hostmask, $capabilities, $password);
return "User added.";
foreach my $cap (split /\s*,\s*/, lc $capabilities) {
next if $cap eq 'none';
return "There is no such capability $cap." if not $self->{pbot}->{capabilities}->exists($cap);
if (not $self->{pbot}->{capabilities}->userhas($u, $cap)) { return "To set the $cap capability your user account must also have it."; }
if ($self->{pbot}->{capabilities}->has($cap, 'admin') and not $self->{pbot}->{capabilities}->userhas($u, 'can-modify-admins')) {
return "To set the $cap capability your user account must have the can-modify-admins capability.";
}
}
$self->{pbot}->{users}->add_user($name, $channel, $hostmask, $capabilities, $password);
return "User added.";
}
sub userdel {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($channel, $hostmask) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($channel, $hostmask) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
if (not defined $channel or not defined $hostmask) {
return "Usage: userdel <channel> <hostmask or account name>";
}
if (not defined $channel or not defined $hostmask) { return "Usage: userdel <channel> <hostmask or account name>"; }
my $u = $self->find_user($channel, "$nick!$user\@$host");
my $t = $self->find_user($channel, $hostmask);
my $u = $self->find_user($channel, "$nick!$user\@$host");
my $t = $self->find_user($channel, $hostmask);
if ($self->{pbot}->{capabilities}->userhas($t, 'botowner') and not $self->{pbot}->{capabilities}->userhas($u, 'botowner')) {
return "Only botowners may delete botowner user accounts.";
}
if ($self->{pbot}->{capabilities}->userhas($t, 'botowner') and not $self->{pbot}->{capabilities}->userhas($u, 'botowner')) {
return "Only botowners may delete botowner user accounts.";
}
if ($self->{pbot}->{capabilities}->userhas($t, 'admin') and not $self->{pbot}->{capabilities}->userhas($u, 'can-modify-admins')) {
return "To delete admin user accounts your user account must have the can-modify-admins capability.";
}
if ($self->{pbot}->{capabilities}->userhas($t, 'admin') and not $self->{pbot}->{capabilities}->userhas($u, 'can-modify-admins')) {
return "To delete admin user accounts your user account must have the can-modify-admins capability.";
}
my ($found_channel, $found_hostmask) = $self->find_user_account($channel, $hostmask);
$found_channel = $channel if not defined $found_channel; # let DualIndexHashObject disambiguate
return $self->remove_user($found_channel, $found_hostmask);
my ($found_channel, $found_hostmask) = $self->find_user_account($channel, $hostmask);
$found_channel = $channel if not defined $found_channel; # let DualIndexHashObject disambiguate
return $self->remove_user($found_channel, $found_hostmask);
}
sub userset {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
if (length $arguments and $stuff->{arglist}[0] !~ m/^(#|\.\*$|global$)/) {
$self->{pbot}->{interpreter}->unshift_arg($stuff->{arglist}, $from)
}
if (length $arguments and $stuff->{arglist}[0] !~ m/^(#|\.\*$|global$)/) { $self->{pbot}->{interpreter}->unshift_arg($stuff->{arglist}, $from) }
my ($channel, $hostmask, $key, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 4);
my ($channel, $hostmask, $key, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 4);
if (not defined $hostmask) {
return "Usage: userset [channel] <hostmask or account name> [key [value]]";
}
if (not defined $hostmask) { return "Usage: userset [channel] <hostmask or account name> [key [value]]"; }
my $u = $self->find_user($channel, "$nick!$user\@$host");
my $target = $self->find_user($channel, $hostmask);
my $u = $self->find_user($channel, "$nick!$user\@$host");
my $target = $self->find_user($channel, $hostmask);
if (not $u) {
$channel = 'global' if $channel eq '.*';
return "You do not have a user account for $channel; cannot modify their users.";
}
if (not $target) {
if ($channel !~ /^#/) {
return "There is no user account $hostmask.";
} else {
return "There is no user account $hostmask for $channel.";
if (not $u) {
$channel = 'global' if $channel eq '.*';
return "You do not have a user account for $channel; cannot modify their users.";
}
}
if (defined $value and not $self->{pbot}->{capabilities}->userhas($u, 'can-modify-capabilities')) {
if ($key =~ m/^can-/i or $self->{pbot}->{capabilities}->exists($key)) {
return "The $key metadata requires the can-modify-capabilities capability, which your user account does not have.";
if (not $target) {
if ($channel !~ /^#/) { return "There is no user account $hostmask."; }
else { return "There is no user account $hostmask for $channel."; }
}
}
if (defined $value and $self->{pbot}->{capabilities}->userhas($target, 'admin') and not $self->{pbot}->{capabilities}->userhas($u, 'can-modify-admins')) {
return "To modify admin user accounts your user account must have the can-modify-admins capability.";
}
if (defined $value and not $self->{pbot}->{capabilities}->userhas($u, 'can-modify-capabilities')) {
if ($key =~ m/^can-/i or $self->{pbot}->{capabilities}->exists($key)) {
return "The $key metadata requires the can-modify-capabilities capability, which your user account does not have.";
}
}
if (defined $key and $self->{pbot}->{capabilities}->exists($key) and not $self->{pbot}->{capabilities}->userhas($u, $key)) {
return "To set the $key capability your user account must also have it." unless $self->{pbot}->{capabilities}->userhas($u, 'botowner');
}
if (defined $value and $self->{pbot}->{capabilities}->userhas($target, 'admin') and not $self->{pbot}->{capabilities}->userhas($u, 'can-modify-admins')) {
return "To modify admin user accounts your user account must have the can-modify-admins capability.";
}
my ($found_channel, $found_hostmask) = $self->find_user_account($channel, $hostmask);
$found_channel = $channel if not defined $found_channel; # let DualIndexHashObject disambiguate
my $result = $self->{users}->set($found_channel, $found_hostmask, $key, $value);
$result =~ s/^password => .*;?$/password => <private>;/m;
return $result;
if (defined $key and $self->{pbot}->{capabilities}->exists($key) and not $self->{pbot}->{capabilities}->userhas($u, $key)) {
return "To set the $key capability your user account must also have it." unless $self->{pbot}->{capabilities}->userhas($u, 'botowner');
}
my ($found_channel, $found_hostmask) = $self->find_user_account($channel, $hostmask);
$found_channel = $channel if not defined $found_channel; # let DualIndexHashObject disambiguate
my $result = $self->{users}->set($found_channel, $found_hostmask, $key, $value);
$result =~ s/^password => .*;?$/password => <private>;/m;
return $result;
}
sub userunset {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
if (length $arguments and $stuff->{arglist}[0] !~ m/^(#|\.\*$|global$)/) {
$self->{pbot}->{interpreter}->unshift_arg($stuff->{arglist}, $from)
}
if (length $arguments and $stuff->{arglist}[0] !~ m/^(#|\.\*$|global$)/) { $self->{pbot}->{interpreter}->unshift_arg($stuff->{arglist}, $from) }
my ($channel, $hostmask, $key) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 3);
my ($channel, $hostmask, $key) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 3);
if (not defined $hostmask) {
return "Usage: userunset [channel] <hostmask or account name> <key>";
}
if (not defined $hostmask) { return "Usage: userunset [channel] <hostmask or account name> <key>"; }
my $u = $self->find_user($channel, "$nick!$user\@$host");
my $target = $self->find_user($channel, $hostmask);
my $u = $self->find_user($channel, "$nick!$user\@$host");
my $target = $self->find_user($channel, $hostmask);
if (not $u) {
$channel = 'global' if $channel eq '.*';
return "You do not have a user account for $channel; cannot modify their users.";
}
if (not $target) {
if ($channel !~ /^#/) {
return "There is no user account $hostmask.";
} else {
return "There is no user account $hostmask for $channel.";
if (not $u) {
$channel = 'global' if $channel eq '.*';
return "You do not have a user account for $channel; cannot modify their users.";
}
}
if (defined $key and not $self->{pbot}->{capabilities}->userhas($u, 'can-modify-capabilities')) {
if ($key =~ m/^can-/i or $self->{pbot}->{capabilities}->exists($key)) {
return "The $key metadata requires the can-modify-capabilities capability, which your user account does not have.";
if (not $target) {
if ($channel !~ /^#/) { return "There is no user account $hostmask."; }
else { return "There is no user account $hostmask for $channel."; }
}
}
if (defined $key and $self->{pbot}->{capabilities}->userhas($target, 'admin') and not $self->{pbot}->{capabilities}->userhas($u, 'can-modify-admins')) {
return "To modify admin user accounts your user account must have the can-modify-admins capability.";
}
if (defined $key and not $self->{pbot}->{capabilities}->userhas($u, 'can-modify-capabilities')) {
if ($key =~ m/^can-/i or $self->{pbot}->{capabilities}->exists($key)) {
return "The $key metadata requires the can-modify-capabilities capability, which your user account does not have.";
}
}
if (defined $key and $self->{pbot}->{capabilities}->exists($key) and not $self->{pbot}->{capabilities}->userhas($u, $key)) {
return "To unset the $key capability your user account must also have it." unless $self->{pbot}->{capabilities}->userhas($u, 'botowner');
}
if (defined $key and $self->{pbot}->{capabilities}->userhas($target, 'admin') and not $self->{pbot}->{capabilities}->userhas($u, 'can-modify-admins')) {
return "To modify admin user accounts your user account must have the can-modify-admins capability.";
}
my ($found_channel, $found_hostmask) = $self->find_user_account($channel, $hostmask);
$found_channel = $channel if not defined $found_channel; # let DualIndexHashObject disambiguate
return $self->{users}->unset($found_channel, $found_hostmask, $key);
if (defined $key and $self->{pbot}->{capabilities}->exists($key) and not $self->{pbot}->{capabilities}->userhas($u, $key)) {
return "To unset the $key capability your user account must also have it." unless $self->{pbot}->{capabilities}->userhas($u, 'botowner');
}
my ($found_channel, $found_hostmask) = $self->find_user_account($channel, $hostmask);
$found_channel = $channel if not defined $found_channel; # let DualIndexHashObject disambiguate
return $self->{users}->unset($found_channel, $found_hostmask, $key);
}
sub mycmd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($key, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($key, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
if (defined $value) {
$value =~ s/^is\s+//;
$value = undef if not length $value;
}
my $channel = $from;
my $hostmask = "$nick!$user\@$host";
my $u = $self->find_user($channel, $hostmask, 1);
if (not $u) {
$channel = '.*';
$hostmask = "$nick!$user\@" . $self->{pbot}->{antiflood}->address_to_mask($host);
my $name = $nick;
my ($existing_channel, $existing_hostmask) = $self->find_user_account($channel, $name);
if ($existing_hostmask ne lc $name) {
# user exists by name
return "There is already an user account named $name but its hostmask ($existing_hostmask) does not match your hostmask ($hostmask). Ask an admin for help.";
}
$u = $self->add_user($name, $channel, $hostmask, undef, undef, 1);
$u->{loggedin} = 1;
$u->{stayloggedin} = 1;
$u->{autologin} = 1;
$self->save;
}
my $result = '';
if (defined $key) {
$key = lc $key;
if (defined $value) {
if (not $self->{pbot}->{capabilities}->userhas($u, 'can-modify-capabilities')) {
if ($key =~ m/^is-/ or $key =~ m/^can-/ or $self->{pbot}->{capabilities}->exists($key)) {
return "The $key metadata requires the can-modify-capabilities capability, which your user account does not have.";
}
}
if (not $self->{pbot}->{capabilities}->userhas($u, 'botowner')) {
my @disallowed = qw/can-modify-admins botowner can-modify-capabilities/;
if (grep { $_ eq $key } @disallowed) {
return "The $key metadata requires the botowner capability to set, which your user account does not have.";
}
}
if (not $self->{pbot}->{capabilities}->userhas($u, 'admin')) {
my @disallowed = qw/name autoop autovoice chanop admin/;
if (grep { $_ eq $key } @disallowed) {
return "The $key metadata requires the admin capability to set, which your user account does not have.";
}
}
$value =~ s/^is\s+//;
$value = undef if not length $value;
}
} else {
$result = "Usage: my <key> [value]; ";
}
my ($found_channel, $found_hostmask) = $self->find_user_account($channel, $hostmask, 1);
($found_channel, $found_hostmask) = $self->find_user_account('.*', $hostmask, 1) if not defined $found_channel;
return "No user account found in $channel." if not defined $found_channel;
$result .= $self->{users}->set($found_channel, $found_hostmask, $key, $value);
$result =~ s/^password => .*;?$/password => <private>;/m;
return $result;
my $channel = $from;
my $hostmask = "$nick!$user\@$host";
my $u = $self->find_user($channel, $hostmask, 1);
if (not $u) {
$channel = '.*';
$hostmask = "$nick!$user\@" . $self->{pbot}->{antiflood}->address_to_mask($host);
my $name = $nick;
my ($existing_channel, $existing_hostmask) = $self->find_user_account($channel, $name);
if ($existing_hostmask ne lc $name) {
# user exists by name
return "There is already an user account named $name but its hostmask ($existing_hostmask) does not match your hostmask ($hostmask). Ask an admin for help.";
}
$u = $self->add_user($name, $channel, $hostmask, undef, undef, 1);
$u->{loggedin} = 1;
$u->{stayloggedin} = 1;
$u->{autologin} = 1;
$self->save;
}
my $result = '';
if (defined $key) {
$key = lc $key;
if (defined $value) {
if (not $self->{pbot}->{capabilities}->userhas($u, 'can-modify-capabilities')) {
if ($key =~ m/^is-/ or $key =~ m/^can-/ or $self->{pbot}->{capabilities}->exists($key)) {
return "The $key metadata requires the can-modify-capabilities capability, which your user account does not have.";
}
}
if (not $self->{pbot}->{capabilities}->userhas($u, 'botowner')) {
my @disallowed = qw/can-modify-admins botowner can-modify-capabilities/;
if (grep { $_ eq $key } @disallowed) {
return "The $key metadata requires the botowner capability to set, which your user account does not have.";
}
}
if (not $self->{pbot}->{capabilities}->userhas($u, 'admin')) {
my @disallowed = qw/name autoop autovoice chanop admin/;
if (grep { $_ eq $key } @disallowed) {
return "The $key metadata requires the admin capability to set, which your user account does not have.";
}
}
}
} else {
$result = "Usage: my <key> [value]; ";
}
my ($found_channel, $found_hostmask) = $self->find_user_account($channel, $hostmask, 1);
($found_channel, $found_hostmask) = $self->find_user_account('.*', $hostmask, 1) if not defined $found_channel;
return "No user account found in $channel." if not defined $found_channel;
$result .= $self->{users}->set($found_channel, $found_hostmask, $key, $value);
$result =~ s/^password => .*;?$/password => <private>;/m;
return $result;
}
1;

View File

@ -1,10 +1,11 @@
package PBot::Utils::Indefinite;
use 5.010; use warnings;
use feature 'unicode_strings';
require Exporter;
our @ISA = qw/Exporter/;
our @ISA = qw/Exporter/;
our @EXPORT = qw/prepend_indefinite_article select_indefinite_article/;
# This module implements A/AN inflexion for nouns...
@ -44,48 +45,47 @@ sub select_indefinite_article {
my ($word) = @_;
# Handle ordinal forms...
return "a" if $word =~ $ORDINAL_A;
return "an" if $word =~ $ORDINAL_AN;
return "a" if $word =~ $ORDINAL_A;
return "an" if $word =~ $ORDINAL_AN;
# Handle special cases...
return "an" if $word =~ $EXPLICIT_AN;
return "an" if $word =~ $SINGLE_AN;
return "a" if $word =~ $SINGLE_A;
return "an" if $word =~ $EXPLICIT_AN;
return "an" if $word =~ $SINGLE_AN;
return "a" if $word =~ $SINGLE_A;
# Handle abbreviations...
return "an" if $word =~ $ABBREV_AN;
return "an" if $word =~ /\A [aefhilmnorsx][.-]/xi;
return "a" if $word =~ /\A [a-z][.-]/xi;
return "an" if $word =~ $ABBREV_AN;
return "an" if $word =~ /\A [aefhilmnorsx][.-]/xi;
return "a" if $word =~ /\A [a-z][.-]/xi;
# Handle consonants
return "a" if $word =~ /\A [^aeiouy] /xi;
return "a" if $word =~ /\A [^aeiouy] /xi;
# Handle special vowel-forms
return "a" if $word =~ /\A e [uw] /xi;
return "a" if $word =~ /\A onc?e \b /xi;
return "a" if $word =~ /\A uni (?: [^nmd] | mo) /xi;
return "an" if $word =~ /\A ut[th] /xi;
return "a" if $word =~ /\A u [bcfhjkqrst] [aeiou] /xi;
return "a" if $word =~ /\A e [uw] /xi;
return "a" if $word =~ /\A onc?e \b /xi;
return "a" if $word =~ /\A uni (?: [^nmd] | mo) /xi;
return "an" if $word =~ /\A ut[th] /xi;
return "a" if $word =~ /\A u [bcfhjkqrst] [aeiou] /xi;
# Handle special capitals
return "a" if $word =~ /\A U [NK] [AIEO]? /x;
return "a" if $word =~ /\A U [NK] [AIEO]? /x;
# Handle vowels
return "an" if $word =~ /\A [aeiou]/xi;
return "an" if $word =~ /\A [aeiou]/xi;
# Handle Y... (before certain consonants implies (unnaturalized) "I.." sound)
return "an" if $word =~ $INITIAL_Y_AN;
return "an" if $word =~ $INITIAL_Y_AN;
# Otherwise, guess "A"
return "a";
}
1; # Magic true value required at end of module
1; # Magic true value required at end of module
__END__
=head1 NAME

View File

@ -11,43 +11,41 @@ use File::HomeDir;
use File::Spec;
our %default_cache_args = (
'namespace' => 'pbot-cached',
'cache_root' => File::Spec->catfile(File::HomeDir->my_home, '.cache'),
'default_expires_in' => 600
'namespace' => 'pbot-cached',
'cache_root' => File::Spec->catfile(File::HomeDir->my_home, '.cache'),
'default_expires_in' => 600
);
sub new {
my $class = shift;
my $cache_opt;
my %lwp_opt;
unless (scalar @_ % 2) {
%lwp_opt = @_;
$cache_opt = {};
for my $key (qw(namespace cache_root default_expires_in)) {
$cache_opt->{$key} = delete $lwp_opt{$key} if exists $lwp_opt{$key};
my $class = shift;
my $cache_opt;
my %lwp_opt;
unless (scalar @_ % 2) {
%lwp_opt = @_;
$cache_opt = {};
for my $key (qw(namespace cache_root default_expires_in)) { $cache_opt->{$key} = delete $lwp_opt{$key} if exists $lwp_opt{$key}; }
} else {
$cache_opt = shift || {};
%lwp_opt = @_;
}
} else {
$cache_opt = shift || {};
%lwp_opt = @_;
}
my $self = $class->SUPER::new(%lwp_opt);
my %cache_args = (%default_cache_args, %$cache_opt);
$self->{cache} = Cache::FileCache->new(\%cache_args);
return $self
my $self = $class->SUPER::new(%lwp_opt);
my %cache_args = (%default_cache_args, %$cache_opt);
$self->{cache} = Cache::FileCache->new(\%cache_args);
return $self;
}
sub request {
my ($self, @args) = @_;
my $request = $args[0];
return $self->SUPER::request(@args) if $request->method ne 'GET';
my ($self, @args) = @_;
my $request = $args[0];
return $self->SUPER::request(@args) if $request->method ne 'GET';
my $uri = $request->uri->as_string;
my $cached = $self->{cache}->get($uri);
return HTTP::Response->parse($cached) if defined $cached;
my $uri = $request->uri->as_string;
my $cached = $self->{cache}->get($uri);
return HTTP::Response->parse($cached) if defined $cached;
my $res = $self->SUPER::request(@args);
$self->{cache}->set($uri, $res->as_string) if $res->code eq HTTP::Status::RC_OK;
return $res;
my $res = $self->SUPER::request(@args);
$self->{cache}->set($uri, $res->as_string) if $res->code eq HTTP::Status::RC_OK;
return $res;
}
1;

View File

@ -16,129 +16,127 @@ use DateTime::Format::Flexible;
use DateTime::Format::Duration;
sub new {
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference") if ref $_[1] eq 'HASH';
my ($class, %conf) = @_;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference") if ref $_[1] eq 'HASH';
my ($class, %conf) = @_;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($self, %conf) = @_;
$self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
my ($self, %conf) = @_;
$self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
}
# expands stuff like "7d3h" to "7 days and 3 hours"
sub unconcise {
my ($input) = @_;
my %word = (y => 'years', w => 'weeks', d => 'days', h => 'hours', m => 'minutes', s => 'seconds');
$input =~ s/(\d+)([ywdhms])(?![a-z])/"$1 " . $word{lc $2} . ' and '/ige;
$input =~ s/ and $//;
return $input;
my ($input) = @_;
my %word = (y => 'years', w => 'weeks', d => 'days', h => 'hours', m => 'minutes', s => 'seconds');
$input =~ s/(\d+)([ywdhms])(?![a-z])/"$1 " . $word{lc $2} . ' and '/ige;
$input =~ s/ and $//;
return $input;
}
# parses English natural language date strings into seconds
# does not accept times or dates in the past
sub parsedate {
my ($self, $input) = @_;
my ($self, $input) = @_;
my $override ="";
my $override = "";
TRY_AGAIN:
$input = "$override$input" if length $override;
$input = "$override$input" if length $override;
# expand stuff like 7d3h
$input = unconcise($input);
# expand stuff like 7d3h
$input = unconcise($input);
# some aliases
$input =~ s/\bsecs?\b/seconds/g;
$input =~ s/\bmins?\b/minutes/g;
$input =~ s/\bhrs?\b/hours/g;
$input =~ s/\bwks?\b/weeks/g;
$input =~ s/\byrs?\b/years/g;
# some aliases
$input =~ s/\bsecs?\b/seconds/g;
$input =~ s/\bmins?\b/minutes/g;
$input =~ s/\bhrs?\b/hours/g;
$input =~ s/\bwks?\b/weeks/g;
$input =~ s/\byrs?\b/years/g;
# sanitizers
$input =~ s/\b(\d+)\s+(am?|pm?)\b/$1$2/; # remove leading spaces from am/pm
$input =~ s/ (\d+)(am?|pm?)\b/ $1:00:00$2/; # convert 3pm to 3:00:00pm
$input =~ s/ (\d+:\d+)(am?|pm?)\b/ $1:00$2/; # convert 4:20pm to 4:20:00pm
$input =~ s/next (jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|june?|july?|aug(?:ust)?|sept(?:ember)?|oct(?:ober)?|nov(?:ember)|dec(?:ember)?) (\d+)(?:st|nd|rd|th)?(.*)/"next $1 and " . ($2 - 1) . " days" . (length $3 ? " and $3" : "")/ie;
# sanitizers
$input =~ s/\b(\d+)\s+(am?|pm?)\b/$1$2/; # remove leading spaces from am/pm
$input =~ s/ (\d+)(am?|pm?)\b/ $1:00:00$2/; # convert 3pm to 3:00:00pm
$input =~ s/ (\d+:\d+)(am?|pm?)\b/ $1:00$2/; # convert 4:20pm to 4:20:00pm
$input =~
s/next (jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|june?|july?|aug(?:ust)?|sept(?:ember)?|oct(?:ober)?|nov(?:ember)|dec(?:ember)?) (\d+)(?:st|nd|rd|th)?(.*)/"next $1 and " . ($2 - 1) . " days" . (length $3 ? " and $3" : "")/ie;
# split input on "and" or comma, then we'll add up the results
# this allows us to parse things like "1 hour and 30 minutes"
my @inputs = split /(?:,?\s+and\s+|\s*,\s*|\s+at\s+)/, $input;
# split input on "and" or comma, then we'll add up the results
# this allows us to parse things like "1 hour and 30 minutes"
my @inputs = split /(?:,?\s+and\s+|\s*,\s*|\s+at\s+)/, $input;
# adjust timezone to user-override if user provides a timezone
# we won't know if a timezone was provided until it is parsed
my $timezone;
my $tz_override = 'UTC';
# adjust timezone to user-override if user provides a timezone
# we won't know if a timezone was provided until it is parsed
my $timezone;
my $tz_override = 'UTC';
ADJUST_TIMEZONE:
$timezone = $tz_override;
my $now = DateTime->now(time_zone => $timezone);
$timezone = $tz_override;
my $now = DateTime->now(time_zone => $timezone);
my $seconds = 0;
my ($to, $base);
my $seconds = 0;
my ($to, $base);
foreach my $input (@inputs) {
return -1 if $input =~ m/forever/i;
$input .= ' seconds' if $input =~ m/^\s*\d+\s*$/;
foreach my $input (@inputs) {
return -1 if $input =~ m/forever/i;
$input .= ' seconds' if $input =~ m/^\s*\d+\s*$/;
# DateTime::Format::Flexible doesn't support seconds, but that's okay;
# we can take care of that easily here!
if ($input =~ m/^\s*(\d+)\s+seconds$/) {
$seconds += $1;
next;
# DateTime::Format::Flexible doesn't support seconds, but that's okay;
# we can take care of that easily here!
if ($input =~ m/^\s*(\d+)\s+seconds$/) {
$seconds += $1;
next;
}
# adjust base
if (defined $to) {
$base = $to->clone;
$base->set_time_zone($timezone);
} else {
$base = $now;
}
# First, attempt to parse as-is...
$to = eval { return DateTime::Format::Flexible->parse_datetime($input, lang => ['en'], base => $base); };
# If there was an error, then append "from now" and attempt to parse as a relative time...
if ($@) {
$input .= ' from now';
$to = eval { return DateTime::Format::Flexible->parse_datetime($input, lang => ['en'], base => $base); };
# If there's still an error, it's bad input
if ($@) {
$@ =~ s/ ${override}from now at PBot.*$//;
return (0, $@);
}
}
# there was a timezone parsed, set the tz override and try again
if ($to->time_zone_short_name ne 'floating' and $to->time_zone_short_name ne 'UTC' and $tz_override eq 'UTC') {
$tz_override = $to->time_zone_long_name;
$to = undef;
goto ADJUST_TIMEZONE;
}
$to->set_time_zone('UTC');
$base->set_time_zone('UTC');
my $duration = $to->subtract_datetime_absolute($base);
# If the time is in the past, prepend "tomorrow" or "next" and reparse
if ($duration->is_negative) {
if ($input =~ m/^\d/) { $override = "tomorrow "; }
else { $override = "next "; }
$to = undef;
goto TRY_AGAIN;
}
# add the seconds from this input chunk
$seconds += $duration->seconds;
}
# adjust base
if (defined $to) {
$base = $to->clone;
$base->set_time_zone($timezone);
} else {
$base = $now;
}
# First, attempt to parse as-is...
$to = eval { return DateTime::Format::Flexible->parse_datetime($input, lang => ['en'], base => $base); };
# If there was an error, then append "from now" and attempt to parse as a relative time...
if ($@) {
$input .= ' from now';
$to = eval { return DateTime::Format::Flexible->parse_datetime($input, lang => ['en'], base => $base); };
# If there's still an error, it's bad input
if ($@) {
$@ =~ s/ ${override}from now at PBot.*$//;
return (0, $@);
}
}
# there was a timezone parsed, set the tz override and try again
if ($to->time_zone_short_name ne 'floating' and $to->time_zone_short_name ne 'UTC' and $tz_override eq 'UTC') {
$tz_override = $to->time_zone_long_name;
$to = undef;
goto ADJUST_TIMEZONE;
}
$to->set_time_zone('UTC');
$base->set_time_zone('UTC');
my $duration = $to->subtract_datetime_absolute($base);
# If the time is in the past, prepend "tomorrow" or "next" and reparse
if ($duration->is_negative) {
if ($input =~ m/^\d/) {
$override = "tomorrow ";
} else {
$override = "next ";
}
$to = undef;
goto TRY_AGAIN;
}
# add the seconds from this input chunk
$seconds += $duration->seconds;
}
return $seconds;
return $seconds;
}
1;

View File

@ -1,26 +1,23 @@
package PBot::Utils::SafeFilename;
use 5.010; use warnings;
use feature 'unicode_strings';
require Exporter;
our @ISA = qw/Exporter/;
our @ISA = qw/Exporter/;
our @EXPORT = qw/safe_filename/;
sub safe_filename {
my $name = shift;
my $safe = '';
my $name = shift;
my $safe = '';
while ($name =~ m/(.)/gms) {
if ($1 eq '&') {
$safe .= '&amp;';
} elsif ($1 eq '/') {
$safe .= '&fslash;';
} else {
$safe .= $1;
while ($name =~ m/(.)/gms) {
if ($1 eq '&') { $safe .= '&amp;'; }
elsif ($1 eq '/') { $safe .= '&fslash;'; }
else { $safe .= $1; }
}
}
return lc $safe;
return lc $safe;
}
1;

View File

@ -6,35 +6,33 @@ use strict;
use feature 'unicode_strings';
require Exporter;
our @ISA = qw/Exporter/;
our @ISA = qw/Exporter/;
our @EXPORT = qw/validate_string/;
use JSON;
sub validate_string {
my ($string, $max_length) = @_;
my ($string, $max_length) = @_;
return $string if not defined $string or not length $string;
$max_length = 1024 * 8 if not defined $max_length;
return $string if not defined $string or not length $string;
$max_length = 1024 * 8 if not defined $max_length;
eval {
my $h = decode_json($string);
foreach my $k (keys %$h) {
$h->{$k} = substr $h->{$k}, 0, $max_length unless $max_length <= 0;
eval {
my $h = decode_json($string);
foreach my $k (keys %$h) { $h->{$k} = substr $h->{$k}, 0, $max_length unless $max_length <= 0; }
$string = encode_json($h);
};
if ($@) {
# not a json string
$string = substr $string, 0, $max_length unless $max_length <= 0;
}
$string = encode_json($h);
};
# $string =~ s/(\P{PosixGraph})/my $ch = $1; if ($ch =~ m{[\s\x03\x02\x1d\x1f\x16\x0f]}) { $ch } else { sprintf "\\x%02X", ord $ch }/ge;
# $string = substr $string, 0, $max_length unless $max_length <= 0;
if ($@) {
# not a json string
$string = substr $string, 0, $max_length unless $max_length <= 0;
}
# $string =~ s/(\P{PosixGraph})/my $ch = $1; if ($ch =~ m{[\s\x03\x02\x1d\x1f\x16\x0f]}) { $ch } else { sprintf "\\x%02X", ord $ch }/ge;
# $string = substr $string, 0, $max_length unless $max_length <= 0;
return $string;
return $string;
}
1;

View File

@ -18,57 +18,51 @@ use LWP::UserAgent;
# These are set automatically by the misc/update_version script
use constant {
BUILD_NAME => "PBot",
BUILD_REVISION => 3311,
BUILD_DATE => "2020-02-15",
BUILD_NAME => "PBot",
BUILD_REVISION => 3311,
BUILD_DATE => "2020-02-15",
};
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->version_cmd(@_) }, "version", 0);
$self->{last_check} = { timestamp => 0, version => BUILD_REVISION, date => BUILD_DATE };
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->version_cmd(@_) }, "version", 0);
$self->{last_check} = {timestamp => 0, version => BUILD_REVISION, date => BUILD_DATE};
}
sub version {
return BUILD_NAME . " version " . BUILD_REVISION . " " . BUILD_DATE;
}
sub version { return BUILD_NAME . " version " . BUILD_REVISION . " " . BUILD_DATE; }
sub version_cmd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $ratelimit = $self->{pbot}->{registry}->get_value('version', 'check_limit') // 300;
my $ratelimit = $self->{pbot}->{registry}->get_value('version', 'check_limit') // 300;
if (time - $self->{last_check}->{timestamp} >= $ratelimit) {
$self->{last_check}->{timestamp} = time;
if (time - $self->{last_check}->{timestamp} >= $ratelimit) {
$self->{last_check}->{timestamp} = time;
my $url = $self->{pbot}->{registry}->get_value('version', 'check_url') // 'https://raw.githubusercontent.com/pragma-/pbot/master/PBot/VERSION.pm';
$self->{pbot}->{logger}->log("Checking $url for new version...\n");
my $ua = LWP::UserAgent->new(timeout => 10);
my $response = $ua->get($url);
my $url = $self->{pbot}->{registry}->get_value('version', 'check_url') // 'https://raw.githubusercontent.com/pragma-/pbot/master/PBot/VERSION.pm';
$self->{pbot}->{logger}->log("Checking $url for new version...\n");
my $ua = LWP::UserAgent->new(timeout => 10);
my $response = $ua->get($url);
return "Unable to get version information: " . $response->status_line if not $response->is_success;
return "Unable to get version information: " . $response->status_line if not $response->is_success;
my $text = $response->decoded_content;
my ($version, $date) = $text =~ m/^\s+BUILD_REVISION => (\d+).*^\s+BUILD_DATE\s+=> "([^"]+)"/ms;
my $text = $response->decoded_content;
my ($version, $date) = $text =~ m/^\s+BUILD_REVISION => (\d+).*^\s+BUILD_DATE\s+=> "([^"]+)"/ms;
if (not defined $version or not defined $date) {
return "Unable to get version information: data did not match expected format";
if (not defined $version or not defined $date) { return "Unable to get version information: data did not match expected format"; }
$self->{last_check} = {timestamp => time, version => $version, date => $date};
}
$self->{last_check} = { timestamp => time, version => $version, date => $date };
}
my $target_nick;
$target_nick = $self->{pbot}->{nicklist}->is_present_similar($from, $arguments) if length $arguments;
my $target_nick;
$target_nick = $self->{pbot}->{nicklist}->is_present_similar($from, $arguments) if length $arguments;
my $result = '/say ';
$result .= "$target_nick: " if $target_nick;
$result .= $self->version;
my $result = '/say ';
$result .= "$target_nick: " if $target_nick;
$result .= $self->version;
if ($self->{last_check}->{version} > BUILD_REVISION) {
$result .= "; new version available: $self->{last_check}->{version} $self->{last_check}->{date}!";
}
return $result;
if ($self->{last_check}->{version} > BUILD_REVISION) { $result .= "; new version available: $self->{last_check}->{version} $self->{last_check}->{date}!"; }
return $result;
}
1;

View File

@ -8,6 +8,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::WebPaste;
use parent 'PBot::Class';
use warnings; use strict;
@ -19,56 +20,54 @@ use LWP::UserAgent::Paranoid;
use Encode;
sub initialize {
my ($self, %conf) = @_;
my ($self, %conf) = @_;
$self->{paste_sites} = [
sub { $self->paste_ixio(@_) },
];
$self->{paste_sites} = [
sub { $self->paste_ixio(@_) },
];
$self->{current_site} = 0;
$self->{current_site} = 0;
}
sub get_paste_site {
my ($self) = @_;
my $subref = $self->{paste_sites}->[$self->{current_site}];
if (++$self->{current_site} >= @{$self->{paste_sites}}) {
$self->{current_site} = 0;
}
return $subref;
my ($self) = @_;
my $subref = $self->{paste_sites}->[$self->{current_site}];
if (++$self->{current_site} >= @{$self->{paste_sites}}) { $self->{current_site} = 0; }
return $subref;
}
sub paste {
my ($self, $text, %opts) = @_;
my %default_opts = (
no_split => 0,
);
%opts = (%default_opts, %opts);
my ($self, $text, %opts) = @_;
my %default_opts = (
no_split => 0,
);
%opts = (%default_opts, %opts);
$text =~ s/(.{120})\s/$1\n/g unless $opts{no_split};
$text = encode('UTF-8', $text);
$text =~ s/(.{120})\s/$1\n/g unless $opts{no_split};
$text = encode('UTF-8', $text);
my $result;
for (my $tries = 3; $tries > 0; $tries--) {
my $paste_site = $self->get_paste_site;
$result = $paste_site->($text);
last if $result !~ m/error pasting/;
}
return $result;
my $result;
for (my $tries = 3; $tries > 0; $tries--) {
my $paste_site = $self->get_paste_site;
$result = $paste_site->($text);
last if $result !~ m/error pasting/;
}
return $result;
}
sub paste_ixio {
my ($self, $text) = @_;
my $ua = LWP::UserAgent::Paranoid->new(request_timeout => 10);
$ua->agent("Mozilla/5.0");
push @{ $ua->requests_redirectable }, 'POST';
my %post = ('f:1' => $text);
my $response = $ua->post("http://ix.io", \%post);
alarm 1; # LWP::UserAgent::Paranoid kills alarm
return "error pasting: " . $response->status_line if not $response->is_success;
my $result = $response->content;
$result =~ s/^\s+//;
$result =~ s/\s+$//;
return $result;
my ($self, $text) = @_;
my $ua = LWP::UserAgent::Paranoid->new(request_timeout => 10);
$ua->agent("Mozilla/5.0");
push @{$ua->requests_redirectable}, 'POST';
my %post = ('f:1' => $text);
my $response = $ua->post("http://ix.io", \%post);
alarm 1; # LWP::UserAgent::Paranoid kills alarm
return "error pasting: " . $response->status_line if not $response->is_success;
my $result = $response->content;
$result =~ s/^\s+//;
$result =~ s/\s+$//;
return $result;
}
1;

View File

@ -38,42 +38,42 @@ use Time::Duration qw/duration/;
use Time::HiRes qw/gettimeofday/;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->actiontrigger(@_) }, 'actiontrigger', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-actiontrigger', 1);
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->actiontrigger(@_) }, 'actiontrigger', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-actiontrigger', 1);
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_public(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.caction', sub { $self->on_action(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.join', sub { $self->on_join(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.part', sub { $self->on_departure(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.quit', sub { $self->on_departure(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.kick', sub { $self->on_kick(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_public(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.caction', sub { $self->on_action(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.join', sub { $self->on_join(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.part', sub { $self->on_departure(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.quit', sub { $self->on_departure(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.kick', sub { $self->on_kick(@_) });
$self->{filename} = $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/triggers.sqlite3';
$self->{filename} = $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/triggers.sqlite3';
$self->dbi_begin;
$self->create_database;
$self->dbi_begin;
$self->create_database;
}
sub unload {
my $self = shift;
$self->dbi_end;
$self->{pbot}->{commands}->unregister('actiontrigger');
$self->{pbot}->{capabilities}->remove('can-actiontrigger');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.caction');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.join');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.part');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.quit');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.kick');
my $self = shift;
$self->dbi_end;
$self->{pbot}->{commands}->unregister('actiontrigger');
$self->{pbot}->{capabilities}->remove('can-actiontrigger');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.caction');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.join');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.part');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.quit');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.kick');
}
sub create_database {
my $self = shift;
return if not $self->{dbh};
my $self = shift;
return if not $self->{dbh};
eval {
$self->{dbh}->do(<<SQL);
eval {
$self->{dbh}->do(<<SQL);
CREATE TABLE IF NOT EXISTS Triggers (
channel TEXT,
trigger TEXT,
@ -84,350 +84,331 @@ CREATE TABLE IF NOT EXISTS Triggers (
lastused NUMERIC
)
SQL
};
};
$self->{pbot}->{logger}->log("ActionTrigger create database failed: $@") if $@;
$self->{pbot}->{logger}->log("ActionTrigger create database failed: $@") if $@;
}
sub dbi_begin {
my ($self) = @_;
eval {
$self->{dbh} = DBI->connect("dbi:SQLite:dbname=$self->{filename}", "", "", { RaiseError => 1, PrintError => 0, AutoInactiveDestroy => 1, sqlite_unicode => 1 }) or die $DBI::errstr;
};
if ($@) {
$self->{pbot}->{logger}->log("Error opening ActionTrigger database: $@");
return 0;
} else {
return 1;
}
}
sub dbi_end {
my ($self) = @_;
return if not $self->{dbh};
$self->{dbh}->disconnect;
delete $self->{dbh};
}
sub add_trigger {
my ($self, $channel, $trigger, $action, $owner, $level, $repeatdelay) = @_;
return 0 if $self->get_trigger($channel, $trigger);
eval {
my $sth = $self->{dbh}->prepare('INSERT INTO Triggers (channel, trigger, action, owner, level, repeatdelay, lastused) VALUES (?, ?, ?, ?, ?, ?, 0)');
$sth->execute(lc $channel, $trigger, $action, $owner, $level, $repeatdelay);
};
if ($@) {
$self->{pbot}->{logger}->log("Add trigger failed: $@");
return 0;
}
return 1;
}
sub delete_trigger {
my ($self, $channel, $trigger) = @_;
return 0 if not $self->get_trigger($channel, $trigger);
my $sth = $self->{dbh}->prepare('DELETE FROM Triggers WHERE channel = ? AND trigger = ?');
$sth->execute(lc $channel, $trigger);
return 1;
}
sub list_triggers {
my ($self, $channel) = @_;
my $triggers = eval {
my $sth;
if ($channel eq '*') {
$sth = $self->{dbh}->prepare('SELECT * FROM Triggers WHERE channel != ?');
$channel = 'global';
} else {
$sth = $self->{dbh}->prepare('SELECT * FROM Triggers WHERE channel = ?');
}
$sth->execute(lc $channel);
return $sth->fetchall_arrayref({});
};
if ($@) {
$self->{pbot}->{logger}->log("List triggers failed: $@");
}
$triggers = [] if not defined $triggers;
return @$triggers;
}
sub update_trigger {
my ($self, $channel, $trigger, $data) = @_;
eval {
my $sql = 'UPDATE Triggers SET ';
my $comma = '';
foreach my $key (keys %$data) {
$sql .= "$comma$key = ?";
$comma = ", ";
}
$sql .= "WHERE trigger = ? AND channel = ?";
my $sth = $self->{dbh}->prepare($sql);
my $param = 1;
foreach my $key (keys %$data) {
$sth->bind_param($param++, $data->{$key});
}
$sth->bind_param($param++, $trigger);
$sth->bind_param($param, $channel);
$sth->execute();
};
$self->{pbot}->{logger}->log("Update trigger $channel/$trigger failed: $@\n") if $@;
}
sub get_trigger {
my ($self, $channel, $trigger) = @_;
my $row = eval {
my $sth = $self->{dbh}->prepare('SELECT * FROM Triggers WHERE channel = ? AND trigger = ?');
$sth->execute(lc $channel, $trigger);
my $row = $sth->fetchrow_hashref();
return $row;
};
if ($@) {
$self->{pbot}->{logger}->log("Get trigger failed: $@");
return undef;
}
return $row;
}
sub on_kick {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host);
my ($victim, $reason) = ($event->{event}->to, $event->{event}->{args}[1]);
my $channel = $event->{event}->{args}[0];
return 0 if $event->{interpreted};
$self->check_trigger($nick, $user, $host, $channel, "KICK $victim $reason");
return 0;
}
sub on_action {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my $channel = $event->{event}->{to}[0];
return 0 if $event->{interpreted};
$msg =~ s/^\/me\s+//;
$self->check_trigger($nick, $user, $host, $channel, "ACTION $msg");
return 0;
}
sub on_public {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my $channel = $event->{event}->{to}[0];
return 0 if $event->{interpreted};
$self->check_trigger($nick, $user, $host, $channel, "PRIVMSG $msg");
return 0;
}
sub on_join {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel, $args) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to, $event->{event}->args);
$channel = lc $channel;
$self->check_trigger($nick, $user, $host, $channel, "JOIN");
return 0;
}
sub on_departure {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel, $args) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to, $event->{event}->args);
$channel = lc $channel;
$self->check_trigger($nick, $user, $host, $channel, (uc $event->{event}->type) . " $args");
return 0;
}
sub check_trigger {
my ($self, $nick, $user, $host, $channel, $text) = @_;
return 0 if not $self->{dbh};
my @triggers = $self->list_triggers($channel);
my @globals = $self->list_triggers('global');
push @triggers, @globals;
$text = "$nick!$user\@$host $text";
my $now = gettimeofday;
foreach my $trigger (@triggers) {
my ($self) = @_;
eval {
$trigger->{lastused} = 0 if not defined $trigger->{lastused};
$trigger->{repeatdelay} = 0 if not defined $trigger->{repeatdelay};
if ($now - $trigger->{lastused} >= $trigger->{repeatdelay} and $text =~ m/$trigger->{trigger}/) {
$trigger->{lastused} = $now;
my $data = { lastused => $now };
$self->update_trigger($trigger->{channel}, $trigger->{trigger}, $data);
my $action = $trigger->{action};
my @stuff = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
my $i;
map { ++$i; $action =~ s/\$$i/$_/g; } @stuff;
my $delay = 0;
my ($n, $u, $h) = $trigger->{owner} =~ /^([^!]+)!([^@]+)\@(.*)$/;
my $command = {
nick => $n,
user => $u,
host => $h,
command => $action,
level => $trigger->{level} // 0
};
$self->{pbot}->{logger}->log("ActionTrigger: ($channel) $trigger->{trigger} -> $action [$command->{level}]\n");
$self->{pbot}->{interpreter}->add_to_command_queue($channel, $command, $delay);
}
$self->{dbh} = DBI->connect("dbi:SQLite:dbname=$self->{filename}", "", "", {RaiseError => 1, PrintError => 0, AutoInactiveDestroy => 1, sqlite_unicode => 1})
or die $DBI::errstr;
};
if ($@) {
$self->{pbot}->{logger}->log("Skipping bad trigger $trigger->{trigger}: $@");
$self->{pbot}->{logger}->log("Error opening ActionTrigger database: $@");
return 0;
} else {
return 1;
}
}
return 0;
}
sub dbi_end {
my ($self) = @_;
return if not $self->{dbh};
$self->{dbh}->disconnect;
delete $self->{dbh};
}
sub add_trigger {
my ($self, $channel, $trigger, $action, $owner, $level, $repeatdelay) = @_;
return 0 if $self->get_trigger($channel, $trigger);
eval {
my $sth = $self->{dbh}->prepare('INSERT INTO Triggers (channel, trigger, action, owner, level, repeatdelay, lastused) VALUES (?, ?, ?, ?, ?, ?, 0)');
$sth->execute(lc $channel, $trigger, $action, $owner, $level, $repeatdelay);
};
if ($@) {
$self->{pbot}->{logger}->log("Add trigger failed: $@");
return 0;
}
return 1;
}
sub delete_trigger {
my ($self, $channel, $trigger) = @_;
return 0 if not $self->get_trigger($channel, $trigger);
my $sth = $self->{dbh}->prepare('DELETE FROM Triggers WHERE channel = ? AND trigger = ?');
$sth->execute(lc $channel, $trigger);
return 1;
}
sub list_triggers {
my ($self, $channel) = @_;
my $triggers = eval {
my $sth;
if ($channel eq '*') {
$sth = $self->{dbh}->prepare('SELECT * FROM Triggers WHERE channel != ?');
$channel = 'global';
} else {
$sth = $self->{dbh}->prepare('SELECT * FROM Triggers WHERE channel = ?');
}
$sth->execute(lc $channel);
return $sth->fetchall_arrayref({});
};
if ($@) { $self->{pbot}->{logger}->log("List triggers failed: $@"); }
$triggers = [] if not defined $triggers;
return @$triggers;
}
sub update_trigger {
my ($self, $channel, $trigger, $data) = @_;
eval {
my $sql = 'UPDATE Triggers SET ';
my $comma = '';
foreach my $key (keys %$data) {
$sql .= "$comma$key = ?";
$comma = ", ";
}
$sql .= "WHERE trigger = ? AND channel = ?";
my $sth = $self->{dbh}->prepare($sql);
my $param = 1;
foreach my $key (keys %$data) { $sth->bind_param($param++, $data->{$key}); }
$sth->bind_param($param++, $trigger);
$sth->bind_param($param, $channel);
$sth->execute();
};
$self->{pbot}->{logger}->log("Update trigger $channel/$trigger failed: $@\n") if $@;
}
sub get_trigger {
my ($self, $channel, $trigger) = @_;
my $row = eval {
my $sth = $self->{dbh}->prepare('SELECT * FROM Triggers WHERE channel = ? AND trigger = ?');
$sth->execute(lc $channel, $trigger);
my $row = $sth->fetchrow_hashref();
return $row;
};
if ($@) {
$self->{pbot}->{logger}->log("Get trigger failed: $@");
return undef;
}
return $row;
}
sub on_kick {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host);
my ($victim, $reason) = ($event->{event}->to, $event->{event}->{args}[1]);
my $channel = $event->{event}->{args}[0];
return 0 if $event->{interpreted};
$self->check_trigger($nick, $user, $host, $channel, "KICK $victim $reason");
return 0;
}
sub on_action {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my $channel = $event->{event}->{to}[0];
return 0 if $event->{interpreted};
$msg =~ s/^\/me\s+//;
$self->check_trigger($nick, $user, $host, $channel, "ACTION $msg");
return 0;
}
sub on_public {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my $channel = $event->{event}->{to}[0];
return 0 if $event->{interpreted};
$self->check_trigger($nick, $user, $host, $channel, "PRIVMSG $msg");
return 0;
}
sub on_join {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel, $args) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to, $event->{event}->args);
$channel = lc $channel;
$self->check_trigger($nick, $user, $host, $channel, "JOIN");
return 0;
}
sub on_departure {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel, $args) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to, $event->{event}->args);
$channel = lc $channel;
$self->check_trigger($nick, $user, $host, $channel, (uc $event->{event}->type) . " $args");
return 0;
}
sub check_trigger {
my ($self, $nick, $user, $host, $channel, $text) = @_;
return 0 if not $self->{dbh};
my @triggers = $self->list_triggers($channel);
my @globals = $self->list_triggers('global');
push @triggers, @globals;
$text = "$nick!$user\@$host $text";
my $now = gettimeofday;
foreach my $trigger (@triggers) {
eval {
$trigger->{lastused} = 0 if not defined $trigger->{lastused};
$trigger->{repeatdelay} = 0 if not defined $trigger->{repeatdelay};
if ($now - $trigger->{lastused} >= $trigger->{repeatdelay} and $text =~ m/$trigger->{trigger}/) {
$trigger->{lastused} = $now;
my $data = {lastused => $now};
$self->update_trigger($trigger->{channel}, $trigger->{trigger}, $data);
my $action = $trigger->{action};
my @stuff = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
my $i;
map { ++$i; $action =~ s/\$$i/$_/g; } @stuff;
my $delay = 0;
my ($n, $u, $h) = $trigger->{owner} =~ /^([^!]+)!([^@]+)\@(.*)$/;
my $command = {
nick => $n,
user => $u,
host => $h,
command => $action,
level => $trigger->{level} // 0
};
$self->{pbot}->{logger}->log("ActionTrigger: ($channel) $trigger->{trigger} -> $action [$command->{level}]\n");
$self->{pbot}->{interpreter}->add_to_command_queue($channel, $command, $delay);
}
};
if ($@) { $self->{pbot}->{logger}->log("Skipping bad trigger $trigger->{trigger}: $@"); }
}
return 0;
}
sub actiontrigger {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
return "Internal error." if not $self->{dbh};
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
return "Internal error." if not $self->{dbh};
my $command = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
my $result;
given ($command) {
when ('list') {
my $channel = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
if (not defined $channel) {
if ($from !~ /^#/) {
$channel = 'global';
} else {
$channel = $from;
my $command = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
my $result;
given ($command) {
when ('list') {
my $channel = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
if (not defined $channel) {
if ($from !~ /^#/) { $channel = 'global'; }
else { $channel = $from; }
} elsif ($channel !~ m/^#/ and $channel ne 'global') {
return "Invalid channel $channel. Usage: actiontrigger list [#channel or global]";
}
my @triggers = $self->list_triggers($channel);
if (not @triggers) { $result = "No action triggers set for $channel."; }
else {
$result = "Triggers for $channel:\n";
my $comma = '';
foreach my $trigger (@triggers) {
$trigger->{level} //= 0;
$trigger->{repeatdelay} //= 0;
$result .= "$comma$trigger->{trigger} -> $trigger->{action}";
$result .= " (level=$trigger->{level})" if $trigger->{level} != 0;
$result .= " (repeatdelay=$trigger->{repeatdelay})" if $trigger->{repeatdelay} != 0;
$comma = ",\n";
}
}
}
} elsif ($channel !~ m/^#/ and $channel ne 'global') {
return "Invalid channel $channel. Usage: actiontrigger list [#channel or global]";
}
my @triggers = $self->list_triggers($channel);
# TODO: use GetOpt flags instead of positional arguments
when ('add') {
my $channel;
if ($from =~ m/^#/) { $channel = $from; }
else {
$channel = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
if (not @triggers) {
$result = "No action triggers set for $channel.";
} else {
$result = "Triggers for $channel:\n";
my $comma = '';
foreach my $trigger (@triggers) {
$trigger->{level} //= 0;
$trigger->{repeatdelay} //= 0;
$result .= "$comma$trigger->{trigger} -> $trigger->{action}";
$result .= " (level=$trigger->{level})" if $trigger->{level} != 0;
$result .= " (repeatdelay=$trigger->{repeatdelay})" if $trigger->{repeatdelay} != 0;
$comma = ",\n";
if (not defined $channel) {
return
"To use this command from private message the <channel> argument is required. Usage: actiontrigger add <#channel or global> <level> <repeat delay (in seconds)> <regex trigger> <command>";
} elsif ($channel !~ m/^#/ and $channel ne 'global') {
return "Invalid channel $channel. Usage: actiontrigger add <#channel or global> <level> <repeat delay (in seconds)> <regex trigger> <command>";
}
}
my ($level, $repeatdelay, $trigger, $action) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 4, 0, 1);
if (not defined $trigger or not defined $action) {
if ($from !~ m/^#/) {
$result =
"To use this command from private message the <channel> argument is required. Usage: actiontrigger add <#channel or global> <level> <repeat delay (in seconds)> <regex trigger> <command>";
} else {
$result = "Usage: actiontrigger add <level> <repeat delay (in seconds)> <regex trigger> <command>";
}
return $result;
}
my $exists = $self->get_trigger($channel, $trigger);
if (defined $exists) { return "Trigger already exists."; }
if ($level !~ m/^\d+$/) { return "$nick: Missing level argument?\n"; }
if ($repeatdelay !~ m/^\d+$/) { return "$nick: Missing repeat delay argument?\n"; }
if ($level > 0) {
my $admin = $self->{pbot}->{users}->find_admin($channel, "$nick!$user\@$host");
if (not defined $admin or $level > $admin->{level}) { return "You may not set a level higher than your own."; }
}
if ($self->add_trigger($channel, $trigger, $action, "$nick!$user\@$host", $level, $repeatdelay)) { $result = "Trigger added."; }
else { $result = "Failed to add trigger."; }
}
when ('delete') {
my $channel;
if ($from =~ m/^#/) { $channel = $from; }
else {
$channel = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
if ($channel !~ m/^#/ and $channel ne 'global') {
return "To use this command from private message the <channel> argument is required. Usage: actiontrigger delete <#channel or global> <regex trigger>";
}
}
my ($trigger) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 1);
if (not defined $trigger) {
if ($from !~ m/^#/) {
$result = "To use this command from private message the <channel> argument is required. Usage: from private message: actiontrigger delete <channel> <regex trigger>";
} else {
$result = "Usage: actiontrigger delete <regex trigger>";
}
return $result;
}
my $exists = $self->get_trigger($channel, $trigger);
if (not defined $exists) { $result = "No such trigger."; }
else {
$self->delete_trigger($channel, $trigger);
$result = "Trigger deleted.";
}
}
default {
if ($from !~ m/^#/) {
$result =
"Usage from private message: actiontrigger list [#channel or global] | actiontrigger add <#channel or global> <level> <repeat delay (in seconds)> <regex trigger> <command> | actiontrigger delete <#channel or global> <regex trigger>";
} else {
$result =
"Usage: actiontrigger list [#channel or global] | actiontrigger add <level> <repeat delay (in seconds)> <regex trigger> <command> | actiontrigger delete <regex>";
}
}
}
}
# TODO: use GetOpt flags instead of positional arguments
when ('add') {
my $channel;
if ($from =~ m/^#/) {
$channel = $from;
} else {
$channel = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
if (not defined $channel) {
return "To use this command from private message the <channel> argument is required. Usage: actiontrigger add <#channel or global> <level> <repeat delay (in seconds)> <regex trigger> <command>";
} elsif ($channel !~ m/^#/ and $channel ne 'global') {
return "Invalid channel $channel. Usage: actiontrigger add <#channel or global> <level> <repeat delay (in seconds)> <regex trigger> <command>";
}
}
my ($level, $repeatdelay, $trigger, $action) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 4, 0, 1);
if (not defined $trigger or not defined $action) {
if ($from !~ m/^#/) {
$result = "To use this command from private message the <channel> argument is required. Usage: actiontrigger add <#channel or global> <level> <repeat delay (in seconds)> <regex trigger> <command>";
} else {
$result = "Usage: actiontrigger add <level> <repeat delay (in seconds)> <regex trigger> <command>";
}
return $result;
}
my $exists = $self->get_trigger($channel, $trigger);
if (defined $exists) {
return "Trigger already exists.";
}
if ($level !~ m/^\d+$/) {
return "$nick: Missing level argument?\n";
}
if ($repeatdelay !~ m/^\d+$/) {
return "$nick: Missing repeat delay argument?\n";
}
if ($level > 0) {
my $admin = $self->{pbot}->{users}->find_admin($channel, "$nick!$user\@$host");
if (not defined $admin or $level > $admin->{level}) {
return "You may not set a level higher than your own.";
}
}
if ($self->add_trigger($channel, $trigger, $action, "$nick!$user\@$host", $level, $repeatdelay)) {
$result = "Trigger added.";
} else {
$result = "Failed to add trigger.";
}
}
when ('delete') {
my $channel;
if ($from =~ m/^#/) {
$channel = $from;
} else {
$channel = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
if ($channel !~ m/^#/ and $channel ne 'global') {
return "To use this command from private message the <channel> argument is required. Usage: actiontrigger delete <#channel or global> <regex trigger>";
}
}
my ($trigger) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 1);
if (not defined $trigger) {
if ($from !~ m/^#/) {
$result = "To use this command from private message the <channel> argument is required. Usage: from private message: actiontrigger delete <channel> <regex trigger>";
} else {
$result = "Usage: actiontrigger delete <regex trigger>";
}
return $result;
}
my $exists = $self->get_trigger($channel, $trigger);
if (not defined $exists) {
$result = "No such trigger.";
} else {
$self->delete_trigger($channel, $trigger);
$result = "Trigger deleted.";
}
}
default {
if ($from !~ m/^#/) {
$result = "Usage from private message: actiontrigger list [#channel or global] | actiontrigger add <#channel or global> <level> <repeat delay (in seconds)> <regex trigger> <command> | actiontrigger delete <#channel or global> <regex trigger>";
} else {
$result = "Usage: actiontrigger list [#channel or global] | actiontrigger add <level> <repeat delay (in seconds)> <regex trigger> <command> | actiontrigger delete <regex>";
}
}
}
return $result;
return $result;
}
1;

View File

@ -14,61 +14,62 @@ use warnings; use strict;
use feature 'unicode_strings';
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{registry}->add_default('text', 'antiaway', 'bad_nicks', $conf{bad_nicks} // '([[:punct:]](afk|brb|bbl|away|sleep|z+|work|gone|study|out|home|busy|off)[[:punct:]]*$|.+\[.*\]$)');
$self->{pbot}->{registry}->add_default('text', 'antiaway', 'bad_actions', $conf{bad_actions} // '^/me (is (away|gone)|.*auto.?away)');
$self->{pbot}->{registry}->add_default('text', 'antiaway', 'kick_msg', 'http://sackheads.org/~bnaylor/spew/away_msgs.html');
my ($self, %conf) = @_;
$self->{pbot}->{registry}
->add_default('text', 'antiaway', 'bad_nicks', $conf{bad_nicks} // '([[:punct:]](afk|brb|bbl|away|sleep|z+|work|gone|study|out|home|busy|off)[[:punct:]]*$|.+\[.*\]$)');
$self->{pbot}->{registry}->add_default('text', 'antiaway', 'bad_actions', $conf{bad_actions} // '^/me (is (away|gone)|.*auto.?away)');
$self->{pbot}->{registry}->add_default('text', 'antiaway', 'kick_msg', 'http://sackheads.org/~bnaylor/spew/away_msgs.html');
$self->{pbot}->{event_dispatcher}->register_handler('irc.nick', sub { $self->on_nickchange(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.caction', sub { $self->on_action(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.nick', sub { $self->on_nickchange(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.caction', sub { $self->on_action(@_) });
}
sub unload {
my ($self) = @_;
$self->{pbot}->{event_dispatcher}->remove_handler('irc.nick');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.caction');
my ($self) = @_;
$self->{pbot}->{event_dispatcher}->remove_handler('irc.nick');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.caction');
}
sub on_nickchange {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $newnick) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $newnick) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my $bad_nicks = $self->{pbot}->{registry}->get_value('antiaway', 'bad_nicks');
if ($newnick =~ m/$bad_nicks/i) {
my $kick_msg = $self->{pbot}->{registry}->get_value('antiaway', 'kick_msg');
my $channels = $self->{pbot}->{nicklist}->get_channels($newnick);
foreach my $chan (@$channels) {
next if not $self->{pbot}->{chanops}->can_gain_ops($chan);
my $bad_nicks = $self->{pbot}->{registry}->get_value('antiaway', 'bad_nicks');
if ($newnick =~ m/$bad_nicks/i) {
my $kick_msg = $self->{pbot}->{registry}->get_value('antiaway', 'kick_msg');
my $channels = $self->{pbot}->{nicklist}->get_channels($newnick);
foreach my $chan (@$channels) {
next if not $self->{pbot}->{chanops}->can_gain_ops($chan);
my $u = $self->{pbot}->{users}->loggedin($chan, "$nick!$user\@$host");
next if $self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted');
my $u = $self->{pbot}->{users}->loggedin($chan, "$nick!$user\@$host");
next if $self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted');
$self->{pbot}->{logger}->log("$newnick matches bad away nick regex, kicking from $chan\n");
$self->{pbot}->{chanops}->add_op_command($chan, "kick $chan $newnick $kick_msg");
$self->{pbot}->{chanops}->gain_ops($chan);
$self->{pbot}->{logger}->log("$newnick matches bad away nick regex, kicking from $chan\n");
$self->{pbot}->{chanops}->add_op_command($chan, "kick $chan $newnick $kick_msg");
$self->{pbot}->{chanops}->gain_ops($chan);
}
}
}
return 0;
return 0;
}
sub on_action {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->{args}[0], $event->{event}->{to}[0]);
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->{args}[0], $event->{event}->{to}[0]);
return 0 if $channel !~ /^#/;
return 0 if not $self->{pbot}->{chanops}->can_gain_ops($channel);
return 0 if $channel !~ /^#/;
return 0 if not $self->{pbot}->{chanops}->can_gain_ops($channel);
my $u = $self->{pbot}->{users}->loggedin($channel, "$nick!$user\@$host");
return 0 if $self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted');
my $u = $self->{pbot}->{users}->loggedin($channel, "$nick!$user\@$host");
return 0 if $self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted');
my $bad_actions = $self->{pbot}->{registry}->get_value('antiaway', 'bad_actions');
if ($msg =~ m/$bad_actions/i) {
$self->{pbot}->{logger}->log("$nick $msg matches bad away actions regex, kicking...\n");
my $kick_msg = $self->{pbot}->{registry}->get_value('antiaway', 'kick_msg');
$self->{pbot}->{chanops}->add_op_command($channel, "kick $channel $nick $kick_msg");
$self->{pbot}->{chanops}->gain_ops($channel);
}
return 0;
my $bad_actions = $self->{pbot}->{registry}->get_value('antiaway', 'bad_actions');
if ($msg =~ m/$bad_actions/i) {
$self->{pbot}->{logger}->log("$nick $msg matches bad away actions regex, kicking...\n");
my $kick_msg = $self->{pbot}->{registry}->get_value('antiaway', 'kick_msg');
$self->{pbot}->{chanops}->add_op_command($channel, "kick $channel $nick $kick_msg");
$self->{pbot}->{chanops}->gain_ops($channel);
}
return 0;
}
1;

View File

@ -8,6 +8,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package Plugins::AntiKickAutoRejoin;
use parent 'Plugins::Plugin';
use warnings; use strict;
@ -17,63 +18,60 @@ use Time::HiRes qw/gettimeofday/;
use Time::Duration;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{registry}->add_default('array', 'antikickautorejoin', 'punishment', '30,90,180,300,28800');
$self->{pbot}->{registry}->add_default('text', 'antikickautorejoin', 'threshold', '2');
my ($self, %conf) = @_;
$self->{pbot}->{registry}->add_default('array', 'antikickautorejoin', 'punishment', '30,90,180,300,28800');
$self->{pbot}->{registry}->add_default('text', 'antikickautorejoin', 'threshold', '2');
$self->{pbot}->{event_dispatcher}->register_handler('irc.kick', sub { $self->on_kick(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.join', sub { $self->on_join(@_) });
$self->{kicks} = {};
$self->{pbot}->{event_dispatcher}->register_handler('irc.kick', sub { $self->on_kick(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.join', sub { $self->on_join(@_) });
$self->{kicks} = {};
}
sub unload {
my ($self) = @_;
$self->{pbot}->{event_dispatcher}->remove_handler('irc.kick');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.join');
my ($self) = @_;
$self->{pbot}->{event_dispatcher}->remove_handler('irc.kick');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.join');
}
sub on_kick {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $target, $channel, $reason) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to, $event->{event}->{args}[0], $event->{event}->{args}[1]);
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $target, $channel, $reason) =
($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to, $event->{event}->{args}[0], $event->{event}->{args}[1]);
$channel = lc $channel;
return 0 if not $self->{pbot}->{chanops}->can_gain_ops($channel);
return 0 if $reason eq '*BANG!*'; # roulette
$channel = lc $channel;
return 0 if not $self->{pbot}->{chanops}->can_gain_ops($channel);
return 0 if $reason eq '*BANG!*'; # roulette
if (not exists $self->{kicks}->{$channel}
or not exists $self->{kicks}->{$channel}->{$target}) {
$self->{kicks}->{$channel}->{$target}->{rejoins} = 0;
}
if (not exists $self->{kicks}->{$channel} or not exists $self->{kicks}->{$channel}->{$target}) { $self->{kicks}->{$channel}->{$target}->{rejoins} = 0; }
$self->{kicks}->{$channel}->{$target}->{last_kick} = gettimeofday;
return 0;
$self->{kicks}->{$channel}->{$target}->{last_kick} = gettimeofday;
return 0;
}
sub on_join {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to);
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to);
$channel = lc $channel;
return 0 if not $self->{pbot}->{chanops}->can_gain_ops($channel);
my $u = $self->{pbot}->{users}->loggedin($channel, "$nick!$user\@$host");
return 0 if $self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted');
$channel = lc $channel;
return 0 if not $self->{pbot}->{chanops}->can_gain_ops($channel);
my $u = $self->{pbot}->{users}->loggedin($channel, "$nick!$user\@$host");
return 0 if $self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted');
if (exists $self->{kicks}->{$channel}
and exists $self->{kicks}->{$channel}->{$nick}) {
my $now = gettimeofday;
if (exists $self->{kicks}->{$channel} and exists $self->{kicks}->{$channel}->{$nick}) {
my $now = gettimeofday;
if ($now - $self->{kicks}->{$channel}->{$nick}->{last_kick} <= $self->{pbot}->{registry}->get_value('antikickautorejoin', 'threshold')) {
my $timeout = $self->{pbot}->{registry}->get_array_value('antikickautorejoin', 'punishment', $self->{kicks}->{$channel}->{$nick}->{rejoins});
my $duration = duration($timeout);
$duration =~ s/s$//; # hours -> hour, minutes -> minute
if ($now - $self->{kicks}->{$channel}->{$nick}->{last_kick} <= $self->{pbot}->{registry}->get_value('antikickautorejoin', 'threshold')) {
my $timeout = $self->{pbot}->{registry}->get_array_value('antikickautorejoin', 'punishment', $self->{kicks}->{$channel}->{$nick}->{rejoins});
my $duration = duration($timeout);
$duration =~ s/s$//; # hours -> hour, minutes -> minute
$self->{pbot}->{chanops}->ban_user_timed($self->{pbot}->{registry}->get_value('irc', 'botnick'), 'autorejoining after kick', "*!$user\@$host", $channel, $timeout);
$self->{pbot}->{chanops}->add_op_command($channel, "kick $channel $nick $duration ban for auto-rejoining after kick; use this time to think about why you were kicked");
$self->{pbot}->{chanops}->gain_ops($channel);
$self->{kicks}->{$channel}->{$nick}->{rejoins}++;
$self->{pbot}->{chanops}->ban_user_timed($self->{pbot}->{registry}->get_value('irc', 'botnick'), 'autorejoining after kick', "*!$user\@$host", $channel, $timeout);
$self->{pbot}->{chanops}->add_op_command($channel, "kick $channel $nick $duration ban for auto-rejoining after kick; use this time to think about why you were kicked");
$self->{pbot}->{chanops}->gain_ops($channel);
$self->{kicks}->{$channel}->{$nick}->{rejoins}++;
}
}
}
return 0;
return 0;
}
1;

View File

@ -9,6 +9,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package Plugins::AntiNickSpam;
use parent 'Plugins::Plugin';
use warnings; use strict;
@ -18,73 +19,70 @@ use Time::Duration qw/duration/;
use Time::HiRes qw/gettimeofday/;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_public(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.caction', sub { $self->on_action(@_) });
$self->{nicks} = {};
my ($self, %conf) = @_;
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_public(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.caction', sub { $self->on_action(@_) });
$self->{nicks} = {};
}
sub unload {
my ($self) = @_;
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.caction');
my ($self) = @_;
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.caction');
}
sub on_action {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my $channel = $event->{event}->{to}[0];
return 0 if $event->{interpreted};
$self->check_flood($nick, $user, $host, $channel, $msg);
return 0;
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my $channel = $event->{event}->{to}[0];
return 0 if $event->{interpreted};
$self->check_flood($nick, $user, $host, $channel, $msg);
return 0;
}
sub on_public {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my $channel = $event->{event}->{to}[0];
return 0 if $event->{interpreted};
$self->check_flood($nick, $user, $host, $channel, $msg);
return 0;
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my $channel = $event->{event}->{to}[0];
return 0 if $event->{interpreted};
$self->check_flood($nick, $user, $host, $channel, $msg);
return 0;
}
sub check_flood {
my ($self, $nick, $user, $host, $channel, $msg) = @_;
return 0 if not $self->{pbot}->{chanops}->can_gain_ops($channel);
my ($self, $nick, $user, $host, $channel, $msg) = @_;
return 0 if not $self->{pbot}->{chanops}->can_gain_ops($channel);
$channel = lc $channel;
my @words = split /\s+/, $msg;
my @nicks;
$channel = lc $channel;
my @words = split /\s+/, $msg;
my @nicks;
foreach my $word (@words) {
$word =~ s/[:;\+,\.!?\@\%\$]+$//g;
if ($self->{pbot}->{nicklist}->is_present($channel, $word) and not grep { $_ eq $word } @nicks) {
push @{$self->{nicks}->{$channel}}, [scalar gettimeofday, $word];
push @nicks, $word;
foreach my $word (@words) {
$word =~ s/[:;\+,\.!?\@\%\$]+$//g;
if ($self->{pbot}->{nicklist}->is_present($channel, $word) and not grep { $_ eq $word } @nicks) {
push @{$self->{nicks}->{$channel}}, [scalar gettimeofday, $word];
push @nicks, $word;
}
}
}
$self->clear_old_nicks($channel);
$self->clear_old_nicks($channel);
if (exists $self->{nicks}->{$channel} and @{$self->{nicks}->{$channel}} >= 10) {
$self->{pbot}->{logger}->log("Nick spam flood detected in $channel\n");
$self->{pbot}->{chanops}->mute_user_timed($self->{pbot}->{registry}->get_value('irc', 'botnick'), 'nick spam flooding', '$~a', $channel, 60 * 15);
}
if (exists $self->{nicks}->{$channel} and @{$self->{nicks}->{$channel}} >= 10) {
$self->{pbot}->{logger}->log("Nick spam flood detected in $channel\n");
$self->{pbot}->{chanops}->mute_user_timed($self->{pbot}->{registry}->get_value('irc', 'botnick'), 'nick spam flooding', '$~a', $channel, 60 * 15);
}
}
sub clear_old_nicks {
my ($self, $channel) = @_;
my $now = gettimeofday;
return if not exists $self->{nicks}->{$channel};
my ($self, $channel) = @_;
my $now = gettimeofday;
return if not exists $self->{nicks}->{$channel};
while (1) {
if (@{$self->{nicks}->{$channel}} and $self->{nicks}->{$channel}->[0]->[0] <= $now - 15) {
shift @{$self->{nicks}->{$channel}};
} else {
last;
while (1) {
if (@{$self->{nicks}->{$channel}} and $self->{nicks}->{$channel}->[0]->[0] <= $now - 15) { shift @{$self->{nicks}->{$channel}}; }
else { last; }
}
}
delete $self->{nicks}->{$channel} if not @{$self->{nicks}->{$channel}};
delete $self->{nicks}->{$channel} if not @{$self->{nicks}->{$channel}};
}
1;

View File

@ -16,157 +16,143 @@ use Time::HiRes qw/gettimeofday/;
use POSIX qw/strftime/;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{registry}->add_default('text', 'antiflood', 'antirepeat', $conf{antirepeat} // 1);
$self->{pbot}->{registry}->add_default('text', 'antiflood', 'antirepeat_threshold', $conf{antirepeat_threshold} // 2.5);
$self->{pbot}->{registry}->add_default('text', 'antiflood', 'antirepeat_match', $conf{antirepeat_match} // 0.5);
$self->{pbot}->{registry}->add_default('text', 'antiflood', 'antirepeat_allow_bot', $conf{antirepeat_allow_bot} // 1);
my ($self, %conf) = @_;
$self->{pbot}->{registry}->add_default('text', 'antiflood', 'antirepeat', $conf{antirepeat} // 1);
$self->{pbot}->{registry}->add_default('text', 'antiflood', 'antirepeat_threshold', $conf{antirepeat_threshold} // 2.5);
$self->{pbot}->{registry}->add_default('text', 'antiflood', 'antirepeat_match', $conf{antirepeat_match} // 0.5);
$self->{pbot}->{registry}->add_default('text', 'antiflood', 'antirepeat_allow_bot', $conf{antirepeat_allow_bot} // 1);
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_public(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.caction', sub { $self->on_public(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_public(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.caction', sub { $self->on_public(@_) });
$self->{pbot}->{timer}->register(sub { $self->adjust_offenses }, 60 * 60 * 1, 'antirepeat');
$self->{offenses} = {};
$self->{pbot}->{timer}->register(sub { $self->adjust_offenses }, 60 * 60 * 1, 'antirepeat');
$self->{offenses} = {};
}
sub unload {
my $self = shift;
$self->{pbot}->{timer}->unregister('antirepeat');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.caction');
my $self = shift;
$self->{pbot}->{timer}->unregister('antirepeat');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.caction');
}
sub on_public {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my $channel = lc $event->{event}->{to}[0];
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my $channel = lc $event->{event}->{to}[0];
($nick, $user, $host) = $self->{pbot}->{irchandlers}->normalize_hostmask($nick, $user, $host);
($nick, $user, $host) = $self->{pbot}->{irchandlers}->normalize_hostmask($nick, $user, $host);
return 0 if not $self->{pbot}->{registry}->get_value('antiflood', 'antirepeat');
return 0 if not $self->{pbot}->{registry}->get_value('antiflood', 'antirepeat');
my $antirepeat = $self->{pbot}->{registry}->get_value($channel, 'antirepeat');
return 0 if defined $antirepeat and not $antirepeat;
my $antirepeat = $self->{pbot}->{registry}->get_value($channel, 'antirepeat');
return 0 if defined $antirepeat and not $antirepeat;
return 0 if $self->{pbot}->{registry}->get_value($channel, 'dont_enforce_antiflood');
return 0 if $self->{pbot}->{registry}->get_value($channel, 'dont_enforce_antiflood');
return 0 if $channel !~ m/^#/;
return 0 if $event->{interpreted};
return 0 if $channel !~ m/^#/;
return 0 if $event->{interpreted};
my $u = $self->{pbot}->{users}->loggedin($channel, "$nick!$user\@$host");
return 0 if $self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted');
my $u = $self->{pbot}->{users}->loggedin($channel, "$nick!$user\@$host");
return 0 if $self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted');
my $account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
my $account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
# don't enforce anti-repeat for unreg spam
my $chanmodes = $self->{pbot}->{channels}->get_meta($channel, 'MODE');
if (defined $chanmodes and $chanmodes =~ m/z/ and exists $self->{pbot}->{bantracker}->{banlist}->{$channel}->{'+q'}->{'$~a'}) {
my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($account);
return 0 if not defined $nickserv or not length $nickserv;
}
my $messages = $self->{pbot}->{messagehistory}->{database}->get_recent_messages($account, $channel, 6, $self->{pbot}->{messagehistory}->{MSG_CHAT});
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
my $bot_trigger = $self->{pbot}->{registry}->get_value($channel, 'trigger')
// $self->{pbot}->{registry}->get_value('general', 'trigger');
my $allow_bot = $self->{pbot}->{registry}->get_value($channel, 'antirepeat_allow_bot')
// $self->{pbot}->{registry}->get_value('antiflood', 'antirepeat_allow_bot');
my $match = $self->{pbot}->{registry}->get_value($channel, 'antirepeat_match')
// $self->{pbot}->{registry}->get_value('antiflood', 'antirepeat_match');
my %matches;
my $now = gettimeofday;
foreach my $string1 (@$messages) {
next if $now - $string1->{timestamp} > 60 * 60 * 2;
next if $allow_bot and $string1->{msg} =~ m/^(?:$bot_trigger|$botnick.?)/;
$string1->{msg} =~ s/^[^;,:]{1,20}[;,:]//; # remove nick-like prefix if one exists
next if length $string1->{msg} <= 5; # allow really short messages since "yep" "ok" etc are so common
if (exists $self->{offenses}->{$account} and exists $self->{offenses}->{$account}->{$channel}) {
next if $self->{offenses}->{$account}->{$channel}->{last_offense} >= $string1->{timestamp};
# don't enforce anti-repeat for unreg spam
my $chanmodes = $self->{pbot}->{channels}->get_meta($channel, 'MODE');
if (defined $chanmodes and $chanmodes =~ m/z/ and exists $self->{pbot}->{bantracker}->{banlist}->{$channel}->{'+q'}->{'$~a'}) {
my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($account);
return 0 if not defined $nickserv or not length $nickserv;
}
foreach my $string2 (@$messages) {
next if $now - $string2->{timestamp} > 60 * 60 * 2;
next if $allow_bot and $string2->{msg} =~ m/^(?:$bot_trigger|$botnick.?)/;
$string2->{msg} =~ s/^[^;,:]{1,20}[;,:]//; # remove nick-like prefix if one exists
next if length $string2->{msg} <= 5; # allow really short messages since "yep" "ok" etc are so common
my $messages = $self->{pbot}->{messagehistory}->{database}->get_recent_messages($account, $channel, 6, $self->{pbot}->{messagehistory}->{MSG_CHAT});
if (exists $self->{offenses}->{$account} and exists $self->{offenses}->{$account}->{$channel}) {
next if $self->{offenses}->{$account}->{$channel}->{last_offense} >= $string2->{timestamp};
}
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
my $string = lcss(lc $string1->{msg}, lc $string2->{msg});
my $bot_trigger = $self->{pbot}->{registry}->get_value($channel, 'trigger') // $self->{pbot}->{registry}->get_value('general', 'trigger');
if (defined $string) {
my $length = length $string;
my $length1 = $length / length $string1->{msg};
my $length2 = $length / length $string2->{msg};
my $allow_bot = $self->{pbot}->{registry}->get_value($channel, 'antirepeat_allow_bot') // $self->{pbot}->{registry}->get_value('antiflood', 'antirepeat_allow_bot');
if ($length1 >= $match && $length2 >= $match) {
$matches{$string}++;
my $match = $self->{pbot}->{registry}->get_value($channel, 'antirepeat_match') // $self->{pbot}->{registry}->get_value('antiflood', 'antirepeat_match');
my %matches;
my $now = gettimeofday;
foreach my $string1 (@$messages) {
next if $now - $string1->{timestamp} > 60 * 60 * 2;
next if $allow_bot and $string1->{msg} =~ m/^(?:$bot_trigger|$botnick.?)/;
$string1->{msg} =~ s/^[^;,:]{1,20}[;,:]//; # remove nick-like prefix if one exists
next if length $string1->{msg} <= 5; # allow really short messages since "yep" "ok" etc are so common
if (exists $self->{offenses}->{$account} and exists $self->{offenses}->{$account}->{$channel}) {
next if $self->{offenses}->{$account}->{$channel}->{last_offense} >= $string1->{timestamp};
}
foreach my $string2 (@$messages) {
next if $now - $string2->{timestamp} > 60 * 60 * 2;
next if $allow_bot and $string2->{msg} =~ m/^(?:$bot_trigger|$botnick.?)/;
$string2->{msg} =~ s/^[^;,:]{1,20}[;,:]//; # remove nick-like prefix if one exists
next if length $string2->{msg} <= 5; # allow really short messages since "yep" "ok" etc are so common
if (exists $self->{offenses}->{$account} and exists $self->{offenses}->{$account}->{$channel}) {
next if $self->{offenses}->{$account}->{$channel}->{last_offense} >= $string2->{timestamp};
}
my $string = lcss(lc $string1->{msg}, lc $string2->{msg});
if (defined $string) {
my $length = length $string;
my $length1 = $length / length $string1->{msg};
my $length2 = $length / length $string2->{msg};
if ($length1 >= $match && $length2 >= $match) { $matches{$string}++; }
}
}
}
}
}
my $threshold = $self->{pbot}->{registry}->get_value($channel, 'antirepeat_threshold')
// $self->{pbot}->{registry}->get_value('antiflood', 'antirepeat_threshold');
my $threshold = $self->{pbot}->{registry}->get_value($channel, 'antirepeat_threshold') // $self->{pbot}->{registry}->get_value('antiflood', 'antirepeat_threshold');
foreach my $match (keys %matches) {
if (sqrt $matches{$match} > $threshold) {
$self->{offenses}->{$account}->{$channel}->{last_offense} = gettimeofday;
$self->{offenses}->{$account}->{$channel}->{last_adjustment} = gettimeofday;
$self->{offenses}->{$account}->{$channel}->{offenses}++;
foreach my $match (keys %matches) {
if (sqrt $matches{$match} > $threshold) {
$self->{offenses}->{$account}->{$channel}->{last_offense} = gettimeofday;
$self->{offenses}->{$account}->{$channel}->{last_adjustment} = gettimeofday;
$self->{offenses}->{$account}->{$channel}->{offenses}++;
$self->{pbot}->{logger}->log("$nick!$user\@$host triggered anti-repeat; offense $self->{offenses}->{$account}->{$channel}->{offenses}\n");
$self->{pbot}->{logger}->log("$nick!$user\@$host triggered anti-repeat; offense $self->{offenses}->{$account}->{$channel}->{offenses}\n");
given ($self->{offenses}->{$account}->{$channel}->{offenses}) {
when (1) {
$self->{pbot}->{chanops}->add_op_command($channel, "kick $channel $nick Stop repeating yourself");
$self->{pbot}->{chanops}->gain_ops($channel);
given ($self->{offenses}->{$account}->{$channel}->{offenses}) {
when (1) {
$self->{pbot}->{chanops}->add_op_command($channel, "kick $channel $nick Stop repeating yourself");
$self->{pbot}->{chanops}->gain_ops($channel);
}
when (2) { $self->{pbot}->{chanops}->ban_user_timed($botnick, 'repeating messages', "*!*\@$host", $channel, 60); }
when (3) { $self->{pbot}->{chanops}->ban_user_timed($botnick, 'repeating messages', "*!*\@$host", $channel, 60 * 15); }
default { $self->{pbot}->{chanops}->ban_user_timed($botnick, 'repeating messages', "*!*\@$host", $channel, 60 * 60); }
}
return 0;
}
when (2) {
$self->{pbot}->{chanops}->ban_user_timed($botnick, 'repeating messages', "*!*\@$host", $channel, 60);
}
when (3) {
$self->{pbot}->{chanops}->ban_user_timed($botnick, 'repeating messages', "*!*\@$host", $channel, 60 * 15);
}
default {
$self->{pbot}->{chanops}->ban_user_timed($botnick, 'repeating messages', "*!*\@$host", $channel, 60 * 60);
}
}
return 0;
}
}
return 0;
return 0;
}
sub adjust_offenses {
my $self = shift;
my $now = gettimeofday;
my $self = shift;
my $now = gettimeofday;
foreach my $account (keys %{ $self->{offenses} }) {
foreach my $channel (keys %{ $self->{offenses}->{$account} }) {
if ($self->{offenses}->{$account}->{$channel}->{offenses} > 0 and $now - $self->{offenses}->{$account}->{$channel}->{last_adjustment} > 60 * 60 * 3) {
$self->{offenses}->{$account}->{$channel}->{offenses}--;
foreach my $account (keys %{$self->{offenses}}) {
foreach my $channel (keys %{$self->{offenses}->{$account}}) {
if ($self->{offenses}->{$account}->{$channel}->{offenses} > 0 and $now - $self->{offenses}->{$account}->{$channel}->{last_adjustment} > 60 * 60 * 3) {
$self->{offenses}->{$account}->{$channel}->{offenses}--;
if ($self->{offenses}->{$account}->{$channel}->{offenses} <= 0) {
delete $self->{offenses}->{$account}->{$channel};
if (keys %{ $self->{offenses}->{$account} } == 0) {
delete $self->{offenses}->{$account};
}
} else {
$self->{offenses}->{$account}->{$channel}->{last_adjustment} = $now;
if ($self->{offenses}->{$account}->{$channel}->{offenses} <= 0) {
delete $self->{offenses}->{$account}->{$channel};
if (keys %{$self->{offenses}->{$account}} == 0) { delete $self->{offenses}->{$account}; }
} else {
$self->{offenses}->{$account}->{$channel}->{last_adjustment} = $now;
}
}
}
}
}
}
}
1;

View File

@ -9,6 +9,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package Plugins::AntiTwitter;
use parent 'Plugins::Plugin';
use warnings; use strict;
@ -18,72 +19,75 @@ use Time::HiRes qw/gettimeofday/;
use Time::Duration qw/duration/;
use feature 'switch';
no if $] >= 5.018, warnings => "experimental::smartmatch";
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_public(@_) });
$self->{pbot}->{timer}->register(sub { $self->adjust_offenses }, 60 * 60 * 1, 'antitwitter');
$self->{offenses} = {};
my ($self, %conf) = @_;
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_public(@_) });
$self->{pbot}->{timer}->register(sub { $self->adjust_offenses }, 60 * 60 * 1, 'antitwitter');
$self->{offenses} = {};
}
sub unload {
my ($self) = @_;
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
my ($self) = @_;
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
}
sub on_public {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->{to}[0], $event->{event}->args);
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->{to}[0], $event->{event}->args);
return 0 if $event->{interpreted};
$channel = lc $channel;
return 0 if not $self->{pbot}->{chanops}->can_gain_ops($channel);
return 0 if $event->{interpreted};
$channel = lc $channel;
return 0 if not $self->{pbot}->{chanops}->can_gain_ops($channel);
while ($msg =~ m/\B[@]([a-z0-9_^{}\-\\\[\]\|]+)/ig) {
my $n = $1;
if ($self->{pbot}->{nicklist}->is_present_similar($channel, $n, 0.05)) {
$self->{offenses}->{$channel}->{$nick}->{offenses}++;
$self->{offenses}->{$channel}->{$nick}->{time} = gettimeofday;
while ($msg =~ m/\B[@]([a-z0-9_^{}\-\\\[\]\|]+)/ig) {
my $n = $1;
if ($self->{pbot}->{nicklist}->is_present_similar($channel, $n, 0.05)) {
$self->{offenses}->{$channel}->{$nick}->{offenses}++;
$self->{offenses}->{$channel}->{$nick}->{time} = gettimeofday;
$self->{pbot}->{logger}->log("$nick!$user\@$host is a twit. ($self->{offenses}->{$channel}->{$nick}->{offenses} offenses) $channel: $msg\n");
$self->{pbot}->{logger}->log("$nick!$user\@$host is a twit. ($self->{offenses}->{$channel}->{$nick}->{offenses} offenses) $channel: $msg\n");
given ($self->{offenses}->{$channel}->{$nick}->{offenses}) {
when (1) {
$event->{conn}->privmsg($nick, "Please do not use \@nick to address people. Drop the @ symbol; it's not necessary and it's ugly.");
given ($self->{offenses}->{$channel}->{$nick}->{offenses}) {
when (1) { $event->{conn}->privmsg($nick, "Please do not use \@nick to address people. Drop the @ symbol; it's not necessary and it's ugly."); }
when (2) {
$event->{conn}
->privmsg($nick, "Please do not use \@nick to address people. Drop the @ symbol; it's not necessary and it's ugly. Doing this again will result in a temporary ban.");
}
default {
my $offenses = $self->{offenses}->{$channel}->{$nick}->{offenses} - 2;
my $length = 60 * ($offenses * $offenses + 1);
$self->{pbot}->{chanops}->ban_user_timed($self->{pbot}->{registry}->get_value('irc', 'botnick'), 'using @nick too much', "*!*\@$host", $channel, $length);
$self->{pbot}->{chanops}->gain_ops($channel);
$length = duration $length;
$event->{conn}->privmsg(
$nick,
"Please do not use \@nick to address people. Drop the @ symbol; it's not necessary and it's ugly. You were warned. You will be allowed to speak again in $length."
);
}
}
last;
}
when (2) {
$event->{conn}->privmsg($nick, "Please do not use \@nick to address people. Drop the @ symbol; it's not necessary and it's ugly. Doing this again will result in a temporary ban.");
}
default {
my $offenses = $self->{offenses}->{$channel}->{$nick}->{offenses} - 2;
my $length = 60 * ($offenses * $offenses + 1);
$self->{pbot}->{chanops}->ban_user_timed($self->{pbot}->{registry}->get_value('irc', 'botnick'), 'using @nick too much', "*!*\@$host", $channel, $length);
$self->{pbot}->{chanops}->gain_ops($channel);
$length = duration $length;
$event->{conn}->privmsg($nick, "Please do not use \@nick to address people. Drop the @ symbol; it's not necessary and it's ugly. You were warned. You will be allowed to speak again in $length.");
}
}
last;
}
}
return 0;
return 0;
}
sub adjust_offenses {
my $self = shift;
my $now = gettimeofday;
my $self = shift;
my $now = gettimeofday;
foreach my $channel (keys %{ $self->{offenses} }) {
foreach my $nick (keys %{ $self->{offenses}->{$channel} }) {
if ($now - $self->{offenses}->{$channel}->{$nick}->{time} >= 60 * 60 * 24 * 7) {
if (--$self->{offenses}->{$channel}->{$nick}->{offenses} <= 0) {
delete $self->{offenses}->{$channel}->{$nick};
delete $self->{offenses}->{$channel} if not keys %{ $self->{offenses}->{$channel} };
foreach my $channel (keys %{$self->{offenses}}) {
foreach my $nick (keys %{$self->{offenses}->{$channel}}) {
if ($now - $self->{offenses}->{$channel}->{$nick}->{time} >= 60 * 60 * 24 * 7) {
if (--$self->{offenses}->{$channel}->{$nick}->{offenses} <= 0) {
delete $self->{offenses}->{$channel}->{$nick};
delete $self->{offenses}->{$channel} if not keys %{$self->{offenses}->{$channel}};
}
}
}
}
}
}
}
1;

View File

@ -8,6 +8,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package Plugins::AutoRejoin;
use parent 'Plugins::Plugin';
use warnings; use strict;
@ -17,57 +18,54 @@ use Time::HiRes qw/gettimeofday/;
use Time::Duration;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{registry}->add_default('array', 'autorejoin', 'rejoin_delay', '900,1800,3600');
$self->{pbot}->{event_dispatcher}->register_handler('irc.kick', sub { $self->on_kick(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.part', sub { $self->on_part(@_) });
$self->{rejoins} = {};
my ($self, %conf) = @_;
$self->{pbot}->{registry}->add_default('array', 'autorejoin', 'rejoin_delay', '900,1800,3600');
$self->{pbot}->{event_dispatcher}->register_handler('irc.kick', sub { $self->on_kick(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.part', sub { $self->on_part(@_) });
$self->{rejoins} = {};
}
sub unload {
my ($self) = @_;
$self->{pbot}->{event_dispatcher}->remove_handler('irc.kick');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.part');
my ($self) = @_;
$self->{pbot}->{event_dispatcher}->remove_handler('irc.kick');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.part');
}
sub rejoin_channel {
my ($self, $channel) = @_;
$self->{rejoins}->{$channel}->{rejoins} = 0 if not exists $self->{rejoins}->{$channel};
my ($self, $channel) = @_;
$self->{rejoins}->{$channel}->{rejoins} = 0 if not exists $self->{rejoins}->{$channel};
my $delay = $self->{pbot}->{registry}->get_array_value($channel, 'rejoin_delay', $self->{rejoins}->{$channel}->{rejoins});
$delay = $self->{pbot}->{registry}->get_array_value('autorejoin', 'rejoin_delay', $self->{rejoins}->{$channel}->{rejoins}) if not defined $delay;
my $delay = $self->{pbot}->{registry}->get_array_value($channel, 'rejoin_delay', $self->{rejoins}->{$channel}->{rejoins});
$delay = $self->{pbot}->{registry}->get_array_value('autorejoin', 'rejoin_delay', $self->{rejoins}->{$channel}->{rejoins}) if not defined $delay;
$self->{pbot}->{interpreter}->add_botcmd_to_command_queue($channel, "join $channel", $delay);
$self->{pbot}->{interpreter}->add_botcmd_to_command_queue($channel, "join $channel", $delay);
$delay = duration $delay;
$self->{pbot}->{logger}->log("Rejoining $channel in $delay.\n");
$self->{rejoins}->{$channel}->{last_rejoin} = gettimeofday;
$delay = duration $delay;
$self->{pbot}->{logger}->log("Rejoining $channel in $delay.\n");
$self->{rejoins}->{$channel}->{last_rejoin} = gettimeofday;
}
sub on_kick {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $target, $channel, $reason) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to, $event->{event}->{args}[0], $event->{event}->{args}[1]);
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $target, $channel, $reason) =
($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to, $event->{event}->{args}[0], $event->{event}->{args}[1]);
return 0 if not $self->{pbot}->{channels}->is_active($channel);
return 0 if $self->{pbot}->{channels}->{channels}->{hash}->{lc $channel}->{noautorejoin};
return 0 if not $self->{pbot}->{channels}->is_active($channel);
return 0 if $self->{pbot}->{channels}->{channels}->{hash}->{lc $channel}->{noautorejoin};
if ($target eq $self->{pbot}->{registry}->get_value('irc', 'botnick')) {
$self->rejoin_channel($channel);
}
return 0;
if ($target eq $self->{pbot}->{registry}->get_value('irc', 'botnick')) { $self->rejoin_channel($channel); }
return 0;
}
sub on_part {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to);
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to);
return 0 if not $self->{pbot}->{channels}->is_active($channel);
return 0 if $self->{pbot}->{channels}->{channels}->{hash}->{lc $channel}->{noautorejoin};
return 0 if not $self->{pbot}->{channels}->is_active($channel);
return 0 if $self->{pbot}->{channels}->{channels}->{hash}->{lc $channel}->{noautorejoin};
if ($nick eq $self->{pbot}->{registry}->get_value('irc', 'botnick')) {
$self->rejoin_channel($channel);
}
return 0;
if ($nick eq $self->{pbot}->{registry}->get_value('irc', 'botnick')) { $self->rejoin_channel($channel); }
return 0;
}
1;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -16,55 +16,54 @@ use feature 'unicode_strings';
use Getopt::Long qw(GetOptionsFromString);
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{registry}->add_default('text', 'date', 'default_timezone', 'UTC');
$self->{pbot}->{commands}->register(sub { $self->datecmd(@_) }, "date", 0);
my ($self, %conf) = @_;
$self->{pbot}->{registry}->add_default('text', 'date', 'default_timezone', 'UTC');
$self->{pbot}->{commands}->register(sub { $self->datecmd(@_) }, "date", 0);
}
sub unload {
my $self = shift;
$self->{pbot}->{commands}->unregister("date");
my $self = shift;
$self->{pbot}->{commands}->unregister("date");
}
sub datecmd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "date [-u <user account>] [timezone]";
my $getopt_error;
local $SIG{__WARN__} = sub {
$getopt_error = shift;
chomp $getopt_error;
};
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "date [-u <user account>] [timezone]";
my $getopt_error;
local $SIG{__WARN__} = sub {
$getopt_error = shift;
chomp $getopt_error;
};
Getopt::Long::Configure("bundling");
Getopt::Long::Configure("bundling");
my ($user_override, $show_usage);
my ($ret, $args) = GetOptionsFromString($arguments,
'u=s' => \$user_override,
'h' => \$show_usage
);
my ($user_override, $show_usage);
my ($ret, $args) = GetOptionsFromString(
$arguments,
'u=s' => \$user_override,
'h' => \$show_usage
);
return $usage if $show_usage;
return "/say $getopt_error -- $usage" if defined $getopt_error;
$arguments = "@$args";
return $usage if $show_usage;
return "/say $getopt_error -- $usage" if defined $getopt_error;
$arguments = "@$args";
my $hostmask = defined $user_override ? $user_override : "$nick!$user\@$host";
my $tz_override = $self->{pbot}->{users}->get_user_metadata($from, $hostmask, 'timezone') // '';
my $hostmask = defined $user_override ? $user_override : "$nick!$user\@$host";
my $tz_override = $self->{pbot}->{users}->get_user_metadata($from, $hostmask, 'timezone') // '';
my $timezone = $self->{pbot}->{registry}->get_value('date', 'default_timezone') // 'UTC';
$timezone = $tz_override if $tz_override;
$timezone = $arguments if length $arguments;
my $timezone = $self->{pbot}->{registry}->get_value('date', 'default_timezone') // 'UTC';
$timezone = $tz_override if $tz_override;
$timezone = $arguments if length $arguments;
if (defined $user_override and not length $tz_override) {
return "No timezone set or user account does not exist.";
}
if (defined $user_override and not length $tz_override) { return "No timezone set or user account does not exist."; }
my $newstuff = {
from => $from, nick => $nick, user => $user, host => $host,
command => "date_module $timezone", root_channel => $from, root_keyword => "date_module",
keyword => "date_module", arguments => "$timezone"
};
my $newstuff = {
from => $from, nick => $nick, user => $user, host => $host,
command => "date_module $timezone", root_channel => $from, root_keyword => "date_module",
keyword => "date_module", arguments => "$timezone"
};
$self->{pbot}->{modules}->execute_module($newstuff);
$self->{pbot}->{modules}->execute_module($newstuff);
}
1;

View File

@ -9,27 +9,28 @@ use warnings; use strict;
use feature 'unicode_strings';
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_public(@_) });
my ($self, %conf) = @_;
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_public(@_) });
}
sub unload {
my $self = shift;
# perform plugin clean-up here
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
my $self = shift;
# perform plugin clean-up here
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
}
sub on_public {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
if ($event->{interpreted}) {
$self->{pbot}->{logger}->log("Message was already handled by the interpreter.\n");
if ($event->{interpreted}) {
$self->{pbot}->{logger}->log("Message was already handled by the interpreter.\n");
return 0;
}
$self->{pbot}->{logger}->log("Example plugin: got message from $nick!$user\@$host: $msg\n");
return 0;
}
$self->{pbot}->{logger}->log("Example plugin: got message from $nick!$user\@$host: $msg\n");
return 0;
}
1;

View File

@ -14,106 +14,108 @@ use warnings; use strict;
use feature 'unicode_strings';
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{functions}->register(
'title',
{
desc => 'Title-cases text',
usage => 'title <text>',
subref => sub { $self->func_title(@_) }
}
);
$self->{pbot}->{functions}->register(
'ucfirst',
{
desc => 'Uppercases first character',
usage => 'ucfirst <text>',
subref => sub { $self->func_ucfirst(@_) }
}
);
$self->{pbot}->{functions}->register(
'uc',
{
desc => 'Uppercases all characters',
usage => 'uc <text>',
subref => sub { $self->func_uc(@_) }
}
);
$self->{pbot}->{functions}->register(
'lc',
{
desc => 'Lowercases all characters',
usage => 'lc <text>',
subref => sub { $self->func_lc(@_) }
}
);
$self->{pbot}->{functions}->register(
'unquote',
{
desc => 'removes unescaped surrounding quotes and strips escapes from escaped quotes',
usage => 'unquote <text>',
subref => sub { $self->func_unquote(@_) }
}
);
$self->{pbot}->{functions}->register('uri_escape',
{
desc => 'percent-encode unsafe URI characters',
usage => 'uri_escape <text>',
subref => sub { $self->func_uri_escape(@_) }
}
);
my ($self, %conf) = @_;
$self->{pbot}->{functions}->register(
'title',
{
desc => 'Title-cases text',
usage => 'title <text>',
subref => sub { $self->func_title(@_) }
}
);
$self->{pbot}->{functions}->register(
'ucfirst',
{
desc => 'Uppercases first character',
usage => 'ucfirst <text>',
subref => sub { $self->func_ucfirst(@_) }
}
);
$self->{pbot}->{functions}->register(
'uc',
{
desc => 'Uppercases all characters',
usage => 'uc <text>',
subref => sub { $self->func_uc(@_) }
}
);
$self->{pbot}->{functions}->register(
'lc',
{
desc => 'Lowercases all characters',
usage => 'lc <text>',
subref => sub { $self->func_lc(@_) }
}
);
$self->{pbot}->{functions}->register(
'unquote',
{
desc => 'removes unescaped surrounding quotes and strips escapes from escaped quotes',
usage => 'unquote <text>',
subref => sub { $self->func_unquote(@_) }
}
);
$self->{pbot}->{functions}->register(
'uri_escape',
{
desc => 'percent-encode unsafe URI characters',
usage => 'uri_escape <text>',
subref => sub { $self->func_uri_escape(@_) }
}
);
}
sub unload {
my $self = shift;
$self->{pbot}->{functions}->unregister('title');
$self->{pbot}->{functions}->unregister('ucfirst');
$self->{pbot}->{functions}->unregister('uc');
$self->{pbot}->{functions}->unregister('lc');
$self->{pbot}->{functions}->unregister('unquote');
$self->{pbot}->{functions}->unregister('uri_escape');
my $self = shift;
$self->{pbot}->{functions}->unregister('title');
$self->{pbot}->{functions}->unregister('ucfirst');
$self->{pbot}->{functions}->unregister('uc');
$self->{pbot}->{functions}->unregister('lc');
$self->{pbot}->{functions}->unregister('unquote');
$self->{pbot}->{functions}->unregister('uri_escape');
}
sub func_unquote {
my $self = shift;
my $text = "@_";
$text =~ s/^"(.*?)(?<!\\)"$/$1/ || $text =~ s/^'(.*?)(?<!\\)'$/$1/;
$text =~ s/(?<!\\)\\'/'/g;
$text =~ s/(?<!\\)\\"/"/g;
return $text;
my $self = shift;
my $text = "@_";
$text =~ s/^"(.*?)(?<!\\)"$/$1/ || $text =~ s/^'(.*?)(?<!\\)'$/$1/;
$text =~ s/(?<!\\)\\'/'/g;
$text =~ s/(?<!\\)\\"/"/g;
return $text;
}
sub func_title {
my $self = shift;
my $text = "@_";
$text = ucfirst lc $text;
$text =~ s/ (\w)/' ' . uc $1/ge;
return $text;
my $self = shift;
my $text = "@_";
$text = ucfirst lc $text;
$text =~ s/ (\w)/' ' . uc $1/ge;
return $text;
}
sub func_ucfirst {
my $self = shift;
my $text = "@_";
return ucfirst $text;
my $self = shift;
my $text = "@_";
return ucfirst $text;
}
sub func_uc {
my $self = shift;
my $text = "@_";
return uc $text;
my $self = shift;
my $text = "@_";
return uc $text;
}
sub func_lc {
my $self = shift;
my $text = "@_";
return lc $text;
my $self = shift;
my $text = "@_";
return lc $text;
}
use URI::Escape qw/uri_escape_utf8/;
sub func_uri_escape {
my $self = shift;
my $text = "@_";
return uri_escape_utf8($text);
my $self = shift;
my $text = "@_";
return uri_escape_utf8($text);
}
1;

View File

@ -14,51 +14,46 @@ use warnings; use strict;
use feature 'unicode_strings';
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{functions}->register(
'sed',
{
desc => 'a sed-like stream editor',
usage => 'sed s/<regex>/<replacement>/[Pig]; P preserve case; i ignore case; g replace all',
subref => sub { $self->func_sed(@_) }
}
);
my ($self, %conf) = @_;
$self->{pbot}->{functions}->register(
'sed',
{
desc => 'a sed-like stream editor',
usage => 'sed s/<regex>/<replacement>/[Pig]; P preserve case; i ignore case; g replace all',
subref => sub { $self->func_sed(@_) }
}
);
}
sub unload {
my $self = shift;
$self->{pbot}->{functions}->unregister('sed');
my $self = shift;
$self->{pbot}->{functions}->unregister('sed');
}
# near-verbatim insertion of krok's `sed` factoid
no warnings;
sub func_sed {
my $self = shift;
my $text = "@_";
my $self = shift;
my $text = "@_";
if ($text =~ /^s(.)(.*?)(?<!\\)\1(.*?)(?<!\\)\1(\S*)\s+(.*)/p) {
my ($a, $r, $g, $m, $t) = ($5,"'\"$3\"'", index($4,"g") != -1, $4, $2);
if ($text =~ /^s(.)(.*?)(?<!\\)\1(.*?)(?<!\\)\1(\S*)\s+(.*)/p) {
my ($a, $r, $g, $m, $t) = ($5, "'\"$3\"'", index($4, "g") != -1, $4, $2);
if ($m=~/P/) {
$r =~ s/^'"(.*)"'$/$1/;
$m=~s/P//g;
if ($m =~ /P/) {
$r =~ s/^'"(.*)"'$/$1/;
$m =~ s/P//g;
if($g) {
$a =~ s|(?$m)($t)|$1=~/^[A-Z][^A-Z]/?ucfirst$r:($1=~/^[A-Z]+$/?uc$r:$r)|gie;
} else {
$a =~ s|(?$m)($t)|$1=~/^[A-Z][^A-Z]/?ucfirst$r:($1=~/^[A-Z]+$/?uc$r:$r)|ie;
}
if ($g) { $a =~ s|(?$m)($t)|$1=~/^[A-Z][^A-Z]/?ucfirst$r:($1=~/^[A-Z]+$/?uc$r:$r)|gie; }
else { $a =~ s|(?$m)($t)|$1=~/^[A-Z][^A-Z]/?ucfirst$r:($1=~/^[A-Z]+$/?uc$r:$r)|ie; }
} else {
if ($g) { $a =~ s/(?$m)$t/$r/geee; }
else { $a =~ s/(?$m)$t/$r/eee; }
}
return $a;
} else {
if ($g) {
$a =~ s/(?$m)$t/$r/geee;
} else {
$a=~s/(?$m)$t/$r/eee;
}
return "sed: syntax error";
}
return $a;
} else {
return "sed: syntax error";
}
}
use warnings;

View File

@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package Plugins::GoogleSearch;
use parent 'Plugins::Plugin';
use warnings; use strict;
@ -14,80 +15,80 @@ use WWW::Google::CustomSearch;
use HTML::Entities;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{registry}->add_default('text', 'googlesearch', 'api_key', '');
$self->{pbot}->{registry}->add_default('text', 'googlesearch', 'context', '');
my ($self, %conf) = @_;
$self->{pbot}->{registry}->add_default('text', 'googlesearch', 'api_key', '');
$self->{pbot}->{registry}->add_default('text', 'googlesearch', 'context', '');
$self->{pbot}->{registry}->set_default('googlesearch', 'api_key', 'private', 1);
$self->{pbot}->{registry}->set_default('googlesearch', 'context', 'private', 1);
$self->{pbot}->{registry}->set_default('googlesearch', 'api_key', 'private', 1);
$self->{pbot}->{registry}->set_default('googlesearch', 'context', 'private', 1);
$self->{pbot}->{commands}->register(sub { $self->googlesearch(@_) }, 'google', 0);
$self->{pbot}->{commands}->register(sub { $self->googlesearch(@_) }, 'google', 0);
}
sub unload {
my $self = shift;
$self->{pbot}->{commands}->unregister('google');
my $self = shift;
$self->{pbot}->{commands}->unregister('google');
}
sub googlesearch {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
return "Usage: google [number of results] query\n" if not length $arguments;
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
return "Usage: google [number of results] query\n" if not length $arguments;
my $matches = 1;
$matches = $1 if $arguments =~ s/^-n\s+([0-9]+)\s*//;
my $matches = 1;
$matches = $1 if $arguments =~ s/^-n\s+([0-9]+)\s*//;
my $api_key = $self->{pbot}->{registry}->get_value('googlesearch', 'api_key'); # https://developers.google.com/custom-search/v1/overview
my $cx = $self->{pbot}->{registry}->get_value('googlesearch', 'context'); # https://cse.google.com/all
my $api_key = $self->{pbot}->{registry}->get_value('googlesearch', 'api_key'); # https://developers.google.com/custom-search/v1/overview
my $cx = $self->{pbot}->{registry}->get_value('googlesearch', 'context'); # https://cse.google.com/all
if (not length $api_key) {
return "$nick: Registry item googlesearch.api_key is not set. See https://developers.google.com/custom-search/v1/overview to get an API key.";
}
if (not length $cx) {
return "$nick: Registry item googlesearch.context is not set. See https://cse.google.com/all to set up a context.";
}
my $engine = WWW::Google::CustomSearch->new(api_key => $api_key, cx => $cx, quotaUser => "$nick!$user\@$host");
if ($arguments =~ m/(.*)\svs\s(.*)/i) {
my ($a, $b) = ($1, $2);
my $result1 = $engine->search("\"$a\" -\"$b\"");
my $result2 = $engine->search("\"$b\" -\"$a\"");
if (not defined $result1 or not defined $result1->items or not @{$result1->items}) {
return "$nick: No results for $a";
if (not length $api_key) {
return "$nick: Registry item googlesearch.api_key is not set. See https://developers.google.com/custom-search/v1/overview to get an API key.";
}
if (not defined $result2 or not defined $result2->items or not @{$result2->items}) {
return "$nick: No results for $b";
if (not length $cx) { return "$nick: Registry item googlesearch.context is not set. See https://cse.google.com/all to set up a context."; }
my $engine = WWW::Google::CustomSearch->new(api_key => $api_key, cx => $cx, quotaUser => "$nick!$user\@$host");
if ($arguments =~ m/(.*)\svs\s(.*)/i) {
my ($a, $b) = ($1, $2);
my $result1 = $engine->search("\"$a\" -\"$b\"");
my $result2 = $engine->search("\"$b\" -\"$a\"");
if (not defined $result1 or not defined $result1->items or not @{$result1->items}) { return "$nick: No results for $a"; }
if (not defined $result2 or not defined $result2->items or not @{$result2->items}) { return "$nick: No results for $b"; }
my $title1 = $result1->items->[0]->title;
my $title2 = $result2->items->[0]->title;
utf8::decode $title1;
utf8::decode $title2;
return
"$nick: $a: ("
. $result1->formattedTotalResults . ") "
. decode_entities($title1) . " <"
. $result1->items->[0]->link
. "> VS $b: ("
. $result2->formattedTotalResults . ") "
. decode_entities($title2) . " <"
. $result2->items->[0]->link . ">";
}
my $title1 = $result1->items->[0]->title;
my $title2 = $result2->items->[0]->title;
my $result = $engine->search($arguments);
utf8::decode $title1;
utf8::decode $title2;
if (not defined $result or not defined $result->items or not @{$result->items}) { return "$nick: No results found"; }
return "$nick: $a: (" . $result1->formattedTotalResults . ") " . decode_entities($title1) . " <" . $result1->items->[0]->link . "> VS $b: (" . $result2->formattedTotalResults . ") " . decode_entities($title2) . " <" . $result2->items->[0]->link . ">";
}
my $output = "$nick: (" . $result->formattedTotalResults . " results) ";
my $result = $engine->search($arguments);
if (not defined $result or not defined $result->items or not @{$result->items}) {
return "$nick: No results found";
}
my $output = "$nick: (" . $result->formattedTotalResults . " results) ";
my $comma = "";
foreach my $item (@{$result->items}) {
my $title = $item->title;
utf8::decode $title;
$output .= $comma . decode_entities($title) . ': <' . $item->link . ">";
$comma = " -- ";
last if --$matches <= 0;
}
return $output;
my $comma = "";
foreach my $item (@{$result->items}) {
my $title = $item->title;
utf8::decode $title;
$output .= $comma . decode_entities($title) . ': <' . $item->link . ">";
$comma = " -- ";
last if --$matches <= 0;
}
return $output;
}
1;

View File

@ -14,21 +14,21 @@ use warnings; use strict;
use feature 'unicode_strings';
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { return $self->magic(@_)}, "mc", 90);
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { return $self->magic(@_) }, "mc", 90);
}
sub unload {
my $self = shift;
$self->{pbot}->{commands}->unregister("mc");
my $self = shift;
$self->{pbot}->{commands}->unregister("mc");
}
sub magic {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
# do something magical!
return "Did something magical.";
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
# do something magical!
return "Did something magical.";
}
1;

View File

@ -13,21 +13,21 @@ use feature 'unicode_strings';
use Time::Duration qw/duration/;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { return $self->pd(@_)}, "pd", 0);
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { return $self->pd(@_) }, "pd", 0);
}
sub unload {
my $self = shift;
$self->{pbot}->{commands}->unregister("pd");
my $self = shift;
$self->{pbot}->{commands}->unregister("pd");
}
sub pd {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my ($seconds, $error) = $self->{pbot}->{parsedate}->parsedate($arguments);
return $error if defined $error;
return duration $seconds;
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my ($seconds, $error) = $self->{pbot}->{parsedate}->parsedate($arguments);
return $error if defined $error;
return duration $seconds;
}
1;

View File

@ -9,31 +9,31 @@ package Plugins::Plugin;
use warnings; use strict;
sub new {
my ($proto, %conf) = @_;
my $class = ref($proto) || $proto;
my $self = bless {}, $class;
my ($proto, %conf) = @_;
my $class = ref($proto) || $proto;
my $self = bless {}, $class;
if (not exists $conf{pbot}) {
my ($package, $filename, $line) = caller(0);
my (undef, undef, undef, $subroutine) = caller(1);
Carp::croak("Missing pbot reference to " . $class . ", created by $subroutine at $filename:$line");
}
if (not exists $conf{pbot}) {
my ($package, $filename, $line) = caller(0);
my (undef, undef, undef, $subroutine) = caller(1);
Carp::croak("Missing pbot reference to " . $class . ", created by $subroutine at $filename:$line");
}
$self->{pbot} = $conf{pbot};
$self->initialize(%conf);
return $self;
$self->{pbot} = $conf{pbot};
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($package, $filename, $line) = caller(0);
my (undef, undef, undef, $subroutine) = caller(1);
Carp::croak("Missing initialize subroutine in $subroutine at $filename:$line");
my ($package, $filename, $line) = caller(0);
my (undef, undef, undef, $subroutine) = caller(1);
Carp::croak("Missing initialize subroutine in $subroutine at $filename:$line");
}
sub unload {
my ($package, $filename, $line) = caller(0);
my (undef, undef, undef, $subroutine) = caller(1);
Carp::croak("Missing unload subroutine in $subroutine at $filename:$line");
my ($package, $filename, $line) = caller(0);
my (undef, undef, undef, $subroutine) = caller(1);
Carp::croak("Missing unload subroutine in $subroutine at $filename:$line");
}
1;

View File

@ -8,6 +8,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package Plugins::Quotegrabs;
use parent 'Plugins::Plugin';
use warnings; use strict;
@ -18,372 +19,341 @@ use Time::Duration;
use Time::HiRes qw(gettimeofday);
use Getopt::Long qw(GetOptionsFromArray);
use Plugins::Quotegrabs::Quotegrabs_SQLite; # use SQLite backend for quotegrabs database
use Plugins::Quotegrabs::Quotegrabs_SQLite; # use SQLite backend for quotegrabs database
#use Plugins::Quotegrabs::Quotegrabs_Hashtable; # use Perl hashtable backend for quotegrabs database
use PBot::Utils::ValidateString;
use POSIX qw(strftime);
sub initialize {
my ($self, %conf) = @_;
$self->{filename} = $conf{quotegrabs_file} // $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/quotegrabs.sqlite3';
my ($self, %conf) = @_;
$self->{filename} = $conf{quotegrabs_file} // $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/quotegrabs.sqlite3';
$self->{database} = Plugins::Quotegrabs::Quotegrabs_SQLite->new(pbot => $self->{pbot}, filename => $self->{filename});
#$self->{database} = Plugins::Quotegrabs::Quotegrabs_Hashtable->new(pbot => $self->{pbot}, filename => $self->{filename});
$self->{database}->begin();
$self->{database} = Plugins::Quotegrabs::Quotegrabs_SQLite->new(pbot => $self->{pbot}, filename => $self->{filename});
$self->{pbot}->{atexit}->register(sub { $self->{database}->end(); return; });
#$self->{database} = Plugins::Quotegrabs::Quotegrabs_Hashtable->new(pbot => $self->{pbot}, filename => $self->{filename});
$self->{database}->begin();
$self->{pbot}->{commands}->register(sub { $self->grab_quotegrab(@_) }, 'grab', 0);
$self->{pbot}->{commands}->register(sub { $self->show_quotegrab(@_) }, 'getq', 0);
$self->{pbot}->{commands}->register(sub { $self->delete_quotegrab(@_) }, 'delq', 0);
$self->{pbot}->{commands}->register(sub { $self->show_random_quotegrab(@_) }, 'rq', 0);
$self->{pbot}->{atexit}->register(sub { $self->{database}->end(); return; });
$self->{pbot}->{commands}->register(sub { $self->grab_quotegrab(@_) }, 'grab', 0);
$self->{pbot}->{commands}->register(sub { $self->show_quotegrab(@_) }, 'getq', 0);
$self->{pbot}->{commands}->register(sub { $self->delete_quotegrab(@_) }, 'delq', 0);
$self->{pbot}->{commands}->register(sub { $self->show_random_quotegrab(@_) }, 'rq', 0);
}
sub unload {
my ($self) = @_;
$self->{pbot}->{commands}->unregister('grab');
$self->{pbot}->{commands}->unregister('getq');
$self->{pbot}->{commands}->unregister('delq');
$self->{pbot}->{commands}->unregister('rq');
my ($self) = @_;
$self->{pbot}->{commands}->unregister('grab');
$self->{pbot}->{commands}->unregister('getq');
$self->{pbot}->{commands}->unregister('delq');
$self->{pbot}->{commands}->unregister('rq');
}
sub uniq { my %seen; grep !$seen{$_}++, @_ }
sub export_quotegrabs {
my $self = shift;
my $self = shift;
$self->{export_path} = $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/quotegrabs.html';
$self->{export_path} = $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/quotegrabs.html';
my $quotegrabs = $self->{database}->get_all_quotegrabs();
my $quotegrabs = $self->{database}->get_all_quotegrabs();
my $text;
my $table_id = 1;
my $had_table = 0;
open FILE, "> $self->{export_path}" or return "Could not open export path.";
my $time = localtime;
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
print FILE "<html>\n<head><link href=\"css/blue.css\" rel=\"stylesheet\" type=\"text/css\">\n";
print FILE '<script type="text/javascript" src="js/jquery-latest.js"></script>' . "\n";
print FILE '<script type="text/javascript" src="js/jquery.tablesorter.js"></script>' . "\n";
print FILE '<script type="text/javascript" src="js/picnet.table.filter.min.js"></script>' . "\n";
print FILE "</head>\n<body><i>Generated at $time</i><hr><h2>$botnick\'s Quotegrabs</h2>\n";
my $i = 0;
my $text;
my $table_id = 1;
my $had_table = 0;
open FILE, "> $self->{export_path}" or return "Could not open export path.";
my $time = localtime;
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
print FILE "<html>\n<head><link href=\"css/blue.css\" rel=\"stylesheet\" type=\"text/css\">\n";
print FILE '<script type="text/javascript" src="js/jquery-latest.js"></script>' . "\n";
print FILE '<script type="text/javascript" src="js/jquery.tablesorter.js"></script>' . "\n";
print FILE '<script type="text/javascript" src="js/picnet.table.filter.min.js"></script>' . "\n";
print FILE "</head>\n<body><i>Generated at $time</i><hr><h2>$botnick\'s Quotegrabs</h2>\n";
my $i = 0;
my $last_channel = "";
foreach my $quotegrab (sort { $$a{channel} cmp $$b{channel} or $$a{nick} cmp $$b{nick} } @$quotegrabs) {
if (not $quotegrab->{channel} =~ /^$last_channel$/i) {
print FILE "<a href='#" . encode_entities($quotegrab->{channel}) . "'>" . encode_entities($quotegrab->{channel}) . "</a><br>\n";
$last_channel = $quotegrab->{channel};
}
}
$last_channel = "";
foreach my $quotegrab (sort { $$a{channel} cmp $$b{channel} or lc $$a{nick} cmp lc $$b{nick} } @$quotegrabs) {
if (not $quotegrab->{channel} =~ /^$last_channel$/i) {
print FILE "</tbody>\n</table>\n" if $had_table;
print FILE "<a name='" . encode_entities($quotegrab->{channel}) . "'></a>\n";
print FILE "<hr><h3>" . encode_entities($quotegrab->{channel}) . "</h3><hr>\n";
print FILE "<table border=\"0\" id=\"table$table_id\" class=\"tablesorter\">\n";
print FILE "<thead>\n<tr>\n";
print FILE "<th>id&nbsp;&nbsp;&nbsp;&nbsp;</th>\n";
print FILE "<th>author(s)</th>\n";
print FILE "<th>quote</th>\n";
print FILE "<th>date</th>\n";
print FILE "<th>grabbed by</th>\n";
print FILE "</tr>\n</thead>\n<tbody>\n";
$had_table = 1;
$table_id++;
my $last_channel = "";
foreach my $quotegrab (sort { $$a{channel} cmp $$b{channel} or $$a{nick} cmp $$b{nick} } @$quotegrabs) {
if (not $quotegrab->{channel} =~ /^$last_channel$/i) {
print FILE "<a href='#" . encode_entities($quotegrab->{channel}) . "'>" . encode_entities($quotegrab->{channel}) . "</a><br>\n";
$last_channel = $quotegrab->{channel};
}
}
$last_channel = $quotegrab->{channel};
$i++;
$last_channel = "";
foreach my $quotegrab (sort { $$a{channel} cmp $$b{channel} or lc $$a{nick} cmp lc $$b{nick} } @$quotegrabs) {
if (not $quotegrab->{channel} =~ /^$last_channel$/i) {
print FILE "</tbody>\n</table>\n" if $had_table;
print FILE "<a name='" . encode_entities($quotegrab->{channel}) . "'></a>\n";
print FILE "<hr><h3>" . encode_entities($quotegrab->{channel}) . "</h3><hr>\n";
print FILE "<table border=\"0\" id=\"table$table_id\" class=\"tablesorter\">\n";
print FILE "<thead>\n<tr>\n";
print FILE "<th>id&nbsp;&nbsp;&nbsp;&nbsp;</th>\n";
print FILE "<th>author(s)</th>\n";
print FILE "<th>quote</th>\n";
print FILE "<th>date</th>\n";
print FILE "<th>grabbed by</th>\n";
print FILE "</tr>\n</thead>\n<tbody>\n";
$had_table = 1;
$table_id++;
}
if ($i % 2) {
print FILE "<tr bgcolor=\"#dddddd\">\n";
} else {
print FILE "<tr>\n";
$last_channel = $quotegrab->{channel};
$i++;
if ($i % 2) { print FILE "<tr bgcolor=\"#dddddd\">\n"; }
else { print FILE "<tr>\n"; }
print FILE "<td>" . ($quotegrab->{id}) . "</td>";
my @nicks = split /\+/, $quotegrab->{nick};
$text = join ', ', uniq(@nicks);
print FILE "<td>" . encode_entities($text) . "</td>";
my $nick;
$text = $quotegrab->{text};
if ($text =~ s/^\/me\s+//) { $nick = "* $nicks[0]"; }
else { $nick = "<$nicks[0]>"; }
$text = "<td><b>" . encode_entities($nick) . "</b> " . encode_entities($text) . "</td>\n";
print FILE $text;
print FILE "<td>" . encode_entities(strftime "%Y/%m/%d %a %H:%M:%S", localtime $quotegrab->{timestamp}) . "</td>\n";
print FILE "<td>" . encode_entities($quotegrab->{grabbed_by}) . "</td>\n";
print FILE "</tr>\n";
}
print FILE "<td>" . ($quotegrab->{id}) . "</td>";
my @nicks = split /\+/, $quotegrab->{nick};
$text = join ', ', uniq(@nicks);
print FILE "<td>" . encode_entities($text) . "</td>";
my $nick;
$text = $quotegrab->{text};
if ($text =~ s/^\/me\s+//) {
$nick = "* $nicks[0]";
} else {
$nick = "<$nicks[0]>";
}
$text = "<td><b>". encode_entities($nick) . "</b> " . encode_entities($text) . "</td>\n";
print FILE $text;
print FILE "<td>" . encode_entities(strftime "%Y/%m/%d %a %H:%M:%S", localtime $quotegrab->{timestamp}) . "</td>\n";
print FILE "<td>" . encode_entities($quotegrab->{grabbed_by}) . "</td>\n";
print FILE "</tr>\n";
}
print FILE "</tbody>\n</table>\n" if $had_table;
print FILE "<script type='text/javascript'>\n";
$table_id--;
print FILE '$(document).ready(function() {' . "\n";
while ($table_id > 0) {
print FILE '$("#table' . $table_id . '").tablesorter();' . "\n";
print FILE '$("#table' . $table_id . '").tableFilter();' . "\n";
print FILE "</tbody>\n</table>\n" if $had_table;
print FILE "<script type='text/javascript'>\n";
$table_id--;
}
print FILE "});\n";
print FILE "</script>\n";
print FILE "</body>\n</html>\n";
close(FILE);
return "$i quotegrabs exported.";
print FILE '$(document).ready(function() {' . "\n";
while ($table_id > 0) {
print FILE '$("#table' . $table_id . '").tablesorter();' . "\n";
print FILE '$("#table' . $table_id . '").tableFilter();' . "\n";
$table_id--;
}
print FILE "});\n";
print FILE "</script>\n";
print FILE "</body>\n</html>\n";
close(FILE);
return "$i quotegrabs exported.";
}
sub grab_quotegrab {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
if (not defined $from) {
$self->{pbot}->{logger}->log("Command missing ~from parameter!\n");
return "";
}
if (not defined $arguments or not length $arguments) {
return "Usage: grab <nick> [history [channel]] [+ <nick> [history [channel]] ...] -- where [history] is an optional regex argument; e.g., to grab a message containing 'pizza', use `grab nick pizza`; you can chain grabs with + to grab multiple messages";
}
$arguments = lc $arguments;
my @grabs = split /\s\+\s/, $arguments;
my ($grab_nick, $grab_history, $channel, $grab_nicks, $grab_text);
foreach my $grab (@grabs) {
($grab_nick, $grab_history, $channel) = $self->{pbot}->{interpreter}->split_line($grab, strip_quotes => 1);
$grab_history = $nick eq $grab_nick ? 2 : 1 if not defined $grab_history; # skip grab command if grabbing self without arguments
$channel = $from if not defined $channel;
if (not $channel =~ m/^#/) {
return "'$channel' is not a valid channel; usage: grab <nick> [[history] channel] (you must specify a history parameter before the channel parameter)";
if (not defined $from) {
$self->{pbot}->{logger}->log("Command missing ~from parameter!\n");
return "";
}
my ($account, $found_nick) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($grab_nick);
if (not defined $account) {
return "I don't know anybody named $grab_nick";
if (not defined $arguments or not length $arguments) {
return
"Usage: grab <nick> [history [channel]] [+ <nick> [history [channel]] ...] -- where [history] is an optional regex argument; e.g., to grab a message containing 'pizza', use `grab nick pizza`; you can chain grabs with + to grab multiple messages";
}
$found_nick =~ s/!.*$//;
$arguments = lc $arguments;
$grab_nick = $found_nick; # convert nick to proper casing
my @grabs = split /\s\+\s/, $arguments;
my $message;
my ($grab_nick, $grab_history, $channel, $grab_nicks, $grab_text);
if ($grab_history =~ /^\d+$/) {
# integral history
my $max_messages = $self->{pbot}->{messagehistory}->{database}->get_max_messages($account, $channel);
if ($grab_history < 1 || $grab_history > $max_messages) {
return "Please choose a history between 1 and $max_messages";
}
foreach my $grab (@grabs) {
($grab_nick, $grab_history, $channel) = $self->{pbot}->{interpreter}->split_line($grab, strip_quotes => 1);
$grab_history--;
$grab_history = $nick eq $grab_nick ? 2 : 1 if not defined $grab_history; # skip grab command if grabbing self without arguments
$channel = $from if not defined $channel;
$message = $self->{pbot}->{messagehistory}->{database}->recall_message_by_count($account, $channel, $grab_history, 'grab');
if (not $channel =~ m/^#/) {
return "'$channel' is not a valid channel; usage: grab <nick> [[history] channel] (you must specify a history parameter before the channel parameter)";
}
my ($account, $found_nick) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($grab_nick);
if (not defined $account) { return "I don't know anybody named $grab_nick"; }
$found_nick =~ s/!.*$//;
$grab_nick = $found_nick; # convert nick to proper casing
my $message;
if ($grab_history =~ /^\d+$/) {
# integral history
my $max_messages = $self->{pbot}->{messagehistory}->{database}->get_max_messages($account, $channel);
if ($grab_history < 1 || $grab_history > $max_messages) { return "Please choose a history between 1 and $max_messages"; }
$grab_history--;
$message = $self->{pbot}->{messagehistory}->{database}->recall_message_by_count($account, $channel, $grab_history, 'grab');
} else {
# regex history
$message = $self->{pbot}->{messagehistory}->{database}->recall_message_by_text($account, $channel, $grab_history, 'grab');
if (not defined $message) { return "No such message for nick $grab_nick in channel $channel containing text '$grab_history'"; }
}
$self->{pbot}->{logger}->log("$nick ($from) grabbed <$grab_nick/$channel> $message->{msg}\n");
if (not defined $grab_nicks) { $grab_nicks = $grab_nick; }
else { $grab_nicks .= "+$grab_nick"; }
my $text = $message->{msg};
if (not defined $grab_text) { $grab_text = $text; }
else {
if ($text =~ s/^\/me\s+//) { $grab_text .= " * $grab_nick $text"; }
else { $grab_text .= " <$grab_nick> $text"; }
}
}
my $quotegrab = {};
$quotegrab->{nick} = $grab_nicks;
$quotegrab->{channel} = $channel;
$quotegrab->{timestamp} = gettimeofday;
$quotegrab->{grabbed_by} = "$nick!$user\@$host";
$quotegrab->{text} = validate_string($grab_text);
$quotegrab->{id} = undef;
$quotegrab->{id} = $self->{database}->add_quotegrab($quotegrab);
if (not defined $quotegrab->{id}) { return "Failed to grab quote."; }
$self->export_quotegrabs();
my $text = $quotegrab->{text};
($grab_nick) = split /\+/, $grab_nicks, 2;
if ($text =~ s/^(NICKCHANGE)\b/changed nick to/ or $text =~ s/^(KICKED|QUIT)\b/lc "$1"/e or $text =~ s/^(JOIN|PART)\b/lc "$1ed"/e) {
# fix ugly "[nick] quit Quit: Leaving." messages
$text =~ s/^(quit) (.*)/$1 ($2)/;
return "Quote grabbed: $quotegrab->{id}: $grab_nick $text";
} elsif ($text =~ s/^\/me\s+//) {
return "Quote grabbed: $quotegrab->{id}: * $grab_nick $text";
} else {
# regex history
$message = $self->{pbot}->{messagehistory}->{database}->recall_message_by_text($account, $channel, $grab_history, 'grab');
if (not defined $message) {
return "No such message for nick $grab_nick in channel $channel containing text '$grab_history'";
}
return "Quote grabbed: $quotegrab->{id}: <$grab_nick> $text";
}
$self->{pbot}->{logger}->log("$nick ($from) grabbed <$grab_nick/$channel> $message->{msg}\n");
if (not defined $grab_nicks) {
$grab_nicks = $grab_nick;
} else {
$grab_nicks .= "+$grab_nick";
}
my $text = $message->{msg};
if (not defined $grab_text) {
$grab_text = $text;
} else {
if ($text =~ s/^\/me\s+//) {
$grab_text .= " * $grab_nick $text";
} else {
$grab_text .= " <$grab_nick> $text";
}
}
}
my $quotegrab = {};
$quotegrab->{nick} = $grab_nicks;
$quotegrab->{channel} = $channel;
$quotegrab->{timestamp} = gettimeofday;
$quotegrab->{grabbed_by} = "$nick!$user\@$host";
$quotegrab->{text} = validate_string($grab_text);
$quotegrab->{id} = undef;
$quotegrab->{id} = $self->{database}->add_quotegrab($quotegrab);
if (not defined $quotegrab->{id}) {
return "Failed to grab quote.";
}
$self->export_quotegrabs();
my $text = $quotegrab->{text};
($grab_nick) = split /\+/, $grab_nicks, 2;
if ($text =~ s/^(NICKCHANGE)\b/changed nick to/ or
$text =~ s/^(KICKED|QUIT)\b/lc "$1"/e or
$text =~ s/^(JOIN|PART)\b/lc "$1ed"/e) {
# fix ugly "[nick] quit Quit: Leaving." messages
$text =~ s/^(quit) (.*)/$1 ($2)/;
return "Quote grabbed: $quotegrab->{id}: $grab_nick $text";
} elsif ($text =~ s/^\/me\s+//) {
return "Quote grabbed: $quotegrab->{id}: * $grab_nick $text";
} else {
return "Quote grabbed: $quotegrab->{id}: <$grab_nick> $text";
}
}
sub delete_quotegrab {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $quotegrab = $self->{database}->get_quotegrab($arguments);
my $quotegrab = $self->{database}->get_quotegrab($arguments);
if (not defined $quotegrab) {
return "/msg $nick No quotegrab matching id $arguments found.";
}
if (not defined $quotegrab) { return "/msg $nick No quotegrab matching id $arguments found."; }
if (not $self->{pbot}->{users}->loggedin_admin($from, "$nick!$user\@$host") and $quotegrab->{grabbed_by} ne "$nick!$user\@$host") {
return "You are not the grabber of this quote.";
}
if (not $self->{pbot}->{users}->loggedin_admin($from, "$nick!$user\@$host") and $quotegrab->{grabbed_by} ne "$nick!$user\@$host") {
return "You are not the grabber of this quote.";
}
$self->{database}->delete_quotegrab($arguments);
$self->export_quotegrabs();
$self->{database}->delete_quotegrab($arguments);
$self->export_quotegrabs();
my $text = $quotegrab->{text};
my $text = $quotegrab->{text};
my ($first_nick) = split /\+/, $quotegrab->{nick}, 2;
my ($first_nick) = split /\+/, $quotegrab->{nick}, 2;
if ($text =~ s/^\/me\s+//) {
return "Deleted $arguments: * $first_nick $text";
} else {
return "Deleted $arguments: <$first_nick> $text";
}
if ($text =~ s/^\/me\s+//) { return "Deleted $arguments: * $first_nick $text"; }
else { return "Deleted $arguments: <$first_nick> $text"; }
}
sub show_quotegrab {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $quotegrab = $self->{database}->get_quotegrab($arguments);
my $quotegrab = $self->{database}->get_quotegrab($arguments);
if (not defined $quotegrab) {
return "/msg $nick No quotegrab matching id $arguments found.";
}
if (not defined $quotegrab) { return "/msg $nick No quotegrab matching id $arguments found."; }
my $timestamp = $quotegrab->{timestamp};
my $ago = ago(gettimeofday - $timestamp);
my $text = $quotegrab->{text};
my ($first_nick) = split /\+/, $quotegrab->{nick}, 2;
my $timestamp = $quotegrab->{timestamp};
my $ago = ago(gettimeofday - $timestamp);
my $text = $quotegrab->{text};
my ($first_nick) = split /\+/, $quotegrab->{nick}, 2;
if ($text =~ s/^\/me\s+//) {
return "$arguments: grabbed by $quotegrab->{grabbed_by} in $quotegrab->{channel} on " . localtime($timestamp) . " [$ago] * $first_nick $text";
} else {
return "$arguments: grabbed by $quotegrab->{grabbed_by} in $quotegrab->{channel} on " . localtime($timestamp) . " [$ago] <$first_nick> $text";
}
if ($text =~ s/^\/me\s+//) {
return "$arguments: grabbed by $quotegrab->{grabbed_by} in $quotegrab->{channel} on " . localtime($timestamp) . " [$ago] * $first_nick $text";
} else {
return "$arguments: grabbed by $quotegrab->{grabbed_by} in $quotegrab->{channel} on " . localtime($timestamp) . " [$ago] <$first_nick> $text";
}
}
sub show_random_quotegrab {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my @quotes = ();
my ($nick_search, $channel_search, $text_search);
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my @quotes = ();
my ($nick_search, $channel_search, $text_search);
if (not defined $from) {
$self->{pbot}->{logger}->log("Command missing ~from parameter!\n");
return "";
}
my $usage = 'Usage: rq [nick [channel [text]]] [-c <channel>] [-t <text>]';
if (defined $arguments) {
my $getopt_error;
local $SIG{__WARN__} = sub {
$getopt_error = shift;
chomp $getopt_error;
};
my @opt_args = $self->{pbot}->{interpreter}->split_line($arguments, preserve_escapes => 1, strip_quotes => 1);
GetOptionsFromArray(\@opt_args,
'channel|c=s' => \$channel_search,
'text|t=s' => \$text_search);
return "$getopt_error -- $usage" if defined $getopt_error;
$nick_search = shift @opt_args;
$channel_search = shift @opt_args if not defined $channel_search;
$text_search = shift @opt_args if not defined $text_search;
if ($nick_search =~ m/^#/) {
my $tmp = $channel_search;
$channel_search = $nick_search;
$nick_search = $tmp;
if (not defined $from) {
$self->{pbot}->{logger}->log("Command missing ~from parameter!\n");
return "";
}
if (not defined $channel_search) {
$channel_search = $from;
}
}
my $usage = 'Usage: rq [nick [channel [text]]] [-c <channel>] [-t <text>]';
if (defined $channel_search and $channel_search !~ /^#/) {
if ($channel_search eq $nick) {
$channel_search = undef;
} elsif ($channel_search =~ m/^\./) {
# do nothing
if (defined $arguments) {
my $getopt_error;
local $SIG{__WARN__} = sub {
$getopt_error = shift;
chomp $getopt_error;
};
my @opt_args = $self->{pbot}->{interpreter}->split_line($arguments, preserve_escapes => 1, strip_quotes => 1);
GetOptionsFromArray(
\@opt_args,
'channel|c=s' => \$channel_search,
'text|t=s' => \$text_search
);
return "$getopt_error -- $usage" if defined $getopt_error;
$nick_search = shift @opt_args;
$channel_search = shift @opt_args if not defined $channel_search;
$text_search = shift @opt_args if not defined $text_search;
if ($nick_search =~ m/^#/) {
my $tmp = $channel_search;
$channel_search = $nick_search;
$nick_search = $tmp;
}
if (not defined $channel_search) { $channel_search = $from; }
}
if (defined $channel_search and $channel_search !~ /^#/) {
if ($channel_search eq $nick) { $channel_search = undef; }
elsif ($channel_search =~ m/^\./) {
# do nothing
} else {
return "$channel_search is not a valid channel.";
}
}
my $quotegrab = $self->{database}->get_random_quotegrab($nick_search, $channel_search, $text_search);
if (not defined $quotegrab) {
my $result = "No quotes grabbed ";
if (defined $nick_search) { $result .= "for nick $nick_search "; }
if (defined $channel_search) { $result .= "in channel $channel_search "; }
if (defined $text_search) { $result .= "matching text '$text_search' "; }
return $result . "yet ($usage).";
}
my $text = $quotegrab->{text};
my ($first_nick) = split /\+/, $quotegrab->{nick}, 2;
if ($text =~ s/^\/me\s+//) {
return "$quotegrab->{id}: " . (($channel_search eq '.*' or $quotegrab->{channel} ne $from) ? "[$quotegrab->{channel}] " : "") . "* $first_nick $text";
} else {
return "$channel_search is not a valid channel.";
return "$quotegrab->{id}: " . (($channel_search eq '.*' or $quotegrab->{channel} ne $from) ? "[$quotegrab->{channel}] " : "") . "<$first_nick> $text";
}
}
my $quotegrab = $self->{database}->get_random_quotegrab($nick_search, $channel_search, $text_search);
if (not defined $quotegrab) {
my $result = "No quotes grabbed ";
if (defined $nick_search) {
$result .= "for nick $nick_search ";
}
if (defined $channel_search) {
$result .= "in channel $channel_search ";
}
if (defined $text_search) {
$result .= "matching text '$text_search' ";
}
return $result . "yet ($usage).";;
}
my $text = $quotegrab->{text};
my ($first_nick) = split /\+/, $quotegrab->{nick}, 2;
if ($text =~ s/^\/me\s+//) {
return "$quotegrab->{id}: " . (($channel_search eq '.*' or $quotegrab->{channel} ne $from) ? "[$quotegrab->{channel}] " : "") . "* $first_nick $text";
} else {
return "$quotegrab->{id}: " . (($channel_search eq '.*' or $quotegrab->{channel} ne $from) ? "[$quotegrab->{channel}] " : "") . "<$first_nick> $text";
}
}
1;

View File

@ -22,155 +22,145 @@ use Getopt::Long qw(GetOptionsFromString);
use POSIX qw(strftime);
sub new {
if (ref($_[1]) eq 'HASH') {
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference");
}
if (ref($_[1]) eq 'HASH') { Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference"); }
my ($class, %conf) = @_;
my ($class, %conf) = @_;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($self, %conf) = @_;
my ($self, %conf) = @_;
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference in " . __FILE__);
$self->{filename} = delete $conf{filename};
$self->{quotegrabs} = [];
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference in " . __FILE__);
$self->{filename} = delete $conf{filename};
$self->{quotegrabs} = [];
}
sub begin {
my $self = shift;
$self->load_quotegrabs;
my $self = shift;
$self->load_quotegrabs;
}
sub end {
}
sub end { }
sub load_quotegrabs {
my $self = shift;
my $filename;
my $self = shift;
my $filename;
if (@_) { $filename = shift; } else { $filename = $self->{filename}; }
return if not defined $filename;
if (@_) { $filename = shift; }
else { $filename = $self->{filename}; }
return if not defined $filename;
$self->{pbot}->{logger}->log("Loading quotegrabs from $filename ...\n");
$self->{pbot}->{logger}->log("Loading quotegrabs from $filename ...\n");
open(FILE, "< $filename") or die "Couldn't open $filename: $!\n";
my @contents = <FILE>;
close(FILE);
open(FILE, "< $filename") or die "Couldn't open $filename: $!\n";
my @contents = <FILE>;
close(FILE);
my $i = 0;
foreach my $line (@contents) {
chomp $line;
$i++;
my ($nick, $channel, $timestamp, $grabbed_by, $text) = split(/\s+/, $line, 5);
if (not defined $nick || not defined $channel || not defined $timestamp
|| not defined $grabbed_by || not defined $text) {
die "Syntax error around line $i of $filename\n";
my $i = 0;
foreach my $line (@contents) {
chomp $line;
$i++;
my ($nick, $channel, $timestamp, $grabbed_by, $text) = split(/\s+/, $line, 5);
if (not defined $nick || not defined $channel || not defined $timestamp || not defined $grabbed_by || not defined $text) {
die "Syntax error around line $i of $filename\n";
}
my $quotegrab = {};
$quotegrab->{nick} = $nick;
$quotegrab->{channel} = $channel;
$quotegrab->{timestamp} = $timestamp;
$quotegrab->{grabbed_by} = $grabbed_by;
$quotegrab->{text} = $text;
$quotegrab->{id} = $i + 1;
push @{$self->{quotegrabs}}, $quotegrab;
}
my $quotegrab = {};
$quotegrab->{nick} = $nick;
$quotegrab->{channel} = $channel;
$quotegrab->{timestamp} = $timestamp;
$quotegrab->{grabbed_by} = $grabbed_by;
$quotegrab->{text} = $text;
$quotegrab->{id} = $i + 1;
push @{ $self->{quotegrabs} }, $quotegrab;
}
$self->{pbot}->{logger}->log(" $i quotegrabs loaded.\n");
$self->{pbot}->{logger}->log("Done.\n");
$self->{pbot}->{logger}->log(" $i quotegrabs loaded.\n");
$self->{pbot}->{logger}->log("Done.\n");
}
sub save_quotegrabs {
my $self = shift;
my $filename;
my $self = shift;
my $filename;
if (@_) { $filename = shift; } else { $filename = $self->{filename}; }
return if not defined $filename;
if (@_) { $filename = shift; }
else { $filename = $self->{filename}; }
return if not defined $filename;
open(FILE, "> $filename") or die "Couldn't open $filename: $!\n";
open(FILE, "> $filename") or die "Couldn't open $filename: $!\n";
for(my $i = 0; $i <= $#{ $self->{quotegrabs} }; $i++) {
my $quotegrab = $self->{quotegrabs}[$i];
next if $quotegrab->{timestamp} == 0;
print FILE "$quotegrab->{nick} $quotegrab->{channel} $quotegrab->{timestamp} $quotegrab->{grabbed_by} $quotegrab->{text}\n";
}
for (my $i = 0; $i <= $#{$self->{quotegrabs}}; $i++) {
my $quotegrab = $self->{quotegrabs}[$i];
next if $quotegrab->{timestamp} == 0;
print FILE "$quotegrab->{nick} $quotegrab->{channel} $quotegrab->{timestamp} $quotegrab->{grabbed_by} $quotegrab->{text}\n";
}
close(FILE);
close(FILE);
}
sub add_quotegrab {
my ($self, $quotegrab) = @_;
my ($self, $quotegrab) = @_;
push @{ $self->{quotegrabs} }, $quotegrab;
$self->save_quotegrabs();
return $#{ $self->{quotegrabs} } + 1;
push @{$self->{quotegrabs}}, $quotegrab;
$self->save_quotegrabs();
return $#{$self->{quotegrabs}} + 1;
}
sub delete_quotegrab {
my ($self, $id) = @_;
my ($self, $id) = @_;
if ($id < 1 || $id > $#{ $self->{quotegrabs} } + 1) {
return undef;
}
if ($id < 1 || $id > $#{$self->{quotegrabs}} + 1) { return undef; }
splice @{ $self->{quotegrabs} }, $id - 1, 1;
splice @{$self->{quotegrabs}}, $id - 1, 1;
for(my $i = $id - 1; $i <= $#{ $self->{quotegrabs} }; $i++ ) {
$self->{quotegrabs}[$i]->{id}--;
}
for (my $i = $id - 1; $i <= $#{$self->{quotegrabs}}; $i++) { $self->{quotegrabs}[$i]->{id}--; }
$self->save_quotegrabs();
$self->save_quotegrabs();
}
sub get_quotegrab {
my ($self, $id) = @_;
my ($self, $id) = @_;
if ($id < 1 || $id > $#{ $self->{quotegrabs} } + 1) {
return undef;
}
if ($id < 1 || $id > $#{$self->{quotegrabs}} + 1) { return undef; }
return $self->{quotegrabs}[$id - 1];
return $self->{quotegrabs}[$id - 1];
}
sub get_random_quotegrab {
my ($self, $nick, $channel, $text) = @_;
my ($self, $nick, $channel, $text) = @_;
$nick = '.*' if not defined $nick;
$channel = '.*' if not defined $channel;
$text = '.*' if not defined $text;
$nick = '.*' if not defined $nick;
$channel = '.*' if not defined $channel;
$text = '.*' if not defined $text;
my @quotes;
my @quotes;
eval {
for(my $i = 0; $i <= $#{ $self->{quotegrabs} }; $i++) {
my $hash = $self->{quotegrabs}[$i];
if ($hash->{channel} =~ /$channel/i && $hash->{nick} =~ /$nick/i && $hash->{text} =~ /$text/i) {
$hash->{id} = $i + 1;
push @quotes, $hash;
}
eval {
for (my $i = 0; $i <= $#{$self->{quotegrabs}}; $i++) {
my $hash = $self->{quotegrabs}[$i];
if ($hash->{channel} =~ /$channel/i && $hash->{nick} =~ /$nick/i && $hash->{text} =~ /$text/i) {
$hash->{id} = $i + 1;
push @quotes, $hash;
}
}
};
if ($@) {
$self->{pbot}->{logger}->log("Error in show_random_quotegrab parameters: $@\n");
return undef;
}
};
if ($@) {
$self->{pbot}->{logger}->log("Error in show_random_quotegrab parameters: $@\n");
return undef;
}
if ($#quotes < 0) { return undef; }
if ($#quotes < 0) {
return undef;
}
return $quotes[int rand($#quotes + 1)];
return $quotes[int rand($#quotes + 1)];
}
sub get_all_quotegrabs {
my $self = shift;
return $self->{quotegrabs};
my $self = shift;
return $self->{quotegrabs};
}
1;

View File

@ -18,33 +18,31 @@ use DBI;
use Carp qw(shortmess);
sub new {
if (ref($_[1]) eq 'HASH') {
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference");
}
if (ref($_[1]) eq 'HASH') { Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference"); }
my ($class, %conf) = @_;
my ($class, %conf) = @_;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($self, %conf) = @_;
my ($self, %conf) = @_;
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference in " . __FILE__);
$self->{filename} = delete $conf{filename};
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference in " . __FILE__);
$self->{filename} = delete $conf{filename};
}
sub begin {
my $self = shift;
my $self = shift;
$self->{pbot}->{logger}->log("Opening quotegrabs SQLite database: $self->{filename}\n");
$self->{pbot}->{logger}->log("Opening quotegrabs SQLite database: $self->{filename}\n");
$self->{dbh} = DBI->connect("dbi:SQLite:dbname=$self->{filename}", "", "", { RaiseError => 1, PrintError => 0, sqlite_unicode => 1 }) or die $DBI::errstr;
$self->{dbh} = DBI->connect("dbi:SQLite:dbname=$self->{filename}", "", "", {RaiseError => 1, PrintError => 0, sqlite_unicode => 1}) or die $DBI::errstr;
eval {
$self->{dbh}->do(<< 'SQL');
eval {
$self->{dbh}->do(<< 'SQL');
CREATE TABLE IF NOT EXISTS Quotegrabs (
id INTEGER PRIMARY KEY,
nick TEXT,
@ -54,126 +52,126 @@ CREATE TABLE IF NOT EXISTS Quotegrabs (
timestamp NUMERIC
)
SQL
};
};
$self->{pbot}->{logger}->log($@) if $@;
$self->{pbot}->{logger}->log($@) if $@;
}
sub end {
my $self = shift;
my $self = shift;
$self->{pbot}->{logger}->log("Closing quotegrabs SQLite database\n");
$self->{pbot}->{logger}->log("Closing quotegrabs SQLite database\n");
if (exists $self->{dbh} and defined $self->{dbh}) {
$self->{dbh}->disconnect();
delete $self->{dbh};
}
if (exists $self->{dbh} and defined $self->{dbh}) {
$self->{dbh}->disconnect();
delete $self->{dbh};
}
}
sub add_quotegrab {
my ($self, $quotegrab) = @_;
my ($self, $quotegrab) = @_;
my $id = eval {
my $sth = $self->{dbh}->prepare('INSERT INTO Quotegrabs VALUES (?, ?, ?, ?, ?, ?)');
$sth->bind_param(1, undef);
$sth->bind_param(2, $quotegrab->{nick});
$sth->bind_param(3, $quotegrab->{channel});
$sth->bind_param(4, $quotegrab->{grabbed_by});
$sth->bind_param(5, $quotegrab->{text});
$sth->bind_param(6, $quotegrab->{timestamp});
$sth->execute();
my $id = eval {
my $sth = $self->{dbh}->prepare('INSERT INTO Quotegrabs VALUES (?, ?, ?, ?, ?, ?)');
$sth->bind_param(1, undef);
$sth->bind_param(2, $quotegrab->{nick});
$sth->bind_param(3, $quotegrab->{channel});
$sth->bind_param(4, $quotegrab->{grabbed_by});
$sth->bind_param(5, $quotegrab->{text});
$sth->bind_param(6, $quotegrab->{timestamp});
$sth->execute();
return $self->{dbh}->sqlite_last_insert_rowid();
};
return $self->{dbh}->sqlite_last_insert_rowid();
};
$self->{pbot}->{logger}->log($@) if $@;
return $id;
$self->{pbot}->{logger}->log($@) if $@;
return $id;
}
sub get_quotegrab {
my ($self, $id) = @_;
my ($self, $id) = @_;
my $quotegrab = eval {
my $sth = $self->{dbh}->prepare('SELECT * FROM Quotegrabs WHERE id == ?');
$sth->bind_param(1, $id);
$sth->execute();
return $sth->fetchrow_hashref();
};
my $quotegrab = eval {
my $sth = $self->{dbh}->prepare('SELECT * FROM Quotegrabs WHERE id == ?');
$sth->bind_param(1, $id);
$sth->execute();
return $sth->fetchrow_hashref();
};
$self->{pbot}->{logger}->log($@) if $@;
return $quotegrab;
$self->{pbot}->{logger}->log($@) if $@;
return $quotegrab;
}
sub get_random_quotegrab {
my ($self, $nick, $channel, $text) = @_;
my ($self, $nick, $channel, $text) = @_;
$nick =~ s/\.?\*\??/%/g if defined $nick;
$channel =~ s/\.?\*\??/%/g if defined $channel;
$text =~ s/\.?\*\??/%/g if defined $text;
$nick =~ s/\.?\*\??/%/g if defined $nick;
$channel =~ s/\.?\*\??/%/g if defined $channel;
$text =~ s/\.?\*\??/%/g if defined $text;
$nick =~ s/\./_/g if defined $nick;
$channel =~ s/\./_/g if defined $channel;
$text =~ s/\./_/g if defined $text;
$nick =~ s/\./_/g if defined $nick;
$channel =~ s/\./_/g if defined $channel;
$text =~ s/\./_/g if defined $text;
my $quotegrab = eval {
my $sql = 'SELECT * FROM Quotegrabs ';
my @params;
my $where = 'WHERE ';
my $and = '';
my $quotegrab = eval {
my $sql = 'SELECT * FROM Quotegrabs ';
my @params;
my $where = 'WHERE ';
my $and = '';
if (defined $nick) {
$sql .= $where . 'nick LIKE ? ';
push @params, "$nick";
$where = '';
$and = 'AND ';
}
if (defined $nick) {
$sql .= $where . 'nick LIKE ? ';
push @params, "$nick";
$where = '';
$and = 'AND ';
}
if (defined $channel) {
$sql .= $where . $and . 'channel LIKE ? ';
push @params, $channel;
$where = '';
$and = 'AND ';
}
if (defined $channel) {
$sql .= $where . $and . 'channel LIKE ? ';
push @params, $channel;
$where = '';
$and = 'AND ';
}
if (defined $text) {
$sql .= $where . $and . 'text LIKE ? ';
push @params, "%$text%";
}
if (defined $text) {
$sql .= $where . $and . 'text LIKE ? ';
push @params, "%$text%";
}
$sql .= 'ORDER BY RANDOM() LIMIT 1';
$sql .= 'ORDER BY RANDOM() LIMIT 1';
my $sth = $self->{dbh}->prepare($sql);
$sth->execute(@params);
return $sth->fetchrow_hashref();
};
my $sth = $self->{dbh}->prepare($sql);
$sth->execute(@params);
return $sth->fetchrow_hashref();
};
$self->{pbot}->{logger}->log($@) if $@;
return $quotegrab;
$self->{pbot}->{logger}->log($@) if $@;
return $quotegrab;
}
sub get_all_quotegrabs {
my $self = shift;
my $self = shift;
my $quotegrabs = eval {
my $sth = $self->{dbh}->prepare('SELECT * from Quotegrabs');
$sth->execute();
return $sth->fetchall_arrayref({});
};
my $quotegrabs = eval {
my $sth = $self->{dbh}->prepare('SELECT * from Quotegrabs');
$sth->execute();
return $sth->fetchall_arrayref({});
};
$self->{pbot}->{logger}->log($@) if $@;
return $quotegrabs;
$self->{pbot}->{logger}->log($@) if $@;
return $quotegrabs;
}
sub delete_quotegrab {
my ($self, $id) = @_;
my ($self, $id) = @_;
eval {
my $sth = $self->{dbh}->prepare('DELETE FROM Quotegrabs WHERE id == ?');
$sth->bind_param(1, $id);
$sth->execute();
};
eval {
my $sth = $self->{dbh}->prepare('DELETE FROM Quotegrabs WHERE id == ?');
$sth->bind_param(1, $id);
$sth->execute();
};
$self->{pbot}->{logger}->log($@) if $@;
$self->{pbot}->{logger}->log($@) if $@;
}
1;

View File

@ -8,116 +8,118 @@ use feature 'unicode_strings';
use Time::HiRes qw/gettimeofday/;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_public(@_) });
$self->{queue} = [];
$self->{notified} = {};
$self->{pbot}->{timer}->register(sub { $self->check_queue }, 1, 'RelayUnreg');
my ($self, %conf) = @_;
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_public(@_) });
$self->{queue} = [];
$self->{notified} = {};
$self->{pbot}->{timer}->register(sub { $self->check_queue }, 1, 'RelayUnreg');
}
sub unload {
my $self = shift;
$self->{pbot}->{timer}->unregister('RelayUnreg');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
my $self = shift;
$self->{pbot}->{timer}->unregister('RelayUnreg');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
}
sub on_public {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my $channel = lc $event->{event}->{to}[0];
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my $channel = lc $event->{event}->{to}[0];
$msg =~ s/^\s+|\s+$//g;
return 0 if not length $msg;
$msg =~ s/^\s+|\s+$//g;
return 0 if not length $msg;
# exit if channel hasn't muted $~a
return 0 if not exists $self->{pbot}->{bantracker}->{banlist}->{$channel}->{'+q'}->{'$~a'};
# exit if channel hasn't muted $~a
return 0 if not exists $self->{pbot}->{bantracker}->{banlist}->{$channel}->{'+q'}->{'$~a'};
# exit if channel isn't +z
my $chanmodes = $self->{pbot}->{channels}->get_meta($channel, 'MODE');
return 0 if not defined $chanmodes or not $chanmodes =~ m/z/;
# exit if channel isn't +z
my $chanmodes = $self->{pbot}->{channels}->get_meta($channel, 'MODE');
return 0 if not defined $chanmodes or not $chanmodes =~ m/z/;
my $account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($account);
my $account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($account);
# debug
# my $nickserv_text = $nickserv ? "is logged into $nickserv" : "is not logged in";
# $self->{pbot}->{logger}->log("RelayUnreg: $nick!$user\@$host ($account) $nickserv_text.\n");
# debug
# my $nickserv_text = $nickserv ? "is logged into $nickserv" : "is not logged in";
# $self->{pbot}->{logger}->log("RelayUnreg: $nick!$user\@$host ($account) $nickserv_text.\n");
# exit if user is identified
return 0 if defined $nickserv && length $nickserv;
# exit if user is identified
return 0 if defined $nickserv && length $nickserv;
my @filters = (
qr{https://bryanostergaard.com/},
qr{https://encyclopediadramatica.rs/Freenodegate},
qr{https://MattSTrout.com/},
qr{Contact me on twitter},
qr{At the beginning there was only Chaos},
qr{https://williampitcock.com/},
qr{Achievement Method},
qr{perceived death signal},
qr{efnet},
qr{https://evestigatorsucks.com},
qr{eVestigator},
);
my @filters = (
qr{https://bryanostergaard.com/},
qr{https://encyclopediadramatica.rs/Freenodegate},
qr{https://MattSTrout.com/},
qr{Contact me on twitter},
qr{At the beginning there was only Chaos},
qr{https://williampitcock.com/},
qr{Achievement Method},
qr{perceived death signal},
qr{efnet},
qr{https://evestigatorsucks.com},
qr{eVestigator},
);
# don't notify/relay for spammers
foreach my $filter (@filters) {
if ($msg =~ m/$filter/i) {
$self->{pbot}->{logger}->log("RelayUnreg: Ignoring filtered message.\n");
return 0;
# don't notify/relay for spammers
foreach my $filter (@filters) {
if ($msg =~ m/$filter/i) {
$self->{pbot}->{logger}->log("RelayUnreg: Ignoring filtered message.\n");
return 0;
}
}
}
# don't notify/relay for spammers
return 0 if $self->{pbot}->{antispam}->is_spam($channel, $msg, 1);
# don't notify/relay for spammers
return 0 if $self->{pbot}->{antispam}->is_spam($channel, $msg, 1);
# don't notify/relay if user is voiced
return 0 if $self->{pbot}->{nicklist}->get_meta($channel, $nick, '+v');
# don't notify/relay if user is voiced
return 0 if $self->{pbot}->{nicklist}->get_meta($channel, $nick, '+v');
unless (exists $self->{notified}->{lc $nick}) {
$self->{pbot}->{logger}->log("RelayUnreg: Notifying $nick to register with NickServ in $channel.\n");
$event->{conn}->privmsg($nick, "Please register your nick to speak in $channel. See https://freenode.net/kb/answer/registration and https://freenode.net/kb/answer/sasl");
$self->{notified}->{lc $nick} = gettimeofday;
}
unless (exists $self->{notified}->{lc $nick}) {
$self->{pbot}->{logger}->log("RelayUnreg: Notifying $nick to register with NickServ in $channel.\n");
$event->{conn}->privmsg($nick, "Please register your nick to speak in $channel. See https://freenode.net/kb/answer/registration and https://freenode.net/kb/answer/sasl");
$self->{notified}->{lc $nick} = gettimeofday;
}
# don't relay unregistered chat unless enabled
return 0 if not $self->{pbot}->{registry}->get_value($channel, 'relay_unregistered_chat');
# don't relay unregistered chat unless enabled
return 0 if not $self->{pbot}->{registry}->get_value($channel, 'relay_unregistered_chat');
# add message to delay send queue to see if Sigyn kills them first (or if they leave)
$self->{pbot}->{logger}->log("RelayUnreg: Queuing unregistered message for $channel: <$nick> $msg\n");
push @{$self->{queue}}, [gettimeofday + 10, $channel, $nick, $user, $host, $msg];
# add message to delay send queue to see if Sigyn kills them first (or if they leave)
$self->{pbot}->{logger}->log("RelayUnreg: Queuing unregistered message for $channel: <$nick> $msg\n");
push @{$self->{queue}}, [gettimeofday + 10, $channel, $nick, $user, $host, $msg];
return 0;
return 0;
}
sub check_queue {
my $self = shift;
my $now = gettimeofday;
my $self = shift;
my $now = gettimeofday;
if (@{$self->{queue}}) {
my ($time, $channel, $nick, $user, $host, $msg) = @{$self->{queue}->[0]};
if (@{$self->{queue}}) {
my ($time, $channel, $nick, $user, $host, $msg) = @{$self->{queue}->[0]};
if ($now >= $time) {
# if nick is still present in channel, send the message
if ($self->{pbot}->{nicklist}->is_present($channel, $nick)) {
# ensure they're not banned (+z allows us to see +q/+b messages as normal ones)
my $banned = $self->{pbot}->{bantracker}->is_banned($nick, $user, $host, $channel);
$self->{pbot}->{logger}->log("[RelayUnreg] $nick!$user\@$host $banned->{mode} as $banned->{banmask} in $banned->{channel} by $banned->{owner}, not relaying unregistered message\n") if $banned;
$self->{pbot}->{conn}->privmsg($channel, "(unreg) <$nick> $msg") unless $banned;
}
shift @{$self->{queue}};
if ($now >= $time) {
# if nick is still present in channel, send the message
if ($self->{pbot}->{nicklist}->is_present($channel, $nick)) {
# ensure they're not banned (+z allows us to see +q/+b messages as normal ones)
my $banned = $self->{pbot}->{bantracker}->is_banned($nick, $user, $host, $channel);
$self->{pbot}->{logger}
->log("[RelayUnreg] $nick!$user\@$host $banned->{mode} as $banned->{banmask} in $banned->{channel} by $banned->{owner}, not relaying unregistered message\n")
if $banned;
$self->{pbot}->{conn}->privmsg($channel, "(unreg) <$nick> $msg") unless $banned;
}
shift @{$self->{queue}};
}
}
}
# check notification timeouts here too, why not?
if (keys %{$self->{notified}}) {
my $timeout = gettimeofday - 60 * 15;
foreach my $nick (keys %{$self->{notified}}) {
if ($self->{notified}->{$nick} <= $timeout) {
delete $self->{notified}->{$nick};
}
# check notification timeouts here too, why not?
if (keys %{$self->{notified}}) {
my $timeout = gettimeofday - 60 * 15;
foreach my $nick (keys %{$self->{notified}}) {
if ($self->{notified}->{$nick} <= $timeout) { delete $self->{notified}->{$nick}; }
}
}
}
}
1;

View File

@ -17,27 +17,27 @@ use Time::HiRes qw/gettimeofday/;
use Getopt::Long qw(GetOptionsFromString);
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->remindme(@_) }, 'remindme', 0);
$self->{filename} = $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/reminders.sqlite3';
$self->{pbot}->{timer}->register(sub { $self->check_reminders(@_) }, 1, 'RemindMe');
$self->dbi_begin;
$self->create_database;
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->remindme(@_) }, 'remindme', 0);
$self->{filename} = $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/reminders.sqlite3';
$self->{pbot}->{timer}->register(sub { $self->check_reminders(@_) }, 1, 'RemindMe');
$self->dbi_begin;
$self->create_database;
}
sub unload {
my $self = shift;
$self->dbi_end;
$self->{pbot}->{commands}->unregister('remindme');
$self->{pbot}->{timer}->unregister('RemindMe');
my $self = shift;
$self->dbi_end;
$self->{pbot}->{commands}->unregister('remindme');
$self->{pbot}->{timer}->unregister('RemindMe');
}
sub create_database {
my $self = shift;
return if not $self->{dbh};
my $self = shift;
return if not $self->{dbh};
eval {
$self->{dbh}->do(<<SQL);
eval {
$self->{dbh}->do(<<SQL);
CREATE TABLE IF NOT EXISTS Reminders (
id INTEGER PRIMARY KEY,
account TEXT,
@ -50,340 +50,305 @@ CREATE TABLE IF NOT EXISTS Reminders (
created_by TEXT
)
SQL
};
};
$self->{pbot}->{logger}->log("RemindMe: create database failed: $@") if $@;
$self->{pbot}->{logger}->log("RemindMe: create database failed: $@") if $@;
}
sub dbi_begin {
my ($self) = @_;
eval {
$self->{dbh} = DBI->connect("dbi:SQLite:dbname=$self->{filename}", "", "", { RaiseError => 1, PrintError => 0, AutoInactiveDestroy => 1, sqlite_unicode => 1 }) or die $DBI::errstr;
};
my ($self) = @_;
eval {
$self->{dbh} = DBI->connect("dbi:SQLite:dbname=$self->{filename}", "", "", {RaiseError => 1, PrintError => 0, AutoInactiveDestroy => 1, sqlite_unicode => 1})
or die $DBI::errstr;
};
if ($@) {
$self->{pbot}->{logger}->log("Error opening RemindMe database: $@");
delete $self->{dbh};
return 0;
} else {
return 1;
}
if ($@) {
$self->{pbot}->{logger}->log("Error opening RemindMe database: $@");
delete $self->{dbh};
return 0;
} else {
return 1;
}
}
sub dbi_end {
my ($self) = @_;
return if not $self->{dbh};
$self->{dbh}->disconnect;
delete $self->{dbh};
my ($self) = @_;
return if not $self->{dbh};
$self->{dbh}->disconnect;
delete $self->{dbh};
}
sub add_reminder {
my ($self, $account, $target, $text, $alarm, $duration, $repeat, $owner) = @_;
my ($self, $account, $target, $text, $alarm, $duration, $repeat, $owner) = @_;
eval {
my $sth = $self->{dbh}->prepare('INSERT INTO Reminders (account, target, text, alarm, duration, repeat, created_on, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?)');
$sth->execute($account, $target, $text, $alarm, $duration, $repeat, scalar gettimeofday, $owner);
};
eval {
my $sth = $self->{dbh}->prepare('INSERT INTO Reminders (account, target, text, alarm, duration, repeat, created_on, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?)');
$sth->execute($account, $target, $text, $alarm, $duration, $repeat, scalar gettimeofday, $owner);
};
if ($@) {
$self->{pbot}->{logger}->log("Add reminder failed: $@");
return 0;
}
return 1;
if ($@) {
$self->{pbot}->{logger}->log("Add reminder failed: $@");
return 0;
}
return 1;
}
sub update_reminder {
my ($self, $id, $data) = @_;
my ($self, $id, $data) = @_;
eval {
my $sql = 'UPDATE Reminders SET ';
eval {
my $sql = 'UPDATE Reminders SET ';
my $comma = '';
foreach my $key (keys %$data) {
$sql .= "$comma$key = ?";
$comma = ', ';
}
my $comma = '';
foreach my $key (keys %$data) {
$sql .= "$comma$key = ?";
$comma = ', ';
}
$sql .= ' WHERE id = ?';
$sql .= ' WHERE id = ?';
my $sth = $self->{dbh}->prepare($sql);
my $sth = $self->{dbh}->prepare($sql);
my $param = 1;
foreach my $key (keys %$data) { $sth->bind_param($param++, $data->{$key}); }
my $param = 1;
foreach my $key (keys %$data) {
$sth->bind_param($param++, $data->{$key});
}
$sth->bind_param($param++, $id);
$sth->execute();
};
$self->{pbot}->{logger}->log($@) if $@;
$sth->bind_param($param++, $id);
$sth->execute();
};
$self->{pbot}->{logger}->log($@) if $@;
}
sub get_reminder {
my ($self, $id) = @_;
my ($self, $id) = @_;
my $reminder = eval {
my $sth = $self->{dbh}->prepare('SELECT * FROM Reminders WHERE id = ?');
$sth->execute($id);
return $sth->fetchrow_hashref();
};
my $reminder = eval {
my $sth = $self->{dbh}->prepare('SELECT * FROM Reminders WHERE id = ?');
$sth->execute($id);
return $sth->fetchrow_hashref();
};
if ($@) {
$self->{pbot}->{logger}->log("List reminders failed: $@");
return undef;
}
return $reminder;
if ($@) {
$self->{pbot}->{logger}->log("List reminders failed: $@");
return undef;
}
return $reminder;
}
sub get_reminders {
my ($self, $account) = @_;
my ($self, $account) = @_;
my $reminders = eval {
my $sth = $self->{dbh}->prepare('SELECT * FROM Reminders WHERE account = ? ORDER BY id');
$sth->execute($account);
return $sth->fetchall_arrayref({});
};
my $reminders = eval {
my $sth = $self->{dbh}->prepare('SELECT * FROM Reminders WHERE account = ? ORDER BY id');
$sth->execute($account);
return $sth->fetchall_arrayref({});
};
if ($@) {
$self->{pbot}->{logger}->log("List reminders failed: $@");
return [];
}
if ($@) {
$self->{pbot}->{logger}->log("List reminders failed: $@");
return [];
}
return $reminders;
return $reminders;
}
sub delete_reminder {
my ($self, $id) = @_;
return if not $self->{dbh};
my ($self, $id) = @_;
return if not $self->{dbh};
eval {
my $sth = $self->{dbh}->prepare('DELETE FROM Reminders WHERE id = ?');
$sth->execute($id);
};
eval {
my $sth = $self->{dbh}->prepare('DELETE FROM Reminders WHERE id = ?');
$sth->execute($id);
};
if ($@) {
$self->{pbot}->{logger}->log("Delete reminder $id failed: $@");
return 0;
}
return 1;
if ($@) {
$self->{pbot}->{logger}->log("Delete reminder $id failed: $@");
return 0;
}
return 1;
}
sub remindme {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my ($self, $from, $nick, $user, $host, $arguments) = @_;
if (not $self->{dbh}) {
return "Internal error.";
}
if (not $self->{dbh}) { return "Internal error."; }
my $usage = "Usage: remindme [-c channel] [-r count] message -t time | remindme -l [nick] | remindme -d id";
my $usage = "Usage: remindme [-c channel] [-r count] message -t time | remindme -l [nick] | remindme -d id";
return $usage if not length $arguments;
return $usage if not length $arguments;
my ($target, $repeat, $text, $alarm, $list_reminders, $delete_id);
my ($target, $repeat, $text, $alarm, $list_reminders, $delete_id);
my $getopt_error;
local $SIG{__WARN__} = sub {
$getopt_error = shift;
chomp $getopt_error;
};
my $getopt_error;
local $SIG{__WARN__} = sub {
$getopt_error = shift;
chomp $getopt_error;
};
Getopt::Long::Configure ("bundling");
Getopt::Long::Configure("bundling");
$arguments =~ s/(?<!\\)'/\\'/g;
my ($ret, $args) = GetOptionsFromString($arguments,
'r:i' => \$repeat,
't:s' => \$alarm,
'c:s' => \$target,
'm:s' => \$text,
'l:s' => \$list_reminders,
'd:i' => \$delete_id);
$arguments =~ s/(?<!\\)'/\\'/g;
my ($ret, $args) = GetOptionsFromString(
$arguments,
'r:i' => \$repeat,
't:s' => \$alarm,
'c:s' => \$target,
'm:s' => \$text,
'l:s' => \$list_reminders,
'd:i' => \$delete_id
);
return "$getopt_error -- $usage" if defined $getopt_error;
return "$getopt_error -- $usage" if defined $getopt_error;
if (defined $list_reminders) {
my $nick_override = $list_reminders if length $list_reminders;
my $account;
if ($nick_override) {
my $hostmask;
($account, $hostmask) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($nick_override);
if (defined $list_reminders) {
my $nick_override = $list_reminders if length $list_reminders;
my $account;
if ($nick_override) {
my $hostmask;
($account, $hostmask) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($nick_override);
if (not $account) {
return "I don't know anybody named $nick_override.";
}
if (not $account) { return "I don't know anybody named $nick_override."; }
($nick_override) = $hostmask =~ m/^([^!]+)!/;
} else {
$account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
}
$account = $self->{pbot}->{messagehistory}->{database}->get_ancestor_id($account);
($nick_override) = $hostmask =~ m/^([^!]+)!/;
} else {
$account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
}
$account = $self->{pbot}->{messagehistory}->{database}->get_ancestor_id($account);
my $reminders = $self->get_reminders($account);
my $count = 0;
my $text = '';
my $now = scalar gettimeofday;
my $reminders = $self->get_reminders($account);
my $count = 0;
my $text = '';
my $now = scalar gettimeofday;
foreach my $reminder (@$reminders) {
my $duration = concise duration $reminder->{alarm} - $now;
$text .= "$reminder->{id}) [in $duration]";
$text .= " ($reminder->{repeat} repeats left)" if $reminder->{repeat};
$text .= " $reminder->{text}\n";
$count++;
foreach my $reminder (@$reminders) {
my $duration = concise duration $reminder->{alarm} - $now;
$text .= "$reminder->{id}) [in $duration]";
$text .= " ($reminder->{repeat} repeats left)" if $reminder->{repeat};
$text .= " $reminder->{text}\n";
$count++;
}
if (not $count) {
if ($nick_override) { return "$nick_override has no reminders."; }
else { return "You have no reminders."; }
}
$reminders = $count == 1 ? 'reminder' : 'reminders';
return "$count $reminders: $text";
}
if (not $count) {
if ($nick_override) {
return "$nick_override has no reminders.";
} else {
return "You have no reminders.";
}
if ($delete_id) {
my $admininfo = $self->{pbot}->{users}->loggedin_admin($target ? $target : $from, "$nick!$user\@$host");
# admins can delete any reminders (perhaps check admin levels against owner level?)
if ($admininfo) {
if (not $self->get_reminder($delete_id)) { return "Reminder $delete_id does not exist."; }
if ($self->delete_reminder($delete_id)) { return "Reminder $delete_id deleted."; }
else { return "Could not delete reminder $delete_id."; }
}
my $account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
$account = $self->{pbot}->{messagehistory}->{database}->get_ancestor_id($account);
my $reminder = $self->get_reminder($delete_id);
if (not $reminder) { return "Reminder $delete_id does not exist."; }
if ($reminder->{account} != $account) { return "Reminder $delete_id does not belong to you."; }
if ($self->delete_reminder($delete_id)) { return "Reminder $delete_id deleted."; }
else { return "Could not delete reminder $delete_id."; }
}
$reminders = $count == 1 ? 'reminder' : 'reminders';
return "$count $reminders: $text";
}
$text = join ' ', @$args if not defined $text;
return "Please specify a point in time for this reminder." if not $alarm;
return "Please specify a reminder message." if not $text;
if ($delete_id) {
my $admininfo = $self->{pbot}->{users}->loggedin_admin($target ? $target : $from, "$nick!$user\@$host");
# admins can delete any reminders (perhaps check admin levels against owner level?)
if ($admininfo) {
if (not $self->get_reminder($delete_id)) {
return "Reminder $delete_id does not exist.";
}
if ($target) {
if (not defined $admininfo) { return "Only admins can create channel reminders."; }
if ($self->delete_reminder($delete_id)) {
return "Reminder $delete_id deleted.";
} else {
return "Could not delete reminder $delete_id.";
}
if (not $self->{pbot}->{channels}->is_active($target)) { return "I'm not active in channel $target."; }
}
print "alarm: $alarm\n";
my ($length, $error) = $self->{pbot}->{parsedate}->parsedate($alarm);
print "length: $length, error: $error!\n";
return $error if $error;
# I don't know how I feel about enforcing arbitrary time restrictions
if ($length > 31536000 * 10) { return "Come on now, I'll be dead by then."; }
if (not defined $admininfo and $length < 60) { return "Time must be a minimum of 60 seconds."; }
if (not defined $admininfo and $repeat > 10) { return "You may only set up to 10 repeats."; }
if ($repeat < 0) { return "Repeats must be 0 or greater."; }
$alarm = gettimeofday + $length;
my $account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
$account = $self->{pbot}->{messagehistory}->{database}->get_ancestor_id($account);
my $reminder = $self->get_reminder($delete_id);
if (not $reminder) {
return "Reminder $delete_id does not exist.";
}
if ($reminder->{account} != $account) {
return "Reminder $delete_id does not belong to you.";
}
if ($self->delete_reminder($delete_id)) {
return "Reminder $delete_id deleted.";
} else {
return "Could not delete reminder $delete_id.";
}
}
$text = join ' ', @$args if not defined $text;
return "Please specify a point in time for this reminder." if not $alarm;
return "Please specify a reminder message." if not $text;
my $admininfo = $self->{pbot}->{users}->loggedin_admin($target ? $target : $from, "$nick!$user\@$host");
if ($target) {
if (not defined $admininfo) {
return "Only admins can create channel reminders.";
my $reminders = $self->get_reminders($account);
if (@$reminders >= 3) { return "You may only set 3 reminders at a time. Use `remindme -d id` to remove a reminder."; }
}
if (not $self->{pbot}->{channels}->is_active($target)) {
return "I'm not active in channel $target.";
}
}
print "alarm: $alarm\n";
my ($length, $error) = $self->{pbot}->{parsedate}->parsedate($alarm);
print "length: $length, error: $error!\n";
return $error if $error;
# I don't know how I feel about enforcing arbitrary time restrictions
if ($length > 31536000 * 10) {
return "Come on now, I'll be dead by then.";
}
if (not defined $admininfo and $length < 60) {
return "Time must be a minimum of 60 seconds.";
}
if (not defined $admininfo and $repeat > 10) {
return "You may only set up to 10 repeats.";
}
if ($repeat < 0) {
return "Repeats must be 0 or greater.";
}
$alarm = gettimeofday + $length;
my $account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
$account = $self->{pbot}->{messagehistory}->{database}->get_ancestor_id($account);
if (not defined $admininfo) {
my $reminders = $self->get_reminders($account);
if (@$reminders >= 3) {
return "You may only set 3 reminders at a time. Use `remindme -d id` to remove a reminder.";
}
}
if ($self->add_reminder($account, $target, $text, $alarm, $length, $repeat, "$nick!$user\@$host")) {
return "Reminder added.";
} else {
return "Failed to add reminder.";
}
if ($self->add_reminder($account, $target, $text, $alarm, $length, $repeat, "$nick!$user\@$host")) { return "Reminder added."; }
else { return "Failed to add reminder."; }
}
sub check_reminders {
my $self = shift;
my $self = shift;
return if not $self->{dbh};
return if not $self->{dbh};
my $reminders = eval {
my $sth = $self->{dbh}->prepare('SELECT * FROM Reminders WHERE alarm <= ?');
$sth->execute(scalar gettimeofday);
return $sth->fetchall_arrayref({});
};
my $reminders = eval {
my $sth = $self->{dbh}->prepare('SELECT * FROM Reminders WHERE alarm <= ?');
$sth->execute(scalar gettimeofday);
return $sth->fetchall_arrayref({});
};
if ($@) {
$self->{pbot}->{logger}->log("Check reminders failed: $@");
return;
}
foreach my $reminder (@$reminders) {
# ensures we get the current nick of the person
my $hostmask = $self->{pbot}->{messagehistory}->{database}->find_most_recent_hostmask($reminder->{account});
my ($nick) = $hostmask =~ /^([^!]+)!/;
# delete this reminder if it's expired by 31 days
if (gettimeofday - $reminder->{alarm} >= 86400 * 31) {
$self->{pbot}->{logger}->log("Deleting expired reminder: $reminder->{id}) $reminder->{text} set by $reminder->{created_by}\n");
$self->delete_reminder($reminder->{id});
next;
if ($@) {
$self->{pbot}->{logger}->log("Check reminders failed: $@");
return;
}
# don't execute this reminder if the person isn't around yet
next if not $self->{pbot}->{nicklist}->is_present_any_channel($nick);
foreach my $reminder (@$reminders) {
my $text = "Reminder: $reminder->{text}";
my $target = $reminder->{target} // $nick;
$self->{pbot}->{conn}->privmsg($target, $text);
# ensures we get the current nick of the person
my $hostmask = $self->{pbot}->{messagehistory}->{database}->find_most_recent_hostmask($reminder->{account});
my ($nick) = $hostmask =~ /^([^!]+)!/;
$self->{pbot}->{logger}->log("Reminded $target about \"$text\"\n");
# delete this reminder if it's expired by 31 days
if (gettimeofday - $reminder->{alarm} >= 86400 * 31) {
$self->{pbot}->{logger}->log("Deleting expired reminder: $reminder->{id}) $reminder->{text} set by $reminder->{created_by}\n");
$self->delete_reminder($reminder->{id});
next;
}
if ($reminder->{repeat} > 0) {
$reminder->{repeat}--;
$reminder->{alarm} = gettimeofday + $reminder->{duration};
my $data = { repeat => $reminder->{repeat}, alarm => $reminder->{alarm} };
$self->update_reminder($reminder->{id}, $data);
} else {
$self->delete_reminder($reminder->{id});
# don't execute this reminder if the person isn't around yet
next if not $self->{pbot}->{nicklist}->is_present_any_channel($nick);
my $text = "Reminder: $reminder->{text}";
my $target = $reminder->{target} // $nick;
$self->{pbot}->{conn}->privmsg($target, $text);
$self->{pbot}->{logger}->log("Reminded $target about \"$text\"\n");
if ($reminder->{repeat} > 0) {
$reminder->{repeat}--;
$reminder->{alarm} = gettimeofday + $reminder->{duration};
my $data = {repeat => $reminder->{repeat}, alarm => $reminder->{alarm}};
$self->update_reminder($reminder->{id}, $data);
} else {
$self->delete_reminder($reminder->{id});
}
}
}
}
1;

View File

@ -17,171 +17,167 @@ use feature 'unicode_strings';
use Storable qw/dclone/;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->modcmd(@_) }, 'mod', 0);
$self->{pbot}->{commands}->set_meta('mod', 'help', 'Provides restricted moderation abilities to voiced users. They can kick/ban/etc only users that are not admins, whitelisted, voiced or opped.');
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->modcmd(@_) }, 'mod', 0);
$self->{pbot}->{commands}->set_meta(
'mod', 'help',
'Provides restricted moderation abilities to voiced users. They can kick/ban/etc only users that are not admins, whitelisted, voiced or opped.'
);
$self->{pbot}->{capabilities}->add('chanmod', 'can-mod', 1);
$self->{pbot}->{capabilities}->add('chanmod', 'can-voice', 1);
$self->{pbot}->{capabilities}->add('chanmod', 'can-devoice', 1);
$self->{pbot}->{capabilities}->add('chanmod', 'can-mod', 1);
$self->{pbot}->{capabilities}->add('chanmod', 'can-voice', 1);
$self->{pbot}->{capabilities}->add('chanmod', 'can-devoice', 1);
$self->{commands} = {
'help' => { subref => sub { $self->help(@_) }, help => "Provides help about this command. Usage: mod help <mod command>; see also: mod help list" },
'list' => { subref => sub { $self->list(@_) }, help => "Lists available mod commands. Usage: mod list" },
'kick' => { subref => sub { $self->kick(@_) }, help => "Kicks a nick from the channel. Usage: mod kick <nick>" },
'ban' => { subref => sub { $self->ban(@_) }, help => "Bans a nick from the channel. Cannot be used to set a custom banmask. Usage: mod ban <nick>" },
'mute' => { subref => sub { $self->mute(@_) }, help => "Mutes a nick in the channel. Usage: mod mute <nick>" },
'unban' => { subref => sub { $self->unban(@_) }, help => "Removes bans set by moderators. Cannot remove any other types of bans. Usage: mod unban <nick or mask>" },
'unmute' => { subref => sub { $self->unmute(@_) }, help => "Removes mutes set by moderators. Cannot remove any other types of mutes. Usage: mod unmute <nick or mask>" },
'kb' => { subref => sub { $self->kb(@_) }, help => "Kickbans a nick from the channel. Cannot be used to set a custom banmask. Usage: mod kb <nick>" },
};
$self->{commands} = {
'help' => {subref => sub { $self->help(@_) }, help => "Provides help about this command. Usage: mod help <mod command>; see also: mod help list"},
'list' => {subref => sub { $self->list(@_) }, help => "Lists available mod commands. Usage: mod list"},
'kick' => {subref => sub { $self->kick(@_) }, help => "Kicks a nick from the channel. Usage: mod kick <nick>"},
'ban' => {subref => sub { $self->ban(@_) }, help => "Bans a nick from the channel. Cannot be used to set a custom banmask. Usage: mod ban <nick>"},
'mute' => {subref => sub { $self->mute(@_) }, help => "Mutes a nick in the channel. Usage: mod mute <nick>"},
'unban' => {subref => sub { $self->unban(@_) }, help => "Removes bans set by moderators. Cannot remove any other types of bans. Usage: mod unban <nick or mask>"},
'unmute' => {subref => sub { $self->unmute(@_) }, help => "Removes mutes set by moderators. Cannot remove any other types of mutes. Usage: mod unmute <nick or mask>"},
'kb' => {subref => sub { $self->kb(@_) }, help => "Kickbans a nick from the channel. Cannot be used to set a custom banmask. Usage: mod kb <nick>"},
};
}
sub unload {
my ($self) = @_;
$self->{pbot}->{commands}->unregister('mod');
$self->{pbot}->{capabilities}->remove('chanmod');
my ($self) = @_;
$self->{pbot}->{commands}->unregister('mod');
$self->{pbot}->{capabilities}->remove('chanmod');
}
sub help {
my ($self, $stuff) = @_;
my $command = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}) // 'help';
my ($self, $stuff) = @_;
my $command = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}) // 'help';
if (exists $self->{commands}->{$command}) {
return $self->{commands}->{$command}->{help};
} else {
return "No such mod command '$command'. I can't help you with that.";
}
if (exists $self->{commands}->{$command}) { return $self->{commands}->{$command}->{help}; }
else { return "No such mod command '$command'. I can't help you with that."; }
}
sub list {
my ($self, $stuff) = @_;
return "Available mod commands: " . join ', ', sort keys %{$self->{commands}};
my ($self, $stuff) = @_;
return "Available mod commands: " . join ', ', sort keys %{$self->{commands}};
}
sub generic_command {
my ($self, $stuff, $command) = @_;
my ($self, $stuff, $command) = @_;
my $channel = $stuff->{from};
if ($channel !~ m/^#/) {
$channel = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
my $channel = $stuff->{from};
if ($channel !~ m/^#/) {
$channel = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
if (not defined $channel or $channel !~ /^#/) {
return "Must specify channel from private message. Usage: mod $command <channel> <nick>";
if (not defined $channel or $channel !~ /^#/) { return "Must specify channel from private message. Usage: mod $command <channel> <nick>"; }
}
}
return "I do not have OPs for this channel. I cannot do any moderation here."
if not $self->{pbot}->{chanops}->can_gain_ops($channel);
return "Voiced moderation is not enabled for this channel. Use `regset $channel.restrictedmod 1` to enable."
if not $self->{pbot}->{registry}->get_value($channel, 'restrictedmod');
return "I do not have OPs for this channel. I cannot do any moderation here." if not $self->{pbot}->{chanops}->can_gain_ops($channel);
return "Voiced moderation is not enabled for this channel. Use `regset $channel.restrictedmod 1` to enable."
if not $self->{pbot}->{registry}->get_value($channel, 'restrictedmod');
my $hostmask = "$stuff->{nick}!$stuff->{user}\@$stuff->{host}";
my $user = $self->{pbot}->{users}->loggedin($channel, $hostmask) // { admin => 0, chanmod => 0};
my $voiced = $self->{pbot}->{nicklist}->get_meta($channel, $stuff->{nick}, '+v');
my $hostmask = "$stuff->{nick}!$stuff->{user}\@$stuff->{host}";
my $user = $self->{pbot}->{users}->loggedin($channel, $hostmask) // {admin => 0, chanmod => 0};
my $voiced = $self->{pbot}->{nicklist}->get_meta($channel, $stuff->{nick}, '+v');
if (not $voiced and not $self->{pbot}->{capabilities}->userhas($user, 'admin')
and not $self->{pbot}->{capabilities}->userhas($user, 'chanmod')) {
return "You must be voiced (usermode +v) or have the admin or chanmod capability to use this command.";
}
my $target = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
return "Missing target. Usage: mod $command <nick>" if not defined $target;
if ($command eq 'unban') {
my $reason = $self->{pbot}->{chanops}->checkban($channel, $target);
if ($reason =~ m/moderator ban/) {
$self->{pbot}->{chanops}->unban_user($target, $channel, 1);
return "";
} else {
return "I don't think so. That ban was not set by a moderator.";
if (not $voiced and not $self->{pbot}->{capabilities}->userhas($user, 'admin') and not $self->{pbot}->{capabilities}->userhas($user, 'chanmod')) {
return "You must be voiced (usermode +v) or have the admin or chanmod capability to use this command.";
}
} elsif ($command eq 'unmute') {
my $reason = $self->{pbot}->{chanops}->checkmute($channel, $target);
if ($reason =~ m/moderator mute/) {
$self->{pbot}->{chanops}->unmute_user($target, $channel, 1);
return "";
} else {
return "I don't think so. That mute was not set by a moderator.";
my $target = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
return "Missing target. Usage: mod $command <nick>" if not defined $target;
if ($command eq 'unban') {
my $reason = $self->{pbot}->{chanops}->checkban($channel, $target);
if ($reason =~ m/moderator ban/) {
$self->{pbot}->{chanops}->unban_user($target, $channel, 1);
return "";
} else {
return "I don't think so. That ban was not set by a moderator.";
}
} elsif ($command eq 'unmute') {
my $reason = $self->{pbot}->{chanops}->checkmute($channel, $target);
if ($reason =~ m/moderator mute/) {
$self->{pbot}->{chanops}->unmute_user($target, $channel, 1);
return "";
} else {
return "I don't think so. That mute was not set by a moderator.";
}
}
}
my $target_nicklist;
if (not $self->{pbot}->{nicklist}->is_present($channel, $target)) {
return "$stuff->{nick}: I do not see anybody named $target in this channel.";
} else {
$target_nicklist = $self->{pbot}->{nicklist}->{nicklist}->{lc $channel}->{lc $target};
}
my $target_nicklist;
if (not $self->{pbot}->{nicklist}->is_present($channel, $target)) { return "$stuff->{nick}: I do not see anybody named $target in this channel."; }
else { $target_nicklist = $self->{pbot}->{nicklist}->{nicklist}->{lc $channel}->{lc $target}; }
my $target_user = $self->{pbot}->{users}->loggedin($channel, $target_nicklist->{hostmask});
my $target_user = $self->{pbot}->{users}->loggedin($channel, $target_nicklist->{hostmask});
if ((defined $target_user and $target_user->{autoop} or $target_user->{autovoice})
or $target_nicklist->{'+v'} or $target_nicklist->{'+o'}
or $self->{pbot}->{capabilities}->userhas($target_user, 'is-whitelisted')) {
return "I don't think so."
}
if ( (defined $target_user and $target_user->{autoop} or $target_user->{autovoice})
or $target_nicklist->{'+v'}
or $target_nicklist->{'+o'}
or $self->{pbot}->{capabilities}->userhas($target_user, 'is-whitelisted'))
{
return "I don't think so.";
}
if ($command eq 'kick') {
$self->{pbot}->{chanops}->add_op_command($channel, "kick $channel $target Have a nice day!");
$self->{pbot}->{chanops}->gain_ops($channel);
} elsif ($command eq 'ban') {
$self->{pbot}->{chanops}->ban_user_timed("$stuff->{nick}!$stuff->{user}\@$stuff->{host}",
"doing something naughty (moderator ban)", $target, $channel, 3600 * 24, 1);
} elsif ($command eq 'mute') {
$self->{pbot}->{chanops}->mute_user_timed("$stuff->{nick}!$stuff->{user}\@$stuff->{host}",
"doing something naughty (moderator mute)", $target, $channel, 3600 * 24, 1);
}
return "";
if ($command eq 'kick') {
$self->{pbot}->{chanops}->add_op_command($channel, "kick $channel $target Have a nice day!");
$self->{pbot}->{chanops}->gain_ops($channel);
} elsif ($command eq 'ban') {
$self->{pbot}->{chanops}->ban_user_timed(
"$stuff->{nick}!$stuff->{user}\@$stuff->{host}",
"doing something naughty (moderator ban)", $target, $channel, 3600 * 24, 1
);
} elsif ($command eq 'mute') {
$self->{pbot}->{chanops}->mute_user_timed(
"$stuff->{nick}!$stuff->{user}\@$stuff->{host}",
"doing something naughty (moderator mute)", $target, $channel, 3600 * 24, 1
);
}
return "";
}
sub kick {
my ($self, $stuff) = @_;
return $self->generic_command($stuff, 'kick');
my ($self, $stuff) = @_;
return $self->generic_command($stuff, 'kick');
}
sub ban {
my ($self, $stuff) = @_;
return $self->generic_command($stuff, 'ban');
my ($self, $stuff) = @_;
return $self->generic_command($stuff, 'ban');
}
sub mute {
my ($self, $stuff) = @_;
return $self->generic_command($stuff, 'mute');
my ($self, $stuff) = @_;
return $self->generic_command($stuff, 'mute');
}
sub unban {
my ($self, $stuff) = @_;
return $self->generic_command($stuff, 'unban');
my ($self, $stuff) = @_;
return $self->generic_command($stuff, 'unban');
}
sub unmute {
my ($self, $stuff) = @_;
return $self->generic_command($stuff, 'unmute');
my ($self, $stuff) = @_;
return $self->generic_command($stuff, 'unmute');
}
sub kb {
my ($self, $stuff) = @_;
my $result = $self->ban(dclone $stuff); # note: using copy of $stuff to preserve $stuff->{arglist} for $self->kick($stuff)
return $result if length $result;
return $self->kick($stuff);
my ($self, $stuff) = @_;
my $result = $self->ban(dclone $stuff); # note: using copy of $stuff to preserve $stuff->{arglist} for $self->kick($stuff)
return $result if length $result;
return $self->kick($stuff);
}
sub modcmd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $command = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}) // '';
$command = lc $command;
my $command = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}) // '';
$command = lc $command;
if (grep { $_ eq $command } keys %{$self->{commands}}) {
return $self->{commands}->{$command}->{subref}->($stuff);
} else {
my $commands = join ', ', sort keys %{$self->{commands}};
if ($from !~ m/^#/) {
return "Usage: mod <channel> <command> [arguments]; commands are: $commands; see `mod help <command>` for more information.";
if (grep { $_ eq $command } keys %{$self->{commands}}) {
return $self->{commands}->{$command}->{subref}->($stuff);
} else {
return "Usage: mod <command> [arguments]; commands are: $commands; see `mod help <command>` for more information.";
my $commands = join ', ', sort keys %{$self->{commands}};
if ($from !~ m/^#/) { return "Usage: mod <channel> <command> [arguments]; commands are: $commands; see `mod help <command>` for more information."; }
else { return "Usage: mod <command> [arguments]; commands are: $commands; see `mod help <command>` for more information."; }
}
}
}
1;

File diff suppressed because it is too large Load Diff

View File

@ -18,319 +18,359 @@ use Plugins::Spinach::Stats;
use Math::Expression::Evaluator;
sub new {
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference") if ref $_[1] eq 'HASH';
my ($class, %conf) = @_;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference") if ref $_[1] eq 'HASH';
my ($class, %conf) = @_;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($self, %conf) = @_;
$self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
$self->{channel} = $conf{channel} // Carp::croak("Missing channel reference to " . __FILE__);
$self->{filename} = $conf{filename} // 'stats.sqlite';
$self->{stats} = Plugins::Spinach::Stats->new(pbot => $self->{pbot}, filename => $self->{filename});
my ($self, %conf) = @_;
$self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
$self->{channel} = $conf{channel} // Carp::croak("Missing channel reference to " . __FILE__);
$self->{filename} = $conf{filename} // 'stats.sqlite';
$self->{stats} = Plugins::Spinach::Stats->new(pbot => $self->{pbot}, filename => $self->{filename});
}
sub sort_generic {
my ($self, $key) = @_;
if ($self->{rank_direction} eq '+') {
return $b->{$key} <=> $a->{$key};
} else {
return $a->{$key} <=> $b->{$key};
}
my ($self, $key) = @_;
if ($self->{rank_direction} eq '+') { return $b->{$key} <=> $a->{$key}; }
else { return $a->{$key} <=> $b->{$key}; }
}
sub print_generic {
my ($self, $key, $player) = @_;
return undef if $player->{games_played} == 0;
return "$player->{nick}: $player->{$key}";
my ($self, $key, $player) = @_;
return undef if $player->{games_played} == 0;
return "$player->{nick}: $player->{$key}";
}
sub print_avg_score {
my ($self, $player) = @_;
return undef if $player->{games_played} == 0;
my $result = int $player->{avg_score};
return "$player->{nick}: $result";
my ($self, $player) = @_;
return undef if $player->{games_played} == 0;
my $result = int $player->{avg_score};
return "$player->{nick}: $result";
}
sub sort_bad_lies {
my ($self) = @_;
if ($self->{rank_direction} eq '+') {
return $b->{questions_played} - $b->{good_lies} <=> $a->{questions_played} - $a->{good_lies};
} else {
return $a->{questions_played} - $a->{good_lies} <=> $b->{questions_played} - $b->{good_lies};
}
my ($self) = @_;
if ($self->{rank_direction} eq '+') { return $b->{questions_played} - $b->{good_lies} <=> $a->{questions_played} - $a->{good_lies}; }
else { return $a->{questions_played} - $a->{good_lies} <=> $b->{questions_played} - $b->{good_lies}; }
}
sub print_bad_lies {
my ($self, $player) = @_;
return undef if $player->{games_played} == 0;
my $result = $player->{questions_played} - $player->{good_lies};
return "$player->{nick}: $result";
my ($self, $player) = @_;
return undef if $player->{games_played} == 0;
my $result = $player->{questions_played} - $player->{good_lies};
return "$player->{nick}: $result";
}
sub sort_mentions {
my ($self) = @_;
if ($self->{rank_direction} eq '+') {
return $b->{games_played} - $b->{times_first} - $b->{times_second} - $b->{times_third} <=>
$a->{games_played} - $a->{times_first} - $a->{times_second} - $a->{times_third};
} else {
return $a->{games_played} - $a->{times_first} - $a->{times_second} - $a->{times_third} <=>
$b->{games_played} - $b->{times_first} - $b->{times_second} - $b->{times_third};
}
my ($self) = @_;
if ($self->{rank_direction} eq '+') {
return $b->{games_played} - $b->{times_first} - $b->{times_second} - $b->{times_third} <=> $a->{games_played} - $a->{times_first} - $a->{times_second} -
$a->{times_third};
} else {
return $a->{games_played} - $a->{times_first} - $a->{times_second} - $a->{times_third} <=> $b->{games_played} - $b->{times_first} - $b->{times_second} -
$b->{times_third};
}
}
sub print_mentions {
my ($self, $player) = @_;
return undef if $player->{games_played} == 0;
my $result = $player->{games_played} - $player->{times_first} - $player->{times_second} - $player->{times_third};
return "$player->{nick}: $result";
my ($self, $player) = @_;
return undef if $player->{games_played} == 0;
my $result = $player->{games_played} - $player->{times_first} - $player->{times_second} - $player->{times_third};
return "$player->{nick}: $result";
}
sub sort_expr {
my ($self) = @_;
my ($self) = @_;
my $result = eval {
my $result_a = $self->{expr}->val({
highscore => $a->{high_score},
lowscore => $a->{low_score},
avgscore => $a->{avg_score},
goodlies => $a->{good_lies},
badlies => $a->{questions_played} - $a->{good_lies},
first => $a->{times_first},
second => $a->{times_second},
third => $a->{times_third},
mentions => $a->{games_played} - $a->{times_first} - $a->{times_second} - $a->{times_third},
games => $a->{games_played},
questions => $a->{questions_played},
goodguesses => $a->{good_guesses},
badguesses => $a->{bad_guesses},
deceptions => $a->{players_deceived}
});
my $result = eval {
my $result_a = $self->{expr}->val(
{
highscore => $a->{high_score},
lowscore => $a->{low_score},
avgscore => $a->{avg_score},
goodlies => $a->{good_lies},
badlies => $a->{questions_played} - $a->{good_lies},
first => $a->{times_first},
second => $a->{times_second},
third => $a->{times_third},
mentions => $a->{games_played} - $a->{times_first} - $a->{times_second} - $a->{times_third},
games => $a->{games_played},
questions => $a->{questions_played},
goodguesses => $a->{good_guesses},
badguesses => $a->{bad_guesses},
deceptions => $a->{players_deceived}
}
);
my $result_b = $self->{expr}->val({
highscore => $b->{high_score},
lowscore => $b->{low_score},
avgscore => $b->{avg_score},
goodlies => $b->{good_lies},
badlies => $b->{questions_played} - $b->{good_lies},
first => $b->{times_first},
second => $b->{times_second},
third => $b->{times_third},
mentions => $b->{games_played} - $b->{times_first} - $b->{times_second} - $b->{times_third},
games => $b->{games_played},
questions => $b->{questions_played},
goodguesses => $b->{good_guesses},
badguesses => $b->{bad_guesses},
deceptions => $b->{players_deceived}
});
my $result_b = $self->{expr}->val(
{
highscore => $b->{high_score},
lowscore => $b->{low_score},
avgscore => $b->{avg_score},
goodlies => $b->{good_lies},
badlies => $b->{questions_played} - $b->{good_lies},
first => $b->{times_first},
second => $b->{times_second},
third => $b->{times_third},
mentions => $b->{games_played} - $b->{times_first} - $b->{times_second} - $b->{times_third},
games => $b->{games_played},
questions => $b->{questions_played},
goodguesses => $b->{good_guesses},
badguesses => $b->{bad_guesses},
deceptions => $b->{players_deceived}
}
);
if ($self->{rank_direction} eq '+') {
return $result_b <=> $result_a;
} else {
return $result_a <=> $result_b;
if ($self->{rank_direction} eq '+') { return $result_b <=> $result_a; }
else { return $result_a <=> $result_b; }
};
if ($@) {
$self->{pbot}->{logger}->log("expr sort error: $@\n");
return 0;
}
};
if ($@) {
$self->{pbot}->{logger}->log("expr sort error: $@\n");
return 0;
}
return $result;
return $result;
}
sub print_expr {
my ($self, $player) = @_;
my ($self, $player) = @_;
return undef if $player->{games_played} == 0;
return undef if $player->{games_played} == 0;
my $result = eval {
$self->{expr}->val({
highscore => $player->{high_score},
lowscore => $player->{low_score},
avgscore => $player->{avg_score},
goodlies => $player->{good_lies},
badlies => $player->{questions_played} - $player->{good_lies},
first => $player->{times_first},
second => $player->{times_second},
third => $player->{times_third},
mentions => $player->{games_played} - $player->{times_first} - $player->{times_second} - $player->{times_third},
games => $player->{games_played},
questions => $player->{questions_played},
goodguesses => $player->{good_guesses},
badguesses => $player->{bad_guesses},
deceptions => $player->{players_deceived}
});
};
my $result = eval {
$self->{expr}->val(
{
highscore => $player->{high_score},
lowscore => $player->{low_score},
avgscore => $player->{avg_score},
goodlies => $player->{good_lies},
badlies => $player->{questions_played} - $player->{good_lies},
first => $player->{times_first},
second => $player->{times_second},
third => $player->{times_third},
mentions => $player->{games_played} - $player->{times_first} - $player->{times_second} - $player->{times_third},
games => $player->{games_played},
questions => $player->{questions_played},
goodguesses => $player->{good_guesses},
badguesses => $player->{bad_guesses},
deceptions => $player->{players_deceived}
}
);
};
if ($@) {
$self->{pbot}->{logger}->log("Error in expr print: $@\n");
return undef;
}
if ($@) {
$self->{pbot}->{logger}->log("Error in expr print: $@\n");
return undef;
}
return "$player->{nick}: $result";
return "$player->{nick}: $result";
}
sub rank {
my ($self, $arguments) = @_;
my ($self, $arguments) = @_;
my %ranks = (
highscore => { sort => sub { $self->sort_generic('high_score', @_) }, print => sub { $self->print_generic('high_score', @_) }, title => 'high score' },
lowscore => { sort => sub { $self->sort_generic('low_score', @_) }, print => sub { $self->print_generic('low_score', @_) }, title => 'low score' },
avgscore => { sort => sub { $self->sort_generic('avg_score', @_) }, print => sub { $self->print_avg_score(@_) }, title => 'average score' },
goodlies => { sort => sub { $self->sort_generic('good_lies', @_) }, print => sub { $self->print_generic('good_lies', @_) }, title => 'good lies' },
badlies => { sort => sub { $self->sort_bad_lies(@_) }, print => sub { $self->print_bad_lies(@_) }, title => 'bad lies' },
first => { sort => sub { $self->sort_generic('times_first', @_) }, print => sub { $self->print_generic('times_first', @_) }, title => 'first place' },
second => { sort => sub { $self->sort_generic('times_second', @_) }, print => sub { $self->print_generic('times_second', @_) }, title => 'second place' },
third => { sort => sub { $self->sort_generic('times_third', @_) }, print => sub { $self->print_generic('times_third', @_) }, title => 'third place' },
mentions => { sort => sub { $self->sort_mentions(@_) }, print => sub { $self->print_mentions(@_) }, title => 'mentions' },
games => { sort => sub { $self->sort_generic('games_played', @_) }, print => sub { $self->print_generic('games_played', @_) }, title => 'games played' },
questions => { sort => sub { $self->sort_generic('questions_played', @_) }, print => sub { $self->print_generic('questions_played', @_) }, title => 'questions played' },
goodguesses => { sort => sub { $self->sort_generic('good_guesses', @_) }, print => sub { $self->print_generic('good_guesses', @_) }, title => 'good guesses' },
badguesses => { sort => sub { $self->sort_generic('bad_guesses', @_) }, print => sub { $self->print_generic('bad_guesses', @_) }, title => 'bad guesses' },
deceptions => { sort => sub { $self->sort_generic('players_deceived', @_) }, print => sub { $self->print_generic('players_deceived', @_) }, title => 'players deceived' },
expr => { sort => sub { $self->sort_expr(@_) }, print => sub { $self->print_expr(@_) }, title => 'expr' },
);
my %ranks = (
highscore => {
sort => sub { $self->sort_generic('high_score', @_) },
print => sub { $self->print_generic('high_score', @_) },
title => 'high score'
},
lowscore => {
sort => sub { $self->sort_generic('low_score', @_) },
print => sub { $self->print_generic('low_score', @_) },
title => 'low score'
},
avgscore => {
sort => sub { $self->sort_generic('avg_score', @_) },
print => sub { $self->print_avg_score(@_) },
title => 'average score'
},
goodlies => {
sort => sub { $self->sort_generic('good_lies', @_) },
print => sub { $self->print_generic('good_lies', @_) },
title => 'good lies'
},
badlies => {
sort => sub { $self->sort_bad_lies(@_) },
print => sub { $self->print_bad_lies(@_) },
title => 'bad lies'
},
first => {
sort => sub { $self->sort_generic('times_first', @_) },
print => sub { $self->print_generic('times_first', @_) },
title => 'first place'
},
second => {
sort => sub { $self->sort_generic('times_second', @_) },
print => sub { $self->print_generic('times_second', @_) },
title => 'second place'
},
third => {
sort => sub { $self->sort_generic('times_third', @_) },
print => sub { $self->print_generic('times_third', @_) },
title => 'third place'
},
mentions => {
sort => sub { $self->sort_mentions(@_) },
print => sub { $self->print_mentions(@_) },
title => 'mentions'
},
games => {
sort => sub { $self->sort_generic('games_played', @_) },
print => sub { $self->print_generic('games_played', @_) },
title => 'games played'
},
questions => {
sort => sub { $self->sort_generic('questions_played', @_) },
print => sub { $self->print_generic('questions_played', @_) },
title => 'questions played'
},
goodguesses => {
sort => sub { $self->sort_generic('good_guesses', @_) },
print => sub { $self->print_generic('good_guesses', @_) },
title => 'good guesses'
},
badguesses => {
sort => sub { $self->sort_generic('bad_guesses', @_) },
print => sub { $self->print_generic('bad_guesses', @_) },
title => 'bad guesses'
},
deceptions => {
sort => sub { $self->sort_generic('players_deceived', @_) },
print => sub { $self->print_generic('players_deceived', @_) },
title => 'players deceived'
},
expr => {
sort => sub { $self->sort_expr(@_) },
print => sub { $self->print_expr(@_) },
title => 'expr'
},
);
my @order = qw/highscore lowscore avgscore first second third mentions games questions goodlies badlies deceptions goodguesses badguesses expr/;
my @order = qw/highscore lowscore avgscore first second third mentions games questions goodlies badlies deceptions goodguesses badguesses expr/;
if (not $arguments) {
my $result = "Usage: rank [-]<keyword> [offset] or rank [-]<nick>; available keywords: ";
$result .= join ', ', @order;
$result .= ".\n";
$result .= "Prefix with a dash to invert sort.\n";
return $result;
}
$arguments = lc $arguments;
if ($arguments =~ s/^([+-])//) {
$self->{rank_direction} = $1;
} else {
$self->{rank_direction} = '+';
}
my $offset = 1;
if ($arguments =~ s/\s+(\d+)$//) {
$offset = $1;
}
my $opt_arg;
if ($arguments =~ /^expr/) {
if ($arguments =~ s/^expr (.+)$/expr/) {
$opt_arg = $1;
} else {
return "Usage: spinach rank expr <expression>";
if (not $arguments) {
my $result = "Usage: rank [-]<keyword> [offset] or rank [-]<nick>; available keywords: ";
$result .= join ', ', @order;
$result .= ".\n";
$result .= "Prefix with a dash to invert sort.\n";
return $result;
}
$arguments = lc $arguments;
if ($arguments =~ s/^([+-])//) { $self->{rank_direction} = $1; }
else { $self->{rank_direction} = '+'; }
my $offset = 1;
if ($arguments =~ s/\s+(\d+)$//) { $offset = $1; }
my $opt_arg;
if ($arguments =~ /^expr/) {
if ($arguments =~ s/^expr (.+)$/expr/) { $opt_arg = $1; }
else { return "Usage: spinach rank expr <expression>"; }
}
if (not exists $ranks{$arguments}) {
$self->{stats}->begin;
my $player_id = $self->{stats}->get_player_id($arguments, $self->{channel}, 1);
my $player_data = $self->{stats}->get_player_data($player_id);
if (not defined $player_id) {
$self->{stats}->end;
return "I don't know anybody named $arguments.";
}
my $players = $self->{stats}->get_all_players($self->{channel});
my @rankings;
foreach my $key (@order) {
next if $key eq 'expr';
my $sort_method = $ranks{$key}->{sort};
@$players = sort $sort_method @$players;
my $rank = 0;
my $stats;
my $last_value = -1;
foreach my $player (@$players) {
$stats = $ranks{$key}->{print}->($player);
if (defined $stats) {
my ($value) = $stats =~ /[^:]+:\s+(.*)/;
$rank++ if $value ne $last_value;
$last_value = $value;
} else {
$rank++ if lc $player->{nick} eq $arguments;
}
last if lc $player->{nick} eq $arguments;
}
if (not $rank) { push @rankings, "$ranks{key}->{title}: N/A"; }
else {
if (not $stats) { push @rankings, "$ranks{$key}->{title}: N/A"; }
else {
$stats =~ s/[^:]+:\s+//;
push @rankings, "$ranks{$key}->{title}: #$rank ($stats)";
}
}
}
my $result = "$player_data->{nick}'s rankings: ";
$result .= join ', ', @rankings;
$self->{stats}->end;
return $result;
}
}
if (not exists $ranks{$arguments}) {
$self->{stats}->begin;
my $player_id = $self->{stats}->get_player_id($arguments, $self->{channel}, 1);
my $player_data = $self->{stats}->get_player_data($player_id);
if (not defined $player_id) {
$self->{stats}->end;
return "I don't know anybody named $arguments.";
}
my $players = $self->{stats}->get_all_players($self->{channel});
my @rankings;
foreach my $key (@order) {
next if $key eq 'expr';
my $sort_method = $ranks{$key}->{sort};
@$players = sort $sort_method @$players;
my $rank = 0;
my $stats;
my $last_value = -1;
foreach my $player (@$players) {
$stats = $ranks{$key}->{print}->($player);
if (defined $stats) {
my ($value) = $stats =~ /[^:]+:\s+(.*)/;
$rank++ if $value ne $last_value;
$last_value = $value;
} else {
$rank++ if lc $player->{nick} eq $arguments;
if ($arguments eq 'expr') {
$self->{expr} = eval { Math::Expression::Evaluator->new($opt_arg) };
if ($@) {
my $error = $@;
$error =~ s/ at .*//ms;
return "Bad expression: $error";
}
last if lc $player->{nick} eq $arguments;
}
if (not $rank) {
push @rankings, "$ranks{key}->{title}: N/A";
} else {
if (not $stats) {
push @rankings, "$ranks{$key}->{title}: N/A";
} else {
$stats =~ s/[^:]+:\s+//;
push @rankings, "$ranks{$key}->{title}: #$rank ($stats)";
}
}
$self->{expr}->optimize;
}
my $sort_method = $ranks{$arguments}->{sort};
@$players = sort $sort_method @$players;
my @ranking;
my $rank = 0;
my $last_value = -1;
foreach my $player (@$players) {
my $entry = $ranks{$arguments}->{print}->($player);
if (defined $entry) {
my ($value) = $entry =~ /[^:]+:\s+(.*)/;
$rank++ if $value ne $last_value;
$last_value = $value;
next if $rank < $offset;
push @ranking, "#$rank $entry" if defined $entry;
last if scalar @ranking >= 15;
}
}
my $result;
if (not scalar @ranking) {
if ($offset > 1) { $result = "No rankings available for $self->{channel} at offset #$offset.\n"; }
else { $result = "No rankings available for $self->{channel} yet.\n"; }
} else {
if ($arguments eq 'expr') { $result = "Rankings for $opt_arg: "; }
else { $result = "Rankings for $ranks{$arguments}->{title}: "; }
$result .= join ', ', @ranking;
}
my $result = "$player_data->{nick}'s rankings: ";
$result .= join ', ', @rankings;
$self->{stats}->end;
return $result;
}
$self->{stats}->begin;
my $players = $self->{stats}->get_all_players($self->{channel});
if ($arguments eq 'expr') {
$self->{expr} = eval { Math::Expression::Evaluator->new($opt_arg) };
if ($@) {
my $error = $@;
$error =~ s/ at .*//ms;
return "Bad expression: $error";
}
$self->{expr}->optimize;
}
my $sort_method = $ranks{$arguments}->{sort};
@$players = sort $sort_method @$players;
my @ranking;
my $rank = 0;
my $last_value = -1;
foreach my $player (@$players) {
my $entry = $ranks{$arguments}->{print}->($player);
if (defined $entry) {
my ($value) = $entry =~ /[^:]+:\s+(.*)/;
$rank++ if $value ne $last_value;
$last_value = $value;
next if $rank < $offset;
push @ranking, "#$rank $entry" if defined $entry;
last if scalar @ranking >= 15;
}
}
my $result;
if (not scalar @ranking) {
if ($offset > 1) {
$result = "No rankings available for $self->{channel} at offset #$offset.\n";
} else {
$result = "No rankings available for $self->{channel} yet.\n";
}
} else {
if ($arguments eq 'expr') {
$result = "Rankings for $opt_arg: ";
} else {
$result = "Rankings for $ranks{$arguments}->{title}: ";
}
$result .= join ', ', @ranking;
}
$self->{stats}->end;
return $result;
}
1;

View File

@ -15,27 +15,27 @@ use DBI;
use Carp qw(shortmess);
sub new {
my ($class, %conf) = @_;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
my ($class, %conf) = @_;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($self, %conf) = @_;
$self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
$self->{filename} = $conf{filename} // 'stats.sqlite';
my ($self, %conf) = @_;
$self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
$self->{filename} = $conf{filename} // 'stats.sqlite';
}
sub begin {
my $self = shift;
my $self = shift;
$self->{pbot}->{logger}->log("Opening Spinach stats SQLite database: $self->{filename}\n");
$self->{pbot}->{logger}->log("Opening Spinach stats SQLite database: $self->{filename}\n");
$self->{dbh} = DBI->connect("dbi:SQLite:dbname=$self->{filename}", "", "", { RaiseError => 1, PrintError => 0 }) or die $DBI::errstr;
$self->{dbh} = DBI->connect("dbi:SQLite:dbname=$self->{filename}", "", "", {RaiseError => 1, PrintError => 0}) or die $DBI::errstr;
eval {
$self->{dbh}->do(<< 'SQL');
eval {
$self->{dbh}->do(<< 'SQL');
CREATE TABLE IF NOT EXISTS Stats (
id INTEGER PRIMARY KEY,
nick TEXT NOT NULL COLLATE NOCASE,
@ -54,125 +54,122 @@ CREATE TABLE IF NOT EXISTS Stats (
bad_guesses INTEGER DEFAULT 0
)
SQL
};
};
$self->{pbot}->{logger}->log("Error creating database: $@\n") if $@;
$self->{pbot}->{logger}->log("Error creating database: $@\n") if $@;
}
sub end {
my $self = shift;
my $self = shift;
if (exists $self->{dbh} and defined $self->{dbh}) {
$self->{pbot}->{logger}->log("Closing stats SQLite database\n");
$self->{dbh}->disconnect();
delete $self->{dbh};
}
if (exists $self->{dbh} and defined $self->{dbh}) {
$self->{pbot}->{logger}->log("Closing stats SQLite database\n");
$self->{dbh}->disconnect();
delete $self->{dbh};
}
}
sub add_player {
my ($self, $id, $nick, $channel) = @_;
my ($self, $id, $nick, $channel) = @_;
eval {
my $sth = $self->{dbh}->prepare('INSERT INTO Stats (id, nick, channel) VALUES (?, ?, ?)');
$sth->execute($id, $nick, $channel);
};
eval {
my $sth = $self->{dbh}->prepare('INSERT INTO Stats (id, nick, channel) VALUES (?, ?, ?)');
$sth->execute($id, $nick, $channel);
};
if ($@) {
$self->{pbot}->{logger}->log("Spinach stats: failed to add new player ($id, $nick $channel): $@\n");
return 0;
}
if ($@) {
$self->{pbot}->{logger}->log("Spinach stats: failed to add new player ($id, $nick $channel): $@\n");
return 0;
}
return $id;
return $id;
}
sub get_player_id {
my ($self, $nick, $channel, $dont_create_new) = @_;
my ($self, $nick, $channel, $dont_create_new) = @_;
my ($account_id) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($nick);
$account_id = $self->{pbot}->{messagehistory}->{database}->get_ancestor_id($account_id);
my ($account_id) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($nick);
$account_id = $self->{pbot}->{messagehistory}->{database}->get_ancestor_id($account_id);
return undef if not $account_id;
return undef if not $account_id;
my $id = eval {
my $sth = $self->{dbh}->prepare('SELECT id FROM Stats WHERE id = ? AND channel = ?');
$sth->execute($account_id, $channel);
my $row = $sth->fetchrow_hashref();
return $row->{id};
};
my $id = eval {
my $sth = $self->{dbh}->prepare('SELECT id FROM Stats WHERE id = ? AND channel = ?');
$sth->execute($account_id, $channel);
my $row = $sth->fetchrow_hashref();
return $row->{id};
};
if ($@) {
$self->{pbot}->{logger}->log("Spinach stats: failed to get player id: $@\n");
return undef;
}
if ($@) {
$self->{pbot}->{logger}->log("Spinach stats: failed to get player id: $@\n");
return undef;
}
$id = $self->add_player($account_id, $nick, $channel) if not defined $id and not $dont_create_new;
return $id;
$id = $self->add_player($account_id, $nick, $channel) if not defined $id and not $dont_create_new;
return $id;
}
sub get_player_data {
my ($self, $id, @columns) = @_;
my ($self, $id, @columns) = @_;
return undef if not $id;
return undef if not $id;
my $player_data = eval {
my $sql = 'SELECT ';
my $player_data = eval {
my $sql = 'SELECT ';
if (not @columns) {
$sql .= '*';
} else {
my $comma = '';
foreach my $column (@columns) {
$sql .= "$comma$column";
$comma = ', ';
}
}
if (not @columns) { $sql .= '*'; }
else {
my $comma = '';
foreach my $column (@columns) {
$sql .= "$comma$column";
$comma = ', ';
}
}
$sql .= ' FROM Stats WHERE id = ?';
my $sth = $self->{dbh}->prepare($sql);
$sth->execute($id);
return $sth->fetchrow_hashref();
};
print STDERR $@ if $@;
return $player_data;
$sql .= ' FROM Stats WHERE id = ?';
my $sth = $self->{dbh}->prepare($sql);
$sth->execute($id);
return $sth->fetchrow_hashref();
};
print STDERR $@ if $@;
return $player_data;
}
sub update_player_data {
my ($self, $id, $data) = @_;
my ($self, $id, $data) = @_;
eval {
my $sql = 'UPDATE Stats SET ';
eval {
my $sql = 'UPDATE Stats SET ';
my $comma = '';
foreach my $key (keys %$data) {
$sql .= "$comma$key = ?";
$comma = ', ';
}
my $comma = '';
foreach my $key (keys %$data) {
$sql .= "$comma$key = ?";
$comma = ', ';
}
$sql .= ' WHERE id = ?';
$sql .= ' WHERE id = ?';
my $sth = $self->{dbh}->prepare($sql);
my $sth = $self->{dbh}->prepare($sql);
my $param = 1;
foreach my $key (keys %$data) {
$sth->bind_param($param++, $data->{$key});
}
my $param = 1;
foreach my $key (keys %$data) { $sth->bind_param($param++, $data->{$key}); }
$sth->bind_param($param, $id);
$sth->execute();
};
print STDERR $@ if $@;
$sth->bind_param($param, $id);
$sth->execute();
};
print STDERR $@ if $@;
}
sub get_all_players {
my ($self, $channel) = @_;
my ($self, $channel) = @_;
my $players = eval {
my $sth = $self->{dbh}->prepare('SELECT * FROM Stats WHERE channel = ?');
$sth->execute($channel);
return $sth->fetchall_arrayref({});
};
$self->{pbot}->{logger}->log($@) if $@;
return $players;
my $players = eval {
my $sth = $self->{dbh}->prepare('SELECT * FROM Stats WHERE channel = ?');
$sth->execute($channel);
return $sth->fetchall_arrayref({});
};
$self->{pbot}->{logger}->log($@) if $@;
return $players;
}
1;

View File

@ -9,94 +9,90 @@ use Time::Piece;
my $self = {};
sub load_questions {
my ($filename) = @_;
my ($filename) = @_;
if (not defined $filename) {
$filename = $ENV{HOME} . "/pbot/data/spinach/trivia.json";
}
if (not defined $filename) { $filename = $ENV{HOME} . "/pbot/data/spinach/trivia.json"; }
$self->{loaded_filename} = $filename;
$self->{loaded_filename} = $filename;
my $contents = do {
open my $fh, '<', $filename or do {
print "Spinach: Failed to open $filename: $!\n";
return "Failed to load $filename";
my $contents = do {
open my $fh, '<', $filename or do {
print "Spinach: Failed to open $filename: $!\n";
return "Failed to load $filename";
};
local $/;
<$fh>;
};
local $/;
<$fh>;
};
$self->{questions} = decode_json $contents;
$self->{categories} = ();
$self->{questions} = decode_json $contents;
$self->{categories} = ();
my $questions;
foreach my $key (keys %{$self->{questions}}) {
foreach my $question (@{$self->{questions}->{$key}}) {
$question->{category} = uc $question->{category};
$self->{categories}{$question->{category}}{$question->{id}} = $question;
my $questions;
foreach my $key (keys %{$self->{questions}}) {
foreach my $question (@{$self->{questions}->{$key}}) {
$question->{category} = uc $question->{category};
$self->{categories}{$question->{category}}{$question->{id}} = $question;
if (not exists $question->{seen_timestamp}) {
$question->{seen_timestamp} = 0;
}
if (not exists $question->{seen_timestamp}) { $question->{seen_timestamp} = 0; }
$questions++;
$questions++;
}
}
}
my $categories;
foreach my $category (sort { keys %{$self->{categories}{$b}} <=> keys %{$self->{categories}{$a}} } keys %{$self->{categories}}) {
my $count = keys %{$self->{categories}{$category}};
print "Category [$category]: $count\n";
$categories++;
}
my $categories;
foreach my $category (sort { keys %{$self->{categories}{$b}} <=> keys %{$self->{categories}{$a}} } keys %{$self->{categories}}) {
my $count = keys %{$self->{categories}{$category}};
print "Category [$category]: $count\n";
$categories++;
}
print "Spinach: Loaded $questions questions in $categories categories.\n";
return "Loaded $questions questions in $categories categories.";
print "Spinach: Loaded $questions questions in $categories categories.\n";
return "Loaded $questions questions in $categories categories.";
}
sub save_questions {
my $json = encode_json $self->{questions};
my $filename = exists $self->{loaded_filename} ? $self->{loaded_filename} : $self->{questions_filename};
open my $fh, '>', $filename or do {
print "Failed to open Spinach file $filename: $!\n";
return;
};
print $fh "$json\n";
close $fh;
my $json = encode_json $self->{questions};
my $filename = exists $self->{loaded_filename} ? $self->{loaded_filename} : $self->{questions_filename};
open my $fh, '>', $filename or do {
print "Failed to open Spinach file $filename: $!\n";
return;
};
print $fh "$json\n";
close $fh;
}
load_questions;
open my $fh, '<', 'seent' or do {
print "Failed to open seent file: $!\n";
die;
print "Failed to open seent file: $!\n";
die;
};
my $nr = 0;
foreach my $line (<$fh>) {
++$nr;
my ($date, $id) = $line =~ m/^(.*?) :: .*? question:.*?\s(\d+,?\d*)\)/;
++$nr;
my ($date, $id) = $line =~ m/^(.*?) :: .*? question:.*?\s(\d+,?\d*)\)/;
if (not defined $date or not defined $id) {
print "Parse error at line $nr\n";
die;
}
$id =~ s/,//g;
print "matched [$date] and [$id]\n";
my $time = Time::Piece->strptime($date, "%a %b %e %H:%M:%S %Y");
print "epoch: ", $time->epoch, "\n";
foreach my $q (@{$self->{questions}->{questions}}) {
if ($q->{id} == $id) {
print "question: $q->{question}\n";
$q->{seen_timestamp} = $time->epoch;
last;
if (not defined $date or not defined $id) {
print "Parse error at line $nr\n";
die;
}
$id =~ s/,//g;
print "matched [$date] and [$id]\n";
my $time = Time::Piece->strptime($date, "%a %b %e %H:%M:%S %Y");
print "epoch: ", $time->epoch, "\n";
foreach my $q (@{$self->{questions}->{questions}}) {
if ($q->{id} == $id) {
print "question: $q->{question}\n";
$q->{seen_timestamp} = $time->epoch;
last;
}
}
}
}
close $fh;

View File

@ -21,87 +21,84 @@ use warnings; use strict;
use feature 'unicode_strings';
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_public(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.caction', sub { $self->on_public(@_) });
my ($self, %conf) = @_;
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_public(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.caction', sub { $self->on_public(@_) });
}
sub unload {
my ($self) = @_;
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.caction');
my ($self) = @_;
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.caction');
}
sub on_public {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my $channel = lc $event->{event}->{to}[0];
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
my $channel = lc $event->{event}->{to}[0];
($nick, $user, $host) = $self->{pbot}->{irchandlers}->normalize_hostmask($nick, $user, $host);
($nick, $user, $host) = $self->{pbot}->{irchandlers}->normalize_hostmask($nick, $user, $host);
my $nosubs = $self->{pbot}->{registry}->get_value($channel, 'notyposub');
return 0 if defined $nosubs and not $nosubs;
my $nosubs = $self->{pbot}->{registry}->get_value($channel, 'notyposub');
return 0 if defined $nosubs and not $nosubs;
return 0 if $channel !~ m/^#/;
return 0 if $event->{interpreted};
return 0 if $channel !~ m/^#/;
return 0 if $event->{interpreted};
if ($msg =~ m/^\s*s([[:punct:]])/) {
my $separator = $1;
my $sep = quotemeta $separator;
if ($msg =~ m/^\s*s${sep}(.*?)(?<!\\)${sep}(.*?)(?<!\\)${sep}([g]*).*$/ or $msg =~ m/^\s*s${sep}(.*?)(?<!\\)${sep}(.*)$/) {
my ($regex, $replacement, $modifiers) = ($1, $2, $3);
eval {
my $rx = qr/$regex/;
if ($msg =~ m/^\s*s([[:punct:]])/) {
my $separator = $1;
my $sep = quotemeta $separator;
if ($msg =~ m/^\s*s${sep}(.*?)(?<!\\)${sep}(.*?)(?<!\\)${sep}([g]*).*$/ or $msg =~ m/^\s*s${sep}(.*?)(?<!\\)${sep}(.*)$/) {
my ($regex, $replacement, $modifiers) = ($1, $2, $3);
eval {
my $messages = $self->{pbot}->{messagehistory}->{database}->get_recent_messages_from_channel($channel, 50, $self->{pbot}->{messagehistory}->{MSG_CHAT}, 'DESC');
my $rx = qr/$regex/;
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
my $messages = $self->{pbot}->{messagehistory}->{database}->get_recent_messages_from_channel($channel, 50, $self->{pbot}->{messagehistory}->{MSG_CHAT}, 'DESC');
my $bot_trigger = $self->{pbot}->{registry}->get_value($channel, 'trigger')
// $self->{pbot}->{registry}->get_value('general', 'trigger');
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
my $ignore_commands = $self->{pbot}->{registry}->get_value($channel, 'typosub_ignore_commands')
// $self->{pbot}->{registry}->get_value('typosub', 'ignore_commands') // 1;
my $bot_trigger = $self->{pbot}->{registry}->get_value($channel, 'trigger') // $self->{pbot}->{registry}->get_value('general', 'trigger');
foreach my $message (@$messages) {
next if $ignore_commands and $message->{msg} =~ m/^(?:$bot_trigger|$botnick.?)/;
next if $message->{msg} =~ m/^\s*s[[:punct:]](.*?)[[:punct:]](.*?)[[:punct:]]?g?\s*$/;
my $ignore_commands = $self->{pbot}->{registry}->get_value($channel, 'typosub_ignore_commands') // $self->{pbot}->{registry}->get_value('typosub', 'ignore_commands')
// 1;
if ($message->{msg} =~ /$rx/) {
my $hostmask = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_id($message->{id});
my ($target) = $hostmask =~ m/([^!]+)/;
my $result;
if ($nick eq $target) {
$result = "$nick meant to say: ";
} else {
$result = "$nick thinks $target meant to say: ";
foreach my $message (@$messages) {
next if $ignore_commands and $message->{msg} =~ m/^(?:$bot_trigger|$botnick.?)/;
next if $message->{msg} =~ m/^\s*s[[:punct:]](.*?)[[:punct:]](.*?)[[:punct:]]?g?\s*$/;
if ($message->{msg} =~ /$rx/) {
my $hostmask = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_id($message->{id});
my ($target) = $hostmask =~ m/([^!]+)/;
my $result;
if ($nick eq $target) { $result = "$nick meant to say: "; }
else { $result = "$nick thinks $target meant to say: "; }
my $text = $message->{msg};
if ($modifiers =~ m/g/) {
$text =~ s/$rx/$replacement/g;
my @stuff = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
my $i;
map { ++$i; $text =~ s/[\$\\]$i/$_/g; } @stuff;
} else {
$text =~ s/$rx/$replacement/;
my @stuff = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
my $i;
map { ++$i; $text =~ s/[\$\\]$i/$_/g; } @stuff;
}
$event->{conn}->privmsg($channel, "$result$text");
return 0;
}
}
};
if ($@) {
my $error = "Error in `s${separator}${regex}${separator}${replacement}${separator}${modifiers}`: $@";
$error =~ s/ at .*$//;
$event->{conn}->privmsg($nick, $error);
return 0;
}
my $text = $message->{msg};
if ($modifiers =~ m/g/) {
$text =~ s/$rx/$replacement/g;
my @stuff = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
my $i;
map { ++$i; $text =~ s/[\$\\]$i/$_/g; } @stuff;
} else {
$text =~ s/$rx/$replacement/;
my @stuff = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
my $i;
map { ++$i; $text =~ s/[\$\\]$i/$_/g; } @stuff;
}
$event->{conn}->privmsg($channel, "$result$text");
return 0;
}
}
};
if ($@) {
my $error = "Error in `s${separator}${regex}${separator}${replacement}${separator}${modifiers}`: $@";
$error =~ s/ at .*$//;
$event->{conn}->privmsg($nick, $error);
return 0;
}
}
}
return 0;
return 0;
}
1;

View File

@ -13,71 +13,69 @@ use parent 'Plugins::Plugin';
use warnings; use strict;
use feature 'unicode_strings';
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{registry}->add_default('text', 'general', 'show_url_titles', $conf{show_url_titles} // 1);
$self->{pbot}->{registry}->add_default('array', 'general', 'show_url_titles_channels', $conf{show_url_titles_channels} // '.*');
$self->{pbot}->{registry}->add_default('array', 'general', 'show_url_titles_ignore_channels', $conf{show_url_titles_ignore_channels} // 'none');
my ($self, %conf) = @_;
$self->{pbot}->{registry}->add_default('text', 'general', 'show_url_titles', $conf{show_url_titles} // 1);
$self->{pbot}->{registry}->add_default('array', 'general', 'show_url_titles_channels', $conf{show_url_titles_channels} // '.*');
$self->{pbot}->{registry}->add_default('array', 'general', 'show_url_titles_ignore_channels', $conf{show_url_titles_ignore_channels} // 'none');
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->show_url_titles(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.caction', sub { $self->show_url_titles(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->show_url_titles(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.caction', sub { $self->show_url_titles(@_) });
}
sub unload {
my ($self) = @_;
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.caction');
my ($self) = @_;
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.caction');
}
sub show_url_titles {
my ($self, $event_type, $event) = @_;
my $channel = $event->{event}->{to}[0];
my ($nick, $user, $host) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host);
my $msg = $event->{event}->{args}[0];
my ($self, $event_type, $event) = @_;
my $channel = $event->{event}->{to}[0];
my ($nick, $user, $host) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host);
my $msg = $event->{event}->{args}[0];
return 0 if not $msg =~ m/https?:\/\/[^\s]/;
return 0 if $event->{interpreted};
return 0 if not $msg =~ m/https?:\/\/[^\s]/;
return 0 if $event->{interpreted};
if ($self->{pbot}->{ignorelist}->check_ignore($nick, $user, $host, $channel)) {
my $admin = $self->{pbot}->{users}->loggedin_admin($channel, "$nick!$user\@$host");
if (!defined $admin || $admin->{level} < 10) {
return 0;
if ($self->{pbot}->{ignorelist}->check_ignore($nick, $user, $host, $channel)) {
my $admin = $self->{pbot}->{users}->loggedin_admin($channel, "$nick!$user\@$host");
if (!defined $admin || $admin->{level} < 10) { return 0; }
}
}
# no titles for unidentified users in +z channels
my $chanmodes = $self->{pbot}->{channels}->get_meta($channel, 'MODE');
if (defined $chanmodes and $chanmodes =~ m/z/) {
my $account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($account);
return 0 if not defined $nickserv or not length $nickserv;
}
if ($self->{pbot}->{registry}->get_value('general', 'show_url_titles')
and not $self->{pbot}->{registry}->get_value($channel, 'no_url_titles')
and not grep { $channel =~ /$_/i } $self->{pbot}->{registry}->get_value('general', 'show_url_titles_ignore_channels')
and grep { $channel =~ /$_/i } $self->{pbot}->{registry}->get_value('general', 'show_url_titles_channels')) {
my $count = 0;
while ($msg =~ s/(https?:\/\/[^\s]+)//i && ++$count <= 3) {
my $url = $1;
if ($self->{pbot}->{antispam}->is_spam('url', $url)) {
$self->{pbot}->{logger}->log("Ignoring spam URL $url\n");
next;
}
my $stuff = {
from => $channel, nick => $nick, user => $user, host => $host,
command => "title $nick $url", root_channel => $channel, root_keyword => "title",
keyword => "title", arguments => "$nick $url"
};
$self->{pbot}->{modules}->execute_module($stuff);
# no titles for unidentified users in +z channels
my $chanmodes = $self->{pbot}->{channels}->get_meta($channel, 'MODE');
if (defined $chanmodes and $chanmodes =~ m/z/) {
my $account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($account);
return 0 if not defined $nickserv or not length $nickserv;
}
}
return 0;
if ( $self->{pbot}->{registry}->get_value('general', 'show_url_titles')
and not $self->{pbot}->{registry}->get_value($channel, 'no_url_titles')
and not grep { $channel =~ /$_/i } $self->{pbot}->{registry}->get_value('general', 'show_url_titles_ignore_channels')
and grep { $channel =~ /$_/i } $self->{pbot}->{registry}->get_value('general', 'show_url_titles_channels'))
{
my $count = 0;
while ($msg =~ s/(https?:\/\/[^\s]+)//i && ++$count <= 3) {
my $url = $1;
if ($self->{pbot}->{antispam}->is_spam('url', $url)) {
$self->{pbot}->{logger}->log("Ignoring spam URL $url\n");
next;
}
my $stuff = {
from => $channel, nick => $nick, user => $user, host => $host,
command => "title $nick $url", root_channel => $channel, root_keyword => "title",
keyword => "title", arguments => "$nick $url"
};
$self->{pbot}->{modules}->execute_module($stuff);
}
}
return 0;
}
1;

View File

@ -8,6 +8,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package Plugins::Weather;
use parent 'Plugins::Plugin';
use warnings; use strict;
@ -18,107 +19,104 @@ use XML::LibXML;
use Getopt::Long qw(GetOptionsFromString);
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->weathercmd(@_) }, "weather", 0);
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->weathercmd(@_) }, "weather", 0);
}
sub unload {
my $self = shift;
$self->{pbot}->{commands}->unregister("weather");
my $self = shift;
$self->{pbot}->{commands}->unregister("weather");
}
sub weathercmd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "Usage: weather [-u <user account>] [location]";
my $getopt_error;
local $SIG{__WARN__} = sub {
$getopt_error = shift;
chomp $getopt_error;
};
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "Usage: weather [-u <user account>] [location]";
my $getopt_error;
local $SIG{__WARN__} = sub {
$getopt_error = shift;
chomp $getopt_error;
};
Getopt::Long::Configure("bundling");
Getopt::Long::Configure("bundling");
my ($user_override, $show_usage);
my ($ret, $args) = GetOptionsFromString($arguments,
'u=s' => \$user_override,
'h' => \$show_usage
);
my ($user_override, $show_usage);
my ($ret, $args) = GetOptionsFromString(
$arguments,
'u=s' => \$user_override,
'h' => \$show_usage
);
return $usage if $show_usage;
return "/say $getopt_error -- $usage" if defined $getopt_error;
$arguments = "@$args";
return $usage if $show_usage;
return "/say $getopt_error -- $usage" if defined $getopt_error;
$arguments = "@$args";
my $hostmask = defined $user_override ? $user_override : "$nick!$user\@$host";
my $location_override = $self->{pbot}->{users}->get_user_metadata($from, $hostmask, 'location') // '';
$arguments = $location_override if not length $arguments;
my $hostmask = defined $user_override ? $user_override : "$nick!$user\@$host";
my $location_override = $self->{pbot}->{users}->get_user_metadata($from, $hostmask, 'location') // '';
$arguments = $location_override if not length $arguments;
if (defined $user_override and not length $location_override) {
return "No location set or user account does not exist.";
}
if (defined $user_override and not length $location_override) { return "No location set or user account does not exist."; }
if (not length $arguments) {
return $usage;
}
return $self->get_weather($arguments);
if (not length $arguments) { return $usage; }
return $self->get_weather($arguments);
}
sub get_weather {
my ($self, $location) = @_;
my %cache_opt = (
'namespace' => 'accuweather',
'default_expires_in' => 3600
);
my ($self, $location) = @_;
my $ua = PBot::Utils::LWPUserAgentCached->new(\%cache_opt, timeout => 10);
my $response = $ua->get("http://rss.accuweather.com/rss/liveweather_rss.asp?metric=0&locCode=$location");
my %cache_opt = (
'namespace' => 'accuweather',
'default_expires_in' => 3600
);
my $xml;
if ($response->is_success) {
$xml = $response->decoded_content;
} else {
return "Failed to fetch weather data: " . $response->status_line;
}
my $ua = PBot::Utils::LWPUserAgentCached->new(\%cache_opt, timeout => 10);
my $response = $ua->get("http://rss.accuweather.com/rss/liveweather_rss.asp?metric=0&locCode=$location");
my $dom = XML::LibXML->load_xml(string => $xml);
my $xml;
my $result = '';
if ($response->is_success) { $xml = $response->decoded_content; }
else { return "Failed to fetch weather data: " . $response->status_line; }
foreach my $channel ($dom->findnodes('//channel')) {
my $title = $channel->findvalue('./title');
my $description = $channel->findvalue('./description');
my $dom = XML::LibXML->load_xml(string => $xml);
if ($description eq 'Invalid Location') {
return "Location $location not found. Use \"<city>, <country abbrev>\" (e.g. \"paris, fr\") or a US Zip Code or \"<city>, <state abbrev>, US\" (e.g., \"austin, tx, us\").";
my $result = '';
foreach my $channel ($dom->findnodes('//channel')) {
my $title = $channel->findvalue('./title');
my $description = $channel->findvalue('./description');
if ($description eq 'Invalid Location') {
return
"Location $location not found. Use \"<city>, <country abbrev>\" (e.g. \"paris, fr\") or a US Zip Code or \"<city>, <state abbrev>, US\" (e.g., \"austin, tx, us\").";
}
$title =~ s/ - AccuW.*$//;
$result .= "Weather for $title: ";
}
$title =~ s/ - AccuW.*$//;
$result .= "Weather for $title: ";
}
foreach my $item ($dom->findnodes('//item')) {
my $title = $item->findvalue('./title');
my $description = $item->findvalue('./description');
foreach my $item ($dom->findnodes('//item')) {
my $title = $item->findvalue('./title');
my $description = $item->findvalue('./description');
if ($title =~ m/^Currently:/) {
$title = $self->fix_temps($title);
$result .= "$title; ";
}
if ($title =~ m/^Currently:/) {
$title = $self->fix_temps($title);
$result .= "$title; ";
if ($title =~ m/Forecast$/) {
$description =~ s/ <img.*$//;
$description = $self->fix_temps($description);
$result .= "Forecast: $description";
last;
}
}
if ($title =~ m/Forecast$/) {
$description =~ s/ <img.*$//;
$description = $self->fix_temps($description);
$result .= "Forecast: $description";
last;
}
}
return $result;
return $result;
}
sub fix_temps {
my ($self, $text) = @_;
$text =~ s|(-?\d+)\s*F|my $f = $1; my $c = ($f - 32 ) * 5 / 9; $c = sprintf("%.1d", $c); "${f}F/${c}C"|eg;
return $text;
my ($self, $text) = @_;
$text =~ s|(-?\d+)\s*F|my $f = $1; my $c = ($f - 32 ) * 5 / 9; $c = sprintf("%.1d", $c); "${f}F/${c}C"|eg;
return $text;
}
1;

View File

@ -8,6 +8,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package Plugins::Wttr;
use parent 'Plugins::Plugin';
use warnings; use strict;
@ -15,6 +16,7 @@ use feature 'unicode_strings';
use utf8;
use feature 'switch';
no if $] >= 5.018, warnings => "experimental::smartmatch";
use PBot::Utils::LWPUserAgentCached;
@ -23,257 +25,234 @@ use URI::Escape qw/uri_escape_utf8/;
use Getopt::Long qw(GetOptionsFromString);
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->wttrcmd(@_) }, "wttr", 0);
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->wttrcmd(@_) }, "wttr", 0);
}
sub unload {
my $self = shift;
$self->{pbot}->{commands}->unregister("wttr");
my $self = shift;
$self->{pbot}->{commands}->unregister("wttr");
}
sub wttrcmd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my @wttr_options = (
"conditions",
"forecast",
"feelslike",
"uvindex",
"visibility",
"dewpoint",
"heatindex",
"cloudcover",
"wind",
"sunrise|sunset",
"moon",
"chances",
"sunhours",
"snowfall",
"location",
"default",
"all",
);
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "Usage: wttr [-u <user account>] [location] [" . join(' ', map { "-$_" } @wttr_options) . "]";
my $getopt_error;
local $SIG{__WARN__} = sub {
$getopt_error = shift;
chomp $getopt_error;
};
my @wttr_options = (
"conditions",
"forecast",
"feelslike",
"uvindex",
"visibility",
"dewpoint",
"heatindex",
"cloudcover",
"wind",
"sunrise|sunset",
"moon",
"chances",
"sunhours",
"snowfall",
"location",
"default",
"all",
);
Getopt::Long::Configure("bundling_override", "ignorecase_always");
my $usage = "Usage: wttr [-u <user account>] [location] [" . join(' ', map { "-$_" } @wttr_options) . "]";
my $getopt_error;
local $SIG{__WARN__} = sub {
$getopt_error = shift;
chomp $getopt_error;
};
my %options;
my ($ret, $args) = GetOptionsFromString($arguments,
\%options,
'u=s',
'h',
@wttr_options
);
Getopt::Long::Configure("bundling_override", "ignorecase_always");
return "/say $getopt_error -- $usage" if defined $getopt_error;
return $usage if exists $options{h};
$arguments = "@$args";
my %options;
my ($ret, $args) = GetOptionsFromString(
$arguments,
\%options,
'u=s',
'h',
@wttr_options
);
my $hostmask = defined $options{u} ? $options{u} : "$nick!$user\@$host";
my $location_override = $self->{pbot}->{users}->get_user_metadata($from, $hostmask, 'location') // '';
$arguments = $location_override if not length $arguments;
return "/say $getopt_error -- $usage" if defined $getopt_error;
return $usage if exists $options{h};
$arguments = "@$args";
if (defined $options{u} and not length $location_override) {
return "No location set or user account does not exist.";
}
my $hostmask = defined $options{u} ? $options{u} : "$nick!$user\@$host";
my $location_override = $self->{pbot}->{users}->get_user_metadata($from, $hostmask, 'location') // '';
$arguments = $location_override if not length $arguments;
delete $options{u};
if (defined $options{u} and not length $location_override) { return "No location set or user account does not exist."; }
if (not length $arguments) {
return $usage;
}
delete $options{u};
$options{default} = 1 if not keys %options;
if (not length $arguments) { return $usage; }
if (defined $options{all}) {
%options = ();
map { my $opt = $_; $opt =~ s/\|.*$//; $options{$opt} = 1 } @wttr_options;
delete $options{all};
delete $options{default};
}
$options{default} = 1 if not keys %options;
return $self->get_wttr($arguments, %options);
if (defined $options{all}) {
%options = ();
map { my $opt = $_; $opt =~ s/\|.*$//; $options{$opt} = 1 } @wttr_options;
delete $options{all};
delete $options{default};
}
return $self->get_wttr($arguments, %options);
}
sub get_wttr {
my ($self, $location, %options) = @_;
my %cache_opt = (
'namespace' => 'wttr',
'default_expires_in' => 3600
);
my ($self, $location, %options) = @_;
my $location_uri = uri_escape_utf8 $location;
my %cache_opt = (
'namespace' => 'wttr',
'default_expires_in' => 3600
);
my $ua = PBot::Utils::LWPUserAgentCached->new(\%cache_opt, timeout => 30);
my $response = $ua->get("http://wttr.in/$location_uri?format=j1&m");
my $location_uri = uri_escape_utf8 $location;
my $json;
if ($response->is_success) {
$json = $response->decoded_content;
} else {
return "Failed to fetch weather data: " . $response->status_line;
}
my $ua = PBot::Utils::LWPUserAgentCached->new(\%cache_opt, timeout => 30);
my $response = $ua->get("http://wttr.in/$location_uri?format=j1&m");
my $wttr = decode_json $json;
my $json;
# title-case location
$location = ucfirst lc $location;
$location =~ s/( |\.)(\w)/$1 . uc $2/ge;
if ($response->is_success) { $json = $response->decoded_content; }
else { return "Failed to fetch weather data: " . $response->status_line; }
my $result = "Weather for $location: ";
my $wttr = decode_json $json;
my $c = $wttr->{'current_condition'}->[0];
my $w = $wttr->{'weather'}->[0];
my $h = $w->{'hourly'}->[0];
# title-case location
$location = ucfirst lc $location;
$location =~ s/( |\.)(\w)/$1 . uc $2/ge;
foreach my $option (sort keys %options) {
given ($option) {
when ('default') {
$result .= "Currently: $c->{'weatherDesc'}->[0]->{'value'}: $c->{'temp_C'}C/$c->{'temp_F'}F; ";
$result .= "Forecast: High: $w->{maxtempC}C/$w->{maxtempF}F, Low: $w->{mintempC}C/$w->{mintempF}F; ";
$result .= "Condition changes: ";
my $result = "Weather for $location: ";
my $last_condition = $c->{'weatherDesc'}->[0]->{'value'};
my $sep = '';
my $c = $wttr->{'current_condition'}->[0];
my $w = $wttr->{'weather'}->[0];
my $h = $w->{'hourly'}->[0];
foreach my $hour (@{ $w->{'hourly'} }) {
my $condition = $hour->{'weatherDesc'}->[0]->{'value'};
my $temp = "$hour->{FeelsLikeC}C/$hour->{FeelsLikeF}F";
my $time = sprintf "%04d", $hour->{'time'};
$time =~ s/(\d{2})$/:$1/;
foreach my $option (sort keys %options) {
given ($option) {
when ('default') {
$result .= "Currently: $c->{'weatherDesc'}->[0]->{'value'}: $c->{'temp_C'}C/$c->{'temp_F'}F; ";
$result .= "Forecast: High: $w->{maxtempC}C/$w->{maxtempF}F, Low: $w->{mintempC}C/$w->{mintempF}F; ";
$result .= "Condition changes: ";
if ($condition ne $last_condition) {
$result .= "$sep$time: $condition ($temp)";
$sep = '-> ';
$last_condition = $condition;
}
my $last_condition = $c->{'weatherDesc'}->[0]->{'value'};
my $sep = '';
foreach my $hour (@{$w->{'hourly'}}) {
my $condition = $hour->{'weatherDesc'}->[0]->{'value'};
my $temp = "$hour->{FeelsLikeC}C/$hour->{FeelsLikeF}F";
my $time = sprintf "%04d", $hour->{'time'};
$time =~ s/(\d{2})$/:$1/;
if ($condition ne $last_condition) {
$result .= "$sep$time: $condition ($temp)";
$sep = '-> ';
$last_condition = $condition;
}
}
if ($sep eq '') { $result .= $last_condition; }
$result .= "; ";
}
when ('conditions') {
$result .= "Current conditions: $c->{'weatherDesc'}->[0]->{'value'}: $c->{'temp_C'}C/$c->{'temp_F'}F (Feels like $c->{'FeelsLikeC'}C/$c->{'FeelsLikeF'}F); ";
$result .= "Cloud cover: $c->{'cloudcover'}%; Visibility: $c->{'visibility'}km; ";
$result .= "Wind: $c->{'windspeedKmph'}kph/$c->{'windspeedMiles'}mph $c->{'winddirDegree'}°/$c->{'winddir16Point'}; ";
$result .= "Humidity: $c->{'humidity'}%; Precip: $c->{'precipMM'}mm; Pressure: $c->{'pressure'}hPa; UV Index: $c->{'uvIndex'}; ";
}
when ('forecast') {
$result .= "Hourly forecast: ";
my ($last_temp, $last_condition, $sep) = ('', '', '');
foreach my $hour (@{$wttr->{'weather'}->[0]->{'hourly'}}) {
my $temp = "$hour->{FeelsLikeC}C/$hour->{FeelsLikeF}F";
my $condition = $hour->{'weatherDesc'}->[0]->{'value'};
my $text = '';
if ($temp ne $last_temp) {
$text .= $temp;
$last_temp = $temp;
}
if ($condition ne $last_condition) {
$text .= ' ' if length $text;
$text .= $condition;
$last_condition = $condition;
}
if (length $text) {
my $time = sprintf "%04d", $hour->{'time'};
$time =~ s/(\d{2})$/:$1/;
$result .= "$sep $time: $text";
$sep = ', ';
}
}
$result .= "; ";
}
when ('chances') {
$result .= "Chances of: ";
$result .= "Fog: $h->{'chanceoffog'}%, " if $h->{'chanceoffog'};
$result .= "Frost: $h->{'chanceoffrost'}%, " if $h->{'chanceoffrost'};
$result .= "High temp: $h->{'chanceofhightemp'}%, " if $h->{'chanceofhightemp'};
$result .= "Overcast: $h->{'chanceofovercast'}%, " if $h->{'chanceofovercast'};
$result .= "Rain: $h->{'chanceofrain'}%, " if $h->{'chanceofrain'};
$result .= "Remaining dry: $h->{'chanceofremdry'}%, " if $h->{'chanceofremdry'};
$result .= "Snow: $h->{'chanceofsnow'}%, " if $h->{'chanceofsnow'};
$result .= "Sunshine: $h->{'chanceofsunshine'}%, " if $h->{'chanceofsunshine'};
$result .= "Thunder: $h->{'chanceofthunder'}%, " if $h->{'chanceofthunder'};
$result .= "Windy: $h->{'chanceofwindy'}%, " if $h->{'chanceofwindy'};
$result =~ s/,\s+$/; /;
}
when ('wind') {
$result .= "Wind: $c->{'windspeedKmph'}kph/$c->{'windspeedMiles'}mph $c->{'winddirDegree'}°/$c->{'winddir16Point'}, ";
$result .= "gust: $h->{'WindGustKmph'}kph/$h->{'WindGustMiles'}mph, chill: $h->{'WindChillC'}C/$h->{'WindChillF'}F; ";
}
when ('location') {
my $l = $wttr->{'request'}->[0];
$result .= "Location: $l->{'query'} ($l->{'type'}); ";
}
when ('dewpoint') { $result .= "Dew point: $h->{'DewPointC'}C/$h->{'DewPointF'}F; "; }
when ('feelslike') { $result .= "Feels like: $h->{'FeelsLikeC'}C/$h->{'FeelsLikeF'}F; "; }
when ('heatindex') { $result .= "Heat index: $h->{'HeatIndexC'}C/$h->{'HeatIndexF'}F; "; }
when ('moon') {
my $a = $w->{'astronomy'}->[0];
$result .= "Moon: phase: $a->{'moon_phase'}, illumination: $a->{'moon_illumination'}%, rise: $a->{'moonrise'}, set: $a->{'moonset'}; ";
}
when ('sunrise') {
my $a = $w->{'astronomy'}->[0];
$result .= "Sun: rise: $a->{'sunrise'}, set: $a->{'sunset'}; ";
}
when ('sunhours') { $result .= "Hours of sun: $w->{'sunHour'}; "; }
when ('snowfall') { $result .= "Total snow: $w->{'totalSnow_cm'}cm; "; }
when ('uvindex') { $result .= "UV Index: $c->{'uvIndex'}; "; }
when ('visibility') { $result .= "Visibility: $c->{'visibility'}km; "; }
when ('cloudcover') { $result .= "Cloud cover: $c->{'cloudcover'}%; "; }
default { $result .= "Option $_ coming soon; " unless lc $_ eq 'u'; }
}
if ($sep eq '') {
$result .= $last_condition;
}
$result .= "; ";
}
when ('conditions') {
$result .= "Current conditions: $c->{'weatherDesc'}->[0]->{'value'}: $c->{'temp_C'}C/$c->{'temp_F'}F (Feels like $c->{'FeelsLikeC'}C/$c->{'FeelsLikeF'}F); ";
$result .= "Cloud cover: $c->{'cloudcover'}%; Visibility: $c->{'visibility'}km; ";
$result .= "Wind: $c->{'windspeedKmph'}kph/$c->{'windspeedMiles'}mph $c->{'winddirDegree'}°/$c->{'winddir16Point'}; ";
$result .= "Humidity: $c->{'humidity'}%; Precip: $c->{'precipMM'}mm; Pressure: $c->{'pressure'}hPa; UV Index: $c->{'uvIndex'}; ";
}
when ('forecast') {
$result .= "Hourly forecast: ";
my ($last_temp, $last_condition, $sep) = ('', '', '');
foreach my $hour (@{ $wttr->{'weather'}->[0]->{'hourly'} }) {
my $temp = "$hour->{FeelsLikeC}C/$hour->{FeelsLikeF}F";
my $condition = $hour->{'weatherDesc'}->[0]->{'value'};
my $text = '';
if ($temp ne $last_temp) {
$text .= $temp;
$last_temp = $temp;
}
if ($condition ne $last_condition) {
$text .= ' ' if length $text;
$text .= $condition;
$last_condition = $condition;
}
if (length $text) {
my $time = sprintf "%04d", $hour->{'time'};
$time =~ s/(\d{2})$/:$1/;
$result .= "$sep $time: $text";
$sep = ', ';
}
}
$result .= "; ";
}
when ('chances') {
$result .= "Chances of: ";
$result .= "Fog: $h->{'chanceoffog'}%, " if $h->{'chanceoffog'};
$result .= "Frost: $h->{'chanceoffrost'}%, " if $h->{'chanceoffrost'};
$result .= "High temp: $h->{'chanceofhightemp'}%, " if $h->{'chanceofhightemp'};
$result .= "Overcast: $h->{'chanceofovercast'}%, " if $h->{'chanceofovercast'};
$result .= "Rain: $h->{'chanceofrain'}%, " if $h->{'chanceofrain'};
$result .= "Remaining dry: $h->{'chanceofremdry'}%, " if $h->{'chanceofremdry'};
$result .= "Snow: $h->{'chanceofsnow'}%, " if $h->{'chanceofsnow'};
$result .= "Sunshine: $h->{'chanceofsunshine'}%, " if $h->{'chanceofsunshine'};
$result .= "Thunder: $h->{'chanceofthunder'}%, " if $h->{'chanceofthunder'};
$result .= "Windy: $h->{'chanceofwindy'}%, " if $h->{'chanceofwindy'};
$result =~ s/,\s+$/; /;
}
when ('wind') {
$result .= "Wind: $c->{'windspeedKmph'}kph/$c->{'windspeedMiles'}mph $c->{'winddirDegree'}°/$c->{'winddir16Point'}, ";
$result .= "gust: $h->{'WindGustKmph'}kph/$h->{'WindGustMiles'}mph, chill: $h->{'WindChillC'}C/$h->{'WindChillF'}F; ";
}
when ('location') {
my $l = $wttr->{'request'}->[0];
$result .= "Location: $l->{'query'} ($l->{'type'}); ";
}
when ('dewpoint') {
$result .= "Dew point: $h->{'DewPointC'}C/$h->{'DewPointF'}F; ";
}
when ('feelslike') {
$result .= "Feels like: $h->{'FeelsLikeC'}C/$h->{'FeelsLikeF'}F; ";
}
when ('heatindex') {
$result .= "Heat index: $h->{'HeatIndexC'}C/$h->{'HeatIndexF'}F; ";
}
when ('moon') {
my $a = $w->{'astronomy'}->[0];
$result .= "Moon: phase: $a->{'moon_phase'}, illumination: $a->{'moon_illumination'}%, rise: $a->{'moonrise'}, set: $a->{'moonset'}; ";
}
when ('sunrise') {
my $a = $w->{'astronomy'}->[0];
$result .= "Sun: rise: $a->{'sunrise'}, set: $a->{'sunset'}; ";
}
when ('sunhours') {
$result .= "Hours of sun: $w->{'sunHour'}; ";
}
when ('snowfall') {
$result .= "Total snow: $w->{'totalSnow_cm'}cm; ";
}
when ('uvindex') {
$result .= "UV Index: $c->{'uvIndex'}; ";
}
when ('visibility') {
$result .= "Visibility: $c->{'visibility'}km; ";
}
when ('cloudcover') {
$result .= "Cloud cover: $c->{'cloudcover'}%; ";
}
default {
$result .= "Option $_ coming soon; " unless lc $_ eq 'u';
}
}
}
$result =~ s/;\s+$//;
return $result;
$result =~ s/;\s+$//;
return $result;
}
1;

2
misc/tidy vendored Normal file
View File

@ -0,0 +1,2 @@
#!/bin/bash
perltidy -utf8 -nanl -boc $* -b -bext=/ && perltidy -conv -boc -utf8 -kis -l=170 -vmll -pt=2 -sbt=2 -bt=2 -nsfs -ce -b -bext=/ -nolq -nolc -iscl -nbbc -kgb $*

4
modules/ago.pl vendored
View File

@ -9,8 +9,8 @@ use Time::Duration;
my ($ago) = @ARGV;
if (not defined $ago) {
print "Usage: ago <seconds>\n";
exit 0;
print "Usage: ago <seconds>\n";
exit 0;
}
print ago_exact($ago), "\n";

256
modules/c11std.pl vendored
View File

@ -16,57 +16,55 @@ my $RESULTS_SPECIFIED = 2;
my $search = join ' ', @ARGV;
if (not length $search) {
print "Usage: c11std [-list] [-n#] [-section <section>] [search text] -- 'section' must be in the form of X.YpZ where X and Y are section/chapter and, optionally, pZ is paragraph. If both 'section' and 'search text' are specified, then the search space will be within the specified section. You may use -n # to skip to the #th match. To list only the section numbers containing 'search text', add -list.\n";
exit 0;
print
"Usage: c11std [-list] [-n#] [-section <section>] [search text] -- 'section' must be in the form of X.YpZ where X and Y are section/chapter and, optionally, pZ is paragraph. If both 'section' and 'search text' are specified, then the search space will be within the specified section. You may use -n # to skip to the #th match. To list only the section numbers containing 'search text', add -list.\n";
exit 0;
}
my ($section, $paragraph, $section_specified, $paragraph_specified, $match, $list_only, $list_titles);
$section_specified = 0;
$section_specified = 0;
$paragraph_specified = 0;
if ($search =~ s/-section\s*([A-Z0-9\.p]+)//i or $search =~ s/\b([A-Z0-9]+\.[0-9\.p]+)//i) {
$section = $1;
$section = $1;
if ($section =~ s/p(\d+)//i) {
$paragraph = $1;
$paragraph_specified = $USER_SPECIFIED;
} else {
$paragraph = 1;
}
if ($section =~ s/p(\d+)//i) {
$paragraph = $1;
$paragraph_specified = $USER_SPECIFIED;
} else {
$paragraph = 1;
}
$section = "$section." if $section =~ m/^[A-Z0-9]+$/i;
$section = "$section." if $section =~ m/^[A-Z0-9]+$/i;
$section_specified = 1;
$section_specified = 1;
}
if ($search =~ s/-n\s*(\d+)//) {
$match = $1;
} else {
$match = 1;
}
if ($search =~ s/-n\s*(\d+)//) { $match = $1; }
else { $match = 1; }
if ($search =~ s/-list//i) {
$list_only = 1;
$list_titles = 1; # Added here instead of removing -titles option
$list_only = 1;
$list_titles = 1; # Added here instead of removing -titles option
}
if ($search =~ s/-titles//i) {
$list_only = 1;
$list_titles = 1;
$list_only = 1;
$list_titles = 1;
}
$search =~ s/^\s+//;
$search =~ s/\s+$//;
if (not defined $section) {
$section = "1.";
$paragraph = 1;
$section = "1.";
$paragraph = 1;
}
if ($list_only and not length $search) {
print "You must specify some search text to use with -list.\n";
exit 0;
print "You must specify some search text to use with -list.\n";
exit 0;
}
open FH, "<n1570.out" or die "Could not open n1570: $!";
@ -77,156 +75,150 @@ my $text = join '', @contents;
$text =~ s/\r//g;
my $result;
my $found_section = "";
my $found_section = "";
my $found_section_title = "";
my $section_title;
my $found_paragraph;
my $found = 0;
my $found = 0;
my $matches = 0;
my $this_section;
my $comma = "";
if ($list_only) {
$result = "Sections containing '$search':\n ";
}
if ($list_only) { $result = "Sections containing '$search':\n "; }
my $qsearch = quotemeta $search;
$qsearch =~ s/\\ / /g;
$qsearch =~ s/\s+/\\s+/g;
while ($text =~ m/^\s{0,4}([0-9A-Z]+\.[0-9\.]*)/msg) {
$this_section = $1;
$this_section = $1;
print "----------------------------------\n" if $debug >= 2;
print "Processing section [$this_section]\n" if $debug;
print "----------------------------------\n" if $debug >= 2;
print "Processing section [$this_section]\n" if $debug;
if ($section_specified and $this_section !~ m/^$section/i) {
print "No section match, skipping.\n" if $debug >= 4;
next;
}
my $section_text;
if ($text =~ m/(.*?)^(?=\s{0,4}(?!FOOTNOTE)[0-9A-Z]+\.)/msg) {
$section_text = $1;
} else {
print "No section text, end of file marker found.\n" if $debug >= 4;
last;
}
if ($section =~ /FOOTNOTE/i) {
$section_text =~ s/^\s{4}//ms;
$section_text =~ s/^\s{4}FOOTNOTE.*//msi;
$section_text =~ s/^\d.*//ms;
} elsif ($section_text =~ m/(.*?)$/msg) {
$section_title = $1 if length $1;
$section_title =~ s/^\s+//;
$section_title =~ s/\s+$//;
}
print "$this_section [$section_title]\n" if $debug >= 2;
while ($section_text =~ m/^(\d+)\s(.*?)^(?=\d)/msgic or $section_text =~ m/^(\d+)\s(.*)/msgi) {
my $p = $1 ;
my $t = $2;
print "paragraph $p: [$t]\n" if $debug >= 3;
if ($paragraph_specified == $USER_SPECIFIED and not length $search and $p == $paragraph) {
$result = $t if not $found;
$found_paragraph = $p;
$found_section = $this_section;
$found_section_title = $section_title;
$found = 1;
last;
if ($section_specified and $this_section !~ m/^$section/i) {
print "No section match, skipping.\n" if $debug >= 4;
next;
}
if (length $search) {
eval {
if ($t =~ m/\b$qsearch\b/mis or $section_title =~ m/\b$qsearch\b/mis) {
$matches++;
if ($matches >= $match) {
if ($list_only) {
$result .= sprintf("%s%-15s", $comma, $this_section."p".$p);
$result .= " $section_title" if $list_titles;
$comma = ",\n ";
} else {
if (not $found) {
$result = $t;
$found_section = $this_section;
$found_section_title = $section_title;
$found_paragraph = $p;
$paragraph_specified = $RESULTS_SPECIFIED;
}
$found = 1;
}
}
my $section_text;
if ($text =~ m/(.*?)^(?=\s{0,4}(?!FOOTNOTE)[0-9A-Z]+\.)/msg) { $section_text = $1; }
else {
print "No section text, end of file marker found.\n" if $debug >= 4;
last;
}
if ($section =~ /FOOTNOTE/i) {
$section_text =~ s/^\s{4}//ms;
$section_text =~ s/^\s{4}FOOTNOTE.*//msi;
$section_text =~ s/^\d.*//ms;
} elsif ($section_text =~ m/(.*?)$/msg) {
$section_title = $1 if length $1;
$section_title =~ s/^\s+//;
$section_title =~ s/\s+$//;
}
print "$this_section [$section_title]\n" if $debug >= 2;
while ($section_text =~ m/^(\d+)\s(.*?)^(?=\d)/msgic or $section_text =~ m/^(\d+)\s(.*)/msgi) {
my $p = $1;
my $t = $2;
print "paragraph $p: [$t]\n" if $debug >= 3;
if ($paragraph_specified == $USER_SPECIFIED and not length $search and $p == $paragraph) {
$result = $t if not $found;
$found_paragraph = $p;
$found_section = $this_section;
$found_section_title = $section_title;
$found = 1;
last;
}
};
if ($@) {
print "Error in search regex; you may need to escape characters such as *, ?, ., etc.\n";
if (length $search) {
eval {
if ($t =~ m/\b$qsearch\b/mis or $section_title =~ m/\b$qsearch\b/mis) {
$matches++;
if ($matches >= $match) {
if ($list_only) {
$result .= sprintf("%s%-15s", $comma, $this_section . "p" . $p);
$result .= " $section_title" if $list_titles;
$comma = ",\n ";
} else {
if (not $found) {
$result = $t;
$found_section = $this_section;
$found_section_title = $section_title;
$found_paragraph = $p;
$paragraph_specified = $RESULTS_SPECIFIED;
}
$found = 1;
}
}
}
};
if ($@) {
print "Error in search regex; you may need to escape characters such as *, ?, ., etc.\n";
exit 0;
}
}
}
last if $found && $paragraph_specified == $USER_SPECIFIED;
if ($paragraph_specified == $USER_SPECIFIED) {
if (length $search) { print "No such text '$search' in paragraph $paragraph of section $section of n1570.\n"; }
else { print "No such paragraph $paragraph in section $section of n1570.\n"; }
exit 0;
}
}
}
last if $found && $paragraph_specified == $USER_SPECIFIED;
if ($paragraph_specified == $USER_SPECIFIED) {
if (length $search) {
print "No such text '$search' in paragraph $paragraph of section $section of n1570.\n";
} else {
print "No such paragraph $paragraph in section $section of n1570.\n";
if (defined $section_specified and not length $search) {
$found = 1;
$found_section = $this_section;
$found_section_title = $section_title;
$found_paragraph = $paragraph;
$result = $section_text;
last;
}
exit 0;
}
if (defined $section_specified and not length $search) {
$found = 1;
$found_section = $this_section;
$found_section_title = $section_title;
$found_paragraph = $paragraph;
$result = $section_text;
last;
}
}
if (not $found and $comma eq "") {
$search =~ s/\\s\+/ /g;
if ($section_specified) {
print "No such text '$search' found within section '$section' in C11 Draft Standard (n1570).\n" if length $search;
print "No such section '$section' in C11 Draft Standard (n1570).\n" if not length $search;
exit 0;
}
$search =~ s/\\s\+/ /g;
if ($section_specified) {
print "No such text '$search' found within section '$section' in C11 Draft Standard (n1570).\n" if length $search;
print "No such section '$section' in C11 Draft Standard (n1570).\n" if not length $search;
exit 0;
}
print "No such section '$section' in C11 Draft Standard (n1570).\n" if not length $search;
print "No such text '$search' found in C11 Draft Standard (n1570).\n" if length $search;
exit 0;
print "No such section '$section' in C11 Draft Standard (n1570).\n" if not length $search;
print "No such text '$search' found in C11 Draft Standard (n1570).\n" if length $search;
exit 0;
}
$result =~ s/$found_section_title// if length $found_section_title;
$result =~ s/^\s+//;
$result =~ s/\s+$//;
=cut
$result =~ s/\s+/ /g;
$result =~ s/[\n\r]/ /g;
=cut
if ($matches > 1 and not $list_only) {
print "Displaying $match of $matches matches: ";
}
if ($matches > 1 and not $list_only) { print "Displaying $match of $matches matches: "; }
if ($comma eq "") {
=cut
print $found_section;
print "p" . $found_paragraph if $paragraph_specified;
=cut
print "http://www.iso-9899.info/n1570.html\#$found_section";
print "p" . $found_paragraph if $paragraph_specified;
print "\n\n";
print "[", $found_section_title, "]\n\n" if length $found_section_title;
print "http://www.iso-9899.info/n1570.html\#$found_section";
print "p" . $found_paragraph if $paragraph_specified;
print "\n\n";
print "[", $found_section_title, "]\n\n" if length $found_section_title;
}
$result =~ s/\s*Constraints\s*$//;

598
modules/c2english.pl vendored
View File

@ -17,19 +17,18 @@ my $debug = 0;
my $code = join ' ', @ARGV;
if (not length $code) {
print "Usage: english <any C11 code>\n";
exit;
print "Usage: english <any C11 code>\n";
exit;
}
my $output;
my $force;
if ($code =~ s/^-f\s+//) {
$force = 1;
}
if ($code =~ s/^-f\s+//) { $force = 1; }
my ($has_function, $has_main, $got_nomain);
my $prelude_base = "#define _XOPEN_SOURCE 9001\n#define __USE_XOPEN\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <math.h>\n#include <limits.h>\n#include <sys/types.h>\n#include <stdint.h>\n#include <errno.h>\n#include <ctype.h>\n#include <assert.h>\n#include <stdnoreturn.h>\n#include <stdbool.h>\n#include <stdalign.h>\n#include <time.h>\n#include <stddef.h>\n#include <uchar.h>\n#define _Atomic\n#define _Static_assert(a, b)\n\n";
my $prelude_base =
"#define _XOPEN_SOURCE 9001\n#define __USE_XOPEN\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <math.h>\n#include <limits.h>\n#include <sys/types.h>\n#include <stdint.h>\n#include <errno.h>\n#include <ctype.h>\n#include <assert.h>\n#include <stdnoreturn.h>\n#include <stdbool.h>\n#include <stdalign.h>\n#include <time.h>\n#include <stddef.h>\n#include <uchar.h>\n#define _Atomic\n#define _Static_assert(a, b)\n\n";
my $prelude = $prelude_base;
print "code before: [$code]\n" if $debug;
@ -38,55 +37,47 @@ print "code before: [$code]\n" if $debug;
my $new_code = "";
use constant {
NORMAL => 0,
DOUBLE_QUOTED => 1,
SINGLE_QUOTED => 2,
NORMAL => 0,
DOUBLE_QUOTED => 1,
SINGLE_QUOTED => 2,
};
my $state = NORMAL;
my $state = NORMAL;
my $escaped = 0;
while ($code =~ m/(.)/gs) {
my $ch = $1;
my $ch = $1;
given ($ch) {
when ('\\') {
if ($escaped == 0) {
$escaped = 1;
next;
}
given ($ch) {
when ('\\') {
if ($escaped == 0) {
$escaped = 1;
next;
}
}
if ($state == NORMAL) {
when ($_ eq '"' and not $escaped) { $state = DOUBLE_QUOTED; }
when ($_ eq "'" and not $escaped) { $state = SINGLE_QUOTED; }
when ($_ eq 'n' and $escaped == 1) {
$ch = "\n";
$escaped = 0;
}
}
if ($state == DOUBLE_QUOTED) {
when ($_ eq '"' and not $escaped) { $state = NORMAL; }
}
if ($state == SINGLE_QUOTED) {
when ($_ eq "'" and not $escaped) { $state = NORMAL; }
}
}
if ($state == NORMAL) {
when ($_ eq '"' and not $escaped) {
$state = DOUBLE_QUOTED;
}
when ($_ eq "'" and not $escaped) {
$state = SINGLE_QUOTED;
}
when ($_ eq 'n' and $escaped == 1) {
$ch = "\n";
$escaped = 0;
}
}
if ($state == DOUBLE_QUOTED) {
when ($_ eq '"' and not $escaped) {
$state = NORMAL;
}
}
if ($state == SINGLE_QUOTED) {
when ($_ eq "'" and not $escaped) {
$state = NORMAL;
}
}
}
$new_code .= '\\' and $escaped = 0 if $escaped;
$new_code .= $ch;
$new_code .= '\\' and $escaped = 0 if $escaped;
$new_code .= $ch;
}
$code = $new_code;
@ -95,69 +86,68 @@ print "code after \\n replacement: [$code]\n" if $debug;
my $single_quote = 0;
my $double_quote = 0;
my $parens = 0;
my $parens = 0;
$escaped = 0;
my $cpp = 0; # preprocessor
my $cpp = 0; # preprocessor
while ($code =~ m/(.)/msg) {
my $ch = $1;
my $pos = pos $code;
my $ch = $1;
my $pos = pos $code;
print "adding newlines, ch = [$ch], parens: $parens, cpp: $cpp, single: $single_quote, double: $double_quote, escaped: $escaped, pos: $pos\n" if $debug >= 10;
print "adding newlines, ch = [$ch], parens: $parens, cpp: $cpp, single: $single_quote, double: $double_quote, escaped: $escaped, pos: $pos\n" if $debug >= 10;
if ($ch eq '\\') {
$escaped = not $escaped;
} elsif ($ch eq '#' and not $cpp and not $escaped and not $single_quote and not $double_quote) {
$cpp = 1;
if ($ch eq '\\') { $escaped = not $escaped; }
elsif ($ch eq '#' and not $cpp and not $escaped and not $single_quote and not $double_quote) {
$cpp = 1;
if ($code =~ m/include\s*[<"]([^>"]*)[>"]/msg) {
my $match = $1;
$pos = pos $code;
substr ($code, $pos, 0) = "\n";
pos $code = $pos;
$cpp = 0;
if ($code =~ m/include\s*[<"]([^>"]*)[>"]/msg) {
my $match = $1;
$pos = pos $code;
substr($code, $pos, 0) = "\n";
pos $code = $pos;
$cpp = 0;
} else {
pos $code = $pos;
}
} elsif ($ch eq '"') {
$double_quote = not $double_quote unless $escaped or $single_quote;
$escaped = 0;
} elsif ($ch eq '(' and not $single_quote and not $double_quote) {
$parens++;
} elsif ($ch eq ')' and not $single_quote and not $double_quote) {
$parens--;
$parens = 0 if $parens < 0;
} elsif ($ch eq ';' and not $cpp and not $single_quote and not $double_quote and $parens == 0) {
if (not substr($code, $pos, 1) =~ m/[\n\r]/) {
substr($code, $pos, 0) = "\n";
pos $code = $pos + 1;
}
} elsif ($ch eq "'") {
$single_quote = not $single_quote unless $escaped or $double_quote;
$escaped = 0;
} elsif ($ch eq 'n' and $escaped) {
if (not $single_quote and not $double_quote) {
print "added newline\n" if $debug >= 10;
substr($code, $pos - 2, 2) = "\n";
pos $code = $pos;
$cpp = 0;
}
$escaped = 0;
} elsif ($ch eq '{' and not $cpp and not $single_quote and not $double_quote) {
if (not substr($code, $pos, 1) =~ m/[\n\r]/) {
substr($code, $pos, 0) = "\n";
pos $code = $pos + 1;
}
} elsif ($ch eq '}' and not $cpp and not $single_quote and not $double_quote) {
if (not substr($code, $pos, 1) =~ m/[\n\r;]/) {
substr($code, $pos, 0) = "\n";
pos $code = $pos + 1;
}
} elsif ($ch eq "\n" and $cpp and not $single_quote and not $double_quote) {
$cpp = 0;
} else {
pos $code = $pos;
$escaped = 0;
}
} elsif ($ch eq '"') {
$double_quote = not $double_quote unless $escaped or $single_quote;
$escaped = 0;
} elsif ($ch eq '(' and not $single_quote and not $double_quote) {
$parens++;
} elsif ($ch eq ')' and not $single_quote and not $double_quote) {
$parens--;
$parens = 0 if $parens < 0;
} elsif ($ch eq ';' and not $cpp and not $single_quote and not $double_quote and $parens == 0) {
if (not substr($code, $pos, 1) =~ m/[\n\r]/) {
substr ($code, $pos, 0) = "\n";
pos $code = $pos + 1;
}
} elsif ($ch eq "'") {
$single_quote = not $single_quote unless $escaped or $double_quote;
$escaped = 0;
} elsif ($ch eq 'n' and $escaped) {
if (not $single_quote and not $double_quote) {
print "added newline\n" if $debug >= 10;
substr ($code, $pos - 2, 2) = "\n";
pos $code = $pos;
$cpp = 0;
}
$escaped = 0;
} elsif ($ch eq '{' and not $cpp and not $single_quote and not $double_quote) {
if (not substr($code, $pos, 1) =~ m/[\n\r]/) {
substr ($code, $pos, 0) = "\n";
pos $code = $pos + 1;
}
} elsif ($ch eq '}' and not $cpp and not $single_quote and not $double_quote) {
if (not substr($code, $pos, 1) =~ m/[\n\r;]/) {
substr ($code, $pos, 0) = "\n";
pos $code = $pos + 1;
}
} elsif ($ch eq "\n" and $cpp and not $single_quote and not $double_quote) {
$cpp = 0;
} else {
$escaped = 0;
}
}
print "code after \\n additions: [$code]\n" if $debug;
@ -168,11 +158,8 @@ $white_code =~ s/(?:\"((?:\\\"|(?!\").)*)\")/'"' . ('-' x length $1) . '"'/ge;
$white_code =~ s/(?:\'((?:\\\'|(?!\').)*)\')/"'" . ('-' x length $1) . "'"/ge;
my $precode;
if ($white_code =~ m/#include/) {
$precode = $code;
} else {
$precode = $prelude . $code;
}
if ($white_code =~ m/#include/) { $precode = $code; }
else { $precode = $prelude . $code; }
$code = '';
my $warn_unterminated_define = 0;
@ -181,117 +168,111 @@ print "--- precode: [$precode]\n" if $debug;
my $lang = 'C89';
if ($lang eq 'C89' or $lang eq 'C99' or $lang eq 'C11' or $lang eq 'C++') {
my $prelude = '';
while ($precode =~ s/^\s*(#.*\n{1,2})//g) {
$prelude .= $1;
}
my $prelude = '';
while ($precode =~ s/^\s*(#.*\n{1,2})//g) { $prelude .= $1; }
if ($precode =~ m/^\s*(#.*)/ms) {
my $line = $1;
if ($precode =~ m/^\s*(#.*)/ms) {
my $line = $1;
if ($line !~ m/\n/) {
$warn_unterminated_define = 1;
if ($line !~ m/\n/) { $warn_unterminated_define = 1; }
}
}
print "*** prelude: [$prelude]\n precode: [$precode]\n" if $debug;
print "*** prelude: [$prelude]\n precode: [$precode]\n" if $debug;
my $preprecode = $precode;
my $preprecode = $precode;
# white-out contents of quoted literals
$preprecode =~ s/(?:\"((?:\\\"|(?!\").)*)\")/'"' . ('-' x length $1) . '"'/ge;
$preprecode =~ s/(?:\'((?:\\\'|(?!\').)*)\')/"'" . ('-' x length $1) . "'"/ge;
# white-out contents of quoted literals
$preprecode =~ s/(?:\"((?:\\\"|(?!\").)*)\")/'"' . ('-' x length $1) . '"'/ge;
$preprecode =~ s/(?:\'((?:\\\'|(?!\').)*)\')/"'" . ('-' x length $1) . "'"/ge;
# strip C and C++ style comments
if ($lang eq 'C89') {
$preprecode =~ s#/\*[^*]*\*+([^/*][^*]*\*+)*/# #gs;
$preprecode =~ s#|//([^\\]|[^\n][\n]?)*?\n|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse;
} else {
$preprecode =~ s#|//([^\\]|[^\n][\n]?)*?\n|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse;
$preprecode =~ s#/\*[^*]*\*+([^/*][^*]*\*+)*/# #gs;
}
print "preprecode: [$preprecode]\n" if $debug;
print "looking for functions, has main: $has_main\n" if $debug >= 2;
my $func_regex = qr/^([ *\w]+)\s+([ ()*\w]+)\s*\(([^;{]*)\s*\)\s*({.*|<%.*|\?\?<.*)/ims;
# look for potential functions to extract
while ($preprecode =~ /$func_regex/ms) {
my ($pre_ret, $pre_ident, $pre_params, $pre_potential_body) = ($1, $2, $3, $4);
print "looking for functions, found [$pre_ret][$pre_ident][$pre_params][$pre_potential_body], has main: $has_main\n" if $debug >= 1;
# find the pos at which this function lives, for extracting from precode
$preprecode =~ m/(\Q$pre_ret\E\s+\Q$pre_ident\E\s*\(\s*\Q$pre_params\E\s*\)\s*\Q$pre_potential_body\E)/g;
my $extract_pos = (pos $preprecode) - (length $1);
# now that we have the pos, substitute out the extracted potential function from preprecode
$preprecode =~ s/$func_regex//ms;
# create tmpcode object that starts from extract pos, to skip any quoted code
my $tmpcode = substr($precode, $extract_pos);
print "tmpcode: [$tmpcode]\n" if $debug;
$precode = substr($precode, 0, $extract_pos);
print "precode: [$precode]\n" if $debug;
$tmpcode =~ m/$func_regex/ms;
my ($ret, $ident, $params, $potential_body) = ($1, $2, $3, $4);
print "1st extract: [$ret][$ident][$params][$potential_body]\n" if $debug;
$ret =~ s/^\s+//;
$ret =~ s/\s+$//;
if (not length $ret or $ret eq "else" or $ret eq "while" or $ret eq "if" or $ret eq "for" or $ident eq "for" or $ident eq "while" or $ident eq "if") {
$precode .= "$ret $ident ($params) $potential_body";
next;
# strip C and C++ style comments
if ($lang eq 'C89') {
$preprecode =~ s#/\*[^*]*\*+([^/*][^*]*\*+)*/# #gs;
$preprecode =~ s#|//([^\\]|[^\n][\n]?)*?\n|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse;
} else {
$tmpcode =~ s/$func_regex//ms;
$preprecode =~ s#|//([^\\]|[^\n][\n]?)*?\n|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse;
$preprecode =~ s#/\*[^*]*\*+([^/*][^*]*\*+)*/# #gs;
}
$potential_body =~ s/^\s*<%/{/ms;
$potential_body =~ s/%>\s*$/}/ms;
$potential_body =~ s/^\s*\?\?</{/ms;
$potential_body =~ s/\?\?>$/}/ms;
print "preprecode: [$preprecode]\n" if $debug;
my @extract = extract_bracketed($potential_body, '{}');
my $body;
if (not defined $extract[0]) {
if ($debug == 0) {
print "error: unmatched brackets\n";
print "looking for functions, has main: $has_main\n" if $debug >= 2;
my $func_regex = qr/^([ *\w]+)\s+([ ()*\w]+)\s*\(([^;{]*)\s*\)\s*({.*|<%.*|\?\?<.*)/ims;
# look for potential functions to extract
while ($preprecode =~ /$func_regex/ms) {
my ($pre_ret, $pre_ident, $pre_params, $pre_potential_body) = ($1, $2, $3, $4);
print "looking for functions, found [$pre_ret][$pre_ident][$pre_params][$pre_potential_body], has main: $has_main\n" if $debug >= 1;
# find the pos at which this function lives, for extracting from precode
$preprecode =~ m/(\Q$pre_ret\E\s+\Q$pre_ident\E\s*\(\s*\Q$pre_params\E\s*\)\s*\Q$pre_potential_body\E)/g;
my $extract_pos = (pos $preprecode) - (length $1);
# now that we have the pos, substitute out the extracted potential function from preprecode
$preprecode =~ s/$func_regex//ms;
# create tmpcode object that starts from extract pos, to skip any quoted code
my $tmpcode = substr($precode, $extract_pos);
print "tmpcode: [$tmpcode]\n" if $debug;
$precode = substr($precode, 0, $extract_pos);
print "precode: [$precode]\n" if $debug;
$tmpcode =~ m/$func_regex/ms;
my ($ret, $ident, $params, $potential_body) = ($1, $2, $3, $4);
print "1st extract: [$ret][$ident][$params][$potential_body]\n" if $debug;
$ret =~ s/^\s+//;
$ret =~ s/\s+$//;
if (not length $ret or $ret eq "else" or $ret eq "while" or $ret eq "if" or $ret eq "for" or $ident eq "for" or $ident eq "while" or $ident eq "if") {
$precode .= "$ret $ident ($params) $potential_body";
next;
} else {
print "error: unmatched brackets for function '$ident';\n";
print "body: [$potential_body]\n";
$tmpcode =~ s/$func_regex//ms;
}
exit;
} else {
$body = $extract[0];
$preprecode .= $extract[1];
$precode .= $extract[1];
$potential_body =~ s/^\s*<%/{/ms;
$potential_body =~ s/%>\s*$/}/ms;
$potential_body =~ s/^\s*\?\?</{/ms;
$potential_body =~ s/\?\?>$/}/ms;
my @extract = extract_bracketed($potential_body, '{}');
my $body;
if (not defined $extract[0]) {
if ($debug == 0) { print "error: unmatched brackets\n"; }
else {
print "error: unmatched brackets for function '$ident';\n";
print "body: [$potential_body]\n";
}
exit;
} else {
$body = $extract[0];
$preprecode .= $extract[1];
$precode .= $extract[1];
}
print "final extract: [$ret][$ident][$params][$body]\n" if $debug;
$code .= "$ret $ident($params) $body\n\n";
$has_main = 1 if $ident =~ m/^\s*\(?\s*main\s*\)?\s*$/;
$has_function = 1;
}
print "final extract: [$ret][$ident][$params][$body]\n" if $debug;
$code .= "$ret $ident($params) $body\n\n";
$has_main = 1 if $ident =~ m/^\s*\(?\s*main\s*\)?\s*$/;
$has_function = 1;
}
$precode =~ s/^\s+//;
$precode =~ s/\s+$//;
$precode =~ s/^\s+//;
$precode =~ s/\s+$//;
$precode =~ s/^{(.*)}$/$1/s;
$precode =~ s/^{(.*)}$/$1/s;
if (not $has_main and not $got_nomain) {
$code = "$prelude\n$code" . "int main(void) {\n$precode\n;\n}\n";
} else {
print "code: [$code]; precode: [$precode]\n" if $debug;
$code = "$prelude\n$precode\n\n$code\n";
}
if (not $has_main and not $got_nomain) { $code = "$prelude\n$code" . "int main(void) {\n$precode\n;\n}\n"; }
else {
print "code: [$code]; precode: [$precode]\n" if $debug;
$code = "$prelude\n$precode\n\n$code\n";
}
} else {
$code = $precode;
$code = $precode;
}
print "after func extract, code: [$code]\n" if $debug;
@ -314,86 +295,91 @@ print $fh $code;
close $fh;
#my ($ret, $result) = execute(10, "gcc -std=c89 -pedantic -Werror -Wno-unused -fsyntax-only -fno-diagnostics-show-option -fno-diagnostics-show-caret code.c");
my ($ret, $result) = execute(10, "gcc -std=c11 -pedantic -Werror -Wno-implicit -Wno-unused -fsyntax-only -fno-diagnostics-show-option -fno-diagnostics-show-caret code.c");
my ($ret, $result) =
execute(10, "gcc -std=c11 -pedantic -Werror -Wno-implicit -Wno-unused -fsyntax-only -fno-diagnostics-show-option -fno-diagnostics-show-caret code.c");
if (not $force and $ret != 0) {
$output = $result;
$output = $result;
#print STDERR "output: [$output]\n";
#print STDERR "output: [$output]\n";
$output =~ s/\s*In file included from\s+.*?:\d+:\d+:\s*//g;
$output =~ s/code\.c:\d+:\d+://g;
$output =~ s/code\.c://g;
$output =~ s/error=edantic/error=pedantic/g;
$output =~ s/(\d+:\d+:\s*)*cc1: all warnings being treated as errors//;
$output =~ s/(\d+:\d+:\s*)* \(first use in this function\)//g;
$output =~ s/(\d+:\d+:\s*)*error: \(Each undeclared identifier is reported only once.*?\)//msg;
$output =~ s/(\d+:\d+:\s*)*ld: warning: cannot find entry symbol _start; defaulting to [^ ]+//;
#$output =~ s/(\d+:\d+:\s*)*error: (.*?) error/error: $1; error/msg;
$output =~ s/(\d+:\d+:\s*)*\/tmp\/.*\.o://g;
$output =~ s/(\d+:\d+:\s*)*collect2: ld returned \d+ exit status//g;
$output =~ s/\(\.text\+[^)]+\)://g;
$output =~ s/\[ In/[In/;
$output =~ s/(\d+:\d+:\s*)*warning: Can't read pathname for load map: Input.output error.//g;
my $left_quote = chr(226) . chr(128) . chr(152);
my $right_quote = chr(226) . chr(128) . chr(153);
$output =~ s/$left_quote/'/msg;
$output =~ s/$right_quote/'/msg;
$output =~ s/`/'/msg;
$output =~ s/\t/ /g;
$output =~ s/(\d+:\d+:\s*)*\s*In function .main.:\s*//g;
$output =~ s/(\d+:\d+:\s*)*warning: unknown conversion type character 'b' in format \[-Wformat\]\s+(\d+:\d+:\s*)*warning: too many arguments for format \[-Wformat-extra-args\]/info: %b is a candide extension/g;
$output =~ s/(\d+:\d+:\s*)*warning: unknown conversion type character 'b' in format \[-Wformat\]//g;
$output =~ s/\s\(core dumped\)/./;
# $output =~ s/\[\s+/[/g;
$output =~ s/ \[enabled by default\]//g;
$output =~ s/initializer\s+warning: \(near/initializer (near/g;
$output =~ s/(\d+:\d+:\s*)*note: each undeclared identifier is reported only once for each function it appears in//g;
$output =~ s/\(gdb\)//g;
$output =~ s/", '\\(\d{3})' <repeats \d+ times>,? ?"/\\$1/g;
$output =~ s/, '\\(\d{3})' <repeats \d+ times>\s*//g;
$output =~ s/(\\000)+/\\0/g;
$output =~ s/\\0[^">']+/\\0/g;
$output =~ s/= (\d+) '\\0'/= $1/g;
$output =~ s/\\0"/"/g;
$output =~ s/"\\0/"/g;
$output =~ s/\.\.\.>/>/g;
# $output =~ s/(\\\d{3})+//g;
$output =~ s/<\s*included at \/home\/compiler\/>\s*//g;
$output =~ s/\s*compilation terminated due to -Wfatal-errors\.//g;
$output =~ s/^======= Backtrace.*\[vsyscall\]\s*$//ms;
$output =~ s/glibc detected \*\*\* \/home\/compiler\/code: //;
$output =~ s/: \/home\/compiler\/code terminated//;
$output =~ s/<Defined at \/home\/compiler\/>/<Defined at \/home\/compiler\/code.c:0>/g;
$output =~ s/\s*In file included from\s+\/usr\/include\/.*?:\d+:\d+:\s*/, /g;
$output =~ s/\s*collect2: error: ld returned 1 exit status//g;
$output =~ s/In function\s*`main':\s*\/home\/compiler\/ undefined reference to/error: undefined reference to/g;
$output =~ s/\/home\/compiler\///g;
$output =~ s/compilation terminated.//;
$output =~ s/<'(.*)' = char>/<'$1' = int>/g;
$output =~ s/= (-?\d+) ''/= $1/g;
$output =~ s/, <incomplete sequence >//g;
$output =~ s/\s*error: expected ';' before 'return'//g;
$output =~ s/^\s+//;
$output =~ s/\s+$//;
$output =~ s/error: ISO C forbids nested functions\s+//g;
$output =~ s/\s*note: this is the location of the previous definition//g;
$output =~ s/\s*note: use option -std=c99 or -std=gnu99 to compile your code//g;
$output =~ s/\s*\(declared at .*?\)//g;
$output =~ s/, note: declared here//g;
$output =~ s#/usr/include/.*?.h:\d+:\d+:/##g;
$output =~ s/\s*error: storage size of.*?isn't known\s*//g;
$output =~ s/; did you mean '.*?'\?//g;
$output =~ s/\s*In file included from\s+.*?:\d+:\d+:\s*//g;
$output =~ s/code\.c:\d+:\d+://g;
$output =~ s/code\.c://g;
$output =~ s/error=edantic/error=pedantic/g;
$output =~ s/(\d+:\d+:\s*)*cc1: all warnings being treated as errors//;
$output =~ s/(\d+:\d+:\s*)* \(first use in this function\)//g;
$output =~ s/(\d+:\d+:\s*)*error: \(Each undeclared identifier is reported only once.*?\)//msg;
$output =~ s/(\d+:\d+:\s*)*ld: warning: cannot find entry symbol _start; defaulting to [^ ]+//;
# don't error about undeclared objects
$output =~ s/error: '[^']+' undeclared\s*//g;
#$output =~ s/(\d+:\d+:\s*)*error: (.*?) error/error: $1; error/msg;
$output =~ s/(\d+:\d+:\s*)*\/tmp\/.*\.o://g;
$output =~ s/(\d+:\d+:\s*)*collect2: ld returned \d+ exit status//g;
$output =~ s/\(\.text\+[^)]+\)://g;
$output =~ s/\[ In/[In/;
$output =~ s/(\d+:\d+:\s*)*warning: Can't read pathname for load map: Input.output error.//g;
my $left_quote = chr(226) . chr(128) . chr(152);
my $right_quote = chr(226) . chr(128) . chr(153);
$output =~ s/$left_quote/'/msg;
$output =~ s/$right_quote/'/msg;
$output =~ s/`/'/msg;
$output =~ s/\t/ /g;
$output =~ s/(\d+:\d+:\s*)*\s*In function .main.:\s*//g;
$output =~
s/(\d+:\d+:\s*)*warning: unknown conversion type character 'b' in format \[-Wformat\]\s+(\d+:\d+:\s*)*warning: too many arguments for format \[-Wformat-extra-args\]/info: %b is a candide extension/g;
$output =~ s/(\d+:\d+:\s*)*warning: unknown conversion type character 'b' in format \[-Wformat\]//g;
$output =~ s/\s\(core dumped\)/./;
if (length $output) {
print "$output\n";
exit 0;
} else {
$output = undef;
}
# $output =~ s/\[\s+/[/g;
$output =~ s/ \[enabled by default\]//g;
$output =~ s/initializer\s+warning: \(near/initializer (near/g;
$output =~ s/(\d+:\d+:\s*)*note: each undeclared identifier is reported only once for each function it appears in//g;
$output =~ s/\(gdb\)//g;
$output =~ s/", '\\(\d{3})' <repeats \d+ times>,? ?"/\\$1/g;
$output =~ s/, '\\(\d{3})' <repeats \d+ times>\s*//g;
$output =~ s/(\\000)+/\\0/g;
$output =~ s/\\0[^">']+/\\0/g;
$output =~ s/= (\d+) '\\0'/= $1/g;
$output =~ s/\\0"/"/g;
$output =~ s/"\\0/"/g;
$output =~ s/\.\.\.>/>/g;
# $output =~ s/(\\\d{3})+//g;
$output =~ s/<\s*included at \/home\/compiler\/>\s*//g;
$output =~ s/\s*compilation terminated due to -Wfatal-errors\.//g;
$output =~ s/^======= Backtrace.*\[vsyscall\]\s*$//ms;
$output =~ s/glibc detected \*\*\* \/home\/compiler\/code: //;
$output =~ s/: \/home\/compiler\/code terminated//;
$output =~ s/<Defined at \/home\/compiler\/>/<Defined at \/home\/compiler\/code.c:0>/g;
$output =~ s/\s*In file included from\s+\/usr\/include\/.*?:\d+:\d+:\s*/, /g;
$output =~ s/\s*collect2: error: ld returned 1 exit status//g;
$output =~ s/In function\s*`main':\s*\/home\/compiler\/ undefined reference to/error: undefined reference to/g;
$output =~ s/\/home\/compiler\///g;
$output =~ s/compilation terminated.//;
$output =~ s/<'(.*)' = char>/<'$1' = int>/g;
$output =~ s/= (-?\d+) ''/= $1/g;
$output =~ s/, <incomplete sequence >//g;
$output =~ s/\s*error: expected ';' before 'return'//g;
$output =~ s/^\s+//;
$output =~ s/\s+$//;
$output =~ s/error: ISO C forbids nested functions\s+//g;
$output =~ s/\s*note: this is the location of the previous definition//g;
$output =~ s/\s*note: use option -std=c99 or -std=gnu99 to compile your code//g;
$output =~ s/\s*\(declared at .*?\)//g;
$output =~ s/, note: declared here//g;
$output =~ s#/usr/include/.*?.h:\d+:\d+:/##g;
$output =~ s/\s*error: storage size of.*?isn't known\s*//g;
$output =~ s/; did you mean '.*?'\?//g;
# don't error about undeclared objects
$output =~ s/error: '[^']+' undeclared\s*//g;
if (length $output) {
print "$output\n";
exit 0;
} else {
$output = undef;
}
}
$code =~ s/^\Q$prelude_base\E\s*//;
@ -405,55 +391,49 @@ close $fh;
$output = `./c2eng.pl code2eng.c` if not defined $output;
if (not $has_function and not $has_main) {
$output =~ s/Let .main. be a function taking no arguments and returning int.\s*When called, the function will.\s*(do nothing.)?//i;
$output =~ s/\s*Return 0.\s*End of function .main..\s*//;
$output =~ s/\s*Finally, return 0.$//;
$output =~ s/\s*and then return 0.$/./;
$output =~ s/\s*Do nothing.\s*$//;
$output =~ s/^\s*(.)/\U$1/;
$output =~ s/\.\s+(\S)/. \U$1/g;
$output =~ s/Let .main. be a function taking no arguments and returning int.\s*When called, the function will.\s*(do nothing.)?//i;
$output =~ s/\s*Return 0.\s*End of function .main..\s*//;
$output =~ s/\s*Finally, return 0.$//;
$output =~ s/\s*and then return 0.$/./;
$output =~ s/\s*Do nothing.\s*$//;
$output =~ s/^\s*(.)/\U$1/;
$output =~ s/\.\s+(\S)/. \U$1/g;
} elsif ($has_function and not $has_main) {
$output =~ s/\s*Let `main` be a function taking no arguments and returning int.\s*When called, the function will do nothing.//;
$output =~ s/\s*Finally, return 0.$//;
$output =~ s/\s*and then return 0.$/./;
$output =~ s/\s*Let `main` be a function taking no arguments and returning int.\s*When called, the function will do nothing.//;
$output =~ s/\s*Finally, return 0.$//;
$output =~ s/\s*and then return 0.$/./;
}
$output =~ s/\s+/ /;
if (not $output) {
$output = "Does not compute; I only understand valid C11 code.\n";
}
if (not $output) { $output = "Does not compute; I only understand valid C11 code.\n"; }
print "$output\n";
sub execute {
my $timeout = shift @_;
my ($cmdline) = @_;
my $timeout = shift @_;
my ($cmdline) = @_;
my ($ret, $result);
my ($ret, $result);
($ret, $result) = eval {
my $result = '';
($ret, $result) = eval {
my $result = '';
my $pid = open(my $fh, '-|', "$cmdline 2>&1");
my $pid = open(my $fh, '-|', "$cmdline 2>&1");
local $SIG{ALRM} = sub { kill 'TERM', $pid; die "$result [Timed-out]\n"; };
alarm($timeout);
local $SIG{ALRM} = sub { kill 'TERM', $pid; die "$result [Timed-out]\n"; };
alarm($timeout);
while (my $line = <$fh>) {
$result .= $line;
}
while (my $line = <$fh>) { $result .= $line; }
close $fh;
my $ret = $? >> 8;
alarm 0;
return ($ret, $result);
};
close $fh;
my $ret = $? >> 8;
alarm 0;
if ($@ =~ /Timed-out/) { return (-1, $@); }
return ($ret, $result);
};
alarm 0;
if ($@ =~ /Timed-out/) {
return (-1, $@);
}
return ($ret, $result);
}

253
modules/c99std.pl vendored
View File

@ -16,57 +16,55 @@ my $RESULTS_SPECIFIED = 2;
my $search = join ' ', @ARGV;
if (not length $search) {
print "Usage: c99std [-list] [-n#] [-section <section>] [search text] -- 'section' must be in the form of X.YpZ where X and Y are section/chapter and, optionally, pZ is paragraph. If both 'section' and 'search text' are specified, then the search space will be within the specified section. You may use -n # to skip to the #th match. To list only the section numbers containing 'search text', add -list.\n";
exit 0;
print
"Usage: c99std [-list] [-n#] [-section <section>] [search text] -- 'section' must be in the form of X.YpZ where X and Y are section/chapter and, optionally, pZ is paragraph. If both 'section' and 'search text' are specified, then the search space will be within the specified section. You may use -n # to skip to the #th match. To list only the section numbers containing 'search text', add -list.\n";
exit 0;
}
my ($section, $paragraph, $section_specified, $paragraph_specified, $match, $list_only, $list_titles);
$section_specified = 0;
$section_specified = 0;
$paragraph_specified = 0;
if ($search =~ s/-section\s*([A-Z0-9\.p]+)//i or $search =~ s/\b([A-Z0-9]+\.[0-9\.p]+)//i) {
$section = $1;
$section = $1;
if ($section =~ s/p(\d+)//i) {
$paragraph = $1;
$paragraph_specified = $USER_SPECIFIED;
} else {
$paragraph = 1;
}
if ($section =~ s/p(\d+)//i) {
$paragraph = $1;
$paragraph_specified = $USER_SPECIFIED;
} else {
$paragraph = 1;
}
$section = "$section." if $section =~ m/^[A-Z0-9]+$/i;
$section = "$section." if $section =~ m/^[A-Z0-9]+$/i;
$section_specified = 1;
$section_specified = 1;
}
if ($search =~ s/-n\s*(\d+)//) {
$match = $1;
} else {
$match = 1;
}
if ($search =~ s/-n\s*(\d+)//) { $match = $1; }
else { $match = 1; }
if ($search =~ s/-list//i) {
$list_only = 1;
$list_titles = 1; # Added here instead of removing -titles option
$list_only = 1;
$list_titles = 1; # Added here instead of removing -titles option
}
if ($search =~ s/-titles//i) {
$list_only = 1;
$list_titles = 1;
$list_only = 1;
$list_titles = 1;
}
$search =~ s/^\s+//;
$search =~ s/\s+$//;
if (not defined $section) {
$section = "1.";
$paragraph = 1;
$section = "1.";
$paragraph = 1;
}
if ($list_only and not length $search) {
print "You must specify some search text to use with -list.\n";
exit 0;
print "You must specify some search text to use with -list.\n";
exit 0;
}
open FH, "<n1256.out" or die "Could not open n1256: $!";
@ -77,151 +75,144 @@ my $text = join '', @contents;
$text =~ s/\r//g;
my $result;
my $found_section = "";
my $found_section = "";
my $found_section_title = "";
my $section_title;
my $found_paragraph;
my $found = 0;
my $found = 0;
my $matches = 0;
my $this_section;
my $comma = "";
if ($list_only) {
$result = "Sections containing '$search':\n ";
}
if ($list_only) { $result = "Sections containing '$search':\n "; }
my $qsearch = quotemeta $search;
$qsearch =~ s/\\ / /g;
$qsearch =~ s/\s+/\\s+/g;
while ($text =~ m/^\s{0,4}([0-9A-Z]+\.[0-9\.]*)/msg) {
$this_section = $1;
$this_section = $1;
print "----------------------------------\n" if $debug >= 2;
print "Processing section [$this_section]\n" if $debug;
print "----------------------------------\n" if $debug >= 2;
print "Processing section [$this_section]\n" if $debug;
if ($section_specified and $this_section !~ m/^$section/i) {
print "No section match, skipping.\n" if $debug >= 4;
next;
}
my $section_text;
if ($text =~ m/(.*?)^(?=\s{0,4}(?!FOOTNOTE)[0-9A-Z]+\.)/msg) {
$section_text = $1;
} else {
print "No section text, end of file marker found.\n" if $debug >= 4;
last;
}
if ($section =~ /FOOTNOTE/i) {
$section_text =~ s/^\s{4}//ms;
$section_text =~ s/^\s{4}FOOTNOTE.*//msi;
$section_text =~ s/^\d.*//ms;
} elsif ($section_text =~ m/(.*?)$/msg) {
$section_title = $1 if length $1;
$section_title =~ s/^\s+//;
$section_title =~ s/\s+$//;
}
print "$this_section [$section_title]\n" if $debug >= 2;
while ($section_text =~ m/^(\d+)\s(.*?)^(?=\d)/msgic or $section_text =~ m/^(\d+)\s(.*)/msgi) {
my $p = $1 ;
my $t = $2;
print "paragraph $p: [$t]\n" if $debug >= 3;
if ($paragraph_specified == $USER_SPECIFIED and not length $search and $p == $paragraph) {
$result = $t if not $found;
$found_paragraph = $p;
$found_section = $this_section;
$found_section_title = $section_title;
$found = 1;
last;
if ($section_specified and $this_section !~ m/^$section/i) {
print "No section match, skipping.\n" if $debug >= 4;
next;
}
if (length $search) {
eval {
if ($t =~ m/\b$qsearch\b/mis or $section_title =~ m/\b$qsearch\b/mis) {
$matches++;
if ($matches >= $match) {
if ($list_only) {
$result .= sprintf("%s%-15s", $comma, $this_section."p".$p);
$result .= " $section_title" if $list_titles;
$comma = ",\n ";
} else {
if (not $found) {
$result = $t;
$found_section = $this_section;
$found_section_title = $section_title;
$found_paragraph = $p;
$paragraph_specified = $RESULTS_SPECIFIED;
}
$found = 1;
}
}
my $section_text;
if ($text =~ m/(.*?)^(?=\s{0,4}(?!FOOTNOTE)[0-9A-Z]+\.)/msg) { $section_text = $1; }
else {
print "No section text, end of file marker found.\n" if $debug >= 4;
last;
}
if ($section =~ /FOOTNOTE/i) {
$section_text =~ s/^\s{4}//ms;
$section_text =~ s/^\s{4}FOOTNOTE.*//msi;
$section_text =~ s/^\d.*//ms;
} elsif ($section_text =~ m/(.*?)$/msg) {
$section_title = $1 if length $1;
$section_title =~ s/^\s+//;
$section_title =~ s/\s+$//;
}
print "$this_section [$section_title]\n" if $debug >= 2;
while ($section_text =~ m/^(\d+)\s(.*?)^(?=\d)/msgic or $section_text =~ m/^(\d+)\s(.*)/msgi) {
my $p = $1;
my $t = $2;
print "paragraph $p: [$t]\n" if $debug >= 3;
if ($paragraph_specified == $USER_SPECIFIED and not length $search and $p == $paragraph) {
$result = $t if not $found;
$found_paragraph = $p;
$found_section = $this_section;
$found_section_title = $section_title;
$found = 1;
last;
}
};
if ($@) {
print "Error in search regex; you may need to escape characters such as *, ?, ., etc.\n";
if (length $search) {
eval {
if ($t =~ m/\b$qsearch\b/mis or $section_title =~ m/\b$qsearch\b/mis) {
$matches++;
if ($matches >= $match) {
if ($list_only) {
$result .= sprintf("%s%-15s", $comma, $this_section . "p" . $p);
$result .= " $section_title" if $list_titles;
$comma = ",\n ";
} else {
if (not $found) {
$result = $t;
$found_section = $this_section;
$found_section_title = $section_title;
$found_paragraph = $p;
$paragraph_specified = $RESULTS_SPECIFIED;
}
$found = 1;
}
}
}
};
if ($@) {
print "Error in search regex; you may need to escape characters such as *, ?, ., etc.\n";
exit 0;
}
}
}
last if $found && $paragraph_specified == $USER_SPECIFIED;
if ($paragraph_specified == $USER_SPECIFIED) {
if (length $search) { print "No such text '$search' found within paragraph $paragraph of section $section of n1256.\n"; }
else { print "No such paragraph $paragraph in section $section of n1256.\n"; }
exit 0;
}
}
}
last if $found && $paragraph_specified == $USER_SPECIFIED;
if ($paragraph_specified == $USER_SPECIFIED) {
if (length $search) {
print "No such text '$search' found within paragraph $paragraph of section $section of n1256.\n";
} else {
print "No such paragraph $paragraph in section $section of n1256.\n";
if (defined $section_specified and not length $search) {
$found = 1;
$found_section = $this_section;
$found_section_title = $section_title;
$found_paragraph = $paragraph;
$result = $section_text;
last;
}
exit 0;
}
if (defined $section_specified and not length $search) {
$found = 1;
$found_section = $this_section;
$found_section_title = $section_title;
$found_paragraph = $paragraph;
$result = $section_text;
last;
}
}
if (not $found and $comma eq "") {
$search =~ s/\\s\+/ /g;
if ($section_specified) {
print "No such text '$search' found within section '$section' in C99 Draft Standard (n1256).\n" if length $search;
print "No such section '$section' in C99 Draft Standard (n1256).\n" if not length $search;
exit 0;
}
$search =~ s/\\s\+/ /g;
if ($section_specified) {
print "No such text '$search' found within section '$section' in C99 Draft Standard (n1256).\n" if length $search;
print "No such section '$section' in C99 Draft Standard (n1256).\n" if not length $search;
exit 0;
}
print "No such section '$section' in C99 Draft Standard (n1256).\n" if not length $search;
print "No such text '$search' found in C99 Draft Standard (n1256).\n" if length $search;
exit 0;
print "No such section '$section' in C99 Draft Standard (n1256).\n" if not length $search;
print "No such text '$search' found in C99 Draft Standard (n1256).\n" if length $search;
exit 0;
}
$result =~ s/$found_section_title// if length $found_section_title;
$result =~ s/^\s+//;
$result =~ s/\s+$//;
=cut
$result =~ s/\s+/ /g;
$result =~ s/[\n\r]/ /g;
=cut
if ($matches > 1 and not $list_only) {
print "Displaying $match of $matches matches: ";
}
if ($matches > 1 and not $list_only) { print "Displaying $match of $matches matches: "; }
if ($comma eq "") {
print "http://www.iso-9899.info/n1256.html\#$found_section";
print "p" . $found_paragraph if $paragraph_specified;
print "\n\n";
print "[", $found_section_title, "]\n\n" if length $found_section_title;
print "http://www.iso-9899.info/n1256.html\#$found_section";
print "p" . $found_paragraph if $paragraph_specified;
print "\n\n";
print "[", $found_section_title, "]\n\n" if length $found_section_title;
}
$result =~ s/\s*Constraints\s*$//;

9
modules/cdecl.pl vendored
View File

@ -8,10 +8,10 @@
my $command = join(' ', @ARGV);
my @args = split(' ', $command); # because @ARGV may be one quoted argument
my @args = split(' ', $command); # because @ARGV may be one quoted argument
if (@args < 2) {
print "Usage: cdecl <explain|declare|cast|set|...> <code>, see http://linux.die.net/man/1/cdecl (Don't use this command. Use `english` instead.)\n";
die;
print "Usage: cdecl <explain|declare|cast|set|...> <code>, see http://linux.die.net/man/1/cdecl (Don't use this command. Use `english` instead.)\n";
die;
}
$command = quotemeta($command);
@ -23,5 +23,6 @@ chomp $result;
$result =~ s/\n/, /g;
print $result;
print " (Don't use this command. It can only handle C90 declarations -- poorly. Use `english` instead, which can translate any complete C11 code.)" if $result =~ m/^declare/;
print " (Don't use this command. It can only handle C90 declarations -- poorly. Use `english` instead, which can translate any complete C11 code.)"
if $result =~ m/^declare/;
print "\n";

82
modules/cfaq.pl vendored
View File

@ -4,9 +4,9 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
my $match = 1;
my $match = 1;
my $matches = 0;
my $found = 0;
my $found = 0;
print "Usage: faq [match #] <search regex>\n" and exit 0 if not defined $ARGV[0];
@ -20,8 +20,8 @@ $query =~ s/\[/\\[/g;
$query =~ s/\]/\\]/g;
if ($query =~ /^(\d+)\.\*\?/) {
$match = $1;
$query =~ s/^\d+\.\*\?//;
$match = $1;
$query =~ s/^\d+\.\*\?//;
}
open(FILE, "< cfaq-questions.html") or print "Can't open cfaq-questions.html: $!" and exit 1;
@ -31,51 +31,51 @@ close(FILE);
my ($heading, $question_full, $question_link, $question_number, $question_text, $result);
foreach my $line (@contents) {
if ($line =~ m/^<H4>(.*?)<\/H4>/) {
$heading = $1;
next;
}
if ($line =~ m/<p><a href="(.*?)" rel=subdocument>(.*?)<\/a>/) {
($question_link, $question_number) = ($1, $2);
if (defined $question_full) {
if ($question_full =~ m/$query/i) {
$matches++;
$found = 1;
if ($match == $matches) {
$question_text =~ s/\s+/ /g;
$result = $question_text;
}
}
if ($line =~ m/^<H4>(.*?)<\/H4>/) {
$heading = $1;
next;
}
$question_full = "$question_number $question_link ";
$question_text = "http://c-faq.com/$question_link - $heading, $question_number: ";
next;
}
if ($line =~ m/<p><a href="(.*?)" rel=subdocument>(.*?)<\/a>/) {
($question_link, $question_number) = ($1, $2);
if (defined $question_full) {
$line =~ s/[\n\r]/ /g;
$line =~ s/(<pre>|<\/pre>|<TT>|<\/TT>|<\/a>|<br>)//g;
$line =~ s/<a href=".*?">//g;
$line =~ s/&nbsp;/ /g;
$line =~ s/&amp;/&/g;
$line =~ s/&lt;/</g;
$line =~ s/&gt;/>/g;
if (defined $question_full) {
if ($question_full =~ m/$query/i) {
$matches++;
$found = 1;
if ($match == $matches) {
$question_text =~ s/\s+/ /g;
$result = $question_text;
}
}
}
$question_full .= $line;
$question_text .= $line;
}
$question_full = "$question_number $question_link ";
$question_text = "http://c-faq.com/$question_link - $heading, $question_number: ";
next;
}
if (defined $question_full) {
$line =~ s/[\n\r]/ /g;
$line =~ s/(<pre>|<\/pre>|<TT>|<\/TT>|<\/a>|<br>)//g;
$line =~ s/<a href=".*?">//g;
$line =~ s/&nbsp;/ /g;
$line =~ s/&amp;/&/g;
$line =~ s/&lt;/</g;
$line =~ s/&gt;/>/g;
$question_full .= $line;
$question_text .= $line;
}
}
if ($found == 1) {
print "But there are $matches results...\n" and exit if ($match > $matches);
print "But there are $matches results...\n" and exit if ($match > $matches);
print "$matches results, displaying #$match: " if ($matches > 1);
print "$matches results, displaying #$match: " if ($matches > 1);
print "$result\n";
print "$result\n";
} else {
$query =~ s/\.\*\?/ /g;
print "No FAQs match $query\n";
$query =~ s/\.\*\?/ /g;
print "No FAQs match $query\n";
}

117
modules/codepad.pl vendored
View File

@ -17,13 +17,14 @@ use Text::Balanced qw(extract_codeblock);
my @languages = qw/C C++ D Haskell Lua OCaml PHP Perl Python Ruby Scheme Tcl/;
my %preludes = ( 'C' => "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n",
'C++' => "#include <iostream>\n#include <cstdio>\n",
);
my %preludes = (
'C' => "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n",
'C++' => "#include <iostream>\n#include <cstdio>\n",
);
if ($#ARGV <= 0) {
print "Usage: cc [-lang=<language>] <code>\n";
exit 0;
print "Usage: cc [-lang=<language>] <code>\n";
exit 0;
}
my $nick = shift @ARGV;
@ -42,22 +43,22 @@ $show_url = 1 if $code =~ s/-showurl//i;
my $found = 0;
foreach my $l (@languages) {
if (uc $lang eq uc $l) {
$lang = $l;
$found = 1;
last;
}
if (uc $lang eq uc $l) {
$lang = $l;
$found = 1;
last;
}
}
if (not $found) {
print "$nick: Invalid language '$lang'. Supported languages are: @languages\n";
exit 0;
print "$nick: Invalid language '$lang'. Supported languages are: @languages\n";
exit 0;
}
my $ua = LWP::UserAgent->new();
$ua->agent("Mozilla/5.0");
push @{ $ua->requests_redirectable }, 'POST';
push @{$ua->requests_redirectable}, 'POST';
$code =~ s/#include <([^>]+)>/\n#include <$1>\n/g;
$code =~ s/#([^ ]+) (.*?)\\n/\n#$1 $2\n/g;
@ -67,65 +68,60 @@ my $precode = $preludes{$lang} . $code;
$code = '';
if ($lang eq "C" or $lang eq "C++") {
my $has_main = 0;
my $has_main = 0;
my $prelude = '';
$prelude = "$1$2" if $precode =~ s/^\s*(#.*)(#.*?[>\n])//s;
my $prelude = '';
$prelude = "$1$2" if $precode =~ s/^\s*(#.*)(#.*?[>\n])//s;
while ($precode =~ s/([ a-zA-Z0-9_*\[\]]+)\s+([a-zA-Z0-9_*]+)\s*\((.*?)\)\s*({.*)//) {
my ($ret, $ident, $params, $potential_body) = ($1, $2, $3, $4);
while ($precode =~ s/([ a-zA-Z0-9_*\[\]]+)\s+([a-zA-Z0-9_*]+)\s*\((.*?)\)\s*({.*)//) {
my ($ret, $ident, $params, $potential_body) = ($1, $2, $3, $4);
my @extract = extract_codeblock($potential_body, '{}');
my $body;
if (not defined $extract[0]) {
$output .= "<pre>error: unmatched brackets for function '$ident'; </pre>";
$body = $extract[1];
} else {
$body = $extract[0];
$precode .= $extract[1];
my @extract = extract_codeblock($potential_body, '{}');
my $body;
if (not defined $extract[0]) {
$output .= "<pre>error: unmatched brackets for function '$ident'; </pre>";
$body = $extract[1];
} else {
$body = $extract[0];
$precode .= $extract[1];
}
$code .= "$ret $ident($params) $body\n\n";
$has_main = 1 if $ident eq 'main';
}
$code .= "$ret $ident($params) $body\n\n";
$has_main = 1 if $ident eq 'main';
}
$precode =~ s/^\s+//;
$precode =~ s/\s+$//;
$precode =~ s/^\s+//;
$precode =~ s/\s+$//;
if (not $has_main) {
$code = "$prelude\n\n$code\n\nint main(int argc, char **argv) { $precode\n;\n return 0;}\n";
} else {
$code = "$prelude\n\n$precode\n\n$code\n";
}
if (not $has_main) { $code = "$prelude\n\n$code\n\nint main(int argc, char **argv) { $precode\n;\n return 0;}\n"; }
else { $code = "$prelude\n\n$precode\n\n$code\n"; }
} else {
$code = $precode;
$code = $precode;
}
if ($lang eq "C" or $lang eq "C++") {
# $code = pretty($code);
# $code = pretty($code);
}
$code =~ s/^\s+//;
$code =~ s/\s+$//;
my %post = ( 'lang' => $lang, 'code' => $code, 'private' => 'True', 'run' => 'True', 'submit' => 'Submit' );
my %post = ('lang' => $lang, 'code' => $code, 'private' => 'True', 'run' => 'True', 'submit' => 'Submit');
my $response = $ua->post("http://codepad.org", \%post);
if (not $response->is_success) {
print "There was an error compiling the code.\n";
die $response->status_line;
print "There was an error compiling the code.\n";
die $response->status_line;
}
my $text = $response->decoded_content;
my $url = $response->request->uri;
my $url = $response->request->uri;
# remove line numbers
$text =~ s/<a style="" name="output-line-\d+">\d+<\/a>//g;
if ($text =~ /<span class="heading">Output:<\/span>.+?<div class="code">(.*)<\/div>.+?<\/table>/si) {
$output .= "$1";
} else {
$output .= "<pre>No output.</pre>";
}
if ($text =~ /<span class="heading">Output:<\/span>.+?<div class="code">(.*)<\/div>.+?<\/table>/si) { $output .= "$1"; }
else { $output .= "<pre>No output.</pre>"; }
$output = decode_entities($output);
$output = HTML::FormatText->new->format(parse_html($output));
@ -141,24 +137,19 @@ print FILE localtime() . "\n";
print FILE "$nick: [ $url ] $output\n\n";
close FILE;
if ($show_url) {
print "$nick: [ $url ] $output\n";
} else {
print "$nick: $output\n";
}
if ($show_url) { print "$nick: [ $url ] $output\n"; }
else { print "$nick: $output\n"; }
sub pretty {
my $code = join '', @_;
my $result;
my $code = join '', @_;
my $result;
my $pid = open2(\*IN, \*OUT, 'astyle -Upf');
print OUT "$code\n";
close OUT;
while (my $line = <IN>) {
$result .= $line;
}
close IN;
waitpid($pid, 0);
return $result;
my $pid = open2(\*IN, \*OUT, 'astyle -Upf');
print OUT "$code\n";
close OUT;
while (my $line = <IN>) { $result .= $line; }
close IN;
waitpid($pid, 0);
return $result;
}

View File

@ -16,32 +16,29 @@ use IO::Socket::INET;
use JSON;
my $sock = IO::Socket::INET->new(
PeerAddr => '192.168.0.42',
PeerPort => 9000,
Proto => 'tcp');
PeerAddr => '192.168.0.42',
PeerPort => 9000,
Proto => 'tcp'
);
if (not defined $sock) {
print "Fatal error compiling: $!; try again later\n";
die $!;
print "Fatal error compiling: $!; try again later\n";
die $!;
}
my $json = join ' ', @ARGV;
my $h = decode_json $json;
my $h = decode_json $json;
$h->{code} =~ s/\s*}\s*$//;
my $lang = $h->{lang} // "c11";
if ($code =~ s/-lang=([^ ]+)//) {
$lang = lc $1;
}
if ($code =~ s/-lang=([^ ]+)//) { $lang = lc $1; }
$h->{lang} = $lang;
$json = encode_json $h;
print $sock "$json\n";
while (my $line = <$sock>) {
print "$line";
}
while (my $line = <$sock>) { print "$line"; }
close $sock;

View File

@ -16,30 +16,27 @@ use IO::Socket;
use JSON;
my $sock = IO::Socket::INET->new(
PeerAddr => '127.0.0.1',
PeerPort => 9000,
Proto => 'tcp');
PeerAddr => '127.0.0.1',
PeerPort => 9000,
Proto => 'tcp'
);
if(not defined $sock) {
print "Fatal error compiling: $!; try again later\n";
die $!;
if (not defined $sock) {
print "Fatal error compiling: $!; try again later\n";
die $!;
}
my $json = join ' ', @ARGV;
my $h = decode_json $json;
my $h = decode_json $json;
my $lang = $h->{lang} // "c11";
if ($h->{code} =~ s/-lang=([^ ]+)//) {
$lang = lc $1;
}
if ($h->{code} =~ s/-lang=([^ ]+)//) { $lang = lc $1; }
$h->{lang} = $lang;
$json = encode_json $h;
print $sock "$json\n";
while(my $line = <$sock>) {
print "$line";
}
while (my $line = <$sock>) { print "$line"; }
close $sock;

132
modules/define.pl vendored
View File

@ -10,73 +10,63 @@ use LWP::Simple;
my ($defint, $phrase, $text, $entry, $entries, $i);
if ($#ARGV < 0)
{
print "What phrase would you like to define?\n";
die;
if ($#ARGV < 0) {
print "What phrase would you like to define?\n";
die;
}
$phrase = join("%20", @ARGV);
$entry = 1;
if ($phrase =~ m/([0-9]+)%20(.*)/)
{
$entry = $1;
$phrase = $2;
if ($phrase =~ m/([0-9]+)%20(.*)/) {
$entry = $1;
$phrase = $2;
}
$text = get("http://dictionary.reference.com/browse/$phrase");
$phrase =~ s/\%20/ /g;
if ($text =~ m/no dictionary results/i)
{
print "No entry found for '$phrase'. ";
if ($text =~ m/no dictionary results/i) {
print "No entry found for '$phrase'. ";
if ($text =~ m/Did you mean <a class.*?>(.*?)<\/a>/g) {
print "Did you mean '$1'? Alternate suggestions: ";
if ($text =~ m/Did you mean <a class.*?>(.*?)<\/a>/g)
{
print "Did you mean '$1'? Alternate suggestions: ";
$i = 90;
$comma = "";
while ($text =~ m/<div id="spellSuggestWrapper"><li .*?><a href=.*?>(.*?)<\/a>/g && $i > 0)
{
print "$comma$1";
$i--;
$comma = ", ";
$i = 90;
$comma = "";
while ($text =~ m/<div id="spellSuggestWrapper"><li .*?><a href=.*?>(.*?)<\/a>/g && $i > 0) {
print "$comma$1";
$i--;
$comma = ", ";
}
}
}
# if ($text =~ m/Encyclopedia suggestions:/g)
# {
# print "Suggestions: ";
#
# $i = 30;
# while ($text =~ m/<a href=".*?\/search\?r=13&amp;q=.*?>(.*?)<\/a>/g
# && $i > 0)
# {
# print "$1, ";
# $i--;
# }
# }
# if ($text =~ m/Encyclopedia suggestions:/g)
# {
# print "Suggestions: ";
#
# $i = 30;
# while ($text =~ m/<a href=".*?\/search\?r=13&amp;q=.*?>(.*?)<\/a>/g
# && $i > 0)
# {
# print "$1, ";
# $i--;
# }
# }
print "\n";
exit 0;
print "\n";
exit 0;
}
if ($text =~ m/- (.*?) dictionary result/g)
{
$entries = $1;
}
if ($text =~ m/- (.*?) dictionary result/g) { $entries = $1; }
$entries = 1 if (not defined $entries);
if ($entry > $entries)
{
print "No entry found for $phrase.\n";
exit 0;
if ($entry > $entries) {
print "No entry found for $phrase.\n";
exit 0;
}
print "$phrase: ";
@ -85,40 +75,36 @@ $i = $entry;
$defint = "";
my $quote = chr(226) . chr(128) . chr(156);
my $quote = chr(226) . chr(128) . chr(156);
my $quote2 = chr(226) . chr(128) . chr(157);
my $dash = chr(226) . chr(128) . chr(147);
my $dash = chr(226) . chr(128) . chr(147);
while ($i <= $entries)
{
if ($text =~ m/<td>(.*?)<\/td>/gs)
{
$defint = $1;
}
while ($i <= $entries) {
if ($text =~ m/<td>(.*?)<\/td>/gs) { $defint = $1; }
# and now for some fugly beautifying regexps...
# and now for some fugly beautifying regexps...
$defint =~ s/$quote/"/g;
$defint =~ s/$quote2/"/g;
$defint =~ s/$dash/-/g;
$defint =~ s/<b>Pronun.*?<BR>//gsi;
$defint =~ s/<.*?>//gsi;
$defint =~ s/\&nbsp\;/ /gi;
$defint =~ s/\&.*?\;//g;
$defint =~ s/\r\n//gs;
$defint =~ s/\( P \)//gs;
$defint =~ s/\s+/ /gs;
$defint =~ s/$quote/"/g;
$defint =~ s/$quote2/"/g;
$defint =~ s/$dash/-/g;
$defint =~ s/<b>Pronun.*?<BR>//gsi;
$defint =~ s/<.*?>//gsi;
$defint =~ s/\&nbsp\;/ /gi;
$defint =~ s/\&.*?\;//g;
$defint =~ s/\r\n//gs;
$defint =~ s/\( P \)//gs;
$defint =~ s/\s+/ /gs;
if ($defint =~ /interfaceflash/) {
$i++;
next;
}
$i++ and next if $defint eq " ";
print "$i) $defint ";
if ($defint =~ /interfaceflash/) {
$i++;
next;
}
$i++ and next if $defint eq " ";
print "$i) $defint ";
$i++;
}
print "\n";

37
modules/dice_roll.pl vendored
View File

@ -10,36 +10,31 @@ use Games::Dice qw/roll roll_array/;
my ($result, $rolls, $show);
if ($#ARGV <0)
{
print "Usage: roll [-show] <dice roll>; e.g.: roll 3d6+1. To see all individual dice rolls, add -show.\n";
die;
if ($#ARGV < 0) {
print "Usage: roll [-show] <dice roll>; e.g.: roll 3d6+1. To see all individual dice rolls, add -show.\n";
die;
}
$rolls = join("", @ARGV);
if ($rolls =~ s/\s*-show\s*//) {
$show = 1;
}
if ($rolls =~ s/\s*-show\s*//) { $show = 1; }
if ($rolls =~ m/^\s*(\d+)d\d+(?:\+?-?\d+)?\s*$/) {
if ($1 > 100) {
print "Sorry, maximum of 100 rolls.\n";
die;
}
if ($1 > 100) {
print "Sorry, maximum of 100 rolls.\n";
die;
}
} else {
print "Usage: roll [-show] <dice roll>; e.g.: roll 3d6+1. To see all individual dice rolls, add -show.\n";
die;
print "Usage: roll [-show] <dice roll>; e.g.: roll 3d6+1. To see all individual dice rolls, add -show.\n";
die;
}
if ($show) {
my @results = roll_array $rolls;
$result = 0;
foreach my $n (@results) {
$result += $n;
}
print "/me rolled $rolls for @results totaling $result.\n";
my @results = roll_array $rolls;
$result = 0;
foreach my $n (@results) { $result += $n; }
print "/me rolled $rolls for @results totaling $result.\n";
} else {
$result = roll $rolls;
print "/me rolled $rolls for $result.\n";
$result = roll $rolls;
print "/me rolled $rolls for $result.\n";
}

316
modules/dict.org.pl vendored
View File

@ -4,6 +4,7 @@
eval 'exec /usr/bin/perl -w -S $0 ${1+"$@"}'
if 0; # not running under some shell
=cut
#
# dict - perl DICT client (for accessing network dictionary servers)
#
@ -26,9 +27,9 @@ $VERSION = sprintf("%d.%d", q$Revision: 1.2 $ =~ /(\d+)\.(\d+)/);
#-----------------------------------------------------------------------
# Global variables
#-----------------------------------------------------------------------
my $PROGRAM; # The name we're running as, minus path
my $config; # Config object (AppConfig::Std)
my $dict; # Dictionary object (Net::Dict)
my $PROGRAM; # The name we're running as, minus path
my $config; # Config object (AppConfig::Std)
my $dict; # Dictionary object (Net::Dict)
initialise();
@ -43,17 +44,14 @@ list_databases() if $config->dbs;
list_strategies() if $config->strats;
=cut
if ($config->database) {
$dict->setDicts($config->database);
} else {
$dict->setDicts('wn');
}
if ($config->database) { $dict->setDicts($config->database); }
else { $dict->setDicts('wn'); }
#-----------------------------------------------------------------------
# Perform define or match, if a word or pattern was given
#-----------------------------------------------------------------------
if (@ARGV > 0)
{
if (@ARGV > 0) {
=cut
if ($config->match)
{
@ -62,13 +60,17 @@ if (@ARGV > 0)
else
{
=cut
define_word(join ' ', @ARGV);
define_word(join ' ', @ARGV);
=cut
}
=cut
} else {
print "Usage: dict [-d database] [-n start from definition number] [-t abbreviation of word class type (n]oun, v]erb, adv]erb, adj]ective, etc)] [-search <regex> for definitions matching <regex>] <word>\n";
exit 0;
print
"Usage: dict [-d database] [-n start from definition number] [-t abbreviation of word class type (n]oun, v]erb, adv]erb, adj]ective, etc)] [-search <regex> for definitions matching <regex>] <word>\n";
exit 0;
}
exit 0;
@ -80,116 +82,100 @@ exit 0;
# Look up definition(s) for the specified word.
#
#=======================================================================
sub define_word
{
my $word = shift;
my $eref;
my $entry;
my ($db, $def);
sub define_word {
my $word = shift;
my $eref;
my $entry;
my ($db, $def);
$eref = $dict->define($word);
$eref = $dict->define($word);
if (@$eref == 0) { _no_definitions($word); }
else {
foreach $entry (@$eref) {
($db, $def) = @$entry;
if (@$eref == 0)
{
_no_definitions($word);
}
else
{
foreach $entry (@$eref)
{
($db, $def) = @$entry;
my $defs = dict_hash($def);
print "$defs->{word}: ";
my $defs = dict_hash($def);
print "$defs->{word}: ";
my $comma = '';
my $def_type = $config->def_type;
my $def_contains = $config->def_contains;
my $comma = '';
my $def_type = $config->def_type;
my $def_contains = $config->def_contains;
# normalize '*' to '.*'
$def_type =~ s/\.\*/*/g;
$def_type =~ s/\*/.*/g;
# normalize '*' to '.*'
$def_type =~ s/\.\*/*/g;
$def_type =~ s/\*/.*/g;
# normalize '*' to '.*'
$def_contains =~ s/\.\*/*/g;
$def_contains =~ s/\*/.*/g;
# normalize '*' to '.*'
$def_contains =~ s/\.\*/*/g;
$def_contains =~ s/\*/.*/g;
my $defined = 0;
my $defined = 0;
eval {
foreach my $type (keys %$defs) {
next if $type eq 'word';
next unless $type =~ m/$def_type/i;
print "$comma$type: " if length $type;
foreach my $number (sort { $a <=> $b } keys %{$defs->{$type}}) {
next unless $number >= $config->def_number;
next unless $defs->{$type}{$number} =~ m/$def_contains/i;
print "$comma" unless $number == 1;
print "$number) $defs->{$type}{$number}";
$comma = ",\n\n";
$defined = 1;
}
}
};
eval {
foreach my $type (keys %$defs) {
next if $type eq 'word';
next unless $type =~ m/$def_type/i;
print "$comma$type: " if length $type;
foreach my $number (sort { $a <=> $b } keys %{ $defs->{$type} }) {
next unless $number >= $config->def_number;
next unless $defs->{$type}{$number} =~ m/$def_contains/i;
print "$comma" unless $number == 1;
print "$number) $defs->{$type}{$number}";
$comma = ",\n\n";
$defined = 1;
}
if ($@) {
print "Error in -t parameter. Use v, n, *, etc.\n";
exit 0;
}
if (not $defined && $def_type ne '*') {
my $types = '';
$comma = '';
foreach my $type (sort keys %$defs) {
next if $type eq 'word';
$types .= "$comma$type";
$comma = ', ';
}
if (length $types) { print "no `$def_type` definition found; available definitions: $types.\n"; }
else { print "no definition found.\n"; }
} elsif (not $defined) {
print "no definition found.\n";
}
}
};
if ($@) {
print "Error in -t parameter. Use v, n, *, etc.\n";
exit 0;
}
if (not $defined && $def_type ne '*') {
my $types = '';
$comma = '';
foreach my $type (sort keys %$defs) {
next if $type eq 'word';
$types .= "$comma$type";
$comma = ', ';
}
if (length $types) {
print "no `$def_type` definition found; available definitions: $types.\n";
} else {
print "no definition found.\n";
}
} elsif (not $defined) {
print "no definition found.\n";
}
}
}
}
sub dict_hash {
my $def = shift;
my $defs = {};
my $def = shift;
my $defs = {};
$def =~ s/{([^}]+)}/$1/g;
$def =~ s/{([^}]+)}/$1/g;
my @lines = split /[\n\r]/, $def;
my @lines = split /[\n\r]/, $def;
$defs->{word} = shift @lines;
$defs->{word} = shift @lines;
my ($type, $number, $text) = ('', 1, '');
my ($type, $number, $text) = ('', 1, '');
foreach my $line (@lines) {
$line =~ s/^\s+//;
$line =~ s/\s+$//;
$line =~ s/\s+/ /g;
foreach my $line (@lines) {
$line =~ s/^\s+//;
$line =~ s/\s+$//;
$line =~ s/\s+/ /g;
if ($line =~ m/^([a-z]+) (\d+): (.*)/i) {
($type, $number, $text) = ($1, $2, $3);
}
elsif ($line =~ m/^(\d+): (.*)/i) {
($number, $text) = ($1, $2);
}
else {
$text = $line;
if ($line =~ m/^([a-z]+) (\d+): (.*)/i) { ($type, $number, $text) = ($1, $2, $3); }
elsif ($line =~ m/^(\d+): (.*)/i) { ($number, $text) = ($1, $2); }
else { $text = $line; }
$text = " $text" if exists $defs->{$type}{$number};
$defs->{$type}{$number} .= $text;
}
$text = " $text" if exists $defs->{$type}{$number};
$defs->{$type}{$number} .= $text;
}
return $defs;
return $defs;
}
#=======================================================================
@ -202,34 +188,25 @@ sub dict_hash {
# it, etc.
#
#=======================================================================
sub _no_definitions
{
sub _no_definitions {
my $word = shift;
my %strategies;
my %words;
my $strategy;
%strategies = $dict->strategies;
if (!exists($strategies{'lev'}) && !exists($strategies{'soundex'}))
{
if (!exists($strategies{'lev'}) && !exists($strategies{'soundex'})) {
print "no definition found for \"$word\"\n";
return;
}
$strategy = exists $strategies{'lev'} ? 'lev' : 'soundex';
foreach my $entry (@{ $dict->match($word, $strategy) })
{
$words{$entry->[1]}++;
}
if (keys %words == 0)
{
foreach my $entry (@{$dict->match($word, $strategy)}) { $words{$entry->[1]}++; }
if (keys %words == 0) {
print "no definition found for \"$word\", ",
"and no similar words found\n";
}
else
{
"and no similar words found\n";
} else {
print "no definition found for \"$word\" - perhaps you meant: ", join(', ', keys %words), "\n";
}
}
@ -242,28 +219,18 @@ sub _no_definitions
# with the -strategy switch.
#
#=======================================================================
sub match_word
{
sub match_word {
my $word = shift;
my $eref;
my $entry;
my ($db, $match);
unless ($config->strategy)
{
die "you must specify -strategy when using -match\n";
}
unless ($config->strategy) { die "you must specify -strategy when using -match\n"; }
$eref = $dict->match($word, $config->strategy);
if (@$eref == 0)
{
print "no matches for \"$word\"\n";
}
else
{
foreach $entry (@$eref)
{
if (@$eref == 0) { print "no matches for \"$word\"\n"; }
else {
foreach $entry (@$eref) {
($db, $match) = @$entry;
print "$db : $match\n";
}
@ -278,11 +245,9 @@ sub match_word
# DICT server.
#
#=======================================================================
sub list_databases
{
sub list_databases {
my %dbs = $dict->dbs();
tabulate_hash(\%dbs, 'Database', 'Description');
}
@ -294,11 +259,9 @@ sub list_databases
# by the DICT server.
#
#=======================================================================
sub list_strategies
{
sub list_strategies {
my %strats = $dict->strategies();
tabulate_hash(\%strats, 'Strategy', 'Description');
}
@ -314,14 +277,11 @@ sub list_strategies
# credits, etc.
#
#=======================================================================
sub show_db_info
{
sub show_db_info {
my $db = shift;
my %dbs = $dict->dbs();
if (not exists $dbs{$config->info})
{
if (not exists $dbs{$config->info}) {
print " dictionary \"$db\" not known\n";
return;
}
@ -336,8 +296,8 @@ sub show_db_info
# check config file and command-line
#
#=======================================================================
sub initialise
{
sub initialise {
#-------------------------------------------------------------------
# Initialise misc global variables
#-------------------------------------------------------------------
@ -346,16 +306,20 @@ sub initialise
#-------------------------------------------------------------------
# Create AppConfig::Std, define parameters, and parse command-line
#-------------------------------------------------------------------
$config = AppConfig::Std->new({ CASE => 1 })
|| die "failed to create AppConfig::Std: $!\n";
$config = AppConfig::Std->new({CASE => 1}) || die "failed to create AppConfig::Std: $!\n";
$config->define('host', { ARGCOUNT => 1, ALIAS => 'h' });
$config->define('port', { ARGCOUNT => 1, ALIAS => 'p',
DEFAULT => 2628 });
$config->define('database', { ARGCOUNT => 1, ALIAS => 'd' });
$config->define('def_number', { ARGCOUNT => 1, ALIAS => 'n', DEFAULT => 1 });
$config->define('def_type', { ARGCOUNT => 1, ALIAS => 't', DEFAULT => '*'});
$config->define('def_contains', { ARGCOUNT => 1, ALIAS => 'search', DEFAULT => '*'});
$config->define('host', {ARGCOUNT => 1, ALIAS => 'h'});
$config->define(
'port',
{
ARGCOUNT => 1, ALIAS => 'p',
DEFAULT => 2628
}
);
$config->define('database', {ARGCOUNT => 1, ALIAS => 'd'});
$config->define('def_number', {ARGCOUNT => 1, ALIAS => 'n', DEFAULT => 1});
$config->define('def_type', {ARGCOUNT => 1, ALIAS => 't', DEFAULT => '*'});
$config->define('def_contains', {ARGCOUNT => 1, ALIAS => 'search', DEFAULT => '*'});
=cut
$config->define('match', { ARGCOUNT => 0, ALIAS => 'm' });
@ -363,10 +327,15 @@ sub initialise
$config->define('strategy', { ARGCOUNT => 1, ALIAS => 's' });
$config->define('strats', { ARGCOUNT => 0, ALIAS => 'S' });
=cut
$config->define('client', { ARGCOUNT => 1, ALIAS => 'c',
DEFAULT => "$PROGRAM $VERSION ".
"[using Net::Dict $Net::Dict::VERSION]",
});
$config->define(
'client',
{
ARGCOUNT => 1, ALIAS => 'c',
DEFAULT => "$PROGRAM $VERSION " . "[using Net::Dict $Net::Dict::VERSION]",
}
);
=cut
$config->define('info', { ARGCOUNT => 1, ALIAS => 'i' });
$config->define('serverinfo', { ARGCOUNT => 0, ALIAS => 'I' });
@ -374,8 +343,9 @@ sub initialise
=cut
if (not $config->args(\@ARGV)) {
print "Usage: dict [-d database] [-n start from definition number] [-t abbreviation of word class type (n]oun, v]erb, adv]erb, adj]ective, etc)] [-search <regex> for definitions matching <regex>] <word>\n";
exit;
print
"Usage: dict [-d database] [-n start from definition number] [-t abbreviation of word class type (n]oun, v]erb, adv]erb, adj]ective, etc)] [-search <regex> for definitions matching <regex>] <word>\n";
exit;
}
#-------------------------------------------------------------------
@ -388,12 +358,12 @@ sub initialise
#-------------------------------------------------------------------
# Create connection to DICT server
#-------------------------------------------------------------------
$dict = Net::Dict->new($config->host,
Port => $config->port,
Client => $config->client,
Debug => $config->debug,
)
|| die "failed to create Net::Dict: $!\n";
$dict = Net::Dict->new(
$config->host,
Port => $config->port,
Client => $config->client,
Debug => $config->debug,
) || die "failed to create Net::Dict: $!\n";
}
#=======================================================================
@ -404,8 +374,8 @@ sub initialise
# of databases and strategies.
#
#=======================================================================
sub tabulate_hash
{
sub tabulate_hash {
my $hashref = shift;
my $keytitle = shift;
my $value_title = shift;
@ -413,29 +383,21 @@ sub tabulate_hash
my $width = length $keytitle;
my ($key, $value);
#-------------------------------------------------------------------
# Find the length of the longest key, so we can right align
# the column of keys
#-------------------------------------------------------------------
foreach $key (keys %$hashref)
{
$width = length($key) if length($key) > $width;
}
foreach $key (keys %$hashref) { $width = length($key) if length($key) > $width; }
#-------------------------------------------------------------------
# print out keys and values in a basic ascii formatted table view
#-------------------------------------------------------------------
printf(" %${width}s $value_title\n", $keytitle);
print ' ', '-' x $width, ' ', '-' x (length $value_title), "\n";
while (($key, $value) = each %$hashref)
{
printf(" %${width}s : $value\n", $key);
}
while (($key, $value) = each %$hashref) { printf(" %${width}s : $value\n", $key); }
print "\n";
}
__END__
=head1 NAME

View File

@ -15,14 +15,14 @@ use LWP::UserAgent;
my $debug = 0;
my $USE_LOCAL = defined $ENV{'CC_LOCAL'};
my $USE_LOCAL = defined $ENV{'CC_LOCAL'};
my $output = "";
my $output = "";
my $nooutput = 'No output.';
if ($#ARGV < 0) {
print "Usage: expand <code>\n";
exit 0;
print "Usage: expand <code>\n";
exit 0;
}
my $code = join ' ', @ARGV;
@ -35,55 +35,47 @@ print " code: [$code]\n" if $debug;
my $new_code = "";
use constant {
NORMAL => 0,
DOUBLE_QUOTED => 1,
SINGLE_QUOTED => 2,
NORMAL => 0,
DOUBLE_QUOTED => 1,
SINGLE_QUOTED => 2,
};
my $state = NORMAL;
my $state = NORMAL;
my $escaped = 0;
while ($code =~ m/(.)/gs) {
my $ch = $1;
my $ch = $1;
given ($ch) {
when ('\\') {
if ($escaped == 0) {
$escaped = 1;
next;
}
given ($ch) {
when ('\\') {
if ($escaped == 0) {
$escaped = 1;
next;
}
}
if ($state == NORMAL) {
when ($_ eq '"' and not $escaped) { $state = DOUBLE_QUOTED; }
when ($_ eq "'" and not $escaped) { $state = SINGLE_QUOTED; }
when ($_ eq 'n' and $escaped == 1) {
$ch = "\n";
$escaped = 0;
}
}
if ($state == DOUBLE_QUOTED) {
when ($_ eq '"' and not $escaped) { $state = NORMAL; }
}
if ($state == SINGLE_QUOTED) {
when ($_ eq "'" and not $escaped) { $state = NORMAL; }
}
}
if ($state == NORMAL) {
when ($_ eq '"' and not $escaped) {
$state = DOUBLE_QUOTED;
}
when ($_ eq "'" and not $escaped) {
$state = SINGLE_QUOTED;
}
when ($_ eq 'n' and $escaped == 1) {
$ch = "\n";
$escaped = 0;
}
}
if ($state == DOUBLE_QUOTED) {
when ($_ eq '"' and not $escaped) {
$state = NORMAL;
}
}
if ($state == SINGLE_QUOTED) {
when ($_ eq "'" and not $escaped) {
$state = NORMAL;
}
}
}
$new_code .= '\\' and $escaped = 0 if $escaped;
$new_code .= $ch;
$new_code .= '\\' and $escaped = 0 if $escaped;
$new_code .= $ch;
}
$code = $new_code;
@ -92,69 +84,68 @@ print "code after \\n replacement: [$code]\n" if $debug;
my $single_quote = 0;
my $double_quote = 0;
my $parens = 0;
my $escaped = 0;
my $cpp = 0; # preprocessor
my $parens = 0;
my $escaped = 0;
my $cpp = 0; # preprocessor
while ($code =~ m/(.)/msg) {
my $ch = $1;
my $pos = pos $code;
my $ch = $1;
my $pos = pos $code;
print "adding newlines, ch = [$ch], parens: $parens, cpp: $cpp, single: $single_quote, double: $double_quote, escaped: $escaped, pos: $pos\n" if $debug >= 10;
print "adding newlines, ch = [$ch], parens: $parens, cpp: $cpp, single: $single_quote, double: $double_quote, escaped: $escaped, pos: $pos\n" if $debug >= 10;
if ($ch eq '\\') {
$escaped = not $escaped;
} elsif ($ch eq '#' and not $cpp and not $escaped and not $single_quote and not $double_quote) {
$cpp = 1;
if ($ch eq '\\') { $escaped = not $escaped; }
elsif ($ch eq '#' and not $cpp and not $escaped and not $single_quote and not $double_quote) {
$cpp = 1;
if ($code =~ m/include\s*[<"]([^>"]*)[>"]/msg) {
my $match = $1;
$pos = pos $code;
substr ($code, $pos, 0) = "\n";
pos $code = $pos;
$cpp = 0;
if ($code =~ m/include\s*[<"]([^>"]*)[>"]/msg) {
my $match = $1;
$pos = pos $code;
substr($code, $pos, 0) = "\n";
pos $code = $pos;
$cpp = 0;
} else {
pos $code = $pos;
}
} elsif ($ch eq '"') {
$double_quote = not $double_quote unless $escaped or $single_quote;
$escaped = 0;
} elsif ($ch eq '(' and not $single_quote and not $double_quote) {
$parens++;
} elsif ($ch eq ')' and not $single_quote and not $double_quote) {
$parens--;
$parens = 0 if $parens < 0;
} elsif ($ch eq ';' and not $cpp and not $single_quote and not $double_quote and $parens == 0) {
if (not substr($code, $pos, 1) =~ m/[\n\r]/) {
substr($code, $pos, 0) = "\n";
pos $code = $pos + 1;
}
} elsif ($ch eq "'") {
$single_quote = not $single_quote unless $escaped or $double_quote;
$escaped = 0;
} elsif ($ch eq 'n' and $escaped) {
if (not $single_quote and not $double_quote) {
print "added newline\n" if $debug >= 10;
substr($code, $pos - 2, 2) = "\n";
pos $code = $pos;
$cpp = 0;
}
$escaped = 0;
} elsif ($ch eq '{' and not $cpp and not $single_quote and not $double_quote) {
if (not substr($code, $pos, 1) =~ m/[\n\r]/) {
substr($code, $pos, 0) = "\n";
pos $code = $pos + 1;
}
} elsif ($ch eq '}' and not $cpp and not $single_quote and not $double_quote) {
if (not substr($code, $pos, 1) =~ m/[\n\r;]/) {
substr($code, $pos, 0) = "\n";
pos $code = $pos + 1;
}
} elsif ($ch eq "\n" and $cpp and not $single_quote and not $double_quote) {
$cpp = 0;
} else {
pos $code = $pos;
$escaped = 0;
}
} elsif ($ch eq '"') {
$double_quote = not $double_quote unless $escaped or $single_quote;
$escaped = 0;
} elsif ($ch eq '(' and not $single_quote and not $double_quote) {
$parens++;
} elsif ($ch eq ')' and not $single_quote and not $double_quote) {
$parens--;
$parens = 0 if $parens < 0;
} elsif ($ch eq ';' and not $cpp and not $single_quote and not $double_quote and $parens == 0) {
if (not substr($code, $pos, 1) =~ m/[\n\r]/) {
substr ($code, $pos, 0) = "\n";
pos $code = $pos + 1;
}
} elsif ($ch eq "'") {
$single_quote = not $single_quote unless $escaped or $double_quote;
$escaped = 0;
} elsif ($ch eq 'n' and $escaped) {
if (not $single_quote and not $double_quote) {
print "added newline\n" if $debug >= 10;
substr ($code, $pos - 2, 2) = "\n";
pos $code = $pos;
$cpp = 0;
}
$escaped = 0;
} elsif ($ch eq '{' and not $cpp and not $single_quote and not $double_quote) {
if (not substr($code, $pos, 1) =~ m/[\n\r]/) {
substr ($code, $pos, 0) = "\n";
pos $code = $pos + 1;
}
} elsif ($ch eq '}' and not $cpp and not $single_quote and not $double_quote) {
if (not substr($code, $pos, 1) =~ m/[\n\r;]/) {
substr ($code, $pos, 0) = "\n";
pos $code = $pos + 1;
}
} elsif ($ch eq "\n" and $cpp and not $single_quote and not $double_quote) {
$cpp = 0;
} else {
$escaped = 0;
}
}
print "code after \\n additions: [$code]\n" if $debug;
@ -170,110 +161,107 @@ print "--- precode: [$precode]\n" if $debug;
my $has_main = 0;
if ($lang eq 'C89' or $lang eq 'C99' or $lang eq 'C11' or $lang eq 'C++') {
my $prelude = '';
while ($precode =~ s/^\s*(#.*\n{1,2})//g) {
$prelude .= $1;
}
my $prelude = '';
while ($precode =~ s/^\s*(#.*\n{1,2})//g) { $prelude .= $1; }
print "*** prelude: [$prelude]\n precode: [$precode]\n" if $debug;
print "*** prelude: [$prelude]\n precode: [$precode]\n" if $debug;
my $preprecode = $precode;
my $preprecode = $precode;
# white-out contents of quoted literals
$preprecode =~ s/(?:\"((?:\\\"|(?!\").)*)\")/'"' . ('-' x length $1) . '"'/ge;
$preprecode =~ s/(?:\'((?:\\\'|(?!\').)*)\')/"'" . ('-' x length $1) . "'"/ge;
# white-out contents of quoted literals
$preprecode =~ s/(?:\"((?:\\\"|(?!\").)*)\")/'"' . ('-' x length $1) . '"'/ge;
$preprecode =~ s/(?:\'((?:\\\'|(?!\').)*)\')/"'" . ('-' x length $1) . "'"/ge;
# strip C and C++ style comments
if ($lang eq 'C89' or $args =~ m/-std=(gnu89|c89)/i) {
$preprecode =~ s#/\*[^*]*\*+([^/*][^*]*\*+)*/# #gs;
$preprecode =~ s#|//([^\\]|[^\n][\n]?)*?\n|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse;
} else {
$preprecode =~ s#|//([^\\]|[^\n][\n]?)*?\n|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse;
$preprecode =~ s#/\*[^*]*\*+([^/*][^*]*\*+)*/# #gs;
}
print "preprecode: [$preprecode]\n" if $debug;
print "looking for functions, has main: $has_main\n" if $debug >= 2;
my $func_regex = qr/^([ *\w]+)\s+([*\w]+)\s*\(([^;{]*)\s*\)\s*({.*|<%.*|\?\?<.*)/ims;
# look for potential functions to extract
while ($preprecode =~ /$func_regex/ms) {
my ($pre_ret, $pre_ident, $pre_params, $pre_potential_body) = ($1, $2, $3, $4);
print "looking for functions, found [$pre_ret][$pre_ident][$pre_params][$pre_potential_body], has main: $has_main\n" if $debug >= 1;
# find the pos at which this function lives, for extracting from precode
$preprecode =~ m/(\Q$pre_ret\E\s+\Q$pre_ident\E\s*\(\s*\Q$pre_params\E\s*\)\s*\Q$pre_potential_body\E)/g;
my $extract_pos = (pos $preprecode) - (length $1);
# now that we have the pos, substitute out the extracted potential function from preprecode
$preprecode =~ s/$func_regex//ms;
# create tmpcode object that starts from extract pos, to skip any quoted code
my $tmpcode = substr($precode, $extract_pos);
print "tmpcode: [$tmpcode]\n" if $debug;
$precode = substr($precode, 0, $extract_pos);
print "precode: [$precode]\n" if $debug;
$tmpcode =~ m/$func_regex/ms;
my ($ret, $ident, $params, $potential_body) = ($1, $2, $3, $4);
print "1st extract: [$ret][$ident][$params][$potential_body]\n" if $debug;
$ret =~ s/^\s+//;
$ret =~ s/\s+$//;
if (not length $ret or $ret eq "else" or $ret eq "while" or $ret eq "if" or $ret eq "for" or $ident eq "for" or $ident eq "while" or $ident eq "if") {
$precode .= "$ret $ident ($params) $potential_body";
next;
# strip C and C++ style comments
if ($lang eq 'C89' or $args =~ m/-std=(gnu89|c89)/i) {
$preprecode =~ s#/\*[^*]*\*+([^/*][^*]*\*+)*/# #gs;
$preprecode =~ s#|//([^\\]|[^\n][\n]?)*?\n|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse;
} else {
$tmpcode =~ s/$func_regex//ms;
$preprecode =~ s#|//([^\\]|[^\n][\n]?)*?\n|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse;
$preprecode =~ s#/\*[^*]*\*+([^/*][^*]*\*+)*/# #gs;
}
$potential_body =~ s/^\s*<%/{/ms;
$potential_body =~ s/%>\s*$/}/ms;
$potential_body =~ s/^\s*\?\?</{/ms;
$potential_body =~ s/\?\?>$/}/ms;
print "preprecode: [$preprecode]\n" if $debug;
my @extract = extract_bracketed($potential_body, '{}');
my $body;
if (not defined $extract[0]) {
if ($debug == 0) {
print "error: unmatched brackets\n";
print "looking for functions, has main: $has_main\n" if $debug >= 2;
my $func_regex = qr/^([ *\w]+)\s+([*\w]+)\s*\(([^;{]*)\s*\)\s*({.*|<%.*|\?\?<.*)/ims;
# look for potential functions to extract
while ($preprecode =~ /$func_regex/ms) {
my ($pre_ret, $pre_ident, $pre_params, $pre_potential_body) = ($1, $2, $3, $4);
print "looking for functions, found [$pre_ret][$pre_ident][$pre_params][$pre_potential_body], has main: $has_main\n" if $debug >= 1;
# find the pos at which this function lives, for extracting from precode
$preprecode =~ m/(\Q$pre_ret\E\s+\Q$pre_ident\E\s*\(\s*\Q$pre_params\E\s*\)\s*\Q$pre_potential_body\E)/g;
my $extract_pos = (pos $preprecode) - (length $1);
# now that we have the pos, substitute out the extracted potential function from preprecode
$preprecode =~ s/$func_regex//ms;
# create tmpcode object that starts from extract pos, to skip any quoted code
my $tmpcode = substr($precode, $extract_pos);
print "tmpcode: [$tmpcode]\n" if $debug;
$precode = substr($precode, 0, $extract_pos);
print "precode: [$precode]\n" if $debug;
$tmpcode =~ m/$func_regex/ms;
my ($ret, $ident, $params, $potential_body) = ($1, $2, $3, $4);
print "1st extract: [$ret][$ident][$params][$potential_body]\n" if $debug;
$ret =~ s/^\s+//;
$ret =~ s/\s+$//;
if (not length $ret or $ret eq "else" or $ret eq "while" or $ret eq "if" or $ret eq "for" or $ident eq "for" or $ident eq "while" or $ident eq "if") {
$precode .= "$ret $ident ($params) $potential_body";
next;
} else {
print "error: unmatched brackets for function '$ident';\n";
print "body: [$potential_body]\n";
$tmpcode =~ s/$func_regex//ms;
}
exit;
} else {
$body = $extract[0];
$preprecode .= $extract[1];
$precode .= $extract[1];
$potential_body =~ s/^\s*<%/{/ms;
$potential_body =~ s/%>\s*$/}/ms;
$potential_body =~ s/^\s*\?\?</{/ms;
$potential_body =~ s/\?\?>$/}/ms;
my @extract = extract_bracketed($potential_body, '{}');
my $body;
if (not defined $extract[0]) {
if ($debug == 0) { print "error: unmatched brackets\n"; }
else {
print "error: unmatched brackets for function '$ident';\n";
print "body: [$potential_body]\n";
}
exit;
} else {
$body = $extract[0];
$preprecode .= $extract[1];
$precode .= $extract[1];
}
print "final extract: [$ret][$ident][$params][$body]\n" if $debug;
$code .= "$ret $ident($params) $body\n\n";
$has_main = 1 if $ident eq 'main';
}
print "final extract: [$ret][$ident][$params][$body]\n" if $debug;
$code .= "$ret $ident($params) $body\n\n";
$has_main = 1 if $ident eq 'main';
}
$precode =~ s/^\s+//;
$precode =~ s/\s+$//;
$precode =~ s/^\s+//;
$precode =~ s/\s+$//;
$precode =~ s/^{(.*)}$/$1/s;
$precode =~ s/^{(.*)}$/$1/s;
if (not $has_main) {
$code = "$prelude\n$code" . "int main(void) {\n$precode\n}\n";
$nooutput = "No warnings, errors or output.";
} else {
print "code: [$code]; precode: [$precode]\n" if $debug;
$code = "$prelude\n$precode\n\n$code\n";
$nooutput = "No warnings, errors or output.";
}
if (not $has_main) {
$code = "$prelude\n$code" . "int main(void) {\n$precode\n}\n";
$nooutput = "No warnings, errors or output.";
} else {
print "code: [$code]; precode: [$precode]\n" if $debug;
$code = "$prelude\n$precode\n\n$code\n";
$nooutput = "No warnings, errors or output.";
}
} else {
$code = $precode;
$code = $precode;
}
print "after func extract, code: [$code]\n" if $debug;
@ -300,8 +288,8 @@ $result =~ s/\s+/ /gm;
print "result: [$result]\n" if $debug;
if (not $has_main) {
$result =~ s/\s*int main\(void\) \{//;
$result =~ s/\s*\}\s*$//;
$result =~ s/\s*int main\(void\) \{//;
$result =~ s/\s*\}\s*$//;
}
$output = length $result ? $result : $nooutput;
@ -309,40 +297,35 @@ $output = length $result ? $result : $nooutput;
print "$output\n";
sub execute {
my $timeout = shift @_;
my ($cmdline) = @_;
my $timeout = shift @_;
my ($cmdline) = @_;
my ($ret, $result);
my ($ret, $result);
($ret, $result) = eval {
print "eval\n" if $debug;
($ret, $result) = eval {
print "eval\n" if $debug;
my $result = '';
my $result = '';
my $pid = open(my $fh, '-|', "$cmdline 2>&1");
my $pid = open(my $fh, '-|', "$cmdline 2>&1");
local $SIG{ALRM} = sub { print "Time out\n" if $debug; kill 'TERM', $pid; die "$result [Timed-out]\n"; };
alarm($timeout);
local $SIG{ALRM} = sub { print "Time out\n" if $debug; kill 'TERM', $pid; die "$result [Timed-out]\n"; };
alarm($timeout);
while (my $line = <$fh>) {
$result .= $line;
}
while (my $line = <$fh>) { $result .= $line; }
close $fh;
my $ret = $? >> 8;
close $fh;
my $ret = $? >> 8;
alarm 0;
return ($ret, $result);
};
print "done eval\n" if $debug;
alarm 0;
if ($@ =~ /Timed-out/) { return (-1, $@); }
print "[$ret, $result]\n" if $debug;
return ($ret, $result);
};
print "done eval\n" if $debug;
alarm 0;
if ($@ =~ /Timed-out/) {
return (-1, $@);
}
print "[$ret, $result]\n" if $debug;
return ($ret, $result);
}

View File

@ -13,14 +13,13 @@ my ($text, $t);
my $ua = LWP::UserAgent->new;
$ua->agent("Mozilla/5.0");
my %post = ( 'number' => '4', 'collection[]' => '20thcent' );
my %post = ('number' => '4', 'collection[]' => '20thcent');
my $response = $ua->post("http://www.quotationspage.com/random.php3", \%post);
if (not $response->is_success)
{
print "Couldn't get quote information.\n";
die;
if (not $response->is_success) {
print "Couldn't get quote information.\n";
die;
}
$text = $response->content;
@ -28,9 +27,9 @@ $text = $response->content;
$text =~ m/<dt class="quote"><a.*?>(.*?)<\/a>.*?<dd class="author"><div.*?><a.*?>.*?<b>(.*?)<\/b>/g;
$t = "\"$1\" -- $2.";
my $quote = chr(226) . chr(128) . chr(156);
my $quote = chr(226) . chr(128) . chr(156);
my $quote2 = chr(226) . chr(128) . chr(157);
my $dash = chr(226) . chr(128) . chr(147);
my $dash = chr(226) . chr(128) . chr(147);
$t =~ s/<[^>]+>//g;
$t =~ s/<\/[^>]+>//g;

73
modules/gdefine.pl vendored
View File

@ -12,20 +12,18 @@ use LWP::UserAgent;
my ($defint, $phrase, $text, $entry, $entries, $i);
my @defs;
if ($#ARGV < 0)
{
print "What phrase would you like to define?\n";
die;
if ($#ARGV < 0) {
print "What phrase would you like to define?\n";
die;
}
$phrase = join("+", @ARGV);
$entry = 1;
if ($phrase =~ m/([0-9]+)\+(.*)/)
{
$entry = $1;
$phrase = $2;
if ($phrase =~ m/([0-9]+)\+(.*)/) {
$entry = $1;
$phrase = $2;
}
my $ua = LWP::UserAgent->new;
@ -33,29 +31,22 @@ $ua->agent("howdy");
my $response = $ua->get("http://www.google.com/search?q=define:$phrase");
$phrase =~ s/\+/ /g;
if (not $response->is_success) {
exit(1);
}
if (not $response->is_success) { exit(1); }
$text = $response->content;
if ($text =~ m/No definitions were found/i)
{
print "No entry found for '$phrase'. ";
print "\n";
exit 1;
if ($text =~ m/No definitions were found/i) {
print "No entry found for '$phrase'. ";
print "\n";
exit 1;
}
print "$phrase: ";
$i = $entry;
while ($i <= $entry + 5)
{
if ($text =~ m/<li>(.*?)<br>/gs)
{
push @defs, $1;
}
$i++;
while ($i <= $entry + 5) {
if ($text =~ m/<li>(.*?)<br>/gs) { push @defs, $1; }
$i++;
}
my %uniq = map { $_ => 1 } @defs;
@ -63,27 +54,27 @@ my %uniq = map { $_ => 1 } @defs;
my $comma = "";
for($i = 1; $i <= $#defs + 1; $i++) {
for ($i = 1; $i <= $#defs + 1; $i++) {
# and now for some fugly beautifying regexps...
# and now for some fugly beautifying regexps...
my $quote = chr(226) . chr(128) . chr(156);
my $quote2 = chr(226) . chr(128) . chr(157);
my $dash = chr(226) . chr(128) . chr(147);
my $quote = chr(226) . chr(128) . chr(156);
my $quote2 = chr(226) . chr(128) . chr(157);
my $dash = chr(226) . chr(128) . chr(147);
$_ = $defs[$i-1];
$_ = $defs[$i - 1];
s/$quote/"/g;
s/$quote2/"/g;
s/$dash/-/g;
s/<b>Pronun.*?<BR>//gsi;
s/<.*?>//gsi;
s/\&nbsp\;/ /gi;
s/\&.*?\;//g;
s/\r\n//gs;
s/\( P \)//gs;
s/\s+/ /gs;
s/$quote/"/g;
s/$quote2/"/g;
s/$dash/-/g;
s/<b>Pronun.*?<BR>//gsi;
s/<.*?>//gsi;
s/\&nbsp\;/ /gi;
s/\&.*?\;//g;
s/\r\n//gs;
s/\( P \)//gs;
s/\s+/ /gs;
print "$i) $_$comma";
$comma = ", ";
print "$i) $_$comma";
$comma = ", ";
}

Some files were not shown because too many files have changed in this diff Show More