3 # SoftSnow XChat2 filter script
7 # Filter out fileserver announcements and SPAM on IRC
11 # This script started as an upgrade to the SoftSnow filter script
12 # from http://dukelupus.pri.ee/softsnow/ircscripts/scripts.shtml
13 # (originally http://www.softsnow.biz/softsnow_filter/filter.shtml)
14 # It borrows some ideas from filter-ebooks (#ebooks Xchat2 filter
15 # script) by KiBo, and its older version by RJVJR, mainly moving
16 # from the old IRC:: interface to the new Xchat2 API.
18 # Tested on #ebooks channel on IRCHighWay (irc.irchighway.net)
22 # Place SoftSnow_filter.pl in your ~/.xchat directory
24 ## URL (repositories):
25 # * http://github.com/jnareb/softsnow-xchat2-filter
26 # * http://gitorious.org/projects/softsnow-xchat2-filter
27 # * http://repo.or.cz/w/softsnow_xchat2_filter.git
29 ## ChangeLog (main points only):
32 # * Original version of SoftSnow filter this one is based on
34 # * Add /FILTER command, to turn filter off and on, and to limit
35 # filtering to current IRC server only
37 # * Allow to save and load filter rules from file (UNIX only)
38 # * Add ALLOW rules, for example to show '@search' while filtering '@'
40 # * Use new XChat2 API (Xchat:: instead of IRC::)
42 # * More secure saving rules to a file (always save whole file)
44 # * Allow printing (logging) filtered content to '(filtered)' window
45 # via 'Channel Message' text event, with nick of sender
47 # * /FILTERWINDOW command to control and query of logging filtered
48 # contents to separate '(filtered)' window
52 # * Add GUI and MENU (would require XChat >= 2.4.5)
53 # * Change format of saved rules to 'm/.../' or 'qr{...}';
54 # see YAML (YAML::Types) and Data::Dumper code and output
55 # * Save and read config together with filter rules
56 # * Read default config and rules from __DATA__, add reset
57 # * Save filter rules usage statistics
58 # * Import filter rules from filter-ebooks3.3FINAL script
59 # * Limit filter to specified channels (or all channels)
60 # * Filter private SPAM (SPAM sent to you, but not other)
61 # * ? Don't accept DCC from users not on common channel
62 # * ? Do not accept files, or dangerous files, from regular users
63 # * Color nicks in '(filtered)' window according to matched rule
64 # * Add command to clear '(filtered)' window
65 # * Add option to strip codes from logged filtered lines
66 # * Limit number of lines in '(filtered)' window
67 # * ? Perhaps something about '@find' and '!find' results?
72 use File
::Temp
qw(tempfile);
73 use File
::Copy
qw(move);
76 my $scriptName = "SoftSnow XChat2 Filter";
77 my $scriptVersion = "2.1.4";
78 my $scriptDescr = "Filter out file server announcements and IRC SPAM";
81 my $U = chr 31; # underline
82 my $C = chr 3; # start of color sequence
83 my $R = chr 22; # reverse
84 my $O = chr 15; # reset
87 my $filter_file = Xchat
::get_info
("xchatdir") . "/SoftSnow_filter.conf";
89 my $filter_turned_on = 0; # is filter is turned on
90 my $limit_to_server = ''; # if true limit to given server (host)
91 my $use_filter_allow = 0; # use overrides (ALLOW before DENY)
93 my $filtered_to_window = 0;
94 my $filter_window = "(filtered)";
97 my $filter_commands = 'ON|OFF|STATUS|SERVER|SERVERON|ALL|HELP|DEBUG|PRINT|ALLOW|ADD|DELETE|SAVE|LOAD';
99 my $filter_help = <<"EOF";
100 ${B}/FILTER $filter_commands${B}
101 /FILTER ON|OFF - turns filtering on/off
102 /FILTER HELP - prints this help message
103 /FILTER STATUS - prints if filter is turned on, and with what limits
104 /FILTER DEBUG - shows some info; used in debuggin the filter
105 /FILTER PRINT - prints all the rules
106 /FILTER ALLOW - toggle use of ALLOW rules (before DENY).
107 /FILTER SERVER - limits filtering to current server (host)
108 /FILTER SERVERON - limits to server and turns filter on
109 /FILTER ALL - resumes filtering everywhere i.e. removes limits
110 /FILTER SAVE - saves the rules to the file $filter_file
111 /FILTER LOAD - loads the rules from the file, replacing existing rules
112 /FILTER ADD <rule> - add rule at the end of the DENY rules
113 /FILTER DELETE [<num>] - delete rule number <num>, or last rule
114 /FILTER SHOW [<num>] - show rule number <num>, or last rule
115 /FILTER VERSION - prints the name and version of this script
116 /FILTER WINDOW <arg>... - same as /FILTERWINDOW <arg>...
117 /FILTER without parameter is equivalent to /FILTER STATUS
120 my $filterwindow_commands = 'ON|OFF|CLOSE|HELP|STATUS|DEBUG';
122 my $filterwindow_help = <<"EOF";
123 ${B}/FILTERWINDOW $filterwindow_commands${B}
124 /FILTERWINDOW ON|OFF - turns saving filtered content to ${U}$filter_window${U}
125 /FILTERWINDOW CLOSE - close ${U}$filter_window${U} (and turn off logging)
126 /FILTERWINDOW STATUS - prints if saving to ${U}$filter_window${U} is turned on
127 /FILTERWINDOW HELP - prints this help message
128 /FILTERWINDOW DEBUG - shows some info; used in debugging this part of filter
129 /FILTERWINDOW without parameter is equivalent to /FILTERWINDOW STATUS
132 Xchat
::register
($scriptName, $scriptVersion, $scriptDescr);
134 Xchat
::hook_command
("FILTER", \
&filter_command_handler
,
135 { help_text
=> $filter_help });
136 Xchat
::hook_command
("FILTERWINDOW", \
&filterwindow_command_handler
,
137 { help_text
=> $filterwindow_help });
138 Xchat
::hook_server
("PRIVMSG", \
&privmsg_handler
);
140 Xchat
::print("Loading ${B}$scriptName $scriptVersion${B}...\n");
144 if ($filtered_to_window) {
145 Xchat
::command
("QUERY $filter_window");
148 # information about (default) options used
149 if ($filter_turned_on) {
150 Xchat
::print("Filter turned ${B}ON${B}\n");
152 Xchat
::print("Filter turned ${B}OFF${B}\n");
154 if ($limit_to_server) {
155 Xchat
::print("Filter limited to server $limit_to_server\n")
157 if ($use_filter_allow) {
158 Xchat
::print("Filter uses ALLOW rules\n");
161 # ------------------------------------------------------------
177 q
/(?i)fserve.*trigger/,
179 q
/(?i)trigger.*\/ctcp
/,
181 q
/(?i)file server online/,
190 #messages for when a file is received/failed to receive
191 q
/(?i)DEFINITELY had the right stuff to get/,
192 q
/(?i)has just received/,
193 q
/(?i)I have just received/,
204 q
/brave soldier in the war/,
207 # return 1 (true) if text given as argument is to be filtered out
212 #strip colour, underline, bold codes, etc.
213 $text = Xchat
::strip_code
($text);
215 if ($use_filter_allow) {
216 foreach $regexp (@filter_allow) {
217 return 0 if ($text =~ /$regexp/);
221 foreach $regexp (@filter_deny) {
222 return 1 if ($text =~ /$regexp/);
228 #called when someone says something in the channel
229 #1: address of speaker
232 #4: text said (prefixed with :)
233 sub privmsg_handler
{
234 # $_[0] - array reference containing the IRC message or command
235 # and arguments broken into words
236 # $_[1] - array reference containing the Nth word to the last word
237 my ($address, $msgtype, $channel) = @
{$_[0]};
238 my ($nick, $user, $host) = ($address =~ /^:(.*?)!(.*?)@(.*)$/);
240 my $text = $_[1][3]; # Get server message
242 my $server = Xchat
::get_info
("host");
244 #-- EXAMPLE RAW COMMANDS: --
245 #chanmsg: [':epitaph!~epitaph@CPE00a0241892b7-CM014480119187.cpe.net.cable.rogers.com', 'PRIVMSG', '#werd', ':mah', 'script', 'is', 'doing', 'stuff.']
246 #action: [':rlz!railz@bzq-199-176.red.bezeqint.net', 'PRIVMSG', '#werd', ':\x01ACTION', 'hugs', 'elhaym', '\x01']
247 #private: [':olene!oqd@girli.sh', 'PRIVMSG', 'epinoodle', ':hey']
250 return Xchat
::EAT_NONE
unless $filter_turned_on;
251 if ($limit_to_server) {
252 return Xchat
::EAT_NONE
unless $server eq $limit_to_server;
254 # do not filter out private messages
255 return Xchat
::EAT_NONE
unless ($channel =~ /^#/);
259 if (isFiltered
($text)) {
260 if (defined $nick && $filtered_to_window) {
261 #Xchat::print($text, $filter_window)
263 my $ctx = Xchat
::get_context
();
264 Xchat
::set_context
($filter_window);
265 Xchat
::emit_print
('Channel Message', $nick, $text);
266 Xchat
::set_context
($ctx);
268 #return Xchat::EAT_XCHAT;
269 return Xchat
::EAT_ALL
;
271 return Xchat
::EAT_NONE
;
275 # ------------------------------------------------------------
278 my ($fh, $tmpfile) = tempfile
($filter_file.'.XXXXXX', UNLINK
=>1);
281 Xchat
::print("${B}FILTER:${B} ".
282 "Couldn't open temporary file $tmpfile to save filter: $!\n");
286 Xchat
::print("${B}FILTER SAVE >$filter_file${B}\n");
287 foreach my $regexp (@filter_deny) {
288 Xchat
::print("/".$regexp."/ saved\n");
289 print $fh $regexp."\n";
293 Xchat
::print("${B}FILTER:${B} Couldn't close file to save filter: $!\n");
296 #move($tmpfile, $filter_file);
297 rename($tmpfile, $filter_file);
298 Xchat
::print("${B}FILTER SAVED ----------${B}\n");
306 Xchat
::print("${B}FILTER:${B} ...loading filter patterns\n");
307 unless (open $fh, '<', $filter_file) {
308 Xchat
::print("${B}FILTER:${B} Couldn't open file to load filter: $!\n");
312 @filter_deny = <$fh>;
313 map (chomp, @filter_deny);
316 Xchat
::print("${B}FILTER:${B} Couldn't close file to load filter: $!\n");
320 Xchat
::print("${B}FILTER DENY ----------${B}\n");
321 for (my $i = 0; $i <= $#filter_deny; $i++) {
322 Xchat
::print(" [$i]: /".$filter_deny[$i]."/\n");
324 Xchat
::print("${B}FILTER DENY ----------${B}\n");
330 # always ading rules at the end
331 push @filter_deny, $rule;
334 sub delete_rule
( $ ) {
335 my $num = shift || $#filter_deny;
337 splice @filter_deny, $num, 1;
340 # ============================================================
341 # ------------------------------------------------------------
342 # ............................................................
345 Xchat
::print("${B}$scriptName $scriptVersion${B}\n");
346 Xchat
::print(" * URL: http://github.com/jnareb/softsnow-xchat2-filter\n");
347 Xchat
::print(" * URL: http://gitorious.org/projects/softsnow-xchat2-filter\n");
348 Xchat
::print(" * URL: http://repo.or.cz/w/softsnow_xchat2_filter.git\n");
354 if ($filter_turned_on) {
355 Xchat
::print("Filter is turned ${B}ON${B}\n");
357 Xchat
::print("Filter is turned ${B}OFF${B}\n");
359 if ($limit_to_server) {
360 if ($server eq $limit_to_server) {
361 Xchat
::print("Filter is limited to ${B}current${B} ".
362 "server $limit_to_server\n");
364 Xchat
::print("Filter is limited to server ".
365 "$limit_to_server != $server\n");
368 if ($use_filter_allow) {
369 Xchat
::print("Filter is using ALLOW rules (before DENY)\n");
374 Xchat
::print("${B}FILTER DEBUG ----------${B}\n");
375 Xchat
::print("Channel: ".Xchat
::get_info
("channel")."\n");
376 Xchat
::print("Host: ".Xchat
::get_info
("host")."\n");
377 Xchat
::print("Server: ".Xchat
::get_info
("server")."\n");
378 Xchat
::print("Server Id: ".Xchat
::get_info
("id")."\n");
379 Xchat
::print("Network: ".Xchat
::get_info
("network")."\n");
381 Xchat
::printf("%3u %s rules\n", scalar(@filter_allow), "allow");
382 Xchat
::printf("%3u %s rules\n", scalar(@filter_deny), "deny");
383 Xchat
::print("${B}FILTER DEBUG ----------${B}\n");
386 sub cmd_server_limit
{
390 # adding limiting to given (single) server
391 if ($limit_to_server) {
392 Xchat
::print("${B}FILTER:${B} Changing server from $limit_to_server to $server\n");
393 Xchat
::print("[FILTER LIMITED TO SERVER ${B}$server${B} (WAS TO $limit_to_server)]",
396 Xchat
::print("${B}FILTER:${B} Limiting filtering to server $server\n");
397 Xchat
::print("[FILTER LIMITED TO SERVER ${B}$server${B} (WAS UNLIMITED)]",
400 $limit_to_server = $server;
403 # removing limiting to server
404 if ($limit_to_server) {
405 Xchat
::print("${B}FILTER:${B} Removing limit to server $limit_to_server\n");
406 Xchat
::print("[FILTER ${B}NOT LIMITED${B} TO SERVER (WAS TO $limit_to_server)]",
409 $limit_to_server = '';
414 sub cmd_print_rules
{
415 Xchat
::print("${B}FILTER PRINT ----------${B}\n");
416 Xchat
::print("${B}ALLOW${B}".($use_filter_allow ?
' (on)' : ' (off)')."\n");
418 for (my $i = 0; $i <= $#filter_allow; $i++) {
419 Xchat
::print("[$i]: /".$filter_allow[$i]."/\n");
421 Xchat
::print("${B}DENY${B}\n");
422 for (my $i = 0; $i <= $#filter_deny; $i++) {
423 Xchat
::print("[$i]: /".$filter_deny[$i]."/\n");
425 Xchat
::print("${B}FILTER PRINT ----------${B}\n");
433 Xchat
::print("${B}FILTER RULE [$#filter_deny]:${B} /$rule/\n");
435 Xchat
::print("Syntax: ${B}/FILTER ADD ${U}rule${U}${B} to add\n")
439 sub cmd_delete_rule
{
443 $num =~ s/^\s*(.*?)\s*$/$1/g if $num;
446 Xchat
::print("${B}FILTER:${B} deleting /".$filter_deny[-1]."/\n");
448 Xchat
::print("${B}FILTER:${B} deleted successfully last rule\n");
451 if ($num !~ /^\d+$/) {
452 Xchat
::print("${B}FILTER:${B} $num is not a number\n");
455 if ($num < 0 || $num > $#filter_deny) {
456 Xchat
::print("${B}FILTER:${B} $num outside range [0,$#filter_deny]\n");
461 Xchat
::print("${B}FILTER:${B} deleting /".$filter_deny[$num]."/\n");
463 Xchat
::print("${B}FILTER:${B} deleted successfully rule $num\n");
471 $num =~ s/^\s*(.*?)\s*$/$1/g if $num;
473 if (defined $num && $num !~ /^\d+$/) {
474 Xchat
::print("${B}FILTER:${B} $num is not a number\n");
475 } elsif (defined $num && !defined $filter_deny[$num]) {
476 Xchat
::print("${B}FILTER:${B} rule $num does not exist\n");
478 Xchat
::print("${B}FILTER:${B} ".(defined $num ?
"[$num]" : "last").
479 " rule /".$filter_deny[defined $num ?
$num : -1]."/\n");
483 # ============================================================
484 # ============================================================
485 # ============================================================
487 sub filter_command_handler
{
488 my $cmd = $_[0][1]; # 1st parameter (after FILTER)
489 my $arg = $_[1][2]; # 2nd word to the last word
490 my $server = Xchat
::get_info
("host");
493 if (!$cmd || $cmd =~ /^STATUS$/i) {
496 } elsif ($cmd =~ /^ON$/i) {
497 $filter_turned_on = 1;
498 Xchat
::print("Filter turned ${B}ON${B}\n");
499 Xchat
::print("[FILTER TURNED ${B}ON${B}]",
502 } elsif ($cmd =~ /^OFF$/i) {
503 $filter_turned_on = 0;
504 Xchat
::print("Filter turned ${B}OFF${B}\n");
505 Xchat
::print("[FILTER TURNED ${B}OFF${B}]",
508 } elsif ($cmd =~ /^SERVER$/i) {
509 cmd_server_limit
($server);
511 } elsif ($cmd =~ /^SERVERON$/i) {
512 cmd_server_limit
($server);
514 Xchat
::print("[FILTER TURNED ${B}ON${B}]",
516 if (!$filter_turned_on);
517 $filter_turned_on = 1;
518 Xchat
::print("Filter turned ${B}ON${B}\n");
520 } elsif ($cmd =~ /^ALL$/i) {
521 cmd_server_limit
(undef);
523 } elsif ($cmd =~ /^HELP$/i) {
524 Xchat
::print($filter_help);
525 Xchat
::print($filterwindow_help);
527 } elsif ($cmd =~ /^VERSION$/i) {
530 } elsif ($cmd =~ /^DEBUG$/i || $cmd =~ /^INFO$/i) {
533 } elsif ($cmd =~ /^(?:PRINT|LIST)$/i) {
536 } elsif ($cmd =~ /^ALLOW$/i) {
537 $use_filter_allow = !$use_filter_allow;
538 Xchat
::print("${B}FILTER:${B} ALLOW rules ".
539 ($use_filter_allow ?
"enabled" : "disabled")."\n");
541 } elsif ($cmd =~ /^ADD$/i) {
544 } elsif ($cmd =~ /^DEL(?:ETE)$/i) {
545 cmd_delete_rule
($arg);
547 } elsif ($cmd =~ /^SHOW$/i) {
550 } elsif ($cmd =~ /^SAVE$/i) {
552 Xchat
::print("${B}FILTER:${B} saved DENY rules to $filter_file\n");
554 } elsif ($cmd =~ /^(RE)?LOAD$/i) {
556 Xchat
::print("${B}FILTER:${B} loaded DENY rules from $filter_file\n");
558 } elsif ($cmd =~ /^WINDOW$/i) {
559 return filterwindow_command_handler
(
560 [ 'FILTERWINDOW', @
{$_[0]}[2..$#{$_[0]}] ],
561 [ "FILTERWINDOW $_[1][2]", @
{$_[1]}[2..$#{$_[1]}] ],
566 Xchat
::print("Unknown command ${B}/FILTER $_[1][1]${B}\n") if $cmd;
571 sub filterwindow_command_handler
{
572 my $cmd = $_[0][1]; # 1st parameter (after FILTER)
573 #my $arg = $_[1][2]; # 2nd word to the last word
574 my $ctx = Xchat
::find_context
($filter_window);
576 if (!$cmd || $cmd =~ /^STATUS$/i) {
577 Xchat
::print(($filtered_to_window ?
"Show" : "Don't show").
578 " filtered content in ".
579 (defined $ctx ?
"open" : "closed").
580 " window ${B}$filter_window${B}\n");
582 } elsif ($cmd =~ /^DEBUG$/i) {
583 my $ctx_info = Xchat
::context_info
($ctx);
584 Xchat
::print("${B}FILTERWINDOW DEBUG ----------${B}\n");
585 Xchat
::print("filtered_to_window = $filtered_to_window\n");
586 Xchat
::print("filter_window = $filter_window\n");
588 Xchat
::print("$filter_window is ${B}open${B}\n");
589 Xchat
::print("$filter_window: network => $ctx_info->{network}\n")
590 if defined $ctx_info->{'network'};
591 Xchat
::print("$filter_window: host => $ctx_info->{host}\n")
592 if defined $ctx_info->{'host'};
593 Xchat
::print("$filter_window: channel => $ctx_info->{channel}\n");
594 Xchat
::print("$filter_window: server_id => $ctx_info->{id}\n")
595 if defined $ctx_info->{'id'};
597 Xchat
::print("$filter_window is ${B}closed${B}\n");
599 # requires XChat >= 2.8.2
600 #Xchat::print("'Channel Message' format: ".
601 # Xchat::get_info("event_text Channel Message")."\n");
602 #Xchat::print("'Channel Msg Hilight' format: ".
603 # Xchat::get_info("event_text Channel Msg Hilight")."\n");
604 Xchat
::print("${B}FILTERWINDOW DEBUG ----------${B}\n");
606 } elsif ($cmd =~ /^ON$/i) {
607 Xchat
::command
("QUERY $filter_window");
608 Xchat
::print("${B}----- START LOGGING FILTERED CONTENTS -----${B}\n",
610 if !$filtered_to_window;
612 $filtered_to_window = 1;
613 Xchat
::print("Filter shows filtered content in ${B}$filter_window${B}\n");
615 } elsif ($cmd =~ /^(?:OFF|CLOSE)$/i) {
616 Xchat
::print("${B}----- STOP LOGGING FILTERED CONTENTS -----${B}\n",
618 if $filtered_to_window;
619 Xchat
::command
("CLOSE", $filter_window)
620 if ($cmd =~ /^CLOSE$/i);
622 $filtered_to_window = 0;
623 Xchat
::print("Filter doesn't show filtered content in ${B}$filter_window${B}\n");
624 Xchat
::print("${B}FILTER:${B} ${B}$filter_window${B} closed\n")
625 if ($cmd =~ /^CLOSE$/i);
627 } elsif ($cmd =~ /^HELP$/i) {
628 Xchat
::print($filterwindow_help);
631 Xchat
::print("Unknown command ${B}/FILTERWINDOW $_[1][1]${B}\n") if $cmd;
632 Xchat
::print("${B}${U}USAGE:${U} /FILTERWINDOW $filterwindow_commands${B}\n");
638 # ======================================================================
639 # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
640 # ----------------------------------------------------------------------
642 Xchat
::print("${B}$scriptName $scriptVersion${B} loaded\n",
643 " For help: ${B}/FILTER HELP${B}\n");