import JSZip from 'jszip'
import escapeStringRegexp from 'escape-string-regexp'
import zeroFill from 'zero-fill'
import { XmlEntities } from 'html-entities'
import sanitize from 'sanitize-filename'
import URL from 'url'
import isNode from 'detect-node'
import fileType from 'file-type'
import { styleCss, coverstyleCss, titlestyleCss } from './styles'
import { cleanMarkup } from './cleanMarkup'
import fetch from './fetch'
import fetchRemote from './fetchRemote'
import * as template from './templates'
import { containerXml } from './constants'
const entities = new XmlEntities()
module.exports = class FimFic2Epub {
static getStoryId (id) {
if (isNaN(id)) {
let url = URL.parse(id, false, true)
if (url.hostname === 'www.fimfiction.net' || url.hostname === 'fimfiction.net') {
let m = url.pathname.match(/^\/story\/(\d+)/)
if (m) {
id = m[1]
}
}
}
return id
}
static getFilename (storyInfo) {
return sanitize(storyInfo.title + ' by ' + storyInfo.author.name + '.epub')
}
static fetchStoryInfo (storyId, raw = false) {
return new Promise((resolve, reject) => {
storyId = FimFic2Epub.getStoryId(storyId)
let url = '/api/story.php?story=' + storyId
fetch(url).then((content) => {
let data
try {
data = JSON.parse(content)
} catch (e) {}
if (!data) {
reject('Unable to fetch story info')
return
}
if (data.error) {
reject(data.error)
return
}
let story = data.story
if (raw) {
resolve(story)
return
}
// this is so the metadata can be cached.
if (!story.chapters) story.chapters = []
delete story.likes
delete story.dislikes
delete story.views
delete story.total_views
delete story.comments
story.chapters.forEach((ch) => {
delete ch.views
})
// Add version number
story.FIMFIC2EPUB_VERSION = FIMFIC2EPUB_VERSION
resolve(story)
})
})
}
static parseChapterPage (html) {
let trimWhitespace = /^\s*(
)+|(
)+\s*$/ig
let authorNotesPos = html.indexOf('
Author\'s Note:')
authorNotes = html.substring(authorNotesPos + 22)
authorNotes = authorNotes.substring(0, authorNotes.indexOf('\t\n\t
'))
authorNotes = authorNotes.trim()
authorNotes = authorNotes.replace(trimWhitespace, '')
}
let chapterPos = html.indexOf('')
let chapter = html.substring(chapterPos + 29)
let pos = chapter.indexOf('\t
\t\t\n\t')
chapter = chapter.substring(0, pos).trim()
// remove leading and trailing
tags and whitespace
chapter = chapter.replace(trimWhitespace, '')
return {content: chapter, notes: authorNotes, notesFirst: authorNotesPos < chapterPos}
}
constructor (storyId) {
this.storyId = FimFic2Epub.getStoryId(storyId)
this.hasDownloaded = false
this.fetchPromise = null
this.storyInfo = null
this.chapters = []
this.chapterContent = []
this.remoteResources = new Map()
this.cachedFile = null
this.hasCoverImage = false
this.coverImageDimensions = {width: 0, height: 0}
// this.includeTitlePage = true
// this.categories = []
// this.tags = []
this.zip = new JSZip()
}
fetch () {
if (this.fetchPromise) {
return this.fetchPromise
}
this.storyInfo = null
this.chapters.length = 0
this.remoteResources.clear()
console.log('Fetching story metadata...')
let p = FimFic2Epub.fetchStoryInfo(this.storyId)
.then((storyInfo) => {
this.storyInfo = storyInfo
this.storyInfo.uuid = 'urn:fimfiction:' + this.storyInfo.id
this.filename = FimFic2Epub.getFilename(this.storyInfo)
})
.then(this.fetchTitlePage.bind(this))
.then(this.fetchChapters.bind(this))
.then(() => {
console.log('Fetch complete')
console.log(this)
this.fetchPromise = null
})
this.fetchPromise = p
return p
}
build () {
this.chapterContent.length = 0
return this.checkCoverImage()
.then(this.processChapters.bind(this))
.then(this.fetchRemoteFiles.bind(this))
.then(this.processStory.bind(this))
.then(() => {
console.log('Build complete')
})
}
fetchTitlePage () {
console.log('Fetching title page...')
let url = this.storyInfo.url.replace('http://www.fimfiction.net', '')
return fetch(url).then(this.extractTitlePageInfo.bind(this))
}
extractTitlePageInfo (html) {
let descPos = html.indexOf('')
let catsHtml = html.substring(startCatsPos, endCatsPos)
html = html.substring(endCatsPos + 6)
let categories = []
let matchCategory = /(.*?)<\/a>/g
for (let c; (c = matchCategory.exec(catsHtml));) {
categories.push({
url: 'http://www.fimfiction.net' + c[1],
className: c[2],
name: entities.decode(c[3])
})
}
this.categories = categories
ma = html.match(/This story is a sequel to (.*?)<\/a>/)
if (ma) {
this.storyInfo.prequel = {
url: 'http://www.fimfiction.net' + ma[1],
title: entities.decode(ma[2])
}
html = html.substring(html.indexOf('
') + 6)
}
let endDescPos = html.indexOf('\n')
let description = html.substring(0, endDescPos).trim()
html = html.substring(endDescPos + 7)
let extraPos = html.indexOf('