1# -----------------------------------------------------------------------------
2# $Id: Grant.pm 15318 2008-07-06 15:34:51Z hio $
3# -----------------------------------------------------------------------------
4package Channel::Mode::Oper::Grant;
5use strict;
6use warnings;
7use base qw(Module);
8use Mask;
9use Multicast;
10use Timer;
11use base qw(Tiarra::Mixin::NewIRCMessage);
12
13sub new {
14    my $class = shift;
15    my $this = $class->SUPER::new(@_);
16    $this->{queue} = {}; # network name => [[channel(short),nick], ...]
17    $this->{timer} = undef; # queueが空でない時だけ必要になるTimer
18    $this;
19}
20
21sub destruct {
22    my ($this) = @_;
23    if (defined $this->{timer}) {
24	$this->{timer}->uninstall;
25    }
26}
27
28sub message_arrived {
29    my ($this,$msg,$sender) = @_;
30    # 先に進むための条件:
31    # 1. サーバーからのメッセージである
32    # 2. コマンドはJOINである
33    # 3. 自分のJOINではない
34    # 4. @付きのJOINではない
35    # 5. そのチャンネルで自分は@を持っている
36    # 6. 相手はmaskに一致する
37    if ($sender->isa('IrcIO::Server') &&
38	$msg->command eq 'JOIN' &&
39	defined $msg->nick &&
40	$msg->nick ne RunLoop->shared->current_nick) {
41	foreach (split /,/,$msg->param(0)) {
42	    my ($ch_full,$mode) = (m/^(.+?)(?:\x07(.*))?$/);
43	    my $ch_short = Multicast::detatch($ch_full);
44	    my $ch = $sender->channel($ch_short);
45	    my $myself = $ch->names($sender->current_nick);
46	    if (defined $myself && $myself->has_o && (!defined $mode || $mode !~ /o/)) {
47		if (Mask::match_deep_chan([$this->config->mask('all')],$msg->prefix,$ch_full)) {
48		    # waitで指定された秒数の経過後に、キューに入れる。
49		    # 同時にキュー消化タイマーを準備する。
50		    $this->push_to_queue($sender,$ch_short,$msg->nick);
51		}
52	    }
53	}
54    }
55    $msg;
56}
57
58sub push_to_queue {
59    my ($this,$server,$ch_short,$nick) = @_;
60    my $wait = $this->config->wait || 0;
61    if ($wait =~ /^\s*(\d+)\s*-\s*(\d+)\s*$/) {
62	$wait = int(rand($2 - $1 + 1)) + $1;
63    }
64    Timer->new(
65	After => $wait,
66	Code => sub {
67	    # 対象の人が既に+oされていたら中止。
68	    my $ch = $server->channel($ch_short);
69	    return if !defined $ch;
70	    my $target = $ch->names($nick);
71	    return if !defined $target;
72	    return if $target->has_o;
73
74	    my $queue = $this->{queue}->{$server->network_name};
75	    if (!defined $queue) {
76		$queue = $this->{queue}->{$server->network_name} = [];
77	    }
78	    push @$queue,[$ch_short,$nick];
79	    $this->prepare_timer;
80	})->install;
81}
82
83sub prepare_timer {
84    my ($this) = @_;
85    # キュー消化タイマーが存在しなければ作る
86    if (!defined $this->{timer}) {
87	$this->{timer} = Timer->new(
88	    Interval => 0, # 勿論、最初のtriggerで変更する。
89	    Repeat => 1,
90	    Code => sub {
91		my ($timer) = @_;
92		$timer->interval(1);
93
94		# 鯖毎に3つずつ消化する。
95		# チャンネル毎に最大3つずつ纏める。
96		foreach my $network_name (keys %{$this->{queue}}) {
97		    my $queue = $this->{queue}->{$network_name};
98		    my $server = $this->_runloop->network($network_name);
99		    my $channels = {}; # ch_shortname => [nick,nick,...]
100		    for (my $i = 0; @$queue && $i < 3; $i++) {
101			my $elem = shift(@$queue);
102			my $nicks = $channels->{$elem->[0]};
103			if (!defined $nicks) {
104			    $nicks = $channels->{$elem->[0]} = [];
105			}
106			push @$nicks,$elem->[1];
107		    }
108		    while (my ($ch_short,$nicks) = each %$channels) {
109			$server->send_message(
110			    $this->construct_irc_message(
111				Command => 'MODE',
112				Params => [$ch_short,
113					   '+'.('o' x @$nicks),
114					   @$nicks]));
115		    }
116		    # キューが空になったらキーごと消す。
117		    delete $this->{queue}->{$network_name} unless @$queue;
118		}
119
120		# 全てのキューが空になったら終了。
121		if (!%{$this->{queue}}) {
122		    $timer->uninstall;
123		    $this->{timer} = undef;
124		}
125	    })->install;
126    }
127}
128
1291;
130
131=pod
132info: 特定のチャンネルに特定の人間がjoinした時に、自分がチャンネルオペレータ権限を持っていれば+oする。
133default: off
134section: important
135
136# splitからの復帰などで+o対象の人が一度に大量に入って来ても+oは少しずつ実行します。
137# Excess Floodにはならない筈ですが、本格的な防衛BOTに使える程の物ではありません。
138
139# 対象の人間がjoinしてから実際に+oするまで何秒待つか。
140# 省略されたら待ちません。
141# 5-10 のように指定されると、その値の中でランダムに待ちます。
142wait: 2-5
143
144# チャンネルと人間のマスクを定義。Auto::Operと同様。
145-mask: * example!~example@*.example.ne.jp
146=cut
147