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