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