#!/usr/bin/perl -w

################################INFO######################################
##
#		Blackmore's reactor 8.0 - script for X-Chat 
#		    Copyright (C) 2001-2005 Simeon Simeonov
#	
#	
#	This program is free software; you can redistribute it and/or modify
#	it under the terms of the GNU General Public License 2 as published by
#       the Free Software Foundation
#
#       This program is distributed in the hope that it will be useful, but
#       WITHOUT ANY WARRANTY; without even the implied warranty of
#       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#       General Public License (http://www.gnu.org/copyleft/gpl.html) 
#	for more details.
#
#       
#       If you have no access to read the license, write to the Free Software
#       Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
####

##
# Release date: 21.03.2005
##

## Changelog
# v 8.0	    Added "toggle" and "reset_ttl [rule]" arguments for the /reactor command.
#	    New optional line ($ttl[x]).
#	    $action[x] = -1; (used to ignore action without ignoring the output) has been implemented.
#	    Added 4 new macros (%C, %G, %I, %H)
#	    The $raw[x] line has been removed 
#	    Updated documentation+examples in rules.conf
#	    The script is now taking advantage of the new X-Chat Perl interface.
#
# v 7.0	    Wildcards support has been added for $word[x].
#	    New optional line $wildcards[x].
#	    Few code improvements.
#
# v 6.0	    New optional line ($raw[x])
#	    Depricated functions have been removed.
#
# v 6.0-rc4 Added "disable <rule>" and "enable <rule>" arguments for the /reactor command
#
# v 6.0-rc3 Fixed a small bug causing unproper handleling of macros in $executable[x]
#	    (Thanks to Village)
#
# v 6.0-rc2 The script now loads the rules from an external file.
#           Added "list" and "reload" arguments for the /reactor command
#
# v 6.0-rc1 Bug causing the $source[x] line to allow any words to be triggered
#	    has been fixed (thanks to Joel Galenson)
#
# v 5.1  -  Macros can be used with $executable[x]
#
# v 5.0  -  New optional line ($executable[x])
#	 -  Added %e macros
#	 -  Some bugfixes
#	    Read the documentation for more details! 
#
# v 4.2  -  Contains a small bugfix 
#
# v 4.1	 -  $action[x] = 0; has been implemented 
#
# v 4.0  -  The script will now react on notice messages (/notice)
#        -  3 new optional lines ($source[x], $usource[x], $apply[x])
#        -  Flood protection system implemented
#
# v 3.0  -  The macros (most of them) can be used in the
#           trigger section of the rules ($word[x])
#        -  New optional line for the rule settings ($destination[x])
#        -  Write confirmation switch
#
# v 2.1  -  Added "status" argument for the /reactor command
# v.2.0  -  Added 3 new macros (%a, %d, %z) +login capability
#
# v.1.0  -  The first version of the script
##

## Intro: This script reacts to words or phrases said in the channel or on
# private. The script uses configuration rules defined by the user.
# The script can help you to define kick/ban words, public commands,
# ignore lame users, beep whenever somebody talks about you... you name it.
# The best thing with about this script is that you are in complete
# control by defining your own action rules. Blackmore's reactor has
# also its own macros. When you learn how to use this script, you will realize
# that you don't need some of the other X-Chat scripts you've been using
# until now.
##

## Installation: Copy this file to your X-Chat directory (usually ~/.xchat2/),
# restart your X-Chat or load it by typing: /load ~/.xchat2/reactor.pl
# The script has been tested on X-Chat 2.4.1 using the following systems:
# GNU/Linux (Gentoo)
# I don't know which older versions of X-Chat will support my script.
# I will not make any attempts to make this script work under Windows.
# I am not responsible for versions of this script downloaded from
# other places then http://www.pichove.org/scripts.php 
##

## Background:  I'm a great fan of X-Chat, UNIX, GNU/Hurd and Perl. I've been
# trying to find script for X-Chat that does the same thing as
# this script. Finally I decided to write it myself. I hope that
# after this script is released some people will find it useful
# and will consider using X-Chat instead of mIRC (which SUX) or other
# stupid IRC-clients.
##

## Contact me:  I will try to describe the configuration and the rules
# setting as clearly as I can, but if you have any further questions,
# comments, ideas or simply find this script useful feel free to
# contact me (Blackmore) on #pichove - Undernet (http://www.pichove.org) [recommended]
# or send mail to: blackmore(AT)pichove(DOT)org
# My PGP/GPG key can be downloaded from: http://www.pichove.org/blackmore.gpg
#
# N.B It is very important that you read the documentation before asking!!
##

## Downloading and checking for updates: My server is currently firewalled (port 80)
# by chello.no and I am forced to run it on port 81. I am about to change my provider.
# If you can't make contact with http://www.pichove.org/scripts.php, try http://pichove.org:81/scripts.php
# You can always get the latest version of the reactor script by joining #pichove - Undernet and typing: !reactor
# My bot (PurpleBOT) will DCC-send you the latest version of Blackmore's reactor.
##

##########################################################################
#############################CONFIGURATION################################
##########################################################################
# You have to reconfigure this script and make your own rules!

## The channel(s) where the script will "listen".
##  "" = all channels, "!#" = no channels (only listen for private messages)
## You can define active channels for each rule (se below!)
$chan = "#pichove";

## Command name. The name of the command which will enable|disable your script
## Default: reactor   Example: /reactor on - turns the script on /reactor off - turns the script off
## /reactor toggle - Used to toggle the on and off state of the reactor <NEW>
## /reactor status - shows the current status of the script (on or off)
## /reactor list - lists the triggerwords for each rule you have defined.
## /reactor reload - reloads the rules file. You can edit and reload your rules 
##  		     without having to restart the whole script.
## /reactor disable <x> - disables rule x, where x is the number of the rule.
##			  Disabled rule will not trigger any reaction until enabled.
##			  The ruke can be enabled by using /reactor enable <x>
##			  Using the "reload" argument will reload and reenable all rules.
## /reactor enable <x>	- enables the disabled rule x. Use /reactor list to see which rules 
##			  are enabled and which are disabled.
## /reactor reset_ttl [x] - resets the TTL for rule x, where x is the number of the rule. <NEW>
##			    If no rule number provided, all TTLs will be reset.
$command = "reactor";

## Rules file. The full path and filename of the file where you have defined your rules.
## If the file does not exists no error message will be given. No rules will be loaded.
$rules_file = "/home/blackmore/.xchat2/rules.conf";

## Autostart? Do you want the script to be turned on when X-Chat starts?
## 0 = no, 1 = yes
##N.B You can always start the script manually by typing /command on
$autostart = 1;

## Case sensitive? 0 = no, 1 = yes
## If set to 0 the script won't make difference between lower and uppercase letters
## if the word "test" is set as active word and case = 0 "test", "Test", "TeSt" will trigger the action
$case = 0;

## Wildcards support? 0 = disabled, 1 = enabled, 2 = rule defined.
$wcards = 1;

## Flood protection
## How many times in 1 minute can a word be triggered before considered as flood? 0 = no limit.
$flimit = 4;

## N.B Remember to reconfigure your rules-file!! (rules.conf)
## rules.conf contains a detailed documentation for making rules and some useful examples
## The rules in this version are *NOT* compatible with the rules from version previous to this version.

########################## END OF CONFIGURATION###########################
#### Do *NOT* edit below this point if you are not familiar with Perl ####


use POSIX;

$version = "v.8.0";
$flood = 0;

load_rules();

Xchat::register("Blackmore's reactor", $version, "by Simeon Simeonov", "");

Xchat::hook_server("PRIVMSG", main_sub);
Xchat::hook_server("NOTICE", main_sub);
Xchat::hook_command($command, "on_off");
Xchat::hook_timer(60000, "flood_sub");

sub load_rules {
	do $rules_file;
	for ($i = 0; $i < @word; $i++) {
		if (defined $word[$i]) { 
			$enabled[$i] = 1;
			$counter[$i] = 0;  ## zero off all counters on reload.
			if (defined $ttl[$i]) { 
				$ttl_save[$i] = $ttl[$i]; 
			} ## @ttl_save is used to save the original values of @ttl and later used for restoring them.
		}
	}
	$gcounter = 0;  ## zero off the global counter.
}

sub reset_ttl { ## Used to reset all ttls
	if (defined $_[0]) {
		if (defined $ttl[$_[0]]) {
			$ttl[$_[0]] = $ttl_save[$_[0]];
			Xchat::print("TTL for rule $_[0] is now reset to: $ttl[$_[0]]\n");
		} else {
			Xchat::print("No TTL defined for rule: $_[0]\n");
		}
	} else {
		for ($i = 0; $i < @word; $i++) {
			if (defined $ttl[$i]) { 
				$ttl[$i] = $ttl_save[$i];
			}
		}
		Xchat::print("All TTLs are now reset.\n");
	}
}

sub on_off {

	my($capture) = $_[1][1];
	$capture =~ /(.*?) (.*)?/;

	my $arg1 = $1;
	my $arg2 = $2;

	SWITCH: for ($capture) {
		if (/on/) {
			$autostart = 1;
		        Xchat::print("Script ON\n");
			last SWITCH;
		}

		if (/off/) {
                        $autostart = 0;
                        Xchat::print("Script OFF\n");
                        last SWITCH;
		}

		if (/toggle/) {
			if ($autostart == 0) {
				$autostart = 1;
				Xchat::print("Script ON\n");
			} else {
				$autostart = 0;
				Xchat::print("Script OFF\n");
			}
			last SWITCH;
		}
				

		if (/status/) {
			if ($autostart == 1) { Xchat::print("Blackmore's reactor is currently ON\n");}
			if ($autostart == 0) { Xchat::print("Blackmore's reactor is currently OFF\n");}
			last SWITCH;
		}
				

		if (/status/) {
			if ($autostart == 1) { Xchat::print("Blackmore's reactor is currently ON\n");}
			if ($autostart == 0) { Xchat::print("Blackmore's reactor is currently OFF\n");}
			last SWITCH;
		}

                if (/reload/) {
			@word = 0;
			@where = 0;
			@action = 0;
			@destination = 0;
			@apply = 0;
			@usource = 0;
			@source = 0;
			@executable = 0;
			@raw = 0;
			@wildcards = 0;
			@ttl = 0;
                        load_rules();
                        Xchat::print("$rules_file reloaded.");
                        last SWITCH;
                }
		
		if (/reset_ttl/) { ## resets one or all tlls
			if ($arg2 =~ /([0-9])/) {
				reset_ttl($arg2);
			} else {
				reset_ttl();
			}
			last SWITCH;
		}
		
                if (/list/) {
                        if (@word > 0) {
                                Xchat::print("N -> Trigger -> Status");
                                Xchat::print("____________");
                                Xchat::print("\n");
                        }
                        for ($i = 0; $i < @word; $i++) {
                                if (defined $word[$i]) { 
					if ($enabled[$i] == 1) { 
						Xchat::print("$i -> $word[$i] -> enabled\n"); 
					} else { 
						Xchat::print("$i -> $word[$i] -> disabled\n"); 
					}
				}
                        }
                        last SWITCH;
                }

		if (/disable/) {
			if (($arg1 =~ /disable/) && ($arg2 =~ /([0-9])/)) {
				if (defined $word[$arg2]) {
					if ($enabled[$arg2] == 0) {
						Xchat::print("Rule $arg2 already disabled\n");
					} else {
						$enabled[$arg2] = 0;
						Xchat::print("$arg2 -> disabled\n");
					}
				} else {
					Xchat::print("Rule $arg2 is not defined\n");
				}
				last SWITCH;
			}
		}

		if (/enable/) {
			if (($arg1 =~ /enable/) && ($arg2 =~ /([0-9])/)) {
				if (defined $word[$arg2]) {
					if ($enabled[$arg2] == 1) {
						Xchat::print("Rule $arg2 already enabled\n");
					} else {
						$enabled[$arg2] = 1;
						Xchat::print("$arg2 -> enabled\n");
					}
				} else {
					Xchat::print("Rule $arg2 is not defined\n");
				}
				last SWITCH;
			}
		}



        Xchat::print("Wrong command argument! Syntax: /$command < on | off | toggle | status | list | reload | disable <num> | enable <num> > | reset_ttl [num]");
        }


	return Xchat::EAT_XCHAT;
}


sub main_sub {

        if (($autostart == 1) && (($flood < $flimit) || ($flimit == 0))) {
	
		my ($line) = $_[1][0];
		
		$line =~ s/NOTICE/PRIVMSG/g;

		$line =~ m/\:(.*?)\!(.*?)\sPRIVMSG\s(.*?)\s\:(.*)?/;
      		my $nick = $1;
      		my $host = $2;
		($ident, $shost) = split (/@/, $host); ## splits the $host into ident and hostname parts. 
      		my $channel = $3;
      		my $text = $4;
		my ($bell) = "\a";
		my $me = Xchat::get_info("nick");
		my $server = Xchat::get_info("server");
		my ($time) = strftime( "%H:%M:%S", localtime );
		my ($date) = strftime( "%d:%m:%y", localtime );

		for ($i = 0; $i < @word; $i++) {

			my $dest = $chan;
			if (defined $destination[$i]) {
				$dest = $destination[$i];
			}

	
			if ((($dest =~ /$channel/) || ($dest eq "") || ($channel eq $me)) && ($enabled[$i] == 1)) {
				$trig = 0;

				my ($temp2) = $word[$i];

				$temp2 =~ s/%n/$nick/g;
				$temp2 =~ s/%c/$channel/g;
				$temp2 =~ s/%h/$host/g;
				$temp2 =~ s/%s/$server/g;
				$temp2 =~ s/%m/$me/g;
				$temp2 =~ s/%z/$time/g;
				$temp2 =~ s/%d/$date/g;
				$temp2 =~ s/%I/$ident/g;
				$temp2 =~ s/%H/$shost/g;

				## Wildcards support ##
				if (($wcards == 1) || (($wcards == 2) && ($wildcards[$i] == 1))) {	
					$temp2 =~ s/\./\\\./g;	# make sure the .-char is not NOT handled 
								# as regexp.
					$temp2 =~ s/\?/./g;	# make sure the ?-char is handled 
								# as regexp. (.)
					$temp2 =~ s/\*/.*/g;	# make sure the .-char is handled 
								# as regexp. (.*)
				}
				#######################


				if (($where[$i] == 0) && ($channel ne $me)) { next; }
				if (($where[$i] == 1) && ($channel eq $me)) { next; }
				

				if (($case == 1) && (($text =~ /^$temp2\b/) || ($text =~ /\b$temp2\b/))) { $trig = 1; }
				if (($case == 0) && (($text =~ /^$temp2\b/i) || ($text =~ /\b$temp2\b/i))) { $trig = 1; }


                                if ((defined $apply[$i]) && ($channel ne $me)) {
				
					$access = Xchat::user_info($nick);
					$op = 0;
					$voice = 0;
					if ($access->{prefix} eq "@") {
						$op = 1;
					}

					if ($access->{prefix} eq "+") {
						$voice = 1;
					}

                                        SWITCH: for ($apply[$i]) {

                                                if (/0/) {
							unless (($op == 0) && ($voice == 0)) { $trig = 0; }
                                                        last SWITCH;
                                                 }

                                                if (/1/) {
							unless ((($op == 0) && ($voice == 0)) || (($op == 0) && ($voice == 1))) { $trig = 0; }	
                                                        last SWITCH;
                                                }

                                                if (/2/) {
							unless (($op == 0) && ($voice == 1)) { $trig = 0; }
                                                        last SWITCH;
                                                }

                                                if (/3/) {
							unless ((($op == 0) && ($voice == 0)) || ($op == 1)) { $trig = 0; }	
                                                        last SWITCH;
                                                }

                                                if (/4/) {
							unless (($voice == 1) || ($op == 1)) { $trig = 0; }	
                                                        last SWITCH;
                                                }

                                                if (/5/) {
							unless ($op == 1) { $trig = 0; }
                                                        last SWITCH;
                                                }
                                        }
                                }


				if (defined $source[$i]) {
					unless ((($source[$i]=~ /^$nick\b/i) || ($source[$i] =~/\b$nick\b/i)) && ($trig == 1)) { next; }
                                }

                                if (defined $usource[$i]) {
					if (($usource[$i]=~ /^$nick\b/i) || ($usource[$i] =~/\b$nick\b/i)) { next; }
                                }


				
				if ($trig == 1) {
				
					my ($capture) = $text;
					$capture =~ /$word[$i]\s+(\S+)/;
					my $arg = $1;
				
					if (defined $ttl[$i]) { ## TTL check
						if ($ttl[$i] == 0) { next; }
						if ($ttl[$i] > 0) { $ttl[$i]--; }
					}

					$counter[$i]++;  ## We have a rule match. The counter increments by 1.
					$gcounter++;  ## ... and the global counter
					$flood++;	## .. and the flood counter too
					Xchat::hook_timer(60000, "flood_sub");
		
					if ($action[$i] eq "0") { 
						return Xchat::EAT_ALL;
					}

					my ($temp) = $action[$i];
				
					$temp =~ s/%n/$nick/g;
					$temp =~ s/%c/$channel/g;
					$temp =~ s/%C/$counter[$i]/g;
					$temp =~ s/%h/$host/g;
					$temp =~ s/%t/$text/g;
					$temp =~ s/%b/$bell/g;
					$temp =~ s/%s/$server/g;
					$temp =~ s/%m/$me/g;
					$temp =~ s/%w/$temp2/g;
					$temp =~ s/%z/$time/g;
					$temp =~ s/%d/$date/g;
					$temp =~ s/%a/$arg/g;
					$temp =~ s/%I/$ident/g;
					$temp =~ s/%H/$shost/g;
					$temp =~ s/%G/$gcounter/g;
					
				
					if (defined $executable[$i]) {
						$execute = $executable[$i];
						$execute =~ s/%a/$arg/g;
						$execute =~ s/%n/$nick/g;
						$execute =~ s/%c/$channel/g;
						$execute =~ s/%t/$text/g;
						$execute =~ s/%s/$server/g;
						$execute =~ s/%m/$me/g;
						$execute =~ s/%w/$temp2/g;
						$execute =~ s/%z/$time/g;
						$execute =~ s/%d/$date/g;
						$execute =~ s/%I/$ident/g;
						$execute =~ s/%H/$shost/g;
						$execute =~ s/%C/$counter[$i]/g;
						$execute =~ s/%G/$gcounter/g;
						
						my (@exe) = `$execute`;
						
						my ($tmp) = "%e";

						if ($temp =~ /$tmp/) {
							my ($temp3) = $temp;
							for ($j = 0; $j < @exe; $j++) {
								$temp3 = $temp;
								my ($t) = $exe[$j];
								chomp($t);
								$temp3 =~ s/$tmp/$t/g;
								
								if ($action[$i] == -1) { next; } # no action to be taken.
								Xchat::command($temp3);
							}
						} else {
							if ($action[$i] == -1) { next; } # no action to be taken.
							Xchat::command($temp);
						}
					} else {
						if ($action[$i] == -1) { next; } # no action to be taken.
						Xchat::command($temp);
					}
				}
			}
		}
	}
}

sub flood_sub {
	$flood = 0;
	return Xchat::EAT_XCHAT;
}

Xchat::print("Blackmore's reactor $version loaded!! Using $rules_file\n");
