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 { styleCss, coverstyleCss, titlestyleCss } from './styles' import { cleanMarkup } from './cleanMarkup' import fetchRemote from './fetchRemote' import * as template from './templates' import { mimeMap, containerXml } from './constants' const entities = new XmlEntities() module.exports = class FimFic2Epub { constructor (storyId) { this.storyId = storyId if (isNaN(storyId)) { let url = URL.parse(storyId, false, true) if (url.hostname === 'www.fimfiction.net' || url.hostname === 'fimfiction.net') { let m = url.pathname.match(/^\/story\/(\d+)/) if (m) { this.storyId = m[1] } } } this.hasDownloaded = false this.isDownloading = false this.zip = null this.chapterContent = [] this.remoteResources = new Map() this.storyInfo = null this.isDownloading = false this.cachedFile = null this.hasCoverImage = false this.coverImageDimensions = {width: 0, height: 0} this.includeTitlePage = true this.categories = [] this.tags = [] } download () { return new Promise((resolve, reject) => { if (this.isDownloading) { reject('Already downloading') return } if (this.hasDownloaded) { resolve() return } this.build().then(resolve).catch(reject) }) } build () { return new Promise((resolve, reject) => { this.isDownloading = true this.zip = new JSZip() this.zip.file('mimetype', 'application/epub+zip') this.zip.file('META-INF/container.xml', containerXml) console.log('Fetching story metadata...') let url = 'https://www.fimfiction.net/api/story.php?story=' + this.storyId fetchRemote(url, (raw, type) => { let data try { data = JSON.parse(raw) } catch (e) {} if (!data) { reject('Unable to fetch story json') return } if (data.error) { reject(data.error) return } this.storyInfo = data.story this.storyInfo.chapters = this.storyInfo.chapters || [] this.storyInfo.uuid = 'urn:fimfiction:' + this.storyInfo.id this.filename = sanitize(this.storyInfo.title + ' by ' + this.storyInfo.author.name + '.epub') this.zip.file('Styles/style.css', styleCss) this.zip.file('Styles/coverstyle.css', coverstyleCss) if (this.includeTitlePage) { this.zip.file('Styles/titlestyle.css', titlestyleCss) } this.zip.file('toc.ncx', template.createNcx(this)) this.zip.file('Text/nav.xhtml', template.createNav(this)) this.fetchTitlePage(resolve, reject) }) }) } fetchTitlePage (resolve, reject) { console.log('Fetching index page...') let url = this.storyInfo.url fetchRemote(url, (raw, type) => { this.extractTitlePageInfo(raw).then(() => this.checkCoverImage(resolve, reject)) }) } extractTitlePageInfo (html) { return new Promise((resolve, reject) => { let descPos = html.indexOf('