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 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 = 'https://www.fimfiction.net/api/story.php?story=' + storyId fetchRemote(url, (content, type) => { 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) }) }) } constructor (storyId) { this.storyId = FimFic2Epub.getStoryId(storyId) this.hasDownloaded = false this.downloadPromise = null this.storyInfo = null this.titlePage = null 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() } download () { if (this.downloadPromise) { return this.downloadPromise } 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.downloadPromise = null }) this.downloadPromise = p return p } build () { return this.extractTitlePageInfo(this.titlePage) .then(this.checkCoverImage.bind(this)) } fetchTitlePage () { console.log('Fetching index page...') return new Promise((resolve, reject) => { fetchRemote(this.storyInfo.url, (raw, type) => { this.titlePage = raw resolve(raw) }) }) } extractTitlePageInfo (html) { return new Promise((resolve, reject) => { let descPos = html.indexOf('