1 package be
.nikiroo
.fanfix
.supported
;
3 import java
.io
.IOException
;
4 import java
.io
.InputStream
;
5 import java
.net
.MalformedURLException
;
7 import java
.util
.AbstractMap
;
8 import java
.util
.ArrayList
;
9 import java
.util
.Collections
;
10 import java
.util
.List
;
11 import java
.util
.Map
.Entry
;
12 import java
.util
.SortedMap
;
13 import java
.util
.TreeMap
;
15 import org
.jsoup
.helper
.DataUtil
;
16 import org
.jsoup
.nodes
.Element
;
17 import org
.jsoup
.select
.Elements
;
19 import be
.nikiroo
.fanfix
.Instance
;
20 import be
.nikiroo
.fanfix
.data
.MetaData
;
21 import be
.nikiroo
.utils
.Image
;
22 import be
.nikiroo
.utils
.Progress
;
23 import be
.nikiroo
.utils
.StringUtils
;
25 class MangaFox
extends BasicSupport
{
27 protected boolean isHtml() {
32 protected MetaData
getMeta() throws IOException
{
33 MetaData meta
= new MetaData();
34 Element doc
= getSourceNode();
36 Element title
= doc
.getElementById("title");
37 Elements table
= null;
39 table
= title
.getElementsByTag("table");
43 Elements rows
= table
.first().getElementsByTag("tr");
44 if (rows
.size() > 1) {
45 table
= rows
.get(1).getElementsByTag("td");
46 // Columns: Realeased, Authors, Artists, Genres
47 if (table
.size() < 4) {
53 meta
.setTitle(getTitle());
55 meta
.setAuthor(getAuthors(table
.get(1).text() + ","
56 + table
.get(2).text()));
58 meta
.setDate(StringUtils
.unhtml(table
.get(0).text()).trim());
59 meta
.setTags(explode(table
.get(3).text()));
61 meta
.setSource(getType().getSourceName());
62 meta
.setUrl(getSource().toString());
63 meta
.setPublisher(getType().getSourceName());
64 meta
.setUuid(getSource().toString());
67 meta
.setSubject("manga");
68 meta
.setType(getType().toString());
69 meta
.setImageDocument(true);
70 meta
.setCover(getCover());
75 private String
getTitle() {
76 Element doc
= getSourceNode();
78 Element title
= doc
.getElementById("title");
79 Element h1
= title
.getElementsByTag("h1").first();
81 return StringUtils
.unhtml(h1
.text()).trim();
87 private String
getAuthors(String authorList
) {
89 for (String auth
: explode(authorList
)) {
90 if (!author
.isEmpty()) {
91 author
= author
+ ", ";
100 protected String
getDesc() {
101 Element doc
= getSourceNode();
102 Element title
= doc
.getElementsByClass("summary").first();
104 return StringUtils
.unhtml(title
.text()).trim();
110 private Image
getCover() {
111 Element doc
= getSourceNode();
112 Element cover
= doc
.getElementsByClass("cover").first();
114 cover
= cover
.getElementsByTag("img").first();
118 String coverUrl
= cover
.absUrl("src");
122 coverIn
= openEx(coverUrl
);
124 return new Image(coverIn
);
128 } catch (IOException e
) {
129 Instance
.getTraceHandler().error(e
);
137 protected List
<Entry
<String
, URL
>> getChapters(Progress pg
) {
138 List
<Entry
<String
, URL
>> urls
= new ArrayList
<Entry
<String
, URL
>>();
140 String prefix
= null; // each chapter starts with this prefix, then a
141 // chapter number (including "x.5"), then name
143 Element doc
= getSourceNode();
144 for (Element li
: doc
.getElementsByTag("li")) {
145 Element el
= li
.getElementsByTag("h4").first();
147 el
= li
.getElementsByTag("h3").first();
150 Element a
= el
.getElementsByTag("a").first();
152 String title
= StringUtils
.unhtml(el
.text()).trim();
154 String url
= a
.absUrl("href");
155 if (url
.endsWith("1.html")) {
156 url
= url
.substring(0,
157 url
.length() - "1.html".length());
159 if (!url
.endsWith("/")) {
163 if (prefix
== null || !prefix
.isEmpty()) {
164 StringBuilder possiblePrefix
= new StringBuilder(
165 StringUtils
.unhtml(a
.text()).trim());
166 while (possiblePrefix
.length() > 0) {
167 char car
= possiblePrefix
.charAt(possiblePrefix
169 boolean punctuation
= (car
== '.' || car
== ' ');
170 boolean digit
= (car
>= '0' && car
<= '9');
171 if (!punctuation
&& !digit
) {
175 possiblePrefix
.setLength(possiblePrefix
179 if (prefix
== null) {
180 prefix
= possiblePrefix
.toString();
183 if (!prefix
.equalsIgnoreCase(possiblePrefix
185 prefix
= ""; // prefix not ok
189 urls
.add(new AbstractMap
.SimpleEntry
<String
, URL
>(
190 title
, new URL(url
)));
191 } catch (Exception e
) {
192 Instance
.getTraceHandler().error(e
);
198 if (prefix
!= null && !prefix
.isEmpty()) {
200 // We found a prefix, so everything should be sortable
201 SortedMap
<Double
, Entry
<String
, URL
>> map
= new TreeMap
<Double
, Entry
<String
, URL
>>();
202 for (Entry
<String
, URL
> entry
: urls
) {
203 String num
= entry
.getKey().substring(prefix
.length() + 1)
206 int pos
= num
.indexOf(' ');
208 name
= num
.substring(pos
).trim();
209 num
= num
.substring(0, pos
).trim();
212 if (!name
.isEmpty()) {
213 name
= "Tome " + num
+ ": " + name
;
215 name
= "Tome " + num
;
218 double key
= Double
.parseDouble(num
);
220 map
.put(key
, new AbstractMap
.SimpleEntry
<String
, URL
>(name
,
223 urls
= new ArrayList
<Entry
<String
, URL
>>(map
.values());
224 } catch (NumberFormatException e
) {
225 Instance
.getTraceHandler()
226 .error(new IOException(
227 "Cannot find a tome number, revert to default sorting",
229 // by default, the chapters are in reversed order
230 Collections
.reverse(urls
);
233 // by default, the chapters are in reversed order
234 Collections
.reverse(urls
);
241 protected String
getChapterContent(URL chapUrl
, int number
, Progress pg
)
247 StringBuilder builder
= new StringBuilder();
249 String url
= chapUrl
.toString();
250 InputStream imageIn
= null;
251 Element imageDoc
= null;
253 // 1. find out how many images there are
256 // note: when used, the base URL can be an ad-page
257 imageIn
= openEx(url
+ "1.html");
258 imageDoc
= DataUtil
.load(imageIn
, "UTF-8", url
+ "1.html");
259 } catch (IOException e
) {
260 Instance
.getTraceHandler().error(
261 new IOException("Cannot get image " + 1 + " of manga", e
));
263 if (imageIn
!= null) {
267 Element select
= imageDoc
.getElementsByClass("m").first();
268 Elements options
= select
.getElementsByTag("option");
269 size
= options
.size() - 1; // last is "Comments"
271 pg
.setMinMax(0, size
);
274 for (int i
= 1; i
<= size
; i
++) {
275 if (i
> 1) { // because first one was opened for size
277 imageIn
= openEx(url
+ i
+ ".html");
278 imageDoc
= DataUtil
.load(imageIn
, "UTF-8", url
+ i
281 String linkImage
= imageDoc
.getElementById("image").absUrl(
283 if (linkImage
!= null) {
285 // to help with the retry and the originalUrl, part 1
286 builder
.append(withoutQuery(linkImage
));
287 builder
.append("]<br/>");
290 // to help with the retry and the originalUrl, part 2
292 } catch (IOException e
) {
293 Instance
.getTraceHandler().error(
294 new IOException("Cannot get image " + i
297 if (imageIn
!= null) {
304 return builder
.toString();
308 * Refresh the {@link URL} by calling {@link MangaFox#openEx(String)}.
313 * @return TRUE if it was refreshed
315 private boolean refresh(String url
) {
319 } catch (Exception e
) {
325 * Open the URL through the cache, but: retry a second time after 100ms if
326 * it fails, remove the query part of the {@link URL} before saving it to
327 * the cache (so it can be recalled later).
332 * @return the resource
334 * @throws IOException
335 * in case of I/O error
337 private InputStream
openEx(String url
) throws IOException
{
339 return Instance
.getCache().open(new URL(url
), withoutQuery(url
),
341 } catch (Exception e
) {
345 } catch (InterruptedException ee
) {
348 return Instance
.getCache().open(new URL(url
), withoutQuery(url
),
354 * Return the same input {@link URL} but without the query part.
357 * the inpiut {@link URL} as a {@link String}
359 * @return the input {@link URL} without query
361 private URL
withoutQuery(String url
) {
364 // Remove the query from o (originalUrl), so it can be cached
367 o
= new URL(o
.getProtocol() + "://" + o
.getHost() + o
.getPath());
370 } catch (MalformedURLException e
) {
376 * Explode an HTML comma-separated list of values into a non-duplicate text
380 * the comma-separated values in HTML format
382 * @return the full list with no duplicate in text format
384 private List
<String
> explode(String values
) {
385 List
<String
> list
= new ArrayList
<String
>();
386 if (values
!= null && !values
.isEmpty()) {
387 for (String auth
: values
.split(",")) {
388 String a
= StringUtils
.unhtml(auth
).trim();
389 if (!a
.isEmpty() && !list
.contains(a
.trim())) {
399 protected boolean supports(URL url
) {
400 return "mangafox.me".equals(url
.getHost())
401 || "www.mangafox.me".equals(url
.getHost())
402 || "fanfox.net".equals(url
.getHost())
403 || "www.fanfox.net".equals(url
.getHost());