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