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