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