File Coverage

lib/Colloquy/Bot/Simple.pm
Criterion Covered Total %
statement 3 3 100.0
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 4 4 100.0


line stmt bran cond sub pod time code
1             ############################################################
2             #
3             # $Id: Simple.pm 518 2006-05-29 11:32:23Z nicolaw $
4             # Colloquy::Bot::Simple - Simple robot interface for Colloquy
5             #
6             # Copyright 2006 Nicola Worthington
7             #
8             # Licensed under the Apache License, Version 2.0 (the "License");
9             # you may not use this file except in compliance with the License.
10             # You may obtain a copy of the License at
11             #
12             # http://www.apache.org/licenses/LICENSE-2.0
13             #
14             # Unless required by applicable law or agreed to in writing, software
15             # distributed under the License is distributed on an "AS IS" BASIS,
16             # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17             # See the License for the specific language governing permissions and
18             # limitations under the License.
19             #
20             ############################################################
21              
22             package Colloquy::Bot::Simple;
23             # vim:ts=4:sw=4:tw=78
24              
25 2     2   10088 use base qw(Chatbot::TalkerBot);
  2         5  
  2         1430  
26              
27             use strict;
28             no warnings qw(redefine);
29              
30             use Exporter;
31             use Carp qw(croak cluck carp confess);
32             use Parse::Colloquy::Bot qw(:all);
33              
34             use vars qw(@EXPORT @EXPORT_OK $VERSION);
35              
36             @EXPORT = qw(&connect_through_firewall &connect_directly &daemonize);
37             @EXPORT_OK = qw(TB_TRACE TB_LOG);
38              
39             $VERSION = '1.08' || sprintf('%d', q$Revision: 518 $ =~ /(\d+)/g);
40              
41             sub TB_LOG { Chatbot::TalkerBot::TB_TRACE(@_); }
42             sub TB_TRACE { Chatbot::TalkerBot::TB_TRACE(@_); }
43              
44             sub listenLoop {
45             my $self = shift;
46             my $callback = shift;
47             my $interrupt = shift;
48            
49             # check that any supplied callback is a coderef
50             if ($callback && (ref( $callback ) ne 'CODE')) { die("The callback must be a code reference"); }
51             if ($interrupt) { TB_LOG("Installing interrupt handler every $interrupt secs"); }
52            
53             my $STOPLOOP = 0;
54             local $SIG{'ALRM'} = ($interrupt? sub { $callback->($self, 'ALRM'); alarm($interrupt); } : 'IGNORE');
55             alarm($interrupt) if $interrupt;
56            
57             # enter event loop
58             TB_LOG("Entering listening loop");
59             my $socket = $self->{'connection'};
60              
61             while( <$socket> ) {
62             # we don't know how long it will take to process this line, so stop interrupts
63             alarm(0) if $interrupt;
64            
65             s/[\n\r]//g;
66            
67             # only pay any attention to that regular expression
68             if ($self->{'AnyCommands'} == 1) {
69             my $args = Parse::Colloquy::Bot::parse_line($_);
70             $args->{alarm} = 0;
71              
72             TB_LOG("Attending: <$args->{msgtype}> = <$args->{text}>");
73             $self->{'lines_in'} += 1;
74              
75             $STOPLOOP = $callback->($self, %{$args});
76             }
77            
78             # command processing done, turn interrupts back on
79             last if $STOPLOOP;
80             alarm($interrupt) if $interrupt;
81             }
82             TB_LOG("Fallen out of listening loop");
83             }
84              
85              
86             sub new {
87             my $class = shift;
88             croak "Odd number of elements passed when even was expected"
89             if @_ % 2;
90              
91             my $self = {};
92             while (my $key = shift(@_)) {
93             $self->{lc($key)} = shift(@_);
94             }
95              
96             for my $key qw(username password host port) {
97             unless (exists $self->{$key} && length($self->{$key})) {
98             croak "No '$key' value was specified";
99             }
100             }
101              
102             my $socket = Chatbot::TalkerBot::connect_directly(
103             $self->{host},
104             $self->{port}
105             );
106              
107             my $talker = $class->SUPER::new($socket, {
108             Username => $self->{username},
109             Password => $self->{password},
110             UsernameResponse => $self->{usernameresponse} || ' ',
111             UsernamePrompt => $self->{usernameprompt} || 'HELLO colloquy',
112             PasswordPrompt => $self->{passwordprompt} || '',
113             PasswordResponse => $self->{passwordresponse} || '',
114             LoginSuccess => $self->{loginsuccess} || 'MARK ---',
115             LoginFail => $self->{loginfail} || 'Incorrect login',
116             #NoCommands => 1,
117             });
118              
119             return $talker;
120             }
121              
122             sub _is_list {
123             local $_ = shift || '';
124             if (/^LIST.+\{(\w+?)\}\s*$/) {
125             return '%'.$1;
126             } elsif (/^OBSERVED\s+(\S+)\s+/) {
127             return '@'.$1;
128             }
129             return undef;
130             }
131              
132             # Daemonize self
133             sub daemonize {
134             # Pass in the PID filename to use
135             my $pidfile = shift || undef;
136              
137             # Boolean true will supress "already running" messages if you want to
138             # spawn a process out of cron every so often to ensure it's always
139             # running, and to respawn it if it's died
140             my $cron = shift || 0;
141              
142             # Set the fname to the filename minus path
143             (my $SELF = $0) =~ s|.*/||;
144             $0 = $SELF;
145              
146             # Lazy people have to have everything done for them!
147             $pidfile = "/tmp/$SELF.pid" unless defined $pidfile;
148              
149             # Check that we're not already running, and quit if we are
150             if (-f $pidfile) {
151             unless (open(PID,$pidfile)) {
152             warn "Unable to open file handle PID for file '$pidfile': $!\n";
153             exit 1;
154             }
155             my $pid = ; chomp $pid;
156             close(PID) || warn "Unable to close file handle PID for file '$pidfile': $!\n";
157              
158             # This is a good method to check the process is still running for Linux
159             # kernels since it checks that the fname of the process is the same as
160             # the current process
161             if (-f "/proc/$pid/stat") {
162             open(FH,"/proc/$pid/stat") || warn "Unable to open file handle FH for file '/proc/$pid/stat': $!\n";
163             my $line = ;
164             close(FH) || warn "Unable to close file handle FH for file '/proc/$pid/stat': $!\n";
165             if ($line =~ /\d+[^(]*\((.*)\)\s*/) {
166             my $process = $1;
167             if ($process =~ /^$SELF$/) {
168             warn "$SELF already running at PID $pid; exiting.\n" unless $cron;
169             exit 0;
170             }
171             }
172              
173             # This will work on other UNIX flavors but doesn't gaurentee that the
174             # PID you've just checked is the same process fname as reported in you
175             # PID file
176             } elsif (kill(0,$pid)) {
177             warn "$SELF already running at PID $pid; exiting.\n" unless $cron;
178             exit 0;
179              
180             # Otherwise the PID file is old and stale and it should be removed
181             } else {
182             warn "Removing stale PID file.\n";
183             unlink($pidfile) || warn "Unable to unlink PID file '$pidfile': $!\n";
184             }
185             }
186              
187             # Daemon parent about to spawn
188             if (my $pid = fork) {
189             warn "Forking background daemon, process $pid.\n";
190             exit 0;
191              
192             # Child daemon process that was spawned
193             } else {
194             # Fork a second time to get rid of any attached terminals
195             if (my $pid = fork) {
196             warn "Forking second background daemon, process $pid.\n";
197             exit 0;
198             } else {
199             unless (defined $pid) {
200             warn "Cannot fork: $!\n";
201             exit 2;
202             }
203             unless (open(FH,">$pidfile")) {
204             warn "Unable to open file handle FH for file '$pidfile': $!\n";
205             exit 3;
206             }
207             print FH $$;
208             close(FH) || warn "Unable to close file handle FH for file '$pidfile': $!\n";
209              
210             # Sort out file handles and current working directory
211             chdir '/' || warn "Unable to change directory to '/': $!\n";
212             close(STDOUT) || warn "Unable to close file handle STDOUT: $!\n";
213             close(STDERR) || warn "Unable to close file handle STDERR: $!\n";
214             open(STDOUT,'>>/dev/null'); open(STDERR,'>>/dev/null');
215              
216             return $$;
217             }
218             }
219             }
220              
221             1;
222              
223             =pod
224              
225             =head1 NAME
226              
227             Colloquy::Bot::Simple - Simple robot interface for Colloquy
228              
229             =head1 SYNOPSIS
230              
231             use Colloquy::Bot::Simple qw(daemonize);
232            
233             # Create a connection
234             my $talker = Colloquy::Bot::Simple->new(
235             host => '127.0.0.1',
236             port => 1236,
237             username => 'MyBot',
238             password => 'topsecret',
239             );
240            
241             # Daemonize in to the background
242             daemonize("/tmp/MyBot.pid","quiet");
243            
244             # Execute callback on speech and "alarm" every 60 seconds
245             $talker->listenLoop(\&event_callback, 60);
246              
247             # Tidy up and finish
248             $talker->quit();
249             exit;
250            
251             sub event_callback {
252             my $talker = shift;
253             my $event = @_ % 2 ? { alarm => 1 } : { @_ };
254            
255             if (exists $event->{alarm}) {
256             print "Callback called as ALARM interrupt handler\n";
257             # ... go check an RSS feed for new news items to inform
258             # your users about or something else nice maybe ...?
259            
260             } elsif (lc($event->{command}) eq 'hello') {
261             $talker->whisper(
262             (exists $event->{list} ? $event->{list} : $event->{person}),
263             "Hi there $event->{person}"
264             );
265            
266             } elsif ($event->{msgtype} eq 'TELL') {
267             $talker->whisper($event->{person}, 'Pardon?');
268             }
269            
270             # Return boolean false to continue the listenLoop
271             return 0;
272             }
273              
274             =head1 DESCRIPTION
275              
276             A very simple robot interface to connect and interact with a Colloquy talker,
277             based upon Chatbot::TalkerBot.
278              
279             =head1 METHODS
280              
281             =head2 new
282              
283             =head2 daemonize
284              
285             =head2 listenLoop
286              
287             =head2 say
288              
289             =head2 whisper
290              
291             =head2 quit
292              
293             =head1 TODO
294              
295             Write some decent POD.
296              
297             =head1 SEE ALSO
298              
299             L, L, L
300              
301             =head1 VERSION
302              
303             $Id: Simple.pm 518 2006-05-29 11:32:23Z nicolaw $
304              
305             =head1 AUTHOR
306              
307             Nicola Worthington
308              
309             L
310              
311             =head1 COPYRIGHT
312              
313             Copyright 2006 Nicola Worthington.
314              
315             This software is licensed under The Apache Software License, Version 2.0.
316              
317             L
318              
319             =cut
320              
321              
322              
323              
324              
325