File Coverage

blib/lib/POE/Component/Client/Lingr.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package POE::Component::Client::Lingr;
2              
3 1     1   7 use strict;
  1         1  
  1         47  
4             our $VERSION = '0.04';
5              
6 1     1   466 use Data::Visitor::Callback;
  0            
  0            
7             use HTTP::Request::Common;
8             use JSON::Syck;
9             use POE qw( Component::Client::HTTP );
10             use URI;
11              
12             our $APIBase = "http://www.lingr.com/api";
13             our $Debug = 0;
14              
15             # scraped from Lingr wiki page
16             our $Methods = {
17             'session.create' => 'POST',
18             'session.destroy' => 'POST',
19             'auth.login' => 'POST',
20             'auth.logout' => 'POST',
21             'explore.getHotRooms' => 'GET',
22             'explore.getNewRooms' => 'GET',
23             'explore.getHotTags' => 'GET',
24             'explore.getAllTags' => 'GET',
25             'explore.search' => 'GET',
26             'explore.searchTags' => 'GET',
27             'user.getInfo' => 'GET',
28             'user.startObserving' => 'POST',
29             'user.observe' => 'GET',
30             'user.stopObserving' => 'POST',
31             'room.getInfo' => 'GET',
32             'room.enter' => 'POST',
33             'room.getMessages' => 'GET',
34             'room.observe' => 'GET',
35             'room.setNickname' => 'POST',
36             'room.say' => 'POST',
37             'room.exit' => 'POST',
38             };
39              
40             sub spawn {
41             my($class, %args) = @_;
42              
43             my $self = bless {}, $class;
44              
45             $self->{session_id} = POE::Session->create(
46             object_states => [
47             $self => {
48             _start => '_start',
49             _stop => '_stop',
50             _unregister => '_unregister',
51              
52             # API
53             register => 'register',
54             unregister => 'unregister',
55             notify => 'notify',
56             call => 'call',
57             http_response => 'http_response',
58             },
59             ],
60             args => [ \%args ],
61             )->ID;
62              
63             POE::Component::Client::HTTP->spawn(
64             Agent => "POE::Component::Client::Lingr/$VERSION",
65             Alias => $self->ua_alias,
66             );
67              
68             $self;
69             }
70              
71             sub ua_alias {
72             my $self = shift;
73             return "lingr_ua_" . $self->session_id;
74             }
75              
76             sub session_id { $_[0]->{session_id} }
77              
78             sub yield {
79             my $self = shift;
80             $poe_kernel->post($self->session_id, @_);
81             }
82              
83             sub _start {
84             my($kernel, $heap, $args) = @_[KERNEL, HEAP, ARG0];
85             $kernel->alias_set($args->{alias}) if $args->{alias};
86             }
87              
88             sub _stop { }
89              
90             sub register {
91             my($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
92             $kernel->refcount_increment($sender->ID, __PACKAGE__);
93             $heap->{listeners}->{$sender->ID} = 1;
94             }
95              
96             sub unregister {
97             my($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
98             $kernel->yield(_unregister => $sender->ID);
99             }
100              
101             sub _unregister {
102             my($kernel, $heap, $session) = @_[KERNEL, HEAP, ARG0];
103             $kernel->refcount_decrement($session, __PACKAGE__);
104             delete $heap->{listeners}->{$session};
105             }
106              
107             sub notify {
108             my($kernel, $heap, $name, $args) = @_[KERNEL, HEAP, ARG0, ARG1];
109             $kernel->post($_ => "lingr.$name" => $args) for keys %{$heap->{listeners}};
110             }
111              
112             sub call {
113             my($kernel, $heap, $method, $args, $self) = @_[KERNEL, HEAP, ARG0, ARG1, OBJECT];
114              
115             my $req = create_request($heap, $method, $args);
116             $kernel->post($self->ua_alias => request => 'http_response', $req);
117             }
118              
119             sub http_response {
120             my($kernel, $heap, $session, $request_packet, $response_packet) = @_[KERNEL, HEAP, SESSION, ARG0, ARG1];
121              
122             my $request = $request_packet->[0];
123             my $response = $response_packet->[0];
124              
125             my $data = handle_response($kernel, $request, $response) or return;
126             my $method = uri_to_method($request->uri);
127              
128             # special-case some methods
129             if ($method eq 'session.create') {
130             $heap->{session} = $data->{session};
131             } elsif ($method eq 'room.enter') {
132             # create session for room.observe
133             POE::Session->create(
134             inline_states => {
135             _start => \&observer_start,
136             _stop => \&observer_stop,
137             response => \&observer_response,
138             observe => \&observer_observe,
139             notify => \&observer_notify,
140             },
141             heap => {
142             session => $heap->{session},
143             ticket => $data->{ticket},
144             counter => $data->{room}->{counter},
145             parent => $session->ID,
146             },
147             );
148             }
149              
150             if ($data->{ticket}) {
151             $heap->{ticket} = $data->{ticket};
152             }
153              
154             $kernel->yield(notify => $method, $data);
155             }
156              
157             sub observer_start {
158             my($kernel, $heap) = @_[KERNEL, HEAP];
159             $kernel->alias_set("observer_$heap->{ticket}");
160              
161             POE::Component::Client::HTTP->spawn(
162             Agent => "POE::Component::Client::Lingr/$VERSION",
163             Alias => "lingr_observer_$heap->{ticket}",
164             );
165              
166             $kernel->yield('observe');
167             }
168              
169             sub observer_observe {
170             my($kernel, $heap) = @_[KERNEL, HEAP];
171              
172             my $req = create_request($heap, 'room.observe', {
173             ticket => $heap->{ticket},
174             counter => $heap->{counter},
175             });
176              
177             $kernel->post("lingr_observer_$heap->{ticket}", request => 'response', $req);
178             }
179              
180             sub observer_notify {
181             my($kernel, $heap, $name, $args) = @_[KERNEL, HEAP, ARG0, ARG1];
182             $kernel->post($heap->{parent}, 'notify', $name, $args);
183             }
184              
185             sub observer_response {
186             my($kernel, $heap, $request_packet, $response_packet) = @_[KERNEL, HEAP, ARG0, ARG1];
187              
188             my $request = $request_packet->[0];
189             my $response = $response_packet->[0];
190              
191             my $data = handle_response($kernel, $request, $response) or return;
192             $kernel->post($heap->{parent}, 'notify', 'room.observe', $data);
193              
194             $heap->{counter} = $data->{counter};
195             $kernel->yield('observe');
196             }
197              
198             ### Utility functions
199              
200             sub handle_response {
201             my($kernel, $request, $response) = @_;
202              
203             unless ($response->is_success) {
204             $kernel->yield(notify => "error.http" => { code => $response->status_line });
205             return;
206             }
207              
208             warn $response->content if $Debug;
209              
210             local $JSON::Syck::ImplicitUnicode = 1;
211             my $data = JSON::Syck::Load($response->content);
212             unless ($data->{status} eq 'ok'){
213             $kernel->yield(notify => "error.response" => $data->{error});
214             return;
215             }
216              
217             return $data;
218             }
219              
220             sub create_request {
221             my($heap, $method, $args) = @_;
222              
223             my @method = map { s/([A-Z])/"_".lc($1)/eg; $_ } split /\./, $method;
224             my $uri = URI->new($APIBase . "/" . join("/", @method));
225              
226             # downgrade all parameters to utf-8, if they're Unicode
227             my $v = Data::Visitor::Callback->new(
228             plain_value => sub {
229             if (utf8::is_utf8($_)) {
230             utf8::encode($_);
231             }
232             },
233             ignore_return_values => 1,
234             );
235              
236             $v->visit($args);
237              
238             my $req_method = $Methods->{$method} || do {
239             Carp::carp "Don't know method '$method'. Defaults to GET";
240             "GET";
241             };
242              
243             $args->{format} = 'json';
244              
245             if ($method =~ /^room\./ && $heap->{ticket}) {
246             $args->{ticket} = $heap->{ticket};
247             }
248              
249             if ($heap->{session}) {
250             $args->{session} = $heap->{session};
251             }
252              
253             my $req;
254             if ($req_method eq 'GET') {
255             $uri->query_form(%$args);
256             $req = HTTP::Request->new(GET => $uri);
257             } else {
258             $req = HTTP::Request::Common::POST( $uri, [ %$args ] );
259             }
260              
261             use Data::Dumper;
262             warn Dumper $req if $Debug;
263              
264             return $req;
265             }
266              
267             sub uri_to_method {
268             my $uri = shift;
269             $uri =~ s/^\Q$APIBase\E//;
270             $uri =~ s/\?.*$//;
271             my @method = grep length, map { s/_(\w)/uc($1)/eg; $_ } split '/', $uri;
272             return join ".", @method;
273             }
274              
275             1;
276             __END__
277              
278             =for stopwords Lingr API com lingr.com
279              
280             =head1 NAME
281              
282             POE::Component::Client::Lingr - POE chat component for Lingr.com
283              
284             =head1 SYNOPSIS
285              
286             use POE qw(Component::Client::Lingr);
287              
288             # See eg/bot.pl for sample client code
289              
290             =head1 DESCRIPTION
291              
292             POE::Component::Client::Lingr is a POE component for Lingr API. See
293             L<http://wiki.lingr.com/dev/show/HomePage> for more details about Lingr API.
294              
295             This module is in its B<beta quality> and the API and implementation will be
296             likely changed along with the further development.
297              
298             =head1 AUTHOR
299              
300             Tatsuhiko Miyagawa E<lt>miyagawa@bulknews.netE<gt>
301              
302             =head1 LICENSE
303              
304             This library is free software; you can redistribute it and/or modify
305             it under the same terms as Perl itself.
306              
307             =head1 SEE ALSO
308              
309             L<POE>, L<http://wiki.lingr.com/dev/show/HomePage>
310              
311             =cut