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