File Coverage

blib/lib/WWW/KlickTel/API.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             package WWW::KlickTel::API;
2              
3 1     1   19143 use 5.008001; # perl 5.8.1
  1         4  
  1         69  
4 1     1   6 use strict;
  1         1  
  1         41  
5 1     1   4 use warnings FATAL => 'all';
  1         1  
  1         42  
6 1     1   5 use Carp qw(croak carp);
  1         2  
  1         59  
7 1     1   5 use feature 'say';
  1         2  
  1         82  
8              
9 1     1   843 use REST::Client;
  1         55795  
  1         28  
10 1     1   1024 use JSON::XS;
  1         6516  
  1         77  
11 1     1   1527 use DB_File;
  0            
  0            
12             use Fcntl;
13              
14             =head1 NAME
15              
16             WWW::KlickTel::API - A module to use openapi.klicktel.de (Linux only)
17              
18             =head1 VERSION
19              
20             Version $Revision: 34 $
21              
22             $Id: API.pm 34 2013-03-14 14:51:02Z sysdef $
23              
24             =cut
25              
26             our ($VERSION) = ( q$Revision: 34 $ =~ /(\d+)/ );
27              
28             =head1 SYNOPSIS
29              
30             This module provides a basic access to the KlickTel API
31             http://openapi.klicktel.de
32              
33             NOTE: This POC version supports reverse lookups only.
34              
35             Get an API key at http://openapi.klicktel.de/login/register
36              
37             #!/usr/bin/perl
38             use strict;
39             use warnings;
40             use WWW::KlickTel::API;
41              
42             my $klicktel = WWW::KlickTel::API->new(
43             api_key => '1234567890123456789013456789012',
44             );
45              
46             # -OR-
47             # create a key file at ~/.klicktel/api_key.txt and run
48              
49             my $klicktel = WWW::KlickTel::API->new();
50              
51             =cut
52              
53             # --- GLOBAL VARIABLES ---
54             my %cache_invers = ();
55              
56             # system username
57             my $username = $ENV{LOGNAME} || $ENV{USER} || getpwuid($<);
58              
59             =head1 METHODS
60              
61             =head2 new
62              
63             Create the object. All parameter are optional.
64              
65             my $klicktel = WWW::KlickTel::API->new(
66             api_key => '01234567890abcdef01234567890abcd',
67             protocol => 'https', # or 'http' (http is default)
68             cache_path => '/var/cache/www-klicktel-api/',
69             uri_invers => 'openapi.klicktel.de/searchapi/invers',
70             timeout => 10, # ( 1- 600 seconds)
71             ca_file => '/path/to/ca.file',
72             client_auth => {
73             'cert' => '/path/to/ssl.crt',
74             'key' => '/path/to/ssl.key',
75             },
76             proxy_url => 'http://proxy.example.com',
77             );
78              
79             =cut
80              
81             sub new {
82             my $class = shift;
83             croak 'Odd number of elements passed when even was expected' if @_ % 2;
84             my %args = @_;
85              
86             my $self = {
87             PROTOCOL => $args{protocol}
88             || 'http',
89             CACHE_PATH => $args{'cache_path'}
90             || '/var/cache/www-klicktel-api/',
91             URI_INVERS => $args{'uri_invers'}
92             || 'openapi.klicktel.de/searchapi/invers',
93             REST_TIMEOUT => $args{'timeout'}
94             || 10,
95             REST_CA_FILE => $args{'ca_file'}
96             || q{},
97             CLIENT_CERT => $args{'client_auth'}{'cert'}
98             || q{},
99             CLIENT_KEY => $args{'client_auth'}{'key'}
100             || q{},
101             PROXY_URL => $args{'proxy_url'}
102             || q{},
103             };
104              
105             $self->{API_KEY} = $args{'api_key'};
106             if ( !$self->{API_KEY} ) {
107              
108             # checking for user's API Key
109             $self->{API_KEY_FILE} = '/home/' . $username . '/.klicktel/api_key.txt';
110             if ( -r $self->{API_KEY_FILE} ) {
111              
112             # loading user's api key
113             my $api_key_fh;
114             open $api_key_fh, "<", $self->{API_KEY_FILE};
115             binmode $api_key_fh;
116             read $api_key_fh, $self->{API_KEY}, 32;
117             }
118             else {
119             say 'Hint: You can save your API Key at ' . $self->{API_KEY_FILE};
120             die('FATAL ERROR: No API Key was given.');
121             }
122             }
123              
124             $self->{CACHE_FILE_INVERS} = $self->{CACHE_PATH} . $username . '.invers.dat';
125              
126             # invers phone number cache
127             if ( ref $cache_invers{$self->{CACHE_PATH}} ne 'HASH' ) {
128             tie %{$cache_invers{$self->{CACHE_PATH}}}, 'DB_File',
129             $self->{CACHE_FILE_INVERS}, O_CREAT | O_RDWR, 0666
130             or die "Can't initialize DB_File file ("
131             . $self->{CACHE_FILE_INVERS}
132             . " ): $!\n";
133             }
134              
135             bless $self, $class;
136              
137             return $self;
138             }
139              
140             =head2 test
141              
142             Run selftest
143              
144             # run selftest
145             my $error_count;
146             $error_count = $klicktel->test();
147             print 'Module test: ' . ( $error_count ? "FAILED. $error_count error(s)\n" : "OK\n" );
148              
149             =cut
150              
151             sub test {
152             my ( $self, $number ) = @_;
153             my $error_count = 0;
154              
155             eval "use Test::Simple tests => 6; 1";
156              
157             ok(
158             ( defined $self->{API_KEY}
159             and $self->{API_KEY} =~ m/^[0-9a-f]{32}\z/ ) == 1,
160             'API Key format'
161             ) or $error_count++;
162              
163             ok( ( $self->{REST_TIMEOUT} gt 0 ) and ( $self->{REST_TIMEOUT} lt 600 ),
164             'Network Timeout 1-600 seconds' )
165             or $error_count++;
166              
167             ok( -W $self->{CACHE_PATH}, 'writable cachedir (' . $self->{CACHE_PATH} . ')' )
168             or $error_count++;
169              
170             ok( -W $self->{CACHE_FILE_INVERS}, 'writable invers cache' )
171             or $error_count++;
172              
173             $cache_invers{$self->{CACHE_PATH}}{'test'} = 'test ok';
174             ok( $cache_invers{$self->{CACHE_PATH}}{'test'}
175             eq 'test ok', 'phone number cache connected' )
176             or $error_count++;
177             delete $cache_invers{$self->{CACHE_PATH}}{'test'};
178              
179             delete $cache_invers{$self->{CACHE_PATH}}{'110'};
180             my $result_hash_ref = invers($self, '110');
181             if ( ref $result_hash_ref->{'response'}{'error'} eq 'HASH' ) {
182             warn( "API ERROR MESSAGE: "
183             . $result_hash_ref->{'response'}{'error'}{'message'} );
184             }
185             ok(
186             eval {
187             $result_hash_ref->{'response'}{'results'}[0]{'total'} gt 5000;
188             },
189             'more than 5000 hits for "Notruf" in reverse lookup for number "110"'
190             ) or $error_count++;
191              
192             return $error_count;
193             }
194              
195             =head2 invers
196              
197             Do reverse lookups of phone numbers
198              
199             # reverse lookup phone numbers
200             use Data::Dumper qw(Dumper);
201             my $result = $klicktel->invers($phone_number);
202             print Dumper($result);
203              
204             =cut
205              
206             sub invers {
207             my ( $self, $number ) = @_;
208              
209             my $result = ();
210              
211             if ( $cache_invers{$self->{CACHE_PATH}}{$number} ) {
212              
213             # number is cached
214              
215             my $result_json = $cache_invers{$self->{CACHE_PATH}}{$number};
216             $result = decode_json $result_json;
217             }
218             else {
219              
220             # number is not cached
221              
222             # create and configure REST API connection
223             my $rest_connect = _REST_connect();
224              
225             # get data
226             $rest_connect->GET(
227             $self->{PROTOCOL} . '://'
228             . $self->{URI_INVERS} . '?' . 'key='
229             . $self->{API_KEY}
230             . '&number='
231             . $number
232             . '&parents_only=1'
233             );
234              
235             my $result_json = $rest_connect->responseContent();
236              
237             # save result
238             $cache_invers{$self->{CACHE_PATH}}{$number} = $result_json;
239              
240             # decode json construct to hash
241             $result = decode_json $result_json;
242              
243             undef $rest_connect;
244              
245             }
246              
247             return $result;
248             }
249              
250             =head1 SUBROUTINES (for internal use only)
251              
252             =head2 _REST_connect
253              
254             Create and configure REST API connection
255              
256             _REST_connect();
257              
258             =cut
259              
260             sub _REST_connect {
261             my $self = shift;
262              
263             # create object
264             my $rest_connect = REST::Client->new();
265              
266             # proxy support
267             if ($self->{PROXY_URL}) {
268             $rest_connect->getUseragent()->proxy( ['http'], $self->{PROXY_URL} );
269             }
270              
271             # X509 client authentication
272             if ( $self->{CLIENT_CERT} and $self->{CLIENT_KEY} ) {
273             $rest_connect->setCert($self->{CLIENT_CERT});
274             $rest_connect->setKey($self->{CLIENT_KEY});
275             }
276              
277             # add a CA to verify server certificates
278             if ($self->{REST_CA_FILE}) {
279             $rest_connect->setCa($self->{REST_CA_FILE});
280             }
281              
282             # timeout on requests, in seconds
283             $rest_connect->setTimeout($self->{REST_TIMEOUT});
284              
285             return $rest_connect;
286             }
287              
288             sub DESTROY {
289             my $self = shift;
290              
291             untie %{$cache_invers{$self->{CACHE_PATH}}}
292             or die "cannot untie inverse cache in " . $self->{CACHE_PATH};
293              
294             return;
295             }
296              
297             =head1 AUTHOR
298              
299             Juergen Heine, C<< < sysdef AT cpan D0T org > >>
300              
301             =head1 BUGS
302              
303             Please report any bugs or feature requests to C, or through
304             the web interface at L. I will be notified, and then you'll
305             automatically be notified of progress on your bug as I make changes.
306              
307             =head1 SUPPORT
308              
309             You can find documentation for this module with the perldoc command.
310              
311             perldoc WWW::KlickTel::API
312              
313             You can also look for information at:
314              
315             =over 4
316              
317             =item * RT: CPAN's request tracker (report bugs here)
318              
319             L
320              
321             =item * AnnoCPAN: Annotated CPAN documentation
322              
323             L
324              
325             =item * CPAN Ratings
326              
327             L
328              
329             =item * Search CPAN
330              
331             L
332              
333             =back
334              
335             =head1 LICENSE AND COPYRIGHT
336              
337             Copyright 2013 Juergen Heine ( sysdef AT cpan D0T org ).
338              
339             This program is free software; you can redistribute it and/or modify it
340             under the terms of the the Artistic License (2.0). You may obtain a
341             copy of the full license at:
342              
343             L
344              
345             Any use, modification, and distribution of the Standard or Modified
346             Versions is governed by this Artistic License. By using, modifying or
347             distributing the Package, you accept this license. Do not use, modify,
348             or distribute the Package, if you do not accept this license.
349              
350             If your Modified Version has been derived from a Modified Version made
351             by someone other than you, you are nevertheless required to ensure that
352             your Modified Version complies with the requirements of this license.
353              
354             This license does not grant you the right to use any trademark, service
355             mark, tradename, or logo of the Copyright Holder.
356              
357             This license includes the non-exclusive, worldwide, free-of-charge
358             patent license to make, have made, use, offer to sell, sell, import and
359             otherwise transfer the Package with respect to any patent claims
360             licensable by the Copyright Holder that are necessarily infringed by the
361             Package. If you institute patent litigation (including a cross-claim or
362             counterclaim) against any party alleging that the Package constitutes
363             direct or contributory patent infringement, then this Artistic License
364             to you shall terminate on the date that such litigation is filed.
365              
366             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
367             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
368             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
369             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
370             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
371             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
372             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
373             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
374              
375              
376             =cut
377              
378             1;