Turn ebooks AC from OpenLibrary into usable HTML
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / WWW / AddedContent / OpenLibrary.pm
1 # ---------------------------------------------------------------
2 # Copyright (C) 2009 David Christensen <david.a.christensen@gmail.com>
3 # Copyright (C) 2009-2011 Dan Scott <dscott@laurentian.ca>
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 # ---------------------------------------------------------------
15
16 package OpenILS::WWW::AddedContent::OpenLibrary;
17 use strict; use warnings;
18 use OpenSRF::Utils::Logger qw/$logger/;
19 use OpenSRF::Utils::SettingsParser;
20 use OpenILS::WWW::AddedContent;
21 use OpenSRF::Utils::JSON;
22 use OpenSRF::EX qw/:try/;
23 use Data::Dumper;
24
25 # Edit the <added_content> section of /openils/conf/opensrf.xml
26 # Change <module> to:
27 #   <module>OpenILS::WWW::AddedContent::OpenLibrary</module>
28
29 my $AC = 'OpenILS::WWW::AddedContent';
30
31 # These URLs are always the same for OpenLibrary, so there's no advantage to
32 # pulling from opensrf.xml; we hardcode them here
33
34 # jscmd=details is unstable but includes goodies such as Table of Contents
35 my $base_url_details = 'http://openlibrary.org/api/books?format=json&jscmd=details&bibkeys=ISBN:';
36
37 # jscmd=data is stable and contains links to ebooks, excerpts, etc
38 my $base_url_data = 'http://openlibrary.org/api/books?format=json&jscmd=data&bibkeys=ISBN:';
39
40 my $cover_base_url = 'http://covers.openlibrary.org/b/isbn/';
41
42 sub new {
43     my( $class, $args ) = @_;
44     $class = ref $class || $class;
45     return bless($args, $class);
46 }
47
48 # --------------------------------------------------------------------------
49 sub jacket_small {
50     my( $self, $key ) = @_;
51     return $self->send_img(
52         $self->fetch_cover_response('-S.jpg', $key));
53 }
54
55 sub jacket_medium {
56     my( $self, $key ) = @_;
57     return $self->send_img(
58         $self->fetch_cover_response('-M.jpg', $key));
59
60 }
61 sub jacket_large {
62     my( $self, $key ) = @_;
63     return $self->send_img(
64         $self->fetch_cover_response('-L.jpg', $key));
65 }
66
67 # --------------------------------------------------------------------------
68
69 sub ebooks_html {
70     my( $self, $key ) = @_;
71     my $book_data_json = $self->fetch_data_response($key)->content();
72
73     $logger->debug("$key: " . $book_data_json);
74
75     my $ebook_html;
76     
77     my $book_data = OpenSRF::Utils::JSON->JSON2perl($book_data_json);
78     my $book_key = (keys %$book_data)[0];
79
80     # We didn't find a matching book; short-circuit our response
81     if (!$book_key) {
82         $logger->debug("$key: no found book");
83         return 0;
84     }
85
86     my $ebooks_json = $book_data->{$book_key}->{ebooks};
87
88     # No ebooks are available for this book; short-circuit
89     if (!$ebooks_json or !scalar(@$ebooks_json)) {
90         $logger->debug("$key: no ebooks");
91         return 0;
92     }
93
94     # Check the availability of the ebooks
95     my $available = $ebooks_json->[0]->{'availability'} || '';
96     if (!$available) {
97         $logger->debug("$key: no available ebooks");
98         return 0;
99     }
100
101     # Build a basic list of available ebook types and their URLs
102     # ebooks appears to be an array containing one element - a hash
103
104     # First element of the hash is 'read_url' which is a URL to
105     # Internet Archive online reader
106     my $stream_url = $ebooks_json->[0]->{'read_url'} || '';
107     if ($stream_url) {
108         $ebook_html .= "<li class='ebook_stream'><a href='$stream_url'>Read online</a></li>\n";
109         $logger->debug("$key: stream URL = $stream_url");
110     }
111
112     my $ebook_formats = $ebooks_json->[0]->{'formats'} || '';
113     # Next elements are various ebook formats that are available
114     foreach my $ebook (keys %{$ebook_formats}) {
115         if ($ebook_formats->{$ebook} eq 'read_url') {
116             next;
117         }
118         $ebook_html .= "<li class='ebook_$ebook'><a href='" . 
119             $ebook_formats->{$ebook}->{'url'} . "'>" . uc($ebook) . "</a></li>\n";
120     }
121
122     $logger->debug("$key: $ebook_html");
123     $self->send_html("<ul class='ebooks'>$ebook_html</ul>");
124 }
125 =head1
126
127 OpenLibrary returns a JSON hash of zero or more book responses matching our
128 request. Each response may contain a table of contents within the details
129 section of the response.
130
131 For now, we check only the first response in the hash for a table of
132 contents, and if we find a table of contents, we transform it to a simple
133 HTML table.
134
135 =cut
136
137 sub toc_html {
138     my( $self, $key ) = @_;
139     my $book_details_json = $self->fetch_details_response($key)->content();
140
141     $logger->debug("$key: " . $book_details_json);
142
143     my $toc_html;
144     
145     my $book_details = OpenSRF::Utils::JSON->JSON2perl($book_details_json);
146     my $book_key = (keys %$book_details)[0];
147
148     # We didn't find a matching book; short-circuit our response
149     if (!$book_key) {
150         $logger->debug("$key: no found book");
151         return 0;
152     }
153
154     my $toc_json = $book_details->{$book_key}->{details}->{table_of_contents};
155
156     # No table of contents is available for this book; short-circuit
157     if (!$toc_json or !scalar(@$toc_json)) {
158         $logger->debug("$key: no TOC");
159         return 0;
160     }
161
162     # Build a basic HTML table containing the section number, section title,
163     # and page number. Some rows may not contain section numbers, we should
164     # protect against empty page numbers too.
165     foreach my $chapter (@$toc_json) {
166         my $label = $chapter->{label};
167         if ($label) {
168             $label .= '. ';
169         }
170         my $title = $chapter->{title} || '';
171         my $page_number = $chapter->{pagenum} || '';
172  
173         $toc_html .= '<tr>' .
174             "<td class='toc_label'>$label</td>" .
175             "<td class='toc_title'>$title</td>" .
176             "<td class='toc_page'>$page_number</td>" .
177             "</tr>\n";
178     }
179
180     $logger->debug("$key: $toc_html");
181     $self->send_html("<table>$toc_html</table>");
182 }
183
184 sub toc_json {
185     my( $self, $key ) = @_;
186     my $toc = $self->send_json(
187         $self->fetch_details_response($key)
188     );
189 }
190
191 sub send_img {
192     my($self, $response) = @_;
193     return { 
194         content_type => $response->header('Content-type'),
195         content => $response->content, 
196         binary => 1 
197     };
198 }
199
200 sub send_json {
201     my( $self, $content ) = @_;
202     return 0 unless $content;
203
204     return { content_type => 'text/plain', content => $content };
205 }
206
207 sub send_html {
208     my( $self, $content ) = @_;
209     return 0 unless $content;
210
211     # Hide anything that might contain a link since it will be broken
212     my $HTML = <<"    HTML";
213         <div>
214             <style type='text/css'>
215                 div.ac input, div.ac a[href],div.ac img, div.ac button { display: none; visibility: hidden }
216             </style>
217             <div class='ac'>
218                 $content
219             </div>
220         </div>
221     HTML
222
223     return { content_type => 'text/html', content => $HTML };
224 }
225
226 # returns the HTTP response object from the URL fetch
227 sub fetch_data_response {
228     my( $self, $key ) = @_;
229     my $url = $base_url_data . "$key";
230     my $response = $AC->get_url($url);
231     return $response;
232 }
233 # returns the HTTP response object from the URL fetch
234 sub fetch_details_response {
235     my( $self, $key ) = @_;
236     my $url = $base_url_details . "$key";
237     my $response = $AC->get_url($url);
238     return $response;
239 }
240
241 # returns the HTTP response object from the URL fetch
242 sub fetch_cover_response {
243     my( $self, $size, $key ) = @_;
244     my $url = $cover_base_url . "$key$size";
245     return $AC->get_url($url);
246 }
247
248
249 1;