From fa50724b581fba73111b736a08db087940ecebc3 Mon Sep 17 00:00:00 2001 From: Pragmatic Software Date: Tue, 23 Mar 2010 18:24:02 +0000 Subject: [PATCH] Progressing on object-oriented conversion. Some functionality may be missing. --- PBot/AntiFlood.pm | 25 ++- PBot/ChanOpCommands.pm | 185 ++++++++++++++++++++ PBot/ChanOps.pm | 161 ++++-------------- PBot/Factoids.pm | 23 +-- PBot/IRCHandlers.pm | 52 +++--- PBot/IgnoreList.pm | 23 +++ PBot/PBot.pm | 50 ++++-- PBot/Quotegrabs.pm | 373 ++++++++++++++++++++++++----------------- PBot/Timer.pm | 6 +- admins | 3 +- pbot.pl | 2 - 11 files changed, 548 insertions(+), 355 deletions(-) create mode 100644 PBot/ChanOpCommands.pm diff --git a/PBot/AntiFlood.pm b/PBot/AntiFlood.pm index 604b04a1..637c8441 100644 --- a/PBot/AntiFlood.pm +++ b/PBot/AntiFlood.pm @@ -45,6 +45,8 @@ sub initialize { $self->{flood_msg_count} = 0; $self->{last_timestamp} = gettimeofday; $self->{message_history} = {}; + + $pbot->timer->register(sub { $self->prune_message_history }, 60 * 60 * 1); } sub check_flood { @@ -80,7 +82,7 @@ sub check_flood { $length--; } - return if not exists ${ $self->{pbot}->channels }{$channel} or ${ $self->{pbot}->channels }{$channel}{is_op} == 0; + return if ($channel =~ /^#/) and (not exists ${ $self->{pbot}->channels->channels }{$channel} or ${ $self->{pbot}->channels->channels }{$channel}{is_op} == 0); #$self->{pbot}->logger->log("length: $length, max: $max\n"); @@ -96,7 +98,7 @@ sub check_flood { my $length = ${ $self->message_history }{$nick}{$channel}{offenses} * ${ $self->message_history }{$nick}{$channel}{offenses} * 30; if($channel =~ /^#/) { #channel flood (opposed to private message or otherwise) if($mode == $self->{FLOOD_CHAT}) { - # PBot::OperatorStuff::quiet_nick_timed($nick, $channel, $length); + $self->{pbot}->chanops->quiet_nick_timed($nick, $channel, $length); $self->{pbot}->conn->privmsg($nick, "You have been quieted due to flooding. Please use a web paste service such as http://codepad.org for lengthy pastes. You will be allowed to speak again in $length seconds."); $self->{pbot}->logger->log("$nick $channel flood offense ${ $self->message_history }{$nick}{$channel}{offenses} earned $length second quiet\n"); } @@ -120,4 +122,23 @@ sub message_history { return $self->{message_history}; } +sub prune_message_history { + my $self = shift; + + $self->{pbot}->logger->log("Pruning message history . . .\n"); + foreach my $nick (keys %{ $self->{flood_watch} }) { + foreach my $channel (keys %{ $self->{flood_watch}->{$nick} }) + { + $self->{pbot}->logger->log("Checking [$nick][$channel]\n"); + my $length = $#{ $self->{flood_watch}->{$nick}{$channel}{messages} } + 1; + my %last = %{ @{ $self->{flood_watch}->{$nick}{$channel}{messages} }[$length - 1] }; + + if(gettimeofday - $last{timestamp} >= 60 * 60 * 24) { + $self->{pbot}->logger->log("$nick in $channel hasn't spoken in 24 hours, removing message history.\n"); + delete $self->{flood_watch}->{$nick}{$channel}; + } + } + } +} + 1; diff --git a/PBot/ChanOpCommands.pm b/PBot/ChanOpCommands.pm new file mode 100644 index 00000000..1682f7f1 --- /dev/null +++ b/PBot/ChanOpCommands.pm @@ -0,0 +1,185 @@ +# File: ChanOpCommands.pm +# Authoer: pragma_ +# +# Purpose: Channel operator command subroutines. + +package PBot::ChanOpCommands; + +use warnings; +use strict; + +BEGIN { + use vars qw($VERSION); + $VERSION = $PBot::PBot::VERSION; +} + +use Carp (); + +sub new { + if(ref($_[1]) eq 'HASH') { + Carp::croak("Options to ChanOpCommands should be key/value pairs, not hash reference"); + } + + my ($class, %conf) = @_; + + my $self = bless {}, $class; + $self->initialize(%conf); + return $self; +} + +sub initialize { + my ($self, %conf) = @_; + + my $pbot = delete $conf{pbot}; + if(not defined $pbot) { + Carp::croak("Missing pbot reference to ChanOpCommands"); + } + + $self->{pbot} = $pbot; + + $pbot->commands->register(sub { return $self->quiet(@_) }, "quiet", 10); + $pbot->commands->register(sub { return $self->unquiet(@_) }, "unquiet", 10); + $pbot->commands->register(sub { return $self->ban_user(@_) }, "ban", 10); + $pbot->commands->register(sub { return $self->unban_user(@_) }, "unban", 10); + $pbot->commands->register(sub { return $self->kick_user(@_) }, "kick", 10); +} + +sub quiet { + my $self = shift; + my ($from, $nick, $user, $host, $arguments) = @_; + my ($target, $length) = split(/\s+/, $arguments); + + if(not defined $from) { + $self->{pbot}->logger->log("Command missing ~from parameter!\n"); + return ""; + } + + if(not $from =~ /^#/) { #not a channel + return "/msg $nick This command must be used in the channel."; + } + + if(not defined $target) { + return "/msg $nick Usage: quiet nick [timeout seconds (default: 3600 or 1 hour)]"; + } + + if(not defined $length) { + $length = 60 * 60; # one hour + } + + return "" if $target =~ /\Q$self->{pbot}->botnick\E/i; + + $self->{pbot}->chanops->quiet_nick_timed($target, $from, $length); + $self->{pbot}->conn->privmsg($target, "$nick has quieted you for $length seconds."); +} + +sub unquiet { + my $self = shift; + my ($from, $nick, $user, $host, $arguments) = @_; + + if(not defined $from) { + $self->{pbot}->logger->log("Command missing ~from parameter!\n"); + return ""; + } + + if(not $from =~ /^#/) { #not a channel + return "/msg $nick This command must be used in the channel."; + } + + if(not defined $arguments) { + return "/msg $nick Usage: unquiet nick"; + } + + $self->{pbot}->chanops->unquiet_nick($arguments, $from); + delete ${ $self->{pbot}->chanops->{quieted_nicks} }{$arguments}; + $self->{pbot}->conn->privmsg($arguments, "$nick has allowed you to speak again.") unless $arguments =~ /\Q$self->{pbot}->botnick\E/i; +} + +sub ban_user { + my $self = shift; + my ($from, $nick, $user, $host, $arguments) = @_; + + if(not defined $from) { + $self->{pbot}->logger->log("Command missing ~from parameter!\n"); + return ""; + } + + if(not $from =~ /^#/) { #not a channel + if($arguments =~ /^(#.*?) (.*?) (.*)$/) { + $self->{pbot}->conn->privmsg("ChanServ", "AUTOREM $1 ADD $2 $3"); + unshift @{ $self->{pbot}->chanops->{op_commands} }, "kick $1 $2 Banned"; + $self->{pbot}->chanops->gain_ops($1); + $self->{pbot}->logger->log("$nick!$user\@$host AUTOREM $2 ($3)\n"); + return "/msg $nick $2 added to auto-remove"; + } else { + $self->{pbot}->logger->log("$nick!$user\@$host: bad format for ban in msg\n"); + return "/msg $nick Usage (in msg mode): !ban "; + } + } else { #in a channel + if($arguments =~ /^(.*?) (.*)$/) { + $self->{pbot}->conn->privmsg("ChanServ", "AUTOREM $from ADD $1 $2"); + $self->{pbot}->logger->log("AUTOREM [$from] ADD [$1] [$2]\n"); + $self->{pbot}->logger->log("kick [$from] [$1] Banned\n"); + unshift @{ $self->{pbot}->chanops->{op_commands} }, "kick $from $1 Banned"; + $self->{pbot}->chanops->gain_ops($from); + $self->{pbot}->logger->log("$nick ($from) AUTOREM $1 ($2)\n"); + return "/msg $nick $1 added to auto-remove"; + } else { + $self->{pbot}->logger->log("$nick!$user\@$host: bad format for ban in channel\n"); + return "/msg $nick Usage (in channel mode): !ban "; + } + } +} + +sub unban_user { + my $self = shift; + my ($from, $nick, $user, $host, $arguments) = @_; + + if(not defined $from) { + $self->{pbot}->logger->log("Command missing ~from parameter!\n"); + return ""; + } + + if(not $from =~ /^#/) { #not a channel + if($arguments =~ /^(#.*?) (.*)$/) { + $self->{pbot}->conn->privmsg("ChanServ", "AUTOREM $1 DEL $2"); + unshift @{ $self->{pbot}->chanops->{op_commands} }, "mode $1 -b $2"; + $self->{pbot}->chanops->gain_ops($1); + delete ${ $self->{pbot}->chanops->{unban_timeouts} }{$2}; + $self->{pbot}->logger->log("$nick!$user\@$host AUTOREM DEL $2 ($3)\n"); + return "/msg $nick $2 removed from auto-remove"; + } else { + $self->{pbot}->logger->log("$nick!$user\@$host: bad format for unban in msg\n"); + return "/msg $nick Usage (in msg mode): !unban "; + } + } else { #in a channel + $self->{pbot}->conn->privmsg("ChanServ", "AUTOREM $from DEL $arguments"); + unshift @{ $self->{pbot}->chanops->{op_commands} }, "mode $from -b $arguments"; + $self->{pbot}->chanops->gain_ops($from); + delete ${ $self->{pbot}->chanops->{unban_timeouts} }{$arguments}; + $self->{pbot}->logger->log("$nick!$user\@$host AUTOREM DEL $arguments\n"); + return "/msg $nick $arguments removed from auto-remove"; + } +} + +sub kick_user { + my $self = shift; + my ($from, $nick, $user, $host, $arguments) = @_; + + if(not defined $from) { + $self->{pbot}->logger->log("Command missing ~from parameter!\n"); + return ""; + } + + if(not $from =~ /^#/) { + $self->{pbot}->logger->log("$nick!$user\@$host attempted to /msg kick\n"); + return "/msg $nick Kick must be used in the channel."; + } + if(not $arguments =~ /(.*?) (.*)/) { + $self->{pbot}->logger->log("$nick!$user\@$host: invalid arguments to kick\n"); + return "/msg $nick Usage: !kick "; + } + unshift @{ $self->{pbot}->chanops->{op_commands} }, "kick $from $1 $2"; + $self->{pbot}->chanops->gain_ops($from); +} + +1; diff --git a/PBot/ChanOps.pm b/PBot/ChanOps.pm index 6f2c6d80..f215125c 100644 --- a/PBot/ChanOps.pm +++ b/PBot/ChanOps.pm @@ -38,8 +38,11 @@ sub initialize { $self->{unban_timeouts} = {}; $self->{op_commands} = []; $self->{is_opped} = {}; -} + $pbot->timer->register(sub { $self->check_opped_timeouts }, 10); + $pbot->timer->register(sub { $self->check_quieted_timeouts }, 10); + $pbot->timer->register(sub { $self->check_unban_timeouts }, 10); +} sub gain_ops { my $self = shift; @@ -55,7 +58,7 @@ sub gain_ops { sub lose_ops { my $self = shift; my $channel = shift; - $self->{pbot}->conn->privmsg("chanserv", "op $channel -$self->{pbot}->botnick"); + $self->{pbot}->conn->privmsg("chanserv", "op $channel -" . $self->{pbot}->botnick); if(exists ${ $self->{is_opped} }{$channel}) { ${ $self->{is_opped} }{$channel}{timeout} = gettimeofday + 60; # try again in 1 minute if failed } @@ -77,166 +80,74 @@ sub perform_op_commands { $self->{pbot}->logger->log("Done.\n"); } -sub quiet { - my $self = shift; - my ($from, $nick, $user, $host, $arguments) = @_; - my ($target, $length) = split(/\s+/, $arguments); - - if(not defined $from) { - $self->{pbot}->logger->log("Command missing ~from parameter!\n"); - return ""; - } - - if(not $from =~ /^#/) { #not a channel - return "/msg $nick This command must be used in the channel."; - } - - if(not defined $target) { - return "/msg $nick Usage: quiet nick [timeout seconds (default: 3600 or 1 hour)]"; - } - - if(not defined $length) { - $length = 60 * 60; # one hour - } - - return "" if $target =~ /\Q$self->{pbot}->botnick\E/i; - - quiet_nick_timed($target, $from, $length); - $self->{pbot}->conn->privmsg($target, "$nick has quieted you for $length seconds."); -} - -sub unquiet { - my $self = shift; - my ($from, $nick, $user, $host, $arguments) = @_; - - if(not defined $from) { - $self->{pbot}->logger->log("Command missing ~from parameter!\n"); - return ""; - } - - if(not $from =~ /^#/) { #not a channel - return "/msg $nick This command must be used in the channel."; - } - - if(not defined $arguments) { - return "/msg $nick Usage: unquiet nick"; - } - - unquiet_nick($arguments, $from); - delete ${ $self->{quieted_nicks} }{$arguments}; - $self->{pbot}->conn->privmsg($arguments, "$nick has allowed you to speak again.") unless $arguments =~ /\Q$self->{pbot}->botnick\E/i; -} - sub quiet_nick { my $self = shift; my ($nick, $channel) = @_; unshift @{ $self->{op_commands} }, "mode $channel +q $nick!*@*"; - gain_ops($channel); + $self->gain_ops($channel); } sub unquiet_nick { my $self = shift; my ($nick, $channel) = @_; unshift @{ $self->{op_commands} }, "mode $channel -q $nick!*@*"; - gain_ops($channel); + $self->gain_ops($channel); } sub quiet_nick_timed { my $self = shift; my ($nick, $channel, $length) = @_; - quiet_nick($nick, $channel); + $self->quiet_nick($nick, $channel); ${ $self->{quieted_nicks} }{$nick}{time} = gettimeofday + $length; ${ $self->{quieted_nicks} }{$nick}{channel} = $channel; } -# TODO: need to refactor ban_user() and unban_user() - mostly duplicate code -sub ban_user { +sub check_quieted_timeouts { my $self = shift; - my ($from, $nick, $user, $host, $arguments) = @_; + my $now = gettimeofday(); - if(not defined $from) { - $self->{pbot}->logger->log("Command missing ~from parameter!\n"); - return ""; - } - - if(not $from =~ /^#/) { #not a channel - if($arguments =~ /^(#.*?) (.*?) (.*)$/) { - $self->{pbot}->conn->privmsg("ChanServ", "AUTOREM $1 ADD $2 $3"); - unshift @{ $self->{op_commands} }, "kick $1 $2 Banned"; - gain_ops($1); - $self->{pbot}->logger->log("$nick!$user\@$host AUTOREM $2 ($3)\n"); - return "/msg $nick $2 added to auto-remove"; + foreach my $nick (keys %{ $self->{quieted_nicks} }) { + if($self->{quieted_nicks}->{$nick}{time} < $now) { + $self->{pbot}->logger->log("Unquieting $nick\n"); + $self->unquiet_nick($nick, $self->{quieted_nicks}->{$nick}{channel}); + delete $self->{quieted_nicks}->{$nick}; + $self->{pbot}->conn->privmsg($nick, "You may speak again."); } else { - $self->{pbot}->logger->log("$nick!$user\@$host: bad format for ban in msg\n"); - return "/msg $nick Usage (in msg mode): !ban "; - } - } else { #in a channel - if($arguments =~ /^(.*?) (.*)$/) { - $self->{pbot}->conn->privmsg("ChanServ", "AUTOREM $from ADD $1 $2"); - $self->{pbot}->logger->log("AUTOREM [$from] ADD [$1] [$2]\n"); - $self->{pbot}->logger->log("kick [$from] [$1] Banned\n"); - unshift @{ $self->{op_commands} }, "kick $from $1 Banned"; - gain_ops($from); - $self->{pbot}->logger->log("$nick ($from) AUTOREM $1 ($2)\n"); - return "/msg $nick $1 added to auto-remove"; - } else { - $self->{pbot}->logger->log("$nick!$user\@$host: bad format for ban in channel\n"); - return "/msg $nick Usage (in channel mode): !ban "; + #my $timediff = $quieted_nicks{$nick}{time} - $now; + #$logger->log "quiet: $nick has $timediff seconds remaining\n" } } } -sub unban_user { +sub check_opped_timeouts { my $self = shift; - my ($from, $nick, $user, $host, $arguments) = @_; + my $now = gettimeofday(); - if(not defined $from) { - $self->{pbot}->logger->log("Command missing ~from parameter!\n"); - return ""; - } - - if(not $from =~ /^#/) { #not a channel - if($arguments =~ /^(#.*?) (.*)$/) { - $self->{pbot}->conn->privmsg("ChanServ", "AUTOREM $1 DEL $2"); - unshift @{ $self->{op_commands} }, "mode $1 -b $2"; - $self->gain_ops($1); - delete ${ $self->{unban_timeouts} }{$2}; - $self->{pbot}->logger->log("$nick!$user\@$host AUTOREM DEL $2 ($3)\n"); - return "/msg $nick $2 removed from auto-remove"; + foreach my $channel (keys %{ $self->{is_opped} }) { + if($self->{is_opped}->{$channel}{timeout} < $now) { + $self->lose_ops($channel); } else { - $self->{pbot}->logger->log("$nick!$user\@$host: bad format for unban in msg\n"); - return "/msg $nick Usage (in msg mode): !unban "; + # my $timediff = $is_opped{$channel}{timeout} - $now; + # $logger->log("deop $channel in $timediff seconds\n"); } - } else { #in a channel - $self->{pbot}->conn->privmsg("ChanServ", "AUTOREM $from DEL $arguments"); - unshift @{ $self->{op_commands} }, "mode $from -b $arguments"; - $self->gain_ops($from); - delete ${ $self->{unban_timeouts} }{$arguments}; - $self->{pbot}->logger->log("$nick!$user\@$host AUTOREM DEL $arguments\n"); - return "/msg $nick $arguments removed from auto-remove"; } } -sub kick_nick { +sub check_unban_timeouts { my $self = shift; - my ($from, $nick, $user, $host, $arguments) = @_; + my $now = gettimeofday(); - if(not defined $from) { - $self->{pbot}->logger->log("Command missing ~from parameter!\n"); - return ""; + foreach my $ban (keys %{ $self->{unban_timeouts} }) { + if($self->{unban_timeouts}->{$ban}{timeout} < $now) { + unshift @{ $self->{op_commands} }, "mode " . $self->{unban_timeout}->{$ban}{channel} . " -b $ban"; + $self->gain_ops($self->{unban_timeouts}->{$ban}{channel}); + delete $self->{unban_timeouts}->{$ban}; + } else { + #my $timediff = $unban_timeout{$ban}{timeout} - $now; + #$logger->log("$unban_timeout{$ban}{channel}: unban $ban in $timediff seconds\n"); + } } - - if(not $from =~ /^#/) { - $self->{pbot}->logger->log("$nick!$user\@$host attempted to /msg kick\n"); - return "/msg $nick Kick must be used in the channel."; - } - if(not $arguments =~ /(.*?) (.*)/) { - $self->{pbot}->logger->log("$nick!$user\@$host: invalid arguments to kick\n"); - return "/msg $nick Usage: !kick "; - } - unshift @{ $self->{op_commands} }, "kick $from $1 $2"; - $self->gain_ops($from); } 1; diff --git a/PBot/Factoids.pm b/PBot/Factoids.pm index 79b55815..8597bbe9 100644 --- a/PBot/Factoids.pm +++ b/PBot/Factoids.pm @@ -43,20 +43,10 @@ sub initialize { Carp::croak("Missing pbot reference to Factoids"); } - my $export_timeout = delete $conf{export_timeout}; - if(not defined $export_timeout) { - if(defined $export_path) { - $export_timeout = 300; # every 5 minutes - } else { - $export_timeout = -1; - } - } - $self->{factoids} = {}; $self->{filename} = $filename; $self->{export_path} = $export_path; $self->{export_site} = $export_site; - $self->{export_timeout} = $export_timeout; $self->{pbot} = $pbot; $self->{factoidmodulelauncher} = PBot::FactoidModuleLauncher->new(pbot => $pbot); @@ -164,6 +154,8 @@ sub save_factoids { } } close(FILE); + + $self->export_factoids(); } sub add_factoid { @@ -188,7 +180,6 @@ sub export_factoids { my $filename; if(@_) { $filename = shift; } else { $filename = $self->export_path; } - return if not defined $filename; my $text; @@ -216,9 +207,8 @@ sub export_factoids { } print FILE "\n"; print FILE "
$i factoids memorized.
"; - print FILE "This page is automatically generated every " . $self->export_timeout . " seconds." if $self->export_timeout > 0; close(FILE); - $self->{pbot}->logger->log("$i factoids exported to path: " . $self->export_path . ", site: " . $self->export_site . "\n"); + #$self->{pbot}->logger->log("$i factoids exported to path: " . $self->export_path . ", site: " . $self->export_site . "\n"); return "$i factoids exported to " . $self->export_site; } @@ -411,13 +401,6 @@ sub export_path { return $self->{export_path}; } -sub export_timeout { - my $self = shift; - - if(@_) { $self->{export_timeout} = shift; } - return $self->{export_timeout}; -} - sub logger { my $self = shift; if(@_) { $self->{logger} = shift; } diff --git a/PBot/IRCHandlers.pm b/PBot/IRCHandlers.pm index 9af19a60..fee8838a 100644 --- a/PBot/IRCHandlers.pm +++ b/PBot/IRCHandlers.pm @@ -8,10 +8,11 @@ package PBot::IRCHandlers; use warnings; use strict; -BEGIN { - use vars qw($VERSION); - $VERSION = $PBot::PBot::VERSION; -} +use vars qw($VERSION); +$VERSION = $PBot::PBot::VERSION; + +use Carp(); +use Time::HiRes qw(gettimeofday); sub new { if(ref($_[1]) eq 'HASH') { @@ -50,7 +51,7 @@ sub on_disconnect { $conn->connect(); if(not $conn->connected) { sleep(5); - on_disconnect($self, $conn, $event); + $self->on_disconnect($self, $conn, $event); } } @@ -81,7 +82,7 @@ sub on_msg { $text =~ s/^!?(.*)/\!$1/; $event->{to}[0] = $nick; $event->{args}[0] = $text; - on_public($self, $conn, $event); + $self->on_public($conn, $event); } sub on_notice { @@ -94,7 +95,7 @@ sub on_notice { if($nick eq "NickServ" && $text =~ m/You are now identified/i) { foreach my $chan (keys %{ $self->{pbot}->channels->channels }) { if(${ $self->{pbot}->channels->channels }{$chan}{enabled} != 0) { - $self->{pbot}->logger->log("Joining channel: $chan\n"); + $self->{pbot}->logger->log("Joining channel: $chan\n"); $conn->join($chan); } } @@ -104,7 +105,7 @@ sub on_notice { sub on_action { my ($self, $conn, $event) = @_; - on_public($self, $conn, $event); + $self->on_public($conn, $event); } sub on_mode { @@ -116,39 +117,37 @@ sub on_mode { $channel = lc $channel; $self->{pbot}->logger->log("Got mode: nick: $nick, host: $host, mode: $mode, target: " . (defined $target ? $target : "") . ", channel: $channel\n"); -=cut - if(defined $target && $target eq $botnick) { # bot targeted + if(defined $target && $target eq $self->{pbot}->botnick) { # bot targeted if($mode eq "+o") { $self->{pbot}->logger->log("$nick opped me in $channel\n"); - if(exists $is_opped{$channel}) { - $self->{pbot}->logger->log("warning: erm, I was already opped?\n"); + if(exists $self->{pbot}->chanops->{is_opped}->{$channel}) { + $self->{pbot}->logger->log("erm, I was already opped?\n"); } - $is_opped{$channel}{timeout} = gettimeofday + 300; # 5 minutes - PBot::OperatorStuff::perform_op_commands(); + $self->{pbot}->chanops->{is_opped}->{$channel}{timeout} = gettimeofday + 300; # 5 minutes + $self->{pbot}->chanops->perform_op_commands(); } elsif($mode eq "-o") { $self->{pbot}->logger->log("$nick removed my ops in $channel\n"); - if(not exists $is_opped{$channel}) { + if(not exists $self->{pbot}->chanops->{is_opped}->{$channel}) { $self->{pbot}->logger->log("warning: erm, I wasn't opped?\n"); } - delete $is_opped{$channel}; + delete $self->{pbot}->chanops->{is_opped}->{$channel}; } } else { # bot not targeted if($mode eq "+b") { if($nick eq "ChanServ") { - $unban_timeout{$target}{timeout} = gettimeofday + 3600 * 2; # 2 hours - $unban_timeout{$target}{channel} = $channel; + $self->{pbot}->chanops->{unban_timeout}->{$target}{timeout} = gettimeofday + 3600 * 2; # 2 hours + $self->{pbot}->chanops->{unban_timeout}->{$target}{channel} = $channel; } - } elsif($mode eq "+e" && $channel eq $botnick) { - foreach my $chan (keys %channels) { - if($channels{$chan}{enabled} != 0) { - $self->{pbot}->logger->log("Joining channel: $chan\n"); - $conn->join($chan); + } elsif($mode eq "+e" && $channel eq $self->{pbot}->botnick) { + foreach my $chan (keys %{ $self->{pbot}->channels->channels }) { + if($self->channels->{channels}->{$chan}{enabled} != 0) { + $self->{pbot}->logger->log("Joining channel: $chan\n"); + $self->{pbot}->conn->join($chan); } } } } -=cut } sub on_join { @@ -174,11 +173,6 @@ sub on_departure { =cut } -sub logger { - my $self = shift; - return $self->{logger}; -} - sub pbot { my $self = shift; return $self->{pbot}; diff --git a/PBot/IgnoreList.pm b/PBot/IgnoreList.pm index 235ed83d..bb59da42 100644 --- a/PBot/IgnoreList.pm +++ b/PBot/IgnoreList.pm @@ -37,6 +37,8 @@ sub initialize { $self->{ignore_list} = {}; $self->{ignore_flood_counter} = 0; $self->{last_timestamp} = gettimeofday; + + $pbot->timer->register(sub { $self->check_ignore_timeouts }, 10); } sub add { @@ -101,4 +103,25 @@ sub check_ignore { return 0; } +sub check_ignore_timeouts { + 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 + + if($self->{ignore_list}->{$hostmask}{$channel} < $now) { + $self->{pbot}->{ignorelistcmds}->unignore_user("", "floodcontrol", "", "", "$hostmask $channel"); + if($hostmask eq ".*") { + $self->{pbot}->conn->me($channel, "awakens."); + } + } else { + #my $timediff = $ignore_list{$host}{$channel} - $now; + #$logger->log "ignore: $host has $timediff seconds remaining\n" + } + } + } +} + 1; diff --git a/PBot/PBot.pm b/PBot/PBot.pm index 046a0e7e..bea36e46 100644 --- a/PBot/PBot.pm +++ b/PBot/PBot.pm @@ -8,10 +8,8 @@ package PBot::PBot; use strict; use warnings; -BEGIN { - use vars qw($VERSION); - $VERSION = "0.6.0-beta"; -} +use vars qw($VERSION); +$VERSION = "0.6.0-beta"; # unbuffer stdout STDOUT->autoflush(1); @@ -23,16 +21,14 @@ use PBot::StdinReader; use Net::IRC; use PBot::IRCHandlers; +use PBot::Channels; use PBot::AntiFlood; use PBot::Interpreter; use PBot::Commands; use PBot::ChanOps; -use PBot::Channels; - -use PBot::Quotegrabs; -#use PBot::QuotegrabCommands; +use PBot::ChanOpCommands; use PBot::Factoids; use PBot::FactoidCommands; @@ -43,6 +39,9 @@ use PBot::BotAdminCommands; use PBot::IgnoreList; use PBot::IgnoreListCommands; +use PBot::Quotegrabs; +# no PBot::QuotegrabsCommands (bundled inside PBot::Quotegrabs for a change) + use PBot::Timer; sub new { @@ -73,13 +72,11 @@ sub initialize { my $MAX_NICK_MESSAGES = delete $conf{MAX_NICK_MESSAGES}; my $factoids_file = delete $conf{factoids_file}; - my $export_factoids_timeout = delete $conf{export_factoids_timeout}; my $export_factoids_path = delete $conf{export_factoids_path}; my $export_factoids_site = delete $conf{export_factoids_site}; my $module_dir = delete $conf{module_dir}; my $quotegrabs_file = delete $conf{quotegrabs_file}; - my $export_quotegrabs_timeout = delete $conf{export_quotegrabs_timeout}; my $export_quotegrabs_path = delete $conf{export_quotegrabs_path}; my $export_quotegrabs_site = delete $conf{export_quotegrabs_site}; @@ -102,6 +99,9 @@ sub initialize { my $logger = PBot::Logger->new(log_file => $log_file); $self->{logger} = $logger; + $self->{commands} = PBot::Commands->new(pbot => $self); + $self->{timer} = PBot::Timer->new(timeout => 10); + $self->{admins} = PBot::BotAdmins->new( pbot => $self, filename => $admins_file, @@ -123,8 +123,6 @@ sub initialize { $self->module_dir($module_dir); - $self->{commands} = PBot::Commands->new(pbot => $self); - $self->{antiflood} = PBot::AntiFlood->new(pbot => $self); $self->{ignorelist} = PBot::IgnoreList->new(pbot => $self); @@ -143,11 +141,19 @@ sub initialize { $self->{channels} = PBot::Channels->new(pbot => $self, filename => $channels_file); $self->channels->load_channels() if defined $channels_file; - $self->{chanops} = PBot::ChanOps->new(pbot => $self); + $self->{chanops} = PBot::ChanOps->new(pbot => $self); + $self->{chanopcmds} = PBot::ChanOpCommands->new(pbot => $self); + + $self->{quotegrabs} = PBot::Quotegrabs->new( + pbot => $self, + filename => $quotegrabs_file, + export_path => $export_quotegrabs_path, + export_site => $export_quotegrabs_site, + ); + + $self->quotegrabs->add_quotegrab($botnick, "#pbot2", 0, "pragma_", "Who's a bot?"); + $self->quotegrabs->load_quotegrabs() if defined $quotegrabs_file; - $self->{timer} = PBot::Timer->new(timeout => 10); - $self->timer->register(sub { $self->factoids->export_factoids }, $export_factoids_timeout) if defined $export_factoids_path; -# $self->timer->register(sub { $self->quotegrabs->export_quotegrabs }, $export_quotegrabs_timeout) if defined $export_quotegrabs_path; $self->timer->start(); } @@ -329,6 +335,18 @@ sub antiflood { return $self->{antiflood}; } +sub quotegrabs { + my $self = shift; + if(@_) { $self->{quotegrabs} = shift; } + return $self->{quotegrabs}; +} + +sub chanops { + my $self = shift; + if(@_) { $self->{chanops} = shift; } + return $self->{chanops}; +} + sub ircserver { my $self = shift; if(@_) { $self->{ircserver} = shift; } diff --git a/PBot/Quotegrabs.pm b/PBot/Quotegrabs.pm index 3d55669a..ce2007cb 100644 --- a/PBot/Quotegrabs.pm +++ b/PBot/Quotegrabs.pm @@ -1,167 +1,65 @@ -# File: NewModule.pm +# File: Quotegrabs.pm # Authoer: pragma_ # -# Purpose: New module skeleton +# Purpose: Allows users to "grab" quotes from anti-flood history and store them for later retreival. package PBot::Quotegrabs; use warnings; use strict; -BEGIN { - use Exporter (); - use vars qw($VERSION @ISA @EXPORT_OK); +use vars qw($VERSION); +$VERSION = $PBot::PBot::VERSION; - $VERSION = $PBot::PBot::VERSION; - @ISA = qw(Exporter); - @EXPORT_OK = qw(@quotegrabs $logger $MAX_NICK_MESSAGES %flood_watch $quotegrabs_file $export_quotegrabs_path $export_quotegrabs_timeout); +use HTML::Entities; + +sub new { + if(ref($_[1]) eq 'HASH') { + Carp::croak("Options to Quotegrabs should be key/value pairs, not hash reference"); + } + + my ($class, %conf) = @_; + + my $self = bless {}, $class; + $self->initialize(%conf); + return $self; } -use vars @EXPORT_OK; +sub initialize { + my ($self, %conf) = @_; -*logger = \$PBot::PBot::logger; -*quotegrabs_file = \$PBot::PBot::quotegrabs_file; -*export_quotegrabs_path = \$PBot::PBot::export_quotegrabs_path; -*export_quotegrabs_timeout = \$PBot::PBot::export_quotegrabs_timeout; - -@quotegrabs = (); - -sub quotegrab { - my ($from, $nick, $user, $host, $arguments) = @_; - - if(not defined $from) { - $logger->log("Command missing ~from parameter!\n"); - return ""; + my $pbot = delete $conf{pbot}; + if(not defined $pbot) { + Carp::croak("Missing pbot reference to Quotegrabs"); } - if(not defined $arguments) { - return "Usage: !grab [history] [channel] -- where [history] is an optional argument that is an integer number of recent messages; e.g., to grab the 3rd most recent message for nick, use !grab nick 3"; - } + my $filename = delete $conf{filename}; + my $export_path = delete $conf{export_path}; - my ($grab_nick, $grab_history, $channel) = split(/\s+/, $arguments, 3); + $self->{pbot} = $pbot; + $self->{filename} = $filename; + $self->{export_path} = $export_path; + $self->{quotegrabs} = []; - $grab_history = 1 if not defined $grab_history; - $channel = $from if not defined $channel; - - if($grab_history < 1 || $grab_history > $MAX_NICK_MESSAGES) { - return "/msg $nick Please choose a history between 1 and $MAX_NICK_MESSAGES"; - } - - if(not exists $flood_watch{$grab_nick}) { - return "No message history for $grab_nick."; - } - - if(not exists $flood_watch{$grab_nick}{$channel}) { - return "No message history for $grab_nick in $channel."; - } - - my @messages = @{ $flood_watch{$grab_nick}{$channel}{messages} }; - - $grab_history--; - - if($grab_history > $#messages) { - return "$grab_nick has only " . ($#messages + 1) . " messages in the history."; - } - - $grab_history = $#messages - $grab_history; - - $logger->log("$nick ($from) grabbed <$grab_nick/$channel> $messages[$grab_history]->{msg}\n"); - - my $quotegrab = {}; - $quotegrab->{nick} = $grab_nick; - $quotegrab->{channel} = $channel; - $quotegrab->{timestamp} = $messages[$grab_history]->{timestamp}; - $quotegrab->{grabbed_by} = $nick; - $quotegrab->{text} = $messages[$grab_history]->{msg}; - $quotegrab->{id} = $#quotegrabs + 2; - - push @quotegrabs, $quotegrab; - - save_quotegrabs(); - - my $msg = $messages[$grab_history]->{msg}; - $msg =~ s/(.{8}).*/$1.../; - - return "Quote grabbed: " . ($#quotegrabs + 1) . ": <$grab_nick> $msg"; -} - -sub delete_quotegrab { - my ($from, $nick, $user, $host, $arguments) = @_; - - if($arguments < 1 || $arguments > $#quotegrabs + 1) { - return "/msg $nick Valid range for !getq is 1 - " . ($#quotegrabs + 1); - } - - my $quotegrab = $quotegrabs[$arguments - 1]; - splice @quotegrabs, $arguments - 1, 1; - save_quotegrabs(); - return "Deleted $arguments: <$quotegrab->{nick}> $quotegrab->{text}"; -} - -sub show_quotegrab { - my ($from, $nick, $user, $host, $arguments) = @_; - - if($arguments < 1 || $arguments > $#quotegrabs + 1) { - return "/msg $nick Valid range for !getq is 1 - " . ($#quotegrabs + 1); - } - - my $quotegrab = $quotegrabs[$arguments - 1]; - return "$arguments: <$quotegrab->{nick}> $quotegrab->{text}"; -} - -sub show_random_quotegrab { - my ($from, $nick, $user, $host, $arguments) = @_; - my @quotes = (); - my $nick_search = ".*"; - my $channel_search = $from; - - if(not defined $from) { - $logger->log("Command missing ~from parameter!\n"); - return ""; - } - - if(defined $arguments) { - ($nick_search, $channel_search) = split(/\s+/, $arguments, 2); - # $logger->log("[ns: $nick_search][cs: $channel_search]\n"); - if(not defined $channel_search) { - $channel_search = $from; - } - } - - my $channel_search_quoted = quotemeta($channel_search); - $logger->log("[ns: $nick_search][cs: $channel_search][csq: $channel_search_quoted]\n"); - - eval { - for(my $i = 0; $i <= $#quotegrabs; $i++) { - my $hash = $quotegrabs[$i]; - if($hash->{channel} =~ /$channel_search_quoted/i && $hash->{nick} =~ /$nick_search/i) { - $hash->{id} = $i + 1; - push @quotes, $hash; - } - } - }; - - if($@) { - $logger->log("Error in show_random_quotegrab parameters: $@\n"); - return "/msg $nick Error: $@" - } - - if($#quotes < 0) { - if($nick_search eq ".*") { - return "No quotes grabbed for $channel_search yet. Use !grab to grab a quote."; - } else { - return "No quotes grabbed for $nick_search in $channel_search yet. Use !grab to grab a quote."; - } - } - - my $quotegrab = $quotes[int rand($#quotes + 1)]; - return "$quotegrab->{id}: <$quotegrab->{nick}> $quotegrab->{text}"; + #------------------------------------------------------------------------------------- + # The following could be in QuotegrabsCommands.pm, or they could be kept in here? + #------------------------------------------------------------------------------------- + $pbot->commands->register(sub { $self->grab_quotegrab(@_) }, "grab", 0); + $pbot->commands->register(sub { $self->show_quotegrab(@_) }, "getq", 0); + $pbot->commands->register(sub { $self->delete_quotegrab(@_) }, "delq", 0); + $pbot->commands->register(sub { $self->show_random_quotegrab(@_) }, "rq", 0); } sub load_quotegrabs { - $logger->log("Loading quotegrabs from $quotegrabs_file ...\n"); + my $self = shift; + my $filename; + + if(@_) { $filename = shift; } else { $filename = $self->{filename}; } + return if not defined $filename; + + $self->{pbot}->logger->log("Loading quotegrabs from $filename ...\n"); - open(FILE, "< $quotegrabs_file") or die "Couldn't open $quotegrabs_file: $!\n"; + open(FILE, "< $filename") or die "Couldn't open $filename: $!\n"; my @contents = ; close(FILE); @@ -172,7 +70,7 @@ sub load_quotegrabs { 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 $quotegrabs_file\n"; + die "Syntax error around line $i of $self->{quotegrabs}_file\n"; } my $quotegrab = {}; @@ -182,35 +80,42 @@ sub load_quotegrabs { $quotegrab->{grabbed_by} = $grabbed_by; $quotegrab->{text} = $text; $quotegrab->{id} = $i + 1; - push @quotegrabs, $quotegrab; + push @{ $self->{quotegrabs} }, $quotegrab; } - $logger->log(" $i quotegrabs loaded.\n"); - $logger->log("Done.\n"); + $self->{pbot}->logger->log(" $i quotegrabs loaded.\n"); + $self->{pbot}->logger->log("Done.\n"); } sub save_quotegrabs { - open(FILE, "> $quotegrabs_file") or die "Couldn't open $quotegrabs_file: $!\n"; + my $self = shift; + my $filename; - for(my $i = 0; $i <= $#quotegrabs; $i++) { - my $quotegrab = $quotegrabs[$i]; + if(@_) { $filename = shift; } else { $filename = $self->{filename}; } + return if not defined $filename; + + 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"; } close(FILE); - system("cp $quotegrabs_file $quotegrabs_file.bak"); + $self->export_quotegrabs(); } sub export_quotegrabs() { - return "Not enabled" if not defined $export_quotegrabs_path; + my $self = shift; + return "Not enabled" if not defined $self->{export_path}; my $text; my $last_channel = ""; my $had_table = 0; - open FILE, "> $export_quotegrabs_path" or return "Could not open export path."; + open FILE, "> $self->{export_path}" or return "Could not open export path."; my $time = localtime; print FILE "Generated at $time

Candide's Quotegrabs

\n"; my $i = 0; - foreach my $quotegrab (sort { $$a{channel} cmp $$b{channel} or $$a{nick} cmp $$b{nick} } @quotegrabs) { + foreach my $quotegrab (sort { $$a{channel} cmp $$b{channel} or $$a{nick} cmp $$b{nick} } @{ $self->{quotegrabs} }) { if(not $quotegrab->{channel} =~ /^$last_channel$/i) { print FILE "\n" if $had_table; print FILE "

$quotegrab->{channel}


\n"; @@ -237,9 +142,165 @@ sub export_quotegrabs() { } print FILE "\n"; - print FILE "
$i quotegrabs grabbed.
This page is automatically generated every $export_quotegrabs_timeout seconds."; close(FILE); return "$i quotegrabs exported to http://blackshell.com/~msmud/candide/quotegrabs.html"; } +# ---------------------------------------------------------------------------------------- +# The following subroutines could be in QuotegrabCommands.pm . . . +# ---------------------------------------------------------------------------------------- + +sub grab_quotegrab { + 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) { + return "Usage: !grab [history] [channel] -- where [history] is an optional argument that is an integer number of recent messages; e.g., to grab the 3rd most recent message for nick, use !grab nick 3"; + } + + my ($grab_nick, $grab_history, $channel) = split(/\s+/, $arguments, 3); + + if(not defined $grab_history) { + $grab_history = $nick eq $grab_nick ? 2 : 1; + } + $channel = $from if not defined $channel; + + if($grab_history < 1 || $grab_history > $self->{pbot}->{MAX_NICK_MESSAGES}) { + return "/msg $nick Please choose a history between 1 and $self->{pbot}->{MAX_NICK_MESSAGES}"; + } + + if(not exists $self->{pbot}->antiflood->message_history->{$grab_nick}) { + return "No message history for $grab_nick."; + } + + if(not exists $self->{pbot}->antiflood->message_history->{$grab_nick}{$channel}) { + return "No message history for $grab_nick in $channel."; + } + + my @messages = @{ $self->{pbot}->antiflood->message_history->{$grab_nick}{$channel}{messages} }; + + $grab_history--; + + if($grab_history > $#messages) { + return "$grab_nick has only " . ($#messages + 1) . " messages in the history."; + } + + $grab_history = $#messages - $grab_history; + + $self->{pbot}->logger->log("$nick ($from) grabbed <$grab_nick/$channel> $messages[$grab_history]->{msg}\n"); + + my $quotegrab = {}; + $quotegrab->{nick} = $grab_nick; + $quotegrab->{channel} = $channel; + $quotegrab->{timestamp} = $messages[$grab_history]->{timestamp}; + $quotegrab->{grabbed_by} = $nick; + $quotegrab->{text} = $messages[$grab_history]->{msg}; + $quotegrab->{id} = $#{ $self->{quotegrabs} } + 2; + + push @{ $self->{quotegrabs} }, $quotegrab; + + $self->save_quotegrabs(); + + my $msg = $messages[$grab_history]->{msg}; + $msg =~ s/(.{8}).*/$1.../; + + return "Quote grabbed: " . ($#{ $self->{quotegrabs} } + 1) . ": <$grab_nick> $msg"; +} + +sub add_quotegrab { + my ($self, $nick, $channel, $timestamp, $grabbed_by, $text) = @_; + + my $quotegrab = {}; + $quotegrab->{nick} = $nick; + $quotegrab->{channel} = $channel; + $quotegrab->{timestamp} = $timestamp; + $quotegrab->{grabbed_by} = $grabbed_by; + $quotegrab->{text} = $text; + $quotegrab->{id} = $#{ $self->{quotegrabs} } + 2; + + push @{ $self->{quotegrabs} }, $quotegrab; +} + +sub delete_quotegrab { + my ($self, $from, $nick, $user, $host, $arguments) = @_; + + if($arguments < 1 || $arguments > $#{ $self->{quotegrabs} } + 1) { + return "/msg $nick Valid range for !getq is 1 - " . ($#{ $self->{quotegrabs} } + 1); + } + + my $quotegrab = $self->{quotegrabs}[$arguments - 1]; + splice @{ $self->{quotegrabs} }, $arguments - 1, 1; + + for(my $i = $arguments - 1; $i <= $#{ $self->{quotegrabs} }; $i++ ) { + $self->{quotegrabs}[$i]->{id}--; + } + + $self->save_quotegrabs(); + return "Deleted $arguments: <$quotegrab->{nick}> $quotegrab->{text}"; +} + +sub show_quotegrab { + my ($self, $from, $nick, $user, $host, $arguments) = @_; + + if($arguments < 1 || $arguments > $#{ $self->{quotegrabs} } + 1) { + return "/msg $nick Valid range for !getq is 1 - " . ($#{ $self->{quotegrabs} } + 1); + } + + my $quotegrab = $self->{quotegrabs}[$arguments - 1]; + return "$arguments: <$quotegrab->{nick}> $quotegrab->{text}"; +} + +sub show_random_quotegrab { + my ($self, $from, $nick, $user, $host, $arguments) = @_; + my @quotes = (); + my $nick_search = ".*"; + my $channel_search = $from; + + if(not defined $from) { + $self->{pbot}->logger->log("Command missing ~from parameter!\n"); + return ""; + } + + if(defined $arguments) { + ($nick_search, $channel_search) = split(/\s+/, $arguments, 2); + # $self->{pbot}->logger->log("[ns: $nick_search][cs: $channel_search]\n"); + if(not defined $channel_search) { + $channel_search = $from; + } + } + + my $channel_search_quoted = quotemeta($channel_search); + $self->{pbot}->logger->log("[ns: $nick_search][cs: $channel_search][csq: $channel_search_quoted]\n"); + + eval { + for(my $i = 0; $i <= $#{ $self->{quotegrabs} }; $i++) { + my $hash = $self->{quotegrabs}[$i]; + if($hash->{channel} =~ /$channel_search_quoted/i && $hash->{nick} =~ /$nick_search/i) { + $hash->{id} = $i + 1; + push @quotes, $hash; + } + } + }; + + if($@) { + $self->{pbot}->logger->log("Error in show_random_quotegrab parameters: $@\n"); + return "/msg $nick Error: $@" + } + + if($#quotes < 0) { + if($nick_search eq ".*") { + return "No quotes grabbed for $channel_search yet. Use !grab to grab a quote."; + } else { + return "No quotes grabbed for $nick_search in $channel_search yet. Use !grab to grab a quote."; + } + } + + my $quotegrab = $quotes[int rand($#quotes + 1)]; + return "$quotegrab->{id}: <$quotegrab->{nick}> $quotegrab->{text}"; +} + 1; diff --git a/PBot/Timer.pm b/PBot/Timer.pm index efc889a8..874c087b 100644 --- a/PBot/Timer.pm +++ b/PBot/Timer.pm @@ -10,10 +10,8 @@ package PBot::Timer; use warnings; use strict; -BEGIN { - use vars qw($VERSION); - $VERSION = '1.0.0'; -} +use vars qw($VERSION); +$VERSION = '1.0.0'; use Carp (); diff --git a/admins b/admins index 03f30e75..c028743d 100644 --- a/admins +++ b/admins @@ -1 +1,2 @@ -* *!pragma@unaffiliated/pragma/x-109842 50 pop +channel nick!user@host level password +* *!example@xyzcorp.com 50 5ecret5@uce diff --git a/pbot.pl b/pbot.pl index b13b16b4..e79db8fd 100755 --- a/pbot.pl +++ b/pbot.pl @@ -28,13 +28,11 @@ my %config = ( log_file => "$home/pbot/log", factoids_file => "$home/pbot/factoids", export_factoids_path => "$home/pbot/factoids.html", export_factoids_site => 'http://blackshell.com/~msmud/pbot2/factoids.html', - export_factoids_timeout => 300, # 5 minutes module_dir => "$home/pbot/modules", quotegrabs_file => "$home/pbot/quotegrabs", export_quotegrabs_path => "$home/pbot/quotegrabs.html", export_quotegrabs_site => 'http://blackshell.com/~msmud/pbot2/quotegrabs.html', - export_quotegrabs_timeout => 300, # 5 minutes ircserver => 'irc.freenode.net', botnick => 'pbot3',