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 public String
getSourceName() {
37 protected MetaData
getMeta() throws IOException
{
38 MetaData meta
= new MetaData();
39 Element doc
= getSourceNode();
41 Element title
= doc
.getElementById("title");
42 Elements table
= null;
44 table
= title
.getElementsByTag("table");
48 Elements rows
= table
.first().getElementsByTag("tr");
49 if (rows
.size() > 1) {
50 table
= rows
.get(1).getElementsByTag("td");
51 // Columns: Realeased, Authors, Artists, Genres
52 if (table
.size() < 4) {
58 meta
.setTitle(getTitle());
60 meta
.setAuthor(getAuthors(table
.get(1).text() + ","
61 + table
.get(2).text()));
63 meta
.setDate(StringUtils
.unhtml(table
.get(0).text()).trim());
64 meta
.setTags(explode(table
.get(3).text()));
66 meta
.setSource(getSourceName());
67 meta
.setUrl(getSource().toString());
68 meta
.setPublisher(getSourceName());
69 meta
.setUuid(getSource().toString());
72 meta
.setSubject("manga");
73 meta
.setType(getType().toString());
74 meta
.setImageDocument(true);
75 meta
.setCover(getCover());
80 private String
getTitle() {
81 Element doc
= getSourceNode();
83 Element title
= doc
.getElementById("title");
84 Element h1
= title
.getElementsByTag("h1").first();
86 return StringUtils
.unhtml(h1
.text()).trim();
92 private String
getAuthors(String authorList
) {
94 for (String auth
: explode(authorList
)) {
95 if (!author
.isEmpty()) {
96 author
= author
+ ", ";
105 protected String
getDesc() {
106 Element doc
= getSourceNode();
107 Element title
= doc
.getElementsByClass("summary").first();
109 StringUtils
.unhtml(title
.text()).trim();
115 private Image
getCover() {
116 Element doc
= getSourceNode();
117 Element cover
= doc
.getElementsByClass("cover").first();
119 cover
= cover
.getElementsByTag("img").first();
123 String coverUrl
= cover
.absUrl("src");
127 coverIn
= openEx(coverUrl
);
129 return new Image(coverIn
);
133 } catch (IOException e
) {
134 Instance
.getTraceHandler().error(e
);
142 protected List
<Entry
<String
, URL
>> getChapters(Progress pg
) {
143 List
<Entry
<String
, URL
>> urls
= new ArrayList
<Entry
<String
, URL
>>();
145 String prefix
= null; // each chapter starts with this prefix, then a
146 // chapter number (including "x.5"), then name
148 Element doc
= getSourceNode();
149 for (Element li
: doc
.getElementsByTag("li")) {
150 Element el
= li
.getElementsByTag("h4").first();
152 el
= li
.getElementsByTag("h3").first();
155 Element a
= el
.getElementsByTag("a").first();
157 String title
= StringUtils
.unhtml(el
.text()).trim();
159 String url
= a
.absUrl("href");
160 if (url
.endsWith("1.html")) {
161 url
= url
.substring(0,
162 url
.length() - "1.html".length());
164 if (!url
.endsWith("/")) {
168 if (prefix
== null || !prefix
.isEmpty()) {
169 StringBuilder possiblePrefix
= new StringBuilder(
170 StringUtils
.unhtml(a
.text()).trim());
171 while (possiblePrefix
.length() > 0) {
172 char car
= possiblePrefix
.charAt(possiblePrefix
174 boolean punctuation
= (car
== '.' || car
== ' ');
175 boolean digit
= (car
>= '0' && car
<= '9');
176 if (!punctuation
&& !digit
) {
180 possiblePrefix
.setLength(possiblePrefix
184 if (prefix
== null) {
185 prefix
= possiblePrefix
.toString();
188 if (!prefix
.equalsIgnoreCase(possiblePrefix
190 prefix
= ""; // prefix not ok
194 urls
.add(new AbstractMap
.SimpleEntry
<String
, URL
>(
195 title
, new URL(url
)));
196 } catch (Exception e
) {
197 Instance
.getTraceHandler().error(e
);
203 if (prefix
!= null && !prefix
.isEmpty()) {
205 // We found a prefix, so everything should be sortable
206 SortedMap
<Double
, Entry
<String
, URL
>> map
= new TreeMap
<Double
, Entry
<String
, URL
>>();
207 for (Entry
<String
, URL
> entry
: urls
) {
208 String num
= entry
.getKey().substring(prefix
.length() + 1)
211 int pos
= num
.indexOf(' ');
213 name
= num
.substring(pos
).trim();
214 num
= num
.substring(0, pos
).trim();
217 if (!name
.isEmpty()) {
218 name
= "Tome " + num
+ ": " + name
;
220 name
= "Tome " + num
;
223 double key
= Double
.parseDouble(num
);
225 map
.put(key
, new AbstractMap
.SimpleEntry
<String
, URL
>(name
,
228 urls
= new ArrayList
<Entry
<String
, URL
>>(map
.values());
229 } catch (NumberFormatException e
) {
230 Instance
.getTraceHandler()
231 .error(new IOException(
232 "Cannot find a tome number, revert to default sorting",
234 // by default, the chapters are in reversed order
235 Collections
.reverse(urls
);
238 // by default, the chapters are in reversed order
239 Collections
.reverse(urls
);
246 protected String
getChapterContent(URL chapUrl
, int number
, Progress pg
)
252 StringBuilder builder
= new StringBuilder();
254 String url
= chapUrl
.toString();
255 InputStream imageIn
= null;
256 Element imageDoc
= null;
258 // 1. find out how many images there are
261 // note: when used, the base URL can be an ad-page
262 imageIn
= openEx(url
+ "1.html");
263 imageDoc
= DataUtil
.load(imageIn
, "UTF-8", url
+ "1.html");
264 } catch (IOException e
) {
265 Instance
.getTraceHandler().error(
266 new IOException("Cannot get image " + 1 + " of manga", e
));
268 if (imageIn
!= null) {
272 Element select
= imageDoc
.getElementsByClass("m").first();
273 Elements options
= select
.getElementsByTag("option");
274 size
= options
.size() - 1; // last is "Comments"
276 pg
.setMinMax(0, size
);
279 for (int i
= 1; i
<= size
; i
++) {
280 if (i
> 1) { // because first one was opened for size
282 imageIn
= openEx(url
+ i
+ ".html");
283 imageDoc
= DataUtil
.load(imageIn
, "UTF-8", url
+ i
286 String linkImage
= imageDoc
.getElementById("image").absUrl(
288 if (linkImage
!= null) {
290 // to help with the retry and the originalUrl, part 1
291 builder
.append(withoutQuery(linkImage
));
292 builder
.append("]<br/>");
295 // to help with the retry and the originalUrl, part 2
297 } catch (IOException e
) {
298 Instance
.getTraceHandler().error(
299 new IOException("Cannot get image " + i
302 if (imageIn
!= null) {
309 return builder
.toString();
313 * Refresh the {@link URL} by calling {@link MangaFox#openEx(String)}.
318 * @return TRUE if it was refreshed
320 private boolean refresh(String url
) {
324 } catch (Exception e
) {
330 * Open the URL through the cache, but: retry a second time after 100ms if
331 * it fails, remove the query part of the {@link URL} before saving it to
332 * the cache (so it can be recalled later).
337 * @return the resource
339 * @throws IOException
340 * in case of I/O error
342 private InputStream
openEx(String url
) throws IOException
{
344 return Instance
.getCache().open(new URL(url
), this, true,
346 } catch (Exception e
) {
350 } catch (InterruptedException ee
) {
353 return Instance
.getCache().open(new URL(url
), this, true,
359 * Return the same input {@link URL} but without the query part.
362 * the inpiut {@link URL} as a {@link String}
364 * @return the input {@link URL} without query
366 private URL
withoutQuery(String url
) {
369 // Remove the query from o (originalUrl), so it can be cached
372 o
= new URL(o
.getProtocol() + "://" + o
.getHost() + o
.getPath());
375 } catch (MalformedURLException e
) {
381 * Explode an HTML comma-separated list of values into a non-duplicate text
385 * the comma-separated values in HTML format
387 * @return the full list with no duplicate in text format
389 private List
<String
> explode(String values
) {
390 List
<String
> list
= new ArrayList
<String
>();
391 if (values
!= null && !values
.isEmpty()) {
392 for (String auth
: values
.split(",")) {
393 String a
= StringUtils
.unhtml(auth
).trim();
394 if (!a
.isEmpty() && !list
.contains(a
.trim())) {
404 protected boolean supports(URL url
) {
405 return "mangafox.me".equals(url
.getHost())
406 || "www.mangafox.me".equals(url
.getHost())
407 || "fanfox.net".equals(url
.getHost())
408 || "www.fanfox.net".equals(url
.getHost());