File Coverage

blib/lib/Web/Mention/Author.pm
Criterion Covered Total %
statement 60 72 83.3
branch 12 20 60.0
condition 13 21 61.9
subroutine 12 15 80.0
pod 0 2 0.0
total 97 130 74.6


line stmt bran cond sub pod time code
1             package Web::Mention::Author;
2              
3 8     8   1400 use Moo;
  8         11466  
  8         62  
4 8     8   4574 use MooX::ClassAttribute;
  8         17514  
  8         76  
5 8     8   1741 use Types::Standard qw(InstanceOf Str Maybe);
  8         71098  
  8         78  
6 8     8   7518 use Try::Tiny;
  8         1274  
  8         532  
7 8     8   781 use LWP::UserAgent;
  8         41692  
  8         318  
8 8     8   61 use List::Util qw(first);
  8         17  
  8         609  
9 8     8   59 use Scalar::Util qw(blessed);
  8         19  
  8         425  
10              
11 8     8   635 use Web::Microformats2::Parser;
  8         771844  
  8         7072  
12              
13             has 'name' => (
14             is => 'ro',
15             isa => Maybe[Str],
16             );
17              
18             has 'url' => (
19             is => 'ro',
20             isa => Maybe[InstanceOf['URI']],
21             coerce => sub { URI->new($_[0]) },
22             );
23              
24             has 'photo' => (
25             is => 'ro',
26             isa => Maybe[InstanceOf['URI']],
27             coerce => sub { URI->new($_[0]) },
28             );
29              
30             class_has 'parser' => (
31             is => 'ro',
32             isa => InstanceOf['Web::Microformats2::Parser'],
33             default => sub { Web::Microformats2::Parser->new },
34             );
35              
36             sub new_from_mf2_document {
37 12     12 0 67 my $class = shift;
38 12         39 my ($doc) = @_;
39              
40             # This method implements the Indieweb Authorship Algorithm.
41             # https://indieweb.org/authorship#How_to_determine
42             # The quoted comments below are direct quotes from that page
43             # (as of spring 2018).
44              
45             # "Start with a particular h-entry to determine authorship for,
46             # and no author."
47              
48 12         26 my $author;
49             my $author_page;
50              
51 12         67 my $h_entry = $doc->get_first( 'h-entry' );
52              
53             # "If no h-entry, then there's no post to find authorship for, abort."
54 12 100       2278 unless ( $h_entry ) {
55 4         77 return $class->new;
56             }
57              
58             # "If the h-entry has an author property, use that."
59 8         33 $author = $h_entry->get_property( 'author' );
60              
61             # "Otherwise if the h-entry has a parent h-feed with author property,
62             # use that."
63 8 100 100     215 if (
      66        
64             not ( $author )
65             && $h_entry->parent
66             && ( $h_entry->parent->has_type ('h-feed') )
67             ) {
68 2         149 $author = $h_entry->parent->get_property( 'author' );
69             }
70              
71             # "If an author property was found:"
72              
73             # "If it has an h-card, use it, exit."
74 8 100 100     94 if (
      66        
75             defined $author
76             && blessed( $author )
77             && ( $author->has_type( 'h-card' ) )
78             ) {
79 3         248 return $class->_new_with_h_card( $author );
80             }
81              
82             # "Otherwise if author property is an http(s) URL,
83             # let the author-page have that URL."
84 5 100       16 if ( defined $author ) {
85             try {
86 2     2   88 $author_page = URI->new( $author );
87 2 0       182 unless ( $author_page->schema =~ /^http/ ) {
88 0         0 undef $author_page;
89             }
90 2         58 };
91             }
92              
93             # "Otherwise use the author property as the author name, exit."
94 5 50 66     59 if ( $author and !$author_page ) {
95 0         0 return $class->new( name => $author );
96             }
97              
98             # "If there is an author-page URL:"
99 5 100       27 if ( $author_page ) {
100              
101             # "Get the author-page from that URL and parse it for Microformats-2."
102 2         32 my $ua = LWP::UserAgent->new;
103 2         3600 my $response = $ua->get( $author_page );
104 2         433663 my $author_doc = $class->parser->parse( $response );
105              
106             # "If author-page has 1+ h-card with url == uid == author-page's URL,
107             # then use first such h-card, exit."
108 2         3705 my @h_cards = grep{ $_->has_type( 'h-card' ) } $doc->all_items;
  5         598  
109 2         328 for my $h_card ( @h_cards ) {
110 0         0 my $urls_ref = $h_card->get_properties( 'url' );
111 0         0 my $uids_ref = $h_card->get_properties( 'uid' );
112 0 0 0     0 if (
113 0     0   0 first { $_ eq $author_page->as_string } @$urls_ref
114 0     0   0 && first { $_ eq $author_page->as_string } @$uids_ref
115             ) {
116 0         0 return $class->_new_with_h_card( $h_card );
117             }
118             }
119              
120             # XXX Skipping the "rel-me"-based test.
121              
122             # "if the h-entry's page has 1+ h-card with url == author-page URL,
123             # use first such h-card, exit."
124 2         96 for my $h_card ( @h_cards ) {
125 0         0 my $urls_ref = $h_card->get_properties( 'url' );
126 0 0       0 if (
127 0     0   0 first { $_ eq $author_page->as_string } @$urls_ref
128             ) {
129 0         0 return $class->_new_with_h_card( $h_card );
130             }
131             }
132              
133             }
134              
135 5         98 return $class->new;
136              
137             }
138              
139             sub new_from_html {
140 8     8 0 18182 my $class = shift;
141 8         27 my ($html) = @_;
142              
143 8         244 my $doc = $class->parser->parse( $html );
144              
145 8         127813 return $class->new_from_mf2_document( $doc );
146              
147             }
148              
149             sub _new_with_h_card {
150 3     3   12 my ( $class, $h_card ) = @_;
151              
152 3         9 my %constructor_args;
153              
154 3         9 foreach ( qw (name url photo ) ) {
155 9         23 my $value = $h_card->get_properties( $_ );
156 9 50 33     82 if ( defined $value && defined $value->[0] ) {
157 9         26 $constructor_args{ $_ } = $value->[0];
158             }
159             }
160              
161 3         51 return $class->new( %constructor_args );
162             }
163              
164             1;
165              
166             =pod
167              
168             =head1 NAME
169              
170             Web::Mention::Author - The author of a webmention's source document
171              
172             =head1 DESCRIPTION
173              
174             An object of this class represents the author of a webmention -- or,
175             more specifically, the author of the document that a given webmention
176             points to as its source.
177              
178             It implements the IndieWeb I<authorship protocol>, as defined here:
179             L<https://indieweb.org/authorship#How_to_determine>
180              
181             It is not expected that you'll build objects of this class yourself.
182             Rather, you'll receive and query them by way of the C<author()> method
183             of Web::Mention objects.
184              
185             =head1 METHODS
186              
187             =head2 Object Methods
188              
189             =head3 name
190              
191             $name = $author->name;
192              
193             Returns the author's name.
194              
195             =head3 url
196              
197             $author_url = $author->url;
198              
199             Returns the author's URL as a L<URI> object, or undef.
200              
201             =head3 photo
202              
203             $photo_url = $author->photo;
204              
205             Returns the author's photo (avatar) as a L<URI> object, or undef.
206              
207             =head1 NOTES AND BUGS
208              
209             This software is B<alpha>; its author is still determining how it wants
210             to work, and its interface might change dramatically.
211              
212             (Honestly, the Web::Mention namespace might not even be the best place
213             for it!)
214              
215             Its implementation of the authorship algorithm is I<very> incomplete.
216             The author only got as far as being able to parse typical output from
217             L<http://brid.gy> and then stopped. Tsk tsk.
218              
219             =head1 AUTHOR
220              
221             Jason McIntosh (jmac@jmac.org)
222              
223             =head1 COPYRIGHT AND LICENSE
224              
225             This software is Copyright (c) 2018 by Jason McIntosh.
226              
227             This is free software, licensed under:
228              
229             The MIT (X11) License