diff --git a/.cvsignore b/.cvsignore index e69de29..c33e136 100644 --- a/.cvsignore +++ b/.cvsignore @@ -0,0 +1 @@ +irssi-0.8.9.tar.bz2 diff --git a/convert-replace-trigger.pl b/convert-replace-trigger.pl new file mode 100644 index 0000000..f44cf7f --- /dev/null +++ b/convert-replace-trigger.pl @@ -0,0 +1,109 @@ +# This script converts replaces saved by replace.pl to a triggers-file that can be used by trigger.pl +# This is not an irssi script. You should use this from the command line. +# Usage: +# perl convert-replace-trigger.pl [irssi-dir] +# if no directory is specified, it will asume you use the default ~/.irssi +# +# http://wouter.coekaerts.be/irssi/ + +use strict; +use warnings; +use IO::File; +use Data::Dumper; + +print "convert-replace-trigger 1.1\n"; + +my (@replaces, @triggers, $irssidir); + +if (!$ARGV[0]) { + $irssidir = $ENV{'HOME'}; + print "no arguments given, assuming your irssi directory is $irssidir\n"; +} else { + $irssidir = $ARGV[0]; + print "using $irssidir as irssi directory\n"; +} + +$irssidir =~ s/\/$//; # strip last / +print "reading replaces from $irssidir/replaces...\n"; +my $io = new IO::File "$irssidir/replaces", "r"; +if (not defined $io) { + print "error opening replaces file, aborting\n"; + exit; +} +if (defined $io) { + no strict 'vars'; + my $text; + $text .= $_ foreach ($io->getlines); + my $rep = eval "$text"; + @replaces = @$rep if ref $rep; + $io->close(); +} +print(@replaces . " replaces loaded\n"); + +if (-e "$irssidir/triggers") { # there already is a triggers file + print "reading existing triggers from $irssidir/triggers...\n"; + $io = new IO::File "$irssidir/triggers", "r"; + if (not defined $io) { + print "triggers file already exists, but I can't open it. please remove it.\n"; + exit; + } + no strict 'vars'; + my $text; + $text .= $_ foreach ($io->getlines); + my $rep = eval "$text"; + @triggers = @$rep if ref $rep; + $io->close(); +} + +if (@triggers) { + print (@triggers . " triggers already exist in the triggers file, keeping them\n"); +} + +print "converting...\n"; +REPLACE: +foreach my $replace (@replaces) { + if ($replace->{'case'}) { + delete $replace->{'case'}; + } else { + $replace->{'modifiers'} = 'i'; + } + + if ($replace->{'command'}) { + $replace->{'command'} = $replace->{'replacement'}; + } else { + $replace->{'replace'} = $replace->{'replacement'}; + } + delete $replace->{'replacement'}; + + my $isduplicate = 1; + foreach my $trigger (@triggers) { + if (scalar(keys(%$trigger)) == scalar(keys(%$replace))) { + foreach my $key (keys(%$trigger)) { + if (!(defined($trigger->{$key}) && $replace->{$key} eq $trigger->{$key})) { + $isduplicate = 0; + } + } + } else { + $isduplicate = 0; + } + } + + if ($isduplicate) { + print "skipping duplicate trigger\n"; + next REPLACE; + } + + push @triggers, $replace; +} + +print "saving triggers...\n"; +$io = new IO::File "$irssidir/triggers", "w"; +if (!defined $io) { + print "Error writing triggers\n"; + exit; +} +my $dumper = Data::Dumper->new([\@triggers]); +$dumper->Purity(1)->Deepcopy(1); +$io->print($dumper->Dump); +$io->close; +print("Done. replaces successfully converted to triggers.\n"); diff --git a/convert-replace-trigger.pl-fixpath-patch b/convert-replace-trigger.pl-fixpath-patch new file mode 100644 index 0000000..a788278 --- /dev/null +++ b/convert-replace-trigger.pl-fixpath-patch @@ -0,0 +1,11 @@ +--- convert-replace-trigger.pl.orig 2003-12-20 16:58:20.000000000 +0100 ++++ convert-replace-trigger.pl 2004-01-29 01:40:09.000000000 +0100 +@@ -16,7 +16,7 @@ + my (@replaces, @triggers, $irssidir); + + if (!$ARGV[0]) { +- $irssidir = $ENV{'HOME'}; ++ $irssidir = $ENV{'HOME'} . "/.irssi"; + print "no arguments given, assuming your irssi directory is $irssidir\n"; + } else { + $irssidir = $ARGV[0]; diff --git a/irssi.spec b/irssi.spec new file mode 100644 index 0000000..ce0e542 --- /dev/null +++ b/irssi.spec @@ -0,0 +1,150 @@ +%define perl_vendorarch %(eval "`perl -V:installvendorarch`"; echo $installvendorarch) + +Summary: Modular text mode IRC client with Perl scripting +Name: irssi +Version: 0.8.9 +Release: 0.fdr.3.rh90 +Epoch: 0 +License: GPL +Group: Applications/Communications +URL: http://irssi.org/ +Source0: http://irssi.org/files/%{name}-%{version}.tar.bz2 +Source1: http://wouter.coekaerts.be/irssi/scripts/replace.pl +Source2: http://wouter.coekaerts.be/irssi/scripts/trigger.pl +Source3: http://wouter.coekaerts.be/irssi/scripts/convert-replace-trigger.pl +Patch1: convert-replace-trigger.pl-fixpath-patch +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot +BuildRequires: ncurses-devel openssl-devel zlib-devel pkgconfig gc-devel +%{?_with_glib1:BuildRequires: glib-devel} +%{!?_with_glib1:BuildRequires: glib2-devel} + +# ---------------------------------------------------------------------------- + +%description +Irssi is a modular IRC client with Perl scripting. Only text-mode +frontend is currently supported. The GTK/GNOME frontend is no longer +being maintained. + +# ---------------------------------------------------------------------------- + +%prep +%setup -q +install -m 0644 %{SOURCE3} . +%patch1 -p0 + +# ---------------------------------------------------------------------------- + +%build +%configure --enable-ipv6 --with-textui \ +%{?_with_glib1: --with-glib1} \ + --with-proxy \ + --with-bot \ + --with-perl=module \ + --with-perl-lib=vendor --with-gc +make %{?_smp_mflags} + +# ---------------------------------------------------------------------------- + +%install +rm -rf $RPM_BUILD_ROOT +%makeinstall PERL_INSTALL_ROOT=$RPM_BUILD_ROOT + +# Extra script +install -m 0644 %{SOURCE1} $RPM_BUILD_ROOT%{_datadir}/%{name}/scripts +install -m 0644 %{SOURCE2} $RPM_BUILD_ROOT%{_datadir}/%{name}/scripts + +# Clean up +rm -f $RPM_BUILD_ROOT%{_libdir}/%{name}/modules/lib*.*a +rm -Rf $RPM_BUILD_ROOT/%{_docdir}/%{name} +find $RPM_BUILD_ROOT%{perl_vendorarch} -type f -a -name '*.bs' -a -empty -exec rm -f {} ';' +find $RPM_BUILD_ROOT%{perl_vendorarch} -type f -a -name .packlist -exec rm {} ';' + +# Fix permissions +chmod -R u+w $RPM_BUILD_ROOT%{perl_vendorarch} + +# ---------------------------------------------------------------------------- + +%clean +rm -rf $RPM_BUILD_ROOT + +# ---------------------------------------------------------------------------- + +%files +%defattr(-,root,root,-) +%doc docs/*.txt docs/*.html AUTHORS COPYING NEWS README TODO convert-replace-trigger.pl +%config(noreplace) %{_sysconfdir}/%{name}.conf +%dir %{_libdir}/perl*/* +%dir %(eval "`perl -V:installvendorlib`"; echo $installvendorlib) +%dir %{perl_vendorarch} +%dir %{perl_vendorarch}/auto +%{_bindir}/%{name} +%{_bindir}/botti +%{_datadir}/%{name} +%{_libdir}/%{name} +%{_mandir}/man1/%{name}.1* +%{perl_vendorarch}/Irssi* +%{perl_vendorarch}/auto/Irssi + +# ---------------------------------------------------------------------------- + +%changelog +* Fri Feb 6 2004 Dams 0:0.8.9-0.fdr.3 +- Patch from Michael Schwendt to fix convert-replace-trigger script + (bug #1120 comment #3) + +* Sat Dec 20 2003 Dams 0:0.8.9-0.fdr.2 +- Fixed changelog typo +- Added trigger.pl as replace.pl wont be maintained anymore +- Updated replace.pl to 0.1.4 version +- Added replace.pl URL in Source tag +- Removed .packlist files +- Added as doc a script to convert pref from replace.pl to trigger.pl + +* Thu Dec 11 2003 Dams 0:0.8.9-0.fdr.1 +- Updated to 0.8.9 + +* Mon Nov 24 2003 Dams 0:0.8.8-0.fdr.1 +- Updated to 0.8.8 +- Enabled gc + +* Sun Sep 14 2003 Dams 0:0.8.6-0.fdr.13 +- Rebuild + +* Sun Sep 14 2003 Michael Schwendt 0:0.8.6-0.fdr.12 +- apply openssl patch only if openssl-devel supports pkgconfig + +* Thu Sep 11 2003 Dams 0:0.8.6-0.fdr.11 +- Installing replace.pl in good directory + +* Thu Sep 11 2003 Dams 0:0.8.6-0.fdr.10 +- Rebuild + +* Thu Sep 11 2003 Dams 0:0.8.6-0.fdr.9 +- Using vendor perl directories + +* Thu Sep 11 2003 Dams 0:0.8.6-0.fdr.8 +- Added missing unowned directories +- Added an additionnal useful perl script (replace.pl) + +* Tue Aug 5 2003 Dams 0:0.8.6-0.fdr.7 +- Added zlib-devel buildrequires + +* Sat Jul 12 2003 Dams 0:0.8.6-0.fdr.6 +- Applied Patches from Ville Skyttä (bug #277 comment #11 and + comment #12) + +* Mon Jun 23 2003 Dams 0:0.8.6-0.fdr.5 +- Modified BuildRequires for ssl + +* Wed Jun 11 2003 Dams 0:0.8.6-0.fdr.4 +- Added another dir entry + +* Sun Jun 8 2003 Dams 0:0.8.6-0.fdr.3 +- Added some dir entry in file section + +* Tue May 20 2003 Dams 0:0.8.6-0.fdr.2 +- Exclude modules ".a" files +- Include more files as doc + +* Sat May 10 2003 Dams +- Initial build. diff --git a/replace.pl b/replace.pl new file mode 100644 index 0000000..4ab6fe7 --- /dev/null +++ b/replace.pl @@ -0,0 +1,415 @@ +# This script is no longer maintained, please use trigger.pl instead, it has all the features of this script and much more. +# There is a script for converting your replaces-file to a triggers-file on http://wouter.coekaerts.be/irssi/replace.html + +use strict; +use Irssi 20020324 qw (command_bind command_runsub command signal_add_first signal_continue); +use Text::ParseWords; +use IO::File; +use Data::Dumper; +use vars qw($VERSION %IRSSI); + +$VERSION = '0.1.4'; +%IRSSI = ( + authors => 'Wouter Coekaerts', + contact => 'wouter@coekaerts.be, coekie@#irssi', + name => 'replace', + description => '(replaces regexps in incoming events) Please use trigger.pl instead', + license => 'GPLv2', + url => 'http://wouter.coekaerts.be/irssi/', + changed => '20/09/03', +); + +Irssi::print("%RThis script is no longer maintained, please use trigger.pl instead, it has all the features of this script and much more."); +Irssi::print("%RThere is a script for converting your replace-file to a trigger-file on http://wouter.coekaerts.be/irssi/replace.html"); + +my @replaces; + +sub cmd_help { + print ( <] [-masks ] [-command] + [-[no]] +REPLACE DELETE | +REPLACE LIST +REPLACE SAVE +REPLACE RELOAD + + -[no]case: regexp is [not] case sensitive + -channels: only in . a space-delimited list. (use quotes) + -masks: only for messages from someone mathing one of the masks + (space seperated) + -<[no]types>: [don't] replace these types. The different types are: + -publics,-privmsgs,-actions,-parts,-quits,-kicks, + -topics,-privactions,-all + -all is an alias for all of them, and is the default. + -command: isn't a replacement, but an irssi-command + \$S is expanded to the server tag, \$C to channelname + \$N to nickname, \$A to his address (foo\@bar.com), + \$M to the message + +Examples: + replace every occurence of shit on #chan1 and #chan2 on ircnet with sh*t + /REPLACE ADD -channels \"#chan1 ircnet/#chan2\" shit sh*t + strip all colorcodes from *!lamer\@* + /REPLACE ADD -masks *!lamer\@* '\\x03\\d?\\d?(,\\d\\d?)?|\\x02|\\x1f|\\x16|\\x06' '' + strip backgroundcolors from quitmessages + /REPLACE ADD -quits '\\003(\\d?\\d?),\\d\\d?' '\\003\\\$1' + never let *!bot1\@foo.bar and *!bot2\@foo.bar hilight you + /REPLACE ADD -masks '*!bot1\@foo.bar *!bot2\@foo.bar' mynick my\\x02\\x02nick + avoid being hilighted by !top10 in eggdrops with stats.mod + /REPLACE ADD -case '(Top.0\\(.*\\): 1.*)mynick' '\$1my\\x02\\x02nick' + Convert a Windows-1252 Euro to an ISO-8859-15 Euro (same effect as euro.pl) + /REPLACE ADD '\\x80' '\\xA4' + Show tabs as spaces, not the inverted I (same effect as tab_stop.pl) + /REPLACE ADD '\\t' ' ' + +EOF + ); +} + +#switches in -all option +my @replace_all_switches = ('publics','privmsgs','actions','privactions','parts','quits','kicks','topics'); +#list of all switches +my @replace_switches = ('case','command'); +push @replace_switches,@replace_all_switches; +#list of all options (including switches) +my @replace_options = ('all','masks','channels'); +push @replace_options, @replace_switches; + + + +############################################ +### catch the signals & do the replacing ### +############################################ + +# "message public", SERVER_REC, char *msg, char *nick, char *address, char *target +signal_add_first("message public" => sub {check_signal_message(\@_,1,4,2,3,'publics');}); +# "message private", SERVER_REC, char *msg, char *nick, char *address +signal_add_first("message private" => sub {check_signal_message(\@_,1,-1,2,3,'privmsgs');}); +# "message irc action", SERVER_REC, char *msg, char *nick, char *address, char *target +signal_add_first("message irc action" => sub { + if ($_[4] eq $_[0]->{nick}) { + check_signal_message(\@_,1,-1,2,3,'actions'); + } else { + check_signal_message(\@_,1,4,2,3,'actions'); + } +}); +# "message part", SERVER_REC, char *channel, char *nick, char *address, char *reason +signal_add_first("message part" => sub {check_signal_message(\@_,4,1,2,3,'parts');}); +# "message quit", SERVER_REC, char *nick, char *address, char *reason +signal_add_first("message quit" => sub {check_signal_message(\@_,3,-1,1,2,'quits');}); +# "message kick", SERVER_REC, char *channel, char *nick, char *kicker, char *address, char *reason +signal_add_first("message kick" => sub {check_signal_message(\@_,5,1,3,4,'kicks');}); +# "message topic", SERVER_REC, char *channel, char *topic, char *nick, char *address +signal_add_first("message topic" => sub {check_signal_message(\@_,2,1,3,4,'topics');}); + +# do the replaces on $signal's $parammessage parameter, for replaces with $condition set +# in $paramchannel, for $paramnick!$paramaddress +# set $param* to -1 if not present +sub check_signal_message { + my ($signal,$parammessage,$paramchannel,$paramnick,$paramaddress,$condition) = @_; + my ($replace, $channel, $matches); + my $changed = 0; + my $server = $signal->[0]; + foreach $replace (@replaces) { + if (!$replace->{"$condition"}) { + next; # wrong type of message + } + if ($replace->{'channels'} && $paramchannel != -1) { # check if the channel matches + $matches = 0; + foreach $channel (split(/ /,$replace->{'channels'})) { + if (lc($signal->[$paramchannel]) eq $channel + || lc($server->{'tag'}.'/'.$signal->[$paramchannel]) eq $channel + || lc($server->{'tag'}.'/') eq $channel) { + $matches = 1; + last; # this channel matches, stop checking channels + } + } + if (!$matches) { + next; # this replace doesn't match, try next replace... + } + } + # check the mask + if ($replace->{'masks'} && !$server->masks_match($replace->{'masks'}, $signal->[$paramnick], $signal->[$paramaddress])) { + next; # this replace doesn't match + + } + # if were here, this replace matches + if (!$replace->{'command'}) { # normal replace + if ($replace->{'case'}) { + eval('$changed = ($signal->[$parammessage] =~ s§'. $replace->{'regexp'} . '§' . $replace->{'replacement'} . '§g) || $changed;'); + } else { + eval('$changed = ($signal->[$parammessage] =~ s§'. $replace->{'regexp'} . '§' . $replace->{'replacement'} . '§gi) || $changed;'); + } + } else { # command + if (($replace->{'case'} && $signal->[$parammessage] =~ /$replace->{'regexp'}/) + ||(!$replace->{'case'} && $signal->[$parammessage] =~ /$replace->{'regexp'}/i)) { + my $command = $replace->{'replacement'}; + $command =~ s/\$M/$signal->[$parammessage]/g; + $command =~ s/\$S/$server->{'tag'}/g; + if ($paramchannel != -1) {$command =~ s/\$C/$signal->[$paramchannel]/g;} + if ($paramnick != -1) {$command =~ s/\$N/$signal->[$paramnick]/g;} + if ($paramaddress != -1) {$command =~ s/\$A/$signal->[$paramaddress]/g;} + $server->command($command); + } + } + # + } + if ($changed) { + signal_continue(@$signal); + } +} +# expand($display,"C",$name,"N",$number,"M",$mode,"H",$hilight,"S","}{sb_background}") +# stolen from chanact :) +sub expand { + my ($string, %format) = @_; + my ($exp, $repl); + $string =~ s/\$$exp/$repl/g while (($exp, $repl) = each(%format)); + return $string; +} + +# ugly function, if you know a better way (where /replace add 'a(.)a' '$1' works) +sub do_replace { + my ($text,$replace,$changed) = @_; + if (!$replace->{'command'}) { # normal replace + if ($replace->{'case'}) { + eval('$_[2] = $_[0] =~ s/'. $replace->{'regexp'} . '/' . $replace->{'replacement'} . '/g || $changed;'); + } else { + eval('$_[2] = $_[0] =~ s/'. $replace->{'regexp'} . '/' . $replace->{'replacement'} . '/gi || $changed;'); + } + } else { # command + #if ($replace->{'case'} { + if ($text =~ /$replace->{'regexp'}/) { + command($replace->{'replacement'}); + } + #} + } +} + +################################ +### manage the replaces-list ### +################################ + +# REPLACE SAVE +sub cmd_save { + my $filename = Irssi::settings_get_str('replace_file'); + my $io = new IO::File $filename, "w"; + if (defined $io) { + my $dumper = Data::Dumper->new([\@replaces]); + $dumper->Purity(1)->Deepcopy(1); + $io->print($dumper->Dump); + $io->close; + } + Irssi::print("Replaces saved to ".$filename); +} + +sub sig_command_script_unload { + my $script = shift; + if ($script =~ /(.*\/)?$IRSSI{'name'}\.pl$/) { + cmd_save(); + } +} + +# REPLACE LOAD +sub cmd_load { + my $filename = Irssi::settings_get_str('replace_file'); + my $io = new IO::File $filename, "r"; + if (not defined $io) { + print ERROR "error opening replaces file"; + return; + } + if (defined $io) { + no strict 'vars'; + my $text; + $text .= $_ foreach ($io->getlines); + my $rep = eval "$text"; + @replaces = @$rep if ref $rep; + } + Irssi::print("Replaces loaded from ".$filename); +} + +# converts a replace back to '-options "foo" "bar"' form +sub to_string { + my ($replace) = @_; + my $string = "\'$replace->{'regexp'}\' \'$replace->{'replacement'}\'"; + + # check if all @replace_all_switches are set + my $all_set = 1; + foreach my $switch (@replace_all_switches) { + if (!$replace->{$switch}) { + $all_set = 0; + last; + } + } + if ($all_set) { + $string = '-all '.$string; + if ($replace->{'case'}) { + $string = '-case '.$string; + } + } else { + foreach my $switch (@replace_switches) { + if ($replace->{$switch}) { + $string = '-'.$switch.' '.$string; + } + } + } + if ($replace->{'channels'}) { + $string = "-channels \"$replace->{'channels'}\" ".$string; + } + if ($replace->{'masks'}) { + $string = "-masks \"$replace->{'masks'}\" ".$string; + } + + return $string; +} + +# find a replace (for ADD and DELETE), returns index of replace, or -1 if not found +sub find_replace { + my ($regexp) = @_; + for (my $i=0;$i{'regexp'} eq $regexp) { + return $i; + } + } + return -1; # not found +} + + +# REPLACE ADD [-options(@replace_options)] (see help for more) +sub cmd_add { + my ($data, $server, $item) = @_; + my @args = &shellwords($data); + + # get regexp and replacement + my $replacement = pop @args; + my $regexp = pop @args; + if (not defined $regexp) { + print ERROR "not enough parameters"; + return; + } + + # find existing, or make new replace + my $replace; + my $index = find_replace($regexp); + if ($index != -1) { # change existing replace + $replace = $replaces[$index]; + } else { # new replace + $replace = {'regexp' => $regexp}; + } + $replace->{'replacement'} = $replacement; + + # parse options + my $arg = shift @args; + while ($arg) { + # -channels + if ($arg eq '-channels') { + $replace->{'channels'} = lc(shift @args); + } + # -masks + if ($arg eq '-masks') { + $replace->{'masks'} = shift @args; + } + # -all + if ($arg eq '-all') { + foreach my $switch (@replace_all_switches) { + $replace->{$switch} = 1; + } + } + # - + foreach my $switch (@replace_switches) { + # - + if ($arg eq '-'.$switch) { + $replace->{$switch} = 1; + last; + } + # -no + elsif ($arg eq '-no'.$switch) { + $replace->{$switch} = undef; + last; + } + } + $arg = shift @args; + } + + #check if some switch from replace_all_switches is set + my $some_switch = 0; + foreach my $switch (@replace_all_switches) { + if ($replace->{$switch}) { + $some_switch = 1; + last; + } + } + + # if no switch set, default to all + if (not $some_switch) { + foreach my $switch (@replace_all_switches) { + $replace->{$switch} = 1; + } + } + + if ($index == -1) { # new replace + push @replaces, $replace; + Irssi::print("Added replace " . scalar(@replaces) .": ". to_string($replace)); + } else { # change existing replace + $replaces[$index] = $replace; + Irssi::print("Replace " . ($index+1) ." changed to: ". to_string($replace)); + } +} + +# REPLACE DELETE | +sub cmd_del { + my ($data, $server, $item) = @_; + my @args = &shellwords($data); + my $index = $data-1; + if ((not $data =~ /^[0-9]*$/) or not exists($replaces[$index])) { + $index = find_replace($data); + if ($index == -1) { + Irssi::print ("Replace $data not found."); + return; + } + } + print("Deleted ". ($index+1) .": ". to_string($replaces[$index])); + splice (@replaces,$index,1); +} + +# REPLACE LIST +sub cmd_list { + #my (@args) = @_; + print ("Replace list:"); + my $i=1; + foreach my $replace (@replaces) { + print(" ". $i++ .": ". to_string($replace)); + } +} + +###################### +### initialisation ### +###################### + +command_bind('replace help',\&cmd_help); +command_bind('help replace',\&cmd_help); +command_bind('replace add',\&cmd_add); +command_bind('replace list',\&cmd_list); +command_bind('replace delete',\&cmd_del); +command_bind('replace save',\&cmd_save); +command_bind('replace reload',\&cmd_load); +command_bind 'replace' => sub { + my ( $data, $server, $item ) = @_; + $data =~ s/\s+$//g; + command_runsub ( 'replace', $data, $server, $item ) ; +}; +signal_add_first 'default command timer' => sub { + # gets triggered if called with unknown subcommand + cmd_help(); +}; + +Irssi::signal_add_first('command script load', 'sig_command_script_unload'); +Irssi::signal_add_first('command script unload', 'sig_command_script_unload'); +Irssi::signal_add('setup saved', 'cmd_save'); + +# This makes tab completion work +Irssi::command_set_options('replace add',join(' ',@replace_options)); + +Irssi::settings_add_str($IRSSI{'name'}, 'replace_file', Irssi::get_irssi_dir()."/replaces"); + +cmd_load(); diff --git a/sources b/sources index e69de29..c180b86 100644 --- a/sources +++ b/sources @@ -0,0 +1 @@ +6610ee0e27922f447e40828cf7dee507 irssi-0.8.9.tar.bz2 diff --git a/trigger.pl b/trigger.pl new file mode 100644 index 0000000..42f5413 --- /dev/null +++ b/trigger.pl @@ -0,0 +1,557 @@ +# Do /TRIGGER HELP for help +# +# TODO: +# - -noact: don't hilight the act-bar + nohilight? (how?) +# - check if the -modifiers argument makes sense +# - problems with § with replace, escape the character? +# - ctcps/ctcpreplies/dccchats/dccsends? (2 arguments...) +# - events for modechanges +# - matching on who's being kicked/opped/voiced... +# - command for changing the order of the triggers +# - use format strings +# - multiple commands with ; (before expanding!) +# - lowercase expands where 'dangerous' characters are escaped ? (for /exec,...) +# - trigger on input (own typed lines) + +use strict; +use Irssi 20020324 qw (command_bind command_runsub command signal_add_first signal_continue signal_stop); +use Text::ParseWords; +use IO::File; +use Data::Dumper; +use vars qw($VERSION %IRSSI); + +$VERSION = '0.5'; +%IRSSI = ( + authors => 'Wouter Coekaerts', + contact => 'wouter@coekaerts.be', + name => 'trigger', + description => 'executes an irssi command or replace text, triggered by a message,notice,join,part,quit,kick or topic', + license => 'GPLv2', + url => 'http://wouter.coekaerts.be/irssi/', + changed => '14/09/03', +); + +my @triggers; + +sub cmd_help { + Irssi::print ( <| +TRIGGER LIST +TRIGGER SAVE +TRIGGER RELOAD +TRIGGER ADD [-] [-regexp ] [-modifiers ] [-channels ] [-masks ] [-hasmode ] [-hasflag ] + [-command ] [-replace ] [-once] +When to match: + -: Trigger on these types of messages. The different types are: + publics,privmsgs,actions,privactions,notices,privnotices,joins,parts,quits,kicks,topics + -all is an alias for all of them. + -regexp: the message must match . (see man 7 regex) + -modifiers: use for the regexp. The modifiers you may use are: + i: Ignore case. + g: Match as many times as possible. + -channels: only trigger in . a space-delimited list. (use quotes) + examples: '#chan1 #chan2' or 'IRCNet/#channel' + -masks: only for messages from someone mathing one of the (space seperated) + -hasmode: only if the person who triggers it has the + examples: '-o' means not opped, '+ov' means opped OR voiced, '-o&-v' means not opped AND not voiced + -hasflag: only works if friends.pl (friends_shasta.pl) or people.pl is loaded + only trigger if the person who triggers it has in the script + +What to do when it matches: + -command: execute + You are able to use \$1, \$2 and so on generated by your regexp pattern. + The following variables are also expanded: + \$T: Server tag + \$C: Channel name + \$N: Nickname of the person who triggered this command + \$A: His address (foo\@bar.com), + \$I: His ident (foo) + \$H: His hostname (bar.com) + \$M: The complete message + -replace: replaces the matching part with in your irssi (requires a ) + -once: remove the trigger if it is triggered, so it only executes once and then is forgotten. + +Examples: + knockout people who do a !list: + /TRIGGER ADD -publics -channels "#channel1 #channel2" -modifiers i -regexp ^!list -command "KN \$N This is not a warez channel!" + react to !echo commands from people who are +o in your friends-script: + /TRIGGER ADD -publics -regexp '^!echo (.*)' -hasflag '+o' -command 'say \$1' + ignore all non-ops on #channel + /TRIGGER ADD -publics -channels "#channel" -hasmodes '-o' -stop + +Examples with -replace: + replace every occurence of shit with sh*t, case insensitive + /TRIGGER ADD -modifiers i -regexp shit -replace sh*t + strip all colorcodes from *!lamer\@* + /TRIGGER ADD -masks *!lamer\@* -regexp '\\x03\\d?\\d?(,\\d\\d?)?|\\x02|\\x1f|\\x16|\\x06' -replace '' + never let *!bot1\@foo.bar or *!bot2\@foo.bar hilight you + /TRIGGER ADD -masks '*!bot1\@foo.bar *!bot2\@foo.bar' -regexp 'mynick' -replace 'my\\x02\\x02nick' + avoid being hilighted by !top10 in eggdrops with stats.mod + /TRIGGER ADD -regexp '(Top.0\\(.*\\): 1.*)mynick' -replace '\$1my\\x02\\x02nick' + Convert a Windows-1252 Euro to an ISO-8859-15 Euro (same effect as euro.pl) + /TRIGGER ADD -regexp '\\x80' -replace '\\xA4' + Show tabs as spaces, not the inverted I (same effect as tab_stop.pl) + /TRIGGER ADD -regexp '\\t' -replace ' ' + +WARNING: Be very carefull when you use the 'eval' or 'exec' command with parameters that come from a remote user. Only use them if you understand the risk. +EOF + ,MSGLEVEL_CLIENTCRAP); +} + +#switches in -all option +my @trigger_all_switches = ('publics','privmsgs','actions','privactions','notices','privnotices','joins','parts','quits','kicks','topics'); +#list of all switches +my @trigger_switches = @trigger_all_switches; +push @trigger_switches, 'stop','once'; +#parameters (with an argument) +my @trigger_params = ('masks','channels','modifiers','regexp','command','replace','hasmode','hasflag'); +#list of all options (including switches) +my @trigger_options = ('all'); +push @trigger_options, @trigger_switches; +push @trigger_options, @trigger_params; + +######################################### +### catch the signals & do your thing ### +######################################### + +# "message public", SERVER_REC, char *msg, char *nick, char *address, char *target +signal_add_first("message public" => sub {check_signal_message(\@_,1,4,2,3,'publics');}); +# "message private", SERVER_REC, char *msg, char *nick, char *address +signal_add_first("message private" => sub {check_signal_message(\@_,1,-1,2,3,'privmsgs');}); +# "message irc action", SERVER_REC, char *msg, char *nick, char *address, char *target +signal_add_first("message irc action" => sub { + if ($_[4] eq $_[0]->{nick}) { + check_signal_message(\@_,1,-1,2,3,'privactions'); + } else { + check_signal_message(\@_,1,4,2,3,'actions'); + } +}); +# "message irc notice", SERVER_REC, char *msg, char *nick, char *address, char *target +signal_add_first("message irc notice" => sub { + if ($_[4] eq $_[0]->{nick}) { + check_signal_message(\@_,1,-1,2,3,'privnotices'); + } else { + check_signal_message(\@_,1,4,2,3,'notices'); + } +}); + +# "message join", SERVER_REC, char *channel, char *nick, char *address +signal_add_first("message join" => sub {check_signal_message(\@_,-1,1,2,3,'joins');}); +# "message part", SERVER_REC, char *channel, char *nick, char *address, char *reason +signal_add_first("message part" => sub {check_signal_message(\@_,4,1,2,3,'parts');}); +# "message quit", SERVER_REC, char *nick, char *address, char *reason +signal_add_first("message quit" => sub {check_signal_message(\@_,3,-1,1,2,'quits');}); +# "message kick", SERVER_REC, char *channel, char *nick, char *kicker, char *address, char *reason +signal_add_first("message kick" => sub {check_signal_message(\@_,5,1,3,4,'kicks');}); +# "message topic", SERVER_REC, char *channel, char *topic, char *nick, char *address +signal_add_first("message topic" => sub {check_signal_message(\@_,2,1,3,4,'topics');}); + +# check the triggers on $signal's $parammessage parameter, for triggers with $condition set +# in $paramchannel, for $paramnick!$paramaddress +# set $param* to -1 if not present (only allowed for message and channel) +sub check_signal_message { + my ($signal,$parammessage,$paramchannel,$paramnick,$paramaddress,$condition) = @_; + my ($trigger, $channel, $matched, $changed, $context); + my $server = $signal->[0]; + my $message = ($parammessage == -1) ? '' : $signal->[$parammessage]; + +TRIGGER: + for (my $index=0;$index < scalar(@triggers);$index++) { + my $trigger = $triggers[$index]; + if (!$trigger->{"$condition"}) { + next; # wrong type of message + } + if ($trigger->{'channels'}) { # check if the channel matches + if ($paramchannel == -1) { + next; + } + my $matches = 0; + foreach $channel (split(/ /,$trigger->{'channels'})) { + if (lc($signal->[$paramchannel]) eq lc($channel) + || lc($server->{'tag'}.'/'.$signal->[$paramchannel]) eq lc($channel) + || lc($server->{'tag'}.'/') eq lc($channel)) { + $matches = 1; + last; # this channel matches, stop checking channels + } + } + if (!$matches) { + next; # this trigger doesn't match, try next trigger... + } + } + # check the mask + if ($trigger->{'masks'} && !$server->masks_match($trigger->{'masks'}, $signal->[$paramnick], $signal->[$paramaddress])) { + next; # this trigger doesn't match + + } + # check hasmodes + if ($trigger->{'hasmode'}) { + my ($channel, $nick); + ( $paramchannel != -1 + and $channel = $server->channel_find($signal->[$paramchannel]) + and $nick = $channel->nick_find($signal->[$paramnick]) + ) or next; + + my $modes = ($nick->{'op'}?'o':'').($nick->{'voice'}?'v':'').($nick->{'halfop'}?'h':''); + if (!check_modes($modes,$trigger->{'hasmode'})) { + next; + } + } + + # check hasflags + if ($trigger->{'hasflag'}) { + my $channel = ($paramchannel == -1) ? undef : $signal->[$paramchannel]; + my $flags = get_flags ($server->{'chatnet'},$channel,$signal->[$paramnick],$signal->[$paramaddress]); + if (!defined($flags)) { + next; + } + if (!check_modes($flags,$trigger->{'hasflag'})) { + next; + } + } + + # the only check left, is the regexp matching... + if ($trigger->{'replace'} && $parammessage != -1) { # it's a -replace + # if you know a better way to do this, let me know: + eval('$matched = ($signal->[$parammessage] =~ s§'. $trigger->{'regexp'} . '§' . $trigger->{'replace'} . '§' . $trigger->{'modifiers'} . ');'); + $changed = $changed || $matched; + } + if ($trigger->{'command'}) { + my @vars; + # check if the message matches the regexp (with the modifiers embedded), and put ($1,$2,$3,...) in @vars + @vars = $message =~ m/(?$trigger->{'modifiers'})$trigger->{'regexp'}/; + if (@vars){ # if it matched + $matched = 1; + my $command = $trigger->{'command'}; + my $expands = { + 'M' => $message, + 'T' => $server->{'tag'}, + 'C' => (($paramchannel == -1) ? '' : $signal->[$paramchannel]), + 'N' => (($paramnick == -1) ? '' : $signal->[$paramnick]), + 'A' => (($paramaddress == -1) ? '' : $signal->[$paramaddress]), + 'I' => (($paramaddress == -1) ? '' : split(/\@/,$signal->[$paramaddress]),2)[0], + 'H' => (($paramaddress == -1) ? '' : split(/\@/,$signal->[$paramaddress]),2)[1], + '$' => '$' + }; + # $1 = the stuff behind the $ we want to expand: a number, or a character from %expands + $command =~ s/\$(\d+|[MTCNAIH\$])/expand(\@vars,$1,$expands)/ge; + + if ($paramchannel!=-1 && $server->channel_find($signal->[$paramchannel])) { + $context = $server->channel_find($signal->[$paramchannel]); + } else { + $context = $server; + } + $context->command($command); + #if ($trigger->{'stop'}) { + # signal_stop; + #} + } + } elsif ($message =~ m/(?$trigger->{'modifiers'})$trigger->{'regexp'}/) { + $matched = 1; + #signal_stop; + } + if ($matched) { + if ($trigger->{'stop'}) { + signal_stop; + } + if ($trigger->{'once'}) { + splice (@triggers,$index,1); + $index--; # index of next trigger now is the same as this one was + } + } + } + if ($changed) { # changed with -replace + signal_continue(@$signal); + } +} + +# used in check_signal_message, to expand $'s +sub expand { + my ($vars,$to_expand,$expands) = @_; + if ($to_expand =~ /^\d+$/) { # a number => look up in $vars + return ($to_expand > @{$vars}) ? '' : $vars->[$to_expand-1]; + } else { # look up in $expands + return $expands->{$to_expand}; + } +} + +sub check_modes { + my ($has_modes, $need_modes) = @_; + my $matches; + my $switch = 1; # if a '-' if found, will be 0 (meaning the modes should not be set) + foreach my $need_mode (split /&/,$need_modes) { + $matches = 0; + foreach my $char (split //,$need_mode) { + if ($char eq '-') { + $switch = 0; + } elsif ($char eq '+') { + $switch = 1; + } elsif ((index($has_modes,$char) != -1) == $switch) { + $matches = 1; + last; + } + } + if (!$matches) { + return 0; + } + } + return 1; +} + +# get someones flags from people.pl or friends(_shasta).pl +sub get_flags { + my ($chatnet, $channel, $nick, $address) = @_; + my $flags; + no strict 'refs'; + if (defined %{ 'Irssi::Script::people::' }) { + if (defined ($channel)) { + $flags = (&{ 'Irssi::Script::people::find_local_flags' }($chatnet,$channel,$nick,$address)); + } else { + $flags = (&{ 'Irssi::Script::people::find_global_flags' }($chatnet,$nick,$address)); + } + $flags = join('',keys(%{$flags})); + } else { + my $shasta; + if (defined %{ 'Irssi::Script::friends_shasta::' }) { + $shasta = 'friends_shasta'; + } elsif (defined &{ 'Irssi::Script::friends::get_idx' }) { + $shasta = 'friends'; + } + if (!$shasta) { + return undef; + } + my $idx = (&{ 'Irssi::Script::'.$shasta.'::get_idx' }($nick,$address)); + if ($idx == -1) { + return ''; + } + $flags = (&{ 'Irssi::Script::'.$shasta.'::get_friends_flags' }($idx,undef)); + if ($channel) { + $flags .= (&{ 'Irssi::Script::'.$shasta.'::get_friends_flags' }($idx,$channel)); + } + } + return $flags; +} + +################################ +### manage the triggers-list ### +################################ + +# TRIGGER SAVE +sub cmd_save { + my $filename = Irssi::settings_get_str('trigger_file'); + my $io = new IO::File $filename, "w"; + if (defined $io) { + my $dumper = Data::Dumper->new([\@triggers]); + $dumper->Purity(1)->Deepcopy(1); + $io->print($dumper->Dump); + $io->close; + } + Irssi::print("Triggers saved to ".$filename); +} + +# save on unload +sub sig_command_script_unload { + my $script = shift; + if ($script =~ /(.*\/)?$IRSSI{'name'}\.pl$/) { + cmd_save(); + } +} + +# TRIGGER LOAD +sub cmd_load { + my $filename = Irssi::settings_get_str('trigger_file'); + my $io = new IO::File $filename, "r"; + if (not defined $io) { + if (-e $filename) { + Irssi::print "error opening triggers file"; + } + return; + } + if (defined $io) { + no strict 'vars'; + my $text; + $text .= $_ foreach ($io->getlines); + my $rep = eval "$text"; + @triggers = @$rep if ref $rep; + } + Irssi::print("Triggers loaded from ".$filename); +} + +# converts a trigger back to "-switch -options 'foo'" form +sub to_string { + my ($trigger) = @_; + my $string; + + # check if all @trigger_all_switches are set + my $all_set = 1; + foreach my $switch (@trigger_all_switches) { + if (!$trigger->{$switch}) { + $all_set = 0; + last; + } + } + if ($all_set) { + $string .= ' -all '; + } else { + foreach my $switch (@trigger_switches) { + if ($trigger->{$switch}) { + $string .= '-'.$switch; + } + } + } + + foreach my $param (@trigger_params) { + if ($trigger->{$param}) { + $string .= ' -' . $param . " '$trigger->{$param}'"; + } + } + return $string; +} + +# find a trigger (for REPLACE and DELETE), returns index of trigger, or -1 if not found +sub find_trigger { + my ($data) = @_; + #my $index = $data-1; + if ($data =~ /^[0-9]*$/ and defined($triggers[$data-1])) { + return $data-1; + } + for (my $i=0;$i{'regexp'} eq $data) { + return $i; + } + } + return -1; # not found +} + + +# TRIGGER ADD +sub cmd_add { + my ($data, $server, $item) = @_; + my @args = &shellwords($data); + + my $trigger = parse_options({},@args); + if ($trigger) { + push @triggers, $trigger; + Irssi::print("Added trigger " . scalar(@triggers) .": ". to_string($trigger)); + } +} + +# TRIGGER REPLACE | +sub cmd_replace { + my ($data, $server, $item) = @_; + my @args = &shellwords($data); + my $index = find_trigger(shift @args); + if ($index == -1) { + Irssi::print "Trigger not found."; + } else { + parse_options($triggers[$index],@args); + Irssi::print("Trigger " . ($index+1) ." changed to: ". to_string($triggers[$index])); + } +} + +# parses options to TRIGGER ADD and TRIGGER REPLACE +sub parse_options { + my ($trigger,@args) = @_; +ARGS: for (my $arg = shift @args; $arg; $arg = shift @args) { + # - + foreach my $param (@trigger_params) { + if ($arg eq '-'.$param) { + $trigger->{$param} = shift @args; + next ARGS; + } + } + # -all + if ($arg eq '-all') { + foreach my $switch (@trigger_all_switches) { + $trigger->{$switch} = 1; + } + next ARGS; + } + + # -[no] + foreach my $switch (@trigger_switches) { + # - + if ($arg eq '-'.$switch) { + $trigger->{$switch} = 1; + next ARGS; + } + # -no + elsif ($arg eq '-no'.$switch) { + $trigger->{$switch} = undef; + next ARGS; + } + } + Irssi::print("Unknown option: $arg"); + return undef; + } + + # check if it has at least one type + my $has_a_type; + foreach my $type (@trigger_all_switches) { + if ($trigger->{$type}) { + $has_a_type = 1; + last; + } + } + if (!$has_a_type) { + Irssi::print("Warning: this trigger doesn't trigger on any type of message. you probably want to add -publics or -all"); + } + + return $trigger; +} + +# TRIGGER DELETE | +sub cmd_del { + my ($data, $server, $item) = @_; + my @args = &shellwords($data); + my $index = find_trigger($data); + if ($index == -1) { + Irssi::print ("Trigger $data not found."); + return; + } + print("Deleted ". ($index+1) .": ". to_string($triggers[$index])); + splice (@triggers,$index,1); +} + +# TRIGGER LIST +sub cmd_list { + #my (@args) = @_; + Irssi::print ("Trigger list:",MSGLEVEL_CLIENTCRAP); + my $i=1; + foreach my $trigger (@triggers) { + Irssi::print(" ". $i++ .": ". to_string($trigger),MSGLEVEL_CLIENTCRAP); + } +} + +###################### +### initialisation ### +###################### + +command_bind('trigger help',\&cmd_help); +command_bind('help trigger',\&cmd_help); +command_bind('trigger add',\&cmd_add); +command_bind('trigger replace',\&cmd_replace); +command_bind('trigger list',\&cmd_list); +command_bind('trigger delete',\&cmd_del); +command_bind('trigger save',\&cmd_save); +command_bind('trigger reload',\&cmd_load); +command_bind 'trigger' => sub { + my ( $data, $server, $item ) = @_; + $data =~ s/\s+$//g; + command_runsub ( 'trigger', $data, $server, $item ) ; +}; +signal_add_first 'default command trigger' => sub { + # gets triggered if called with unknown subcommand + cmd_help(); +}; + +Irssi::signal_add_first('command script load', 'sig_command_script_unload'); +Irssi::signal_add_first('command script unload', 'sig_command_script_unload'); +Irssi::signal_add('setup saved', 'cmd_save'); + +# This makes tab completion work +Irssi::command_set_options('trigger add',join(' ',@trigger_options)); + +Irssi::settings_add_str($IRSSI{'name'}, 'trigger_file', Irssi::get_irssi_dir()."/triggers"); + +cmd_load();