fimfic2epub/src/main.js
2016-06-23 15:26:01 +02:00

251 lines
6.8 KiB
JavaScript

/* global chrome, safari */
import JSZip from 'jszip'
import escapeStringRegexp from 'escape-string-regexp'
import { saveAs } from 'file-saver'
import zeroFill from 'zero-fill'
import styleCss from './style'
import coverstyleCss from './coverstyle'
import fetch from './fetch'
import parseChapter from './parseChapter'
import * as template from './templates'
import { mimeMap, containerXml } from './constants'
const STORY_ID = document.location.pathname.match(/^\/story\/(\d*)/)[1]
let storyInfo
let remoteResources = new Map()
let chapterContent = []
let safariQueue = {}
let epubButton = document.querySelector('.story_container ul.chapters li.bottom a[title="Download Story (.epub)"]')
let isDownloading = false
let cachedBlob = null
if (epubButton) {
epubButton.addEventListener('click', function (e) {
e.preventDefault()
if (isDownloading) {
alert("Calm down, I'm working on it (it's processing)")
return
}
if (cachedBlob) {
saveStory()
return
}
downloadStory()
}, false)
}
function blobToDataURL (blob, callback) {
let a = new FileReader()
a.onloadend = function (e) { callback(a.result) }
a.readAsDataURL(blob)
}
function saveStory () {
console.log('Saving epub...')
if (typeof safari !== 'undefined') {
blobToDataURL(cachedBlob, (dataurl) => {
document.location.href = dataurl
alert('Rename downloaded file to .epub')
})
} else {
saveAs(cachedBlob, storyInfo.title + ' by ' + storyInfo.author.name + '.epub')
}
}
// messaging with the safari extension global page
function safariHandler (ev) {
let type = ev.message.type
let url = ev.message.input
let data = ev.message.output // arraybuffer
if (!safariQueue[url]) {
// console.error("Unable to get callback for " + url, JSON.stringify(safariQueue))
return
}
let cb = safariQueue[url].cb
let responseType = safariQueue[url].responseType
console.log(url, cb, responseType, data)
delete safariQueue[url]
if (responseType === 'blob') {
let blob = new Blob([data], {type: type})
cb(blob, type)
} else {
if (!responseType) {
let blob = new Blob([data], {type: type})
let fr = new FileReader()
fr.onloadend = function () {
cb(fr.result, type)
}
fr.readAsText(blob)
} else {
cb(data, type)
}
}
}
if (typeof safari !== 'undefined') {
safari.self.addEventListener('message', safariHandler, false)
}
function fetchBackground (url, cb, responseType) {
if (typeof chrome !== 'undefined' && chrome.runtime.sendMessage) {
chrome.runtime.sendMessage(url, function (objurl) {
fetch(objurl, cb, responseType)
URL.revokeObjectURL(objurl)
})
} else {
safariQueue[url] = {cb: cb, responseType: responseType}
safari.self.tab.dispatchMessage('remote', url)
}
}
function fetchRemote (url, cb, responseType) {
if (url.indexOf('//') === 0) {
url = 'http:' + url
}
if (document.location.protocol === 'https:' && url.indexOf('http:') === 0) {
fetchBackground(url, cb, responseType)
return
}
fetch(url, (data, type) => {
if (!data) {
fetchBackground(url, cb, responseType)
} else {
cb(data, type)
}
}, responseType)
}
function fetchRemoteFiles (zip, cb) {
let iter = remoteResources.entries()
let counter = 0
function recursive () {
let r = iter.next().value
if (!r) {
cb()
return
}
let url = r[0]
r = r[1]
console.log('Fetching remote file ' + (counter + 1) + ' of ' + remoteResources.size + ': ' + r.filename, url)
fetchRemote(url, (data, type) => {
r.dest = null
r.type = type
let dest = mimeMap[type]
if (dest) {
r.dest = dest.replace('*', r.filename)
zip.file(r.dest, data)
}
counter++
recursive()
}, 'arraybuffer')
}
recursive()
}
function fetchChapters (cb) {
let chapters = storyInfo.chapters
let chapterCount = storyInfo.chapters.length
let currentChapter = 0
function recursive () {
let ch = chapters[currentChapter]
console.log('Fetching chapter ' + (currentChapter + 1) + ' of ' + chapters.length + ': ' + ch.title)
fetchRemote(ch.link.replace('http', 'https'), (html) => {
parseChapter(currentChapter, ch, html, remoteResources, (html) => {
chapterContent[currentChapter] = html
currentChapter++
if (currentChapter < chapterCount) {
recursive()
} else {
cb()
}
})
})
}
recursive()
}
function downloadStory () {
isDownloading = true
const zip = new JSZip()
zip.file('mimetype', 'application/epub+zip')
zip.folder('META-INF').file('container.xml', containerXml)
console.log('Fetching story metadata...')
fetchRemote('https://www.fimfiction.net/api/story.php?story=' + STORY_ID, (raw, type) => {
let data
try {
data = JSON.parse(raw)
} catch (e) {
console.log('Unable to fetch story json')
return
}
storyInfo = data.story
storyInfo.uuid = 'urn:fimfiction:' + storyInfo.id
storyInfo.publishDate = '1970-01-01' // TODO!
remoteResources.set(storyInfo.full_image, {filename: 'cover'})
let coverImage = new Image()
coverImage.src = storyInfo.full_image
zip.file('style.css', styleCss)
zip.file('coverstyle.css', coverstyleCss)
coverImage.addEventListener('load', () => {
zip.file('toc.ncx', template.createNcx(storyInfo))
zip.file('nav.xhtml', template.createNav(storyInfo))
fetchChapters(() => {
fetchRemoteFiles(zip, () => {
let coverFilename = ''
remoteResources.forEach((r, url) => {
if (typeof r.chapter !== 'undefined' && r.originalUrl && r.dest) {
chapterContent[r.chapter] = chapterContent[r.chapter].replace(
new RegExp(escapeStringRegexp(r.originalUrl), 'g'),
r.dest
)
}
if (r.filename === 'cover') {
coverFilename = r.dest
}
})
for (let num = 0; num < chapterContent.length; num++) {
let html = chapterContent[num]
let filename = 'chapter_' + zeroFill(3, num + 1) + '.xhtml'
zip.file(filename, html)
}
chapterContent.length = 0
zip.file('cover.xhtml', template.createCoverPage(coverFilename, coverImage.width, coverImage.height))
zip.file('content.opf', template.createOpf(storyInfo, remoteResources))
console.log('Packaging epub...')
zip
.generateAsync({
type: 'blob',
mimeType: 'application/epub+zip',
compression: 'DEFLATE',
compressionOptions: {level: 9}
})
.then((blob) => {
cachedBlob = blob
saveStory()
isDownloading = false
})
})
})
}, false)
})
}