import JSZip from 'jszip' import escapeStringRegexp from 'escape-string-regexp' import { saveAs } from 'file-saver' import zeroFill from 'zero-fill' import { XmlEntities } from 'html-entities' 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() function blobToDataURL (blob, callback) { let a = new FileReader() a.onloadend = function (e) { callback(a.result) } a.readAsDataURL(blob) } export default class FimFic2Epub { constructor (storyId) { this.storyId = storyId this.isDownloading = false this.zip = null this.chapterContent = [] this.remoteResources = new Map() this.storyInfo = null this.isDownloading = false this.cachedBlob = null this.hasCoverImage = false this.includeTitlePage = true this.categories = [] this.tags = [] } download () { return new Promise((resolve, reject) => { if (this.isDownloading) { reject() return } if (this.cachedBlob) { 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...') fetchRemote('https://www.fimfiction.net/api/story.php?story=' + this.storyId, (raw, type) => { let data try { data = JSON.parse(raw) } catch (e) { console.log('Unable to fetch story json') return } if (data.error) { console.error(data.error) return } this.storyInfo = data.story this.storyInfo.chapters = this.storyInfo.chapters || [] this.storyInfo.uuid = 'urn:fimfiction:' + this.storyInfo.id 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...') fetchRemote(this.storyInfo.url, (raw, type) => { this.extractTitlePageInfo(raw).then(() => this.checkCoverImage(resolve, reject)) }) } extractTitlePageInfo (html) { return new Promise((resolve, reject) => { let descPos = html.indexOf('