summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatarWilliam Heimbigner <william.heimbigner@gmail.com>2007-06-06 21:30:25 +0000
committerLibravatarWilliam Heimbigner <william.heimbigner@gmail.com>2007-06-06 21:30:25 +0000
commit04c4d4ae0d6fdfe9e59a50e79b40047a08feefb7 (patch)
tree3db7aa17c73151e038f08e3cd0c1b2faffc0c482
parent110cb874fc58ae4a214889bb8d6545201a57dd2f (diff)
Added files
-rw-r--r--DEPS3
-rw-r--r--USAGE29
-rw-r--r--config-default/channels.xml148
-rw-r--r--config-default/commands.xml67
-rw-r--r--config-default/settings.xml36
-rw-r--r--config-default/users.xml9
-rwxr-xr-xmeta.pl96
-rw-r--r--modules/actions.pl48
-rw-r--r--modules/classes.pl124
-rw-r--r--modules/command.pl41
-rw-r--r--modules/event.pl264
-rw-r--r--modules/inspect.pl72
-rw-r--r--modules/log.pl38
-rw-r--r--modules/services.pl44
-rw-r--r--modules/util.pl165
-rw-r--r--modules/xml.pl39
16 files changed, 1223 insertions, 0 deletions
diff --git a/DEPS b/DEPS
new file mode 100644
index 0000000..5751458
--- /dev/null
+++ b/DEPS
@@ -0,0 +1,3 @@
+IO::All
+String::Interpolate
+Net::IRC
diff --git a/USAGE b/USAGE
new file mode 100644
index 0000000..11905d6
--- /dev/null
+++ b/USAGE
@@ -0,0 +1,29 @@
+a-access commands:
+ ;join <channel>
+ ;part <channel>[ :<reason>]
+ ;quit[ <reason>]
+ ;rehash
+ re-reads config files
+ ;rehash exempts
+ re-reads exempts.txt
+
+d-access commands:
+ ;sl <anything>
+ sends a raw line
+ ;ev <anything>
+ evaluates perl code
+
+t-access commands:
+ ;say <something>
+ says something in the channel where the command was given
+ ;do <something>
+
+o-access commands:
+ ;exempt <something>
+ prevents sending alerts if <something> matches the nick/ident/host
+ ;remove <nick>
+ removes nick from the channel
+
+always-granted commands:
+ !ops[ <message>]
+ gets op attention
diff --git a/config-default/channels.xml b/config-default/channels.xml
new file mode 100644
index 0000000..e738bfa
--- /dev/null
+++ b/config-default/channels.xml
@@ -0,0 +1,148 @@
+<channels>
+ <channel id="#wikimedia-commons" op="no">
+ <hilights>
+ <debug>White_cat</debug>
+ </hilights>
+ <msgs>
+ <debug>#wikimedia-ops</debug>
+ </msgs>
+ </channel>
+ <channel id="##windows" op="no">
+ <hilights>
+ <low>numist</low>
+ <low>quux</low>
+ <low>HentaiXP</low>
+ <low>TechSalvager</low>
+ <low>xyr</low>
+ <low>njan</low>
+ <low>JonathanD</low>
+ <low>Cpudan80</low>
+ </hilights>
+ </channel>
+ <channel id="##asb-meta" op="no" />
+ <channel id="##asb-nexus" op="when" />
+ <channel id="##asb-testing" op="when">
+ <hilights>
+ <low>a</low>
+ <low>b</low>
+ <low>c</low>
+ <low>d</low>
+ </hilights>
+ <msgs>
+ <debug>##asb-testing</debug>
+ </msgs>
+ </channel>
+ <channel id="##linux" op="no">
+ <hilights>
+ <low>WildPikachu</low>
+ <low>nalioth</low>
+ <low>xyr</low>
+ <low>quux</low>
+ <low>denny</low>
+ <low>Fieldy</low>
+ <low>numist</low>
+ <medium>PhilKC</medium>
+ </hilights>
+ <msgs>
+ <debug>##linux-ops</debug>
+ <low>WildPikachu</low>
+ </msgs>
+ <event id="no_ahbl" action="none" class="re" reason="placeholder" risk="debug" time="0" type="join" override="ahbl,no_ahbl">.*</event>
+ </channel>
+ <channel id="##linux-ops" op="no">
+ <hilights>
+ <opalert>WildPikachu</opalert>
+ <opalert>xyr</opalert>
+ <opalert>quux</opalert>
+ <opalert>denny</opalert>
+ <opalert>Fieldy</opalert>
+ <opalert>numist</opalert>
+ <opalert>PhilKC</opalert>
+ </hilights>
+ <msgs>
+ <opalert>##linux-ops</opalert>
+ </msgs>
+ </channel>
+ <channel id="#baadf00d" op="yes">
+ <hilights></hilights>
+ <msgs></msgs>
+ </channel>
+ <channel id="#wikipedia-en-roads-us" op="yes">
+ <hilights>
+ <debug>vishwin60</debug>
+ </hilights>
+ <msgs>
+ <debug>#wikimedia-ops</debug>
+ </msgs>
+ </channel>
+ <channel id="#religion" op="yes">
+ <hilights>
+ <debug>Shadow_mil</debug>
+ <debug>newmanbe</debug>
+ </hilights>
+ </channel>
+ <channel id="#wikimedia-ops" op="no">
+ <hilights></hilights>
+ <msgs></msgs>
+ </channel>
+ <channel id="#wikipedia" op="when">
+ <hilights>
+ <low>AppleBoy</low>
+ <low>xyr</low>
+ <low>Fabexplosive</low>
+ <low>Crazytales2</low>
+ </hilights>
+ <msgs>
+ <debug>#wikimedia-ops</debug>
+ </msgs>
+ <event id="autorem_2" action="fmod_wiki" class="re" reason="on chanserv autoremove" risk="info" time="0" type="raw" override="autoremove">^mode \S+ \+b (.*)$</event>
+ <event id="part_fags" action="ban" class="re" reason="parted with 'Fags!'" risk="low" time="0" type="part">.*Fags! Fags! Fags!.*</event>
+ </channel>
+ <channel id="#wikipedia-en" op="no">
+ <hilights>
+ <low>AppleBoy</low>
+ <low>Soms</low>
+ <low>xyr</low>
+ <debug>White_Cat</debug>
+ </hilights>
+ <msgs>
+ <debug>#wikimedia-ops</debug>
+ </msgs>
+ </channel>
+ <channel id="default" op="yes">
+ <event id="flood-15to45" action="none" class="floodqueue" reason="flooding 15 to 45" risk="low" time="0" type="public">15:45</event>
+ </channel>
+ <channel id="master">
+ <event id="ahbl" action="ban" class="dnsbl" reason="in ircbl.ahbl.org" risk="info" time="0" type="join">ircbl.ahbl.org</event>
+ <event id="autoremove" action="none" class="re" reason="on chanserv autoremove" risk="info" time="0" type="part">requested by ChanServ</event>
+ <event id="badurl1" action="ban" class="re" reason="sending spam url" risk="medium" time="0" type="public">tinyurl\.com/ypvk4n</event>
+ <event id="ctcp-dcc" action="ban" class="re" reason="ctcp-dcc" risk="high" time="0" type="cdcc">.*</event>
+ <event id="dcc" action="ban" class="re" override="dcc-medium" reason="using the DC.C SE.ND exploit" risk="high" time="0" type="public">^DCC SEND |\bDCC SEND &quot;?[A-Za-z0-9]+&quot;? \d+ \d+ \d+</event>
+ <event id="dcc-medium" action="ban" class="re" reason="using the DC.C SE.ND exploit" risk="medium" time="0" type="public">DCC SEND </event>
+ <event id="dcc-topic" action="ban" class="re" reason="setting a bad topic" risk="medium" time="0" type="topic">\bDCC SEND </event>
+ <event id="dosrequest" action="none" class="re" reason="requesting a DoS" risk="medium" time="0" type="public">via paypal if you DoS somone for</event>
+ <event id="exoticforum" action="ban" class="re" reason="link spam" risk="low" time="0" type="public">http://exotics\.ezbbforum\.com</event>
+ <event id="flood-10to30" action="none" class="floodqueue" reason="flooding (10 messages per 30 seconds)" risk="low" time="0" type="public">10:30</event>
+ <event id="flood-5to3" action="quiet" class="floodqueue" reason="flooding (5 messages per 3 seconds)" risk="low" time="10" type="public">5:3</event>
+ <event id="massflood" action="ban" class="splitflood" reason="distributed flooding" risk="medium" time="0" type="public,part,caction">5:3</event>
+ <event id="genspam1" action="none" class="re" reason="generic spamming" risk="debug" time="0" type="public">([^ ]{4,} +)\1{5,}</event>
+ <event id="joinflood-3to20" action="none" class="floodqueue" reason="join flood (3 joins in 20 seconds)" risk="medium" time="0" type="join">3:20</event>
+ <event id="keylogger" action="ban" class="re" override="keylogger-medium" reason="using the norton start-key-logger exploit" risk="high" time="0" type="public">^startkeylogger$|^stopkeylogger$</event>
+ <event id="keylogger-medium" action="ban" class="re" reason="using the norton start-key-logger exploit" risk="medium" time="0" type="public">\bstartkeylogger\b|\bstopkeylogger\b</event>
+ <event id="last_measure_regex" action="kban" class="re" reason="posting what appears to be a last measure link" risk="medium" time="0" type="public">http://\S+\.on\.nimp\.org</event>
+ <event id="lesbian" action="ban" class="re" reason="bad nick match (lesbian..)" risk="medium" time="0" type="join">^lesbian..$</event>
+ <event id="nick_cupid" action="ban" class="re" reason="bad nick match (cupid..)" risk="medium" time="0" type="join">^cupid..$</event>
+ <event id="nick_mudkip" action="ban" class="re" reason="bad nick match (mudkip..)" risk="medium" time="0" type="join">MUDKIP..</event>
+ <event id="nickspam" action="ban" class="nickspam" reason="nickspamming" risk="high" time="0" type="public">150:20</event>
+ <event id="notice" action="ban" class="re" reason="sending a notice to the channel" risk="high" time="0" type="notice">.*</event>
+ <event id="sms_spam" action="none" class="re" reason="spam link / virus" risk="low" time="0" type="public">\.com/sms.exe</event>
+ <event id="dronebl" action="ban" class="dnsbl" reason="in dnsbl.dronebl.org" risk="info" time="0" type="join" override="ahbl">dnsbl.dronebl.org</event>
+ <event id="redarmyoflol" action="ban" class="re" reason="parting with 'red army of lol'" risk="low" time="0" type="part">RED ARMY OF LOL</event>
+ <hilights>
+ <debug>AfterDeath</debug>
+ </hilights>
+ <msgs>
+ <debug>##asb-nexus</debug>
+ </msgs>
+ </channel>
+</channels>
diff --git a/config-default/commands.xml b/config-default/commands.xml
new file mode 100644
index 0000000..4d543c9
--- /dev/null
+++ b/config-default/commands.xml
@@ -0,0 +1,67 @@
+<commands>
+ <command cmd="^;join (.*)" flag="a">
+ <![CDATA[
+ $conn->join($1);
+ ]]>
+ </command>
+ <command cmd="^;part (.*)" flag="a">
+ <![CDATA[
+ $conn->part($1);
+ ]]>
+ </command>
+ <command cmd="^;sl (.*)" flag="d">
+ <![CDATA[
+ $conn->sl($1);
+ ]]>
+ </command>
+ <command cmd="^;quit ?(.*)" flag="a">
+ <![CDATA[
+ $conn->quit($1);
+ ]]>
+ </command>
+ <command cmd="^;ev (.*)" flag="d">
+ <![CDATA[
+ eval $1; warn $@ if $@;
+ ]]>
+ </command>
+ <command cmd="^;rehash$" flag="a">
+ <![CDATA[
+ readXML();
+ $conn->privmsg($event->{to}->[0], 'config files were re-read');
+ ]]>
+ </command>
+ <command cmd="^;say (.*)" flag="t">
+ <![CDATA[
+ $conn->privmsg($event->{to}->[0], $1);
+ ]]>
+ </command>
+ <command cmd="^;do (.*)" flag="t">
+ <![CDATA[
+ $conn->me($event->{to}->[0], $1);
+ ]]>
+ </command>
+ <command cmd="^;exempt (.*)" flag="o">
+ <![CDATA[
+ push(@::eline, $1);
+ "$1\n" >> io 'exempt.txt';
+ $conn->privmsg($event->{to}->[0], "$1 exempted");
+ ]]>
+ </command>
+ <command cmd="^\!ops *(.*)">
+ <![CDATA[
+ my $hilite=commaAndify(getAlert($event->{to}->[0], 'opalert', 'hilights'));
+ $conn->privmsg($_, "$hilite: $event->{nick} wants your attention ($1) ") foreach getAlert($event->{to}->[0], 'opalert', 'msgs');
+ ]]>
+ </command>
+ <command cmd="^;re(load|hash) exempts" flag="a">
+ <![CDATA[
+ @eline=io('exempt.txt')->getlines;
+ chomp @eline;
+ ]]>
+ </command>
+ <command cmd="^;remove (.*)$" flag="o">
+ <![CDATA[
+ $conn->sl("remove $event->{to}->[0] $1");
+ ]]>
+ </command>
+</commands>
diff --git a/config-default/settings.xml b/config-default/settings.xml
new file mode 100644
index 0000000..c7e693f
--- /dev/null
+++ b/config-default/settings.xml
@@ -0,0 +1,36 @@
+<settings>
+ <altnicks>
+ <altnick>AntiSpamMeta_</altnick>
+ <altnick>AntiSpamMeta2</altnick>
+ </altnicks>
+ <autojoins>
+ <autojoin>##asb-nexus</autojoin>
+ <autojoin>##asb-testing</autojoin>
+ <autojoin>##asb-meta</autojoin>
+ <autojoin>#baadf00d</autojoin>
+ <autojoin>##linux</autojoin>
+ <autojoin>##linux-ops</autojoin>
+ <autojoin>#wikipedia</autojoin>
+ <autojoin>#wikimedia-ops</autojoin>
+ <autojoin>#wikipedia-en</autojoin>
+ <autojoin>#religion</autojoin>
+ <autojoin>#wikipedia-en-roads-us</autojoin>
+ <autojoin>##windows</autojoin>
+ <autojoin>#wikimedia-commons</autojoin>
+ </autojoins>
+ <log>
+ <dir>logs/$chan</dir>
+ <filefmt>$chan-%m-%d-%Y.log</filefmt>
+ <timefmt>%B %d %T </timefmt>
+ <zone>GMT</zone>
+ </log>
+ <nick>AntiSpamMeta</nick>
+ <pass></pass>
+ <port>6667</port>
+ <realname>I am a new AntiSpamBot in the making.</realname>
+ <server>
+ <host>irc.freenode.net</host>
+ </server>
+ <username>MetaBot</username>
+ <lookupexpire>2419200</lookupexpire>
+</settings>
diff --git a/config-default/users.xml b/config-default/users.xml
new file mode 100644
index 0000000..7fcdd3b
--- /dev/null
+++ b/config-default/users.xml
@@ -0,0 +1,9 @@
+<people>
+ <person id="afterdeath" flags="oda" host="atheme/troll/about.linux.afterdeath" />
+ <person id="bumm13" flags="o" host="IDENTIFY" />
+ <person id="filiated" flags="oda" host="IDENTIFY" />
+ <person id="ocee" host="unaffiliated/ocee" />
+ <person id="wildpikachu" host="about/linux/staff/wildpikachu" />
+ <person id="slowking_man" host="IDENTIFY" flags="o" />
+ <person id="crazytales2" host="IDENTIFY" flags="oda" />
+</people>
diff --git a/meta.pl b/meta.pl
new file mode 100755
index 0000000..42b30ab
--- /dev/null
+++ b/meta.pl
@@ -0,0 +1,96 @@
+#!/usr/bin/perl -w
+
+use lib '/home/icxcnika/AntiSpamMeta';
+#use lib '/home/wheimbigner/perl/lib/perl5/site_perl/5.8.8';
+use warnings;
+use strict;
+use Net::IRC;
+use Data::Dumper;
+use IO::All;
+use Getopt::Long;
+use POSIX qw(strftime);
+
+@::eline=();
+$::pass = '';
+
+my @modules = qw/Xml Util Inspect Services Log Command Event Classes Actions/;
+
+require 'modules/' . lc $_ . '.pl' foreach @modules;
+
+sub rePlug
+{
+ my ($conn) = @_;
+ foreach (@modules) {
+ eval $_ . '::killsub();';
+ warn $@ if $@;
+ eval 'undef &' . $_ . '::killsub;';
+ warn $@ if $@;
+ delete $INC{'modules/' . lc $_ . '.pl'};
+ require 'modules/' . lc $_ . '.pl';
+ }
+ registerHandlers($conn); # this is necessary in case event.pl has changed
+ # because handlers are registered via pointers
+}
+
+sub init {
+ my ( $conn, $host );
+ my $debug = 0;
+ my $irc = new Net::IRC;
+ $::cset = '';
+ GetOptions( 'debug|d!' => \$debug,
+ 'pass|p:s' => \$::pass,
+ 'config|c:s' => \$::cset
+ );
+ readXML();
+ $::pass = $::settings->{pass} if $::pass eq '';
+ $host = ${$::settings->{server}}[rand @{$::settings->{server}}];
+ print "Connecting to $host\n";
+ $irc->debug($debug);
+ $conn = $irc->newconn( Server => $host,
+ Port => $::settings->{port} || '6667',
+ Nick => $::settings->{nick},
+ Ircname => $::settings->{realname},
+ Username => $::settings->{username},
+ Password => $::pass,
+ Pacing => 1 );
+ $conn->debug($debug);
+ registerHandlers($conn);
+ @::eline=io('exempt.txt')->getlines;
+ chomp @::eline;
+ $irc->start();
+}
+
+sub registerHandlers {
+ my ($conn) = @_;
+ print "Installing handler routines...\n";
+ $conn->add_default_handler(\&blah);
+ $conn->add_handler('bannedfromchan', \&on_bannedfromchan);
+ $conn->add_handler('mode', \&on_mode);
+ $conn->add_handler('join', \&on_join);
+ $conn->add_handler('part', \&on_part);
+ $conn->add_handler('quit', \&on_quit);
+ $conn->add_handler('nick', \&on_nick);
+ $conn->add_handler('notice', \&on_notice);
+ $conn->add_handler('caction', \&on_public);
+ $conn->add_handler('msg', \&on_msg);
+ $conn->add_handler('namreply', \&on_names);
+ $conn->add_handler('endofnames', \&on_names);
+ $conn->add_handler('public', \&on_public);
+ $conn->add_handler('376', \&on_connect);
+ $conn->add_handler('topic', \&irc_topic);
+ $conn->add_handler('topicinfo', \&irc_topic);
+ $conn->add_handler('nicknameinuse', \&on_errnickinuse);
+ $conn->add_handler('kick', \&on_kick);
+ $conn->add_handler('cping', \&on_ctcp);
+ $conn->add_handler('cversion', \&on_ctcp);
+ $conn->add_handler('csource', \&on_ctcp);
+ $conn->add_handler('ctime', \&on_ctcp);
+ $conn->add_handler('cdcc', \&on_ctcp);
+ $conn->add_handler('cuserinfo', \&on_ctcp);
+ $conn->add_handler('cclientinfo', \&on_ctcp);
+ $conn->add_handler('cfinger', \&on_ctcp);
+ $conn->add_handler('320', \&whois_identified);
+ $conn->add_handler('318', \&whois_end);
+}
+
+init();
diff --git a/modules/actions.pl b/modules/actions.pl
new file mode 100644
index 0000000..7d6717d
--- /dev/null
+++ b/modules/actions.pl
@@ -0,0 +1,48 @@
+use strict;
+use warnings;
+
+#package Actions;
+
+sub Actions::ban {
+ our ($conn, $event, $unmode, $chan, %dct, $id);
+ o_send( $conn, "mode $chan +b *!*\@$event->{host}" );
+ $unmode="mode $chan -b *!*\@$event->{host}";
+}
+
+sub Actions::kban {
+ our ($conn, $event, $unmode, $chan, %dct, $id);
+ o_send($conn, "mode $chan +b *!*\@$event->{host}");
+ o_send($conn, "kick $chan $event->{nick} :$dct{$id}{reason}");
+ $unmode = "mode $chan -b *!*\@$event->{host}";
+}
+
+sub Actions::kick {
+ our ($conn, $event, $unmode, $chan, %dct, $id);
+ o_send($conn, "kick $chan $event->{nick} :$dct{$id}{reason}");
+}
+
+sub Actions::none {
+ return;
+}
+
+sub Actions::quiet {
+ our ($conn, $event, $unmode, $chan, %dct, $id);
+ o_send( $conn, "mode $chan +b %*!*\@$event->{host}" );
+ $unmode = "mode $chan -b %*!*\@$event->{host}";
+}
+
+sub Actions::fmod_wiki {
+ our ($conn, $event, $unmode, $chan, %dct, $id);
+ o_send( $conn, "mode $chan -b *!*\@$event->{host}" );
+ o_send( $conn, "mode $chan +b *!*\@$event->{host}!#wikimedia-ops" );
+}
+
+sub Actions::killsub {
+ undef &Actions::ban;
+ undef &Actions::kban;
+ undef &Actions::kick;
+ undef &Actions::none;
+ undef &Actions::quiet;
+}
+
+return 1;
diff --git a/modules/classes.pl b/modules/classes.pl
new file mode 100644
index 0000000..74b2899
--- /dev/null
+++ b/modules/classes.pl
@@ -0,0 +1,124 @@
+use strict;
+use warnings;
+use Data::Dumper;
+#package Classes;
+
+sub Classes::dnsbl {
+ our (%aonx, $id, %dct, $event, $chan, $rev);
+ if (defined $rev) {
+ my $iaddr = hostip( "$rev$aonx{$id}{content}" );
+ my @dnsbl = unpack( 'C4', $iaddr ) if defined $iaddr;
+ $dct{$id} = $aonx{$id} if (@dnsbl);
+ }
+}
+
+sub Classes::floodqueue {
+ our (%aonx, $id, %dct, $event, $chan);
+ my @cut=split(/:/, $aonx{$id}{content});
+ $dct{$id} = $aonx{$id} if ( flood_add( $chan, $id, $event->{host}, int($cut[1]) ) == int($cut[0]) );
+}
+
+sub Classes::nickspam {
+ our (%aonx, $id, %dct, $event, $chan);
+ my @cut = split(/:/, $aonx{$id}{content});
+ if ( length $event->{args}->[0] >= int($cut[0]) ) {
+ %_ = map { $_=>$_ } lc keys %{$::sc{lc $chan}{users}};
+ my @uniq = grep( $_{$_}, split( / /, lc $event->{args}->[0]) );
+ $dct{$id} = $aonx{$id} if ( $#{ @uniq } >= int($cut[1]) );
+ }
+}
+
+my %cf=();
+my %bs=();
+
+sub Classes::splitflood {
+ our (%aonx, $id, %dct, $event, $chan);
+ my $text;
+ my @cut = split(/:/, $aonx{$id}{content});
+ $cf{$id}{timeout}=int($cut[1]);
+ if ($event->{type} =~ /^(public|notice|part|caction)$/) {
+ $text=$event->{args}->[0];
+ }
+ return unless defined($text);
+ return unless length($text) >= 10;
+ if (defined($bs{$id}{$text}) && (time <= $bs{$id}{$text} + 600)) {
+ $dct{$id}=$aonx{$id};
+ return;
+ }
+ push( @{$cf{$id}{$chan}{$text}}, time );
+ foreach my $nid ( keys %cf ) {
+ foreach my $xchan ( keys %{$cf{$nid}} ) {
+ next if $xchan eq 'timeout';
+ foreach my $host ( keys %{$cf{$nid}{$xchan}} ) {
+ next unless defined $cf{$nid}{$xchan}{$host}[0];
+ while ( time >= $cf{$nid}{$xchan}{$host}[0] + $cf{$nid}{'timeout'} ) {
+ last if ( $#{ $cf{$nid}{$xchan}{$host} } == 0 );
+ shift ( @{$cf{$nid}{$xchan}{$host}} );
+ }
+ }
+ }
+ }
+ if ( $#{ @{$cf{$id}{$chan}{$text}}}+1 == int($cut[0]) ) {
+ $dct{$id}=$aonx{$id};
+ $bs{$id}{$text} = time;
+ }
+}
+
+sub Classes::re {
+ our (%aonx, $id, %dct, $event, $chan);
+ my $match = $event->{args}->[0];
+ $match = $event->{nick} if ($event->{type} eq 'join');
+ if ( (defined $aonx{$id}{nocase}) && ($aonx{$id}{nocase}) ) {
+ $dct{$id}=$aonx{$id} if ($match =~ /$aonx{$id}{content}/i);
+ }
+ else {
+ $dct{$id}=$aonx{$id} if ($match =~ /$aonx{$id}{content}/);
+ }
+}
+
+sub Classes::nick {
+ our (%aonx, $id, %dct, $event, $chan);
+ if ( lc $event->{nick} eq lc $aonx{$id}{content} ) {
+ $dct{$id} = $aonx{$id};
+ }
+}
+
+sub Classes::ident {
+ our (%aonx, $id, %dct, $event, $chan);
+ if ( lc $event->{user} eq lc $aonx{$id}{content} ) {
+ $dct{$id} = $aonx{$id};
+ }
+}
+
+sub Classes::host {
+ our (%aonx, $id, %dct, $event, $chan);
+ if ( lc $event->{host} eq lc $aonx{$id}{content} ) {
+ $dct{$id} = $aonx{$id};
+ }
+}
+
+sub Classes::killsub {
+ undef &Classes::dnsbl;
+ undef &Classes::floodqueue;
+ undef &Classes::nickspam;
+ undef &Classes::re;
+}
+#$VAR1 = bless( {
+# 'to' => [
+# '##asb-testing'
+# ],
+# 'format' => 'mode',
+# 'from' => 'ChanServ!ChanServ@services.',
+# 'user' => 'ChanServ',
+# 'args' => [
+# '+o',
+# 'AntiSpamMetaBeta',
+# ''
+# ],
+# 'nick' => 'ChanServ',
+# 'type' => 'mode',
+# 'userhost' => 'ChanServ@services.',
+# 'host' => 'services.'
+# }, 'Net::IRC::Event' );
+
+return 1;
diff --git a/modules/command.pl b/modules/command.pl
new file mode 100644
index 0000000..87c768f
--- /dev/null
+++ b/modules/command.pl
@@ -0,0 +1,41 @@
+use warnings;
+use strict;
+
+sub do_command
+{
+ my ($conn, $event) = @_;
+ my $args = $event->{args}->[0];
+ my $from = $event->{from};
+ my $cmd = $args;
+ my $d1;
+ my $nick = lc $event->{nick};
+ foreach my $command ( @{$::commands->{command}} )
+ {
+ if (defined($command->{flag})) {
+ next unless defined($::xusers->{$nick});
+ next unless defined($::xusers->{$nick}->{flags});
+ next unless defined(grep {$_ eq $command->{flag}} split('', $::xusers->{$nick}->{flags}));
+ if ($::xusers->{$nick}->{host} ne 'IDENTIFY') {
+ next unless leq($::xusers->{$nick}->{host}, $event->{host});
+ }
+ else {
+ if ( $cmd =~ /$command->{cmd}/ ){
+ push (@{$::idqueue{$nick}}, [$cmd, $command, $event]);
+ $conn->sl("whois $nick $nick");
+ next;
+ }
+ }
+ }
+ if ($cmd=~/$command->{cmd}/) {
+ print "$event->{from} told me: $cmd \n";
+ eval $command->{content};
+ warn $@ if $@;
+ }
+ }
+}
+
+sub Command::killsub {
+ undef &do_command;
+}
+
+return 1;
diff --git a/modules/event.pl b/modules/event.pl
new file mode 100644
index 0000000..77583e4
--- /dev/null
+++ b/modules/event.pl
@@ -0,0 +1,264 @@
+use warnings;
+use strict;
+
+use Text::LevenshteinXS qw(distance);
+
+sub on_connect {
+ my ($conn, $event) = @_; # need to check for no services
+ $conn->privmsg( 'NickServ', "ghost $::settings->{nick} $::pass" ) if lc $event->{args}->[0] ne lc $::settings->{nick};
+}
+
+my @leven = ();
+
+sub on_join {
+ my ($conn, $event) = @_;
+ my $nick = lc $event->{nick};
+ my $chan = lc $event->{to}->[0];
+ if ( leq($conn->{_nick}, $nick) ) {
+ $::sc{$chan} = {};
+ $conn->privmsg('ChanServ', "op $chan" ) if (defined cs($chan)->{op}) && (cs($chan)->{op} eq 'yes');
+ }
+ $::sc{$chan}{users}{$nick} = {};
+ $::sc{$chan}{users}{$nick}{hostmask} = $event->{userhost};
+ $::sc{$chan}{users}{$nick}{op} = 0;
+ $::sc{$chan}{users}{$nick}{voice} = 0;
+ inspect( $conn, $event );
+ logg( $event );
+ if ( $#leven ne -1 ) {
+ my $ld = ( ( maxlen($nick, $leven[0]) - distance($nick, $leven[0]) ) / maxlen($nick, $leven[0]) );
+ my $mx = $leven[0];
+ foreach my $item ( @leven ) {
+ next if $nick eq $item; # avoid dups
+ my $tld = ( ( maxlen($nick, $item) - distance($nick, $item) ) / maxlen($nick, $item) );
+ if ($tld > $ld) {
+ $ld = $tld;
+ $mx = $item;
+ }
+ }
+ print "Best match for $nick was $mx with $ld\n"
+ }
+ push(@leven, $nick);
+ shift @leven if $#leven > 5;
+}
+
+sub on_part
+{
+ my ($conn, $event) = @_;
+ inspect( $conn, $event );
+ my $nick = lc $event->{nick};
+ logg( $event );
+ if ( leq( $conn->{_nick}, $nick ) )
+ {
+ delete( $::sc{lc $event->{to}->[0]} );
+ }
+ else
+ {
+ delete( $::sc{lc $event->{to}->[0]}{users}{$nick} );
+ }
+}
+
+sub on_msg
+{
+ my ($conn, $event) = @_;
+ do_command ($conn, $event)
+}
+
+sub on_public
+{
+ my ($conn, $event) = @_;
+ inspect( $conn, $event );
+ logg( $event );
+ do_command( $conn, $event )
+}
+
+sub on_notice
+{
+ my ($conn, $event) = @_;
+ inspect( $conn, $event );
+ logg( $event );
+ doServices($conn, $event);
+}
+
+sub on_errnickinuse
+{
+ my ($conn, $event) = @_;
+ $_ = ${$::settings->{altnicks}}[rand @{$::settings->{altnicks}}];
+ print "Nick is in use, trying $_\n";
+ $conn->nick($_);
+}
+
+sub on_quit
+{
+ my ($conn, $event) = @_;
+ my @channels=();
+ for ( keys %::sc ) {
+ push ( @channels, $_ ) if delete $::sc{lc $_}{users}{lc $event->{nick}};
+ }
+ $event->{to} = \@channels;
+ inspect( $conn, $event );
+ logg ( $event );
+}
+
+sub blah
+{
+ my ($self, $event) = @_;
+ inspect($self, $event);
+}
+
+sub irc_users
+{
+ my ( $channel, @users ) = @_;
+ for (@users)
+ {
+ my ( $op, $voice );
+ $op = 0; $voice = 0;
+ $op = 1 if s/^\@//;
+ $voice = 1 if s/^\+//;
+ $::sc{lc $channel}{users}{lc $_} = {};
+ $::sc{lc $channel}{users}{lc $_}{op} = $op;
+ $::sc{lc $channel}{users}{lc $_}{voice} = $voice;
+ }
+}
+
+sub on_names {
+ my ($conn, $event) = @_;
+ irc_users( $event->{args}->[2], split(/ /, $event->{args}->[3]) ) if ($event->{type} eq 'namreply');
+}
+
+sub irc_topic {
+ my ($conn, $event) = @_;
+ inspect($conn, $event) if ($event->{format} ne 'server');
+ if ($event->{format} eq 'server')
+ {
+ if ($event->{type} eq 'topic')
+ {
+ $::sc{lc $event->{args}->[1]}{topic}{text} = $event->{args}->[2];
+ }
+ elsif ($event->{type} eq 'topicinfo')
+ {
+ $::sc{lc $event->{args}->[1]}{topic}{time} = $event->{args}->[3];
+ $::sc{lc $event->{args}->[1]}{topic}{by} = $event->{args}->[2];
+ }
+ }
+ else
+ {
+ if ($event->{type} eq 'topic')
+ {
+ $::sc{lc $event->{to}->[0]}{topic}{text} = $event->{args}->[0];
+ }
+ logg($event);
+ }
+}
+
+sub on_nick {
+ my ($conn, $event) = @_;
+ my @channels=();
+ for ( keys %::sc )
+ {
+ if ( defined $::sc{lc $_}{users}{lc $event->{nick}} )
+ {
+ $::sc{lc $_}{users}{lc $event->{args}->[0]} = $::sc{lc $_}{users}{lc $event->{nick}};
+ delete( $::sc{lc $_}{users}{lc $event->{nick}} );
+ push ( @channels, lc $_ );
+ }
+ }
+ $event->{to} = \@channels;
+ inspect($conn, $event);
+ logg($event)
+}
+
+sub on_kick {
+ my ($conn, $event) = @_;
+ if (lc $event->{to}->[0] eq lc $::settings->{nick}) {
+ $conn->join($event->{args}->[0]);
+ }
+ logg( $event );
+}
+
+sub on_mode
+{
+ my ($conn, $event) = @_;
+ my $chan = lc $event->{to}->[0];
+ if ($chan =~ /^#/) {
+ my @modes = @{parse_modes($event->{args})};
+ foreach my $line ( @modes ) {
+ my @ex = @{$line};
+ if ( $ex[0] eq '+o' ) {
+ $::sc{$chan}{users}{lc $ex[1]}{op}=1;
+ if (lc $ex[1] eq lc $::settings->{nick}) {
+ doQueue($conn, $chan);
+ if ( $::channels->{channel}->{$chan}->{op} eq "when" ) {
+ $conn->schedule(600, sub { print "Deop timer called!\n"; $conn->privmsg('ChanServ', "op $chan -". $::settings->{nick})});
+ }
+ }
+ }
+ elsif ( $ex[0] eq '-o' ) {
+ $::sc{$chan}{users}{lc $ex[1]}{op}=0;
+ }
+ elsif ( $ex[0] eq '+v' ) {
+ $::sc{$chan}{users}{lc $ex[1]}{voice}=1;
+ }
+ elsif ( $ex[0] eq '-v' ) {
+ $::sc{$chan}{users}{lc $ex[1]}{voice}=0;
+ }
+ }
+ logg($event);
+ }
+}
+
+sub on_ctcp
+{
+ my ($conn, $event) = @_;
+ inspect($conn, $event);
+}
+
+sub whois_identified {
+ my ($conn, $event2) = @_;
+ my $who = lc $event2->{args}->[1];
+ if ( (defined( $::idqueue{$who} )) && ( @{$::idqueue{$who}} ) ) {
+ foreach my $item (@{$::idqueue{$who}}) {
+ my ($cmd, $command, $event) = @{$item};
+ if ( $cmd =~ /$command->{cmd}/ ){
+ print "$event->{from} told me $cmd \n";
+ eval $command->{content};
+ warn $@ if $@;
+ }
+ }
+ $::idqueue{$who} = [];
+ }
+}
+
+sub whois_end {
+ my ($conn, $event) = @_;
+ my $who = lc $event->{args}->[1];
+ $::idqueue{$who} = [];
+}
+
+sub on_bannedfromchan {
+ my ($conn, $event) = @_;
+ $conn->privmsg('ChanServ', "unban $event->{args}->[1]");
+}
+
+sub Event::killsub {
+ undef &on_connect;
+ undef &on_join;
+ undef &on_part;
+ undef &on_msg;
+ undef &on_notice;
+ undef &on_errnickinuse;
+ undef &on_quit;
+ undef &on_names;
+ undef &on_nick;
+ undef &on_kick;
+ undef &on_mode;
+ undef &on_ctcp;
+ undef &on_bannedfromchan;
+ undef &blah;
+ undef &irc_users;
+ undef &irc_topic;
+ undef &whois_identified;
+ undef &whois_end;
+ undef &on_public;
+}
+
+return 1;
diff --git a/modules/inspect.pl b/modules/inspect.pl
new file mode 100644
index 0000000..3837ba9
--- /dev/null
+++ b/modules/inspect.pl
@@ -0,0 +1,72 @@
+use warnings;
+use strict;
+
+use List::Util qw(first);
+
+#my @ignored = ();
+
+sub inspect {
+ our ($conn, $event) = @_;
+ my (%conx, %monx);
+ our (%aonx, %dct, $rev, $chan, $id);
+ %aonx=(); %dct=(); $rev=""; $chan=""; $id="";
+ my (@dnsbl, @unpakt, @uniq, @cut);
+ my ($match, $txtz, $iaddr);
+ my @override = [];
+ our $unmode='';
+ return if (defined(first { ( lc $event->{nick} eq lc $_ ) } @::eline));
+ return if (defined(first { ( lc $event->{user} eq lc $_ ) } @::eline));
+ return if (defined(first { ( lc $event->{host} eq lc $_ ) } @::eline));
+ $iaddr = hostip($event->{host});
+ $rev = join('.', reverse(unpack('C4', $iaddr))).'.' if (defined $iaddr);
+ %monx = defined($::channels->{channel}->{master}->{event}) ? %{$::channels->{channel}->{master}->{event}} : ();
+ ## NB: isn't there a better way to do this with grep, somehow?
+# foreach ( @ignored ) {
+# return if (lc $event->{nick} eq $_);
+# }
+ foreach $chan ( @{$event->{to}} ) {
+ next unless $chan =~ /^#/;
+ %conx = defined($::channels->{channel}->{lc $chan}->{event}) ? %{$::channels->{channel}->{lc $chan}->{event}} : ();
+ %aonx = (%monx, %conx);
+ foreach $id (keys %aonx) {
+ next unless ( defined(first { lc $_ eq $event->{type} } split(/[,:; ]+/, $aonx{$id}{type}) ) )
+ || ( lc $event->{type} eq lc $aonx{$id}{type} );
+# next unless ( defined($::classes->{class}->{$aonx{$id}{class}}));
+ eval "Classes::" . $aonx{$id}{class} . "();";
+ warn $@ if $@;
+ }
+ }
+ foreach ( keys %dct ) {
+ push( @override, split( /[ ,;]+/, $dct{$_}{override} ) ) if ( defined $dct{$_}{override} );
+ }
+ delete $dct{$_} foreach @override;
+ foreach $chan (@{$event->{to}}) {
+ foreach $id ( keys %dct ) {
+ $txtz = "$dct{$id}{risk} risk threat: ".
+ "Detected $event->{nick} $dct{$id}{reason} in $chan ";
+ $txtz = $txtz . commaAndify(getAlert(lc $chan, $dct{$id}{risk}, 'hilights')) if (getAlert(lc $chan, $dct{$id}{risk}, 'hilights'));
+ if (cs(lc $chan)->{op} ne 'no') {
+ if ($event->{type} eq 'topic') { #restore old topic
+ my $oldtopic = $::sc{lc $event->{to}->[0]}{topic}{text};
+ o_send( $conn, "topic $chan :$oldtopic");
+ o_send( $conn, "mode $chan +t");
+ }
+ eval "Actions::" . $dct{$id}{action} . "();";
+ warn $@ if $@;
+ my $lconn=$conn; my $lunmode = $unmode;
+ if ((int($dct{$id}{time}) ne 0) && ($unmode ne '')) {
+ $conn->schedule(int($dct{$id}{time}), sub { print "Timer called!\n"; o_send($lconn,$lunmode); });
+ }
+ }
+ $conn->privmsg($_, $txtz) foreach getAlert($chan, $dct{$id}{risk}, 'msgs');
+# push(@ignored, lc $event->{nick});
+# $conn->schedule(10, sub { @ignored = grep { $_ ne lc $event->{nick} } @ignored; });
+ }
+ }
+}
+
+sub Inspect::killsub {
+ undef &inspect;
+}
+
+return 1;
diff --git a/modules/log.pl b/modules/log.pl
new file mode 100644
index 0000000..6381110
--- /dev/null
+++ b/modules/log.pl
@@ -0,0 +1,38 @@
+use warnings;
+use strict;
+
+use String::Interpolate qw(interpolate);
+
+sub logg
+{
+ my ($event) = @_;
+ my @chans = @{$event->{to}};
+ my $fh;
+ @chans = ( $event->{args}->[0] ) if ($event->{type} eq 'kick');
+ my @time = ($::settings->{log}->{zone} eq 'local') ? localtime : gmtime;
+ foreach my $chan ( @chans)
+ {
+ $chan = lc $chan;
+ io(interpolate($::settings->{log}->{dir}))->mkpath;
+ $_='';
+ $_ = "<$event->{nick}> $event->{args}->[0]" if $event->{type} eq 'public';
+ $_ = "*** $event->{nick} has joined $chan" if $event->{type} eq 'join';
+ $_ = "*** $event->{nick} has left $chan" if $event->{type} eq 'part';
+ $_ = "* $event->{nick} $event->{args}->[0]" if $event->{type} eq 'caction';
+ $_ = "*** $event->{nick} is now known as $event->{args}->[0]" if $event->{type} eq 'nick';
+ $_ = "*** $event->{nick} has quit IRC" if $event->{type} eq 'quit';
+ $_ = "*** $event->{to}->[0] was kicked by $event->{nick}" if $event->{type} eq 'kick';
+ $_ = "-$event->{nick}- $event->{args}->[0]" if $event->{type} eq 'notice';
+ $_ = "*** $event->{nick} sets mode: ".join(" ",@{$event->{args}}) if $event->{type} eq 'mode';
+ $_ = "*** $event->{nick} changes topic to \"$event->{args}->[0]\"" if $event->{type} eq 'topic';
+ print Dumper($event) if ($_ eq '');
+ $_ = interpolate(strftime($::settings->{log}->{timefmt}, @time)) . $_ . "\n" unless $_ eq '';
+ $_ >> io(interpolate($::settings->{log}->{dir}).'/'.interpolate(strftime($::settings->{log}->{filefmt}, @time))) unless ($_ eq '');
+ }
+}
+
+sub Log::killsub {
+ undef &logg;
+}
+
+return 1;
diff --git a/modules/services.pl b/modules/services.pl
new file mode 100644
index 0000000..d110b60
--- /dev/null
+++ b/modules/services.pl
@@ -0,0 +1,44 @@
+use warnings;
+use strict;
+
+sub doServices {
+ my ($conn, $event) = @_;
+ if ($event->{from} eq 'NickServ!NickServ@services.')
+ {
+ print "NickServ: $event->{args}->[0]\n";
+ if ( $event->{args}->[0] eq 'This nickname is owned by someone else' )
+ {
+ $conn->privmsg( 'NickServ', "identify $::pass" );
+ }
+ elsif ( $event->{args}->[0] eq 'Password accepted - you are now recognized' )
+ {
+ $conn->join($_) foreach ( @{$::settings->{autojoins}} );
+ }
+ elsif ($event->{args}->[0] =~ /has been killed$/ )
+ {
+ $conn->nick( $::settings->{nick} );
+ }
+ elsif ($event->{args}->[0] =~ /Password Incorrect/ )
+ {
+ $conn->join($_) foreach ( @{$::settings->{autojoins}} );
+ }
+ }
+ elsif ($event->{from} eq 'ChanServ!ChanServ@services.')
+ {
+ print "ChanServ: $event->{args}->[0] \n";
+ if ($event->{args}->[0] =~ /You are already opped on \[.(.*).\]/)
+ {
+ doQueue($conn, $1);
+ }
+ elsif ( $event->{args}->[0] =~ /^All.*bans matching.*have been cleared on(.*)/)
+ {
+ $conn->join($1);
+ }
+ }
+}
+
+sub Services::killsub {
+ undef &doServices;
+}
+
+return 1;
diff --git a/modules/util.pl b/modules/util.pl
new file mode 100644
index 0000000..060eee0
--- /dev/null
+++ b/modules/util.pl
@@ -0,0 +1,165 @@
+#warning: if you add a function, put it into killsub!
+
+use warnings;
+use strict;
+
+my %sf;
+my %oq;
+
+my %RISKS =
+(
+ 'debug' => 10,
+ 'info' => 20,
+ 'low' => 30,
+ 'medium' => 40,
+ 'high' => 50,
+ 'opalert'=> 9001 #OVER NINE THOUSAND!!!
+);
+#leaves room for more levels if for some reason we end up needing more
+#theoretically, you should be able to change those numbers without any damage
+
+sub maxlen {
+ my ($a, $b) = @_;
+ my ($la, $lb) = (length($a), length($b));
+ return $la if ($la > $lb);
+ return $lb;
+}
+
+#cs: returns the xml settings for the specified chan, or default if there aren't any settings for that chan
+sub cs {
+ my ($chan) = @_;
+ $chan = lc $chan;
+ return $::channels->{channel}->{$chan} if ( defined($::channels->{channel}->{$chan}) );
+ return $::channels->{channel}->{default};
+}
+
+#this item is a stub, dur
+sub hostip {
+ return gethostbyname($_[0]);
+}
+
+# Send something that requires ops
+sub o_send {
+ my ( $conn, $send ) = @_;
+ my @splt = split(/ /, $send);
+ my $chan = lc $splt[1];
+ $oq{$chan} = [] unless defined($oq{$chan});
+ if ( cs($chan)->{op} ne 'no' ) {
+ print Dumper(lc $::settings->{nick}, $::sc{$chan}{users}{lc $::settings->{nick}});
+ print Dumper($send, $chan);
+ if ( $::sc{$chan}{users}{lc $::settings->{nick}}{op} eq 1) {
+ $conn->sl($send);
+ }
+ else {
+ push( @{$oq{$chan}},$send );
+ $conn->privmsg( 'chanserv', "op $chan" );
+ }
+ }
+}
+
+sub doQueue {
+ my ( $conn, $chan ) = @_;
+ return unless defined $oq{$chan};
+ $conn->sl(shift(@{$oq{$chan}})) while (@{$oq{$chan}});
+}
+
+sub flood_add {
+ my ( $chan, $id, $host, $to ) = @_;
+ push( @{$sf{$id}{$chan}{$host}}, time );
+ while ( time >= $sf{$id}{$chan}{$host}[0] + $to ) {
+ last if ( $#{ $sf{$id}{$chan}{$host} } == 0 );
+ shift( @{$sf{$id}{$chan}{$host}} );
+ }
+ return $#{ @{$sf{$id}{$chan}{$host}}}+1;
+}
+
+sub flood_process {
+ for my $id ( keys %sf ) {
+ for my $chan ( keys %{$sf{$id}} ) {
+ for my $host ( keys %{$sf{$id}{$chan}} ) {
+ next unless defined $sf{$id}{$chan}{$host}[0];
+ while ( time >= $sf{$id}{$chan}{$host}[0] + $sf{$id}{'timeout'} ) {
+ last if ( $#{ $sf{$id}{$chan}{$host} } == 0 );
+ shift ( @{$sf{$id}{$chan}{$host}} );
+ }
+ }
+ }
+ }
+}
+
+sub getAlert {
+ my ($c, $risk, $t) = @_;
+ @_ = ();
+ $c = lc $c;
+ foreach my $prisk ( keys %RISKS) {
+ if ( $RISKS{$risk} >= $RISKS{$prisk} ) {
+ push( @_, @{$::channels->{channel}->{master}->{$t}->{$prisk}} ) if defined $::channels->{channel}->{master}->{$t}->{$prisk};
+ push( @_, @{cs($c)->{$t}->{$prisk}} ) if defined cs($c)->{$t}->{$prisk};
+ }
+ }
+ return @_;
+}
+
+sub commaAndify {
+ my @seq = @_;
+ my $len = ($#seq);
+ my $last = $seq[$len];
+ return '' if $len eq -1;
+ return $seq[0] if $len eq 0;
+ return join( ' and ', $seq[0], $seq[1] ) if $len eq 1;
+ return join( ', ', splice(@seq,0,$len) ) . ', and ' . $last;
+}
+
+sub parse_modes
+{
+ my ( $n ) = @_;
+ my @args = @{$n};
+ my @modes = split '', shift @args;
+ my @new_modes=();
+ my $t;
+ foreach my $c ( @modes ) {
+ if (($c eq '-') || ($c eq '+')) {
+ $t=$c;
+ }
+ else {
+ if ( defined( grep( /[abdefhIJkloqv]/,($c) ) ) ) { #modes that take args
+ push (@new_modes, [$t.$c, shift @args]);
+ }
+ elsif ( defined( grep( /[cgijLmnpPQrRstz]/, ($c) ) ) ) {
+ push (@new_modes, [$t.$c]);
+ }
+ else {
+ die "Unknown mode $c !\n";
+ }
+ }
+ }
+ return \@new_modes;
+}
+
+sub leq {
+ my ($s1, $s2) = @_;
+ return (lc $s1 eq lc $s2);
+}
+
+sub seq {
+ my ($n1, $n2) = @_;
+ return 0 unless defined($n1);
+ return 0 unless defined($n2);
+ return ($n1 eq $n2);
+}
+
+sub Util::killsub {
+ undef &cs;
+ undef &hostip;
+ undef &o_send;
+ undef &doQueue;
+ undef &flood_add;
+ undef &flood_process;
+ undef &getAlert;
+ undef &commaAndify;
+ undef &parse_modes;
+ undef &leq;
+ undef &seq;
+}
+
+return 1;
diff --git a/modules/xml.pl b/modules/xml.pl
new file mode 100644
index 0000000..2cb2505
--- /dev/null
+++ b/modules/xml.pl
@@ -0,0 +1,39 @@
+use warnings;
+use strict;
+
+use XML::Simple qw(:strict);
+
+my $xs1 = XML::Simple->new( KeyAttr => ['id'], Cache => [ qw/storable memcopy/ ]);
+
+sub readXML {
+ my ( $p ) = $::cset; #@_;
+ $p = 'default' if $p eq '';
+ $p = "config-$p";
+ $::settings = $xs1->XMLin( "$p/settings.xml", ForceArray => [qw/host/],
+ GroupTags => { altnicks => 'altnick', server => 'host', autojoins=> 'autojoin' });
+ $::channels = $xs1->XMLin( "$p/channels.xml", ForceArray => [qw/event debug info low medium high/] );
+ $::users = $xs1->XMLin( "$p/users.xml", ForceArray => 'person' );
+ $::xusers = $::users->{person};
+ $::commands = $xs1->XMLin( "$p/commands.xml", ForceArray => [qw/command/]);
+}
+
+sub writeXML {
+ my ( $p ) = @_;
+ $p = 'default' if $p eq '';
+ $p = "config-$p";
+ $xs1->XMLout($::settings, RootName => 'settings', KeyAttr => ['id'],
+ GroupTags => { altnicks => 'altnick', server => 'host', autojoins => 'autojoin' },
+ ValueAttr => { debug => 'content', nick => 'content', port => 'content',
+ realname => 'content', username => 'content', dir => 'content',
+ zone => 'content', filefmt => 'content', timefmt => 'content'}) > io("$p/settings.xml");
+ $xs1->XMLout($::channels, RootName => 'channels', KeyAttr => ['id']) > io("$p/channels.xml");
+ $xs1->XMLout($::users, RootName => 'people', KeyAttr => ['id']) > io("$p/users.xml");
+ $xs1->XMLout($::commands, RootName => 'commands', KeyAttr => ['id']) > io("$p/commands.xml");
+}
+
+sub Xml::killsub {
+ undef &readXML;
+ undef &writeXML;
+}
+
+return 1;