zsh_dots 0.5.9 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -0
- data/config/gitconfig +4 -1
- data/config/gitignore +3 -1
- data/config/heroku.zsh.example +2 -0
- data/config/vimrc +27 -1
- data/config/zshenv +7 -3
- data/etc/irssi/config.example +230 -0
- data/etc/irssi/default.theme +294 -0
- data/etc/irssi/nickserv.networks +2 -0
- data/etc/irssi/scripts/autorun/freenode_filter.pl +122 -0
- data/etc/irssi/scripts/autorun/nickserv.pl +563 -0
- data/etc/irssi/scripts/autorun/trigger.pl +1225 -0
- data/etc/irssi/scripts/autorun/vim_mode.pl +3783 -0
- data/etc/irssi/solarized-universal.theme +372 -0
- data/etc/irssi/triggers +2 -0
- data/etc/rails/template.rb +20 -9
- data/lib/dots/aliases.zsh +4 -0
- data/lib/plugins/git/git.plugin.zsh +3 -3
- data/lib/plugins/heroku/heroku.plugin.zsh +12 -0
- data/lib/ruby/dots/bootstrap.rb +46 -26
- data/lib/ruby/dots/packages.rb +19 -0
- data/lib/ruby/dots/version.rb +1 -1
- metadata +16 -6
@@ -0,0 +1,1225 @@
|
|
1
|
+
# trigger.pl - execute a command or replace text, triggered by an event in irssi
|
2
|
+
# Do /TRIGGER HELP or look at http://wouter.coekaerts.be/irssi/ for help
|
3
|
+
|
4
|
+
# Copyright (C) 2002-2006 Wouter Coekaerts <wouter@coekaerts.be>
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
19
|
+
|
20
|
+
use strict;
|
21
|
+
use Irssi 20020324 qw(command_bind command_runsub command signal_add_first signal_continue signal_stop signal_remove);
|
22
|
+
use Text::ParseWords;
|
23
|
+
use IO::File;
|
24
|
+
use vars qw($VERSION %IRSSI);
|
25
|
+
|
26
|
+
$VERSION = '1.0';
|
27
|
+
%IRSSI = (
|
28
|
+
authors => 'Wouter Coekaerts',
|
29
|
+
contact => 'wouter@coekaerts.be',
|
30
|
+
name => 'trigger',
|
31
|
+
description => 'execute a command or replace text, triggered by an event in irssi',
|
32
|
+
license => 'GPLv2 or later',
|
33
|
+
url => 'http://wouter.coekaerts.be/irssi/',
|
34
|
+
changed => '$LastChangedDate: 2006-01-23 13:10:19 +0100 (Mon, 23 Jan 2006) $',
|
35
|
+
);
|
36
|
+
|
37
|
+
sub cmd_help {
|
38
|
+
Irssi::print (<<'SCRIPTHELP_EOF', MSGLEVEL_CLIENTCRAP);
|
39
|
+
|
40
|
+
TRIGGER LIST
|
41
|
+
TRIGGER SAVE
|
42
|
+
TRIGGER RELOAD
|
43
|
+
TRIGGER MOVE <number> <number>
|
44
|
+
TRIGGER DELETE <number>
|
45
|
+
TRIGGER CHANGE <number> ...
|
46
|
+
TRIGGER ADD ...
|
47
|
+
|
48
|
+
When to match:
|
49
|
+
On which types of event to trigger:
|
50
|
+
These are simply specified by -name_of_the_type
|
51
|
+
The normal IRC event types are:
|
52
|
+
publics, %|privmsgs, pubactions, privactions, pubnotices, privnotices, joins, parts, quits, kicks, topics, invites, nick_changes, dcc_msgs, dcc_actions, dcc_ctcps
|
53
|
+
mode_channel: %|a mode on the (whole) channel (like +t, +i, +b)
|
54
|
+
mode_nick: %|a mode on someone in the channel (like +o, +v)
|
55
|
+
-all is an alias for all of those.
|
56
|
+
Additionally, there is:
|
57
|
+
rawin: %|raw text incoming from the server
|
58
|
+
send_command: %|commands you give to irssi
|
59
|
+
send_text: %|lines you type that aren't commands
|
60
|
+
beep: %|when irssi beeps
|
61
|
+
notify_join: %|someone in you notify list comes online
|
62
|
+
notify_part: %|someone in your notify list goes offline
|
63
|
+
notify_away: %|someone in your notify list goes away
|
64
|
+
notify_unaway: %|someone in your notify list goes unaway
|
65
|
+
notify_unidle: %|someone in your notify list stops idling
|
66
|
+
|
67
|
+
Filters (conditions) the event has to satisfy. They all take one parameter.
|
68
|
+
If you can give a list, seperate elements by space and use quotes around the list.
|
69
|
+
-pattern: %|The message must match the given pattern. ? and * can be used as wildcards
|
70
|
+
-regexp: %|The message must match the given regexp. (see man perlre)
|
71
|
+
%|if -nocase is given as an option, the regexp or pattern is matched case insensitive
|
72
|
+
-tags: %|The servertag must be in the given list of tags
|
73
|
+
-channels: %|The event must be in one of the given list of channels.
|
74
|
+
Examples: %|-channels '#chan1 #chan2' or -channels 'IRCNet/#channel'
|
75
|
+
%|-channels 'EFNet/' means every channel on EFNet and is the same as -tags 'EFNet'
|
76
|
+
-masks: %|The person who triggers it must match one of the given list of masks
|
77
|
+
-hasmode: %|The person who triggers it must have the give mode
|
78
|
+
Examples: %|'-o' means not opped, '+ov' means opped OR voiced, '-o&-v' means not opped AND not voiced
|
79
|
+
-hasflag: %|Only trigger if if friends.pl (friends_shasta.pl) or people.pl is loaded and the person who triggers it has the given flag in the script (same syntax as -hasmode)
|
80
|
+
-other_masks
|
81
|
+
-other_hasmode
|
82
|
+
-other_hasflag: %|Same as above but for the victim for kicks or mode_nick.
|
83
|
+
|
84
|
+
What to do when it matches:
|
85
|
+
-command: Execute the given Irssi-command
|
86
|
+
%|You are able to use $1, $2 and so on generated by your regexp pattern.
|
87
|
+
%|For multiple commands ; (or $;) can be used as seperator
|
88
|
+
%|The following variables are also expanded:
|
89
|
+
$T: %|Server tag
|
90
|
+
$C: %|Channel name
|
91
|
+
$N: %|Nickname of the person who triggered this command
|
92
|
+
$A: %|His address (foo@bar.com),
|
93
|
+
$I: %|His ident (foo)
|
94
|
+
$H: %|His hostname (bar.com)
|
95
|
+
$M: %|The complete message
|
96
|
+
${other}: %|The victim for kicks or mode_nick
|
97
|
+
${mode_type}: %|The type ('+' or '-') for a mode_channel or mode_nick
|
98
|
+
${mode_char}: %|The mode char ('o' for ops, 'b' for ban,...)
|
99
|
+
${mode_arg} : %|The argument to the mode (if there is one)
|
100
|
+
%|$\X, with X being one of the above expands (e.g. $\M), escapes all non-alphanumeric characters, so it can be used with /eval or /exec. Don't use /eval or /exec without this, it's not safe.
|
101
|
+
|
102
|
+
-replace: %|replaces the matching part with the given replacement in the event (requires a -regexp or -pattern)
|
103
|
+
-once: %|remove the trigger if it is triggered, so it only executes once and then is forgotten.
|
104
|
+
-stop: %|stops the signal. It won't get displayed by Irssi. Like /IGNORE
|
105
|
+
-debug: %|print some debugging info
|
106
|
+
|
107
|
+
Other options:
|
108
|
+
-disabled: %|Same as removing it, but keeps it in case you might need it later
|
109
|
+
-name: %|Give the trigger a name. You can refer to the trigger with this name in add/del/change commands
|
110
|
+
|
111
|
+
Examples:
|
112
|
+
Knockout people who do a !list:
|
113
|
+
/TRIGGER ADD %|-publics -channels "#channel1 #channel2" -nocase -regexp ^!list -command "KN $N This is not a warez channel!"
|
114
|
+
React to !echo commands from people who are +o in your friends-script:
|
115
|
+
/TRIGGER ADD %|-publics -regexp '^!echo (.*)' -hasflag '+o' -command 'say echo: $1'
|
116
|
+
Ignore all non-ops on #channel:
|
117
|
+
/TRIGGER ADD %|-publics -actions -channels "#channel" -hasmode '-o' -stop
|
118
|
+
Send a mail to yourself every time a topic is changed:
|
119
|
+
/TRIGGER ADD %|-topics -command 'exec echo $\N changed topic of $\C to: $\M | mail you@somewhere.com -s topic'
|
120
|
+
|
121
|
+
|
122
|
+
Examples with -replace:
|
123
|
+
%|Replace every occurence of shit with sh*t, case insensitive:
|
124
|
+
/TRIGGER ADD %|-all -nocase -regexp shit -replace sh*t
|
125
|
+
%|Strip all colorcodes from *!lamer@*:
|
126
|
+
/TRIGGER ADD %|-all -masks *!lamer@* -regexp '\x03\d?\d?(,\d\d?)?|\x02|\x1f|\x16|\x06' -replace ''
|
127
|
+
%|Never let *!bot1@foo.bar or *!bot2@foo.bar hilight you
|
128
|
+
%|(this works by cutting your nick in 2 different parts, 'myn' and 'ick' here)
|
129
|
+
%|you don't need to understand the -replace argument, just trust that it works if the 2 parts separately don't hilight:
|
130
|
+
/TRIGGER ADD %|-all masks '*!bot1@foo.bar *!bot2@foo.bar' -regexp '(myn)(ick)' -nocase -replace '$1\x02\x02$2'
|
131
|
+
%|Avoid being hilighted by !top10 in eggdrops with stats.mod (but show your nick in bold):
|
132
|
+
/TRIGGER ADD %|-publics -regexp '(Top.0\(.*\): 1.*)(my)(nick)' -replace '$1\x02$2\x02\x02$3\x02'
|
133
|
+
%|Convert a Windows-1252 Euro to an ISO-8859-15 Euro (same effect as euro.pl):
|
134
|
+
/TRIGGER ADD %|-regexp '\x80' -replace '\xA4'
|
135
|
+
%|Show tabs as spaces, not the inverted I (same effect as tab_stop.pl):
|
136
|
+
/TRIGGER ADD %|-all -regexp '\t' -replace ' '
|
137
|
+
SCRIPTHELP_EOF
|
138
|
+
} # /
|
139
|
+
|
140
|
+
my @triggers; # array of all triggers
|
141
|
+
my %triggers_by_type; # hash mapping types on triggers of that type
|
142
|
+
my $recursion_depth = 0;
|
143
|
+
my $changed_since_last_save = 0;
|
144
|
+
|
145
|
+
###############
|
146
|
+
### formats ###
|
147
|
+
###############
|
148
|
+
|
149
|
+
Irssi::theme_register([
|
150
|
+
'trigger_header' => 'Triggers:',
|
151
|
+
'trigger_line' => '%#$[-4]0 $1',
|
152
|
+
'trigger_added' => 'Trigger $0 added: $1',
|
153
|
+
'trigger_not_found' => 'Trigger {hilight $0} not found',
|
154
|
+
'trigger_saved' => 'Triggers saved to $0',
|
155
|
+
'trigger_loaded' => 'Triggers loaded from $0'
|
156
|
+
]);
|
157
|
+
|
158
|
+
#########################################
|
159
|
+
### catch the signals & do your thing ###
|
160
|
+
#########################################
|
161
|
+
|
162
|
+
# trigger types with a message and a channel
|
163
|
+
my @allchanmsg_types = qw(publics pubactions pubnotices pubctcps pubctcpreplies parts quits kicks topics);
|
164
|
+
# trigger types with a message
|
165
|
+
my @allmsg_types = (@allchanmsg_types, qw(privmsgs privactions privnotices privctcps privctcpreplies dcc_msgs dcc_actions dcc_ctcps));
|
166
|
+
# trigger types with a channel
|
167
|
+
my @allchan_types = (@allchanmsg_types, qw(mode_channel mode_nick joins invites));
|
168
|
+
# trigger types in -all
|
169
|
+
my @all_types = (@allmsg_types, qw(mode_channel mode_nick joins invites nick_changes));
|
170
|
+
# trigger types with a server
|
171
|
+
my @all_server_types = (@all_types, qw(rawin notify_join notify_part notify_away notify_unaway notify_unidle));
|
172
|
+
# all trigger types
|
173
|
+
my @trigger_types = (@all_server_types, qw(send_command send_text beep));
|
174
|
+
#trigger types that are not in -all
|
175
|
+
#my @notall_types = grep {my $a=$_; return (!grep {$_ eq $a} @all_types);} @trigger_types;
|
176
|
+
my @notall_types = qw(rawin notify_join notify_part notify_away notify_unaway notify_unidle send_command send_text beep);
|
177
|
+
|
178
|
+
my @signals = (
|
179
|
+
# "message public", SERVER_REC, char *msg, char *nick, char *address, char *target
|
180
|
+
{
|
181
|
+
'types' => ['publics'],
|
182
|
+
'signal' => 'message public',
|
183
|
+
'sub' => sub {check_signal_message(\@_,1,$_[0],$_[4],$_[2],$_[3],'publics');},
|
184
|
+
},
|
185
|
+
# "message private", SERVER_REC, char *msg, char *nick, char *address
|
186
|
+
{
|
187
|
+
'types' => ['privmsgs'],
|
188
|
+
'signal' => 'message private',
|
189
|
+
'sub' => sub {check_signal_message(\@_,1,$_[0],undef,$_[2],$_[3],'privmsgs');},
|
190
|
+
},
|
191
|
+
# "message irc action", SERVER_REC, char *msg, char *nick, char *address, char *target
|
192
|
+
{
|
193
|
+
'types' => ['privactions','pubactions'],
|
194
|
+
'signal' => 'message irc action',
|
195
|
+
'sub' => sub {
|
196
|
+
if ($_[4] eq $_[0]->{nick}) {
|
197
|
+
check_signal_message(\@_,1,$_[0],undef,$_[2],$_[3],'privactions');
|
198
|
+
} else {
|
199
|
+
check_signal_message(\@_,1,$_[0],$_[4],$_[2],$_[3],'pubactions');
|
200
|
+
}
|
201
|
+
},
|
202
|
+
},
|
203
|
+
# "message irc notice", SERVER_REC, char *msg, char *nick, char *address, char *target
|
204
|
+
{
|
205
|
+
'types' => ['privnotices','pubnotices'],
|
206
|
+
'signal' => 'message irc notice',
|
207
|
+
'sub' => sub {
|
208
|
+
if ($_[4] eq $_[0]->{nick}) {
|
209
|
+
check_signal_message(\@_,1,$_[0],undef,$_[2],$_[3],'privnotices');
|
210
|
+
} else {
|
211
|
+
check_signal_message(\@_,1,$_[0],$_[4],$_[2],$_[3],'pubnotices');
|
212
|
+
}
|
213
|
+
}
|
214
|
+
},
|
215
|
+
# "message join", SERVER_REC, char *channel, char *nick, char *address
|
216
|
+
{
|
217
|
+
'types' => ['joins'],
|
218
|
+
'signal' => 'message join',
|
219
|
+
'sub' => sub {check_signal_message(\@_,-1,$_[0],$_[1],$_[2],$_[3],'joins');}
|
220
|
+
},
|
221
|
+
# "message part", SERVER_REC, char *channel, char *nick, char *address, char *reason
|
222
|
+
{
|
223
|
+
'types' => ['parts'],
|
224
|
+
'signal' => 'message part',
|
225
|
+
'sub' => sub {check_signal_message(\@_,4,$_[0],$_[1],$_[2],$_[3],'parts');}
|
226
|
+
},
|
227
|
+
# "message quit", SERVER_REC, char *nick, char *address, char *reason
|
228
|
+
{
|
229
|
+
'types' => ['quits'],
|
230
|
+
'signal' => 'message quit',
|
231
|
+
'sub' => sub {check_signal_message(\@_,3,$_[0],undef,$_[1],$_[2],'quits');}
|
232
|
+
},
|
233
|
+
# "message kick", SERVER_REC, char *channel, char *nick, char *kicker, char *address, char *reason
|
234
|
+
{
|
235
|
+
'types' => ['kicks'],
|
236
|
+
'signal' => 'message kick',
|
237
|
+
'sub' => sub {check_signal_message(\@_,5,$_[0],$_[1],$_[3],$_[4],'kicks',{'other'=>$_[2]});}
|
238
|
+
},
|
239
|
+
# "message topic", SERVER_REC, char *channel, char *topic, char *nick, char *address
|
240
|
+
{
|
241
|
+
'types' => ['topics'],
|
242
|
+
'signal' => 'message topic',
|
243
|
+
'sub' => sub {check_signal_message(\@_,2,$_[0],$_[1],$_[3],$_[4],'topics');}
|
244
|
+
},
|
245
|
+
# "message invite", SERVER_REC, char *channel, char *nick, char *address
|
246
|
+
{
|
247
|
+
'types' => ['invites'],
|
248
|
+
'signal' => 'message invite',
|
249
|
+
'sub' => sub {check_signal_message(\@_,-1,$_[0],$_[1],$_[2],$_[3],'invites');}
|
250
|
+
},
|
251
|
+
# "message nick", SERVER_REC, char *newnick, char *oldnick, char *address
|
252
|
+
{
|
253
|
+
'types' => ['nick_changes'],
|
254
|
+
'signal' => 'message nick',
|
255
|
+
'sub' => sub {check_signal_message(\@_,-1,$_[0],undef,$_[1],$_[3],'nick_changes');}
|
256
|
+
},
|
257
|
+
# "message dcc", DCC_REC *dcc, char *msg
|
258
|
+
{
|
259
|
+
'types' => ['dcc_msgs'],
|
260
|
+
'signal' => 'message dcc',
|
261
|
+
'sub' => sub {check_signal_message(\@_,1,$_[0]->{'server'},undef,$_[0]->{'nick'},undef,'dcc_msgs');
|
262
|
+
}
|
263
|
+
},
|
264
|
+
# "message dcc action", DCC_REC *dcc, char *msg
|
265
|
+
{
|
266
|
+
'types' => ['dcc_actions'],
|
267
|
+
'signal' => 'message dcc action',
|
268
|
+
'sub' => sub {check_signal_message(\@_,1,$_[0]->{'server'},undef,$_[0]->{'nick'},undef,'dcc_actions');}
|
269
|
+
},
|
270
|
+
# "message dcc ctcp", DCC_REC *dcc, char *cmd, char *data
|
271
|
+
{
|
272
|
+
'types' => ['dcc_ctcps'],
|
273
|
+
'signal' => 'message dcc ctcp',
|
274
|
+
'sub' => sub {check_signal_message(\@_,1,$_[0]->{'server'},undef,$_[0]->{'nick'},undef,'dcc_ctcps');}
|
275
|
+
},
|
276
|
+
# "server incoming", SERVER_REC, char *data
|
277
|
+
{
|
278
|
+
'types' => ['rawin'],
|
279
|
+
'signal' => 'server incoming',
|
280
|
+
'sub' => sub {check_signal_message(\@_,1,$_[0],undef,undef,undef,'rawin');}
|
281
|
+
},
|
282
|
+
# "send command", char *args, SERVER_REC, WI_ITEM_REC
|
283
|
+
{
|
284
|
+
'types' => ['send_command'],
|
285
|
+
'signal' => 'send command',
|
286
|
+
'sub' => sub {
|
287
|
+
sig_send_text_or_command(\@_,1);
|
288
|
+
}
|
289
|
+
},
|
290
|
+
# "send text", char *line, SERVER_REC, WI_ITEM_REC
|
291
|
+
{
|
292
|
+
'types' => ['send_text'],
|
293
|
+
'signal' => 'send text',
|
294
|
+
'sub' => sub {
|
295
|
+
sig_send_text_or_command(\@_,0);
|
296
|
+
}
|
297
|
+
},
|
298
|
+
# "beep"
|
299
|
+
{
|
300
|
+
'types' => ['beep'],
|
301
|
+
'signal' => 'beep',
|
302
|
+
'sub' => sub {check_signal_message(\@_,-1,undef,undef,undef,undef,'beep');}
|
303
|
+
},
|
304
|
+
# "event "<cmd>, SERVER_REC, char *args, char *sender_nick, char *sender_address
|
305
|
+
{
|
306
|
+
'types' => ['mode_channel', 'mode_nick'],
|
307
|
+
'signal' => 'event mode',
|
308
|
+
'sub' => sub {
|
309
|
+
my ($server, $event_args, $nickname, $address) = @_;
|
310
|
+
my ($target, $modes, $modeargs) = split(/ /, $event_args, 3);
|
311
|
+
return if (!$server->ischannel($target));
|
312
|
+
my (@modeargs) = split(/ /,$modeargs);
|
313
|
+
my ($pos, $type, $event_type, $arg) = (0, '+');
|
314
|
+
foreach my $char (split(//,$modes)) {
|
315
|
+
if ($char eq "+" || $char eq "-") {
|
316
|
+
$type = $char;
|
317
|
+
} else {
|
318
|
+
if ($char =~ /[Oovh]/) { # mode_nick
|
319
|
+
$event_type = 'mode_nick';
|
320
|
+
$arg = $modeargs[$pos++];
|
321
|
+
} elsif ($char =~ /[beIqdk]/ || ( $char =~ /[lfJ]/ && $type eq '+')) { # chan_mode with arg
|
322
|
+
$event_type = 'mode_channel';
|
323
|
+
$arg = $modeargs[$pos++];
|
324
|
+
} else { # chan_mode without arg
|
325
|
+
$event_type = 'mode_channel';
|
326
|
+
$arg = undef;
|
327
|
+
}
|
328
|
+
check_signal_message(\@_,-1,$server,$target,$nickname,$address,$event_type,{
|
329
|
+
'mode_type' => $type,
|
330
|
+
'mode_char' => $char,
|
331
|
+
'mode_arg' => $arg,
|
332
|
+
'other' => ($event_type eq 'mode_nick') ? $arg : undef
|
333
|
+
});
|
334
|
+
}
|
335
|
+
}
|
336
|
+
}
|
337
|
+
},
|
338
|
+
# "notifylist joined", SERVER_REC, char *nick, char *user, char *host, char *realname, char *awaymsg
|
339
|
+
{
|
340
|
+
'types' => ['notify_join'],
|
341
|
+
'signal' => 'notifylist joined',
|
342
|
+
'sub' => sub {check_signal_message(\@_, 5, $_[0], undef, $_[1], $_[2].'@'.$_[3], 'notify_join', {'realname' => $_[4]});}
|
343
|
+
},
|
344
|
+
{
|
345
|
+
'types' => ['notify_part'],
|
346
|
+
'signal' => 'notifylist left',
|
347
|
+
'sub' => sub {check_signal_message(\@_, 5, $_[0], undef, $_[1], $_[2].'@'.$_[3], 'notify_left', {'realname' => $_[4]});}
|
348
|
+
},
|
349
|
+
{
|
350
|
+
'types' => ['notify_unidle'],
|
351
|
+
'signal' => 'notifylist unidle',
|
352
|
+
'sub' => sub {check_signal_message(\@_, 5, $_[0], undef, $_[1], $_[2].'@'.$_[3], 'notify_unidle', {'realname' => $_[4]});}
|
353
|
+
},
|
354
|
+
{
|
355
|
+
'types' => ['notify_away', 'notify_unaway'],
|
356
|
+
'signal' => 'notifylist away changed',
|
357
|
+
'sub' => sub {check_signal_message(\@_, 5, $_[0], undef, $_[1], $_[2].'@'.$_[3], ($_[5] ? 'notify_away' : 'notify_unaway'), {'realname' => $_[4]});}
|
358
|
+
},
|
359
|
+
# "ctcp msg", SERVER_REC, char *args, char *nick, char *addr, char *target
|
360
|
+
{
|
361
|
+
'types' => ['pubctcps', 'privctcps'],
|
362
|
+
'signal' => 'ctcp msg',
|
363
|
+
'sub' => sub {
|
364
|
+
my ($server, $args, $nick, $addr, $target) = @_;
|
365
|
+
if ($target eq $server->{'nick'}) {
|
366
|
+
check_signal_message(\@_, 1, $server, undef, $nick, $addr, 'privctcps');
|
367
|
+
} else {
|
368
|
+
check_signal_message(\@_, 1, $server, $target, $nick, $addr, 'pubctcps');
|
369
|
+
}
|
370
|
+
}
|
371
|
+
},
|
372
|
+
# "ctcp reply", SERVER_REC, char *args, char *nick, char *addr, char *target
|
373
|
+
{
|
374
|
+
'types' => ['pubctcpreplies', 'privctcpreplies'],
|
375
|
+
'signal' => 'ctcp reply',
|
376
|
+
'sub' => sub {
|
377
|
+
my ($server, $args, $nick, $addr, $target) = @_;
|
378
|
+
if ($target eq $server->{'nick'}) {
|
379
|
+
check_signal_message(\@_, 1, $server, undef, $nick, $addr, 'privctcps');
|
380
|
+
} else {
|
381
|
+
check_signal_message(\@_, 1, $server, $target, $nick, $addr, 'pubctcps');
|
382
|
+
}
|
383
|
+
}
|
384
|
+
}
|
385
|
+
);
|
386
|
+
|
387
|
+
sub sig_send_text_or_command {
|
388
|
+
my ($signal, $iscommand) = @_;
|
389
|
+
my ($line, $server, $item) = @$signal;
|
390
|
+
my ($channelname,$nickname,$address) = (undef,undef,undef);
|
391
|
+
if ($item && (ref($item) eq 'Irssi::Irc::Channel' || ref($item) eq 'Irssi::Silc::Channel')) {
|
392
|
+
$channelname = $item->{'name'};
|
393
|
+
} elsif ($item && ref($item) eq 'Irssi::Irc::Query') { # TODO Silc query ?
|
394
|
+
$nickname = $item->{'name'};
|
395
|
+
$address = $item->{'address'}
|
396
|
+
}
|
397
|
+
# TODO pass context also for non-channels (queries and other stuff)
|
398
|
+
check_signal_message($signal,0,$server,$channelname,$nickname,$address,$iscommand ? 'send_command' : 'send_text');
|
399
|
+
|
400
|
+
}
|
401
|
+
|
402
|
+
my %filters = (
|
403
|
+
'tags' => {
|
404
|
+
'types' => \@all_server_types,
|
405
|
+
'sub' => sub {
|
406
|
+
my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
|
407
|
+
|
408
|
+
if (!defined($server)) {
|
409
|
+
return 0;
|
410
|
+
}
|
411
|
+
my $matches = 0;
|
412
|
+
foreach my $tag (split(/ /,$param)) {
|
413
|
+
if (lc($server->{'tag'}) eq lc($tag)) {
|
414
|
+
$matches = 1;
|
415
|
+
last;
|
416
|
+
}
|
417
|
+
}
|
418
|
+
return $matches;
|
419
|
+
}
|
420
|
+
},
|
421
|
+
'channels' => {
|
422
|
+
'types' => \@allchan_types,
|
423
|
+
'sub' => sub {
|
424
|
+
my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
|
425
|
+
|
426
|
+
if (!defined($channelname) || !defined($server)) {
|
427
|
+
return 0;
|
428
|
+
}
|
429
|
+
my $matches = 0;
|
430
|
+
foreach my $trigger_channel (split(/ /,$param)) {
|
431
|
+
if (lc($channelname) eq lc($trigger_channel)
|
432
|
+
|| lc($server->{'tag'}.'/'.$channelname) eq lc($trigger_channel)
|
433
|
+
|| lc($server->{'tag'}.'/') eq lc($trigger_channel)) {
|
434
|
+
$matches = 1;
|
435
|
+
last; # this channel matches, stop checking channels
|
436
|
+
}
|
437
|
+
}
|
438
|
+
return $matches;
|
439
|
+
}
|
440
|
+
},
|
441
|
+
'masks' => {
|
442
|
+
'types' => \@all_types,
|
443
|
+
'sub' => sub {
|
444
|
+
my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
|
445
|
+
return (defined($nickname) && defined($address) && defined($server) && $server->masks_match($param, $nickname, $address));
|
446
|
+
}
|
447
|
+
},
|
448
|
+
'other_masks' => {
|
449
|
+
'types' => ['kicks', 'mode_nick'],
|
450
|
+
'sub' => sub {
|
451
|
+
my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
|
452
|
+
return 0 unless defined($extra->{'other'});
|
453
|
+
my $other_address = get_address($extra->{'other'}, $server, $channelname);
|
454
|
+
return defined($other_address) && $server->masks_match($param, $extra->{'other'}, $other_address);
|
455
|
+
}
|
456
|
+
},
|
457
|
+
'hasmode' => {
|
458
|
+
'types' => \@all_types,
|
459
|
+
'sub' => sub {
|
460
|
+
my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
|
461
|
+
return hasmode($param, $nickname, $server, $channelname);
|
462
|
+
}
|
463
|
+
},
|
464
|
+
'other_hasmode' => {
|
465
|
+
'types' => ['kicks', 'mode_nick'],
|
466
|
+
'sub' => sub {
|
467
|
+
my ($param,$signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
|
468
|
+
return defined($extra->{'other'}) && hasmode($param, $extra->{'other'}, $server, $channelname);
|
469
|
+
}
|
470
|
+
},
|
471
|
+
'hasflag' => {
|
472
|
+
'types' => \@all_types,
|
473
|
+
'sub' => sub {
|
474
|
+
my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
|
475
|
+
return 0 unless defined($nickname) && defined($address) && defined($server);
|
476
|
+
my $flags = get_flags ($server->{'chatnet'},$channelname,$nickname,$address);
|
477
|
+
return defined($flags) && check_modes($flags,$param);
|
478
|
+
}
|
479
|
+
},
|
480
|
+
'other_hasflag' => {
|
481
|
+
'types' => ['kicks', 'mode_nick'],
|
482
|
+
'sub' => sub {
|
483
|
+
my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
|
484
|
+
return 0 unless defined($extra->{'other'});
|
485
|
+
my $other_address = get_address($extra->{'other'}, $server, $channelname);
|
486
|
+
return 0 unless defined($other_address);
|
487
|
+
my $flags = get_flags ($server->{'chatnet'},$channelname,$extra->{'other'},$other_address);
|
488
|
+
return defined($flags) && check_modes($flags,$param);
|
489
|
+
}
|
490
|
+
},
|
491
|
+
'mode_type' => {
|
492
|
+
'types' => ['mode_channel', 'mode_nick'],
|
493
|
+
'sub' => sub {
|
494
|
+
my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
|
495
|
+
return (($param) eq $extra->{'mode_type'});
|
496
|
+
}
|
497
|
+
},
|
498
|
+
'mode_char' => {
|
499
|
+
'types' => ['mode_channel', 'mode_nick'],
|
500
|
+
'sub' => sub {
|
501
|
+
my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
|
502
|
+
return (($param) eq $extra->{'mode_char'});
|
503
|
+
}
|
504
|
+
},
|
505
|
+
'mode_arg' => {
|
506
|
+
'types' => ['mode_channel', 'mode_nick'],
|
507
|
+
'sub' => sub {
|
508
|
+
my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
|
509
|
+
return (($param) eq $extra->{'mode_arg'});
|
510
|
+
}
|
511
|
+
}
|
512
|
+
);
|
513
|
+
|
514
|
+
sub get_address {
|
515
|
+
my ($nick, $server, $channel) = @_;
|
516
|
+
my $nickrec = get_nickrec($nick, $server, $channel);
|
517
|
+
return $nickrec ? $nickrec->{'host'} : undef;
|
518
|
+
}
|
519
|
+
sub get_nickrec {
|
520
|
+
my ($nick, $server, $channel) = @_;
|
521
|
+
return unless defined($server) && defined($channel) && defined($nick);
|
522
|
+
my $chanrec = $server->channel_find($channel);
|
523
|
+
return $chanrec ? $chanrec->nick_find($nick) : undef;
|
524
|
+
}
|
525
|
+
|
526
|
+
sub hasmode {
|
527
|
+
my ($param, $nickname, $server, $channelname) = @_;
|
528
|
+
my $nickrec = get_nickrec($nickname, $server, $channelname);
|
529
|
+
return 0 unless defined $nickrec;
|
530
|
+
my $modes =
|
531
|
+
($nickrec->{'op'} ? 'o' : '')
|
532
|
+
. ($nickrec->{'voice'} ? 'v' : '')
|
533
|
+
. ($nickrec->{'halfop'} ? 'h' : '')
|
534
|
+
;
|
535
|
+
return check_modes($modes, $param);
|
536
|
+
}
|
537
|
+
|
538
|
+
# list of all switches
|
539
|
+
my @trigger_switches = (@trigger_types, qw(all nocase stop once debug disabled));
|
540
|
+
# parameters (with an argument)
|
541
|
+
my @trigger_params = qw(pattern regexp command replace name);
|
542
|
+
# list of all options (including switches) for /TRIGGER ADD
|
543
|
+
my @trigger_add_options = (@trigger_switches, @trigger_params, keys(%filters));
|
544
|
+
# same for /TRIGGER CHANGE, this includes the -no<option>'s
|
545
|
+
my @trigger_options = map(($_,'no'.$_) ,@trigger_add_options);
|
546
|
+
|
547
|
+
# check the triggers on $signal's $parammessage parameter, for triggers with $condition set
|
548
|
+
# on $server in $channelname, for $nickname!$address
|
549
|
+
# set $parammessage to -1 if the signal doesn't have a message
|
550
|
+
# for signal without channel, nick or address, set to undef
|
551
|
+
sub check_signal_message {
|
552
|
+
my ($signal, $parammessage, $server, $channelname, $nickname, $address, $condition, $extra) = @_;
|
553
|
+
my ($changed, $stopped, $context, $need_rebuild);
|
554
|
+
my $message = ($parammessage == -1) ? '' : $signal->[$parammessage];
|
555
|
+
|
556
|
+
return if (!$triggers_by_type{$condition});
|
557
|
+
|
558
|
+
if ($recursion_depth > 10) {
|
559
|
+
Irssi::print("Trigger error: Maximum recursion depth reached, aborting trigger.", MSGLEVEL_CLIENTERROR);
|
560
|
+
return;
|
561
|
+
}
|
562
|
+
$recursion_depth++;
|
563
|
+
|
564
|
+
TRIGGER:
|
565
|
+
foreach my $trigger (@{$triggers_by_type{$condition}}) {
|
566
|
+
# check filters
|
567
|
+
foreach my $trigfilter (@{$trigger->{'filters'}}) {
|
568
|
+
if (! ($trigfilter->[2]($trigfilter->[1], $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra))) {
|
569
|
+
|
570
|
+
next TRIGGER;
|
571
|
+
}
|
572
|
+
}
|
573
|
+
|
574
|
+
# check regexp (and keep matches in @- and @+, so don't make a this a {block})
|
575
|
+
next if ($trigger->{'compregexp'} && ($parammessage == -1 || $message !~ m/$trigger->{'compregexp'}/));
|
576
|
+
|
577
|
+
# if we got this far, it fully matched, and we need to do the replace/command/stop/once
|
578
|
+
my $expands = $extra;
|
579
|
+
$expands->{'M'} = $message,;
|
580
|
+
$expands->{'T'} = (defined($server)) ? $server->{'tag'} : '';
|
581
|
+
$expands->{'C'} = $channelname;
|
582
|
+
$expands->{'N'} = $nickname;
|
583
|
+
$expands->{'A'} = $address;
|
584
|
+
$expands->{'I'} = ((!defined($address)) ? '' : substr($address,0,index($address,'@')));
|
585
|
+
$expands->{'H'} = ((!defined($address)) ? '' : substr($address,index($address,'@')+1));
|
586
|
+
$expands->{'$'} = '$';
|
587
|
+
$expands->{';'} = ';';
|
588
|
+
|
589
|
+
if (defined($trigger->{'replace'})) { # it's a -replace
|
590
|
+
$message =~ s/$trigger->{'compregexp'}/do_expands($trigger->{'compreplace'},$expands,$message)/ge;
|
591
|
+
$changed = 1;
|
592
|
+
}
|
593
|
+
|
594
|
+
if ($trigger->{'command'}) { # it's a (nonempty) -command
|
595
|
+
my $command = $trigger->{'command'};
|
596
|
+
# $1 = the stuff behind the $ we want to expand: a number, or a character from %expands
|
597
|
+
$command = do_expands($command, $expands, $message);
|
598
|
+
|
599
|
+
if (defined($server)) {
|
600
|
+
if (defined($channelname) && $server->channel_find($channelname)) {
|
601
|
+
$context = $server->channel_find($channelname);
|
602
|
+
} else {
|
603
|
+
$context = $server;
|
604
|
+
}
|
605
|
+
} else {
|
606
|
+
$context = undef;
|
607
|
+
}
|
608
|
+
|
609
|
+
if (defined($context)) {
|
610
|
+
$context->command("eval $command");
|
611
|
+
} else {
|
612
|
+
Irssi::command("eval $command");
|
613
|
+
}
|
614
|
+
}
|
615
|
+
|
616
|
+
if ($trigger->{'debug'}) {
|
617
|
+
print("DEBUG: trigger $condition pmesg=$parammessage message=$message server=$server->{tag} channel=$channelname nick=$nickname address=$address " . join(' ',map {$_ . '=' . $extra->{$_}} keys(%$extra)));
|
618
|
+
}
|
619
|
+
|
620
|
+
if ($trigger->{'stop'}) {
|
621
|
+
$stopped = 1;
|
622
|
+
}
|
623
|
+
|
624
|
+
if ($trigger->{'once'}) {
|
625
|
+
# find this trigger in the real trigger list, and remove it
|
626
|
+
for (my $realindex=0; $realindex < scalar(@triggers); $realindex++) {
|
627
|
+
if ($triggers[$realindex] == $trigger) {
|
628
|
+
splice (@triggers,$realindex,1);
|
629
|
+
last;
|
630
|
+
}
|
631
|
+
}
|
632
|
+
$need_rebuild = 1;
|
633
|
+
}
|
634
|
+
}
|
635
|
+
|
636
|
+
if ($need_rebuild) {
|
637
|
+
rebuild();
|
638
|
+
$changed_since_last_save = 1;
|
639
|
+
}
|
640
|
+
if ($stopped) { # stopped with -stop
|
641
|
+
signal_stop();
|
642
|
+
} elsif ($changed) { # changed with -replace
|
643
|
+
$signal->[$parammessage] = $message;
|
644
|
+
signal_continue(@$signal);
|
645
|
+
}
|
646
|
+
$recursion_depth--;
|
647
|
+
}
|
648
|
+
|
649
|
+
# used in check_signal_message to expand $'s
|
650
|
+
# $inthis is a string that can contain $ stuff (like 'foo$1bar$N')
|
651
|
+
sub do_expands {
|
652
|
+
my ($inthis, $expands, $from) = @_;
|
653
|
+
# @+ and @- are copied because there are two s/// nested, and the inner needs the $1 and $2,... of the outer one
|
654
|
+
my @plus = @+;
|
655
|
+
my @min = @-;
|
656
|
+
my $p = \@plus; my $m = \@min;
|
657
|
+
$inthis =~ s/\$(\\*(\d+|[^0-9x{]|x[0-9a-fA-F][0-9a-fA-F]|{.*?}))/expand_and_escape($1,$expands,$m,$p,$from)/ge;
|
658
|
+
return $inthis;
|
659
|
+
}
|
660
|
+
|
661
|
+
# \ $ and ; need extra escaping because we use eval
|
662
|
+
sub expand_and_escape {
|
663
|
+
my $retval = expand(@_);
|
664
|
+
$retval =~ s/([\\\$;])/\\\1/g;
|
665
|
+
return $retval;
|
666
|
+
}
|
667
|
+
|
668
|
+
# used in do_expands (via expand_and_escape), to_expand is the part after the $
|
669
|
+
sub expand {
|
670
|
+
my ($to_expand, $expands, $min, $plus, $from) = @_;
|
671
|
+
if ($to_expand =~ /^\d+$/) { # a number => look up in $vars
|
672
|
+
# from man perlvar:
|
673
|
+
# $3 is the same as "substr $var, $-[3], $+[3] - $-[3])"
|
674
|
+
return ($to_expand > @{$min} ? '' : substr($from,$min->[$to_expand],$plus->[$to_expand]-$min->[$to_expand]));
|
675
|
+
} elsif ($to_expand =~ s/^\\//) { # begins with \, so strip that from to_expand
|
676
|
+
my $exp = expand($to_expand,$expands,$min,$plus,$from); # first expand without \
|
677
|
+
$exp =~ s/([^a-zA-Z0-9])/\\\1/g; # escape non-word chars
|
678
|
+
return $exp;
|
679
|
+
} elsif ($to_expand =~ /^x([0-9a-fA-F]{2})/) { # $xAA
|
680
|
+
return chr(hex($1));
|
681
|
+
} elsif ($to_expand =~ /^{(.*?)}$/) { # ${foo}
|
682
|
+
return expand($1, $expands, $min, $plus, $from);
|
683
|
+
} else { # look up in $expands
|
684
|
+
return $expands->{$to_expand};
|
685
|
+
}
|
686
|
+
}
|
687
|
+
|
688
|
+
sub check_modes {
|
689
|
+
my ($has_modes, $need_modes) = @_;
|
690
|
+
my $matches;
|
691
|
+
my $switch = 1; # if a '-' if found, will be 0 (meaning the modes should not be set)
|
692
|
+
foreach my $need_mode (split /&/, $need_modes) {
|
693
|
+
$matches = 0;
|
694
|
+
foreach my $char (split //, $need_mode) {
|
695
|
+
if ($char eq '-') {
|
696
|
+
$switch = 0;
|
697
|
+
} elsif ($char eq '+') {
|
698
|
+
$switch = 1;
|
699
|
+
} elsif ((index($has_modes, $char) != -1) == $switch) {
|
700
|
+
$matches = 1;
|
701
|
+
last;
|
702
|
+
}
|
703
|
+
}
|
704
|
+
if (!$matches) {
|
705
|
+
return 0;
|
706
|
+
}
|
707
|
+
}
|
708
|
+
return 1;
|
709
|
+
}
|
710
|
+
|
711
|
+
# get someones flags from people.pl or friends(_shasta).pl
|
712
|
+
sub get_flags {
|
713
|
+
my ($chatnet, $channel, $nick, $address) = @_;
|
714
|
+
my $flags;
|
715
|
+
no strict 'refs';
|
716
|
+
if (defined %{ 'Irssi::Script::people::' }) {
|
717
|
+
if (defined ($channel)) {
|
718
|
+
$flags = (&{ 'Irssi::Script::people::find_local_flags' }($chatnet,$channel,$nick,$address));
|
719
|
+
} else {
|
720
|
+
$flags = (&{ 'Irssi::Script::people::find_global_flags' }($chatnet,$nick,$address));
|
721
|
+
}
|
722
|
+
$flags = join('',keys(%{$flags}));
|
723
|
+
} else {
|
724
|
+
my $shasta;
|
725
|
+
if (defined %{ 'Irssi::Script::friends_shasta::' }) {
|
726
|
+
$shasta = 'friends_shasta';
|
727
|
+
} elsif (defined &{ 'Irssi::Script::friends::get_idx' }) {
|
728
|
+
$shasta = 'friends';
|
729
|
+
} else {
|
730
|
+
return undef;
|
731
|
+
}
|
732
|
+
my $idx = (&{ 'Irssi::Script::'.$shasta.'::get_idx' }($nick, $address));
|
733
|
+
if ($idx == -1) {
|
734
|
+
return '';
|
735
|
+
}
|
736
|
+
$flags = (&{ 'Irssi::Script::'.$shasta.'::get_friends_flags' }($idx,undef));
|
737
|
+
if ($channel) {
|
738
|
+
$flags .= (&{ 'Irssi::Script::'.$shasta.'::get_friends_flags' }($idx,$channel));
|
739
|
+
}
|
740
|
+
}
|
741
|
+
return $flags;
|
742
|
+
}
|
743
|
+
|
744
|
+
########################################################
|
745
|
+
### internal stuff called by manage, needed by above ###
|
746
|
+
########################################################
|
747
|
+
|
748
|
+
my %mask_to_regexp = ();
|
749
|
+
foreach my $i (0..255) {
|
750
|
+
my $ch = chr $i;
|
751
|
+
$mask_to_regexp{$ch} = "\Q$ch\E";
|
752
|
+
}
|
753
|
+
$mask_to_regexp{'?'} = '(.)';
|
754
|
+
$mask_to_regexp{'*'} = '(.*)';
|
755
|
+
|
756
|
+
sub compile_trigger {
|
757
|
+
my ($trigger) = @_;
|
758
|
+
my $regexp;
|
759
|
+
|
760
|
+
if ($trigger->{'regexp'}) {
|
761
|
+
$regexp = $trigger->{'regexp'};
|
762
|
+
} elsif ($trigger->{'pattern'}) {
|
763
|
+
$regexp = $trigger->{'pattern'};
|
764
|
+
$regexp =~ s/(.)/$mask_to_regexp{$1}/g;
|
765
|
+
} else {
|
766
|
+
delete $trigger->{'compregexp'};
|
767
|
+
return;
|
768
|
+
}
|
769
|
+
|
770
|
+
if ($trigger->{'nocase'}) {
|
771
|
+
$regexp = '(?i)' . $regexp;
|
772
|
+
}
|
773
|
+
|
774
|
+
$trigger->{'compregexp'} = qr/$regexp/;
|
775
|
+
|
776
|
+
if(defined($trigger->{'replace'})) {
|
777
|
+
(my $replace = $trigger->{'replace'}) =~ s/\$/\$\$/g;
|
778
|
+
$trigger->{'compreplace'} = Irssi::parse_special($replace);
|
779
|
+
}
|
780
|
+
}
|
781
|
+
|
782
|
+
# rebuilds triggers_by_type and updates signal binds
|
783
|
+
sub rebuild {
|
784
|
+
%triggers_by_type = ();
|
785
|
+
foreach my $trigger (@triggers) {
|
786
|
+
if (!$trigger->{'disabled'}) {
|
787
|
+
if ($trigger->{'all'}) {
|
788
|
+
# -all is an alias for all types in @all_types for which the filters can apply
|
789
|
+
ALLTYPES:
|
790
|
+
foreach my $type (@all_types) {
|
791
|
+
# check if all filters can apply to $type
|
792
|
+
foreach my $filter (@{$trigger->{'filters'}}) {
|
793
|
+
if (! grep {$_ eq $type} $filters{$filter->[0]}->{'types'}) {
|
794
|
+
next ALLTYPES;
|
795
|
+
}
|
796
|
+
}
|
797
|
+
push @{$triggers_by_type{$type}}, ($trigger);
|
798
|
+
}
|
799
|
+
}
|
800
|
+
|
801
|
+
foreach my $type ($trigger->{'all'} ? @notall_types : @trigger_types) {
|
802
|
+
if ($trigger->{$type}) {
|
803
|
+
push @{$triggers_by_type{$type}}, ($trigger);
|
804
|
+
}
|
805
|
+
}
|
806
|
+
}
|
807
|
+
}
|
808
|
+
|
809
|
+
foreach my $signal (@signals) {
|
810
|
+
my $should_bind = 0;
|
811
|
+
foreach my $type (@{$signal->{'types'}}) {
|
812
|
+
if (defined($triggers_by_type{$type})) {
|
813
|
+
$should_bind = 1;
|
814
|
+
}
|
815
|
+
}
|
816
|
+
if ($should_bind && !$signal->{'bind'}) {
|
817
|
+
signal_add_first($signal->{'signal'}, $signal->{'sub'});
|
818
|
+
$signal->{'bind'} = 1;
|
819
|
+
} elsif (!$should_bind && $signal->{'bind'}) {
|
820
|
+
signal_remove($signal->{'signal'}, $signal->{'sub'});
|
821
|
+
$signal->{'bind'} = 0;
|
822
|
+
}
|
823
|
+
}
|
824
|
+
}
|
825
|
+
|
826
|
+
################################
|
827
|
+
### manage the triggers-list ###
|
828
|
+
################################
|
829
|
+
|
830
|
+
my $trigger_file; # cached setting
|
831
|
+
|
832
|
+
sub sig_setup_changed {
|
833
|
+
$trigger_file = Irssi::settings_get_str('trigger_file');
|
834
|
+
}
|
835
|
+
|
836
|
+
sub autosave {
|
837
|
+
cmd_save() if ($changed_since_last_save);
|
838
|
+
}
|
839
|
+
|
840
|
+
# TRIGGER SAVE
|
841
|
+
sub cmd_save {
|
842
|
+
my $io = new IO::File $trigger_file, "w";
|
843
|
+
if (defined $io) {
|
844
|
+
$io->print("#Triggers file version $VERSION\n");
|
845
|
+
foreach my $trigger (@triggers) {
|
846
|
+
$io->print(to_string($trigger) . "\n");
|
847
|
+
}
|
848
|
+
$io->close;
|
849
|
+
}
|
850
|
+
Irssi::printformat(MSGLEVEL_CLIENTNOTICE, 'trigger_saved', $trigger_file);
|
851
|
+
$changed_since_last_save = 0;
|
852
|
+
}
|
853
|
+
|
854
|
+
# save on unload
|
855
|
+
sub UNLOAD {
|
856
|
+
cmd_save();
|
857
|
+
}
|
858
|
+
|
859
|
+
# TRIGGER LOAD
|
860
|
+
sub cmd_load {
|
861
|
+
sig_setup_changed(); # make sure we've read the trigger_file setting
|
862
|
+
my $converted = 0;
|
863
|
+
my $io = new IO::File $trigger_file, "r";
|
864
|
+
if (not defined $io) {
|
865
|
+
if (-e $trigger_file) {
|
866
|
+
Irssi::print("Error opening triggers file", MSGLEVEL_CLIENTERROR);
|
867
|
+
}
|
868
|
+
return;
|
869
|
+
}
|
870
|
+
if (defined $io) {
|
871
|
+
@triggers = ();
|
872
|
+
my $text;
|
873
|
+
$text = $io->getline;
|
874
|
+
my $file_version = '';
|
875
|
+
if ($text =~ /^#Triggers file version (.*)\n/) {
|
876
|
+
$file_version = $1;
|
877
|
+
}
|
878
|
+
if ($file_version lt '0.6.1+2') {
|
879
|
+
no strict 'vars';
|
880
|
+
$text .= $_ foreach ($io->getlines);
|
881
|
+
my $rep = eval "$text";
|
882
|
+
if (! ref $rep) {
|
883
|
+
Irssi::print("Error in triggers file");
|
884
|
+
return;
|
885
|
+
}
|
886
|
+
my @old_triggers = @$rep;
|
887
|
+
|
888
|
+
for (my $index=0;$index < scalar(@old_triggers);$index++) {
|
889
|
+
my $trigger = $old_triggers[$index];
|
890
|
+
|
891
|
+
if ($file_version lt '0.6.1') {
|
892
|
+
# convert old names: notices => pubnotices, actions => pubactions
|
893
|
+
foreach $oldname ('notices','actions') {
|
894
|
+
if ($trigger->{$oldname}) {
|
895
|
+
delete $trigger->{$oldname};
|
896
|
+
$trigger->{'pub'.$oldname} = 1;
|
897
|
+
$converted = 1;
|
898
|
+
}
|
899
|
+
}
|
900
|
+
}
|
901
|
+
if ($file_version lt '0.6.1+1' && $trigger->{'modifiers'}) {
|
902
|
+
if ($trigger->{'modifiers'} =~ /i/) {
|
903
|
+
$trigger->{'nocase'} = 1;
|
904
|
+
Irssi::print("Trigger: trigger ".($index+1)." had 'i' in it's modifiers, it has been converted to -nocase");
|
905
|
+
}
|
906
|
+
if ($trigger->{'modifiers'} !~ /^[ig]*$/) {
|
907
|
+
Irssi::print("Trigger: trigger ".($index+1)." had unrecognised modifier '". $trigger->{'modifiers'} ."', which couldn't be converted.");
|
908
|
+
}
|
909
|
+
delete $trigger->{'modifiers'};
|
910
|
+
$converted = 1;
|
911
|
+
}
|
912
|
+
|
913
|
+
if (defined($trigger->{'replace'}) && ! $trigger->{'regexp'}) {
|
914
|
+
Irssi::print("Trigger: trigger ".($index+1)." had -replace but no -regexp, removed it");
|
915
|
+
splice (@old_triggers,$index,1);
|
916
|
+
$index--; # nr of next trigger now is the same as this one was
|
917
|
+
}
|
918
|
+
|
919
|
+
# convert to text with compat, and then to new trigger hash
|
920
|
+
$text = to_string($trigger,1);
|
921
|
+
my @args = &shellwords($text . ' a');
|
922
|
+
my $trigger = parse_options({},@args);
|
923
|
+
if ($trigger) {
|
924
|
+
push @triggers, $trigger;
|
925
|
+
}
|
926
|
+
}
|
927
|
+
} else { # new format
|
928
|
+
while ( $text = $io->getline ) {
|
929
|
+
chop($text);
|
930
|
+
my @args = &shellwords($text . ' a');
|
931
|
+
my $trigger = parse_options({},@args);
|
932
|
+
if ($trigger) {
|
933
|
+
push @triggers, $trigger;
|
934
|
+
}
|
935
|
+
}
|
936
|
+
}
|
937
|
+
}
|
938
|
+
Irssi::printformat(MSGLEVEL_CLIENTNOTICE, 'trigger_loaded', $trigger_file);
|
939
|
+
if ($converted) {
|
940
|
+
Irssi::print("Trigger: Triggers file will be in new format next time it's saved.");
|
941
|
+
}
|
942
|
+
rebuild();
|
943
|
+
}
|
944
|
+
|
945
|
+
# escape for printing with to_string
|
946
|
+
# <<abcdef>> => << 'abcdef' >>
|
947
|
+
# <<abc'def>> => << "abc'def" >>
|
948
|
+
# <<abc'def\x02>> => << 'abc'\''def\x02' >>
|
949
|
+
sub param_to_string {
|
950
|
+
my ($text) = @_;
|
951
|
+
# avoid ugly escaping if we can use "-quotes without other escaping (no " or \)
|
952
|
+
if ($text =~ /^[^"\\]*'[^"\\]$/) {
|
953
|
+
return ' "' . $text . '" ';
|
954
|
+
}
|
955
|
+
# "'" signs without a (odd number of) \ in front of them, need be to escaped as '\''
|
956
|
+
# this is ugly :(
|
957
|
+
$text =~ s/(^|[^\\](\\\\)*)'/$1'\\''/g;
|
958
|
+
return " '$text' ";
|
959
|
+
}
|
960
|
+
|
961
|
+
# converts a trigger back to "-switch -options 'foo'" form
|
962
|
+
# if $compat, $trigger is in the old format (used to convert)
|
963
|
+
sub to_string {
|
964
|
+
my ($trigger, $compat) = @_;
|
965
|
+
my $string;
|
966
|
+
|
967
|
+
foreach my $switch (@trigger_switches) {
|
968
|
+
if ($trigger->{$switch}) {
|
969
|
+
$string .= '-'.$switch.' ';
|
970
|
+
}
|
971
|
+
}
|
972
|
+
|
973
|
+
if ($compat) {
|
974
|
+
foreach my $filter (keys(%filters)) {
|
975
|
+
if ($trigger->{$filter}) {
|
976
|
+
$string .= '-' . $filter . param_to_string($trigger->{$filter});
|
977
|
+
}
|
978
|
+
}
|
979
|
+
} else {
|
980
|
+
foreach my $trigfilter (@{$trigger->{'filters'}}) {
|
981
|
+
$string .= '-' . $trigfilter->[0] . param_to_string($trigfilter->[1]);
|
982
|
+
}
|
983
|
+
}
|
984
|
+
|
985
|
+
foreach my $param (@trigger_params) {
|
986
|
+
if ($trigger->{$param} || ($param eq 'replace' && defined($trigger->{'replace'}))) {
|
987
|
+
$string .= '-' . $param . param_to_string($trigger->{$param});
|
988
|
+
}
|
989
|
+
}
|
990
|
+
return $string;
|
991
|
+
}
|
992
|
+
|
993
|
+
# find a trigger (for REPLACE and DELETE), returns index of trigger, or -1 if not found
|
994
|
+
sub find_trigger {
|
995
|
+
my ($data) = @_;
|
996
|
+
if ($data =~ /^[0-9]*$/ and defined($triggers[$data-1])) {
|
997
|
+
return $data-1;
|
998
|
+
} else {
|
999
|
+
for (my $i=0; $i < scalar(@triggers); $i++) {
|
1000
|
+
if ($triggers[$i]->{'name'} eq $data) {
|
1001
|
+
return $i;
|
1002
|
+
}
|
1003
|
+
}
|
1004
|
+
}
|
1005
|
+
Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trigger_not_found', $data);
|
1006
|
+
return -1; # not found
|
1007
|
+
}
|
1008
|
+
|
1009
|
+
|
1010
|
+
# TRIGGER ADD <options>
|
1011
|
+
sub cmd_add {
|
1012
|
+
my ($data, $server, $item) = @_;
|
1013
|
+
my @args = shellwords($data . ' a');
|
1014
|
+
|
1015
|
+
my $trigger = parse_options({}, @args);
|
1016
|
+
if ($trigger) {
|
1017
|
+
push @triggers, $trigger;
|
1018
|
+
Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trigger_added', scalar(@triggers), to_string($trigger));
|
1019
|
+
rebuild();
|
1020
|
+
$changed_since_last_save = 1;
|
1021
|
+
}
|
1022
|
+
}
|
1023
|
+
|
1024
|
+
# TRIGGER CHANGE <nr> <options>
|
1025
|
+
sub cmd_change {
|
1026
|
+
my ($data, $server, $item) = @_;
|
1027
|
+
my @args = shellwords($data . ' a');
|
1028
|
+
my $index = find_trigger(shift @args);
|
1029
|
+
if ($index != -1) {
|
1030
|
+
if(parse_options($triggers[$index], @args)) {
|
1031
|
+
Irssi::print("Trigger " . ($index+1) ." changed to: ". to_string($triggers[$index]));
|
1032
|
+
}
|
1033
|
+
rebuild();
|
1034
|
+
$changed_since_last_save = 1;
|
1035
|
+
}
|
1036
|
+
}
|
1037
|
+
|
1038
|
+
# parses options for TRIGGER ADD and TRIGGER CHANGE
|
1039
|
+
# if invalid args returns undef, else changes $thetrigger and returns it
|
1040
|
+
sub parse_options {
|
1041
|
+
my ($thetrigger,@args) = @_;
|
1042
|
+
my ($trigger, $option);
|
1043
|
+
|
1044
|
+
if (pop(@args) ne 'a') {
|
1045
|
+
Irssi::print("Syntax error, probably missing a closing quote", MSGLEVEL_CLIENTERROR);
|
1046
|
+
return undef;
|
1047
|
+
}
|
1048
|
+
|
1049
|
+
%$trigger = %$thetrigger; # make a copy to prevent changing the given trigger if args doesn't parse
|
1050
|
+
ARGS: for (my $arg = shift @args; $arg; $arg = shift @args) {
|
1051
|
+
# expand abbreviated options, put in $option
|
1052
|
+
$arg =~ s/^-//;
|
1053
|
+
$option = undef;
|
1054
|
+
foreach my $ioption (@trigger_options) {
|
1055
|
+
if (index($ioption, $arg) == 0) { # -$opt starts with $arg
|
1056
|
+
if ($option) { # another already matched
|
1057
|
+
Irssi::print("Ambiguous option: $arg", MSGLEVEL_CLIENTERROR);
|
1058
|
+
return undef;
|
1059
|
+
}
|
1060
|
+
$option = $ioption;
|
1061
|
+
last if ($arg eq $ioption); # exact match is unambiguous
|
1062
|
+
}
|
1063
|
+
}
|
1064
|
+
if (!$option) {
|
1065
|
+
Irssi::print("Unknown option: $arg", MSGLEVEL_CLIENTERROR);
|
1066
|
+
return undef;
|
1067
|
+
}
|
1068
|
+
|
1069
|
+
# -<param> <value> or -no<param>
|
1070
|
+
foreach my $param (@trigger_params) {
|
1071
|
+
if ($option eq $param) {
|
1072
|
+
$trigger->{$param} = shift @args;
|
1073
|
+
next ARGS;
|
1074
|
+
}
|
1075
|
+
if ($option eq 'no'.$param) {
|
1076
|
+
$trigger->{$param} = undef;
|
1077
|
+
next ARGS;
|
1078
|
+
}
|
1079
|
+
}
|
1080
|
+
|
1081
|
+
# -[no]<switch>
|
1082
|
+
foreach my $switch (@trigger_switches) {
|
1083
|
+
# -<switch>
|
1084
|
+
if ($option eq $switch) {
|
1085
|
+
$trigger->{$switch} = 1;
|
1086
|
+
next ARGS;
|
1087
|
+
}
|
1088
|
+
# -no<switch>
|
1089
|
+
elsif ($option eq 'no'.$switch) {
|
1090
|
+
$trigger->{$switch} = undef;
|
1091
|
+
next ARGS;
|
1092
|
+
}
|
1093
|
+
}
|
1094
|
+
|
1095
|
+
# -<filter> <value>
|
1096
|
+
if ($filters{$option}) {
|
1097
|
+
push @{$trigger->{'filters'}}, [$option, shift @args, $filters{$option}->{'sub'}];
|
1098
|
+
next ARGS;
|
1099
|
+
}
|
1100
|
+
|
1101
|
+
# -<nofilter>
|
1102
|
+
if ($option =~ /^no(.*)$/ && $filters{$1}) {
|
1103
|
+
my $filter = $1;
|
1104
|
+
# the new filters are the old grepped for everything except ones with name $filter
|
1105
|
+
@{$trigger->{'filters'}} = grep( $_->[0] ne $filter, @{$trigger->{'filters'}} );
|
1106
|
+
}
|
1107
|
+
}
|
1108
|
+
|
1109
|
+
if (defined($trigger->{'replace'}) && ! $trigger->{'regexp'} && !$trigger->{'pattern'}) {
|
1110
|
+
Irssi::print("Trigger error: Can't have -replace without -regexp", MSGLEVEL_CLIENTERROR);
|
1111
|
+
return undef;
|
1112
|
+
}
|
1113
|
+
|
1114
|
+
if ($trigger->{'pattern'} && $trigger->{'regexp'}) {
|
1115
|
+
Irssi::print("Trigger error: Can't have -pattern and -regexp in same trigger", MSGLEVEL_CLIENTERROR);
|
1116
|
+
return undef;
|
1117
|
+
}
|
1118
|
+
|
1119
|
+
# remove types that are implied by -all
|
1120
|
+
if ($trigger->{'all'}) {
|
1121
|
+
foreach my $type (@all_types) {
|
1122
|
+
delete $trigger->{$type};
|
1123
|
+
}
|
1124
|
+
}
|
1125
|
+
|
1126
|
+
# remove types for which the filters don't apply
|
1127
|
+
foreach my $type (@trigger_types) {
|
1128
|
+
if ($trigger->{$type}) {
|
1129
|
+
foreach my $filter (@{$trigger->{'filters'}}) {
|
1130
|
+
if (!grep {$_ eq $type} @{$filters{$filter->[0]}->{'types'}}) {
|
1131
|
+
Irssi::print("Warning: the filter -" . $filter->[0] . " can't apply to an event of type -$type, so I'm removing that type from this trigger.");
|
1132
|
+
delete $trigger->{$type};
|
1133
|
+
}
|
1134
|
+
}
|
1135
|
+
}
|
1136
|
+
}
|
1137
|
+
|
1138
|
+
# check if it has at least one type
|
1139
|
+
my $has_a_type;
|
1140
|
+
foreach my $type (@trigger_types) {
|
1141
|
+
if ($trigger->{$type}) {
|
1142
|
+
$has_a_type = 1;
|
1143
|
+
last;
|
1144
|
+
}
|
1145
|
+
}
|
1146
|
+
if (!$has_a_type && !$trigger->{'all'}) {
|
1147
|
+
Irssi::print("Warning: this trigger doesn't trigger on any type of message. you probably want to add -publics or -all");
|
1148
|
+
}
|
1149
|
+
|
1150
|
+
compile_trigger($trigger);
|
1151
|
+
%$thetrigger = %$trigger; # copy changes to real trigger
|
1152
|
+
return $thetrigger;
|
1153
|
+
}
|
1154
|
+
|
1155
|
+
# TRIGGER DELETE <num>
|
1156
|
+
sub cmd_del {
|
1157
|
+
my ($data, $server, $item) = @_;
|
1158
|
+
my @args = shellwords($data);
|
1159
|
+
my $index = find_trigger(shift @args);
|
1160
|
+
if ($index != -1) {
|
1161
|
+
Irssi::print("Deleted ". ($index+1) .": ". to_string($triggers[$index]));
|
1162
|
+
splice (@triggers,$index,1);
|
1163
|
+
rebuild();
|
1164
|
+
$changed_since_last_save = 1;
|
1165
|
+
}
|
1166
|
+
}
|
1167
|
+
|
1168
|
+
# TRIGGER MOVE <num> <num>
|
1169
|
+
sub cmd_move {
|
1170
|
+
my ($data, $server, $item) = @_;
|
1171
|
+
my @args = &shellwords($data);
|
1172
|
+
my $index = find_trigger(shift @args);
|
1173
|
+
if ($index != -1) {
|
1174
|
+
my $newindex = shift @args;
|
1175
|
+
if ($newindex < 1 || $newindex > scalar(@triggers)) {
|
1176
|
+
Irssi::print("$newindex is not a valid trigger number");
|
1177
|
+
return;
|
1178
|
+
}
|
1179
|
+
Irssi::print("Moved from ". ($index+1) ." to $newindex: ". to_string($triggers[$index]));
|
1180
|
+
$newindex -= 1; # array starts counting from 0
|
1181
|
+
my $trigger = splice (@triggers,$index,1); # remove from old place
|
1182
|
+
splice (@triggers,$newindex,0,($trigger)); # insert at new place
|
1183
|
+
rebuild();
|
1184
|
+
$changed_since_last_save = 1;
|
1185
|
+
}
|
1186
|
+
}
|
1187
|
+
|
1188
|
+
# TRIGGER LIST
|
1189
|
+
sub cmd_list {
|
1190
|
+
Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trigger_header');
|
1191
|
+
my $i=1;
|
1192
|
+
foreach my $trigger (@triggers) {
|
1193
|
+
Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trigger_line', $i++, to_string($trigger));
|
1194
|
+
}
|
1195
|
+
}
|
1196
|
+
|
1197
|
+
######################
|
1198
|
+
### initialisation ###
|
1199
|
+
######################
|
1200
|
+
|
1201
|
+
command_bind('trigger help',\&cmd_help);
|
1202
|
+
command_bind('help trigger',\&cmd_help);
|
1203
|
+
command_bind('trigger add',\&cmd_add);
|
1204
|
+
command_bind('trigger change',\&cmd_change);
|
1205
|
+
command_bind('trigger move',\&cmd_move);
|
1206
|
+
command_bind('trigger list',\&cmd_list);
|
1207
|
+
command_bind('trigger delete',\&cmd_del);
|
1208
|
+
command_bind('trigger save',\&cmd_save);
|
1209
|
+
command_bind('trigger reload',\&cmd_load);
|
1210
|
+
command_bind 'trigger' => sub {
|
1211
|
+
my ( $data, $server, $item ) = @_;
|
1212
|
+
$data =~ s/\s+$//g;
|
1213
|
+
command_runsub('trigger', $data, $server, $item);
|
1214
|
+
};
|
1215
|
+
|
1216
|
+
Irssi::signal_add('setup saved', \&autosave);
|
1217
|
+
Irssi::signal_add('setup changed', \&sig_setup_changed);
|
1218
|
+
|
1219
|
+
# This makes tab completion work
|
1220
|
+
Irssi::command_set_options('trigger add',join(' ',@trigger_add_options));
|
1221
|
+
Irssi::command_set_options('trigger change',join(' ',@trigger_options));
|
1222
|
+
|
1223
|
+
Irssi::settings_add_str($IRSSI{'name'}, 'trigger_file', Irssi::get_irssi_dir()."/triggers");
|
1224
|
+
|
1225
|
+
cmd_load();
|