File Coverage

blib/lib/WWW/Geni.pm
Criterion Covered Total %
statement 27 282 9.5
branch 0 62 0.0
condition 0 35 0.0
subroutine 9 65 13.8
pod 3 4 75.0
total 39 448 8.7


line stmt bran cond sub pod time code
1 1     1   50327 use 5.001;
  1         4  
  1         37  
2 1     1   5 use strict;
  1         2  
  1         32  
3 1     1   4 use warnings;
  1         6  
  1         27  
4 1     1   1968 use HTTP::Cookies;
  1         26313  
  1         30  
5 1     1   15199 use HTTP::Response;
  1         124759  
  1         40  
6 1     1   1129 use LWP::UserAgent;
  1         28164  
  1         32  
7 1     1   4206 use JSON;
  1         26295  
  1         6  
8 1     1   1275 use utf8;
  1         11  
  1         7  
9 1     1   34 use vars qw($VERSION $errstr);
  1         2  
  1         15487  
10              
11             binmode STDOUT, ":utf8";
12             binmode STDERR, ":utf8";
13              
14             ##############################################################################
15             # WWW::Geni class
16             ##############################################################################
17             {
18             package WWW::Geni;
19             our $VERSION = '0.3.0';
20             our $geni;
21             # Profile APIs
22             # Returns a data structure containing the immediate family of the requested
23             # profile.
24             sub new {
25 0     0 1   my $class = shift;
26 0           my $self = { @_ };
27 0           ($self->{user}, $self->{pass}) = (shift, shift);
28 0           $self->{json} = new JSON;
29 0 0 0       if (!$self->{user} || !$self->{pass}){
30 0           $WWW::Geni::errstr = "Username and password are required parameters to "
31             . "WWW::Geni::new().";
32 0           return 0;
33             } else {
34 0           bless $self, $class;
35 0 0         $self->login() or $WWW::Geni::errstr = "Login failed!" && return 0;
36 0           $WWW::Geni::geni = $self;
37 0           return $self;
38             }
39             }
40              
41             sub user {
42 0     0 1   my $self = shift;
43 0           return $self->{user};
44             }
45              
46             sub login {
47 0     0 0   my $self = shift;
48 0           $self->{ua} = LWP::UserAgent->new();
49 0           $self->{ua}->cookie_jar(HTTP::Cookies->new());
50 0           my $res = $self->{ua}->post("https://www.geni.com/login/in?username="
51             . $self->{user} . "&password=" . $self->{pass});
52 0           return $res->content =~ /home">redirected/;
53             }
54              
55             sub tree_conflicts() {
56 0     0 1   my $self = shift;
57 0           my $list = WWW::Geni::List->new();
58 0 0         $self->_populate_tree_conflicts($list) or
59             $WWW::Geni::errstr = "Attempt to [re]populate tree conflict list failed." && return 0;
60 0           return $list;
61             }
62              
63             # Returns a data structure containing the immediate family of the requested profile.
64             sub _profile_get_immediate_family_url($) {
65 0     0     my ($self, $profile) = (shift, shift);
66 0 0         $profile = $profile ? $profile : "profile";
67 0           "https://www.geni.com/api/$profile/immediate-family?only_ids=true";
68             }
69             # Returns a list of requested profile merges for the current user.
70             sub _profile_get_merges_url {
71 0     0     my $self = shift;
72 0 0         "https://www.geni.com/api/profile/merges?only_ids=true"
73             . ($self->{collaborators} ? "&collaborators=true" : "");
74             }
75             # Returns a list of data conflicts for the current user.
76             sub _profile_get_data_conflicts_url {
77 0     0     my $self = shift;
78 0 0         "https://www.geni.com/api/profile/data-conflicts?only_ids=true"
79             . ($self->{collaborators} ? "&collaborators=true" : "");
80             }
81             # Returns a list of tree conflicts for the current user.
82             sub _profile_get_tree_conflicts_url($) {
83 0     0     my $self = shift;
84 0 0 0       "https://www.geni.com/api/profile/tree-conflicts?only_ids=true"
85             . ($self->{collaborators} ? "&collaborators=true" : "") . "&page=" . (shift or '1');
86             }
87             # Returns a list of other profiles in our system matching a given profile.
88             # Only users who have upgraded to a Geni Pro Account can see this list.
89             sub _profile_get_tree_matches_url($) {
90 0     0     "https://www.geni.com/api/$_[1]/tree-matches?only_ids=true";
91             }
92             # Will merge two profiles together if you have permission, or it will create a
93             # requested merge if you don’t have edit permission on both profiles.
94             sub _profile_do_merge_url($$) {
95 0     0     "https://www.geni.com/api/$_[1]/merge/$_[2]?only_ids=true";
96             }
97             # Project APIs
98             # Returns a list of users collaborating on a project.
99             sub _project_get_collaborators_url($) {
100 0     0     "https://www.geni.com/api/project-$_[1]/collaborators?only_ids=true";
101             }
102             # Returns a list of profiles tagged in a project.
103             sub _project_profiles_url($) {
104 0     0     "https://www.geni.com/api/project-$_[1]/profiles?only_ids=true";
105             }
106             # Returns a list of users following a project.
107             sub _project_get_followers_url($) {
108 0     0     "https://www.geni.com/api/project-$_[1]/followers?only_ids=true";
109             }
110              
111             sub _check_public_url($) {
112 0     0     "https://www.geni.com/api/profile-$_[1]/check-public";
113             }
114              
115             sub _get_results($) {
116 0     0     my ($self, $url) = (shift, shift);
117 0           my $res = $self->{ua}->get($url);
118 0 0         if ($res->is_success){
119 0           return $self->{json}->allow_nonref->relaxed->decode($res->decoded_content);
120             } else {
121 0           $WWW::Geni::errstr = $res->status_line;
122 0           return 0;
123             }
124             }
125              
126             sub _post_results($) {
127 0     0     my ($self, $url) = (shift, shift);
128 0           my $res = $self->{ua}->post($url);
129 0 0         if ($res->is_success){
130 0           return $self->{json}->allow_nonref->relaxed->decode($res->decoded_content);
131             } else {
132 0           $WWW::Geni::errstr = $res->status_line;
133 0           return 0;
134             }
135             }
136              
137             sub _populate_tree_conflicts($$){
138 0     0     my ($self, $list) = (shift, shift);
139 0 0 0       my $j = $self->_get_results(
140             $list->{next_page_url} or $self->_profile_get_tree_conflicts_url(1)
141             ) or return 0;
142 0           foreach(@{$j->{results}}){
  0            
143 0           my $c = WWW::Geni::Conflict->new(
144             focus => $_->{profile},
145             type => $_->{issue_type},
146             actor => $_->{actor}
147             );
148 0           $c->_add_managers(@{$_->{managers}});
  0            
149 0           $list->add($c);
150             }
151 0           $list->{cur_page_num} = $j->{page};
152 0           $list->{next_page_url} = $j->{next_page};
153             }
154              
155             } # end WWW::Geni class
156              
157             ##############################################################################
158             # WWW::Geni::Conflict class
159             ##############################################################################
160             {
161             package WWW::Geni::Conflict;
162             our $VERSION = $WWW::Geni::VERSION;
163              
164             sub new {
165 0     0     my $class = shift;
166 0           my $self = { @_ };
167 0           $self->{profile} = WWW::Geni::Profile->new(id => $self->{focus});
168 0           $self->{parents} = WWW::Geni::List->new();
169 0           $self->{siblings} = WWW::Geni::List->new();
170 0           $self->{spouses} = WWW::Geni::List->new();
171 0           $self->{children} = WWW::Geni::List->new();
172 0           $self->{parents}->{type} = "parents";
173 0           $self->{siblings}->{type} = "siblings";
174 0           $self->{spouses}->{type} = "spouses";
175 0           $self->{children}->{type} = "children";
176 0           bless $self, $class;
177 0           return $self;
178             }
179              
180             sub profile {
181 0     0     my $self = shift;
182 0 0         if (!$self->{resolved}) {
183 0           $self->_resolve(
184             $WWW::Geni::geni->_profile_get_immediate_family_url($self->{focus})
185             );
186             }
187 0           return $self->{profile};
188             }
189              
190             sub managers {
191 0     0     my $self = shift;
192 0 0         if (!$self->{resolved}) {
193 0           $self->_resolve(
194             $WWW::Geni::geni->_profile_get_immediate_family_url($self->{focus})
195             );
196             }
197 0           my $list = WWW::Geni::List->new();
198 0           foreach my $id (@{$self->{managers}}){
  0            
199 0 0         $id =~ /^profile-/i
200             ? $list->add(WWW::Geni::Profile->new( id => $id))
201             : $list->add(WWW::Geni::Profile->new( guid => $id));
202             }
203 0           return $list;
204             }
205              
206             sub type {
207 0     0     my $self = shift;
208 0           return $self->{type};
209             }
210              
211             sub actor {
212 0     0     my $self = shift;
213 0           return WWW::Geni::Profile->new(id => $self->{actor});
214             }
215              
216             sub fetch_list {
217 0     0     my $self = shift;
218 0 0         if (!$self->{resolved}) {
219 0           $self->_resolve(
220             $WWW::Geni::geni->_profile_get_immediate_family_url($self->profile()->id())
221             );
222             }
223 0 0 0       if ( defined $self->{spouses} && $self->{spouses}->count() > 0 ) {
    0 0        
    0 0        
    0 0        
224 0           return delete $self->{spouses};
225             } elsif ( defined $self->{parents} && $self->{parents}->count() > 0 ) {
226 0           return delete $self->{parents};
227             } elsif ( defined $self->{children} && $self->{children}->count() > 0 ) {
228 0           return delete $self->{children};
229             } elsif ( defined $self->{siblings} && $self->{siblings}->count() > 0 ) {
230 0           return delete $self->{siblings};
231             } else {
232 0           return 0;
233             }
234             }
235              
236              
237             sub _resolve($){
238 0     0     my $self = shift;
239 0           my $url = shift;
240 0           my (%temp_edges, $temp_profile);
241 0 0         my $j = $WWW::Geni::geni->_get_results($url)
242             or return 0;
243 0           my @managers = delete @{$j->{focus}->{managers}}[0..5000];
  0            
244 0           $self->{profile} = WWW::Geni::Profile->new(
245 0           map { $_, ${$j->{focus}}{$_} } keys %{$j->{focus}});
  0            
  0            
246 0           $self->{profile}->_add_managers(@managers);
247 0           foreach my $nodetype (keys %{$j->{nodes}}) {
  0            
248 0 0         if ($nodetype =~ /union-(\d+)/i) {
249 0           foreach my $member (keys %{$j->{nodes}->{$nodetype}->{edges}}){
  0            
250             # if the focal profile is listed as a child in this union
251 0 0 0       if (defined ${$j->{nodes}->{$nodetype}->{edges}->{ $self->{profile}->id() }}{"rel"} &&
  0 0 0        
  0            
252 0           ${$j->{nodes}->{$nodetype}->{edges}->{ $self->{profile}->id() }}{"rel"} eq "child"){
253              
254             # if the current profile is a child, we've found a sibling or duplicate of our focal profile
255 0 0         if (${$j->{nodes}->{$nodetype}->{edges}->{$member}}{"rel"} eq "child") {
  0 0          
  0            
256 0           %temp_edges = %{$j->{nodes}->{$member}->{edges}};
  0            
257 0           $temp_profile = WWW::Geni::Profile->new(
258 0           map { $_, ${$j->{nodes}->{$member}}{$_} } keys %{$j->{nodes}->{$member}});
  0            
  0            
259 0           %{$temp_profile->{edges}} = %temp_edges;
  0            
260 0           $self->{siblings}->add($temp_profile);
261              
262             # if the current profile is a child, we've found a parent of our focal profile
263             }elsif (${$j->{nodes}->{$nodetype}->{edges}->{$member}}{"rel"} eq "partner") {
264 0           %temp_edges = %{$j->{nodes}->{$member}->{edges}};
  0            
265 0           $temp_profile = WWW::Geni::Profile->new(
266 0           map { $_, ${$j->{nodes}->{$member}}{$_} } keys %{$j->{nodes}->{$member}});
  0            
  0            
267 0           %{$temp_profile->{edges}} = %temp_edges;
  0            
268 0           $self->{parents}->add($temp_profile);
269             }
270              
271             # if the focal profile is listed as a partner in this union
272 0           } elsif (defined ${$j->{nodes}->{$nodetype}->{edges}->{ $self->{profile}->id() }}{"rel"} &&
273             ${$j->{nodes}->{$nodetype}->{edges}->{ $self->{profile}->id() }}{"rel"} eq "partner"){
274              
275             # if the current profile is a child, we've found a child of our focal profile
276 0 0         if (${$j->{nodes}->{$nodetype}->{edges}->{$member}}{"rel"} eq "child") {
  0 0          
  0            
277 0           %temp_edges = %{$j->{nodes}->{$member}->{edges}};
  0            
278 0           $temp_profile = WWW::Geni::Profile->new(
279 0           map { $_, ${$j->{nodes}->{$member}}{$_} } keys %{$j->{nodes}->{$member}});
  0            
  0            
280 0           %{$temp_profile->{edges}} = %temp_edges;
  0            
281 0           $self->{children}->add($temp_profile);
282              
283             # if the current profile is a child, we've found a spouse or duplicate of our focal profile
284             }elsif (${$j->{nodes}->{$nodetype}->{edges}->{$member}}{"rel"} eq "partner") {
285 0           %temp_edges = %{$j->{nodes}->{$member}->{edges}};
  0            
286 0           $temp_profile = WWW::Geni::Profile->new(
287 0           map { $_, ${$j->{nodes}->{$member}}{$_} } keys %{$j->{nodes}->{$member}});
  0            
  0            
288 0           %{$temp_profile->{edges}} = %temp_edges;
  0            
289 0           $self->{spouses}->add($temp_profile);
290             }
291             }
292             }
293             }
294             }
295 0           $self->{resolved} = 1;
296             }
297              
298              
299             sub _add_managers {
300 0     0     my $self = shift;
301 0           push @{$self->{managers}}, @_;
  0            
302 0           return $self;
303             }
304              
305             } # end WWW::Geni::Conflict class
306              
307             ##############################################################################
308             # WWW::Geni::Profile class
309             # managers (array), big_tree (true, false), first_name, middle_name, last_name
310             # maiden_name, birth_date, birth_location, death_date, death_location, gender,
311             # url, public, locked (true, false), created_by, guid, name, id
312             ##############################################################################
313             {
314             package WWW::Geni::Profile;
315             our $VERSION = $WWW::Geni::VERSION;
316              
317             sub new {
318 0     0     my $class = shift;
319 0           my $self;
320 0           $self = { @_ };
321 0           bless $self, $class;
322             # TODO: Do this if the current user is a curator
323             #if (defined $self->{public} && $self->{public} eq "false") {
324             # $self->_check_public();
325             #}
326 0           return $self;
327             }
328              
329             sub id {
330 0     0     my $self = shift;
331 0 0         return $self->{id} ? $self->{id} : $self->{guid};
332             }
333              
334             sub first_name {
335 0     0     my $self = shift;
336 0           return $self->{first_name};
337             }
338              
339             sub middle_name {
340 0     0     my $self = shift;
341 0           return $self->{middle_name};
342             }
343              
344             sub last_name {
345 0     0     my $self = shift;
346 0           return $self->{last_name};
347             }
348              
349             sub maiden_name {
350 0     0     my $self = shift;
351 0           return $self->{maiden_name};
352             }
353              
354             sub display_name {
355 0     0     my $self = shift;
356 0           return $self->{name};
357             }
358              
359             sub birth_date {
360 0     0     my $self = shift;
361 0           return $self->{birth_date};
362             }
363              
364             sub birth_location {
365 0     0     my $self = shift;
366 0           return $self->{birth_location};
367             }
368              
369             sub death_date {
370 0     0     my $self = shift;
371 0           return $self->{death_date};
372             }
373              
374             sub death_location{
375 0     0     my $self = shift;
376 0           return $self->{death_location};
377             }
378              
379             sub locked {
380 0     0     my $self = shift;
381 0           return $self->{locked};
382             }
383              
384             sub big_tree {
385 0     0     my $self = shift;
386 0           return ($self->{big_tree} =~ /true/i);
387             }
388              
389             sub claimed {
390 0     0     my $self = shift;
391 0           return ($self->{claimed} =~ /true/i);
392             }
393              
394             sub public {
395 0     0     my $self = shift;
396 0           return ($self->{public} =~ /true/i);
397             }
398              
399             sub gender {
400 0     0     my $self = shift;
401 0           return $self->{first_name};
402             }
403              
404             sub creator {
405 0     0     my $self = shift;
406 0           return WWW::Geni::Profile->new(id => $self->{created_by});
407             }
408              
409             sub guid {
410 0     0     my $self = shift;
411 0           return $self->{guid};
412             }
413              
414             sub managers {
415 0     0     my $self = shift;
416 0           return $self->{managers};
417             }
418              
419             sub _add_managers {
420 0     0     my $self = shift;
421 0           push @{$self->{managers}}, @_;
  0            
422 0           return $self;
423             }
424              
425             sub _check_public {
426 0     0     my $self = shift;
427 0 0 0       if (defined $WWW::Geni::geni && defined $self->{public} && $self->{public} eq "false") {
      0        
428 0           my $j = $WWW::Geni::geni->_post_results($WWW::Geni::geni->_check_public_url($self->id()));
429 0           return $j->{public} =~ /true/i;
430             }
431             }
432              
433             } # end WWW::Geni::Profile class
434              
435             ##############################################################################
436             # WWW::Geni::Family class
437             ##############################################################################
438             {
439             package WWW::Geni::Family;
440             our $VERSION = $WWW::Geni::VERSION;
441              
442             sub new {
443 0     0     my $class = shift;
444 0           my $self = { @_ };
445 0           bless $self, $class;
446 0           return $self;
447             }
448              
449             sub add {
450 0     0     my $self = shift;
451 0 0         if ((shift) == "child"){
452 0           push @{$self->{children}}, (shift);
  0            
453             } else {
454 0           push @{$self->{parents}}, (shift);
  0            
455             }
456             }
457              
458             sub focus {
459 0     0     my $self = shift;
460 0           return $self->{focus};
461             }
462              
463             sub parents {
464 0     0     my $self = shift;
465 0           return @{$self->{parents}};
  0            
466             }
467              
468             sub children {
469 0     0     my $self = shift;
470 0           return @{$self->{childred}};
  0            
471             }
472              
473             } # end WWW::Geni::Family class
474              
475             ##############################################################################
476             # WWW::Geni::List class
477             ##############################################################################
478             {
479             package WWW::Geni::List;
480             our $VERSION = $WWW::Geni::VERSION;
481              
482             sub new {
483 0     0     my $class = shift;
484 0           my $self = {};
485 0           @{$self->{items}} = @_;
  0            
486 0           bless $self, $class;
487 0           return $self;
488             }
489              
490             sub get_next {
491 0     0     my $self = shift;
492 0 0         if ($self->count() == 1) {
493 0 0 0       if(${$self->{items}}[0] && ref(${$self->{items}}[0]) eq "WWW::Geni::Conflict"){
  0            
  0            
494 0           $WWW::Geni::geni->_populate_tree_conflicts($self);
495             }
496             }
497 0           return shift @{$self->{items}};
  0            
498             }
499              
500             sub has_next {
501 0     0     my $self = shift;
502 0           return $#{$self->{items}} > 0;
  0            
503             }
504              
505             sub add {
506 0     0     my $self = shift;
507 0           push @{$self->{items}}, @_;
  0            
508             }
509              
510             sub count {
511 0     0     my $self = shift;
512 0           return $#{$self->{items}};
  0            
513             }
514              
515             } # end WWW::Geni::List class
516              
517              
518             1;
519             __END__