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