#!/usr/bin/perl -w ###################################################################### # This is Cheetah's IRC Tools Version 0.9 # Written by Cheetah # Originally based, in part, on: # auto-op.pl by Marc Quinton@stna.dgac.fr / quinquin/IRCnet # auto-reop.pl (author unknown) # Those bits I think have been rewritten several times since, but credit # still goes to them for getting me started. # # This script performs several distinct tasks: # 1) Automatically gaining ops in channels via eggdrop bots # (or any other bot that uses the same op syntax) # 2) Automatically opping other people in channels # 3) Getting your preffered nicks, as entered in the server list # 4) Getting /whois info on /notify users when they come online # Note that recent versions of xchat have this built in, so this # functionality is turned off by default # 5) Auto-cycling channels when you're the only one and you don't have ops # 6) Setting safe channel modes (+stn) when you join a new channel # 7) It routes "ISON: unknown command" messages to /dev/null for # stupid servers that don't support it. # # The first 2 are controled by config files in your ~/.xchat dir # The /whois function can be enabled/disabled by setting the variable # $whois_new_online_users, near the end of this file # The channel cycling can be controlled with $cycle_dead_channels # The channel mode setting can be controlled with $set_safe_chanmodes # # Preferred Nicks: # The script will find your 3 desired nicks in your xchat.conf file and try # to keep the most preferrable of these. Note that in order for this to # work properly, you MUST add these nicks to your /notify list before you # load the script, or things will go VERY haywire. # TODO: recent xchat versions now have perl support for examining the notify # list. Check for the nicks at load time and add them if necessary. # # Eggdrop Opping: # Eggdrop opping is done via two lists: # 1) A list of bots, their hostmask, and the password # 2) A list of channels and the bots on those channels # Use /addbot, /listbots, and /reloadbots to manipulate the first list # The bot hostmask is expressed as a perl regular expression # use /setchanbots, /listchanbots, and /reloadchanbots for the second # To avoid redundant opping, you should list the bots for each # channel with bots that are on the largest number of your channels first # The script will check every 15 seconds to see if you need ops and will # /msg enough eggdrops asking for ops that, if all respond, you will have # ops everywhere you can get it. The script will cycle through the list # of bots in each channel if it finds you still not opped the next time around # # Auto-opping # Auto-opping is done by a list of nick!user@host regular expressions # mapped to channel lists. So for each person you want to autoop, add # a regular expression for their nick!user@host and a list of channels # to autoop them in to the autoop list. If you use * as a channel list, then # they will be autooped in all channels. # You can manipulate the autoop list with /addautoop, /listautoop, and # /reloadautoop # # Deleting stuff from the lists: # Currently the only way to delete items from any of the lists is manual # editing of the data files. These are fairly self-evident files # with one line per list item. # The bots, channel bots, and autoop lists are stored, respectively, in the # bots.dat, chan_bots.dat, and auto_op.dat files in your ~/.xchat directory. # To delete items, open the desired list in your favorite text editor, # delete the appropriate lines, and then use the /reloadX command for the # list(s) you modified (e.g. /reloadbots). # ############################################################################# package CIT; sub cpmsg { foreach (@_) { IRC::print("\0035CIT:\t$_\003\n"); } } sub cpsmsg { my $s = shift; foreach (@_) { IRC::print_with_channel("\0035CIT:\t$_\003\n", $s, $s); } } sub cperr { foreach (@_) { IRC::print("\0034CIT:\t$_\003\n"); } } sub load_auto_op { # load the auto-op data if ( ! open(OPDAT,"<" . $auto_op_dat_file) ) { cperr("Can't open auto-op data file ($auto_op_dat_file)!"); return; } undef %auto_op; my $n = 0; my $opline = ""; while ($opline = ) { chop($opline); my $opregex = ""; my $opchannels = ""; my @chans = (); ($opregex,$opchannels) = split(" ", $opline); @chans = split(",", $opchannels); foreach my $chan (@chans) { $auto_op{$opregex}->{lc($chan)} = 1; } $n++; } cpmsg("Loaded $n auto-op regexes"); $auto_op_init = 1; close(OPDAT); } sub load_bots { if ( ! open (BOTSDAT,"<" . $bots_dat_file) ) { cperr("Can't open bots data file ($bots_dat_file)!"); return; } undef %bots; my $n = 0; my $botline = ""; my $bothost = ""; my $botpass = ""; while ($botline = ) { chop($botline); my $botname = ""; ($botname,$bothost,$botpass) = split(" ", $botline); $bots{$botname}->{"host"} = $bothost; $bots{$botname}->{"pass"} = $botpass; $n++; } $cur_ask_bot = 0; cpmsg("Loaded $n bot entries"); $bots_init = 1; close(BOTSDAT); } sub load_chan_bots { if ( ! open (CHANBOTSDAT,"<" . $chan_bots_dat_file) ) { cperr("Can't open chan bots data file ($chan_bots_dat_file)!"); return; } undef %chan_bots; ## undef %chan_bot_cur; my $n = 0; my $chanline = ""; my @botlist = (); my $channame; while ($chanline = ) { chop($chanline); $channame = ""; my $bots; ($channame, $bots) = split(" ", $chanline); @botlist = split(",", $bots); $channame = lc($channame); $chan_bots{$channame} = [@botlist]; ## $chan_bot_cur{$channame} = 0; $n++; } update_chan_bots(); cpmsg("Loaded $n channel bot-op entries"); $chan_bots_init = 1; close(CHANBOTSDAT); } sub update_chan_bots { # we do a reverse mapping: bot names -> channels # this is for removing redundant op commands undef %bot_chans; foreach $channame (keys %chan_bots) { my $tbot; foreach $tbot (@{$chan_bots{$channame}}) { push @{$bot_chans{$tbot}}, $channame; } } } sub load_nickgrab { # parse xchat.conf to find preferred nicks if (not open(XCC, "<$xcbase/xchat.conf")) { cperr("Can't open $xcbase/xchat.conf"); return; } %preferrednicks = (); my $cline; # cpmsg("Loading Preferred Nicks"); while ($cline = ) { if ($cline =~ /^nickname(.) = (.*)$/) { $preferrednicks{$2} = $1; # we need it to be in the notify list for this to work #IRC::command("/notify $2"); # cpmsg("Nick '$2' Priority $1"); } } cpmsg("Loaded Preferred Nicks: " . join ', ', sort {$preferrednicks{$a} <=> $preferrednicks{$b}} keys %preferrednicks); close(XCC); } sub add_auto_op { my $param = join('', @_); # clean excess spaces $param =~ s/\s+/ /g; # split it up my $newregex = ""; my $newchans = ""; ($newregex,$newchans) = split(" ", $param); # usage info if ((lc($param) eq "help") or ($newregex eq "") or ($newchans !~ /(^\*$)|(^[#&])/)) { cpmsg("/ADDAUTOOP "); cpmsg(" is a perl regular expression"); cpmsg(" is a comma separated list, e.g. #a,#b,#c"); return 1; } my @chans = split(",", $newchans); foreach my $chan (@chans) { $auto_op{$newregex}->{$chan} = 1; } cpmsg("Adding auto-op for $newregex in $newchans"); save_auto_op(); return 1; } sub add_bot { my $param = join('', @_); # clean excess spaces $param =~ s/\s+/ /g; if (lc($param) eq "help") { cpmsg("/ADDBOT "); cpmsg(" is just a name to keep track of the bot"); cpmsg(" is a perl regex for the nick!user\@host of the bot"); cpmsg(" is your password on the eggdrop"); return 1; } # split it up my ($botname,$bothost,$botpass) = split(" ", $param); if (($botname eq "") or ($bothost eq "") or ($botpass eq "")) { cperr("You need more options, try /addbot help"); return 1; } $bots{$botname}->{"host"} = $bothost; $bots{$botname}->{"pass"} = $botpass; cpmsg("Adding bot $botname at $bothost"); save_bots(); return 1; } sub set_chan_bots { my $param = join('', @_); # clean excess spaces $param =~ s/\s+/ /g; if (lc($param) eq "help") { cpmsg("/SETCHANBOTS "); cpmsg(" is the channel name"); cpmsg(" is a space separated list of bots that will op you"); return 1; } my ($channel, @bots) = split(/[ ,]/, $param); if (($channel eq "") or ($#bots < 0)) { cperr("You need more parameters, try /setchanbots help"); return 1; } $chan_bots{$channel} = [@bots]; ## $chan_bot_cur{$channel} = 0; my $botlist = join(",", @bots); cpmsg("Set bots for $channel to $botlist"); update_chan_bots(); save_chan_bots(); return 1; } sub list_auto_op { # load_auto_op() if ($auto_op_init == 0); cpmsg("Auto-op list"); my $n = 1; my $userregex = ""; foreach $userregex (keys %auto_op) { my $chans = join(",", keys %{$auto_op{$userregex}}); cpmsg("$n $userregex $chans"); $n++; } cpmsg("End of auto-op list"); return 1; } sub list_bots { cpmsg("Bot list"); my $n = 1; foreach $botname (sort keys %bots) { my $bothost = $bots{$botname}->{"host"}; cpmsg("$n $botname $bothost"); $n++; } cpmsg("End of bot list"); return 1; } sub list_chan_bots { cpmsg("Channel bots list"); my $n = 1; foreach $channel (sort keys %chan_bots) { my $chanlist = join(",", @{$chan_bots{$channel}}); cpmsg("$n $channel $chanlist"); $n++; } cpmsg("End of channelbots list"); return 1; } sub save_auto_op { if ( ! open(OPDAT, ">" . $auto_op_dat_file) ) { cperr("Can't open auto-op data file ($auto_op_dat_file)!"); return; } my $userregex; foreach $userregex (keys %auto_op) { my $chans = join(",", keys %{$auto_op{$userregex}}); print OPDAT "$userregex $chans\n"; } close(OPDAT); cpmsg("Saved auto-op data"); } sub save_bots { if ( ! open(BOTSDAT, ">" . $bots_dat_file) ) { cperr("Can't open bots data file ($bots_dat_file)!"); return; } my $botname; foreach $botname (keys %bots) { my $host = $bots{$botname}->{"host"}; my $pass = $bots{$botname}->{"pass"}; print BOTSDAT "$botname $host $pass\n"; } close(BOTSDAT); cpmsg("Saved bot data"); } sub save_chan_bots { if ( ! open(CHANBOTSDAT, ">" . $chan_bots_dat_file) ) { cperr("Can't open channel bots data file ($chan_bots_dat_file)!"); return; } my $channel; foreach $channel (keys %chan_bots) { my $chanlist = join(",", @{$chan_bots{$channel}}); print CHANBOTSDAT "$channel $chanlist\n"; } close(CHANBOTSDAT); cpmsg("Saved channel bots data"); } sub build_chanlist { my $channels = $_[0]; %$channels = (); my @chanlist = IRC::channel_list(); # grab flag info for each user on each channel my $server = IRC::get_info(3); my $mynick = IRC::get_info(1); my $channel; for ($n = 0; $n <= $#chanlist; $n += 3) { # list is grouped to channel, server, user, only grab channel windows next unless (($chanlist[$n+1] eq $server) && ($chanlist[$n] =~ /^[#&]/)); $channel = lc($chanlist[$n]); next if (defined $channels->{$channel}); my @userlist = IRC::user_list($channel, $server); my $m; for ($m = 0; $m <= $#userlist; $m += 5) { my $user = $userlist[$m]; $channels->{$channel}->{$user}->{"host"} = $userlist[$m+1]; $channels->{$channel}->{$user}->{"op"} = $userlist[$m+2]; $channels->{$channel}->{$user}->{"voice"} = $userlist[$m+3]; # the fifth element is a ':' = separator/garbage } } } sub check_ops_cmd { my $server = IRC::get_info(3); my $mynick = IRC::get_info(1); my ($opt) = @_; my %channels; # check to see if we should even run this: # if this fails, the timer won't be re-triggered my $stillserver = 0; foreach my $tserv (IRC::server_list()) { $stillserver = 1 if ($tserv eq $server); } return 1 if (not $stillserver); if ((time() - $last_check_ops{$server}) < $check_ops_delay) { return 1 if ($opt); } $last_check_ops{$server} = time(); build_chanlist(\%channels); # now we have the server/channel/user/info tree, check ops my $skippedbots = get_bot_ops($server, $mynick, \%channels); give_auto_ops($server, $mynick, \%channels); # if we skipped any bots, repeat the check soon ignoring delay IRC::command("/timer $bot_resp_delay /checkops") if ($skippedbots); return 1; } sub check_all_ops { # go through op sequence for each server foreach my $server (IRC::server_list()) { # normally check ops right away my $delay = 1; if (not defined $last_check_ops{$server}) { cpsmsg($server, "Initializing op checking for $server"); $last_check_ops{$server} = 0; # if it's a new server, wait a bit before checking ops $delay = $check_ops_delay; } IRC::command_with_server("/timer $delay /checkops 1", $server); } IRC::add_timeout_handler($check_ops_delay * 1000, "CIT::check_all_ops"); } sub find_bot { my $trybot = $_[0]; my $check_chan = $_[1]; my $channels = $_[2]; my $bothost = $bots{$trybot}{"host"}; foreach my $botnick (keys %{$channels->{$check_chan}}) { my $botaddr = $channels->{$check_chan}{$botnick}{"host"}; # accept only if it's the bot and it's opped return $botnick if (("$botnick\!$botaddr" =~ m/$bothost/i) and ($channels->{$check_chan}{$botnick}{"op"})); } return undef; } sub get_bot_ops { my $server = $_[0]; my $mynick = $_[1]; my $channels = $_[2]; my %needopchans; my $numopchans = 0; my %botnicks; # find channels I need ops in foreach $channame (keys %$channels) { if (not $channels->{$channame}->{$mynick}->{"op"}) { $needopchans{$channame} = 1; $numopchans++; } } return if ($numopchans == 0); # pick a bot set of bots that will op us in each channel, but don't do # any redundant asks (so we may need to go through this more than once) # sort bot list decreasing by # of channels covered so that we're more # likely to get a minimal set my @botlist = sort {$#{$bot_chans{$b}} <=> $#{$bot_chans{$a}}} keys %bots; my $first_bot = $cur_ask_bot; my $done = 0; my @askchans; my $skippedbots = 0; # did we skip anything? # if we skip any bots, we want the next pass to start where we # started skipping, we use this var to track it my $endcurbot = -1; BOT: for (my $botnum = 0; $botnum <= $#botlist; ++$botnum) { $cur_ask_bot = ($first_bot + $botnum) % ($#botlist + 1); my $trybot = $botlist[$cur_ask_bot]; # to avoid redundant asking, we don't ask a bot if any of its # chans have already been asked for ECHAN: foreach my $check_chan (@{$bot_chans{$trybot}}) { # chan already asked ... if (defined $needopchans{$check_chan} and $needopchans{$check_chan} == 0) { ++$skippedbots; $endcurbot = $cur_ask_bot if $endcurbot < 0; next BOT; } } # look at each chan this bot should be in CHAN: foreach my $check_chan (@{$bot_chans{$trybot}}) { next CHAN unless ($needopchans{$check_chan}); # try to find the bot $botnicks{$trybot} = find_bot($trybot, $check_chan, $channels) if (!defined $botnicks{$trybot}); if (defined $botnicks{$trybot}) { # bot found, this chan will be opped $needopchans{$check_chan} = 0; push @askchans, $check_chan; $numopchans--; } } # CHAN: for each chan this bot covers # if we've got everything, stop looking last BOT if $numopchans == 0; } # BOT: for each bot $cur_ask_bot = $endcurbot if $endcurbot >= 0; cpmsg( "Asking for ops in @{[join(', ',@askchans)]} " . "from @{[join(', ',grep {$botnicks{$_}} keys %botnicks)]}" ) if (@askchans); # got the ask list, start asking ASKBOT: foreach my $askbot (keys %botnicks) { if (!defined $bots{$askbot}) { # should NEVER get here!!! cperr("bot $botname doesn't exist, do you need to reload the bots?"); next ASKBOT; } next ASKBOT if (!$botnicks{$askbot}); my $botnick = $botnicks{$askbot}; my $pass = $bots{$askbot}{"pass"}; # display ask source above # cpmsg(" Asking $askbot @{[$askbot ne $askbot?qq[($botnick) ]:qq()]}for ops"); # we use a raw to prevent output IRC::command_with_server("/quote PRIVMSG $botnick :OP $pass", $server); } return $skippedbots; } sub give_auto_ops { my ($server, $mynick, $channels) = @_; CHANNEL: foreach my $channel (keys %$channels) { my $meop = $channels->{$channel}->{$mynick}->{"op"}; next CHANNEL unless ($meop); NICK: foreach my $nick (keys %{$channels->{$channel}}) { my $host = $channels->{$channel}->{$nick}->{"host"}; my $opped = $channels->{$channel}->{$nick}->{"op"}; next NICK if ($opped); foreach my $match (keys %auto_op) { if (("$nick\!$host" =~ m/$match/i) and ($auto_op{$match}->{$channel} or $auto_op{$match}->{"*"})) { cpmsg("Auto-opping $nick in $channel"); IRC::command_with_server("/mode $channel +o $nick", $server); } } } } } sub autocycle_checkchan { my ($chan, $serv) = @_; my @list = IRC::user_list($chan, $serv); my $me = IRC::get_info(1); if ($#list <= 9) { # we get this *before* xchat gets to parse the user parting, # so we ignore the person that's not me while ($#list >= 0) { my ($cnick,$caddr,$cop,$cvoice,$cfoo) = splice(@list,0,5); next if ($cnick ne $me); # don't cycle if I'm opped! if ($cop == 0) { cpmsg("Auto-reop: I'm the last on $chan, no ops, try re-op $chan"); IRC::command("/leave $chan"); IRC::command("/join $chan"); } } } } sub autocycle_pk_handler { return 0 if (not $cycle_dead_channels); my $line = join('', @_); # auto cycle to regain ops $line =~ m/:(.+?)\!(.+?) (PART) (.*)/ or $line =~ m/:(\S+?)\!(\S+?) (KICK) (\S*) (\S*) :(.*)/; my $part_nick; if ($3 eq "PART") { $part_nick = $1; } else { $part_nick = $5; } my $chan = $4; my $serv = IRC::get_info(3); my $me = IRC::get_info(1); # if this is me who leave chan, just quit return undef if ($me eq $part_nick); autocycle_checkchan($chan, $server); return 0; } sub autocycle_q_handler { return 0 if (not $cycle_dead_channels); my $line = join('', @_); # auto cycle to re-claim ops # $line =~ /:(.+?)\!(.+?) QUIT :(.*)/; my @channels = IRC::channel_list(); # list is in groups of 3: channel, server, nick CHANNEL: while (@channels) { my ($chan, $serv, $nick) = splice(@channels, 0, 3); # only process channel items, not server windows next CHANNEL unless ($chan =~ /^[#&]/); autocycle_checkchan($chan, $server); } # return (undef) : continue, return 1 : last plugin (hook) ! return 0; } sub request_nick { my ($nick, $server) = splice(@_, 0, 2); if (($requested_nick{$server}->{nick} ne $nick) or ($requested_nick{$server}->{time} + $check_ops_delay < time())) { IRC::command_with_server("/nick $nick", $server); $requested_nick{$server}->{nick} = $nick; $requested_nick{$server}->{time} = time(); return 1; } return 0; } # this does the nick setting for nickgrab stuff # params: $currentnick, \%nicksonline sub do_nickgrab { # forget it if we're not interested in grabbing nicks if (not $use_nickgrab) { return; } my $server = $_[0]; my $nick = $_[1]; my %nicksonline = %{$_[2]}; # don't do nickgrab on dalnet servers if ($server =~ /dal.?net/) { return; } my $newnick = ""; foreach (keys %preferrednicks) { if ( $preferrednicks{$_} and $preferrednicks{$nick} and ($preferrednicks{$_} < $preferrednicks{$nick}) ) { if (! $nicksonline{lc($_)}) { $newnick = $_; } } } if ($newnick) { my $tret = request_nick($newnick, $server); if ($tret > 0) { cpmsg("Changing nick from ${nick}($preferrednicks{$nick}) to " . "${newnick}($preferrednicks{$newnick})"); } } } sub nickgrab_notify_handler { # notify handler my $line = join('', @_); $line =~ m/:(.+) 303 (.+) :(.*)/; # lastnicksonline and preferrednicks are globals my $server = $1; my $nick = $2; my %nicksonline = map { lc($_) => 1 } split(/ /, $3); do_nickgrab($server, $nick, \%nicksonline); %{$lastnicksonline{$server}} = %nicksonline; # return (undef) : continue, return 1 : last plugin (hook) ! return 0; } sub whois_notify_handler { my $line = join('', @_); $line =~ m/:(.+) 303 (.+) :(.*)/; # lastnicksonline and preferrednicks are globals my $server = $1; my $nick = $2; my %nicksonline = map { lc($_) => 1 } split(/ /, $3); # /whois new online notifies if ($whois_new_online_users) { foreach (keys %nicksonline) { if (! defined $whoisnicksonline{$server}->{$_}) { cpmsg("Getting WHOIS for new online user $_"); IRC::command("/whois $_"); } } } %{$whoisnicksonline{$server}} = %nicksonline; # return (undef) : continue, return 1 : last plugin (hook) ! return 0; } sub nickgrab_quit_handler { # handles grabbing prefered nicks when the owner of that nick quits my $line = join('', @_); $line =~ /:(.+?)\!(.+?) QUIT :(.*)/; my $nick = $1; my $mynick = IRC::get_info(1); my $server = IRC::get_info(3); # copy, not reference! my %nicksonline = %{$lastnicksonline{$server}}; # remove the entry for the quit nick delete $nicksonline{lc($nick)}; # do the nick grab do_nickgrab($server, $mynick, \%nicksonline); # set the lastnicksonline to the current value %{$lastnicksonline{$server}} = %nicksonline; # return (undef) : continue, return 1 : last plugin (hook) ! return 0; } sub nickgrab_nickchange_handler { # handles grabbing prefered nicks when the owner of that nick changes # their nick my $line = join('', @_); $line =~ /:(.+?)\!(.+?) NICK :(.*)/; my $oldnick = $1; my $newnick = $3; my $mynick = IRC::get_info(1); my $server = IRC::get_info(3); # copy, not reference! my %nicksonline = %{$lastnicksonline{$server}}; # remove the entry for the quit nick delete $nicksonline{lc($oldnick)}; # add entry for new nick $nicksonline{lc($newnick)} = 1; # do the nick grab do_nickgrab($server, $mynick, \%nicksonline); # set the lastnicksonline to the current value %{$lastnicksonline{$server}} = %nicksonline; # return (undef) : continue, return 1 : last plugin (hook) ! return 0; } sub isonfail_handler { my $line = join('', @_); # ignore ison failure if ($line =~ m/:(.+) 421 (.+) ISON :Unknown command/o) { return 1 } } #sub blankline_ignore #{ # my $line = join('', @_); # return 1 if ($line =~ /^\s*$/); #} sub safe_chanmode_handler { return 0 if (not $set_safe_chanmodes); my $line = join('', @_); $line =~ /:(.+) 315 (.+) (.+) :End of \/WHO list./; my $nick = $2; my $chan = $3; # only act for channels return 0 unless (($chan =~ /^#/) or ($chan =~ /^&/)); my $me = IRC::get_info(1); my $serv = IRC::get_info(3); # check to make sure we're still in the chan my @chans = IRC::channel_list(); my $ok = 0; while ($#chans >= 0) { my ($lchan, $lserv, $lnick) = splice(@chans, 0, 3); $ok = 1, last if (($lchan eq $chan) and ($lserv eq $serv)); } return 0 unless ($ok); my @users = IRC::user_list($chan, $serv); if ((($#users + 1) / 5) < 2) { # I'm first user, set modes cpmsg("First user in $chan, setting modes"); IRC::command("/mode $chan +stn"); } return 0; } sub mode_handler { # :Cheetah!cheetah@cheetah.STUDENT.CWRU.Edu MODE #sctalk -o Cheetah my $line = join('', @_); my ($nick, $user, $host, $chan, $modes, $extra) = ($line =~ /:(.+)\!(.+)\@(.+) MODE (\S+) (\S+)(.*)/); # clean up extra: remove leading/trailing spaces $extra =~ s/^ *(.*) *$/$1/; my $modeplus = 1; my @modechars = split('', $modes); my @extras = split(' ', $extra); MODECHAR: foreach (@modechars) { my $mchg = ''; my $mparam = ''; if ($_ eq '+') { $modeplus = 1; } elsif ($_ eq '-') { $modeplus = 0; } elsif ($_ =~ /[bdhklov]/i) { if ($modeplus == 1) { $mparam = shift @extras; } } if (($_ eq 'o') and ($modeplus == 0)) { # cpmsg("checking for reop in $chan"); # person got deopped, re-check ops, don't piss about delays # however, we need to let xchat process the mode first, so we # do this with a zero timer IRC::command("/timer 1 /checkops"); # if we sent the check once, we don't need to keep looking ... last MODECHAR; } } } sub connect_handler { # because xchat doesn't mark a server as known & connected until AFTER it # sees the end of MOTD so ... we catch the 001 numeric, and then add a # timer to check everything in a couple seconds, after which xchat should # have parsed it all & whatnot because xchat doesn't know server info, # etc. yet, we have to parse it from the line. We wait a few seconds # because we don't want to have to worry about the multiple server # numerics that exist for end of MOTD, and thus we catch 001, and thus we # don't know how long it'll take for the motd to arrive. my $line = join('', @_); my ($server, $nick) = ($line =~ /^:(\S+) 001 (\S+)/); IRC::add_timeout_handler(2000, "CIT::check_all_ops"); # initialize our nick request data $requested_nick{$server}->{nick} = $nick; $requested_nick{$server}->{time} = time(); return 0; } sub unload_handler { save_auto_op() if ($auto_op_init == 1); save_bots() if ($bots_init == 1); save_chan_bots() if ($chan_bots_init == 1); cpmsg("Unloaded Cheetah's IRC Tools $cit_version"); } $xcbase = IRC::get_info(4); $auto_op_dat_file = "$xcbase/auto_op.dat"; $bots_dat_file = "$xcbase/bots.dat"; $chan_bots_dat_file = "$xcbase/chan_bots.dat"; %lastnicksonline = (); %preferrednicks = (); %whoisnicksonline = (); $check_ops_delay = 30; $bot_resp_delay = 15; # options $whois_new_online_users = 0; # use x-chat's built-in feature for this instead $cycle_dead_channels = 1; $set_safe_chanmodes = 1; $use_nickgrab = 1; $cit_version = "0.9"; IRC::register("Cheetah's IRC Tools", $cit_version, "CIT::unload_handler", ""); # cpmsg("Loading Cheetah's IRC Tools $cit_version"); IRC::add_message_handler("PART", "CIT::autocycle_pk_handler"); IRC::add_message_handler("KICK", "CIT::autocycle_pk_handler"); IRC::add_message_handler("QUIT", "CIT::autocycle_q_handler"); IRC::add_message_handler("QUIT", "CIT::nickgrab_quit_handler"); IRC::add_message_handler("303", "CIT::nickgrab_notify_handler"); IRC::add_message_handler("303", "CIT::whois_notify_handler"); IRC::add_message_handler("NICK", "CIT::nickgrab_nickchange_handler"); IRC::add_message_handler("421", "CIT::isonfail_handler"); IRC::add_message_handler("315", "CIT::safe_chanmode_handler"); IRC::add_message_handler("001", "CIT::connect_handler"); #IRC::add_message_handler("INBOUND", "CIT::blankline_ignore"); # catch deops and re-op if appropriate IRC::add_message_handler("MODE", "CIT::mode_handler"); IRC::add_command_handler("ADDAUTOOP", "CIT::add_auto_op"); IRC::add_command_handler("LISTAUTOOP", "CIT::list_auto_op"); IRC::add_command_handler("RELOADAUTOOP", "CIT::load_auto_op"); IRC::add_command_handler("ADDBOT", "CIT::add_bot"); IRC::add_command_handler("LISTBOTS", "CIT::list_bots"); IRC::add_command_handler("RELOADBOTS", "CIT::load_bots"); IRC::add_command_handler("SETCHANBOTS", "CIT::set_chan_bots"); IRC::add_command_handler("LISTCHANBOTS", "CIT::list_chan_bots"); IRC::add_command_handler("RELOADCHANBOTS", "CIT::load_chan_bots"); # this does bot and auto op checking, to remove the need to build a # channel info list twice IRC::add_command_handler("CHECKOPS", "CIT::check_ops_cmd"); load_auto_op(); load_bots(); load_chan_bots(); load_nickgrab(); check_all_ops(); cpmsg("Loaded Cheetah's IRC Tools $cit_version"); # make script evaluate to true 1;