File Coverage

blib/lib/WebService/DailyConnect.pm
Criterion Covered Total %
statement 17 76 22.3
branch 0 30 0.0
condition 0 9 0.0
subroutine 6 17 35.2
pod 7 7 100.0
total 30 139 21.5


line stmt bran cond sub pod time code
1             package WebService::DailyConnect;
2              
3 2     2   199141 use strict;
  2         8  
  2         66  
4 2     2   10 use warnings;
  2         4  
  2         75  
5 2     2   47 use 5.010;
  2         7  
6 2     2   1108 use Moose;
  2         805824  
  2         13  
7 2     2   13738 use URI;
  2         8100  
  2         51  
8 2     2   12 use Carp ();
  2         4  
  2         1972  
9              
10             # ABSTRACT: Web client to download events from Daily Connect
11             our $VERSION = '0.03'; # VERSION
12              
13              
14             has ua => (
15             is => 'ro',
16             isa => 'HTTP::AnyUA',
17             lazy => 1,
18             default => sub {
19             require HTTP::AnyUA;
20             require LWP::UserAgent;
21             my $ua = LWP::UserAgent->new(
22             cookie_jar => {},
23             );
24             $ua->env_proxy;
25             HTTP::AnyUA->new(
26             ua => $ua,
27             );
28             },
29             );
30              
31              
32             has base_url => (
33             is => 'ro',
34             isa => 'URI',
35             lazy => 1,
36             default => sub {
37             URI->new('https://www.dailyconnect.com/');
38             },
39             );
40              
41              
42             has req => (
43             is => 'rw',
44             );
45              
46              
47             has res => (
48             is => 'rw',
49             );
50              
51             around BUILDARGS => sub {
52             my($orig, $class, %args) = @_;
53              
54             if(defined $args{ua} && ! eval { $args{ua}->isa('HTTP::AnyUA') })
55             {
56             require HTTP::AnyUA;
57             HTTP::AnyUA->new(ua => $args{ua});
58             }
59              
60             return $class->$orig(%args);
61             };
62              
63             sub _url
64             {
65 0     0     my($self, $path) = @_;
66 0           URI->new_abs($path, $self->base_url);
67             }
68              
69              
70             sub login
71             {
72 0     0 1   my($self, $email, $pass) = @_;
73 0 0 0       Carp::croak("Usage: \$dc->login(\$email, \$pass)") unless $email && $pass;
74 0           my $res = $self->_post('Cmd?cmd=UserAuth', { email => $email, pass => $pass });
75 0           $res->{status} == 302;
76             }
77              
78             sub _post
79             {
80 0     0     my($self, $path, $form) = @_;
81 0           my $url = $self->_url($path)->as_string;
82 0           my $req = [ $url, $form ];
83 0           $self->req([ 'POST', @$req ]);
84 0           my $res = $self->ua->post_form(@$req);
85 0           $self->res($res);
86             }
87              
88             sub _json
89             {
90 0     0     my(undef, $json) = @_;
91 0           require JSON::MaybeXS;
92 0           JSON::MaybeXS::decode_json($json);
93             }
94              
95             sub _today
96             {
97 0     0     my($year,$month,$day) = (localtime(time))[5,4,3];
98 0           $month++;
99 0           $year = $year + 1900 - 2000;
100 0           join('', map { sprintf "%02d", $_ } $year, $month, $day);
  0            
101             }
102              
103              
104             sub user_info
105             {
106 0     0 1   my($self) = @_;
107 0           my $res = $self->_post('CmdW', { cmd => 'UserInfoW'});
108 0 0         $res->{status} == 200 ? $self->_json($res->{content}) : ();
109             }
110              
111              
112             sub kid_summary
113             {
114 0     0 1   my($self, $kid_id) = @_;
115 0 0         Carp::croak("Usage: \$dc->kid_summary_by_date(\$kid_id)") unless $kid_id;
116 0           $self->kid_summary_by_date($kid_id, $self->_today);
117             }
118              
119              
120             sub kid_summary_by_date
121             {
122             #date: yymmdd
123 0     0 1   my($self, $kid_id, $date) = @_;
124 0 0 0       Carp::croak("Usage: \$dc->kid_summary_by_date(\$kid_id, \$date)") unless $kid_id && $date;
125 0           my $res = $self->_post('CmdW', { cmd => 'KidGetSummary', Kid => $kid_id, pdt => $date });
126 0 0         $res->{status} == 200 ? $self->_json($res->{content}) : ();
127             }
128              
129              
130             sub kid_status
131             {
132 0     0 1   my($self, $kid_id) = @_;
133 0 0         Carp::croak("Usage: \$dc->kid_status_by_date(\$kid_id)") unless $kid_id;
134 0           $self->kid_status_by_date($kid_id, $self->_today);
135             }
136              
137              
138             sub kid_status_by_date
139             {
140             #date: yymmdd
141 0     0 1   my($self, $kid_id, $date) = @_;
142 0 0 0       Carp::croak("Usage: \$dc->kid_status_by_date(\$kid_id, \$date)") unless $kid_id && $date;
143 0           my $res = $self->_post('CmdListW', { cmd => 'StatusList', Kid => $kid_id, pdt => $date, fmt => 'long' });
144 0 0         $res->{status} == 200 ? $self->_json($res->{content}) : ();
145             }
146              
147              
148             sub photo
149             {
150 0     0 1   my($self, $photo_id, $dest) = @_;
151 0 0         Carp::croak("Usage: \$dc->photo(\$photo_id)") unless $photo_id;
152 0           my $url = $self->_url('GetCmd')->clone;
153 0           $url->query_form(cmd => 'PhotoGet', id => $photo_id, thumb => 0);
154 0           $url = $url->as_string;
155 0           $self->req([ 'GET', $url ]);
156 0           my $res = $self->ua->get($url);
157 0           $self->res($res);
158 0 0         return unless $res->{headers}->{'content-type'} eq 'image/jpg';
159 0 0         if(defined $dest)
160             {
161 0 0         if(ref($dest))
162             {
163 0 0         if(eval { $dest->isa('Path::Tiny') })
  0 0          
    0          
164             {
165 0           $dest->spew_raw($res->{content});
166             }
167 0           elsif(eval { $dest->isa('Path::Class::File') })
168             {
169 0           $dest->spew(iomode => '>:raw', $res->{content});
170             }
171             elsif(ref($dest) eq 'SCALAR')
172             {
173 0           $$dest = $res->{content};
174             }
175             else
176             {
177 0           Carp::croak("unknown destination type. Must be one of: scalar, reference to scalar, Path::Tiny, Path::Class::File");
178             }
179             }
180             else
181             {
182 0           require Path::Tiny;
183 0           Path::Tiny->new($dest)->spew_raw($res->{content});
184             }
185 0           1;
186             }
187             else
188             {
189 0           return $res->{content};
190             }
191             }
192              
193              
194             1;
195              
196             __END__
197              
198             =pod
199              
200             =encoding UTF-8
201              
202             =head1 NAME
203              
204             WebService::DailyConnect - Web client to download events from Daily Connect
205              
206             =head1 VERSION
207              
208             version 0.03
209              
210             =head1 SYNOPSIS
211              
212             use WebService::DailyConnect;
213             use Term::Clui qw( ask ask_password );
214             use Path::Tiny qw( path );
215            
216             my $user = ask("email:");
217             my $pass = ask_password("pass :");
218            
219             my $dc = WebService::DailyConnect->new;
220             $dc->login($user, $pass) || die "bad email/pass";
221            
222             my $user_info = $dc->user_info;
223            
224             foreach my $kid (@{ $dc->user_info->{myKids} })
225             {
226             my $kid_id = $kid->{Id};
227             my $name = lc $kid->{Name};
228             foreach my $photo_id (map { $_->{Photo} || () } @{ $dc->kid_status($kid_id)->{list} })
229             {
230             my $dest = path("~/Pictures/dc/$name-$photo_id.jpg");
231             next if -f $dest;
232             print "new photo: $dest\n";
233             $dest->parent->mkpath;
234             $dc->photo($photo_id, $dest);
235             }
236             }
237              
238             =head1 DESCRIPTION
239              
240             B<NOTE>: I no longer use DailyConnect, and happy to let someone who does need it
241             maintain it. This module is otherwise unsupported.
242              
243             Interface to DailyConnect, which is a service that can provide information about
244             your kids at daycare. This is more or less a port of a node API that I found here:
245              
246             L<https://github.com/Flet/dailyconnect>
247              
248             I wrote this module for more or less the same reasons as that author, although I
249             wanted to be able to use it in perl.
250              
251             It uses L<HTTP::AnyUA>, so should work with any Perl user agent supported by that
252             layer.
253              
254             =head1 ATTRIBUTES
255              
256             =head2 ua
257              
258             An instance of L<HTTP::AnyUA>. If a user agent supported by L<HTTP::AnyUA>
259             (such as L<HTTP::Tiny> or L<LWP::UserAgent>) is provided, a new instance of
260             L<HTTP::AnyUA> will wrap around that user agent instance. The only requirement
261             is that the underlying user agent must support cookies.
262              
263             If a C<ua> attribute is not provided, then an instance of L<HTTP::AnyUA> will
264             be created wrapped around a L<LWP::UserAgent> using the default proxy and a
265             cookie jar.
266              
267             =head2 base_url
268              
269             The base URL for daily connect. The default should be correct.
270              
271             =head2 req
272              
273             The most recent request. The format of the request object is subject to change, and therefore should only be used for debugging.
274              
275             =head2 res
276              
277             The most recent response. The format of the response object is subject to change, and therefore should only be used for debugging.
278              
279             =head2 METHODS
280              
281             Beside login, methods typically return a hash or file content depending on the type of object requested.
282             On error they return C<undef>. Further details for the failure can be deduced from the response object
283             stored in C<res> above.
284              
285             =head2 login
286              
287             my $bool = $dc->login($email, $pass);
288              
289             Login to daily connect using the given email and password. The remaining methods only work once you have successfully logged in.
290              
291             =head2 user_info
292              
293             my $hash = $dc->user_info;
294              
295             Get a hash of the user information.
296              
297             =head2 kid_summary
298              
299             my $hash = $dc->kid_summary($kid_id);
300              
301             Get today's summary for the given kid.
302              
303             =head2 kid_summary_by_date
304              
305             my $hash = $dc->kid_summary_by_date($kid_id, $date);
306              
307             Get the summary for the given kid on the given day. C<$date> is in the form YYMMDD.
308              
309             =head2 kid_status
310              
311             my $hash = $dc->kid_status($kid_id);
312              
313             Get today's status for the given kid.
314              
315             =head2 kid_status_by_date
316              
317             my $hash = $dc->kid_status_by_date($kid_id, $date);
318              
319             Get the status for the given kid on the given date. C<$date> is in the form YYMMDD.
320              
321             =head2 photo
322              
323             $dc->photo($photo_id);
324             $dc->photo($photo_id, $dest);
325              
326             Get the photo with the given C<$photo_id>. If C<$dest> is not provided then the content of the photo in
327             JPEG format will be returned. If C<$dest> is a scalar reference, then the content will be stored in that
328             scalar. If C<$dest> is a string, then that string will be assumed to be a file, and the photo will be saved
329             there. If a L<Path::Tiny> or L<Path::Class::File> object are passed in, then the content will be written
330             to the files at that location.
331              
332             =head1 CAVEATS
333              
334             DailyConnect does not provide a standard RESTful API, so it is entirely possible
335             they might change the interface of their app, and break this module.
336              
337             My kiddo is an only child so I haven't been able to test this for more than one
338             kiddo. May be a problem if you have twins or septuplets.
339              
340             =head1 AUTHOR
341              
342             Graham Ollis <plicease@cpan.org>
343              
344             =head1 COPYRIGHT AND LICENSE
345              
346             This software is copyright (c) 2018,2019 by Graham Ollis.
347              
348             This is free software; you can redistribute it and/or modify it under
349             the same terms as the Perl 5 programming language system itself.
350              
351             =cut