mirror of
https://github.com/daniel-j/fimfic2epub.git
synced 2024-05-15 01:44:15 +12:00
it worksrm test.html
This commit is contained in:
parent
39433e6daf
commit
2a3c6174b2
7
.babelrc
Normal file
7
.babelrc
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"presets": [
|
||||
"node6"
|
||||
],
|
||||
"plugins": ["transform-strict-mode"]
|
||||
}
|
||||
|
25
extension/eventPage.js
Normal file
25
extension/eventPage.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
|
||||
function fetch(url, cb, type) {
|
||||
if (url.indexOf('//') === 0) {
|
||||
url = 'http:'+url
|
||||
}
|
||||
let x = new XMLHttpRequest()
|
||||
x.open('get', url, true)
|
||||
if (type) {
|
||||
x.responseType = type
|
||||
}
|
||||
x.onload = function () {
|
||||
console.log(x.getResponseHeader('content-type'))
|
||||
cb(URL.createObjectURL(x.response), x.getResponseHeader('content-type'))
|
||||
}
|
||||
x.onerror = function () {
|
||||
console.error('error')
|
||||
cb(null)
|
||||
}
|
||||
x.send()
|
||||
}
|
||||
|
||||
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
|
||||
fetch(request, sendResponse, 'blob')
|
||||
return true
|
||||
})
|
23
extension/manifest.json
Normal file
23
extension/manifest.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
|
||||
"name": "fimfic2epub",
|
||||
"description": "",
|
||||
"version": "1.0",
|
||||
|
||||
"background": {
|
||||
"scripts": ["eventPage.js"],
|
||||
"persistent": false
|
||||
},
|
||||
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["https://www.fimfiction.net/story/*"],
|
||||
"js": ["tidy.js", "fimfic2epub.js"]
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
]
|
||||
}
|
31
extension/tidy.js
Normal file
31
extension/tidy.js
Normal file
File diff suppressed because one or more lines are too long
118
gulpfile.babel.js
Normal file
118
gulpfile.babel.js
Normal file
|
@ -0,0 +1,118 @@
|
|||
'use strict'
|
||||
|
||||
|
||||
// gulp and utilities
|
||||
import gulp from 'gulp'
|
||||
import gutil from 'gulp-util'
|
||||
import del from 'del'
|
||||
import mergeStream from 'merge-stream'
|
||||
import Sequence from 'run-sequence'
|
||||
import watch from 'gulp-watch'
|
||||
import lazypipe from 'lazypipe'
|
||||
|
||||
// script
|
||||
import eslint from 'gulp-eslint'
|
||||
import webpack from 'webpack'
|
||||
import webpackConfig from './webpack.config.babel.js'
|
||||
|
||||
const sequence = Sequence.use(gulp)
|
||||
|
||||
let inProduction = process.env.NODE_ENV === 'production' || process.argv.indexOf('-p') !== -1
|
||||
|
||||
let eslintOpts = {
|
||||
envs: ['browser', 'node'],
|
||||
rules: {
|
||||
'strict': 0,
|
||||
'semi': [1, 'never'],
|
||||
'quotes': [1, 'single'],
|
||||
'space-infix-ops': [0, {'int32Hint': true}],
|
||||
'no-empty': 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let watchOpts = {
|
||||
readDelay: 500,
|
||||
verbose: true
|
||||
}
|
||||
|
||||
if (inProduction) {
|
||||
webpackConfig.plugins.push(new webpack.optimize.DedupePlugin())
|
||||
webpackConfig.plugins.push(new webpack.optimize.OccurenceOrderPlugin(false))
|
||||
webpackConfig.plugins.push(new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false,
|
||||
screw_ie8: true
|
||||
},
|
||||
comments: false,
|
||||
mangle: {
|
||||
screw_ie8: true
|
||||
},
|
||||
screw_ie8: true,
|
||||
sourceMap: false
|
||||
}))
|
||||
}
|
||||
|
||||
let wpCompiler = webpack(Object.assign({}, webpackConfig, {
|
||||
cache: {},
|
||||
devtool: inProduction? null:'inline-source-map',
|
||||
debug: !inProduction
|
||||
}))
|
||||
|
||||
function webpackTask(callback) {
|
||||
// run webpack
|
||||
wpCompiler.run(function(err, stats) {
|
||||
if(err) throw new gutil.PluginError('webpack', err)
|
||||
gutil.log('[webpack]', stats.toString({
|
||||
colors: true,
|
||||
hash: false,
|
||||
version: false,
|
||||
chunks: false,
|
||||
chunkModules: false
|
||||
}))
|
||||
callback()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
let lintESPipe = lazypipe()
|
||||
.pipe(eslint, eslintOpts)
|
||||
.pipe(eslint.format)
|
||||
|
||||
// Cleanup tasks
|
||||
gulp.task('clean', () => del('build'))
|
||||
|
||||
gulp.task('clean:script', () => {
|
||||
return del('build/script')
|
||||
})
|
||||
|
||||
|
||||
// Main tasks
|
||||
gulp.task('webpack', webpackTask)
|
||||
gulp.task('script', ['webpack'])
|
||||
gulp.task('watch:script', () => {
|
||||
return watch(['src/**/*.js'], watchOpts, function () {
|
||||
return sequence('script')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
gulp.task('lint', () => {
|
||||
return gulp.src(['src/**/*.js']).pipe(lintESPipe())
|
||||
})
|
||||
gulp.task('watch:lint', () => {
|
||||
return watch(['src/**/*.js'], watchOpts, function (file) {
|
||||
gulp.src(file.path).pipe(lintESPipe())
|
||||
})
|
||||
})
|
||||
|
||||
// Default task
|
||||
gulp.task('default', (done) => {
|
||||
sequence('clean', ['script', 'lint'], done)
|
||||
})
|
||||
|
||||
// Watch task
|
||||
gulp.task('watch', (done) => {
|
||||
sequence('default', ['watch:lint', 'watch:script'], done)
|
||||
})
|
20
package.json
20
package.json
|
@ -6,10 +6,28 @@
|
|||
"author": "djazz",
|
||||
"dependencies": {
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"file-saver": "^1.3.2",
|
||||
"html-entities": "^1.2.0",
|
||||
"jszip": "^3.0.0",
|
||||
"mithril": "^0.2.5",
|
||||
"pretty-data": "^0.40.0",
|
||||
"tidy-html5": "^0.1.1",
|
||||
"xhr2": "^0.1.3",
|
||||
"xhr2": "^0.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.9.1",
|
||||
"babel-loader": "^6.2.4",
|
||||
"babel-plugin-transform-strict-mode": "^6.8.0",
|
||||
"babel-preset-es2015": "^6.9.0",
|
||||
"babel-preset-node6": "^11.0.0",
|
||||
"del": "^2.2.0",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-eslint": "^2.0.0",
|
||||
"gulp-util": "^3.0.7",
|
||||
"gulp-watch": "^4.3.8",
|
||||
"lazypipe": "^1.0.1",
|
||||
"merge-stream": "^1.0.0",
|
||||
"run-sequence": "^1.2.1",
|
||||
"webpack": "^1.13.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
#!/usr/bin/env node
|
||||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
import m from 'mithril'
|
||||
import render from './mithril-node-render'
|
||||
import { pd as pretty } from 'pretty-data'
|
||||
import escapeStringRegexp from 'escape-string-regexp'
|
||||
import { XmlEntities } from 'html-entities'
|
||||
import { saveAs } from 'file-saver'
|
||||
|
||||
const m = require('mithril')
|
||||
const render = require('./mithril-node-render')
|
||||
const pretty = require('pretty-data').pd
|
||||
const tidy = require("tidy-html5").tidy_html5
|
||||
const escapeStringRegexp = require('escape-string-regexp')
|
||||
const entities = new XmlEntities()
|
||||
const tidy = tidy_html5
|
||||
|
||||
const entities = new require('html-entities').XmlEntities
|
||||
|
||||
const XMLHttpRequest = require('xhr2')
|
||||
//const XMLHttpRequest = require('xhr2')
|
||||
|
||||
let beautifyOptions = {
|
||||
indent_size: 2,
|
||||
|
@ -43,17 +42,35 @@ let mimeMap = {
|
|||
'image/gif': 'Images/*.gif'
|
||||
}
|
||||
|
||||
const STORY_ID = 180690 // bbcode test tags
|
||||
//const STORY_ID = 180690 // bbcode test tags
|
||||
//const STORY_ID = 931 // pink eyes
|
||||
//const STORY_ID = 119190 // fallout equestria
|
||||
const STORY_ID = document.location.pathname.match(/^\/story\/(\d*)/)[1]
|
||||
|
||||
let apiUrl = 'http://www.fimfiction.net/api/story.php?story=' + STORY_ID
|
||||
let apiUrl = 'https://www.fimfiction.net/api/story.php?story=' + STORY_ID
|
||||
|
||||
let storyInfo
|
||||
let remoteResources = new Map()
|
||||
|
||||
let chapterContent = {}
|
||||
|
||||
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) {
|
||||
return
|
||||
}
|
||||
if (cachedBlob) {
|
||||
saveStory()
|
||||
return
|
||||
}
|
||||
downloadStory()
|
||||
}, false)
|
||||
}
|
||||
|
||||
function fetch(url, cb, type) {
|
||||
if (url.indexOf('//') === 0) {
|
||||
url = 'http:'+url
|
||||
|
@ -79,7 +96,7 @@ function fetchChapters(cb) {
|
|||
function recursive() {
|
||||
let ch = chapters[currentChapter]
|
||||
console.log('Fetching chapter '+ch.id+' '+ch.title)
|
||||
fetch(ch.link, function (html) {
|
||||
fetch(ch.link.replace('http', 'https'), function (html) {
|
||||
html = parseChapter(ch, html)
|
||||
chapterContent[ch.id] = html
|
||||
currentChapter++
|
||||
|
@ -96,6 +113,7 @@ function fetchChapters(cb) {
|
|||
function fetchRemote(cb) {
|
||||
let iter = remoteResources.entries()
|
||||
let counter = 0
|
||||
|
||||
function recursive() {
|
||||
let r = iter.next().value
|
||||
if (!r) {
|
||||
|
@ -105,85 +123,117 @@ function fetchRemote(cb) {
|
|||
let url = r[0]
|
||||
r = r[1]
|
||||
console.log('Fetching remote file '+r.filename, url)
|
||||
fetch(url, function (data, type) {
|
||||
r.type = type
|
||||
let dest = mimeMap[type]
|
||||
if (dest) {
|
||||
r.dest = dest.replace('*', r.filename)
|
||||
zip.file(r.dest, data)
|
||||
chrome.runtime.sendMessage(url, function (objUrl) {
|
||||
if (objUrl) {
|
||||
fetch(objUrl, function (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)
|
||||
}
|
||||
URL.revokeObjectURL(objUrl)
|
||||
counter++
|
||||
recursive()
|
||||
}, 'arraybuffer')
|
||||
} else {
|
||||
r.dest = null
|
||||
counter++
|
||||
recursive()
|
||||
}
|
||||
counter++
|
||||
recursive()
|
||||
}, 'arraybuffer')
|
||||
})
|
||||
}
|
||||
recursive()
|
||||
}
|
||||
|
||||
function downloadStory() {
|
||||
isDownloading = true
|
||||
console.log('Fetching story...')
|
||||
fetch(apiUrl, function (raw) {
|
||||
|
||||
console.log('Fetching story...')
|
||||
fetch(apiUrl, function (raw) {
|
||||
|
||||
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!
|
||||
console.log(storyInfo)
|
||||
remoteResources.set(storyInfo.full_image, {filename: 'cover'})
|
||||
|
||||
zip.file('toc.ncx', createNcx())
|
||||
zip.file('nav.xhtml', createNav())
|
||||
|
||||
fetchChapters(function () {
|
||||
|
||||
fetchRemote(function () {
|
||||
|
||||
remoteResources.forEach((r, url) => {
|
||||
if (r.chapter && r.originalUrl) {
|
||||
chapterContent[r.chapter] = chapterContent[r.chapter].replace(
|
||||
new RegExp(escapeStringRegexp(r.originalUrl), 'g'),
|
||||
r.dest
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
for (let id in chapterContent) {
|
||||
let html = chapterContent[id]
|
||||
let filename = 'chapter_'+id+'.xhtml'
|
||||
zip.file(filename, html)
|
||||
}
|
||||
|
||||
zip.file('cover.xhtml', createCoverPage())
|
||||
zip.file('content.opf', createOpf())
|
||||
|
||||
zip
|
||||
.generateNodeStream({
|
||||
type: 'nodebuffer',
|
||||
streamFiles: true,
|
||||
mimeType: 'application/epub+zip',
|
||||
compression: 'DEFLATE',
|
||||
compressionOptions: {level: 9}
|
||||
})
|
||||
.pipe(fs.createWriteStream('out.epub'))
|
||||
.on('finish', function () {
|
||||
// JSZip generates a readable stream with a "end" event,
|
||||
// but is piped here in a writable stream which emits a "finish" event.
|
||||
console.log("out.epub written.");
|
||||
})
|
||||
})
|
||||
|
||||
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!
|
||||
console.log(storyInfo)
|
||||
remoteResources.set(storyInfo.full_image, {filename: 'cover'})
|
||||
let coverImage = new Image()
|
||||
coverImage.src = storyInfo.full_image
|
||||
coverImage.addEventListener('load', function () {
|
||||
|
||||
|
||||
zip.file('toc.ncx', createNcx())
|
||||
zip.file('nav.xhtml', createNav())
|
||||
|
||||
fetchChapters(function () {
|
||||
fetchRemote(function () {
|
||||
|
||||
remoteResources.forEach((r, url) => {
|
||||
if (r.chapter && r.originalUrl && r.dest) {
|
||||
chapterContent[r.chapter] = chapterContent[r.chapter].replace(
|
||||
new RegExp(escapeStringRegexp(r.originalUrl), 'g'),
|
||||
r.dest
|
||||
)
|
||||
} else {
|
||||
r.remote = true
|
||||
}
|
||||
})
|
||||
|
||||
for (let id in chapterContent) {
|
||||
let html = chapterContent[id]
|
||||
let filename = 'chapter_'+id+'.xhtml'
|
||||
zip.file(filename, html)
|
||||
}
|
||||
|
||||
zip.file('cover.xhtml', createCoverPage(coverImage.width, coverImage.height))
|
||||
zip.file('content.opf', createOpf())
|
||||
|
||||
/*zip
|
||||
.generateNodeStream({
|
||||
type: 'nodebuffer',
|
||||
streamFiles: true,
|
||||
mimeType: 'application/epub+zip',
|
||||
compression: 'DEFLATE',
|
||||
compressionOptions: {level: 9}
|
||||
})
|
||||
.pipe(fs.createWriteStream('out.epub'))
|
||||
.on('finish', function () {
|
||||
// JSZip generates a readable stream with a "end" event,
|
||||
// but is piped here in a writable stream which emits a "finish" event.
|
||||
console.log("out.epub written.");
|
||||
})*/
|
||||
zip
|
||||
.generateAsync({
|
||||
type: 'blob',
|
||||
mimeType: 'application/epub+zip',
|
||||
compression: 'DEFLATE',
|
||||
compressionOptions: {level: 9}
|
||||
})
|
||||
.then((blob) => {
|
||||
cachedBlob = blob
|
||||
saveStory()
|
||||
isDownloading = false
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
}, false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function saveStory() {
|
||||
saveAs(cachedBlob, storyInfo.title+' by '+storyInfo.author.name+'.epub')
|
||||
}
|
||||
|
||||
//const parse5 = require('parse5')
|
||||
//const xmlserializer = require('xmlserializer')
|
||||
|
@ -288,7 +338,10 @@ function subjects(s) {
|
|||
function createOpf() {
|
||||
|
||||
let remotes = []
|
||||
remoteResources.forEach((r) => {
|
||||
remoteResources.forEach((r, url) => {
|
||||
if (!r.dest) {
|
||||
return
|
||||
}
|
||||
let attrs = {id: r.filename, href: r.dest, 'media-type': r.type}
|
||||
if (r.filename === 'cover') {
|
||||
attrs.properties = 'cover-image'
|
||||
|
@ -403,19 +456,19 @@ function createNav() {
|
|||
return navDocument
|
||||
}
|
||||
|
||||
function createCoverPage() {
|
||||
function createCoverPage(w, h) {
|
||||
let coverPage = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE html>
|
||||
`+pretty.xml(render(
|
||||
m('html', {xmlns: NS.XHTML, 'xmlns:epub': NS.OPS}, [
|
||||
m('head', [
|
||||
m('meta', {name: 'viewport', content: 'width='+100+', height='+100}),
|
||||
m('meta', {name: 'viewport', content: 'width='+w+', height='+h}),
|
||||
m('title', 'Cover'),
|
||||
m('link', {rel: 'stylesheet', type: 'text/css', href: 'coverstyle.css'})
|
||||
]),
|
||||
m('body', {'epub:type': 'cover'}, [
|
||||
m('svg', {xmlns: NS.SVG, 'xmlns:xlink': NS.XLINK, version: '1.1', viewBox: '0 0 100 100', id: 'cover'},
|
||||
m('image', {width: 100, height: 100, 'xlink:href': 'Images/cover.jpg'})
|
||||
m('svg', {xmlns: NS.SVG, 'xmlns:xlink': NS.XLINK, version: '1.1', viewBox: '0 0 '+w+' '+h, id: 'cover'},
|
||||
m('image', {width: w, height: h, 'xlink:href': 'Images/cover.jpg'})
|
||||
)
|
||||
])
|
||||
])
|
49
webpack.config.babel.js
Normal file
49
webpack.config.babel.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
'use strict'
|
||||
|
||||
import webpack from 'webpack'
|
||||
|
||||
let inProduction = process.env.NODE_ENV === 'production' || process.argv.indexOf('-p') !== -1
|
||||
|
||||
export default {
|
||||
entry: {
|
||||
fimfic2epub: ['./src/main']
|
||||
},
|
||||
output: {
|
||||
path: __dirname + '/',
|
||||
filename: './extension/[name].js',
|
||||
chunkFilename: './build/[id].js'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.js$/, loader: 'babel', exclude: /node_modules/, query: {
|
||||
sourceMaps: inProduction,
|
||||
//presets: ['es2015'],
|
||||
//plugins: ['transform-strict-mode']
|
||||
}
|
||||
}
|
||||
],
|
||||
noParse: [
|
||||
/[\/\\]node_modules[\/\\]tidy-html5[\/\\]tidy\.js$/
|
||||
]
|
||||
},
|
||||
|
||||
resolve: {
|
||||
extensions: ['', '.js', '.json'],
|
||||
root: [__dirname+'/src']
|
||||
},
|
||||
|
||||
plugins: [
|
||||
|
||||
new webpack.ProvidePlugin({
|
||||
// Detect and inject
|
||||
//tidy: 'tidy-html5'
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
|
||||
})
|
||||
],
|
||||
|
||||
devtool: 'inline-source-map',
|
||||
debug: true
|
||||
}
|
Loading…
Reference in a new issue