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