1use Irssi 20020300;
2use strict;
3
4use vars qw($VERSION %IRSSI %HELP);
5$HELP{getop} = "
6GETOP [channel]
7
8Gets op on current channel or 'channel' from random opped bot added by ADDGETOP.
9";
10$HELP{addgetop} = "
11ADDGETOP [channel] <mask> <command>
12
13Adds entry to 'channel' or current channel getop list.
14The \$0 in command specifies nick of random found mask
15in channel.
16";
17$HELP{delgetop} = "
18DELGETOP [channel] <mask or index number from LISTGETOP>
19
20Deletes entry from getoplist on current channel or 'channel'.
21";
22$HELP{listgetop} = "
23LISTGETOP [channel]
24
25Lists all entries in getop list or just 'channel's getop list.
26";
27$VERSION = "0.9b";
28%IRSSI = (
29	authors         => "Maciek \'fahren\' Freudenheim",
30	contact         => "fahren\@bochnia.pl",
31	name            => "GetOP",
32	description     => "Automatically request op from random opped person with specifed command from list after joining channel",
33	license         => "GNU GPLv2 or later",
34	changed         => "Fri Jan 10 03:54:07 CET 2003"
35);
36
37Irssi::theme_register([
38	'getop_listline', '[%W$[!-2]0%n]%| $[40]1%_: %_$2',
39	'getop_add', 'Added \'%_$2%_\' to getop list on channel %_$1%_ /$0/',
40	'getop_del', 'Deleted \'%_$2%_\' from getop list on channel %_$1%_ /$0/',
41	'getop_changed', 'Changed command for mask \'%_$2%_\' on channel %_$1%_ /$0/',
42	'getop_noone', '"%Y>>%n No one to get op from on $1 /$0/',
43	'getop_get', '%Y>>%n Getting op from %_$2%_ on $1 /$0/'
44]);
45
46my %getop = ();
47my @userhosts;
48my $getopfile = Irssi::get_irssi_dir . "/getoplist";
49
50sub sub_getop {
51	my ($args, $server, $winit) = @_;
52
53	my $chan;
54	my ($channel) = $args =~ /^([^\s]+)/;
55
56	if ($server->ischannel($channel)) {
57		unless ($chan = $server->channel_find($channel)) {
58			Irssi::print("%R>>%n You are not on $channel.");
59			return;
60		}
61		$args =~ s/^[^\s]+\s?//;
62	} else {
63		unless ($winit && $winit->{type} eq "CHANNEL") {
64			Irssi::print("%R>>%n You don't have active channel in that window.");
65			return;
66		}
67		$channel = $winit->{name};
68		$chan = $winit;
69	}
70
71	if ($chan->{chanop}) {
72		Irssi::print("%R>>%n You are already opped on $channel.");
73		return;
74	}
75
76	$channel = lc($channel);
77	my $tag = lc($server->{tag});
78
79	unless ($getop{$tag}{$channel}) {
80		Irssi::print("%R>>%n Your getop list on channel $channel is empty. Use /ADDGETOP first.");
81		return;
82	};
83
84	unless ($getop{$tag}{$channel}) {
85		Irssi::print("%R>>%n Your getop list on channel $channel is empty.");
86		return;
87	}
88
89	getop_proc($tag, $chan);
90}
91
92sub sub_addgetop {
93	my ($args, $server, $winit) = @_;
94
95	my ($channel) = $args =~ /^([^\s]+)/;
96
97	if ($server->ischannel($channel)) {
98		$args =~ s/^[^\s]+\s?//;
99	} else {
100		unless ($winit && $winit->{type} eq "CHANNEL") {
101			Irssi::print("%R>>%n You don't have active channel in that window.");
102			return;
103		}
104		$channel = $winit->{name};
105	}
106
107	my ($mask, $command) = split(/ +/, $args, 2);
108
109	unless ($command) {
110		Irssi::print("Usage: /ADDGETOP [channel] <mask or nickname> <command>. If you type '\$0' in command then it will be changed automatically into mask's nick.");
111		return;
112	}
113
114	my $cmdchar = Irssi::settings_get_str('cmdchars');
115	$command =~ s/^($cmdchar*)\^?/\1^/g;
116
117	if (index($mask, "@") == -1) {
118		my ($c, $n);
119		if (($c = $server->channel_find($channel)) && ($n = $c->nick_find($mask))) {
120			$mask = $n->{host};
121			$mask =~ s/^[~+\-=^]/*/;
122		} else {
123			$server->redirect_event('userhost', 1, $mask, 0, undef, {
124					'event 302' => 'redir getop userhost',
125					'' => 'event empty' } );
126			$server->send_raw("USERHOST $mask");
127			my $uh = lc($mask) . " " . lc($channel) . " $command";
128			push @userhosts,  $uh;
129			return;
130		}
131	}
132
133	$mask = "*!" . $mask if (index($mask, "!") == -1);
134	my $tag = lc($server->{tag});
135	my $channel = lc($channel);
136
137	for my $entry (@{$getop{$tag}{$channel}}) {
138		if ($entry->{mask} eq $mask) {
139			$entry->{command} = $command;
140			Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'getop_changed', $tag, $channel, $mask, $command);
141			&savegetop;
142			return;
143		}
144	}
145
146	my $gh = {
147		mask	=> $mask,
148		command	=> $command
149	};
150
151	push @{$getop{$tag}{$channel}}, $gh;
152
153	Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'getop_add', $tag, $channel, $mask, $command);
154
155	&savegetop;
156}
157
158sub sub_delgetop {
159	my ($args, $server, $winit) = @_;
160
161	my ($channel) = $args =~ /^([^\s]+)/;
162
163	if ($server->ischannel($channel)) {
164		$args =~ s/^[^\s]+\s?//;
165	} else {
166		unless ($winit && $winit->{type} eq "CHANNEL") {
167			Irssi::print("%R>>%n You don't have active channel in that window.");
168			return;
169		}
170		$channel = $winit->{name};
171	}
172
173	my $tag = lc($server->{tag});
174	my $channel = lc($channel);
175
176	unless ($getop{$tag}{$channel}) {
177		Irssi::print("%R>>%n Your getop list on channel $channel is empty.");
178		return;
179	}
180
181	unless ($args) {
182		Irssi::print("%W>>%n Usage: /DELGETOP [channel] <mask | index from LISTGETOP>");
183		return;
184	}
185
186	my $num;
187	if ($args =~ /^[0-9]+$/) {
188		if ($args > scalar(@{$getop{$tag}{$channel}})) {
189			Irssi::print("%R>>%n No such entry in $channel getop list.");
190			return;
191		}
192		$num = $args - 1;
193	} else {
194		my $i = 0;
195		for my $entry (@{$getop{$tag}{$channel}}) {
196			$args eq $entry->{mask} and $num = $i, last;
197			$i++;
198		}
199	}
200
201	if (my($gh) = splice(@{$getop{$tag}{$channel}}, $num, 1)) {
202		Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'getop_del', $tag, $channel, $gh->{mask}, $gh->{command});
203		unless (scalar(@{$getop{$tag}{$channel}})) {
204			Irssi::print("%R>>%n No more entries in $channel getop list left.");
205			delete $getop{$tag}{$channel};
206		}
207		unless (keys %{$getop{$tag}}) {
208			Irssi::print("%R>>%n No more entries in getop list on $tag left.");
209			delete $getop{$tag};
210		}
211	}
212
213	&savegetop;
214}
215
216sub sub_listgetop {
217	my ($args, $server, $winit) = @_;
218
219	my ($channel) = $args =~ /^([^\s]+)/;
220
221	if ($server->ischannel($channel)) {
222		my $tag = lc($server->{tag});
223		$channel = lc($channel);
224		unless ($getop{$tag}{$channel}) {
225			Irssi::print("%R>>%n Your getop list on channel $channel is empty.");
226			return;
227		}
228		my $i = 0;
229		Irssi::print("Getop list on $channel /$tag/:");
230		for my $entry (@{$getop{$tag}{$channel}}) {
231			Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'getop_listline', $i++, $entry->{mask}, $entry->{command});
232		}
233	} else {
234		unless (keys %getop) {
235			Irssi::print("%R>>%n Your getop list is empty. /ADDGETOP first.");
236			return;
237		}
238		for my $ircnet (keys %getop) {
239			for my $chan (keys %{$getop{$ircnet}}) {
240				Irssi::print("Channel: $chan /$ircnet/");
241				my $i = 1;
242				for my $entry (@{$getop{$ircnet}{$chan}}) {
243					Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'getop_listline', $i++, $entry->{mask}, $entry->{command});
244				}
245			}
246		}
247	}
248}
249
250sub userhost_red {
251	my ($server, $data) = @_;
252	$data =~ s/^[^ ]* :?//;
253
254	my $uh = shift @userhosts;
255	my ($nick, $chan, $command) = split(/ /, $uh, 3);
256
257	unless ($data && $data =~ /^([^=\*]*)\*?=.(.*)@(.*)/ && lc($1) eq $nick) {
258		Irssi::print("%R>>%n No such nickname: $nick");
259		return;
260	}
261
262	my ($user, $host) = ($2, $3);
263	$user =~ s/^[~+\-=^]/*/;
264	my $mask = "*!" . $user . "@" . $host;
265	my $tag = lc($server->{tag});
266
267	for my $entry (@{$getop{$tag}{$chan}}) {
268		if ($entry->{mask} eq $mask) {
269			$entry->{command} = $command;
270			Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'getop_changed', $tag, $chan, $mask, $command);
271			&savegetop;
272			return;
273		}
274	}
275
276	my $gh = {
277		mask => $mask,
278		command => $command
279	};
280
281	push @{$getop{$tag}{$chan}}, $gh;
282
283	Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'getop_add', $tag, $chan, $mask, $command);
284
285	&savegetop;
286}
287
288sub getop_proc ($$) {
289	my ($tag, $chan) = @_;
290
291	my $channel = lc($chan->{name});
292	return unless ($getop{$tag}{$channel});
293
294	my (@list, $mask);
295	for my $nick ($chan->nicks()) {
296		next unless ($nick->{op});
297		$mask = $nick->{nick} . "!" . $nick->{host};
298		for my $entry (@{$getop{$tag}{$channel}}) {
299			if (mask_match($mask, $entry->{mask})) {
300				my $lh = {
301					nick	=> $nick->{nick},
302					command	=> $entry->{command}
303				};
304				push @list, $lh;
305			}
306		}
307	}
308
309	unless (@list) {
310		Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'getop_noone', $tag, $channel);
311	} else {
312		my $get = $list[int(rand(@list))];
313		$get->{command} =~ s/\$0/$get->{nick}/g;
314		Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'getop_get', $tag, $channel, $get->{nick}, $get->{command});
315		$chan->command($get->{command});
316	}
317}
318
319sub mask_match ($$) {
320	my ($what, $match) = @_;
321
322	$match =~ s/\\/\\\\/g;
323	$match =~ s/\./\\\./g;
324	$match =~ s/\*/\.\*/g;
325	$match =~ s/\!/\\\!/g;
326	$match =~ s/\?/\./g;
327	$match =~ s/\+/\\\+/g;
328	$match =~ s/\^/\\\^/g;
329
330	return ($what =~ /^$match$/i);
331}
332
333sub got_notopped {
334	my ($server, $data) = @_;
335	my ($chan) = $data =~ /^[^\s]+\s([^\s]+)\s:/;
336	getop_proc(lc($server->{tag}), $server->channel_find($chan));
337}
338
339sub channel_sync {
340	my $chan = shift;
341	getop_proc(lc($chan->{server}->{tag}), $chan) unless ($chan->{chanop});
342}
343
344sub savegetop {
345	local *fp;
346	open (fp, ">", $getopfile) or die "Couldn't open $getopfile for writing";
347
348	for my $ircnet (keys %getop) {
349		for my $chan (keys %{$getop{$ircnet}}) {
350			for my $entry (@{$getop{$ircnet}{$chan}}) {
351				print(fp "$ircnet $chan $entry->{mask} $entry->{command}\n");
352			}
353		}
354	}
355
356	close fp;
357}
358
359sub loadgetop {
360	%getop = ();
361	return unless (-e $getopfile);
362	local *fp;
363
364	open (fp, "<", $getopfile) or die "Couldn't open $getopfile for reading";
365	local $/ = "\n";
366
367	while (<fp>) {
368		chop;
369		my $gh = {};
370		my ($tag, $chan);
371		($tag, $chan, $gh->{mask}, $gh->{command}) = split(/ /, $_, 4);
372		push @{$getop{$tag}{$chan}}, $gh;
373	}
374
375	close fp;
376}
377
378&loadgetop;
379
380Irssi::command_bind( {
381		'getop' => \&sub_getop,
382		'addgetop' => \&sub_addgetop,
383		'delgetop' => \&sub_delgetop,
384		'listgetop' => \&sub_listgetop } );
385Irssi::signal_add({ 'redir getop userhost' => \&userhost_red,
386		    'event 482' => \&got_notopped,
387	    	    'channel sync' => \&channel_sync});
388