| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package POE::Component::IRC::Plugin::CPAN::Info; |
|
2
|
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
246424
|
use warnings; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
38
|
|
|
4
|
1
|
|
|
1
|
|
6
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
43
|
|
|
5
|
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
our $VERSION = '1.001002'; # VERSION |
|
7
|
|
|
|
|
|
|
|
|
8
|
1
|
|
|
1
|
|
4
|
use Carp; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
76
|
|
|
9
|
1
|
|
|
1
|
|
6
|
use POE; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
9
|
|
|
10
|
1
|
|
|
1
|
|
460
|
use POE::Component::IRC::Plugin (qw( :ALL )); |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
160
|
|
|
11
|
1
|
|
|
1
|
|
1217
|
use POE::Component::CPAN::SQLite::Info; |
|
|
1
|
|
|
|
|
262222
|
|
|
|
1
|
|
|
|
|
4784
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
sub new { |
|
14
|
0
|
|
|
0
|
0
|
|
my $package = shift; |
|
15
|
0
|
|
|
|
|
|
my %args = @_; |
|
16
|
0
|
|
|
|
|
|
$args{ lc $_ } = delete $args{ $_ } for keys %args; |
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
# load defaults and override with user args if any |
|
19
|
0
|
|
|
|
|
|
%args = ( |
|
20
|
|
|
|
|
|
|
mirror => 'http://cpan.perl.org/', |
|
21
|
|
|
|
|
|
|
path => 'cpan_sqlite_info', |
|
22
|
|
|
|
|
|
|
debug => 0, |
|
23
|
|
|
|
|
|
|
freshen_interval => 43200, |
|
24
|
|
|
|
|
|
|
got_info_event => 'cpaninfo_got_info', |
|
25
|
|
|
|
|
|
|
no_result_event => 'cpaninfo_no_result', |
|
26
|
|
|
|
|
|
|
response_event => 'cpaninfo_response', |
|
27
|
|
|
|
|
|
|
respond_no_result => 1, |
|
28
|
|
|
|
|
|
|
send_events => 1, |
|
29
|
|
|
|
|
|
|
show_help => 1, |
|
30
|
|
|
|
|
|
|
eat => 0, |
|
31
|
|
|
|
|
|
|
listen_for_help => [ qw(public notice privmsg) ], |
|
32
|
|
|
|
|
|
|
listen_for_input => [ qw(public notice privmsg) ], |
|
33
|
|
|
|
|
|
|
no_result_responses => [ 'No clue', 'No idea' ], |
|
34
|
|
|
|
|
|
|
max_modules_limit => 5, |
|
35
|
|
|
|
|
|
|
max_modules_length => 300, |
|
36
|
|
|
|
|
|
|
max_output_length => 600, |
|
37
|
|
|
|
|
|
|
max_output_length_pub => 400, |
|
38
|
|
|
|
|
|
|
output_line_length => 300, |
|
39
|
|
|
|
|
|
|
banned => [], |
|
40
|
|
|
|
|
|
|
%args, |
|
41
|
|
|
|
|
|
|
); |
|
42
|
|
|
|
|
|
|
|
|
43
|
0
|
|
|
|
|
|
for my $listen_type (qw( listen_for_help listen_for_input )) { |
|
44
|
0
|
|
|
|
|
|
$args{ $listen_type } = { |
|
45
|
0
|
|
|
|
|
|
map { lc $_ => 1 } |
|
46
|
0
|
|
|
|
|
|
@{ $args{ $listen_type } } |
|
47
|
|
|
|
|
|
|
}; |
|
48
|
|
|
|
|
|
|
} |
|
49
|
|
|
|
|
|
|
|
|
50
|
0
|
0
|
|
|
|
|
unless ( exists $args{ua_args}{timeout} ) { |
|
51
|
0
|
|
|
|
|
|
$args{ua_args}{timeout} = 30; |
|
52
|
|
|
|
|
|
|
} |
|
53
|
|
|
|
|
|
|
|
|
54
|
0
|
0
|
0
|
|
|
|
if ( exists $args{channels} and ref $args{channels} ne 'ARRAY' ) { |
|
55
|
0
|
|
|
|
|
|
carp "Argument `channels` must contain an arrayref.."; |
|
56
|
0
|
|
|
|
|
|
return; |
|
57
|
|
|
|
|
|
|
} |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
# assign default triggers to anything not specified by user. |
|
60
|
0
|
|
|
|
|
|
my $default_triggers_ref = _make_default_triggers(); |
|
61
|
0
|
0
|
|
|
|
|
if ( exists $args{triggers} ) { |
|
62
|
|
|
|
|
|
|
|
|
63
|
0
|
|
|
|
|
|
foreach my $trigger_category (qw( mod dist auth )) { |
|
64
|
0
|
|
|
|
|
|
my $cat_triggers = $default_triggers_ref->{ $trigger_category}; |
|
65
|
|
|
|
|
|
|
|
|
66
|
0
|
0
|
|
|
|
|
$args{triggers}{ $trigger_category } = { |
|
67
|
|
|
|
|
|
|
%$cat_triggers, |
|
68
|
0
|
|
|
|
|
|
%{ $args{triggers}{ $trigger_category } || {} }, |
|
69
|
|
|
|
|
|
|
}; |
|
70
|
|
|
|
|
|
|
|
|
71
|
0
|
|
|
|
|
|
my $cat_trigger = "${trigger_category}_cat"; |
|
72
|
0
|
0
|
|
|
|
|
unless ( exists $args{triggers}{ $cat_trigger } ) { |
|
73
|
0
|
|
|
|
|
|
$args{trigger}{ $cat_trigger } |
|
74
|
|
|
|
|
|
|
= $default_triggers_ref->{ $cat_trigger}; |
|
75
|
|
|
|
|
|
|
} |
|
76
|
|
|
|
|
|
|
} |
|
77
|
|
|
|
|
|
|
} |
|
78
|
|
|
|
|
|
|
else { |
|
79
|
0
|
|
|
|
|
|
$args{triggers} = $default_triggers_ref; |
|
80
|
|
|
|
|
|
|
} |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
# assign default help triggers for anything not specified by user |
|
83
|
0
|
|
|
|
|
|
my $default_help_ref = _make_default_help_triggers(); |
|
84
|
0
|
0
|
|
|
|
|
if ( exists $args{help} ) { |
|
85
|
0
|
|
|
|
|
|
foreach my $category (qw(mod dist auth)) { |
|
86
|
0
|
|
|
|
|
|
my $cat_help = $default_help_ref->{ $category }; |
|
87
|
|
|
|
|
|
|
|
|
88
|
0
|
0
|
|
|
|
|
$args{help}{ $category } = { |
|
89
|
|
|
|
|
|
|
%$cat_help, |
|
90
|
0
|
|
|
|
|
|
%{ $args{help}{ $category } || {} }, |
|
91
|
|
|
|
|
|
|
}; |
|
92
|
|
|
|
|
|
|
|
|
93
|
0
|
|
|
|
|
|
my $cat_trigger = "${category}_cat"; |
|
94
|
0
|
0
|
|
|
|
|
unless ( exists $args{help}{ $cat_trigger } ) { |
|
95
|
0
|
|
|
|
|
|
$args{help}{ $cat_trigger } |
|
96
|
|
|
|
|
|
|
= $default_help_ref->{help}{ $cat_trigger }; |
|
97
|
|
|
|
|
|
|
} |
|
98
|
|
|
|
|
|
|
} |
|
99
|
|
|
|
|
|
|
} |
|
100
|
|
|
|
|
|
|
else { |
|
101
|
0
|
|
|
|
|
|
$args{help} = $default_help_ref; |
|
102
|
|
|
|
|
|
|
} |
|
103
|
|
|
|
|
|
|
|
|
104
|
0
|
0
|
|
|
|
|
unless ( exists $args{help}{help_re} ) { |
|
105
|
0
|
|
|
|
|
|
$args{help}{help_re} = $default_help_ref->{help_re}; |
|
106
|
|
|
|
|
|
|
} |
|
107
|
|
|
|
|
|
|
|
|
108
|
0
|
|
|
|
|
|
return bless \%args, $package; |
|
109
|
|
|
|
|
|
|
} |
|
110
|
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
sub PCI_register { |
|
112
|
0
|
|
|
0
|
0
|
|
my ( $self, $irc ) = splice @_, 0, 2; |
|
113
|
|
|
|
|
|
|
|
|
114
|
0
|
|
|
|
|
|
$self->{irc} = $irc; |
|
115
|
|
|
|
|
|
|
|
|
116
|
0
|
|
|
|
|
|
$irc->plugin_register( $self, 'SERVER', qw(notice public msg) ); |
|
117
|
|
|
|
|
|
|
|
|
118
|
0
|
|
|
|
|
|
$self->{_session_id} = POE::Session->create( |
|
119
|
|
|
|
|
|
|
object_states => [ |
|
120
|
|
|
|
|
|
|
$self => [ |
|
121
|
|
|
|
|
|
|
qw( |
|
122
|
|
|
|
|
|
|
_start |
|
123
|
|
|
|
|
|
|
_shutdown |
|
124
|
|
|
|
|
|
|
_fetched |
|
125
|
|
|
|
|
|
|
_freshen |
|
126
|
|
|
|
|
|
|
_got_info |
|
127
|
|
|
|
|
|
|
) |
|
128
|
|
|
|
|
|
|
] |
|
129
|
|
|
|
|
|
|
], |
|
130
|
|
|
|
|
|
|
)->ID; |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
|
|
133
|
0
|
|
|
|
|
|
return 1; |
|
134
|
|
|
|
|
|
|
} |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
sub _start { |
|
137
|
0
|
|
|
0
|
|
|
my ( $kernel, $self ) = @_[ KERNEL, OBJECT ]; |
|
138
|
0
|
|
|
|
|
|
$self->{_session_id} = $_[SESSION]->ID(); |
|
139
|
0
|
|
|
|
|
|
$kernel->refcount_increment( $self->{_session_id}, __PACKAGE__ ); |
|
140
|
|
|
|
|
|
|
|
|
141
|
0
|
|
|
|
|
|
$self->{poco} = POE::Component::CPAN::SQLite::Info->spawn( |
|
142
|
0
|
|
|
|
|
|
map { $_, $self->{$_} } |
|
143
|
|
|
|
|
|
|
qw(mirror debug path) |
|
144
|
|
|
|
|
|
|
); |
|
145
|
|
|
|
|
|
|
|
|
146
|
0
|
0
|
|
|
|
|
warn "Sending `freshen` request to PoCo::CPAN::SQLite::Info" |
|
147
|
|
|
|
|
|
|
if $self->{debug}; |
|
148
|
|
|
|
|
|
|
|
|
149
|
0
|
|
|
|
|
|
$self->{poco}->freshen( { |
|
150
|
|
|
|
|
|
|
event => '_fetched', |
|
151
|
|
|
|
|
|
|
ua_args => $self->{ua_args}, |
|
152
|
|
|
|
|
|
|
} |
|
153
|
|
|
|
|
|
|
); |
|
154
|
0
|
|
|
|
|
|
$self->{_freshen_alarm} = $kernel->delay( |
|
155
|
|
|
|
|
|
|
'_freshen' => $self->{freshen_interval} |
|
156
|
|
|
|
|
|
|
); |
|
157
|
0
|
|
|
|
|
|
undef; |
|
158
|
|
|
|
|
|
|
} |
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
sub _shutdown { |
|
161
|
0
|
|
|
0
|
|
|
my ($kernel, $self) = @_[ KERNEL, OBJECT ]; |
|
162
|
0
|
|
|
|
|
|
$self->{poco}->shutdown; |
|
163
|
0
|
|
|
|
|
|
$kernel->alarm_remove_all(); |
|
164
|
0
|
|
|
|
|
|
$kernel->refcount_decrement( $self->{_session_id}, __PACKAGE__ ); |
|
165
|
0
|
|
|
|
|
|
undef; |
|
166
|
|
|
|
|
|
|
} |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
sub PCI_unregister { |
|
169
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
|
170
|
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
# Plugin is dying make sure our POE session does as well. |
|
172
|
0
|
|
|
|
|
|
$poe_kernel->call( $self->{_session_id} => '_shutdown' ); |
|
173
|
|
|
|
|
|
|
|
|
174
|
0
|
|
|
|
|
|
delete $self->{irc}; |
|
175
|
|
|
|
|
|
|
|
|
176
|
0
|
|
|
|
|
|
return 1; |
|
177
|
|
|
|
|
|
|
} |
|
178
|
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
sub _freshen { |
|
181
|
0
|
|
|
0
|
|
|
my ( $kernel, $self ) = @_[ KERNEL, OBJECT ]; |
|
182
|
|
|
|
|
|
|
|
|
183
|
0
|
|
|
|
|
|
$self->{poco}->freshen( { |
|
184
|
|
|
|
|
|
|
event => '_fetched', |
|
185
|
|
|
|
|
|
|
ua_args => $self->{ua_args}, |
|
186
|
|
|
|
|
|
|
} |
|
187
|
|
|
|
|
|
|
); |
|
188
|
|
|
|
|
|
|
|
|
189
|
0
|
|
|
|
|
|
$self->{_freshen_alarm} = $kernel->delay( |
|
190
|
|
|
|
|
|
|
'_freshen' => $self->{freshen_delay} |
|
191
|
|
|
|
|
|
|
); |
|
192
|
|
|
|
|
|
|
} |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
sub _fetched { |
|
195
|
0
|
|
|
0
|
|
|
my ( $kernel, $self, $input ) = @_[ KERNEL, OBJECT, ARG0 ]; |
|
196
|
|
|
|
|
|
|
|
|
197
|
0
|
0
|
|
|
|
|
if ( $input->{freshen_error} ) { |
|
198
|
0
|
0
|
|
|
|
|
if ( $self->{debug} ) { |
|
199
|
0
|
0
|
|
|
|
|
if ( $input->{freshen_error} eq 'fetch' ) { |
|
200
|
0
|
|
|
|
|
|
warn "Could not fetch file(s)\n"; |
|
201
|
0
|
|
|
|
|
|
foreach my $file ( keys %{ $input->{freshen_errors} } ) { |
|
|
0
|
|
|
|
|
|
|
|
202
|
0
|
|
|
|
|
|
print "\t$file => $input->{freshen_errors}{ $file }\n"; |
|
203
|
|
|
|
|
|
|
} |
|
204
|
|
|
|
|
|
|
} |
|
205
|
|
|
|
|
|
|
else { |
|
206
|
0
|
|
|
|
|
|
warn "Failed to create storage dir:" |
|
207
|
|
|
|
|
|
|
. " $input->{freshen_error}\n"; |
|
208
|
|
|
|
|
|
|
} |
|
209
|
|
|
|
|
|
|
} |
|
210
|
0
|
0
|
|
|
|
|
$self->{_freshen_alarm} = $kernel->delay( |
|
211
|
|
|
|
|
|
|
'_freshen' => $self->{freshen_interval} < 30 |
|
212
|
|
|
|
|
|
|
? $self->{freshen_interval} : 30, |
|
213
|
|
|
|
|
|
|
); |
|
214
|
|
|
|
|
|
|
} |
|
215
|
|
|
|
|
|
|
else { |
|
216
|
0
|
0
|
|
|
|
|
warn "`freshen` success sending `fetch_info` request" |
|
217
|
|
|
|
|
|
|
if $self->{debug}; |
|
218
|
|
|
|
|
|
|
|
|
219
|
0
|
|
|
|
|
|
$self->{poco}->fetch_info( { event => '_got_info' } ); |
|
220
|
|
|
|
|
|
|
} |
|
221
|
|
|
|
|
|
|
} |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
sub _got_info { |
|
224
|
0
|
|
|
0
|
|
|
my ( $self, $input ) = @_[ OBJECT, ARG0 ]; |
|
225
|
|
|
|
|
|
|
|
|
226
|
0
|
0
|
|
|
|
|
warn "_got_info success" |
|
227
|
|
|
|
|
|
|
if $self->{debug}; |
|
228
|
|
|
|
|
|
|
|
|
229
|
0
|
|
|
|
|
|
delete $input->{path}; |
|
230
|
|
|
|
|
|
|
|
|
231
|
0
|
|
|
|
|
|
$self->{_data} = $input; |
|
232
|
|
|
|
|
|
|
|
|
233
|
0
|
0
|
|
|
|
|
$self->{irc}->send_event( $self->{got_info_event} => time() ) |
|
234
|
|
|
|
|
|
|
if $self->{send_events}; |
|
235
|
|
|
|
|
|
|
} |
|
236
|
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
sub S_public { |
|
238
|
0
|
|
|
0
|
0
|
|
my ( $self, $irc ) = splice @_, 0, 2; |
|
239
|
0
|
|
|
|
|
|
my $who = ${ $_[0] }; |
|
|
0
|
|
|
|
|
|
|
|
240
|
0
|
|
|
|
|
|
my $channel = ${ $_[1] }->[0]; |
|
|
0
|
|
|
|
|
|
|
|
241
|
0
|
|
|
|
|
|
my $message = ${ $_[2] }; |
|
|
0
|
|
|
|
|
|
|
|
242
|
0
|
|
|
|
|
|
return $self->_parse_input( $irc, $who, $channel, $message, 'public' ); |
|
243
|
|
|
|
|
|
|
} |
|
244
|
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
sub S_notice { |
|
246
|
0
|
|
|
0
|
0
|
|
my ( $self, $irc ) = splice @_, 0, 2; |
|
247
|
0
|
|
|
|
|
|
my $who = ${ $_[0] }; |
|
|
0
|
|
|
|
|
|
|
|
248
|
0
|
|
|
|
|
|
my $channel = ${ $_[1] }->[0]; |
|
|
0
|
|
|
|
|
|
|
|
249
|
0
|
|
|
|
|
|
my $message = ${ $_[2] }; |
|
|
0
|
|
|
|
|
|
|
|
250
|
0
|
|
|
|
|
|
return $self->_parse_input( $irc, $who, $channel, $message, 'notice' ); |
|
251
|
|
|
|
|
|
|
} |
|
252
|
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
sub S_msg { |
|
254
|
0
|
|
|
0
|
0
|
|
my ( $self, $irc ) = splice @_, 0, 2; |
|
255
|
0
|
|
|
|
|
|
my $who = ${ $_[0] }; |
|
|
0
|
|
|
|
|
|
|
|
256
|
0
|
|
|
|
|
|
my $channel = ${ $_[1] }->[0]; |
|
|
0
|
|
|
|
|
|
|
|
257
|
0
|
|
|
|
|
|
my $message = ${ $_[2] }; |
|
|
0
|
|
|
|
|
|
|
|
258
|
0
|
|
|
|
|
|
return $self->_parse_input( $irc, $who, $channel, $message, 'privmsg' ); |
|
259
|
|
|
|
|
|
|
} |
|
260
|
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
sub _parse_input { |
|
262
|
0
|
|
|
0
|
|
|
my ( $self, $irc, $who, $channel, $message, $type ) = @_; |
|
263
|
|
|
|
|
|
|
|
|
264
|
0
|
0
|
0
|
|
|
|
return PCI_EAT_NONE |
|
265
|
|
|
|
|
|
|
if !exists $self->{listen_for_input}{ $type } |
|
266
|
|
|
|
|
|
|
and !$self->{send_events}; |
|
267
|
|
|
|
|
|
|
|
|
268
|
0
|
0
|
|
|
|
|
foreach my $ban_re ( @{ $self->{banned} || [] } ) { |
|
|
0
|
|
|
|
|
|
|
|
269
|
0
|
0
|
|
|
|
|
return PCI_EAT_NONE |
|
270
|
|
|
|
|
|
|
if $who =~ /$ban_re/; |
|
271
|
|
|
|
|
|
|
} |
|
272
|
|
|
|
|
|
|
|
|
273
|
0
|
|
|
|
|
|
my $my_nick = $irc->nick_name(); |
|
274
|
0
|
|
|
|
|
|
my $what; |
|
275
|
0
|
0
|
|
|
|
|
if ( $type eq 'public' ) { |
|
276
|
0
|
|
|
|
|
|
($what) = $message =~ m/^\s*\Q$my_nick\E[\:\,\;\.]?\s*(.*)$/i; |
|
277
|
|
|
|
|
|
|
} |
|
278
|
|
|
|
|
|
|
else { |
|
279
|
0
|
|
|
|
|
|
$what = $message; |
|
280
|
|
|
|
|
|
|
} |
|
281
|
|
|
|
|
|
|
|
|
282
|
0
|
0
|
|
|
|
|
return PCI_EAT_NONE |
|
283
|
|
|
|
|
|
|
unless defined $what; |
|
284
|
|
|
|
|
|
|
|
|
285
|
0
|
0
|
|
|
|
|
warn "Got PUBLIC input: [ who => $who, channel => $channel, " |
|
286
|
|
|
|
|
|
|
. "what => $what ]" |
|
287
|
|
|
|
|
|
|
if $self->{debug}; |
|
288
|
|
|
|
|
|
|
|
|
289
|
0
|
|
|
|
|
|
my $response; |
|
290
|
|
|
|
|
|
|
|
|
291
|
0
|
|
|
|
|
|
eval { $response = $self->_parse_commands( $what ); }; |
|
|
0
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
# if $@ => did not match trigger, |
|
293
|
|
|
|
|
|
|
# if undef $response => no data available for this request |
|
294
|
0
|
0
|
|
|
|
|
if ( $@ ) { |
|
295
|
0
|
0
|
0
|
|
|
|
$response = $self->_parse_help( $what ) |
|
296
|
|
|
|
|
|
|
if $self->{help} |
|
297
|
|
|
|
|
|
|
and exists $self->{listen_for_help}{ $type }; |
|
298
|
|
|
|
|
|
|
|
|
299
|
0
|
0
|
0
|
|
|
|
warn "_parse_command( $what ) did not match anything" |
|
300
|
|
|
|
|
|
|
if $self->{debug} and not defined $response; |
|
301
|
|
|
|
|
|
|
|
|
302
|
0
|
0
|
|
|
|
|
return PCI_EAT_NONE |
|
303
|
|
|
|
|
|
|
unless defined $response; |
|
304
|
|
|
|
|
|
|
} |
|
305
|
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
|
|
307
|
0
|
|
|
|
|
|
my ( $nick ) = split /!/, $who; |
|
308
|
0
|
0
|
|
|
|
|
unless ( defined $response ) { |
|
309
|
0
|
0
|
|
|
|
|
if ( $self->{respond_no_result} ) { |
|
310
|
0
|
|
|
|
|
|
my $responses_ref = $self->{no_result_responses}; |
|
311
|
|
|
|
|
|
|
|
|
312
|
0
|
0
|
|
|
|
|
if ( $type eq 'public' ) { |
|
313
|
0
|
|
|
|
|
|
$poe_kernel->post( $irc => privmsg => $channel => |
|
314
|
|
|
|
|
|
|
"$nick, " . $responses_ref->[ rand @$responses_ref ] |
|
315
|
|
|
|
|
|
|
); |
|
316
|
|
|
|
|
|
|
} |
|
317
|
|
|
|
|
|
|
else { |
|
318
|
0
|
|
|
|
|
|
$poe_kernel->post( $irc => $type => $nick => |
|
319
|
|
|
|
|
|
|
$responses_ref->[ rand @$responses_ref ] |
|
320
|
|
|
|
|
|
|
); |
|
321
|
|
|
|
|
|
|
} |
|
322
|
|
|
|
|
|
|
} |
|
323
|
0
|
0
|
|
|
|
|
$self->{irc}->send_event( |
|
324
|
|
|
|
|
|
|
$self->{no_result_event} => { |
|
325
|
|
|
|
|
|
|
'time' => time(), |
|
326
|
|
|
|
|
|
|
who => $who, |
|
327
|
|
|
|
|
|
|
channel => $channel, |
|
328
|
|
|
|
|
|
|
what => $message, |
|
329
|
|
|
|
|
|
|
type => $type, |
|
330
|
|
|
|
|
|
|
} |
|
331
|
|
|
|
|
|
|
) if $self->{send_events}; |
|
332
|
|
|
|
|
|
|
|
|
333
|
0
|
|
|
|
|
|
return PCI_EAT_NONE; |
|
334
|
|
|
|
|
|
|
} |
|
335
|
|
|
|
|
|
|
|
|
336
|
0
|
0
|
|
|
|
|
warn "Got response `$response`" |
|
337
|
|
|
|
|
|
|
if $self->{debug}; |
|
338
|
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
my $max_length = $type eq 'public' |
|
340
|
|
|
|
|
|
|
? $self->{max_output_length_pub} |
|
341
|
0
|
0
|
|
|
|
|
: $self->{max_output_length }; |
|
342
|
|
|
|
|
|
|
|
|
343
|
0
|
0
|
|
|
|
|
if ( length $response > $max_length ) { |
|
344
|
0
|
|
|
|
|
|
$response = substr $response, 0, $max_length - 3; |
|
345
|
0
|
|
|
|
|
|
$response .= '...'; |
|
346
|
|
|
|
|
|
|
} |
|
347
|
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
# break long output into several lines to prefed "Excess Flood" drops |
|
349
|
0
|
|
|
|
|
|
my @responses; |
|
350
|
0
|
|
|
|
|
|
while ( length $response > $self->{output_line_length} ) { |
|
351
|
0
|
|
|
|
|
|
push @responses, substr $response, 0, $self->{output_line_length}; |
|
352
|
0
|
|
|
|
|
|
$response = substr $response, $self->{output_line_length}; |
|
353
|
|
|
|
|
|
|
} |
|
354
|
0
|
|
|
|
|
|
push @responses, $response; |
|
355
|
|
|
|
|
|
|
|
|
356
|
0
|
0
|
|
|
|
|
if ( exists $self->{ listen_for_input }{ $type } ) { |
|
357
|
0
|
0
|
|
|
|
|
if ( $type eq 'public' ) { |
|
358
|
|
|
|
|
|
|
$poe_kernel->post( $irc => privmsg => $channel => |
|
359
|
|
|
|
|
|
|
"$nick, $_" |
|
360
|
0
|
|
|
|
|
|
) for @responses; |
|
361
|
|
|
|
|
|
|
} |
|
362
|
|
|
|
|
|
|
else { |
|
363
|
|
|
|
|
|
|
$poe_kernel->post( $irc => $type => $nick => $_ ) |
|
364
|
0
|
|
|
|
|
|
for @responses; |
|
365
|
|
|
|
|
|
|
} |
|
366
|
|
|
|
|
|
|
} |
|
367
|
|
|
|
|
|
|
|
|
368
|
0
|
0
|
|
|
|
|
if ( $self->{send_events} ) { |
|
369
|
0
|
|
|
|
|
|
for ( @responses ) { |
|
370
|
0
|
|
|
|
|
|
$self->{irc}->send_event( |
|
371
|
|
|
|
|
|
|
$self->{response_event} => { |
|
372
|
|
|
|
|
|
|
'time' => time(), |
|
373
|
|
|
|
|
|
|
who => $who, |
|
374
|
|
|
|
|
|
|
channel => $channel, |
|
375
|
|
|
|
|
|
|
what => $message, |
|
376
|
|
|
|
|
|
|
type => $type, |
|
377
|
|
|
|
|
|
|
response => $_ |
|
378
|
|
|
|
|
|
|
} |
|
379
|
|
|
|
|
|
|
); |
|
380
|
|
|
|
|
|
|
} |
|
381
|
|
|
|
|
|
|
} |
|
382
|
|
|
|
|
|
|
|
|
383
|
0
|
0
|
|
|
|
|
return $self->{eat} ? PCI_EAT_ALL : PCI_EAT_NONE; |
|
384
|
|
|
|
|
|
|
} |
|
385
|
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
sub _parse_help { |
|
387
|
0
|
|
|
0
|
|
|
my ( $self, $what ) = @_; |
|
388
|
0
|
|
|
|
|
|
my $trigs = _make_default_help_triggers(); |
|
389
|
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
return |
|
391
|
0
|
0
|
0
|
|
|
|
unless defined $what and $what =~ s/$trigs->{help_re}//; |
|
392
|
|
|
|
|
|
|
|
|
393
|
0
|
0
|
|
|
|
|
return $self->_make_help_list |
|
394
|
|
|
|
|
|
|
unless length $what; |
|
395
|
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
|
|
397
|
0
|
|
|
|
|
|
my $help_data_ref = _make_help_data(); |
|
398
|
0
|
|
|
|
|
|
$what =~ s/^\s+|\s+$//g; |
|
399
|
0
|
|
|
|
|
|
$what = lc $what; |
|
400
|
0
|
0
|
|
|
|
|
if ( $what =~ s/^$trigs->{mod_cat}//i ) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
401
|
0
|
|
|
|
|
|
return $help_data_ref->{mod}{ $what }; |
|
402
|
|
|
|
|
|
|
} |
|
403
|
|
|
|
|
|
|
elsif ( $what =~ s/^$trigs->{auth_cat}//i ) { |
|
404
|
0
|
|
|
|
|
|
return $help_data_ref->{auth}{ $what }; |
|
405
|
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
} |
|
407
|
|
|
|
|
|
|
elsif ( $what =~ s/^$trigs->{dist_cat}//i ) { |
|
408
|
0
|
|
|
|
|
|
return $help_data_ref->{dist}{ $what }; |
|
409
|
|
|
|
|
|
|
} |
|
410
|
0
|
|
|
|
|
|
return; |
|
411
|
|
|
|
|
|
|
} |
|
412
|
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
sub _parse_commands { |
|
414
|
0
|
|
|
0
|
|
|
my ( $self, $what ) = @_; |
|
415
|
|
|
|
|
|
|
|
|
416
|
0
|
|
|
|
|
|
my $response; |
|
417
|
0
|
0
|
|
|
|
|
if ( $what =~ s/$self->{triggers}{mod_cat}// ) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
418
|
0
|
|
|
|
|
|
my $triggers = $self->{triggers}{mod}; |
|
419
|
|
|
|
|
|
|
|
|
420
|
0
|
0
|
|
|
|
|
if ( $what =~ s/$triggers->{distname}// ) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
421
|
0
|
|
|
|
|
|
$response = $self->_make_info( mods => $what => 'dist_name' ); |
|
422
|
|
|
|
|
|
|
} |
|
423
|
|
|
|
|
|
|
elsif ( $what =~ s/$triggers->{version}// ) { |
|
424
|
0
|
|
|
|
|
|
$response = $self->_make_info( mods => $what => 'mod_vers' ); |
|
425
|
|
|
|
|
|
|
} |
|
426
|
|
|
|
|
|
|
elsif ( $what =~ s/$triggers->{desc}// ) { |
|
427
|
0
|
|
|
|
|
|
$response = $self->_make_info( mods => $what => 'mod_abs' ); |
|
428
|
|
|
|
|
|
|
} |
|
429
|
|
|
|
|
|
|
elsif ( $what =~ s/$triggers->{chapter}// ) { |
|
430
|
0
|
|
|
|
|
|
$response = $self->_make_info( mods => $what => 'chapterid' ); |
|
431
|
|
|
|
|
|
|
} |
|
432
|
|
|
|
|
|
|
elsif ( $what =~ s/$triggers->{dslip}// ) { |
|
433
|
0
|
|
|
|
|
|
$response = $self->_make_info( mods => $what => 'dslip' ); |
|
434
|
|
|
|
|
|
|
} |
|
435
|
|
|
|
|
|
|
else { |
|
436
|
0
|
|
|
|
|
|
die; |
|
437
|
|
|
|
|
|
|
} |
|
438
|
|
|
|
|
|
|
} |
|
439
|
|
|
|
|
|
|
elsif ( $what =~ s/$self->{triggers}{auth_cat}// ) { |
|
440
|
0
|
|
|
|
|
|
my $triggers = $self->{triggers}{auth}; |
|
441
|
|
|
|
|
|
|
|
|
442
|
0
|
0
|
|
|
|
|
if ( $what =~ s/$triggers->{email}// ) { |
|
|
|
0
|
|
|
|
|
|
|
443
|
0
|
|
|
|
|
|
$response = $self->_make_info(auths => uc $what => 'email' ); |
|
444
|
|
|
|
|
|
|
} |
|
445
|
|
|
|
|
|
|
elsif ( $what =~ s/$triggers->{name}// ) { |
|
446
|
0
|
|
|
|
|
|
$response |
|
447
|
|
|
|
|
|
|
= $self->_make_info( auths => uc $what => 'fullname' ); |
|
448
|
|
|
|
|
|
|
} |
|
449
|
|
|
|
|
|
|
else { |
|
450
|
0
|
|
|
|
|
|
die |
|
451
|
|
|
|
|
|
|
} |
|
452
|
|
|
|
|
|
|
} |
|
453
|
|
|
|
|
|
|
elsif ( $what =~ s/$self->{triggers}{dist_cat}// ) { |
|
454
|
0
|
|
|
|
|
|
my $triggers = $self->{triggers}{dist}; |
|
455
|
0
|
0
|
|
|
|
|
if ( $what =~ s/$triggers->{version}// ) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
456
|
0
|
|
|
|
|
|
$response = $self->_make_info( dists => $what => 'dist_vers'); |
|
457
|
|
|
|
|
|
|
} |
|
458
|
|
|
|
|
|
|
elsif ( $what =~ s/$triggers->{file}// ) { |
|
459
|
0
|
|
|
|
|
|
$response = $self->_make_info( dists => $what => 'dist_file'); |
|
460
|
|
|
|
|
|
|
} |
|
461
|
|
|
|
|
|
|
elsif ( $what =~ s/$triggers->{auth}// ) { |
|
462
|
0
|
|
|
|
|
|
$response = $self->_make_info( dists => $what => 'cpanid' ); |
|
463
|
|
|
|
|
|
|
} |
|
464
|
|
|
|
|
|
|
elsif ( $what =~ s/$triggers->{desc}// ) { |
|
465
|
0
|
|
|
|
|
|
$response = $self->_make_info( dists => $what => 'dist_abs' ); |
|
466
|
|
|
|
|
|
|
} |
|
467
|
|
|
|
|
|
|
elsif ( $what =~ s/$triggers->{mods}// ) { |
|
468
|
0
|
|
|
|
|
|
$response = $self->_make_info( dists => $what => 'modules' ); |
|
469
|
|
|
|
|
|
|
} |
|
470
|
|
|
|
|
|
|
elsif ( $what =~ s/$triggers->{chapter}// ) { |
|
471
|
0
|
|
|
|
|
|
$response = $self->_make_info( dists => $what => 'chapterid' ); |
|
472
|
|
|
|
|
|
|
} |
|
473
|
|
|
|
|
|
|
else { |
|
474
|
0
|
|
|
|
|
|
die; |
|
475
|
|
|
|
|
|
|
} |
|
476
|
|
|
|
|
|
|
} |
|
477
|
|
|
|
|
|
|
else { |
|
478
|
0
|
|
|
|
|
|
die; |
|
479
|
|
|
|
|
|
|
} |
|
480
|
0
|
|
|
|
|
|
return $response; |
|
481
|
|
|
|
|
|
|
} |
|
482
|
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
sub _make_help_list { |
|
484
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
485
|
|
|
|
|
|
|
|
|
486
|
0
|
|
|
|
|
|
my $help_data_ref = _make_default_help_triggers(); |
|
487
|
0
|
|
|
|
|
|
my @help_list; |
|
488
|
0
|
|
|
|
|
|
foreach my $category (qw( dist mod auth )) { |
|
489
|
0
|
|
|
|
|
|
my $cat_prefix = $help_data_ref->{ $category . '_cat' }; |
|
490
|
0
|
|
|
|
|
|
push @help_list, join q|, |, |
|
491
|
0
|
|
|
|
|
|
map { $cat_prefix . $_ } |
|
492
|
0
|
|
|
|
|
|
sort keys %{ $help_data_ref->{ $category } }; |
|
493
|
|
|
|
|
|
|
} |
|
494
|
0
|
|
|
|
|
|
return join q|, |, @help_list; |
|
495
|
|
|
|
|
|
|
} |
|
496
|
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
sub _make_info { |
|
498
|
0
|
|
|
0
|
|
|
my ( $self, $category, $item, $section ) = @_; |
|
499
|
|
|
|
|
|
|
return |
|
500
|
0
|
0
|
|
|
|
|
unless defined $section; |
|
501
|
|
|
|
|
|
|
|
|
502
|
0
|
|
|
|
|
|
$item =~ s/^\s+|\s+$//g; |
|
503
|
|
|
|
|
|
|
return |
|
504
|
0
|
0
|
|
|
|
|
unless length $item; |
|
505
|
|
|
|
|
|
|
|
|
506
|
0
|
0
|
|
|
|
|
unless ( exists $self->{_data}{ $category }{ $item } ) { |
|
507
|
0
|
0
|
|
|
|
|
warn "Did not find {_data}{ $category }{ $item }" |
|
508
|
|
|
|
|
|
|
if $self->{debug}; |
|
509
|
|
|
|
|
|
|
|
|
510
|
0
|
|
|
|
|
|
return; |
|
511
|
|
|
|
|
|
|
} |
|
512
|
|
|
|
|
|
|
|
|
513
|
0
|
|
|
|
|
|
my $data = $self->{_data}{ $category }{ $item }; |
|
514
|
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
|
|
516
|
0
|
0
|
|
|
|
|
if ( exists $data->{ $section } ) { |
|
517
|
0
|
0
|
|
|
|
|
if ( $category eq 'dists' ) { |
|
518
|
0
|
0
|
|
|
|
|
if ( $section eq 'modules' ) { |
|
|
|
0
|
|
|
|
|
|
|
519
|
0
|
|
|
|
|
|
return $self->_prepare_dist_modules( $data->{ $section } ); |
|
520
|
|
|
|
|
|
|
} |
|
521
|
|
|
|
|
|
|
elsif ( $section eq 'chapterid' ) { |
|
522
|
0
|
|
|
|
|
|
return $self->_prepare_dist_chapterid($data->{ $section }); |
|
523
|
|
|
|
|
|
|
} |
|
524
|
|
|
|
|
|
|
} |
|
525
|
0
|
|
|
|
|
|
return $data->{ $section }; |
|
526
|
|
|
|
|
|
|
} |
|
527
|
|
|
|
|
|
|
else { |
|
528
|
0
|
|
|
|
|
|
return; |
|
529
|
|
|
|
|
|
|
} |
|
530
|
|
|
|
|
|
|
} |
|
531
|
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
sub _prepare_dist_modules { |
|
533
|
0
|
|
|
0
|
|
|
my ( $self, $modules_ref ) = @_; |
|
534
|
|
|
|
|
|
|
return |
|
535
|
0
|
0
|
|
|
|
|
unless ref $modules_ref eq 'HASH'; |
|
536
|
|
|
|
|
|
|
|
|
537
|
0
|
|
|
|
|
|
my @modules = keys %$modules_ref; |
|
538
|
|
|
|
|
|
|
|
|
539
|
0
|
0
|
|
|
|
|
if ( @modules > $self->{max_modules_limit} ) { |
|
540
|
0
|
|
|
|
|
|
return "Uses " . @modules . " modules..."; |
|
541
|
|
|
|
|
|
|
} |
|
542
|
|
|
|
|
|
|
else { |
|
543
|
0
|
|
|
|
|
|
my $mods = join ' | ', @modules; |
|
544
|
0
|
0
|
|
|
|
|
if ( length $mods > $self->{max_modules_length} ) { |
|
545
|
0
|
|
|
|
|
|
$mods = "(total: " . @modules . " ) $mods"; |
|
546
|
0
|
|
|
|
|
|
$mods = substr $mods, 0, $self->{max_modules_length} - 3; |
|
547
|
0
|
|
|
|
|
|
$mods .= '...'; |
|
548
|
|
|
|
|
|
|
} |
|
549
|
0
|
|
|
|
|
|
return $mods; |
|
550
|
|
|
|
|
|
|
} |
|
551
|
|
|
|
|
|
|
} |
|
552
|
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
sub _make_default_help_triggers { |
|
554
|
|
|
|
|
|
|
return { |
|
555
|
0
|
|
|
0
|
|
|
help_re => qr/^help\s*/i, |
|
556
|
|
|
|
|
|
|
mod_cat => 'mod_', |
|
557
|
|
|
|
|
|
|
mod => { |
|
558
|
|
|
|
|
|
|
distname => 'distname', |
|
559
|
|
|
|
|
|
|
version => 'version', |
|
560
|
|
|
|
|
|
|
desc => 'desc', |
|
561
|
|
|
|
|
|
|
chapter => 'chapter', |
|
562
|
|
|
|
|
|
|
dslip => 'dslip', |
|
563
|
|
|
|
|
|
|
}, |
|
564
|
|
|
|
|
|
|
auth_cat => 'auth_', |
|
565
|
|
|
|
|
|
|
auth => { |
|
566
|
|
|
|
|
|
|
email => 'email', |
|
567
|
|
|
|
|
|
|
name => 'name', |
|
568
|
|
|
|
|
|
|
}, |
|
569
|
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
dist_cat => 'dist_', |
|
571
|
|
|
|
|
|
|
dist => { |
|
572
|
|
|
|
|
|
|
version => 'version', |
|
573
|
|
|
|
|
|
|
file => 'file', |
|
574
|
|
|
|
|
|
|
auth => 'auth', |
|
575
|
|
|
|
|
|
|
desc => 'desc', |
|
576
|
|
|
|
|
|
|
mods => 'mods', |
|
577
|
|
|
|
|
|
|
chapter => 'chapter', |
|
578
|
|
|
|
|
|
|
}, |
|
579
|
|
|
|
|
|
|
}; |
|
580
|
|
|
|
|
|
|
} |
|
581
|
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
sub _make_help_data { |
|
583
|
|
|
|
|
|
|
return { |
|
584
|
0
|
|
|
0
|
|
|
mod => { |
|
585
|
|
|
|
|
|
|
distname => q|Which distribution the module is in|, |
|
586
|
|
|
|
|
|
|
version => q|Module's version|, |
|
587
|
|
|
|
|
|
|
desc => q|Module's description|, |
|
588
|
|
|
|
|
|
|
chapter => q|Module's chapter|, |
|
589
|
|
|
|
|
|
|
dslip => q|Module's DSLIP code|, |
|
590
|
|
|
|
|
|
|
}, |
|
591
|
|
|
|
|
|
|
auth => { |
|
592
|
|
|
|
|
|
|
email => q|Author's e-mail address|, |
|
593
|
|
|
|
|
|
|
name => q|Author's full name|, |
|
594
|
|
|
|
|
|
|
}, |
|
595
|
|
|
|
|
|
|
dist => { |
|
596
|
|
|
|
|
|
|
version => q|Distribution's version|, |
|
597
|
|
|
|
|
|
|
file => q|Distribution's CPAN filename|, |
|
598
|
|
|
|
|
|
|
auth => q|Distribution's author|, |
|
599
|
|
|
|
|
|
|
desc => q|Distribution's description|, |
|
600
|
|
|
|
|
|
|
mods => q|List modules included in the distribution|, |
|
601
|
|
|
|
|
|
|
chapter => q|Chapter and subchapter of the distribution|, |
|
602
|
|
|
|
|
|
|
}, |
|
603
|
|
|
|
|
|
|
}; |
|
604
|
|
|
|
|
|
|
} |
|
605
|
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
sub _make_default_triggers { |
|
607
|
|
|
|
|
|
|
return { |
|
608
|
0
|
|
|
0
|
|
|
mod_cat => qr/ ^ mod_ /xi, |
|
609
|
|
|
|
|
|
|
mod => { |
|
610
|
|
|
|
|
|
|
distname => qr/ ^ distname \s+ /xi, |
|
611
|
|
|
|
|
|
|
version => qr/ ^ version \s+ /xi, |
|
612
|
|
|
|
|
|
|
desc => qr/ ^ desc \s+ /xi, |
|
613
|
|
|
|
|
|
|
chapter => qr/ ^ chapter \s+ /xi, |
|
614
|
|
|
|
|
|
|
dslip => qr/ ^ dslip \s+ /xi, |
|
615
|
|
|
|
|
|
|
}, |
|
616
|
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
auth_cat => qr/ ^auth_ /xi, |
|
618
|
|
|
|
|
|
|
auth => { |
|
619
|
|
|
|
|
|
|
email => qr/ ^ email \s+ /xi, |
|
620
|
|
|
|
|
|
|
name => qr/ ^ name \s+ /xi, |
|
621
|
|
|
|
|
|
|
}, |
|
622
|
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
dist_cat => qr/ ^ dist_ /xi, |
|
624
|
|
|
|
|
|
|
dist => { |
|
625
|
|
|
|
|
|
|
version => qr/ ^ version \s+ /xi, |
|
626
|
|
|
|
|
|
|
file => qr/ ^ file \s+ /xi, |
|
627
|
|
|
|
|
|
|
auth => qr/ ^ auth \s+ /xi, |
|
628
|
|
|
|
|
|
|
desc => qr/ ^ desc \s+ /xi, |
|
629
|
|
|
|
|
|
|
mods => qr/ ^ mods \s+ /xi, |
|
630
|
|
|
|
|
|
|
chapter => qr/ ^ chapter \s+ /xi, |
|
631
|
|
|
|
|
|
|
}, |
|
632
|
|
|
|
|
|
|
}; |
|
633
|
|
|
|
|
|
|
} |
|
634
|
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
1; |
|
636
|
|
|
|
|
|
|
__END__ |