User:Ryan Schmidt/abuse.pl

This is my main IRC script, for all of you who are wondering what I use ;) Explanations are given after the script. This script is written in perl for XChat, so if you have both, you can use it yourself!

The Script
use strict; use warnings; use constant ABUSE_VERSION => "3.8.5r1";
 * 1) !/usr/bin/perl -w

Xchat::register("Abuse", ABUSE_VERSION, "abuse script"); Xchat::hook_command("hammer", "abuse_hammer"); Xchat::hook_command("tempban", "abuse_tempban"); Xchat::hook_command("backstab", "abuse_backstab"); Xchat::hook_command("ctcpstats", "abuse_ctcpblock_stats"); Xchat::hook_command("ctcpreset", "abuse_ctcpblock_reset"); Xchat::hook_print("Part", "abuse_backstab_register_part"); Xchat::hook_print("Part with Reason", "abuse_backstab_register_part"); Xchat::hook_print("Quit", "abuse_backstab_register_quit"); Xchat::hook_print("Kick", "abuse_backstab_register_kick"); Xchat::hook_print("You Part with Reason", "abuse_rejoinonremove"); Xchat::hook_print("Banned", "abuse_ibnull_b"); Xchat::hook_print("You Join", "abuse_ibnull_clear"); Xchat::hook_print("Invite", "abuse_ibnull_i"); Xchat::hook_print("Invited", "abuse_ibnull_join"); Xchat::hook_print("Keyword", "abuse_ibnull_k"); Xchat::hook_print("CTCP Generic", "abuse_ctcpblock", {"priority" => Xchat::PRI_HIGHEST}); Xchat::hook_print('Channel Message', "abuse_filter", {"data" => ["Channel Message"]}); Xchat::hook_print('Channel Msg Hilight', "abuse_filter", {"data" => ["Channel Msg Highlight"]}); Xchat::hook_print('Channel Action', "abuse_filter", {"data" => ["Channel Action"]}); Xchat::hook_print('Channel Action Hilight', "abuse_filter", {"data" => ["Channel Action Hilight"]}); Xchat::print("Abuse script version ".ABUSE_VERSION." loaded\n"); Xchat::print('Script copyright ©2008-2010 Skizzerz '); Xchat::print("Commands: hammer, backstab, tempban"); Xchat::print("Triggers: autorejoin on remove, +i/b/k nullify"); Xchat::print("Features: beep filter"); Xchat::print("CTCP flood protection -- type /ctcpstats for the current stats"); my %data = ; #global var for data passing, use identifier => [data] my %hooks = ; #global var for hook info passing, use subname => $hook id

sub abuse_init { $data{"abuse_ctcpblock"} = [0, 0, 1, 0]; }
 * 1) initialization

abuse_init;

sub abuse_hammer { if(!defined($_[0][1])) { Xchat::print("USAGE: HAMMER [ban]"); return Xchat::EAT_ALL; }	my $ban = $_[0][2]; my $channel = Xchat::get_info('channel'); my $nickinfo = Xchat::user_info($_[0][1]); my $owninfo = Xchat::user_info; $data{"abuse_hammer"} = [$ban, $channel, $nickinfo]; if( $owninfo->{"prefix"} eq '@' ) { $data{"abuse_hammer"}[3] = 'false'; abuse_hammer_execute; } else { Xchat::command("cs op $channel"); $data{"abuse_hammer"}[3] = 'true'; $hooks{"abuse_hammer"} = Xchat::hook_print("Channel Operator", "abuse_hammer_execute"); }	return Xchat::EAT_ALL; }
 * 1) hammer command -- /hammer [ban]
 * 2) This command requires the use of /remove, so it will only work in networks that have it (such as freenode)
 * 3) This command ops you if you are not opped already, then /remove's the user from the channel, then deops you if you didn't start out opped originally
 * 4) If you were opped before using this command, you do not get deopped
 * 5) Specifying anything for the second parameter will place a ban (+b) on the user's host after doing a few transformations on the hostmask

sub abuse_hammer_execute { my $deop = $data{"abuse_hammer"}[3]; my $ban = $data{"abuse_hammer"}[0]; my $channel = $data{"abuse_hammer"}[1]; my $nickinfo = $data{"abuse_hammer"}[2]; my $owninfo = Xchat::user_info; if(exists($hooks{"abuse_hammer"})) { if($_[0][1] ne $owninfo->{"nick"}) { return; #so we don't trigger when OTHER people get opped. }	}	if(defined($ban)) { my $host = $nickinfo->{"host"}; $host =~ s/[ni]=/?=/; $host =~ s/^\~/*/; $host =~ s/[0-9abcdef][0-9abcdef]@/*@/; $host =~ s/\/.*$/\/*/; # user@host/otherstuff => user@host/* $host =~ s/\..*$/.*/; # user@host.stuff.morestuff => user@host.* $host = '*!'. $host; Xchat::command("mode +b $host"); }	my $nick = $nickinfo->{"nick"}; my $ownnick = $owninfo->{"nick"}; Xchat::command("remove $channel $nick :It's hammer time!"); if($deop eq 'true') { Xchat::command("mode -o $ownnick"); }	if(exists($hooks{"abuse_hammer"})) { Xchat::unhook($hooks{"abuse_hammer"}); delete($hooks{"abuse_hammer"}); #unset it so that hammer works while opped }	return; }

sub abuse_backstab { my $nick = $_[0][1]; my $channel = Xchat::get_info('channel'); if(!defined($nick)) { Xchat::print("USAGE: BACKSTAB "); return Xchat::EAT_ALL; }	if(!exists($data{"abuse_backstab"}{$nick})) { Xchat::print("Nick data for $nick not found!"); return Xchat::EAT_ALL; }	my $owninfo = Xchat::user_info; if($owninfo->{"prefix"} eq '@') { $data{"abuse_backstab"}{"#data"} = [$nick, "false", $owninfo->{"nick"}]; abuse_backstab_execute; } else { Xchat::command("cs op $channel"); $data{"abuse_backstab"}{"#data"} = [$nick, "true", $owninfo->{"nick"}]; $hooks{"abuse_backstab"} = Xchat::hook_print("Channel Operator", "abuse_backstab_execute"); }	return Xchat::EAT_ALL; }
 * 1) backstab command -- /backstab
 * 2) Ops you if you were not opped already, bans (+b) a nick that has *previously* parted or quit, then deops you if you did not start out opped
 * 3) If you were opped before running the command, this does not deop you
 * 4) This only works on nicks that have *previously* parted or quit.  It will not work on nicks that are still in the channel (unless they previously parted or quit and then came back on)
 * 5) The hostmask of the nick is transformed to be a bit wider before the ban is implemented

sub abuse_backstab_execute { my $nick = $data{"abuse_backstab"}{"#data"}[0]; my $deop = $data{"abuse_backstab"}{"#data"}[1]; my $ownnick = $data{"abuse_backstab"}{"#data"}[2]; my $host = $data{"abuse_backstab"}{$nick}; if(exists($hooks{"abuse_backstab"})) { if($_[0][1] ne $ownnick) { return; #so we don't trigger when OTHER people get opped. }	}	$host =~ s/[ni]=/?=/; $host =~ s/^\~/*/; $host =~ s/[0-9abcdef][0-9abcdef]@/*@/; $host =~ s/\/.*$/\/*/; # user@host/otherstuff => user@host/* $host =~ s/\..*$/.*/; # user@host.stuff.morestuff => user@host.* $host = '*!'. $host; Xchat::command("mode +b $host"); if($deop eq 'true') { Xchat::command("mode -o $ownnick"); }	if(exists($hooks{"abuse_backstab"})) { Xchat::unhook($hooks{"abuse_backstab"}); delete($hooks{"abuse_backstab"}); #unset it so that backstab works while opped }	return; }

sub abuse_backstab_register_part { my $nick = $_[0][0]; my $host = $_[0][1]; $data{"abuse_backstab"}{$nick} = $host; return Xchat::EAT_NONE; }

sub abuse_backstab_register_quit { my $nick = $_[0][0]; my $host = $_[0][2]; $data{"abuse_backstab"}{$nick} = $host; return Xchat::EAT_NONE; }

sub abuse_backstab_register_kick { my $nick = $_[0][1]; my $info = Xchat::user_info( $nick ); my $host = $info->{"host"}; $data{"abuse_backstab"}{$nick} = $host; return Xchat::EAT_NONE; }

sub abuse_rejoinonremove { my $channel = $_[0][2]; my $reason = $_[0][3]; if($reason =~ m/^[Rr]equested by /) { Xchat::command("join $channel"); }	return Xchat::EAT_NONE; }
 * 1) Rejoins a channel if you have been /remove'd from it
 * 2) Obviously only works if the IRCd has the /remove command and the prefix for the message is "requested by"
 * 3) Note that this will also make you rejoin if your /part message begins by "requested by"

sub abuse_ibnull_b { my $channel = $_[0][0]; if(!exists($data{"abuse_unban"}{$channel})) { Xchat::command("msg chanserv unban $channel"); Xchat::command("msg chanserv invite $channel"); $data{"abuse_unban"}{$channel} = 'true'; }	return Xchat::EAT_NONE; }
 * 1) Invite/Ban nullify -- Automatically attempts to re-invite and unban you on channels you cannot join
 * 2) It uses the /msg chanserv invite and /msg chanserv unban, so you need access to those
 * 3) If you don't have access to those, it will stop trying after the first time to prevent a loop
 * 4) The unban does not work completely on networks that do not allow you to join a channel you are banned in even when invited (such as freenode)

sub abuse_ibnull_i { my $channel = $_[0][0]; if(!exists($data{"abuse_invite"}{$channel})) { Xchat::command("msg chanserv invite $channel"); $data{"abuse_invite"}{$channel} = 'true'; }	return Xchat::EAT_NONE; }

sub abuse_ibnull_k { my $channel = $_[0][0]; if(!exists($data{"abuse_keyword"}{$channel})) { Xchat::command("msg chanserv invite $channel"); Xchat::command("msg chanserv getkey $channel"); $data{"abuse_keyword"}{$channel} = 'true'; }	return Xchat::EAT_NONE; }

sub abuse_ibnull_join { my $channel = $_[0][0]; if(exists($data{"abuse_invite"}{$channel}) || exists($data{"abuse_unban"}{$channel}) || exists($data{"abuse_keyword"}{$channel})) { Xchat::command("join $channel"); }	return Xchat::EAT_NONE; }

sub abuse_ibnull_clear { my $channel = $_[0][1]; if(exists($data{"abuse_invite"}{$channel})) { delete($data{"abuse_invite"}{$channel}); }	if(exists($data{"abuse_unban"}{$channel})) { delete($data{"abuse_unban"}{$channel}); }	if(exists($data{"abuse_keyword"}{$channel})) { delete($data{"abuse_keyword"}{$channel}); }	return Xchat::EAT_NONE; }

sub abuse_tempban { my $nick = $_[0][1]; my $time = $_[0][2]; if(!defined($time) || !defined($nick)) { Xchat::print("USAGE: TEMPBAN "); return Xchat::EAT_ALL; }	my $nickinfo = Xchat::user_info($nick); my $host = $nickinfo->{"host"}; $host =~ s/[ni]=/?=/; $host =~ s/^\~/*/; $host =~ s/[0-9abcdef][0-9abcdef]@/*@/; $host =~ s/\/.*$/\/*/; # user@host/otherstuff => user@host/* $host =~ s/\..*$/.*/; # user@host.stuff.morestuff => user@host.* $host = '*!'. $host; Xchat::command("mode +b $host"); Xchat::command("kick $nick Temporarily banned for $time seconds"); $time *= 1000; if(exists($data{"abuse_tempban"})) { $data{"abuse_tempban"}++; } else { $data{"abuse_tempban"} = 0; }	$hooks{"abuse_tempban"}[$data{"abuse_tempban"}] = Xchat::hook_timer($time, "abuse_tempban_unban", {data => [$nick, $host]}); return Xchat::EAT_ALL; }
 * 1) tempban command -- /tempban 
 * 2) Temporarily kickbans (+b) a user on the channel
 * 3) You must have the ability to set +b (usually by being opped) before executing this command, and you must stay opped during the period the user is banned
 * 4) If you deop, then the ban will not get removed
 * 5) This does some changes on the user's hostmask before banning

sub abuse_tempban_unban { my $nick = $_[0][0]; my $host = $_[0][1]; my @ids = $hooks{"abuse_tempban"}; Xchat::unhook($ids[0]); Xchat::command("mode -b $host"); shift(@ids); return Xchat::EAT_ALL; }

sub abuse_filter { #filter out beeps if(!exists($_[0][1])) { #null input due to blank /me or something return Xchat::EAT_NONE; }	my $line = $_[0][1]; $line =~ s/\x07//g; if($line eq "") { #if all the user did was beep, block the message entirely to prevent floods return Xchat::EAT_XCHAT; }	if($line eq $_[0][1]) { #nothing changed return Xchat::EAT_NONE; }	Xchat::emit_print($_[1][0], $_[0][0], $line, $_[0][2], $_[0][3]); return Xchat::EAT_XCHAT; }

sub abuse_ctcpblock { if($data{"abuse_ctcpblock"}[0] == 0) { $hooks{"abuse_ctcpblock"}{"t1"} = Xchat::hook_timer($data{"abuse_ctcpblock"}[2] * 10000, "abuse_ctcpblock_timer"); }	if($data{"abuse_ctcpblock"}[0] >= 5) { Xchat::unhook($hooks{"abuse_ctcpblock"}{"t1"}); $data{"abuse_ctcpblock"}[3]++; $hooks{"abuse_ctcpblock"}{"t2"} = Xchat::hook_timer($data{"abuse_ctcpblock"}[3] * 60000, "abuse_ctcpblock_ignore"); return Xchat::EAT_ALL; }	$data{"abuse_ctcpblock"}[0]++; $data{"abuse_ctcpblock"}[1]++; if($data{"abuse_ctcpblock"}[1] % 10 == 0) { $data{"abuse_ctcpblock"}[2]++; }	return Xchat::EAT_NONE; }

sub abuse_ctcpblock_timer { $data{"abuse_ctcpblock"}[0]--; if($data{"abuse_ctcpblock"}[0] == 0) { delete($hooks{"abuse_ctcpblock"}{"t1"}); return Xchat::REMOVE; }	return Xchat::KEEP; }

sub abuse_ctcpblock_ignore { $data{"abuse_ctcpblock"}[0] = 0; delete($hooks{"abuse_ctcpblock"}{"t2"}); return Xchat::REMOVE; }

sub abuse_ctcpblock_stats { Xchat::print("CTCP flood protection stats:"); Xchat::print("CTCP count: $data{'abuse_ctcpblock'}[0]"); Xchat::print("CTCP master count: $data{'abuse_ctcpblock'}[1]"); Xchat::print("CTCP decrement timeout: $data{'abuse_ctcpblock'}[2]0 seconds"); Xchat::print("CTCP flood timeout: $data{'abuse_ctcpblock'}[3] minutes"); if(exists($hooks{"abuse_ctcpblock"}{"t2"})) { Xchat::print("Current status: Flood mode initiated, blocking all CTCP replies"); } elsif(exists($hooks{"abuse_ctcpblock"}{"t1"})) { Xchat::print("Current status: Decrementing count"); } else { Xchat::print("Current status: Normal"); }	Xchat::print("Reset statistics with /ctcpreset"); return Xchat::EAT_ALL; }

sub abuse_ctcpblock_reset { $data{"abuse_ctcpblock"} = [0, 0, 1, 0]; if(exists($hooks{"abuse_ctcpblock"}{"t1"})) { Xchat::unhook($hooks{"abuse_ctcpblock"}{"t1"}); }	if(exists($hooks{"abuse_ctcpblock"}{"t2"})) { Xchat::unhook($hooks{"abuse_ctcpblock"}{"t2"}); }	Xchat::print("CTCP flood protection successfully reset"); return Xchat::EAT_ALL; }

Commands

 * hammer [ban]:If you are not already opped, it first ops you. Then, it optionally bans the given nick if the ban parameter was defined. Then, it executes a /remove command on the nick. Afterwards, if you did not start out opped, it deops you. This command requires that your IRCd support the /remove command (works on freenode, not sure where else).
 * backstab :If you are not already opped, it first ops you. Then, it bans a nick that has previously part or quit. It will not work on nicks that have not quit or part!. Then, if you were not opped to begin with, it deops you.
 * tempban :Bans and kicks a nick from the channel. After the given number of seconds, it will automatically unban the nick. Requires channel operator status for both the initial kickban and the unbanning.

Triggers

 * autorejoin on remove:Makes you automatically rejoin the channel if you were /remove-d from it. Obviously, this will only work on IRCds that have the /remove command :)
 * +i/b/k nullify:If you are banned from a channel, it will attempt to automatically unban you, invite you in, then make you join. If the channel is invite-only, it will attempt to invite you in, then make your join. If the channel requires a keyword, it will attempt to retrieve the keyword and the join. Requires unban/invite/getkey access to chanserv on the particular channel to work.

Features

 * beep filter:Filters out the beep character from messages (and suppresses the message entirely if it contained nothing but beeps)

CTCP Flood Protection
This feature allows for protection against CTCP floods. The system is still experimental and may not always work. The current algorithm works like so:
 * The script starts out with all variables set at default. Let's name them x, y, z, and t. x starts out as zero, y as zero, z as one, and t as zero.
 * Every CTCP received increments a x and y by one.
 * A timer is started that reduces x by one every z * ten seconds
 * If x reaches or exceeds five, the decrementing timer is removed, t is incremented by one, and the client will ignore all CTCPs for t * sixty seconds. This is called "Flood mode"
 * When the "Flood mode" timer ends, x is reset to zero
 * Every ten CTCPs received increments z by one.
 * Typing /ctcpstats will show the current statistics
 * Typing /ctcpreset will reset all variables to their default values

About
This is a watered down version of the full abuse.pl script that Ryan Schmidt uses. The full version contains commands that (when used) can be construed as op or oper abuse, or can be used for spam purposes. If you would like the full version, please contact Ryan on IRC, and provide a valid reason why you would need the other commands