line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# ABSTRACT: Yet Another IRC Bot |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
use 5.014; |
4
|
13
|
|
|
13
|
|
401898
|
use exact; |
|
13
|
|
|
|
|
74
|
|
5
|
13
|
|
|
13
|
|
733
|
|
|
13
|
|
|
|
|
48044
|
|
|
13
|
|
|
|
|
62
|
|
6
|
|
|
|
|
|
|
use Daemon::Device; |
7
|
13
|
|
|
13
|
|
14340
|
use Date::Format 'time2str'; |
|
13
|
|
|
|
|
112329
|
|
|
13
|
|
|
|
|
416
|
|
8
|
13
|
|
|
13
|
|
5048
|
use Encode 'decode'; |
|
13
|
|
|
|
|
72030
|
|
|
13
|
|
|
|
|
853
|
|
9
|
13
|
|
|
13
|
|
89
|
use Encode::Detect::Detector 'detect'; |
|
13
|
|
|
|
|
22
|
|
|
13
|
|
|
|
|
504
|
|
10
|
13
|
|
|
13
|
|
4943
|
use IO::Socket::IP -register; |
|
13
|
|
|
|
|
24196
|
|
|
13
|
|
|
|
|
767
|
|
11
|
13
|
|
|
13
|
|
6268
|
use IO::Socket::SSL; |
|
13
|
|
|
|
|
215438
|
|
|
13
|
|
|
|
|
85
|
|
12
|
13
|
|
|
13
|
|
13747
|
use Time::Crontab; |
|
13
|
|
|
|
|
467007
|
|
|
13
|
|
|
|
|
101
|
|
13
|
13
|
|
|
13
|
|
6840
|
|
|
13
|
|
|
|
|
172265
|
|
|
13
|
|
|
|
|
49965
|
|
14
|
|
|
|
|
|
|
our $VERSION = '1.39'; # VERSION |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
my $class = shift; |
17
|
|
|
|
|
|
|
my $self = bless( {@_}, $class ); |
18
|
17
|
|
|
17
|
1
|
21840
|
|
19
|
17
|
|
|
|
|
55
|
croak('Odd number of elements passed to new()') if ( @_ % 2 ); |
20
|
|
|
|
|
|
|
croak('connect/server not provided to new()') |
21
|
17
|
50
|
|
|
|
70
|
unless ( ref $self->{connect} eq 'HASH' and $self->{connect}{server} ); |
22
|
|
|
|
|
|
|
|
23
|
17
|
100
|
66
|
|
|
307
|
$self->{spawn} ||= 2; |
24
|
|
|
|
|
|
|
$self->{encoding} //= 'UTF-8'; |
25
|
16
|
|
100
|
|
|
70
|
|
26
|
16
|
|
50
|
|
|
74
|
$self->{connect}{nick} //= 'bot'; |
27
|
|
|
|
|
|
|
$self->{connect}{name} //= 'Yet Another IRC Bot'; |
28
|
16
|
|
100
|
|
|
103
|
$self->{connect}{port} ||= 6667; |
29
|
16
|
|
100
|
|
|
76
|
|
30
|
16
|
|
100
|
|
|
67
|
$self->{disconnect} //= sub {}; |
31
|
|
|
|
|
|
|
|
32
|
16
|
|
50
|
0
|
|
107
|
$self->{daemon} //= {}; |
33
|
|
|
|
|
|
|
$self->{daemon}{name} //= $self->{connect}{nick}; |
34
|
16
|
|
100
|
|
|
78
|
$self->{daemon}{pid_file} //= $self->{daemon}{name} . '.pid'; |
35
|
16
|
|
66
|
|
|
71
|
|
36
|
16
|
|
66
|
|
|
90
|
$self->{nick} = $self->{connect}{nick}; |
37
|
|
|
|
|
|
|
|
38
|
16
|
|
|
|
|
31
|
$self->{hooks} = []; |
39
|
|
|
|
|
|
|
$self->{ticks} = []; |
40
|
16
|
|
|
|
|
35
|
$self->{helps} = {}; |
41
|
16
|
|
|
|
|
39
|
$self->{loaded} = {}; |
42
|
16
|
|
|
|
|
29
|
$self->{numerics} = []; |
43
|
16
|
|
|
|
|
34
|
|
44
|
16
|
|
|
|
|
29
|
$self->{send_user_nick} ||= 'on_parent'; |
45
|
|
|
|
|
|
|
croak('"send_user_nick" optional value set to invalid value') if ( |
46
|
16
|
|
50
|
|
|
74
|
$self->{send_user_nick} ne 'on_connect' and |
47
|
|
|
|
|
|
|
$self->{send_user_nick} ne 'on_parent' and |
48
|
|
|
|
|
|
|
$self->{send_user_nick} ne 'on_reply' |
49
|
|
|
|
|
|
|
); |
50
|
16
|
50
|
33
|
|
|
88
|
|
|
|
|
33
|
|
|
|
|
51
|
|
|
|
|
|
|
$self->load( |
52
|
|
|
|
|
|
|
( ref $self->{plugins} eq 'ARRAY' ) ? @{ $self->{plugins} } : $self->{plugins} |
53
|
|
|
|
|
|
|
) if ( $self->{plugins} ); |
54
|
2
|
|
|
|
|
7
|
|
55
|
16
|
50
|
|
|
|
53
|
return $self; |
|
|
100
|
|
|
|
|
|
56
|
|
|
|
|
|
|
} |
57
|
15
|
|
|
|
|
59
|
|
58
|
|
|
|
|
|
|
my $self = shift; |
59
|
|
|
|
|
|
|
my $commands = \@_; |
60
|
|
|
|
|
|
|
|
61
|
1
|
|
|
1
|
1
|
636
|
$self->{socket} = ( ( $self->{connect}{ssl} ) ? 'IO::Socket::SSL' : 'IO::Socket::IP' )->new( |
62
|
1
|
|
|
|
|
2
|
PeerAddr => $self->{connect}{server}, |
63
|
|
|
|
|
|
|
PeerPort => $self->{connect}{port}, |
64
|
|
|
|
|
|
|
Proto => 'tcp', |
65
|
|
|
|
|
|
|
Family => ( $self->{connect}{ipv6} ? AF_INET6 : AF_INET ), |
66
|
|
|
|
|
|
|
Type => SOCK_STREAM, |
67
|
|
|
|
|
|
|
SSL_verify_mode => SSL_VERIFY_NONE, |
68
|
1
|
50
|
|
|
|
7
|
) or die $!; |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
if ( $self->{encoding} ) { |
71
|
|
|
|
|
|
|
try { |
72
|
|
|
|
|
|
|
binmode( $self->{socket}, "encoding($self->{encoding})" ); |
73
|
1
|
50
|
|
|
|
10
|
} |
74
|
|
|
|
|
|
|
catch {}; |
75
|
|
|
|
|
|
|
} |
76
|
|
|
|
|
|
|
|
77
|
1
|
|
|
|
|
2
|
if ( $self->{send_user_nick} eq 'on_connect' ) { |
78
|
|
|
|
|
|
|
$self->{socket}->print("USER $self->{nick} 0 * :$self->{connect}{name}\r\n"); |
79
|
|
|
|
|
|
|
$self->{socket}->print("NICK $self->{nick}\r\n"); |
80
|
1
|
50
|
|
|
|
3
|
} |
81
|
0
|
|
|
|
|
0
|
|
82
|
0
|
|
|
|
|
0
|
try { |
83
|
|
|
|
|
|
|
$self->{device} = Daemon::Device->new( |
84
|
|
|
|
|
|
|
parent => \&_parent, |
85
|
|
|
|
|
|
|
child => \&_child, |
86
|
|
|
|
|
|
|
on_message => \&_on_message, |
87
|
|
|
|
|
|
|
spawn => $self->{spawn}, |
88
|
|
|
|
|
|
|
daemon => $self->{daemon}, |
89
|
|
|
|
|
|
|
data => { |
90
|
|
|
|
|
|
|
self => $self, |
91
|
|
|
|
|
|
|
commands => $commands, |
92
|
|
|
|
|
|
|
passwd => $self->{passwd}, |
93
|
|
|
|
|
|
|
}, |
94
|
|
|
|
|
|
|
); |
95
|
|
|
|
|
|
|
} |
96
|
|
|
|
|
|
|
catch { |
97
|
|
|
|
|
|
|
my $e = $_ || $@; |
98
|
|
|
|
|
|
|
croak("Daemon device instantiation failure: $e"); |
99
|
|
|
|
|
|
|
}; |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
$self->{device}->run; |
102
|
1
|
|
|
|
|
2
|
} |
103
|
|
|
|
|
|
|
|
104
|
1
|
|
|
|
|
7
|
my ( $self, $msg, $err ) = @_; |
105
|
|
|
|
|
|
|
chomp($msg); |
106
|
|
|
|
|
|
|
$msg = '[' . time2str( '%d/%b/%Y:%H:%M:%S %z', time() ) . '] ' . $msg . "\n"; |
107
|
|
|
|
|
|
|
|
108
|
4
|
|
|
4
|
1
|
6
|
if ($err) { |
109
|
4
|
|
|
|
|
5
|
die $msg if ( $err eq 'die' ); |
110
|
4
|
|
|
|
|
10
|
warn $msg; |
111
|
|
|
|
|
|
|
} |
112
|
4
|
50
|
|
|
|
666
|
else { |
113
|
0
|
0
|
|
|
|
0
|
print $msg; |
114
|
0
|
|
|
|
|
0
|
} |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
return; |
117
|
4
|
|
|
|
|
103
|
} |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
my ($device) = @_; |
120
|
4
|
|
|
|
|
17
|
my $self = $device->data('self'); |
121
|
|
|
|
|
|
|
my $session = { start => time }; |
122
|
|
|
|
|
|
|
my $delegate = sub { |
123
|
|
|
|
|
|
|
my ($random_child) = |
124
|
0
|
|
|
0
|
|
0
|
map { $_->[0] } |
125
|
0
|
|
|
|
|
0
|
sort { $a->[1] <=> $b->[1] } |
126
|
0
|
|
|
|
|
0
|
map { [ $_, rand() ] } |
127
|
|
|
|
|
|
|
@{ $device->children }; |
128
|
|
|
|
|
|
|
|
129
|
0
|
|
|
|
|
0
|
$device->message( $random_child, @_ ); |
130
|
0
|
|
|
|
|
0
|
}; |
131
|
0
|
|
|
|
|
0
|
my $broadcast = sub { |
132
|
0
|
|
|
0
|
|
0
|
my @messages = @_; |
|
0
|
|
|
|
|
0
|
|
133
|
|
|
|
|
|
|
$device->message( $_, @messages ) for ( @{ $device->children } ); |
134
|
0
|
|
|
|
|
0
|
}; |
135
|
0
|
|
|
|
|
0
|
|
136
|
|
|
|
|
|
|
local $SIG{ALRM} = sub { |
137
|
0
|
|
|
0
|
|
0
|
alarm 1; |
138
|
0
|
|
|
|
|
0
|
my $time = time; |
|
0
|
|
|
|
|
0
|
|
139
|
0
|
|
|
|
|
0
|
|
140
|
|
|
|
|
|
|
for ( |
141
|
|
|
|
|
|
|
grep { |
142
|
0
|
|
|
0
|
|
0
|
ref $_->{timing} and ( $time % 60 == 0 ) and $_->{timing}->match($time) or |
143
|
0
|
|
|
|
|
0
|
not ref $_->{timing} and ( ( $time - $session->{start} ) % $_->{timing} == 0 ) |
144
|
|
|
|
|
|
|
} @{ $self->{ticks} } |
145
|
0
|
|
|
|
|
0
|
) { |
146
|
|
|
|
|
|
|
try { |
147
|
|
|
|
|
|
|
$_->{code}->($self); |
148
|
0
|
0
|
0
|
|
|
0
|
} |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
149
|
0
|
|
|
|
|
0
|
catch { |
150
|
|
|
|
|
|
|
my $e = $_ || $@; |
151
|
|
|
|
|
|
|
warn "Tick execution failure: $e\n"; |
152
|
|
|
|
|
|
|
}; |
153
|
|
|
|
|
|
|
} |
154
|
0
|
|
|
|
|
0
|
}; |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
local $SIG{__WARN__} = sub { note( undef, $_[0], 'warn' ) }; |
157
|
|
|
|
|
|
|
local $SIG{__DIE__} = sub { note( undef, $_[0], 'die' ) }; |
158
|
|
|
|
|
|
|
|
159
|
0
|
|
|
|
|
0
|
srand(); |
160
|
|
|
|
|
|
|
my @lines; |
161
|
0
|
|
|
0
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
162
|
0
|
|
|
0
|
|
0
|
try { |
|
0
|
|
|
|
|
0
|
|
163
|
|
|
|
|
|
|
if ( $self->{send_user_nick} eq 'on_parent' ) { |
164
|
0
|
|
|
|
|
0
|
$self->say("USER $self->{nick} 0 * :$self->{connect}{name}"); |
165
|
0
|
|
|
|
|
0
|
$self->say("NICK $self->{nick}"); |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
while ( my $line = $self->{socket}->getline ) { |
169
|
|
|
|
|
|
|
$line =~ s/\003\d{2}(?:,\d{2})?//g; # remove IRC color codes |
170
|
|
|
|
|
|
|
$line =~ tr/\000-\037//d; # remove all control characters |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
$self->note($line); |
173
|
|
|
|
|
|
|
chomp($line); |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
if ( not $session->{established} ) { |
176
|
|
|
|
|
|
|
if ( not $session->{user} ) { |
177
|
|
|
|
|
|
|
if ( $self->{send_user_nick} eq 'on_reply' ) { |
178
|
|
|
|
|
|
|
$self->say("USER $self->{nick} 0 * :$self->{connect}{name}"); |
179
|
|
|
|
|
|
|
$self->say("NICK $self->{nick}"); |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
elsif ( $self->{send_user_nick} eq 'on_connect' ) { |
182
|
|
|
|
|
|
|
$self->note("<<< USER $self->{nick} 0 * :$self->{connect}{name}\r\n"); |
183
|
|
|
|
|
|
|
$self->note("<<< NICK $self->{nick}\r\n"); |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
$session->{user} = 1; |
186
|
|
|
|
|
|
|
} |
187
|
|
|
|
|
|
|
if ( $line =~ /^ERROR.+onnect\w+ too fast/ ) { |
188
|
|
|
|
|
|
|
warn "$line\n"; |
189
|
|
|
|
|
|
|
warn "Sleeping 20 and retrying...\n"; |
190
|
|
|
|
|
|
|
sleep 20; |
191
|
|
|
|
|
|
|
$device->daemon->do_restart; |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
elsif ( $line =~ /^ERROR\s/ ) { |
194
|
|
|
|
|
|
|
warn "$line\n"; |
195
|
|
|
|
|
|
|
$device->daemon->do_stop; |
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
elsif ( $line =~ /^:\S+\s433\s/ ) { |
198
|
|
|
|
|
|
|
$self->nick( $self->{nick} . '_' ); |
199
|
|
|
|
|
|
|
} |
200
|
|
|
|
|
|
|
elsif ( $line =~ /^:\S+\s001\s/ ) { |
201
|
|
|
|
|
|
|
$self->say($_) for ( map { |
202
|
|
|
|
|
|
|
my $command = $_; |
203
|
|
|
|
|
|
|
$command =~ s|^/msg |PRIVMSG |; |
204
|
|
|
|
|
|
|
$command =~ s|^/(\w+)|uc($1)|e; |
205
|
|
|
|
|
|
|
$command; |
206
|
|
|
|
|
|
|
} @{ $device->data('commands') } ); |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
$self->join; |
209
|
|
|
|
|
|
|
$session->{established} = 1; |
210
|
|
|
|
|
|
|
alarm 1 if ( @{ $self->{ticks} } ); |
211
|
|
|
|
|
|
|
} |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
shift @lines while ( @lines > 10 ); |
215
|
|
|
|
|
|
|
my $now = time(); |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
unless ( grep { $_->{line} eq $line and $_->{time} + 1 > $now } @lines ) { |
218
|
|
|
|
|
|
|
unless ( $line =~ /^:\S+\s\d{3}\s/ ) { |
219
|
|
|
|
|
|
|
$delegate->($line); |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
else { |
222
|
|
|
|
|
|
|
$broadcast->($line); |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
else { |
226
|
|
|
|
|
|
|
$self->note("### Skipped repeated line: $line"); |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
push @lines, { line => $line, time => $now }; |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
catch { |
233
|
|
|
|
|
|
|
my $e = $_ || $@; |
234
|
|
|
|
|
|
|
warn "Daemon parent loop failure: $e\n"; |
235
|
|
|
|
|
|
|
}; |
236
|
|
|
|
|
|
|
|
237
|
0
|
|
|
|
|
0
|
kill( 'KILL', $_ ) for ( @{ $device->children } ); |
238
|
|
|
|
|
|
|
$self->{disconnect}->($self) if ( ref $self->{disconnect} eq 'CODE' ); |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
local $SIG{__WARN__} = sub { note( undef, $_[0], 'warn' ) }; |
242
|
0
|
|
|
|
|
0
|
local $SIG{__DIE__} = sub { note( undef, $_[0], 'die' ) }; |
|
0
|
|
|
|
|
0
|
|
243
|
0
|
0
|
|
|
|
0
|
|
244
|
|
|
|
|
|
|
srand(); |
245
|
|
|
|
|
|
|
sleep 1 while (1); |
246
|
|
|
|
|
|
|
} |
247
|
0
|
|
|
0
|
|
0
|
|
|
0
|
|
|
0
|
|
0
|
|
248
|
0
|
|
|
0
|
|
0
|
my $device = shift; |
|
0
|
|
|
|
|
0
|
|
249
|
|
|
|
|
|
|
my $self = $device->data('self'); |
250
|
0
|
|
|
|
|
0
|
my $passwd = $device->data('passwd'); |
251
|
0
|
|
|
|
|
0
|
|
252
|
|
|
|
|
|
|
for my $line (@_) { |
253
|
|
|
|
|
|
|
if ( $self->{encoding} ) { |
254
|
|
|
|
|
|
|
my $charset = detect($line); |
255
|
0
|
|
|
0
|
|
0
|
$line = decode( $charset => $line ) if ( $charset and $charset eq $self->{encoding} ); |
256
|
0
|
|
|
|
|
0
|
} |
257
|
0
|
|
|
|
|
0
|
|
258
|
|
|
|
|
|
|
push( @{ $self->{numerics} }, $line ) |
259
|
0
|
|
|
|
|
0
|
if ( $line =~ /^:\S+\s\d{3}\s/ and @{ $self->{numerics} } < 100 ); |
260
|
0
|
0
|
|
|
|
0
|
|
261
|
0
|
|
|
|
|
0
|
if ( $line =~ /^>>>\sNICK\s(.*)/ ) { |
262
|
0
|
0
|
0
|
|
|
0
|
$self->{nick} = $1; |
263
|
|
|
|
|
|
|
next; |
264
|
|
|
|
|
|
|
} |
265
|
0
|
|
|
|
|
0
|
elsif ( $line =~ /^:\S+\s433\s/ ) { |
266
|
0
|
0
|
0
|
|
|
0
|
$self->nick( $self->{nick} . '_' ); |
|
0
|
|
|
|
|
0
|
|
267
|
|
|
|
|
|
|
next; |
268
|
0
|
0
|
|
|
|
0
|
} |
|
|
0
|
|
|
|
|
|
269
|
0
|
|
|
|
|
0
|
|
270
|
0
|
|
|
|
|
0
|
$self->{in} = { map { $_ => '' } qw( line source nick user server command forum text ) }; |
271
|
|
|
|
|
|
|
$self->{in}{$_} = 0 for ( qw( private to_me ) ); |
272
|
|
|
|
|
|
|
$self->{in}{line} = $line; |
273
|
0
|
|
|
|
|
0
|
|
274
|
0
|
|
|
|
|
0
|
if ( $line =~ /^(ERROR)\s/ ) { |
275
|
|
|
|
|
|
|
warn $line . "\n"; |
276
|
|
|
|
|
|
|
} |
277
|
0
|
|
|
|
|
0
|
elsif ( $line =~ /^:(\S+?)!~?(\S+?)@(\S+?)\s(\S+)\s(\S+)\s:(.*)/ ) { |
|
0
|
|
|
|
|
0
|
|
278
|
0
|
|
|
|
|
0
|
@{ $self->{in} }{ qw( nick user server command forum text ) } = ( $1, $2, $3, $4, $5, $6 ); |
279
|
0
|
|
|
|
|
0
|
$self->{in}{full_text} = $self->{in}{text}; |
280
|
|
|
|
|
|
|
} |
281
|
0
|
0
|
|
|
|
0
|
elsif ( $line =~ /^:(\S+?)!~?(\S+?)@(\S+?)\s(\S+)\s:(.*)/ ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
282
|
0
|
|
|
|
|
0
|
@{ $self->{in} }{ qw( nick user server command text ) } = ( $1, $2, $3, $4, $5 ); |
283
|
|
|
|
|
|
|
( $self->{in}{forum} = $self->{in}{text} ) =~ s/^:// |
284
|
|
|
|
|
|
|
if ( $self->{in}{command} eq 'JOIN' or $self->{in}{command} eq 'PART' ); |
285
|
0
|
|
|
|
|
0
|
} |
|
0
|
|
|
|
|
0
|
|
286
|
0
|
|
|
|
|
0
|
elsif ( $line =~ /^:(\S+?)!~?(\S+?)@(\S+?)\s(\S+)\s(\S+)\s(.*)/ ) { |
287
|
|
|
|
|
|
|
@{ $self->{in} }{ qw( nick user server command forum text ) } = ( $1, $2, $3, $4, $5, $6 ); |
288
|
|
|
|
|
|
|
} |
289
|
0
|
|
|
|
|
0
|
elsif ( $line =~ /^:(\S+?)!~?(\S+?)@(\S+?)\s(\S+)\s(\S+)/ ) { |
|
0
|
|
|
|
|
0
|
|
290
|
|
|
|
|
|
|
@{ $self->{in} }{ qw( nick user server command forum ) } = ( $1, $2, $3, $4, $5, $6 ); |
291
|
0
|
0
|
0
|
|
|
0
|
} |
292
|
|
|
|
|
|
|
elsif ( $line =~ /^(PING)\s(.+)/ ) { |
293
|
|
|
|
|
|
|
@{ $self->{in} }{ qw( command text ) } = ( $1, $2 ); |
294
|
0
|
|
|
|
|
0
|
$self->say( 'PONG ' . $self->{in}{text} ); |
|
0
|
|
|
|
|
0
|
|
295
|
|
|
|
|
|
|
next; |
296
|
|
|
|
|
|
|
} |
297
|
0
|
|
|
|
|
0
|
elsif ( $line =~ /^:(\S+)\s([A-Z]+|\d+)\s(\S+)\s(.*)/ ) { |
|
0
|
|
|
|
|
0
|
|
298
|
|
|
|
|
|
|
@{ $self->{in} }{ qw( source command forum text ) } = ( $1, $2, $3, $4 ); |
299
|
|
|
|
|
|
|
} |
300
|
0
|
|
|
|
|
0
|
else { |
|
0
|
|
|
|
|
0
|
|
301
|
0
|
|
|
|
|
0
|
warn 'Unparsed line (probably a bug in Bot::IRC; please report it): ', $line . "\n"; |
302
|
0
|
|
|
|
|
0
|
} |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
next unless ( $self->{in}{nick} ne $self->{nick} ); |
305
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
306
|
|
|
|
|
|
|
if ( $self->{in}{command} eq 'PRIVMSG' ) { |
307
|
|
|
|
|
|
|
$self->{in}{private} = 1 if ( $self->{in}{forum} and $self->{in}{forum} eq $self->{nick} ); |
308
|
0
|
|
|
|
|
0
|
$self->{in}{to_me} = 1 if ( |
309
|
|
|
|
|
|
|
$self->{in}{text} =~ s/^\s*\b$self->{nick}\b[\s\W]*//i or |
310
|
|
|
|
|
|
|
$self->{in}{text} =~ s/[\s\W]*\b$self->{nick}\b[\s\W]*$//i or |
311
|
0
|
0
|
|
|
|
0
|
$self->{in}{private} |
312
|
|
|
|
|
|
|
); |
313
|
0
|
0
|
|
|
|
0
|
} |
314
|
0
|
0
|
0
|
|
|
0
|
|
315
|
|
|
|
|
|
|
if ( $self->{in}{to_me} ) { |
316
|
|
|
|
|
|
|
if ( $self->{in}{text} =~ /^\s*help\W*$/i ) { |
317
|
|
|
|
|
|
|
$self->reply_to( |
318
|
|
|
|
|
|
|
'Ask me for help with "help topic" where the topic is one of the following: ' . |
319
|
0
|
0
|
0
|
|
|
0
|
$self->list( ', ', 'and', sort keys %{ $self->{helps} } ) . '.' |
|
|
|
0
|
|
|
|
|
320
|
|
|
|
|
|
|
); |
321
|
|
|
|
|
|
|
next; |
322
|
0
|
0
|
|
|
|
0
|
} |
323
|
0
|
0
|
|
|
|
0
|
elsif ( $self->{in}{text} =~ /^\s*help\s+(.+?)\W*$/i ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
324
|
|
|
|
|
|
|
$self->reply_to( |
325
|
|
|
|
|
|
|
( $self->{helps}{$1} || "Couldn't find the help topic: $1." ) |
326
|
0
|
|
|
|
|
0
|
); |
|
0
|
|
|
|
|
0
|
|
327
|
|
|
|
|
|
|
next; |
328
|
0
|
|
|
|
|
0
|
} |
329
|
|
|
|
|
|
|
elsif ( $self->{in}{text} =~ /Sorry. I don't understand./ ) { |
330
|
|
|
|
|
|
|
next; |
331
|
|
|
|
|
|
|
} |
332
|
0
|
|
0
|
|
|
0
|
} |
333
|
|
|
|
|
|
|
|
334
|
0
|
|
|
|
|
0
|
hook: for my $hook ( |
335
|
|
|
|
|
|
|
@{ $self->{hooks} }, |
336
|
|
|
|
|
|
|
|
337
|
0
|
|
|
|
|
0
|
{ |
338
|
|
|
|
|
|
|
when => { |
339
|
|
|
|
|
|
|
to_me => 1, |
340
|
|
|
|
|
|
|
text => qr/^\s*cmd\s+(?<passwd>\S+)\s+(?<cmd>.+)$/i, |
341
|
0
|
|
|
|
|
0
|
}, |
342
|
0
|
|
|
|
|
0
|
code => sub { |
343
|
|
|
|
|
|
|
my ( $bot, $in, $m ) = @_; |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
if ( $m->{passwd} and $passwd and $m->{passwd} eq $passwd ) { |
346
|
|
|
|
|
|
|
$bot->say($_) for ( |
347
|
|
|
|
|
|
|
map { |
348
|
|
|
|
|
|
|
my $command = $_; |
349
|
|
|
|
|
|
|
$command =~ s|^/msg |PRIVMSG |; |
350
|
0
|
|
|
0
|
|
0
|
$command =~ s|^/(\w+)|uc($1)|e; |
351
|
|
|
|
|
|
|
$command; |
352
|
0
|
0
|
0
|
|
|
0
|
} split( /\s*;\s*/, $m->{cmd} ) |
|
|
|
0
|
|
|
|
|
353
|
0
|
|
|
|
|
0
|
); |
354
|
|
|
|
|
|
|
} |
355
|
0
|
|
|
|
|
0
|
|
356
|
0
|
|
|
|
|
0
|
return 1; |
357
|
0
|
|
|
|
|
0
|
}, |
|
0
|
|
|
|
|
0
|
|
358
|
0
|
|
|
|
|
0
|
}, |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
{ |
361
|
|
|
|
|
|
|
when => { |
362
|
|
|
|
|
|
|
full_text => qr/^\s*$self->{nick}\s*[!\?]\W*$/i, |
363
|
0
|
|
|
|
|
0
|
}, |
364
|
|
|
|
|
|
|
code => sub { |
365
|
|
|
|
|
|
|
my ($bot) = @_; |
366
|
|
|
|
|
|
|
$bot->reply_to('Yes?'); |
367
|
|
|
|
|
|
|
}, |
368
|
|
|
|
|
|
|
}, |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
{ |
371
|
|
|
|
|
|
|
when => { |
372
|
0
|
|
|
0
|
|
0
|
to_me => 1, |
373
|
0
|
|
|
|
|
0
|
text => qr/^(?<word>hello|greetings|hi|good\s+\w+)\W*$/i, |
374
|
|
|
|
|
|
|
}, |
375
|
|
|
|
|
|
|
code => sub { |
376
|
|
|
|
|
|
|
my ( $bot, $in, $m ) = @_; |
377
|
|
|
|
|
|
|
$bot->reply_to( ucfirst( lc( $m->{word} ) ) . '.' ); |
378
|
|
|
|
|
|
|
}, |
379
|
|
|
|
|
|
|
}, |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
( map { |
382
|
|
|
|
|
|
|
{ |
383
|
0
|
|
|
0
|
|
0
|
when => $_, |
384
|
0
|
|
|
|
|
0
|
code => sub { |
385
|
|
|
|
|
|
|
my ($bot) = @_; |
386
|
|
|
|
|
|
|
$bot->reply_to(qq{Sorry. I don't understand. (Try "$self->{nick} help" for help.)}); |
387
|
|
|
|
|
|
|
}, |
388
|
|
|
|
|
|
|
}, |
389
|
|
|
|
|
|
|
} ( |
390
|
|
|
|
|
|
|
{ |
391
|
|
|
|
|
|
|
private => 0, |
392
|
0
|
|
|
0
|
|
0
|
full_text => qr/^\s*$self->{nick}\s*[,:\->~=]/i, |
393
|
0
|
|
|
|
|
0
|
}, |
394
|
|
|
|
|
|
|
{ |
395
|
|
|
|
|
|
|
private => 1, |
396
|
0
|
|
|
|
|
0
|
}, |
397
|
|
|
|
|
|
|
) ), |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
) { |
400
|
|
|
|
|
|
|
my $captured_matches = {}; |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
for my $type ( keys %{ $hook->{when} } ) { |
403
|
|
|
|
|
|
|
next hook unless ( |
404
|
|
|
|
|
|
|
ref( $hook->{when}{$type} ) eq 'Regexp' and |
405
|
|
|
|
|
|
|
$self->{in}{$type} and $self->{in}{$type} =~ $hook->{when}{$type} or |
406
|
|
|
|
|
|
|
ref( $hook->{when}{$type} ) eq 'CODE' and $hook->{when}{$type}->( |
407
|
0
|
|
|
|
|
0
|
$self, |
408
|
|
|
|
|
|
|
$self->{in}{$type}, |
409
|
0
|
|
|
|
|
0
|
{ %{ $self->{in} } }, |
|
0
|
|
|
|
|
0
|
|
410
|
|
|
|
|
|
|
) or |
411
|
|
|
|
|
|
|
( |
412
|
|
|
|
|
|
|
defined $self->{in}{$type} and defined $hook->{when}{$type} and |
413
|
|
|
|
|
|
|
$self->{in}{$type} eq $hook->{when}{$type} |
414
|
|
|
|
|
|
|
) |
415
|
|
|
|
|
|
|
); |
416
|
0
|
|
|
|
|
0
|
|
417
|
|
|
|
|
|
|
$captured_matches = { %$captured_matches, %+ } if ( keys %+ ); |
418
|
|
|
|
|
|
|
} |
419
|
|
|
|
|
|
|
|
420
|
0
|
0
|
0
|
|
|
0
|
my $rv; |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
421
|
|
|
|
|
|
|
try { |
422
|
|
|
|
|
|
|
$rv = $hook->{code}->( |
423
|
|
|
|
|
|
|
$self, |
424
|
13
|
0
|
|
13
|
|
4968
|
{ %{ $self->{in} } }, |
|
13
|
|
|
|
|
4154
|
|
|
13
|
|
|
|
|
16156
|
|
|
0
|
|
|
|
|
0
|
|
425
|
|
|
|
|
|
|
$captured_matches, |
426
|
|
|
|
|
|
|
); |
427
|
0
|
|
|
|
|
0
|
} |
428
|
|
|
|
|
|
|
catch { |
429
|
|
|
|
|
|
|
my $e = $_ || $@; |
430
|
|
|
|
|
|
|
warn "Plugin hook execution failure: $e\n"; |
431
|
|
|
|
|
|
|
}; |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
last if ($rv); |
434
|
|
|
|
|
|
|
} |
435
|
0
|
|
|
|
|
0
|
} |
436
|
|
|
|
|
|
|
} |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
my $self = shift; |
439
|
|
|
|
|
|
|
|
440
|
0
|
0
|
|
|
|
0
|
for my $plugin (@_) { |
441
|
|
|
|
|
|
|
unless ( ref $plugin ) { |
442
|
|
|
|
|
|
|
if ( $plugin =~ /^:core$/i ) { |
443
|
|
|
|
|
|
|
$self->load( |
444
|
|
|
|
|
|
|
'Ping', |
445
|
|
|
|
|
|
|
'Join', |
446
|
7
|
|
|
7
|
1
|
14
|
'Seen', |
447
|
|
|
|
|
|
|
'Greeting', |
448
|
7
|
|
|
|
|
16
|
'Infobot', |
449
|
7
|
50
|
|
|
|
35
|
'Functions', |
450
|
7
|
50
|
|
|
|
32
|
'Convert', |
451
|
0
|
|
|
|
|
0
|
'Karma', |
452
|
|
|
|
|
|
|
'Math', |
453
|
|
|
|
|
|
|
'History', |
454
|
|
|
|
|
|
|
); |
455
|
|
|
|
|
|
|
next; |
456
|
|
|
|
|
|
|
} |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
my $namespace; |
459
|
|
|
|
|
|
|
for ( |
460
|
|
|
|
|
|
|
$plugin, |
461
|
|
|
|
|
|
|
__PACKAGE__ . "::Y::$plugin", |
462
|
|
|
|
|
|
|
__PACKAGE__ . "::X::$plugin", |
463
|
0
|
|
|
|
|
0
|
__PACKAGE__ . "::$plugin", |
464
|
|
|
|
|
|
|
) { |
465
|
|
|
|
|
|
|
( my $path = $_ ) =~ s|::|/|g; |
466
|
7
|
|
|
|
|
10
|
|
467
|
7
|
|
|
|
|
39
|
eval "require $_"; |
468
|
|
|
|
|
|
|
unless ($@) { |
469
|
|
|
|
|
|
|
$namespace = $_; |
470
|
|
|
|
|
|
|
last; |
471
|
|
|
|
|
|
|
} |
472
|
|
|
|
|
|
|
else { |
473
|
22
|
|
|
|
|
69
|
croak("Plugin load failure: $@") unless ( $@ =~ /^Can't locate $path/ ); |
474
|
|
|
|
|
|
|
} |
475
|
22
|
|
|
|
|
1423
|
} |
476
|
22
|
100
|
|
|
|
1350
|
croak("Unable to find or properly load $plugin") unless ($namespace); |
477
|
6
|
|
|
|
|
14
|
|
478
|
6
|
|
|
|
|
12
|
next if ( $self->{loaded}{$namespace} ); |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
$namespace->import if ( $namespace->can('import') ); |
481
|
16
|
50
|
|
|
|
238
|
croak("$namespace does not implement init()") unless ( $namespace->can('init') ); |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
eval "${namespace}::init(\$self)"; |
484
|
7
|
100
|
|
|
|
136
|
die("Plugin init failure: $@\n") if ($@); |
485
|
|
|
|
|
|
|
|
486
|
6
|
50
|
|
|
|
20
|
$self->{loaded}{$namespace} = time; |
487
|
|
|
|
|
|
|
} |
488
|
6
|
50
|
|
|
|
48
|
else { |
489
|
6
|
50
|
|
|
|
40
|
$self->$_( @{ $plugin->{$_} } ) for ( qw( hooks ticks ) ); |
490
|
|
|
|
|
|
|
$self->$_( $plugin->{$_} ) for ( qw( helps subs ) ); |
491
|
6
|
|
|
|
|
350
|
} |
492
|
6
|
50
|
|
|
|
46
|
} |
493
|
|
|
|
|
|
|
|
494
|
6
|
|
|
|
|
33
|
return $self; |
495
|
|
|
|
|
|
|
} |
496
|
|
|
|
|
|
|
|
497
|
0
|
|
|
|
|
0
|
my $self = shift; |
|
0
|
|
|
|
|
0
|
|
498
|
0
|
|
|
|
|
0
|
delete $self->{loaded}{$_} for (@_); |
499
|
|
|
|
|
|
|
return $self->load(@_); |
500
|
|
|
|
|
|
|
} |
501
|
|
|
|
|
|
|
|
502
|
6
|
|
|
|
|
15
|
my ( $self, $when, $code, $attr ) = @_; |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
push( |
505
|
|
|
|
|
|
|
@{ $self->{hooks} }, |
506
|
1
|
|
|
1
|
1
|
205
|
{ |
507
|
1
|
|
|
|
|
4
|
when => $when, |
508
|
1
|
|
|
|
|
4
|
code => $code, |
509
|
|
|
|
|
|
|
attr => ( $attr // {} ), |
510
|
|
|
|
|
|
|
}, |
511
|
|
|
|
|
|
|
); |
512
|
3
|
|
|
3
|
1
|
195
|
|
513
|
|
|
|
|
|
|
$self->subs( %{ $attr->{subs} } ) if ( ref $attr->{subs} eq 'HASH' ); |
514
|
|
|
|
|
|
|
$self->helps( %{ $attr->{helps} } ) if ( ref $attr->{helps} eq 'HASH' ); |
515
|
3
|
|
100
|
|
|
4
|
|
|
3
|
|
|
|
|
14
|
|
516
|
|
|
|
|
|
|
return $self; |
517
|
|
|
|
|
|
|
} |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
my $self = shift; |
520
|
|
|
|
|
|
|
$self->hook(@$_) for (@_); |
521
|
|
|
|
|
|
|
return $self; |
522
|
|
|
|
|
|
|
} |
523
|
3
|
50
|
|
|
|
8
|
|
|
0
|
|
|
|
|
0
|
|
524
|
3
|
50
|
|
|
|
6
|
my ( $self, @input ) = @_; |
|
0
|
|
|
|
|
0
|
|
525
|
|
|
|
|
|
|
|
526
|
3
|
|
|
|
|
6
|
try { |
527
|
|
|
|
|
|
|
$self->{helps} = { %{ $self->{helps} }, @input }; |
528
|
|
|
|
|
|
|
} |
529
|
|
|
|
|
|
|
catch { |
530
|
1
|
|
|
1
|
1
|
184
|
$self->note('Plugin helps called but not properly implemented'); |
531
|
1
|
|
|
|
|
4
|
}; |
532
|
1
|
|
|
|
|
2
|
|
533
|
|
|
|
|
|
|
return $self; |
534
|
|
|
|
|
|
|
} |
535
|
|
|
|
|
|
|
|
536
|
10
|
|
|
10
|
1
|
211
|
my ( $self, $timing, $code ) = @_; |
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
push( @{ $self->{ticks} }, { |
539
|
|
|
|
|
|
|
timing => ( $timing =~ /^\d+$/ ) ? $timing : Time::Crontab->new($timing), |
540
|
|
|
|
|
|
|
code => $code, |
541
|
10
|
|
|
|
|
35
|
} ); |
542
|
|
|
|
|
|
|
return $self; |
543
|
|
|
|
|
|
|
} |
544
|
|
|
|
|
|
|
|
545
|
10
|
|
|
|
|
31
|
my $self = shift; |
546
|
|
|
|
|
|
|
$self->tick(@$_) for (@_); |
547
|
|
|
|
|
|
|
return $self; |
548
|
|
|
|
|
|
|
} |
549
|
3
|
|
|
3
|
1
|
179
|
|
550
|
|
|
|
|
|
|
my $self = shift; |
551
|
3
|
50
|
|
|
|
4
|
|
|
3
|
|
|
|
|
18
|
|
552
|
|
|
|
|
|
|
if ( @_ % 2 ) { |
553
|
|
|
|
|
|
|
$self->note('Plugin helps called but not properly implemented'); |
554
|
|
|
|
|
|
|
return $self; |
555
|
3
|
|
|
|
|
6
|
} |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
my $subs = {@_}; |
558
|
|
|
|
|
|
|
|
559
|
1
|
|
|
1
|
1
|
176
|
for my $name ( keys %$subs ) { |
560
|
1
|
|
|
|
|
4
|
no strict 'refs'; |
561
|
1
|
|
|
|
|
2
|
no warnings 'redefine'; |
562
|
|
|
|
|
|
|
*{ __PACKAGE__ . '::' . $name } = $subs->{$name}; |
563
|
|
|
|
|
|
|
} |
564
|
|
|
|
|
|
|
|
565
|
9
|
|
|
9
|
1
|
193
|
return $self; |
566
|
|
|
|
|
|
|
} |
567
|
9
|
50
|
|
|
|
30
|
|
568
|
0
|
|
|
|
|
0
|
my $self = shift; |
569
|
0
|
|
|
|
|
0
|
$self->{loaded}{$_} = time for (@_); |
570
|
|
|
|
|
|
|
return $self; |
571
|
|
|
|
|
|
|
} |
572
|
9
|
|
|
|
|
25
|
|
573
|
|
|
|
|
|
|
my ( $self, $name ) = @_; |
574
|
9
|
|
|
|
|
25
|
( $name = lc( substr( ( caller() )[0], length(__PACKAGE__) + 2 ) ) ) =~ s/::/\-/g unless ($name); |
575
|
13
|
|
|
13
|
|
92
|
return ( defined $self->{vars}{$name} ) ? $self->{vars}{$name} : {}; |
|
13
|
|
|
|
|
34
|
|
|
13
|
|
|
|
|
419
|
|
576
|
13
|
|
|
13
|
|
64
|
} |
|
13
|
|
|
|
|
29
|
|
|
13
|
|
|
|
|
15372
|
|
577
|
10
|
|
|
|
|
15
|
|
|
10
|
|
|
|
|
58
|
|
578
|
|
|
|
|
|
|
my ( $self, $name ) = @_; |
579
|
|
|
|
|
|
|
return ( defined $name ) ? $self->{$name} : { %$self }; |
580
|
9
|
|
|
|
|
40
|
} |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
my ( $self, $message ) = @_; |
583
|
|
|
|
|
|
|
|
584
|
2
|
|
|
2
|
1
|
173
|
if ( $self->{in}{forum} ) { |
585
|
2
|
|
|
|
|
16
|
$self->msg( |
586
|
2
|
|
|
|
|
5
|
( ( $self->{in}{forum} eq $self->{nick} ) ? $self->{in}{nick} : $self->{in}{forum} ), |
587
|
|
|
|
|
|
|
$message, |
588
|
|
|
|
|
|
|
); |
589
|
|
|
|
|
|
|
} |
590
|
4
|
|
|
4
|
1
|
8
|
else { |
591
|
4
|
100
|
|
|
|
25
|
warn "Didn't have a target to send reply to.\n"; |
592
|
4
|
50
|
|
|
|
34
|
} |
593
|
|
|
|
|
|
|
return $self; |
594
|
|
|
|
|
|
|
} |
595
|
|
|
|
|
|
|
|
596
|
1
|
|
|
1
|
1
|
3
|
my ( $self, $message ) = @_; |
597
|
1
|
50
|
|
|
|
4
|
return $self->reply( ( ( not $self->{in}{private} ) ? "$self->{in}{nick}: " : '' ) . $message ); |
598
|
|
|
|
|
|
|
} |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
my ( $self, $target, $message ) = @_; |
601
|
1
|
|
|
1
|
1
|
673
|
$self->say( "PRIVMSG $target :" . ( ( $message =~ s/^\/me\s+// ) ? "\001ACTION $message\001" : $message ) ); |
602
|
|
|
|
|
|
|
return $self; |
603
|
1
|
50
|
|
|
|
4
|
} |
604
|
|
|
|
|
|
|
|
605
|
0
|
0
|
|
|
|
0
|
my $self = shift; |
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
for (@_) { |
608
|
|
|
|
|
|
|
my $string = $_; |
609
|
|
|
|
|
|
|
$self->{socket}->print( $string . "\r\n" ); |
610
|
1
|
|
|
|
|
8
|
$self->note("<<< $string"); |
611
|
|
|
|
|
|
|
} |
612
|
1
|
|
|
|
|
6
|
return $self; |
613
|
|
|
|
|
|
|
} |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
my ( $self, $nick ) = @_; |
616
|
0
|
|
|
0
|
1
|
0
|
|
617
|
0
|
0
|
|
|
|
0
|
if ($nick) { |
618
|
|
|
|
|
|
|
$self->{nick} = $nick; |
619
|
|
|
|
|
|
|
$self->{device}->message( $_, ">>> NICK $self->{nick}" ) |
620
|
|
|
|
|
|
|
for ( grep { $_ != $$ } $self->{device}->ppid, @{ $self->{device}->children } ); |
621
|
1
|
|
|
1
|
1
|
1271
|
$self->say("NICK $self->{nick}"); |
622
|
1
|
50
|
|
|
|
6
|
} |
623
|
1
|
|
|
|
|
4
|
return $self->{nick}; |
624
|
|
|
|
|
|
|
} |
625
|
|
|
|
|
|
|
|
626
|
|
|
|
|
|
|
my $self = shift; |
627
|
3
|
|
|
3
|
1
|
1097
|
|
628
|
|
|
|
|
|
|
$self->say("JOIN $_") for ( |
629
|
3
|
|
|
|
|
5
|
( not @_ and $self->{connect}{join} ) |
630
|
4
|
|
|
|
|
4
|
? ( |
631
|
4
|
|
|
|
|
15
|
( ref $self->{connect}{join} eq 'ARRAY' ) |
632
|
4
|
|
|
|
|
14
|
? @{ $self->{connect}{join} } |
633
|
|
|
|
|
|
|
: $self->{connect}{join} |
634
|
3
|
|
|
|
|
6
|
) |
635
|
|
|
|
|
|
|
: @_ |
636
|
|
|
|
|
|
|
); |
637
|
|
|
|
|
|
|
|
638
|
1
|
|
|
1
|
1
|
1021
|
return $self; |
639
|
|
|
|
|
|
|
} |
640
|
1
|
50
|
|
|
|
4
|
|
641
|
1
|
|
|
|
|
2
|
my $self = shift; |
642
|
|
|
|
|
|
|
$self->say("PART $_") for (@_); |
643
|
1
|
|
|
|
|
3
|
return $self; |
|
3
|
|
|
|
|
12
|
|
|
1
|
|
|
|
|
5
|
|
644
|
1
|
|
|
|
|
7
|
} |
645
|
|
|
|
|
|
|
|
646
|
1
|
|
|
|
|
5
|
my ( $self, $separator, $conjunction ) = ( shift, shift, shift ); |
647
|
|
|
|
|
|
|
my @list = @_; |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
if ( @list > 2 ) { |
650
|
0
|
|
|
0
|
1
|
0
|
return CORE::join( $separator, @list[ 0 .. @list - 2 ], $conjunction . ' ' . $list[-1] ); |
651
|
|
|
|
|
|
|
} |
652
|
0
|
0
|
0
|
|
|
0
|
elsif ( @list > 1 ) { |
|
|
0
|
|
|
|
|
|
653
|
|
|
|
|
|
|
return $list[0] . ' ' . $conjunction . ' ' . $list[1]; |
654
|
|
|
|
|
|
|
} |
655
|
|
|
|
|
|
|
else { |
656
|
0
|
|
|
|
|
0
|
return $list[0]; |
657
|
|
|
|
|
|
|
} |
658
|
|
|
|
|
|
|
} |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
my ($self) = @_; |
661
|
|
|
|
|
|
|
|
662
|
0
|
|
|
|
|
0
|
return { |
663
|
|
|
|
|
|
|
nick => $self->{nick}, |
664
|
|
|
|
|
|
|
server => $self->{connect}{server}, |
665
|
|
|
|
|
|
|
port => $self->{connect}{port}, |
666
|
0
|
|
|
0
|
1
|
0
|
ssl => $self->{connect}{ssl}, |
667
|
0
|
|
|
|
|
0
|
spawn => $self->{spawn}, |
668
|
0
|
|
|
|
|
0
|
hooks => scalar( @{ $self->{hooks} } ), |
669
|
|
|
|
|
|
|
ticks => scalar( @{ $self->{ticks} } ), |
670
|
|
|
|
|
|
|
plugins => scalar( keys %{ $self->{loaded} } ), |
671
|
|
|
|
|
|
|
}; |
672
|
2
|
|
|
2
|
1
|
648
|
} |
673
|
2
|
|
|
|
|
5
|
|
674
|
|
|
|
|
|
|
my ($self) = @_; |
675
|
2
|
100
|
|
|
|
7
|
return $self->{numerics}; |
|
|
50
|
|
|
|
|
|
676
|
1
|
|
|
|
|
8
|
} |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
1; |
679
|
1
|
|
|
|
|
5
|
|
680
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
=pod |
682
|
0
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
=encoding UTF-8 |
684
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
=head1 NAME |
686
|
|
|
|
|
|
|
|
687
|
0
|
|
|
0
|
1
|
|
Bot::IRC - Yet Another IRC Bot |
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
=head1 VERSION |
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
version 1.39 |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
=for markdown [![test](https://github.com/gryphonshafer/Bot-IRC/workflows/test/badge.svg)](https://github.com/gryphonshafer/Bot-IRC/actions?query=workflow%3Atest) |
694
|
|
|
|
|
|
|
[![codecov](https://codecov.io/gh/gryphonshafer/Bot-IRC/graph/badge.svg)](https://codecov.io/gh/gryphonshafer/Bot-IRC) |
695
|
0
|
|
|
|
|
|
|
696
|
0
|
|
|
|
|
|
=head1 SYNOPSIS |
697
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
use Bot::IRC; |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
# minimal bot instance that does basically nothing except join a channel |
701
|
|
|
|
|
|
|
Bot::IRC->new( |
702
|
0
|
|
|
0
|
1
|
|
connect => { |
703
|
0
|
|
|
|
|
|
server => 'irc.perl.org', |
704
|
|
|
|
|
|
|
join => '#test', |
705
|
|
|
|
|
|
|
}, |
706
|
|
|
|
|
|
|
)->run; |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
# illustrative example of most settings and various ways to get at them |
709
|
|
|
|
|
|
|
my $bot = Bot::IRC->new( |
710
|
|
|
|
|
|
|
spawn => 2, |
711
|
|
|
|
|
|
|
daemon => { |
712
|
|
|
|
|
|
|
name => 'bot', |
713
|
|
|
|
|
|
|
lsb_sdesc => 'Yet Another IRC Bot', |
714
|
|
|
|
|
|
|
pid_file => 'bot.pid', |
715
|
|
|
|
|
|
|
stderr_file => 'bot.err', |
716
|
|
|
|
|
|
|
stdout_file => 'bot.log', |
717
|
|
|
|
|
|
|
}, |
718
|
|
|
|
|
|
|
connect => { |
719
|
|
|
|
|
|
|
server => 'irc.perl.org', |
720
|
|
|
|
|
|
|
port => '6667', |
721
|
|
|
|
|
|
|
nick => 'yabot', |
722
|
|
|
|
|
|
|
name => 'Yet Another IRC Bot', |
723
|
|
|
|
|
|
|
join => [ '#test', '#perl' ], |
724
|
|
|
|
|
|
|
ssl => 0, |
725
|
|
|
|
|
|
|
ipv6 => 0, |
726
|
|
|
|
|
|
|
}, |
727
|
|
|
|
|
|
|
plugins => [ |
728
|
|
|
|
|
|
|
':core', |
729
|
|
|
|
|
|
|
], |
730
|
|
|
|
|
|
|
vars => { |
731
|
|
|
|
|
|
|
store => 'bot.yaml', |
732
|
|
|
|
|
|
|
}, |
733
|
|
|
|
|
|
|
send_user_nick => 'on_parent', # or 'on_connect' or 'on_reply' |
734
|
|
|
|
|
|
|
); |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
$bot->load( 'Infobot', 'Karma' ); |
737
|
|
|
|
|
|
|
|
738
|
|
|
|
|
|
|
## Example inline plugin structure |
739
|
|
|
|
|
|
|
# $bot->load({ |
740
|
|
|
|
|
|
|
# hooks => [ [ {}, sub {}, {} ] ], |
741
|
|
|
|
|
|
|
# helps => { name => 'String' }, |
742
|
|
|
|
|
|
|
# subs => { name => sub {} }, |
743
|
|
|
|
|
|
|
# ticks => [ [ '0 * * * *', sub {} ] ], |
744
|
|
|
|
|
|
|
# }); |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
$bot->run; |
747
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
=head1 DESCRIPTION |
749
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
Yet another IRC bot. Why? There are so many good bots and bot frameworks to |
751
|
|
|
|
|
|
|
select from, but I wanted a bot framework that worked like a Unix service |
752
|
|
|
|
|
|
|
out-of-the-box, operated in a pre-fork way to serve multiple concurrent |
753
|
|
|
|
|
|
|
requests, and has a dirt-simple and highly extendable plugin mechanism. I also |
754
|
|
|
|
|
|
|
wanted to keep the direct dependencies and core bot minimalistic, allowing as |
755
|
|
|
|
|
|
|
much functionality as possible to be defined as optional plugins. |
756
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
=head2 Minimal Bot |
758
|
|
|
|
|
|
|
|
759
|
|
|
|
|
|
|
You can have a running IRC bot with as little as: |
760
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
use Bot::IRC; |
762
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
Bot::IRC->new( |
764
|
|
|
|
|
|
|
connect => { |
765
|
|
|
|
|
|
|
server => 'irc.perl.org', |
766
|
|
|
|
|
|
|
}, |
767
|
|
|
|
|
|
|
)->run; |
768
|
|
|
|
|
|
|
|
769
|
|
|
|
|
|
|
This won't actually do much apart from connecting to the server and responding |
770
|
|
|
|
|
|
|
to pings, but it's useful to understand how this works. Let's say you place the |
771
|
|
|
|
|
|
|
above code into a "bot.pl" file. You start the bot with: |
772
|
|
|
|
|
|
|
|
773
|
|
|
|
|
|
|
./bot.pl start |
774
|
|
|
|
|
|
|
|
775
|
|
|
|
|
|
|
This will startup the bot. Command-line commands include: start, stop, restart, |
776
|
|
|
|
|
|
|
reload, status, help, and so on. (See L<Daemon::Control> for more details.) |
777
|
|
|
|
|
|
|
|
778
|
|
|
|
|
|
|
=head2 Pre-Forking Device |
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
When the bot is started, the parent process will fork or spawn a given number |
781
|
|
|
|
|
|
|
of children workers. You can control their number along with setting locations |
782
|
|
|
|
|
|
|
for things like PID file, log files, and so on. |
783
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
Bot::IRC->new( |
785
|
|
|
|
|
|
|
spawn => 2, |
786
|
|
|
|
|
|
|
daemon => { |
787
|
|
|
|
|
|
|
name => 'bot', |
788
|
|
|
|
|
|
|
lsb_sdesc => 'Yet Another IRC Bot', |
789
|
|
|
|
|
|
|
pid_file => 'bot.pid', |
790
|
|
|
|
|
|
|
stderr_file => 'bot.err', |
791
|
|
|
|
|
|
|
stdout_file => 'bot.log', |
792
|
|
|
|
|
|
|
}, |
793
|
|
|
|
|
|
|
)->run; |
794
|
|
|
|
|
|
|
|
795
|
|
|
|
|
|
|
(See L<Daemon::Device> for more details.) |
796
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
=head1 MAIN METHODS |
798
|
|
|
|
|
|
|
|
799
|
|
|
|
|
|
|
The following are the main or primary available methods from this class. |
800
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
=head2 new |
802
|
|
|
|
|
|
|
|
803
|
|
|
|
|
|
|
This method instantiates a bot object that's potentially ready to start running. |
804
|
|
|
|
|
|
|
All bot settings can be specified to the C<new()> constructor, but some can be |
805
|
|
|
|
|
|
|
set or added to through other methods off the instantiated object. |
806
|
|
|
|
|
|
|
|
807
|
|
|
|
|
|
|
Bot::IRC->new( |
808
|
|
|
|
|
|
|
spawn => 2, |
809
|
|
|
|
|
|
|
daemon => {}, |
810
|
|
|
|
|
|
|
connect => { |
811
|
|
|
|
|
|
|
server => 'irc.perl.org', |
812
|
|
|
|
|
|
|
port => '6667', |
813
|
|
|
|
|
|
|
nick => 'yabot', |
814
|
|
|
|
|
|
|
name => 'Yet Another IRC Bot', |
815
|
|
|
|
|
|
|
join => [ '#test', '#perl' ], |
816
|
|
|
|
|
|
|
ssl => 0, |
817
|
|
|
|
|
|
|
ipv6 => 0, |
818
|
|
|
|
|
|
|
}, |
819
|
|
|
|
|
|
|
plugins => [], |
820
|
|
|
|
|
|
|
vars => {}, |
821
|
|
|
|
|
|
|
)->run; |
822
|
|
|
|
|
|
|
|
823
|
|
|
|
|
|
|
C<spawn> will default to 2. Under C<connect>, C<port> will default to 6667. |
824
|
|
|
|
|
|
|
C<join> can be either a string or an arrayref of strings representing channels |
825
|
|
|
|
|
|
|
to join after connnecting. C<ssl> is a true/false setting for whether to |
826
|
|
|
|
|
|
|
connect to the server over SSL. C<ipv6> is also true/false setting for whether |
827
|
|
|
|
|
|
|
to forcibly connect to the server over IPv6. |
828
|
|
|
|
|
|
|
|
829
|
|
|
|
|
|
|
You can optionally also provide an C<encoding> string representing a strict name |
830
|
|
|
|
|
|
|
of an encoding standard. If you don't set this, it will default to "UTF-8" |
831
|
|
|
|
|
|
|
internally. The encoding string is used to set the binmode for log files and for |
832
|
|
|
|
|
|
|
message text decoding as necessary. If you want to turn off this functionality, |
833
|
|
|
|
|
|
|
set C<encoding> to any defined false value. |
834
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
Read more about plugins below for more information about C<plugins> and C<vars>. |
836
|
|
|
|
|
|
|
Consult L<Daemon::Device> and L<Daemon::Control> for more details about C<spawn> |
837
|
|
|
|
|
|
|
and C<daemon>. |
838
|
|
|
|
|
|
|
|
839
|
|
|
|
|
|
|
There's also an optional C<send_user_nick> parameter, which you probably won't |
840
|
|
|
|
|
|
|
need to use, which defines when the bot will send the C<USER> and initial |
841
|
|
|
|
|
|
|
C<NICK> commands to the IRC server. There are 3 options: C<on_connect>, |
842
|
|
|
|
|
|
|
C<on_parent> (the default), and C<on_reply>. C<on_connect> sends the C<USER> |
843
|
|
|
|
|
|
|
and initial C<NICK> immediately upon establishing a connection to the IRC |
844
|
|
|
|
|
|
|
server, prior to the parent runtime loop and prior to children creation. |
845
|
|
|
|
|
|
|
C<on_parent> (the default) sends the 2 commands within the parent runtime loop |
846
|
|
|
|
|
|
|
prior to any responses from the IRC server. C<on_reply> (the only option in |
847
|
|
|
|
|
|
|
versions <= 1.23 of this module) sends the 2 commands after the IRC server |
848
|
|
|
|
|
|
|
replies with some sort of content after connection. |
849
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
If you provide a C<disconnect> value as a reference to a subroutine, it will be |
851
|
|
|
|
|
|
|
called when the bot is disconnected from a host. It's important to keep in mind |
852
|
|
|
|
|
|
|
that this code is run from within the parent of the daemon, not your program, so |
853
|
|
|
|
|
|
|
it's context will be different. The intent of this bot's design is to run as a |
854
|
|
|
|
|
|
|
service, not a program. This hook is provided so the parent process can send a |
855
|
|
|
|
|
|
|
signal to something that might want to take action. |
856
|
|
|
|
|
|
|
|
857
|
|
|
|
|
|
|
=head2 run |
858
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
This should be the last call you make, which will cause your program to operate |
860
|
|
|
|
|
|
|
like a Unix service from the command-line. (See L<Daemon::Control> for |
861
|
|
|
|
|
|
|
additional details.) |
862
|
|
|
|
|
|
|
|
863
|
|
|
|
|
|
|
C<run> can optionally be passed a list of strings that will be executed after |
864
|
|
|
|
|
|
|
connection to the IRC server. These should be string commands similar to what |
865
|
|
|
|
|
|
|
you'd type in an IRC client. For example: |
866
|
|
|
|
|
|
|
|
867
|
|
|
|
|
|
|
Bot::IRC->new( connect => { server => 'irc.perl.org' } )->run( |
868
|
|
|
|
|
|
|
'/msg nickserv identify bot_password', |
869
|
|
|
|
|
|
|
'/msg operserv identify bot_password', |
870
|
|
|
|
|
|
|
'/oper bot_username bot_password', |
871
|
|
|
|
|
|
|
'/msg chanserv identify #bot_talk bot_password', |
872
|
|
|
|
|
|
|
'/join #bot_talk', |
873
|
|
|
|
|
|
|
'/msg chanserv op #bot_talk', |
874
|
|
|
|
|
|
|
); |
875
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
=head1 PLUGINS |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
To do anything useful with a bot, you have to load plugins. You can do this |
879
|
|
|
|
|
|
|
either by specifying a list of plugins with the C<plugins> key passed to |
880
|
|
|
|
|
|
|
C<new()> or by calling C<load()>. |
881
|
|
|
|
|
|
|
|
882
|
|
|
|
|
|
|
Plugins are just simple packages (or optionally a hashref, but more on that |
883
|
|
|
|
|
|
|
later). The only requirement for plugins is that they provide an C<init()> |
884
|
|
|
|
|
|
|
method. This will get called by the bot prior to forking its worker children. |
885
|
|
|
|
|
|
|
It will be passed the bot object. Within C<init()>, you can call any number of |
886
|
|
|
|
|
|
|
plugin methods (see the list of methods below) to setup desired functionality. |
887
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
package Your::Plugin; |
889
|
|
|
|
|
|
|
use strict; |
890
|
|
|
|
|
|
|
use warnings; |
891
|
|
|
|
|
|
|
|
892
|
|
|
|
|
|
|
sub init { |
893
|
|
|
|
|
|
|
my ($bot) = @_; |
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
$bot->hook( |
896
|
|
|
|
|
|
|
{ |
897
|
|
|
|
|
|
|
to_me => 1, |
898
|
|
|
|
|
|
|
text => qr/\b(?<word>w00t|[l1][e3]{2}[t7])\b/i, |
899
|
|
|
|
|
|
|
}, |
900
|
|
|
|
|
|
|
sub { |
901
|
|
|
|
|
|
|
my ( $bot, $in, $m ) = @_; |
902
|
|
|
|
|
|
|
$bot->reply("$in->{nick}, don't use the word: $m->{word}."); |
903
|
|
|
|
|
|
|
}, |
904
|
|
|
|
|
|
|
); |
905
|
|
|
|
|
|
|
} |
906
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
1; |
908
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
When you load plugins, you can specify their packages a few different ways. When |
910
|
|
|
|
|
|
|
attempting to load a plugin, the bot will start by looking for the name you |
911
|
|
|
|
|
|
|
provided as a sub-class of itself. Then it will look for the plugin under the |
912
|
|
|
|
|
|
|
assumption you provided it's full name. |
913
|
|
|
|
|
|
|
|
914
|
|
|
|
|
|
|
plugins => [ |
915
|
|
|
|
|
|
|
'Store', # matches "Bot::IRC::Store" |
916
|
|
|
|
|
|
|
'Random', # matches "Bot::IRC::X::Random" |
917
|
|
|
|
|
|
|
'Thing', # matches "Bot::IRC::Y::Thing" |
918
|
|
|
|
|
|
|
'My::Own::Plugin', # matches "My::Own::Plugin" |
919
|
|
|
|
|
|
|
], |
920
|
|
|
|
|
|
|
|
921
|
|
|
|
|
|
|
An unenforced convention for public/shared plugins is to have non-core plugins |
922
|
|
|
|
|
|
|
(all plugins not provided directly by this CPAN library) subclasses of |
923
|
|
|
|
|
|
|
"Bot::IRC::X". For private/unshared plugins, you can specify whatever name you |
924
|
|
|
|
|
|
|
want, but maybe consider something like "Bot::IRC::Y". Plugins set in the X or |
925
|
|
|
|
|
|
|
Y subclass namespaces will get matched just like core plugins. "Y" plugins will |
926
|
|
|
|
|
|
|
have precedence over "X" which in turn will have precedence over core. |
927
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
If you need to allow for variables to get passed to your plugins, an unenforced |
929
|
|
|
|
|
|
|
convention is to do so via the C<vars> key to C<new()>. |
930
|
|
|
|
|
|
|
|
931
|
|
|
|
|
|
|
=head2 Core Plugins |
932
|
|
|
|
|
|
|
|
933
|
|
|
|
|
|
|
If you specify ":core" as a plugin name, it will be expanded to load all the |
934
|
|
|
|
|
|
|
core plugins. Core plugins are all the plugins that are bundled and |
935
|
|
|
|
|
|
|
distributed with L<Bot::IRC>. |
936
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
=over 4 |
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
=item * |
940
|
|
|
|
|
|
|
|
941
|
|
|
|
|
|
|
L<Bot::IRC::Ping> |
942
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
=item * |
944
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
L<Bot::IRC::Join> |
946
|
|
|
|
|
|
|
|
947
|
|
|
|
|
|
|
=item * |
948
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
L<Bot::IRC::Seen> |
950
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
=item * |
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
L<Bot::IRC::Greeting> |
954
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
=item * |
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
L<Bot::IRC::Infobot> |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
=item * |
960
|
|
|
|
|
|
|
|
961
|
|
|
|
|
|
|
L<Bot::IRC::Functions> |
962
|
|
|
|
|
|
|
|
963
|
|
|
|
|
|
|
=item * |
964
|
|
|
|
|
|
|
|
965
|
|
|
|
|
|
|
L<Bot::IRC::Convert> |
966
|
|
|
|
|
|
|
|
967
|
|
|
|
|
|
|
=item * |
968
|
|
|
|
|
|
|
|
969
|
|
|
|
|
|
|
L<Bot::IRC::Karma> |
970
|
|
|
|
|
|
|
|
971
|
|
|
|
|
|
|
=item * |
972
|
|
|
|
|
|
|
|
973
|
|
|
|
|
|
|
L<Bot::IRC::Math> |
974
|
|
|
|
|
|
|
|
975
|
|
|
|
|
|
|
=item * |
976
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
L<Bot::IRC::History> |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
=back |
980
|
|
|
|
|
|
|
|
981
|
|
|
|
|
|
|
Some core plugins require a storage plugin. If you don't specify one in your |
982
|
|
|
|
|
|
|
plugins list, then the default L<Bot::IRC::Store> will be used, which is |
983
|
|
|
|
|
|
|
probably not what you want (for performance reasons). Try |
984
|
|
|
|
|
|
|
L<Bot::IRC::Store::SQLite> instead. |
985
|
|
|
|
|
|
|
|
986
|
|
|
|
|
|
|
plugins => [ |
987
|
|
|
|
|
|
|
'Store::SQLite', |
988
|
|
|
|
|
|
|
':core', |
989
|
|
|
|
|
|
|
], |
990
|
|
|
|
|
|
|
|
991
|
|
|
|
|
|
|
=head1 PLUGIN METHODS |
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
The following are methods available from this class related to plugins. |
994
|
|
|
|
|
|
|
|
995
|
|
|
|
|
|
|
=head2 load |
996
|
|
|
|
|
|
|
|
997
|
|
|
|
|
|
|
This method loads plugins. It is the exact equivalent of passing strings to the |
998
|
|
|
|
|
|
|
C<plugins> key in C<new()>. If a plugin has already been loaded, it'll get |
999
|
|
|
|
|
|
|
skipped. |
1000
|
|
|
|
|
|
|
|
1001
|
|
|
|
|
|
|
my $bot = Bot::IRC->new( |
1002
|
|
|
|
|
|
|
connect => { server => 'irc.perl.org' }, |
1003
|
|
|
|
|
|
|
plugins => [ 'Store', 'Infobot', 'Karma' ], |
1004
|
|
|
|
|
|
|
); |
1005
|
|
|
|
|
|
|
|
1006
|
|
|
|
|
|
|
$bot->load( 'Infobot', 'Seen' ); |
1007
|
|
|
|
|
|
|
|
1008
|
|
|
|
|
|
|
From within your plugins, you can call C<load()> to specify plugin dependencies |
1009
|
|
|
|
|
|
|
in your plugins. |
1010
|
|
|
|
|
|
|
|
1011
|
|
|
|
|
|
|
sub init { |
1012
|
|
|
|
|
|
|
my ($bot) = @_; |
1013
|
|
|
|
|
|
|
$bot->load('Dependency'); |
1014
|
|
|
|
|
|
|
} |
1015
|
|
|
|
|
|
|
|
1016
|
|
|
|
|
|
|
=head2 reload |
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
If you need to actually reload a plugin, call C<reload>. It operates in the same |
1019
|
|
|
|
|
|
|
was as C<load>, only it won't skip already-loaded plugins. |
1020
|
|
|
|
|
|
|
|
1021
|
|
|
|
|
|
|
=head2 hook |
1022
|
|
|
|
|
|
|
|
1023
|
|
|
|
|
|
|
This is the method you'll call to add a hook, which is basically a message |
1024
|
|
|
|
|
|
|
response handler. A hook includes a conditions trigger, some code to run |
1025
|
|
|
|
|
|
|
when the trigger fires, and an optional additional attributes hashref. |
1026
|
|
|
|
|
|
|
|
1027
|
|
|
|
|
|
|
$bot->hook( |
1028
|
|
|
|
|
|
|
{ |
1029
|
|
|
|
|
|
|
to_me => 1, |
1030
|
|
|
|
|
|
|
text => qr/\b(?<word>w00t|[l1][e3]{2}[t7])\b/i, |
1031
|
|
|
|
|
|
|
}, |
1032
|
|
|
|
|
|
|
sub { |
1033
|
|
|
|
|
|
|
my ( $bot, $in, $m ) = @_; |
1034
|
|
|
|
|
|
|
$bot->reply("$in->{nick}, don't use the word: $m->{word}."); |
1035
|
|
|
|
|
|
|
}, |
1036
|
|
|
|
|
|
|
{ |
1037
|
|
|
|
|
|
|
subs => [], |
1038
|
|
|
|
|
|
|
helps => [], |
1039
|
|
|
|
|
|
|
}, |
1040
|
|
|
|
|
|
|
); |
1041
|
|
|
|
|
|
|
|
1042
|
|
|
|
|
|
|
The conditions trigger is a hashref of key-value pairs where the key is a |
1043
|
|
|
|
|
|
|
component of the message and the value is either a value to exact match or a |
1044
|
|
|
|
|
|
|
regular expression to match. |
1045
|
|
|
|
|
|
|
|
1046
|
|
|
|
|
|
|
The code block will receive a copy of the bot, a hashref of key-value pairs |
1047
|
|
|
|
|
|
|
representing the message the hook is responding to, and an optionally-available |
1048
|
|
|
|
|
|
|
hashref of any named matches from the regexes in the trigger. |
1049
|
|
|
|
|
|
|
|
1050
|
|
|
|
|
|
|
The hashref representing the message the hook will have the following keys: |
1051
|
|
|
|
|
|
|
|
1052
|
|
|
|
|
|
|
=over 4 |
1053
|
|
|
|
|
|
|
|
1054
|
|
|
|
|
|
|
=item * |
1055
|
|
|
|
|
|
|
|
1056
|
|
|
|
|
|
|
C<text>: text component of the message |
1057
|
|
|
|
|
|
|
|
1058
|
|
|
|
|
|
|
=item * |
1059
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
C<command>: IRC "command" like PRIVMSG, MODE, etc. |
1061
|
|
|
|
|
|
|
|
1062
|
|
|
|
|
|
|
=item * |
1063
|
|
|
|
|
|
|
|
1064
|
|
|
|
|
|
|
C<forum>: origin location like #channel or the nick who privately messaged |
1065
|
|
|
|
|
|
|
|
1066
|
|
|
|
|
|
|
=item * |
1067
|
|
|
|
|
|
|
|
1068
|
|
|
|
|
|
|
C<private>: 1 or 0 representing if the message is private or in a channel |
1069
|
|
|
|
|
|
|
|
1070
|
|
|
|
|
|
|
=item * |
1071
|
|
|
|
|
|
|
|
1072
|
|
|
|
|
|
|
C<to_me>: 1 or 0 representing if the message is addressing the bot or not |
1073
|
|
|
|
|
|
|
|
1074
|
|
|
|
|
|
|
=item * |
1075
|
|
|
|
|
|
|
|
1076
|
|
|
|
|
|
|
C<nick>: nick of the sender of the message |
1077
|
|
|
|
|
|
|
|
1078
|
|
|
|
|
|
|
=item * |
1079
|
|
|
|
|
|
|
|
1080
|
|
|
|
|
|
|
C<source>: the source server's label/name |
1081
|
|
|
|
|
|
|
|
1082
|
|
|
|
|
|
|
=item * |
1083
|
|
|
|
|
|
|
|
1084
|
|
|
|
|
|
|
C<user>: username of the sender of the message |
1085
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
=item * |
1087
|
|
|
|
|
|
|
|
1088
|
|
|
|
|
|
|
C<server>: server of the sender of the message |
1089
|
|
|
|
|
|
|
|
1090
|
|
|
|
|
|
|
=item * |
1091
|
|
|
|
|
|
|
|
1092
|
|
|
|
|
|
|
C<line>: full message line/text |
1093
|
|
|
|
|
|
|
|
1094
|
|
|
|
|
|
|
=item * |
1095
|
|
|
|
|
|
|
|
1096
|
|
|
|
|
|
|
C<full_text>: text component of the message with nick included |
1097
|
|
|
|
|
|
|
|
1098
|
|
|
|
|
|
|
=back |
1099
|
|
|
|
|
|
|
|
1100
|
|
|
|
|
|
|
B<The return value from the code block is important.> If you return a positive |
1101
|
|
|
|
|
|
|
value, all additional hooks are skipped because it will be assumed that this |
1102
|
|
|
|
|
|
|
hook properly responded to the message and no additional work needs to be done. |
1103
|
|
|
|
|
|
|
If the code block returns a false value, additional hooks will be checked as if |
1104
|
|
|
|
|
|
|
this hook's trigger caused the code block to be skipped. |
1105
|
|
|
|
|
|
|
|
1106
|
|
|
|
|
|
|
The optional additional attributes hashref supports a handful of keys. |
1107
|
|
|
|
|
|
|
You can specify C<subs> and C<helps>, which are exactly equivalent to |
1108
|
|
|
|
|
|
|
calling C<subs()> and C<helps()>. (See below.) |
1109
|
|
|
|
|
|
|
|
1110
|
|
|
|
|
|
|
=head2 hooks |
1111
|
|
|
|
|
|
|
|
1112
|
|
|
|
|
|
|
This method accepts a list of arrayrefs, each containing a trigger, code, and |
1113
|
|
|
|
|
|
|
attribute value and calls C<hook> for each set. |
1114
|
|
|
|
|
|
|
|
1115
|
|
|
|
|
|
|
## Example hooks call structure |
1116
|
|
|
|
|
|
|
# $bot->hooks( |
1117
|
|
|
|
|
|
|
# [ {}, sub {}, {} ], |
1118
|
|
|
|
|
|
|
# [ {}, sub {}, {} ], |
1119
|
|
|
|
|
|
|
# ); |
1120
|
|
|
|
|
|
|
|
1121
|
|
|
|
|
|
|
=head2 helps |
1122
|
|
|
|
|
|
|
|
1123
|
|
|
|
|
|
|
This method is how you'd setup any help text you'd like the bot to provide to |
1124
|
|
|
|
|
|
|
users. It expects some number of key-value pairs where the key is the topic |
1125
|
|
|
|
|
|
|
title of the set of functionality and the value is the string of instructions. |
1126
|
|
|
|
|
|
|
|
1127
|
|
|
|
|
|
|
$bot->helps( |
1128
|
|
|
|
|
|
|
seen => 'Tracks when and where people were seen. Usage: seen <nick>, hide, unhide.', |
1129
|
|
|
|
|
|
|
join => 'Join and leave channels. Usage: join <channel>, leave <channel>, channels.', |
1130
|
|
|
|
|
|
|
); |
1131
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
In the example above, let's say your bot had the nick of "bot" and you were in |
1133
|
|
|
|
|
|
|
the same channel as your bot and you typed "bot, help" in your IRC channel. The |
1134
|
|
|
|
|
|
|
bot would respond with a list of available topics. Then if you typed "bot, help |
1135
|
|
|
|
|
|
|
seen" in the channel, the bot would reply with the "seen" string of |
1136
|
|
|
|
|
|
|
instructions. If typing directly to the bot (in a private message directly to |
1137
|
|
|
|
|
|
|
the bot), you don't need to specify the bot's name. |
1138
|
|
|
|
|
|
|
|
1139
|
|
|
|
|
|
|
=head2 tick |
1140
|
|
|
|
|
|
|
|
1141
|
|
|
|
|
|
|
Sometimes you'll want the bot to do something at a specific time or at some sort |
1142
|
|
|
|
|
|
|
of interval. You can cause this to happen by filing ticks. A tick is similar to |
1143
|
|
|
|
|
|
|
a hook in that it's a bit of code that gets called, but not based on a message |
1144
|
|
|
|
|
|
|
but based on time. C<tick()> expects two values. The first is either an integer |
1145
|
|
|
|
|
|
|
representing the number of seconds of interval between calls to the code or a |
1146
|
|
|
|
|
|
|
crontab-like time expression. The second value is the code to call, which will |
1147
|
|
|
|
|
|
|
receive a copy of the bot object. |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
$bot->tick( |
1150
|
|
|
|
|
|
|
10, |
1151
|
|
|
|
|
|
|
sub { |
1152
|
|
|
|
|
|
|
my ($bot) = @_; |
1153
|
|
|
|
|
|
|
$bot->msg( '#test', '10-second interval.' ); |
1154
|
|
|
|
|
|
|
}, |
1155
|
|
|
|
|
|
|
); |
1156
|
|
|
|
|
|
|
|
1157
|
|
|
|
|
|
|
$bot->tick( |
1158
|
|
|
|
|
|
|
'0 0 * * *', |
1159
|
|
|
|
|
|
|
sub { |
1160
|
|
|
|
|
|
|
my ($bot) = @_; |
1161
|
|
|
|
|
|
|
$bot->msg( '#test', "It's midnight!" ); |
1162
|
|
|
|
|
|
|
}, |
1163
|
|
|
|
|
|
|
); |
1164
|
|
|
|
|
|
|
|
1165
|
|
|
|
|
|
|
=head2 ticks |
1166
|
|
|
|
|
|
|
|
1167
|
|
|
|
|
|
|
This method accepts a list of arrayrefs, each containing a time value and code |
1168
|
|
|
|
|
|
|
block and calls C<tick> for each set. |
1169
|
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
$bot->ticks( |
1171
|
|
|
|
|
|
|
[ 10, sub {} ], |
1172
|
|
|
|
|
|
|
[ '0 0 * * *', sub {} ], |
1173
|
|
|
|
|
|
|
); |
1174
|
|
|
|
|
|
|
|
1175
|
|
|
|
|
|
|
=head2 subs |
1176
|
|
|
|
|
|
|
|
1177
|
|
|
|
|
|
|
A plugin can also provide functionality to the bot for use in other plugins. |
1178
|
|
|
|
|
|
|
It can also override core methods of the bot. You do this with the C<subs()> |
1179
|
|
|
|
|
|
|
method. |
1180
|
|
|
|
|
|
|
|
1181
|
|
|
|
|
|
|
$bot->subs( |
1182
|
|
|
|
|
|
|
incr => sub { |
1183
|
|
|
|
|
|
|
my ( $bot, $int ) = @_; |
1184
|
|
|
|
|
|
|
return ++$int; |
1185
|
|
|
|
|
|
|
}, |
1186
|
|
|
|
|
|
|
); |
1187
|
|
|
|
|
|
|
|
1188
|
|
|
|
|
|
|
my $value = $bot->incr(42); # value is 43 |
1189
|
|
|
|
|
|
|
|
1190
|
|
|
|
|
|
|
=head2 register |
1191
|
|
|
|
|
|
|
|
1192
|
|
|
|
|
|
|
There are rare cases when you're writing your plugin where you want to claim |
1193
|
|
|
|
|
|
|
that your plugin satisfies the requirements for a different plugin. In other |
1194
|
|
|
|
|
|
|
words, you want to prevent the future loading of a specific plugin or plugins. |
1195
|
|
|
|
|
|
|
You can do this by calling C<register()> with the list of plugins (by full |
1196
|
|
|
|
|
|
|
namespace) that you want to skip. |
1197
|
|
|
|
|
|
|
|
1198
|
|
|
|
|
|
|
$bot->register('Bot::IRC::Storage'); |
1199
|
|
|
|
|
|
|
|
1200
|
|
|
|
|
|
|
Note that this will not block the reloading of plugins with C<reload()>. |
1201
|
|
|
|
|
|
|
|
1202
|
|
|
|
|
|
|
=head2 vars |
1203
|
|
|
|
|
|
|
|
1204
|
|
|
|
|
|
|
When you are within a plugin, you can call C<vars()> to get the variables for |
1205
|
|
|
|
|
|
|
the plugin by it's lower-case "simplified" name, which is the plugin's class |
1206
|
|
|
|
|
|
|
name all lower-case, without the preceding "Bot::IRC::" bit, and with "::"s |
1207
|
|
|
|
|
|
|
replaced with dashes. For example, let's say you were writing a |
1208
|
|
|
|
|
|
|
"Bot::IRC::X::Something" plugin. You would have users set variables in their |
1209
|
|
|
|
|
|
|
instantiation like so: |
1210
|
|
|
|
|
|
|
|
1211
|
|
|
|
|
|
|
Bot::IRC->new |
1212
|
|
|
|
|
|
|
plugins => ['Something'], |
1213
|
|
|
|
|
|
|
vars => { x-something => { answer => 42 } }, |
1214
|
|
|
|
|
|
|
)->run; |
1215
|
|
|
|
|
|
|
|
1216
|
|
|
|
|
|
|
Then from within the "Bot::IRC::X::Something" plugin, you would access these |
1217
|
|
|
|
|
|
|
variables like so: |
1218
|
|
|
|
|
|
|
|
1219
|
|
|
|
|
|
|
my $my_vars = $bot->vars; |
1220
|
|
|
|
|
|
|
say 'The answer to life, the universe, and everything is ' . $my_vars->{answer}; |
1221
|
|
|
|
|
|
|
|
1222
|
|
|
|
|
|
|
If you want to access the variables for a different namespace, pass into |
1223
|
|
|
|
|
|
|
C<vars()> the "simplified" name you want to access. |
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
my $my_other_vars = $bot->vars('x-something-else'); |
1226
|
|
|
|
|
|
|
|
1227
|
|
|
|
|
|
|
=head2 settings |
1228
|
|
|
|
|
|
|
|
1229
|
|
|
|
|
|
|
If you need access to the bot's settings, you can do so with C<settings()>. |
1230
|
|
|
|
|
|
|
Supply the setting name/key to get that setting, or provide no name/key to get |
1231
|
|
|
|
|
|
|
all settings as a hashref. |
1232
|
|
|
|
|
|
|
|
1233
|
|
|
|
|
|
|
my $connection_settings_hashref = $bot->settings('connect'); |
1234
|
|
|
|
|
|
|
|
1235
|
|
|
|
|
|
|
=head1 INLINE PLUGINS |
1236
|
|
|
|
|
|
|
|
1237
|
|
|
|
|
|
|
You can optionally inject inline plugins by providing them as hashref. This |
1238
|
|
|
|
|
|
|
works both with C<load()> and the C<plugins> key. |
1239
|
|
|
|
|
|
|
|
1240
|
|
|
|
|
|
|
## Example inline plugin structure |
1241
|
|
|
|
|
|
|
# $bot->load( |
1242
|
|
|
|
|
|
|
# { |
1243
|
|
|
|
|
|
|
# hooks => [ [ {}, sub {}, {} ], [ {}, sub {}, {} ] ], |
1244
|
|
|
|
|
|
|
# ticks => [ [ 10, sub {} ], [ '0 0 * * *', sub {} ] ], |
1245
|
|
|
|
|
|
|
# helps => { title => 'Description.' }, |
1246
|
|
|
|
|
|
|
# subs => { name => sub {} }, |
1247
|
|
|
|
|
|
|
# }, |
1248
|
|
|
|
|
|
|
# { |
1249
|
|
|
|
|
|
|
# hooks => [ [ {}, sub {}, {} ], [ {}, sub {}, {} ] ], |
1250
|
|
|
|
|
|
|
# ticks => [ [ 10, sub {} ], [ '0 0 * * *', sub {} ] ], |
1251
|
|
|
|
|
|
|
# helps => { title => 'Description.' }, |
1252
|
|
|
|
|
|
|
# subs => { name => sub {} }, |
1253
|
|
|
|
|
|
|
# }, |
1254
|
|
|
|
|
|
|
# ); |
1255
|
|
|
|
|
|
|
|
1256
|
|
|
|
|
|
|
=head1 OPERATIONAL METHODS |
1257
|
|
|
|
|
|
|
|
1258
|
|
|
|
|
|
|
The following are operational methods available from this class, expected to be |
1259
|
|
|
|
|
|
|
used inside various code blocks passed to plugin methds. |
1260
|
|
|
|
|
|
|
|
1261
|
|
|
|
|
|
|
=head2 reply |
1262
|
|
|
|
|
|
|
|
1263
|
|
|
|
|
|
|
If you're inside a hook, you can usually respond to most messages with the |
1264
|
|
|
|
|
|
|
C<reply()> method, which accepts the text the bot should reply with. The method |
1265
|
|
|
|
|
|
|
returns the bot object. |
1266
|
|
|
|
|
|
|
|
1267
|
|
|
|
|
|
|
$bot->reply('This is a reply. Impressive, huh?'); |
1268
|
|
|
|
|
|
|
|
1269
|
|
|
|
|
|
|
If you want to emote something back or use any other IRC command, type it just |
1270
|
|
|
|
|
|
|
as you would in your IRC client. |
1271
|
|
|
|
|
|
|
|
1272
|
|
|
|
|
|
|
$bot->reply('/me feels something, which for a bot is rather impressive.'); |
1273
|
|
|
|
|
|
|
|
1274
|
|
|
|
|
|
|
=head2 reply_to |
1275
|
|
|
|
|
|
|
|
1276
|
|
|
|
|
|
|
C<reply_to> is exactly like C<reply> except that if the forum for the reply is |
1277
|
|
|
|
|
|
|
a channel instead of to a specific person, the bot will prepend the message |
1278
|
|
|
|
|
|
|
by addressing the nick who was the source of the response the bot is responding |
1279
|
|
|
|
|
|
|
to. |
1280
|
|
|
|
|
|
|
|
1281
|
|
|
|
|
|
|
=head2 msg |
1282
|
|
|
|
|
|
|
|
1283
|
|
|
|
|
|
|
Use C<msg()> when you don't have a forum to reply to or want to reply in a |
1284
|
|
|
|
|
|
|
different forum (i.e. to a different user or channel). The method accepts the |
1285
|
|
|
|
|
|
|
forum for the message and the message text. |
1286
|
|
|
|
|
|
|
|
1287
|
|
|
|
|
|
|
$bot->msg( '#test', 'This is a message for everybody in #test.'); |
1288
|
|
|
|
|
|
|
|
1289
|
|
|
|
|
|
|
=head2 say |
1290
|
|
|
|
|
|
|
|
1291
|
|
|
|
|
|
|
Use C<say()> to write low-level lines to the IRC server. The method expects a |
1292
|
|
|
|
|
|
|
string that's a properly IRC message. |
1293
|
|
|
|
|
|
|
|
1294
|
|
|
|
|
|
|
$bot->say('JOIN #help'); |
1295
|
|
|
|
|
|
|
$bot->say('PRIVMSG #help :I need some help.'); |
1296
|
|
|
|
|
|
|
|
1297
|
|
|
|
|
|
|
=head2 nick |
1298
|
|
|
|
|
|
|
|
1299
|
|
|
|
|
|
|
Use C<nick> to change the bot's nick. If the nick is already in use, the bot |
1300
|
|
|
|
|
|
|
will try appending "_" to it until it finds an open nick. |
1301
|
|
|
|
|
|
|
|
1302
|
|
|
|
|
|
|
=head2 join |
1303
|
|
|
|
|
|
|
|
1304
|
|
|
|
|
|
|
Use C<join()> to join channels. |
1305
|
|
|
|
|
|
|
|
1306
|
|
|
|
|
|
|
$bot->join('#help'); |
1307
|
|
|
|
|
|
|
|
1308
|
|
|
|
|
|
|
If some sort of persistent storage plugin is loaded, the bot will remember the |
1309
|
|
|
|
|
|
|
channels it has joined or parted and use that as it's initial join on restart. |
1310
|
|
|
|
|
|
|
|
1311
|
|
|
|
|
|
|
=head2 part |
1312
|
|
|
|
|
|
|
|
1313
|
|
|
|
|
|
|
Use C<part()> to part channels. |
1314
|
|
|
|
|
|
|
|
1315
|
|
|
|
|
|
|
$bot->part('#help'); |
1316
|
|
|
|
|
|
|
|
1317
|
|
|
|
|
|
|
If some sort of persistent storage plugin is loaded, the bot will remember the |
1318
|
|
|
|
|
|
|
channels it has joined or parted and use that as it's initial join on restart. |
1319
|
|
|
|
|
|
|
|
1320
|
|
|
|
|
|
|
=head1 RANDOM HELPFUL METHODS |
1321
|
|
|
|
|
|
|
|
1322
|
|
|
|
|
|
|
The following are random additional methods that might be helpful in your |
1323
|
|
|
|
|
|
|
plugins. |
1324
|
|
|
|
|
|
|
|
1325
|
|
|
|
|
|
|
=head2 list |
1326
|
|
|
|
|
|
|
|
1327
|
|
|
|
|
|
|
This method is a simple string method that takes a list and crafts it for |
1328
|
|
|
|
|
|
|
readability. It expects a separator string, a final item conjunction string, |
1329
|
|
|
|
|
|
|
and a list of items. |
1330
|
|
|
|
|
|
|
|
1331
|
|
|
|
|
|
|
$bot->list( ', ', 'and', 'Alpha', 'Beta', 'Delta', 'Gamma' ); |
1332
|
|
|
|
|
|
|
# returns "Alpha, Beta, Delta, and Gamma" |
1333
|
|
|
|
|
|
|
|
1334
|
|
|
|
|
|
|
$bot->list( ', ', 'and', 'Alpha', 'Beta' ); |
1335
|
|
|
|
|
|
|
# returns "Alpha and Beta" |
1336
|
|
|
|
|
|
|
|
1337
|
|
|
|
|
|
|
=head2 health |
1338
|
|
|
|
|
|
|
|
1339
|
|
|
|
|
|
|
This method returns a hashref of simple key value pairs for different "health" |
1340
|
|
|
|
|
|
|
aspects (or current state) of the bot. It includes things like server and port |
1341
|
|
|
|
|
|
|
connection, number of children, and so on. |
1342
|
|
|
|
|
|
|
|
1343
|
|
|
|
|
|
|
=head2 note |
1344
|
|
|
|
|
|
|
|
1345
|
|
|
|
|
|
|
While in theory you shouldn't ever need to use it, there is a method called |
1346
|
|
|
|
|
|
|
"note" which is a handler for writing to the log and error files. If you |
1347
|
|
|
|
|
|
|
C<warn> or C<die>, this handler steps in automatically. If you'd like to |
1348
|
|
|
|
|
|
|
C<print> to STDOUT, which you really shouldn't need to do, then it's best to |
1349
|
|
|
|
|
|
|
call this method instead. The reason being is that the log file is designed to |
1350
|
|
|
|
|
|
|
be parsed in a specific way. If you write whatever you want to it, it will |
1351
|
|
|
|
|
|
|
corrupt the log file. That said, if you really, really want to, here's how you |
1352
|
|
|
|
|
|
|
use C<note>: |
1353
|
|
|
|
|
|
|
|
1354
|
|
|
|
|
|
|
$bot->note('Message'); # writes a message to the log file |
1355
|
|
|
|
|
|
|
$bot->note( 'Message', 'warn' ); # writes a message to the error file |
1356
|
|
|
|
|
|
|
$bot->note( 'Message', 'die' ); # writes a message to the error file the dies |
1357
|
|
|
|
|
|
|
|
1358
|
|
|
|
|
|
|
=head2 numerics |
1359
|
|
|
|
|
|
|
|
1360
|
|
|
|
|
|
|
This method will return an arrayref of scalar strings, each an IRC numeric line |
1361
|
|
|
|
|
|
|
from the server. The arrayref is limited to the first 100 numerics from the |
1362
|
|
|
|
|
|
|
server. |
1363
|
|
|
|
|
|
|
|
1364
|
|
|
|
|
|
|
=head1 SEE ALSO |
1365
|
|
|
|
|
|
|
|
1366
|
|
|
|
|
|
|
You can look for additional information at: |
1367
|
|
|
|
|
|
|
|
1368
|
|
|
|
|
|
|
=over 4 |
1369
|
|
|
|
|
|
|
|
1370
|
|
|
|
|
|
|
=item * |
1371
|
|
|
|
|
|
|
|
1372
|
|
|
|
|
|
|
L<GitHub|https://github.com/gryphonshafer/Bot-IRC> |
1373
|
|
|
|
|
|
|
|
1374
|
|
|
|
|
|
|
=item * |
1375
|
|
|
|
|
|
|
|
1376
|
|
|
|
|
|
|
L<MetaCPAN|https://metacpan.org/pod/Bot::IRC> |
1377
|
|
|
|
|
|
|
|
1378
|
|
|
|
|
|
|
=item * |
1379
|
|
|
|
|
|
|
|
1380
|
|
|
|
|
|
|
L<GitHub Actions|https://github.com/gryphonshafer/Bot-IRC/actions> |
1381
|
|
|
|
|
|
|
|
1382
|
|
|
|
|
|
|
=item * |
1383
|
|
|
|
|
|
|
|
1384
|
|
|
|
|
|
|
L<Codecov|https://codecov.io/gh/gryphonshafer/Bot-IRC> |
1385
|
|
|
|
|
|
|
|
1386
|
|
|
|
|
|
|
=item * |
1387
|
|
|
|
|
|
|
|
1388
|
|
|
|
|
|
|
L<CPANTS|http://cpants.cpanauthors.org/dist/Bot-IRC> |
1389
|
|
|
|
|
|
|
|
1390
|
|
|
|
|
|
|
=item * |
1391
|
|
|
|
|
|
|
|
1392
|
|
|
|
|
|
|
L<CPAN Testers|http://www.cpantesters.org/distro/T/Bot-IRC.html> |
1393
|
|
|
|
|
|
|
|
1394
|
|
|
|
|
|
|
=back |
1395
|
|
|
|
|
|
|
|
1396
|
|
|
|
|
|
|
=head1 AUTHOR |
1397
|
|
|
|
|
|
|
|
1398
|
|
|
|
|
|
|
Gryphon Shafer <gryphon@cpan.org> |
1399
|
|
|
|
|
|
|
|
1400
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
1401
|
|
|
|
|
|
|
|
1402
|
|
|
|
|
|
|
This software is Copyright (c) 2016-2050 by Gryphon Shafer. |
1403
|
|
|
|
|
|
|
|
1404
|
|
|
|
|
|
|
This is free software, licensed under: |
1405
|
|
|
|
|
|
|
|
1406
|
|
|
|
|
|
|
The Artistic License 2.0 (GPL Compatible) |
1407
|
|
|
|
|
|
|
|
1408
|
|
|
|
|
|
|
=cut |