File: Grant.pm

package info (click to toggle)
tiarra 20100212-4
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 2,732 kB
  • ctags: 1,712
  • sloc: perl: 32,032; lisp: 193; sh: 109; makefile: 10
file content (146 lines) | stat: -rw-r--r-- 4,705 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# -----------------------------------------------------------------------------
# $Id: Grant.pm 15318 2008-07-06 15:34:51Z hio $
# -----------------------------------------------------------------------------
package Channel::Mode::Oper::Grant;
use strict;
use warnings;
use base qw(Module);
use Mask;
use Multicast;
use Timer;
use base qw(Tiarra::Mixin::NewIRCMessage);

sub new {
    my $class = shift;
    my $this = $class->SUPER::new(@_);
    $this->{queue} = {}; # network name => [[channel(short),nick], ...]
    $this->{timer} = undef; # queueが空でない時だけ必要になるTimer
    $this;
}

sub destruct {
    my ($this) = @_;
    if (defined $this->{timer}) {
	$this->{timer}->uninstall;
    }
}

sub message_arrived {
    my ($this,$msg,$sender) = @_;
    # 先に進むための条件:
    # 1. サーバーからのメッセージである
    # 2. コマンドはJOINである
    # 3. 自分のJOINではない
    # 4. @付きのJOINではない
    # 5. そのチャンネルで自分は@を持っている
    # 6. 相手はmaskに一致する
    if ($sender->isa('IrcIO::Server') &&
	$msg->command eq 'JOIN' &&
	defined $msg->nick &&
	$msg->nick ne RunLoop->shared->current_nick) {
	foreach (split /,/,$msg->param(0)) {
	    my ($ch_full,$mode) = (m/^(.+?)(?:\x07(.*))?$/);
	    my $ch_short = Multicast::detatch($ch_full);
	    my $ch = $sender->channel($ch_short);
	    my $myself = $ch->names($sender->current_nick);
	    if (defined $myself && $myself->has_o && (!defined $mode || $mode !~ /o/)) {
		if (Mask::match_deep_chan([$this->config->mask('all')],$msg->prefix,$ch_full)) {
		    # waitで指定された秒数の経過後に、キューに入れる。
		    # 同時にキュー消化タイマーを準備する。
		    $this->push_to_queue($sender,$ch_short,$msg->nick);
		}
	    }
	}
    }
    $msg;
}

sub push_to_queue {
    my ($this,$server,$ch_short,$nick) = @_;
    my $wait = $this->config->wait || 0;
    if ($wait =~ /^\s*(\d+)\s*-\s*(\d+)\s*$/) {
	$wait = int(rand($2 - $1 + 1)) + $1;
    }
    Timer->new(
	After => $wait,
	Code => sub {
	    # 対象の人が既に+oされていたら中止。
	    my $ch = $server->channel($ch_short);
	    return if !defined $ch;
	    my $target = $ch->names($nick);
	    return if !defined $target;
	    return if $target->has_o;

	    my $queue = $this->{queue}->{$server->network_name};
	    if (!defined $queue) {
		$queue = $this->{queue}->{$server->network_name} = [];
	    }
	    push @$queue,[$ch_short,$nick];
	    $this->prepare_timer;
	})->install;
}

sub prepare_timer {
    my ($this) = @_;
    # キュー消化タイマーが存在しなければ作る
    if (!defined $this->{timer}) {
	$this->{timer} = Timer->new(
	    Interval => 0, # 勿論、最初のtriggerで変更する。
	    Repeat => 1,
	    Code => sub {
		my ($timer) = @_;
		$timer->interval(1);

		# 鯖毎に3つずつ消化する。
		# チャンネル毎に最大3つずつ纏める。
		foreach my $network_name (keys %{$this->{queue}}) {
		    my $queue = $this->{queue}->{$network_name};
		    my $server = $this->_runloop->network($network_name);
		    my $channels = {}; # ch_shortname => [nick,nick,...]
		    for (my $i = 0; @$queue && $i < 3; $i++) {
			my $elem = shift(@$queue);
			my $nicks = $channels->{$elem->[0]};
			if (!defined $nicks) {
			    $nicks = $channels->{$elem->[0]} = [];
			}
			push @$nicks,$elem->[1];
		    }
		    while (my ($ch_short,$nicks) = each %$channels) {
			$server->send_message(
			    $this->construct_irc_message(
				Command => 'MODE',
				Params => [$ch_short,
					   '+'.('o' x @$nicks),
					   @$nicks]));
		    }
		    # キューが空になったらキーごと消す。
		    delete $this->{queue}->{$network_name} unless @$queue;
		}

		# 全てのキューが空になったら終了。
		if (!%{$this->{queue}}) {
		    $timer->uninstall;
		    $this->{timer} = undef;
		}
	    })->install;
    }
}

1;

=pod
info: 特定のチャンネルに特定の人間がjoinした時に、自分がチャンネルオペレータ権限を持っていれば+oする。
default: off
section: important

# splitからの復帰などで+o対象の人が一度に大量に入って来ても+oは少しずつ実行します。
# Excess Floodにはならない筈ですが、本格的な防衛BOTに使える程の物ではありません。

# 対象の人間がjoinしてから実際に+oするまで何秒待つか。
# 省略されたら待ちません。
# 5-10 のように指定されると、その値の中でランダムに待ちます。
wait: 2-5

# チャンネルと人間のマスクを定義。Auto::Operと同様。
-mask: * example!~example@*.example.ne.jp
=cut