File Coverage

blib/lib/Hubot/Robot.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Hubot::Robot;
2             $Hubot::Robot::VERSION = '0.2.7';
3 6     6   19386 use Moose;
  0            
  0            
4             use namespace::autoclean;
5              
6             use Pod::Usage;
7              
8             use AnyEvent;
9             use AnyEvent::HTTPD;
10             use AnyEvent::HTTP::ScopedClient;
11              
12             use Hubot::User;
13             use Hubot::Brain;
14             use Hubot::Listener;
15             use Hubot::TextListener;
16              
17             has 'name' => ( is => 'rw', isa => 'Str' );
18             has 'alias' => ( is => 'rw', isa => 'Str' );
19             has 'mode' => ( is => 'rw', isa => 'Str', default => '' );
20             has 'adapter' => ( is => 'rw' );
21             has 'brain' => (
22             is => 'ro',
23             isa => 'Hubot::Brain',
24             default => sub { Hubot::Brain->new }
25             );
26             has '_helps' => (
27             traits => ['Array'],
28             is => 'rw',
29             isa => 'ArrayRef[Str]',
30             default => sub { [] },
31             handles => { helps => 'elements', addHelp => 'push', }
32             );
33             has '_commands' => (
34             traits => ['Array'],
35             is => 'rw',
36             isa => 'ArrayRef[Str]',
37             default => sub { [] },
38             handles => { commands => 'elements', addCommand => 'push', }
39             );
40             has '_listeners' => (
41             traits => ['Array'],
42             is => 'rw',
43             isa => 'ArrayRef[Hubot::Listener]',
44             default => sub { [] },
45             handles => { listeners => 'elements', addListener => 'push', }
46             );
47              
48             ## Ping Watcher
49             has 'pw' => ( is => 'rw' );
50             has 'httpd' => ( is => 'rw' );
51              
52             sub BUILD {
53             my $self = shift;
54              
55             $self->setupHerokuPing;
56             $self->loadAdapter( $self->adapter );
57             }
58              
59             sub setupHerokuPing {
60             my $self = shift;
61              
62             my $httpd = AnyEvent::HTTPD->new( port => $ENV{PORT} || 8080, host => $ENV{HUBOT_HTTPD_ADDRESS} || '0.0.0.0' );
63             $httpd->reg_cb(
64             '/hubot/ping' => sub {
65             my ( $httpd, $req ) = @_;
66             $req->respond( { content => ['text/plain', "pong"] } );
67             }
68             );
69              
70             $self->httpd($httpd);
71              
72             my $herokuUrl = $ENV{HEROKU_URL} || return;
73             $herokuUrl =~ s{/?$}{/hubot/ping};
74              
75             $self->pw(
76             AE::timer 0,
77             120,
78             sub {
79             AnyEvent::HTTP::ScopedClient->new($herokuUrl)
80             ->post( sub { print "Keep alive ping!\n" if $ENV{DEBUG} } );
81             }
82             );
83             }
84              
85             sub loadAdapter {
86             my ( $self, $adapter ) = @_;
87              
88             $adapter = "Hubot::Adapter::" . ucfirst($adapter);
89             eval "require $adapter; 1";
90             if ($@) {
91             print STDERR "Cannot load adapter $adapter - $@\n";
92             }
93             else {
94             $self->adapter( $adapter->new( { robot => $self } ) );
95             }
96             }
97              
98             sub run { shift->adapter->run }
99              
100             sub userForId {
101             my ( $self, $id, $options ) = @_;
102             my $user = $self->brain->{data}{users}{$id};
103             unless ($user) {
104             $user = Hubot::User->new( { id => $id, %$options } );
105             $self->brain->{data}{users}{$id} = $user;
106             }
107              
108             my $options_room = $options->{room} || '';
109             if ( $options_room
110             && ( !$user->{room} || $user->{room} ne $options_room ) )
111             {
112             $user = Hubot::User->new( { id => $id, %$options } );
113             $self->brain->{data}{users}{$id} = $user;
114             }
115              
116             return $user;
117             }
118              
119             sub userForName {
120             my ( $self, $name ) = @_;
121             my $result;
122             for my $k ( keys %{ $self->brain->{data}{users} } ) {
123             my $userName = $self->brain->{data}{users}{$k}{name};
124             if ( lc $userName eq lc $name ) {
125             $result = $self->brain->{data}{users}{$k};
126             }
127             }
128              
129             return $result;
130             }
131              
132             sub usersForFuzzyRawName {
133             my ( $self, $fuzzyName ) = @_;
134             my $lowerFuzzyName = lc $fuzzyName;
135             my @users;
136             while ( my ( $key, $user ) = each %{ $self->brain->{data}{users} || {} } )
137             {
138             if ( lc( $user->{name} ) =~ m/^$lowerFuzzyName/ ) {
139             push @users, $user;
140             }
141             }
142              
143             return @users;
144             }
145              
146             sub usersForFuzzyName {
147             my ( $self, $fuzzyName ) = @_;
148             my @matchedUsers = $self->usersForFuzzyRawName($fuzzyName);
149             my $lowerFuzzyName = lc $fuzzyName;
150             for my $user (@matchedUsers) {
151             return $user if lc( $user->{name} ) eq $lowerFuzzyName;
152             }
153              
154             return @matchedUsers;
155             }
156              
157             sub shutdown {
158             my $self = shift;
159             $self->brain->close;
160             $self->adapter->close;
161             }
162              
163             sub loadHubotScripts {
164             my ( $self, $scripts ) = @_;
165             ## TODO: Debug Message
166             # print "Loading hubot-scripts\n" if $ENV{DEBUG};
167             for my $script (@$scripts) {
168             $self->loadFile($script);
169             }
170             }
171              
172             sub loadFile {
173             my ( $self, $script ) = @_;
174             my $full = "Hubot::Scripts::$script";
175             eval "require $full; 1";
176             $full->load($self);
177             if ($@) {
178             print STDERR "Unable to load $full: $@\n";
179             }
180             else {
181             $self->parseHelp($full);
182             }
183             }
184              
185             sub parseHelp {
186             my ( $self, $module ) = @_;
187             $module =~ s{::}{/}g;
188             my $fullpath = $INC{ $module . '.pm' };
189              
190             open my $fh, '>', \my $usage or die "Couldn't open filehandle: $!\n";
191             pod2usage(
192             { -input => $fullpath, -output => $fh, -exitval => 'noexit', } );
193              
194             $usage =~ s/^Usage://;
195             $usage =~ s/(^\s+|\s+$)//gm;
196             $self->addHelp($_) for split( /\n/, $usage );
197              
198             $module =~ s{Hubot/Scripts/}{};
199             $self->addCommand($module);
200             }
201              
202             sub hear {
203             my ( $self, $regex, $callback ) = @_;
204             $self->addListener(
205             new Hubot::TextListener(
206             robot => $self,
207             regex => $regex,
208             callback => $callback
209             )
210             );
211             }
212              
213             sub respond {
214             my ( $self, $regex, $callback ) = @_;
215              
216             my $index = index "$regex", ':';
217             my $stringRegex = substr "$regex", ( $index + 1 ), -1;
218             my $first = substr $stringRegex, 0, 1;
219              
220             ## TODO: $^ 에 따른 분기; perl version 에 따라서 Regex object 의 modifier 위치가 달라짐
221             my $modifiers = '';
222             my $modifiersLen = $index - 3;
223             if ( $modifiersLen > 0 && length $stringRegex > 3 ) {
224             $modifiers = substr $stringRegex, 3, $modifiersLen;
225             }
226              
227             if ( $first eq '^' ) {
228             print STDERR
229             "Anchors don't work well with respond, perhaps you want to use 'hear'\n";
230             print STDERR "The regex in question was $stringRegex\n";
231             }
232              
233             my $newRegex;
234             my $name = $self->name;
235             if ( $self->alias ) {
236             my $alias = $self->alias;
237             $alias =~ s/[-[\]{}()\*+?.,\\^$|#\s]/\\$&/g; # escape alias for regexp
238              
239             ## TODO: fix to generate correct regex
240             ## qr/regex/$var 처럼 modifier 에 변수가 들어갈 수 없음
241             ## 일단 modifiers ê°€ 있다면 `i` 라고 가정하고 들어감 WTH..
242             if ($modifiers) {
243             $newRegex = qr/^(?:$alias[:,]?|$name[:,]?)\s*(?:$stringRegex)/i;
244             }
245             else {
246             $newRegex = qr/^(?:$alias[:,]?|$name[:,]?)\s*(?:$stringRegex)/;
247             }
248             }
249             else {
250             if ($modifiers) {
251             $newRegex = qr/^(?:$name[:,]?)\s*(?:$stringRegex)/i;
252             }
253             else {
254             $newRegex = qr/^(?:$name[:,]?)\s*(?:$stringRegex)/;
255             }
256             }
257              
258             print "$newRegex\n" if $ENV{DEBUG};
259             $self->addListener(
260             new Hubot::TextListener(
261             robot => $self,
262             regex => $newRegex,
263             callback => $callback
264             )
265             );
266             }
267              
268             sub enter {
269             my ( $self, $callback ) = @_;
270             $self->addListener(
271             Hubot::Listener->new(
272             robot => $self,
273             matcher => sub { ref(shift) eq 'Hubot::EnterMessage' ? 1 : () },
274             callback => $callback
275             )
276             );
277             }
278              
279             sub leave {
280             my ( $self, $callback ) = @_;
281             $self->addListener(
282             Hubot::Listener->new(
283             robot => $self,
284             matcher => sub { ref(shift) eq 'Hubot::LeaveMessage' ? 1 : () },
285             callback => $callback
286             )
287             );
288             }
289              
290             sub whisper {
291             my ( $self, $callback ) = @_;
292             $self->addListener(
293             Hubot::Listener->new(
294             robot => $self,
295             matcher => sub { ref(shift) eq 'Hubot::WhisperMessage' ? 1 : () },
296             callback => $callback
297             )
298             );
299             }
300              
301             sub notice {
302             my ( $self, $callback ) = @_;
303             $self->addListener(
304             Hubot::Listener->new(
305             robot => $self,
306             matcher => sub { ref(shift) eq 'Hubot::NoticeMessage' ? 1 : () },
307             callback => $callback
308             )
309             );
310             }
311              
312             sub catchAll {
313             my ( $self, $callback ) = @_;
314             $self->addListener(
315             Hubot::Listener->new(
316             robot => $self,
317             matcher => sub { ref(shift) eq 'Hubot::CatchAllMessage' ? 1 : () },
318             callback => sub {
319             my $msg = shift;
320             $msg->message( $msg->message->message );
321             $callback->($msg);
322             }
323             )
324             );
325             }
326              
327             sub receive {
328             my ( $self, $message ) = @_;
329             my $results = [];
330             for my $listener ( $self->listeners ) {
331             eval $listener->call($message);
332             last if $message->done;
333             if ($@) {
334             print STDERR "Unable to call the listener: $@\n";
335             return 0;
336             }
337             }
338              
339             $self->receive( new Hubot::CatchAllMessage( message => $message ) )
340             if ref($message) ne 'Hubot::CatchAllMessage';
341             }
342              
343             sub http { AnyEvent::HTTP::ScopedClient->new( $_[1] ) }
344              
345             __PACKAGE__->meta->make_immutable;
346              
347             1;
348              
349             =pod
350              
351             =encoding utf-8
352              
353             =head1 NAME
354              
355             Hubot::Robot
356              
357             =head1 VERSION
358              
359             version 0.2.7
360              
361             =head1 SYNOPSIS
362              
363             # Hubot::Robot has a CLI. named `hubot`
364             $ perldoc hubot
365              
366             # make sure `hubot-scripts.json` is exist in current working directory
367             use JSON::XS;
368             use Cwd 'cwd';
369             use Hubot::Robot;
370             my $robot = Hubot::Robot->new({
371             adapter => 'shell',
372             name => 'hubot'
373             });
374              
375             $robot->adapter->on(
376             'connected',
377             sub {
378             my $cwd = cwd();
379             my $scriptsFile = "$cwd/hubot-scripts.json";
380             if (-f $scriptsFile) {
381             my $json = read_file($scriptsFile);
382             my $scripts = decode_json($json);
383             $robot->loadHubotScripts($scripts);
384             }
385             }
386             );
387              
388             $robot->run;
389              
390             =head1 DESCRIPTION
391              
392             A customizable, kegerator-powered life embetterment robot.
393              
394             The original hubot description is..
395              
396             "This is a version of GitHub's Campfire bot, hubot. He's pretty cool."
397              
398             this is hubot B<Perl> port.
399              
400             =head1 SEE ALSO
401              
402             =over
403              
404             =item L<http://hubot.github.com/>
405              
406             =item L<https://github.com/github/hubot>
407              
408             =item L<hubot>
409              
410             =back
411              
412             =head1 AUTHOR
413              
414             Hyungsuk Hong <hshong@perl.kr>
415              
416             =head1 COPYRIGHT AND LICENSE
417              
418             This software is copyright (c) 2012 by Hyungsuk Hong.
419              
420             This is free software; you can redistribute it and/or modify it under
421             the same terms as the Perl 5 programming language system itself.
422              
423             =cut