1use Irssi 20011210.0000 ();
2$VERSION = "1.12";
3%IRSSI = (
4    authors     => 'David Leadbeater',
5    contact     => 'dgl@dgl.cx',
6    name        => 'on.pl',
7    description => '/on command - this is very simple and not really designed to be the same as ircII - it tries to fit into Irssi\'s usage style more than emulating ircII.',
8    license     => 'GNU GPLv2 or later',
9    url         => 'http://irssi.dgl.cx/',
10);
11
12use strict;
13my %on;
14
15=head1 on.pl
16
17/on command - this is very simple and not really designed to
18be the same as ircII - it tries to fit into Irssi's usage style
19more than emulating ircII.
20
21=head1 Features
22
23This script allow you to bind Irssi commands or a piece of perl
24code to s particular signal with some forms of filtering.
25
26A command can be set to run in a particular channel (nearly)
27and on a particular chatnet. The commands that you add are
28automatically saved into a file (usually ~/.irssi/on.save).
29
30
31=head1 Usage
32
33 /on list
34 /on add [-perl] [-server] [-channel #channel]  [-stop] 'signal name' command
35 /on remove signal name
36 /on reload
37
38=head2 ON ADD
39
40 -perl: Interpret command as perl instead of the default Irssi
41 -server: Only trigger for events from this chat network
42 -channel #channel: only trigger for events in #channel
43   (only works where $channel->{name} is present (message signals mostly)
44 -stop: Call Irssi::signal_stop() (probably not a good idea to use this)
45
46If you supply a signal name then it must be quoted so it is
47interpeted as one, if you wish to bind to a numeric then just
48entering it will work.
49
50Currently if you specify a Irssi command $0 and $$0 are escaped,
51$0 $1 and so on are the parameters sent to the signal (except the first
52REC), $$0 and so on are the results of spliting $0 on a space so if
53the signal is an event then $$0 will usually be your nickname, $$1
54will be the channel or nickname the numeric is targeting and so on..
55
56=head2 ON REMOVE
57
58This removes *all* events from the signal specified (if you
59want to remove a numeric you must add event eg:
60 /on remove event 401
61
62=head2 ON RELOAD
63
64Reloads the saved list from ~/.irssi/on.save into memory,
65useful if you have to edit it manually (and very useful during debugging :)
66
67=head1 Examples
68
69These are pretty generic examples, there are many more
70specific uses for the commands.
71
72To automatically run a /whowas when the no such nick/channel
73event is recieved:
74 /on add 401 /whowas $$0
75
76To automatically run a command when you become an irc operator
77on this chatnet:
78 /on add -server 381 /whatever
79
80To automatically move to a window with activtiy in it on a hilight:
81 /on add 'window hilight' /window goto active
82
83Obviously perl commands could be used here or many different
84signals (see docs/signals.text in the irssi sources for a list
85of all the signals)
86
87=cut
88
89Irssi::command_bind('on','cmd_on');
90# This makes tab completion work :)
91Irssi::command_set_options('on','stop server perl +channel');
92load();
93add_signals();
94
95# Loads the saved on settings from the saved file
96sub load {
97   my $file = Irssi::get_irssi_dir . '/on.save';
98   return 0 unless -f $file;
99   open(ON, $file) or return 0;
100   while(<ON>) {
101	  chomp;
102      my($event,$chatnet,$settings,$channel,$cmd) = split /\0/;
103	  push(@{$on{$event}},
104	     {
105		    'chatnet' => $chatnet || 'all',
106			'settings' => $settings,
107			'channel' => $channel,
108			'cmd' => $cmd,
109		 }
110	  );
111   }
112   close(ON);
113   return 1;
114}
115
116# Saves the settings currently in the %on hash into the save file
117sub save {
118   my $file = Irssi::get_irssi_dir . '/on.save';
119   open(ON, ">$file") or return 0;
120   for my $event(keys %on) {
121      for(@{$on{$event}}) {
122	     print ON join("\0", $event, @$_{'chatnet','settings','channel','cmd'}) . "\n";
123	  }
124   }
125   close(ON) or return 0;
126   return 1;
127}
128
129# Adds signals from the hash to irssi (only needs to be called once)
130sub add_signals {
131   for(keys %on) {
132      Irssi::signal_add($_, 'signal_handler');
133   }
134}
135
136# Irssi calls this and it figures out what to do with the event
137sub signal_handler {
138   my($item, @stuff) = @_;
139   my $signal = Irssi::signal_get_emitted();
140
141   if(exists $on{$signal}) {
142      for(@{$on{$signal}}) {
143		 next if $_->{chatnet} ne 'all' and $_->{chatnet} ne $item->{chatnet};
144		 next if $_->{channel} and $item->{name} ne $_->{channel};
145	     event_handle(@$_{'settings','cmd'},$item,@stuff);
146	  }
147   }else{
148      Irssi::signal_remove($signal,'signal_handler');
149   }
150}
151
152# Called with the params needed to handle an event from signal_handler
153sub event_handle {
154   my($settings,$cmd,$item,@stuff) = @_;
155   my %settings = settings_to_hash($settings);
156
157   if($settings{type} == 1) {
158	  local @_;
159	  @_ = ($item,@stuff);
160      eval('no strict;' . $cmd);
161   }else{
162	  $cmd =~ s!\$\$(\d)!(split / /,$stuff[0])[$1]!ge;
163	  $cmd =~ s/\$(\d)/$stuff[$1]/g;
164      $item->command($cmd);
165   }
166
167   Irssi::signal_stop() if $settings{stop};
168}
169
170# Converts the settings string to a nice hash
171sub settings_to_hash {
172   my $settings = shift;
173   my %settings;
174   @settings{'type','stop'} = split //, $settings;
175   return %settings;
176}
177
178# Converts a hash to the settings string
179sub hash_to_settings {
180   my %settings = @_;
181   return join '', @settings{'type','stop'};
182}
183
184# Called by the /on command
185sub cmd_on {
186   my $text = shift;
187
188   if($text =~ s/^add //) {
189      my($cmd,%options) = option_parse($text);
190	  if(!$options{event} || !$cmd) {
191		 Irssi::print('No '.($cmd ? 'command' : 'event'). ' supplied');
192	  }else{
193	      my($chatnet,%settings,$channel,$event);
194		  $chatnet = ($options{server} ? Irssi::active_server()->{chatnet} : 'all');
195		  $event = $options{event};
196		  $channel = $options{channel};
197		  $settings{type} = $options{perl};
198		  $settings{stop} = $options{stop};
199	      add_on($event,$cmd,$chatnet,$channel,%settings);
200		  save();
201	  }
202   }elsif($text =~ s/^remove //) {
203      if(del_on($text)) {
204		 Irssi::print("Event $text deleted",MSGLEVEL_CLIENTCRAP);
205	  }else{
206		 Irssi::print("Event not found",MSGLEVEL_CLIENTCRAP);
207	  }
208	  save();
209   }elsif($text =~ /^reload/) {
210	  %on = ();
211	  load();
212   }elsif($text eq "help") {
213	  Irssi::print( <<EOF
214Usage:
215/on list
216/on add [-perl] [-server] [-channel #channel]  [-stop] 'signal name' command
217/on remove signal name
218/on reload
219EOF
220   );
221   }else{
222	 Irssi::print("/on help for usage information");
223     on_list();
224   }
225}
226
227# Output a list of the current contents of %on
228sub on_list {
229   if(!keys %on) {
230	  Irssi::print("On list is empty", MSGLEVEL_CLIENTCRAP);
231	  return;
232   }
233   for my $event(keys %on) {
234	   for(@{$on{$event}}) {
235		  Irssi::print("$event: " .
236		      ($_->{chatnet} ne 'all' ? $_->{chatnet} : '') .
237		      ' ' . $_->{cmd},
238			  MSGLEVEL_CLIENTCRAP
239		  );
240       }
241    }
242}
243
244# Adds into %on and adds a signal if needed.
245sub add_on {
246   my($event,$cmd,$chatnet,$channel,%settings) = @_;
247
248   Irssi::signal_add($event, 'signal_handler') unless $on{$event};
249
250   push(@{$on{$event}},
251	  {
252	     'chatnet' => $chatnet || 'all',
253	     'settings' => hash_to_settings(%settings),
254		 'channel' => $channel,
255	     'cmd' => $cmd,
256	  }
257   );
258}
259
260# Deletes all ons under the event
261sub del_on {
262   my $on = shift;
263   Irssi::signal_remove($on, 'signal_handler');
264   return delete($on{$on});
265}
266
267# This is nasty.
268# It would be nice if perl scripts could use Irssi's internal stuff for option
269# parsing
270sub option_parse {
271   my $text = shift;
272   my($last,%options,$cmd);
273   for(split(' ',$text)) {
274      if($cmd) {
275	     $cmd .= " $_";
276      }elsif(/^-(.+)$/) {
277	     $last = 'channel' if $1 eq 'channel';
278		 $options{$1}++;
279	  }elsif(/^["'0-9]/) {
280	     if(/^\d+$/) {
281		    $options{event} = "event $_" if /^\d+$/;
282		 }else{
283		    $last = 'event';
284			s/^['"]//;
285			$options{event} = $_;
286		 }
287	  }elsif($last eq 'event'){
288		 $last = "" if s/['"]$//;
289	     $options{event} .= " $_";
290	  }elsif($last) {
291	     $options{$last} = $_;
292		 $last = "";
293	  }else{
294	     $cmd = $_;
295	  }
296   }
297   return ($cmd,%options);
298}
299
300