Version 1.2.4: fixes, new "Re-download" UI option
[fanfix.git] / src / be / nikiroo / fanfix / supported / Fanfiction.java
1 package be.nikiroo.fanfix.supported;
2
3 import java.awt.image.BufferedImage;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.net.MalformedURLException;
7 import java.net.URL;
8 import java.text.SimpleDateFormat;
9 import java.util.ArrayList;
10 import java.util.Date;
11 import java.util.List;
12 import java.util.Map.Entry;
13 import java.util.Scanner;
14
15 import be.nikiroo.fanfix.Instance;
16 import be.nikiroo.fanfix.bundles.Config;
17 import be.nikiroo.fanfix.data.MetaData;
18 import be.nikiroo.utils.StringUtils;
19
20 /**
21 * Support class for <a href="http://www.fanfiction.net/">Faniction.net</a>
22 * stories, a website dedicated to fanfictions of many, many different
23 * universes, from TV shows to novels to games.
24 *
25 * @author niki
26 */
27 class Fanfiction extends BasicSupport {
28 @Override
29 protected boolean isHtml() {
30 return true;
31 }
32
33 @Override
34 public String getSourceName() {
35 return "Fanfiction.net";
36 }
37
38 @Override
39 protected MetaData getMeta(URL source, InputStream in) throws IOException {
40 MetaData meta = new MetaData();
41
42 meta.setTitle(getTitle(reset(in)));
43 meta.setAuthor(getAuthor(reset(in)));
44 meta.setDate(getDate(reset(in)));
45 meta.setTags(getTags(reset(in)));
46 meta.setSource(getSourceName());
47 meta.setUrl(source.toString());
48 meta.setPublisher(getSourceName());
49 meta.setUuid(source.toString());
50 meta.setLuid("");
51 meta.setLang("EN");
52 meta.setSubject(getSubject(reset(in)));
53 meta.setType(getType().toString());
54 meta.setImageDocument(false);
55 meta.setCover(getCover(source, reset(in)));
56
57 return meta;
58 }
59
60 private String getSubject(InputStream in) {
61 String line = getLine(in, "id=pre_story_links", 0);
62 if (line != null) {
63 int pos = line.lastIndexOf('"');
64 if (pos >= 1) {
65 line = line.substring(pos + 1);
66 pos = line.indexOf('<');
67 if (pos >= 0) {
68 return StringUtils.unhtml(line.substring(0, pos)).trim();
69 }
70 }
71 }
72
73 return null;
74 }
75
76 private List<String> getTags(InputStream in) throws IOException {
77 List<String> tags = new ArrayList<String>();
78
79 String key = "title=\"Send Private Message\"";
80 String line = getLine(in, key, 2);
81 if (line != null) {
82 key = "Rated:";
83 int pos = line.indexOf(key);
84 if (pos >= 0) {
85 line = line.substring(pos + key.length());
86 key = "Chapters:";
87 pos = line.indexOf(key);
88 if (pos >= 0) {
89 line = line.substring(0, pos);
90 line = StringUtils.unhtml(line).trim();
91 if (line.endsWith("-")) {
92 line = line.substring(0, line.length() - 1);
93 }
94
95 for (String tag : line.split("-")) {
96 tags.add(StringUtils.unhtml(tag).trim());
97 }
98 }
99 }
100 }
101
102 return tags;
103 }
104
105 private String getTitle(InputStream in) {
106 int i = 0;
107 @SuppressWarnings("resource")
108 Scanner scan = new Scanner(in, "UTF-8");
109 scan.useDelimiter("\\n");
110 while (scan.hasNext()) {
111 String line = scan.next();
112 if (line.contains("xcontrast_txt")) {
113 if ((++i) == 2) {
114 line = StringUtils.unhtml(line).trim();
115 if (line.startsWith("Follow/Fav")) {
116 line = line.substring("Follow/Fav".length()).trim();
117 }
118
119 return StringUtils.unhtml(line).trim();
120 }
121 }
122 }
123
124 return null;
125 }
126
127 private String getAuthor(InputStream in) {
128 String author = null;
129
130 int i = 0;
131 @SuppressWarnings("resource")
132 Scanner scan = new Scanner(in, "UTF-8");
133 scan.useDelimiter("\\n");
134 while (scan.hasNext()) {
135 String line = scan.next();
136 if (line.contains("xcontrast_txt")) {
137 if ((++i) == 3) {
138 author = StringUtils.unhtml(line).trim();
139 break;
140 }
141 }
142 }
143
144 return fixAuthor(author);
145 }
146
147 private String getDate(InputStream in) {
148 String key = "Published: <span data-xutime='";
149 String line = getLine(in, key, 0);
150 if (line != null) {
151 int pos = line.indexOf(key);
152 if (pos >= 0) {
153 line = line.substring(pos + key.length());
154 pos = line.indexOf('\'');
155 if (pos >= 0) {
156 line = line.substring(0, pos).trim();
157 try {
158 SimpleDateFormat sdf = new SimpleDateFormat(
159 "YYYY-MM-dd");
160 return sdf
161 .format(new Date(1000 * Long.parseLong(line)));
162 } catch (NumberFormatException e) {
163 Instance.syserr(new IOException(
164 "Cannot convert publication date: " + line, e));
165 }
166 }
167 }
168 }
169
170 return null;
171 }
172
173 @Override
174 protected String getDesc(URL source, InputStream in) {
175 return getLine(in, "title=\"Send Private Message\"", 1);
176 }
177
178 private BufferedImage getCover(URL url, InputStream in) {
179 String key = "class='cimage";
180 String line = getLine(in, key, 0);
181 if (line != null) {
182 int pos = line.indexOf(key);
183 if (pos >= 0) {
184 line = line.substring(pos + key.length());
185 key = "src='";
186 pos = line.indexOf(key);
187 if (pos >= 0) {
188 line = line.substring(pos + key.length());
189 pos = line.indexOf('\'');
190 if (pos >= 0) {
191 line = line.substring(0, pos);
192 if (line.startsWith("//")) {
193 line = url.getProtocol() + "://"
194 + line.substring(2);
195 } else if (line.startsWith("//")) {
196 line = url.getProtocol() + "://" + url.getHost()
197 + "/" + line.substring(1);
198 } else {
199 line = url.getProtocol() + "://" + url.getHost()
200 + "/" + url.getPath() + "/" + line;
201 }
202
203 return getImage(this, null, line);
204 }
205 }
206 }
207 }
208
209 return null;
210 }
211
212 @Override
213 protected List<Entry<String, URL>> getChapters(URL source, InputStream in) {
214 List<Entry<String, URL>> urls = new ArrayList<Entry<String, URL>>();
215
216 String base = source.toString();
217 int pos = base.lastIndexOf('/');
218 String suffix = base.substring(pos); // including '/' at start
219 base = base.substring(0, pos);
220 if (base.endsWith("/1")) {
221 base = base.substring(0, base.length() - 1); // including '/' at end
222 }
223
224 String line = getLine(in, "id=chap_select", 0);
225 String key = "<option value=";
226 int i = 1;
227
228 if (line != null) {
229 for (pos = line.indexOf(key); pos >= 0; pos = line
230 .indexOf(key, pos), i++) {
231 pos = line.indexOf('>', pos);
232 if (pos >= 0) {
233 int endOfName = line.indexOf('<', pos);
234 if (endOfName >= 0) {
235 String name = line.substring(pos + 1, endOfName);
236 String chapNum = i + ".";
237 if (name.startsWith(chapNum)) {
238 name = name.substring(chapNum.length(),
239 name.length());
240 }
241
242 try {
243 final String chapName = name.trim();
244 final URL chapURL = new URL(base + i + suffix);
245 urls.add(new Entry<String, URL>() {
246 public URL setValue(URL value) {
247 return null;
248 }
249
250 public URL getValue() {
251 return chapURL;
252 }
253
254 public String getKey() {
255 return chapName;
256 }
257 });
258 } catch (MalformedURLException e) {
259 Instance.syserr(new IOException(
260 "Cannot parse chapter " + i + " url: "
261 + (base + i + suffix), e));
262 }
263 }
264 }
265 }
266 } else {
267 // only one chapter:
268 final String chapName = getTitle(reset(in));
269 final URL chapURL = source;
270 urls.add(new Entry<String, URL>() {
271 public URL setValue(URL value) {
272 return null;
273 }
274
275 public URL getValue() {
276 return chapURL;
277 }
278
279 public String getKey() {
280 return chapName;
281 }
282 });
283 }
284
285 return urls;
286 }
287
288 @Override
289 protected String getChapterContent(URL source, InputStream in, int number) {
290 StringBuilder builder = new StringBuilder();
291 String startAt = "class='storytext ";
292 String endAt1 = "function review_init";
293 String endAt2 = "id=chap_select";
294 boolean ok = false;
295
296 @SuppressWarnings("resource")
297 Scanner scan = new Scanner(in, "UTF-8");
298 scan.useDelimiter("\\n");
299 while (scan.hasNext()) {
300 String line = scan.next();
301 if (!ok && line.contains(startAt)) {
302 ok = true;
303 } else if (ok && (line.contains(endAt1) || line.contains(endAt2))) {
304 ok = false;
305 break;
306 }
307
308 if (ok) {
309 // First line may contain the title and chap name again
310 if (builder.length() == 0) {
311 int pos = line.indexOf("<hr");
312 if (pos >= 0) {
313 boolean chaptered = false;
314 for (String lang : Instance.getConfig()
315 .getString(Config.CHAPTER).split(",")) {
316 String chapterWord = Instance.getConfig()
317 .getStringX(Config.CHAPTER, lang);
318 int posChap = line.indexOf(chapterWord + " ");
319 if (posChap < pos) {
320 chaptered = true;
321 break;
322 }
323 }
324
325 if (chaptered) {
326 line = line.substring(pos);
327 }
328 }
329 }
330
331 builder.append(line);
332 }
333 }
334
335 return builder.toString();
336 }
337
338 @Override
339 protected boolean supports(URL url) {
340 return "fanfiction.net".equals(url.getHost())
341 || "www.fanfiction.net".equals(url.getHost());
342 }
343 }