1 package be
.nikiroo
.fanfix
.supported
;
3 import java
.awt
.image
.BufferedImage
;
4 import java
.io
.IOException
;
5 import java
.io
.InputStream
;
6 import java
.net
.MalformedURLException
;
8 import java
.text
.SimpleDateFormat
;
9 import java
.util
.ArrayList
;
10 import java
.util
.Date
;
11 import java
.util
.List
;
12 import java
.util
.Map
.Entry
;
13 import java
.util
.Scanner
;
15 import be
.nikiroo
.fanfix
.Instance
;
16 import be
.nikiroo
.fanfix
.data
.MetaData
;
17 import be
.nikiroo
.utils
.StringUtils
;
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.
26 class Fanfiction
extends BasicSupport
{
28 protected boolean isHtml() {
33 public String
getSourceName() {
34 return "Fanfiction.net";
38 protected MetaData
getMeta(URL source
, InputStream in
) throws IOException
{
39 MetaData meta
= new MetaData();
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());
46 meta
.setUrl(source
.toString());
47 meta
.setPublisher(getSourceName());
48 meta
.setUuid(source
.toString());
51 meta
.setSubject(getSubject(reset(in
)));
52 meta
.setType(getType().toString());
53 meta
.setImageDocument(false);
54 meta
.setCover(getCover(source
, reset(in
)));
59 private String
getSubject(InputStream in
) {
60 String line
= getLine(in
, "id=pre_story_links", 0);
62 int pos
= line
.lastIndexOf('"');
64 line
= line
.substring(pos
+ 1);
65 pos
= line
.indexOf('<');
67 return StringUtils
.unhtml(line
.substring(0, pos
)).trim();
75 private List
<String
> getTags(InputStream in
) throws IOException
{
76 List
<String
> tags
= new ArrayList
<String
>();
78 String key
= "title=\"Send Private Message\"";
79 String line
= getLine(in
, key
, 2);
82 int pos
= line
.indexOf(key
);
84 line
= line
.substring(pos
+ key
.length());
86 pos
= line
.indexOf(key
);
88 line
= line
.substring(0, pos
);
89 line
= StringUtils
.unhtml(line
).trim();
90 if (line
.endsWith("-")) {
91 line
= line
.substring(0, line
.length() - 1);
94 for (String tag
: line
.split("-")) {
95 tags
.add(StringUtils
.unhtml(tag
).trim());
104 private String
getTitle(InputStream in
) {
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")) {
113 line
= StringUtils
.unhtml(line
).trim();
114 if (line
.startsWith("Follow/Fav")) {
115 line
= line
.substring("Follow/Fav".length()).trim();
118 return StringUtils
.unhtml(line
).trim();
126 private String
getAuthor(InputStream in
) {
127 String author
= null;
130 @SuppressWarnings("resource")
131 Scanner scan
= new Scanner(in
, "UTF-8");
132 scan
.useDelimiter("\\n");
133 while (scan
.hasNext()) {
134 String line
= scan
.next();
135 if (line
.contains("xcontrast_txt")) {
137 author
= StringUtils
.unhtml(line
).trim();
143 return fixAuthor(author
);
146 private String
getDate(InputStream in
) {
147 String key
= "Published: <span data-xutime='";
148 String line
= getLine(in
, key
, 0);
150 int pos
= line
.indexOf(key
);
152 line
= line
.substring(pos
+ key
.length());
153 pos
= line
.indexOf('\'');
155 line
= line
.substring(0, pos
).trim();
157 SimpleDateFormat sdf
= new SimpleDateFormat(
160 .format(new Date(1000 * Long
.parseLong(line
)));
161 } catch (NumberFormatException e
) {
162 Instance
.syserr(new IOException(
163 "Cannot convert publication date: " + line
, e
));
173 protected String
getDesc(URL source
, InputStream in
) {
174 return getLine(in
, "title=\"Send Private Message\"", 1);
177 private BufferedImage
getCover(URL url
, InputStream in
) {
178 String key
= "class='cimage";
179 String line
= getLine(in
, key
, 0);
181 int pos
= line
.indexOf(key
);
183 line
= line
.substring(pos
+ key
.length());
185 pos
= line
.indexOf(key
);
187 line
= line
.substring(pos
+ key
.length());
188 pos
= line
.indexOf('\'');
190 line
= line
.substring(0, pos
);
191 if (line
.startsWith("//")) {
192 line
= url
.getProtocol() + "://"
194 } else if (line
.startsWith("//")) {
195 line
= url
.getProtocol() + "://" + url
.getHost()
196 + "/" + line
.substring(1);
198 line
= url
.getProtocol() + "://" + url
.getHost()
199 + "/" + url
.getPath() + "/" + line
;
202 return getImage(this, null, line
);
212 protected List
<Entry
<String
, URL
>> getChapters(URL source
, InputStream in
) {
213 List
<Entry
<String
, URL
>> urls
= new ArrayList
<Entry
<String
, URL
>>();
215 String base
= source
.toString();
216 int pos
= base
.lastIndexOf('/');
217 String suffix
= base
.substring(pos
); // including '/' at start
218 base
= base
.substring(0, pos
);
219 if (base
.endsWith("/1")) {
220 base
= base
.substring(0, base
.length() - 1); // including '/' at end
223 String line
= getLine(in
, "id=chap_select", 0);
224 String key
= "<option value=";
228 for (pos
= line
.indexOf(key
); pos
>= 0; pos
= line
229 .indexOf(key
, pos
), i
++) {
230 pos
= line
.indexOf('>', pos
);
232 int endOfName
= line
.indexOf('<', pos
);
233 if (endOfName
>= 0) {
234 String name
= line
.substring(pos
+ 1, endOfName
);
235 String chapNum
= i
+ ".";
236 if (name
.startsWith(chapNum
)) {
237 name
= name
.substring(chapNum
.length(),
242 final String chapName
= name
.trim();
243 final URL chapURL
= new URL(base
+ i
+ suffix
);
244 urls
.add(new Entry
<String
, URL
>() {
245 public URL
setValue(URL value
) {
249 public URL
getValue() {
253 public String
getKey() {
257 } catch (MalformedURLException e
) {
258 Instance
.syserr(new IOException(
259 "Cannot parse chapter " + i
+ " url: "
260 + (base
+ i
+ suffix
), e
));
267 final String chapName
= getTitle(reset(in
));
268 final URL chapURL
= source
;
269 urls
.add(new Entry
<String
, URL
>() {
270 public URL
setValue(URL value
) {
274 public URL
getValue() {
278 public String
getKey() {
288 protected String
getChapterContent(URL source
, InputStream in
, int number
) {
289 StringBuilder builder
= new StringBuilder();
290 String startAt
= "class='storytext ";
291 String endAt1
= "function review_init";
292 String endAt2
= "id=chap_select";
295 @SuppressWarnings("resource")
296 Scanner scan
= new Scanner(in
, "UTF-8");
297 scan
.useDelimiter("\\n");
298 while (scan
.hasNext()) {
299 String line
= scan
.next();
300 if (!ok
&& line
.contains(startAt
)) {
302 } else if (ok
&& (line
.contains(endAt1
) || line
.contains(endAt2
))) {
308 // First line may contain the title and chap name again
309 if (builder
.length() == 0) {
310 int pos
= line
.indexOf("<hr");
312 line
= line
.substring(pos
);
316 builder
.append(line
);
320 return builder
.toString();
324 protected boolean supports(URL url
) {
325 return "fanfiction.net".equals(url
.getHost())
326 || "www.fanfiction.net".equals(url
.getHost());