Merge branch 'master' into master
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
FROM node:20-alpine
|
||||
FROM node:22-alpine
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /usr/src/app
|
||||
@@ -16,6 +16,12 @@ RUN wget https://web.archive.org/web/20150803131026if_/https://kindlegen.s3.amaz
|
||||
chmod +x /usr/local/bin/kindlegen && \
|
||||
rm -rf kindlegen
|
||||
|
||||
RUN apk add --no-cache pipx
|
||||
|
||||
ENV PATH="$PATH:/root/.local/bin"
|
||||
|
||||
RUN pipx install pdfCropMargins
|
||||
|
||||
# Copy files needed by npm install
|
||||
COPY package*.json ./
|
||||
|
||||
|
||||
240
index.js
240
index.js
@@ -1,18 +1,20 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const http = require('http')
|
||||
const Koa = require('koa')
|
||||
const Router = require('@koa/router')
|
||||
const multer = require('@koa/multer')
|
||||
const logger = require('koa-logger')
|
||||
const sendfile = require('koa-sendfile')
|
||||
const serve = require('koa-static')
|
||||
const mount = require('koa-mount')
|
||||
const { mkdirp } = require('mkdirp')
|
||||
const fs = require('fs')
|
||||
const { spawn } = require('child_process')
|
||||
const { join, extname, basename, dirname } = require('path')
|
||||
const resolvepath = require('path').resolve
|
||||
const FileType = require('file-type')
|
||||
const { transliterate } = require('transliteration')
|
||||
const sanitize = require('sanitize-filename')
|
||||
|
||||
const port = 3001
|
||||
const expireDelay = 30 // 30 seconds
|
||||
@@ -78,7 +80,12 @@ function expireKey (key) {
|
||||
|
||||
function flash (ctx, data) {
|
||||
console.log(data)
|
||||
ctx.cookies.set('flash', encodeURIComponent(JSON.stringify(data)), {overwrite: true, httpOnly: false, sameSite: 'strict', maxAge: 10 * 1000})
|
||||
//ctx.cookies.set('flash', encodeURIComponent(JSON.stringify(data)), {overwrite: true, httpOnly: false, sameSite: 'strict', maxAge: 10 * 1000})
|
||||
ctx.response.status = data.success ? 200 : 400
|
||||
if (!data.success) {
|
||||
ctx.set("Connection", "close")
|
||||
}
|
||||
ctx.body = data.message
|
||||
}
|
||||
|
||||
const app = new Koa()
|
||||
@@ -104,18 +111,18 @@ const upload = multer({
|
||||
fileFilter: (req, file, cb) => {
|
||||
// Fixes charset
|
||||
// https://github.com/expressjs/multer/issues/1104#issuecomment-1152987772
|
||||
file.originalname = doTransliterate(Buffer.from(file.originalname, 'latin1').toString('utf8'))
|
||||
file.originalname = sanitize(Buffer.from(file.originalname, 'latin1').toString('utf8'))
|
||||
|
||||
console.log('Incoming file:', file)
|
||||
const key = req.body.key.toUpperCase()
|
||||
if (!app.context.keys.has(key)) {
|
||||
console.error('FileFilter: Unknown key: ' + key)
|
||||
cb(null, false)
|
||||
cb("Unknown key " + key, false)
|
||||
return
|
||||
}
|
||||
if ((!allowedTypes.includes(file.mimetype) && file.mimetype != "application/octet-stream") || !allowedExtensions.includes(extname(file.originalname.toLowerCase()).substring(1))) {
|
||||
console.error('FileFilter: File is of an invalid type ', file)
|
||||
cb(null, false)
|
||||
cb("Invalid filetype: " + JSON.stringify(file), false)
|
||||
return
|
||||
}
|
||||
cb(null, true)
|
||||
@@ -154,13 +161,39 @@ router.post('/generate', async ctx => {
|
||||
if(ctx.keys.get(key) === info) removeKey(key)
|
||||
}, maxExpireDuration * 1000)
|
||||
|
||||
ctx.cookies.set('key', key, {overwrite: true, httpOnly: false, sameSite: 'strict', maxAge: expireDelay * 1000})
|
||||
|
||||
ctx.body = key
|
||||
})
|
||||
|
||||
router.get('/download/:key', async ctx => {
|
||||
const key = ctx.params.key.toUpperCase()
|
||||
const key = ctx.cookies.get('key')
|
||||
if (!key) {
|
||||
await next()
|
||||
return
|
||||
}
|
||||
|
||||
const info = ctx.keys.get(key)
|
||||
|
||||
if (!info || !info.file) {
|
||||
await next()
|
||||
return
|
||||
}
|
||||
|
||||
ctx.redirect('/' + encodeURIComponent(info.file.name));
|
||||
})
|
||||
|
||||
async function downloadFile (ctx, next) {
|
||||
const key = ctx.cookies.get('key')
|
||||
if (!key) {
|
||||
await next()
|
||||
return
|
||||
}
|
||||
const filename = decodeURIComponent(ctx.params.filename)
|
||||
const info = ctx.keys.get(key)
|
||||
|
||||
if (!info || !info.file || info.file.name !== filename) {
|
||||
await next()
|
||||
return
|
||||
}
|
||||
if (info.agent !== ctx.get('user-agent')) {
|
||||
@@ -168,20 +201,31 @@ router.get('/download/:key', async ctx => {
|
||||
return
|
||||
}
|
||||
expireKey(key)
|
||||
// const fallback = basename(info.file.path)
|
||||
const sanename = info.file.name.replace(/[^\.\w\-"'\(\)]/g, '_')
|
||||
console.log('Sending file', [info.file.path, info.file.name, sanename])
|
||||
await sendfile(ctx, info.file.path)
|
||||
console.log('Sending file', [info.file.path, info.file.name])
|
||||
if (info.agent.includes('Kindle')) {
|
||||
// Kindle needs a safe name or it thinks it's an invalid file
|
||||
ctx.attachment(sanename)
|
||||
} else {
|
||||
// Kobo always uses fallback
|
||||
ctx.attachment(info.file.name, {fallback: sanename})
|
||||
ctx.attachment(info.file.name)
|
||||
}
|
||||
})
|
||||
await sendfile(ctx, info.file.path)
|
||||
}
|
||||
|
||||
router.post('/upload', async (ctx, next) => {
|
||||
|
||||
try {
|
||||
await upload.single('file')(ctx, () => {})
|
||||
} catch (err) {
|
||||
flash(ctx, {
|
||||
message: err,
|
||||
success: false
|
||||
})
|
||||
// ctx.throw(400, err)
|
||||
// ctx.res.end(err)
|
||||
await next()
|
||||
return
|
||||
}
|
||||
|
||||
ctx.res.writeContinue()
|
||||
|
||||
router.post('/upload', upload.single('file'), async ctx => {
|
||||
const key = ctx.request.body.key.toUpperCase()
|
||||
|
||||
if (ctx.request.file) {
|
||||
@@ -193,13 +237,13 @@ router.post('/upload', upload.single('file'), async ctx => {
|
||||
message: 'Unknown key ' + key,
|
||||
success: false
|
||||
})
|
||||
ctx.redirect('back', '/')
|
||||
if (ctx.request.file) {
|
||||
fs.unlink(ctx.request.file.path, (err) => {
|
||||
if (err) console.error(err)
|
||||
else console.log('Removed file', ctx.request.file.path)
|
||||
})
|
||||
}
|
||||
await next()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -219,16 +263,17 @@ router.post('/upload', upload.single('file'), async ctx => {
|
||||
|
||||
if (ctx.request.file) {
|
||||
if (ctx.request.file.size === 0) {
|
||||
flash(ctx, {
|
||||
message: 'Invalid file submitted',
|
||||
let data = {
|
||||
message: 'Invalid file submitted (empty file)',
|
||||
success: false,
|
||||
key: key
|
||||
})
|
||||
ctx.redirect('back', '/')
|
||||
}
|
||||
flash(ctx, data)
|
||||
fs.unlink(ctx.request.file.path, (err) => {
|
||||
if (err) console.error(err)
|
||||
else console.log('Removed file', ctx.request.file.path)
|
||||
})
|
||||
await next()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -250,28 +295,46 @@ router.post('/upload', upload.single('file'), async ctx => {
|
||||
success: false,
|
||||
key: key
|
||||
})
|
||||
ctx.redirect('back', '/')
|
||||
fs.unlink(ctx.request.file.path, (err) => {
|
||||
if (err) console.error(err)
|
||||
else console.log('Removed file', ctx.request.file.path)
|
||||
})
|
||||
await next()
|
||||
return
|
||||
}
|
||||
|
||||
let data = null
|
||||
filename = ctx.request.file.originalname
|
||||
if (ctx.request.body.transliteration) {
|
||||
filename = sanitize(doTransliterate(filename))
|
||||
}
|
||||
if (info.agent.includes('Kindle')) {
|
||||
filename = filename.replace(/[^\.\w\-"'\(\)]/g, '_')
|
||||
}
|
||||
|
||||
if (mimetype === TYPE_EPUB && info.agent.includes('Kindle')) {
|
||||
if (mimetype === TYPE_EPUB && info.agent.includes('Kindle') && ctx.request.body.kindlegen) {
|
||||
// convert to .mobi
|
||||
conversion = 'kindlegen'
|
||||
const outname = ctx.request.file.path.replace(/\.epub$/i, '.mobi')
|
||||
filename = filename.replace(/\.kepub\.epub$/i, '.epub').replace(/\.epub$/i, '.mobi')
|
||||
let stderr = ''
|
||||
|
||||
data = await new Promise((resolve, reject) => {
|
||||
let p = new Promise((resolve, reject) => {
|
||||
const kindlegen = spawn('kindlegen', [basename(ctx.request.file.path), '-dont_append_source', '-c1', '-o', basename(outname)], {
|
||||
stdio: 'inherit',
|
||||
// stdio: 'inherit',
|
||||
cwd: dirname(ctx.request.file.path)
|
||||
})
|
||||
kindlegen.once('error', function (err) {
|
||||
fs.unlink(ctx.request.file.path, (err) => {
|
||||
if (err) console.error(err)
|
||||
else console.log('Removed file', ctx.request.file.path)
|
||||
})
|
||||
fs.unlink(ctx.request.file.path.replace(/\.epub$/i, '.mobi8'), (err) => {
|
||||
if (err) console.error(err)
|
||||
else console.log('Removed file', ctx.request.file.path.replace(/\.epub$/i, '.mobi8'))
|
||||
})
|
||||
reject('kindlegen error: ' + err)
|
||||
})
|
||||
kindlegen.once('close', (code) => {
|
||||
fs.unlink(ctx.request.file.path, (err) => {
|
||||
if (err) console.error(err)
|
||||
@@ -281,64 +344,134 @@ router.post('/upload', upload.single('file'), async ctx => {
|
||||
if (err) console.error(err)
|
||||
else console.log('Removed file', ctx.request.file.path.replace(/\.epub$/i, '.mobi8'))
|
||||
})
|
||||
if (code !== 0) {
|
||||
console.warn('kindlegen error code ' + code)
|
||||
if (code !== 0 && code !== 1) {
|
||||
reject('kindlegen error code: ' + code + '\n' + stderr)
|
||||
return
|
||||
}
|
||||
|
||||
resolve(outname)
|
||||
})
|
||||
kindlegen.stdout.on('data', function (str) {
|
||||
stderr += str
|
||||
console.log('kindlegen: ' + str)
|
||||
})
|
||||
kindlegen.stderr.on('data', function (str) {
|
||||
stderr += str
|
||||
console.log('kindlegen: ' + str)
|
||||
})
|
||||
})
|
||||
try {
|
||||
data = await p
|
||||
} catch (err) {
|
||||
flash(ctx, {
|
||||
success: false,
|
||||
message: err.replaceAll(basename(ctx.request.file.path), "infile.epub").replaceAll(basename(outname), "outfile.mobi")
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
} else if (mimetype === TYPE_EPUB && info.agent.includes('Kobo') && ctx.request.body.kepubify) {
|
||||
} else if (mimetype === TYPE_EPUB && (info.agent.includes('Kobo') || info.agent.toLowerCase().includes('tolino')) && ctx.request.body.kepubify) {
|
||||
// convert to Kobo EPUB
|
||||
conversion = 'kepubify'
|
||||
const outname = ctx.request.file.path.replace(/\.epub$/i, '.kepub.epub')
|
||||
filename = filename.replace(/\.kepub\.epub$/i, '.epub').replace(/\.epub$/i, '.kepub.epub')
|
||||
|
||||
data = await new Promise((resolve, reject) => {
|
||||
let p = new Promise((resolve, reject) => {
|
||||
let stderr = ''
|
||||
const kepubify = spawn('kepubify', ['-v', '-u', '-o', basename(outname), basename(ctx.request.file.path)], {
|
||||
stdio: 'inherit',
|
||||
//stdio: 'inherit',
|
||||
cwd: dirname(ctx.request.file.path)
|
||||
})
|
||||
kepubify.once('error', function (err) {
|
||||
fs.unlink(ctx.request.file.path, (err) => {
|
||||
if (err) console.error(err)
|
||||
else console.log('Removed file', ctx.request.file.path)
|
||||
})
|
||||
reject('kepubify error: ' + err)
|
||||
})
|
||||
kepubify.once('close', (code) => {
|
||||
fs.unlink(ctx.request.file.path, (err) => {
|
||||
if (err) console.error(err)
|
||||
else console.log('Removed file', ctx.request.file.path)
|
||||
})
|
||||
if (code !== 0) {
|
||||
reject('kepubify error code ' + code)
|
||||
reject('Kepubify error code: ' + code + '\n' + stderr)
|
||||
return
|
||||
}
|
||||
|
||||
resolve(outname)
|
||||
})
|
||||
kepubify.stdout.on('data', function (str) {
|
||||
stderr += str
|
||||
console.log('kepubify: ' + str)
|
||||
})
|
||||
kepubify.stderr.on('data', function (str) {
|
||||
stderr += str
|
||||
console.log('kepubify: ' + str)
|
||||
})
|
||||
})
|
||||
} else if (mimetype === 'application/pdf' && ctx.request.body.pdfCropMargins) {
|
||||
try {
|
||||
data = await p
|
||||
} catch (err) {
|
||||
flash(ctx, {
|
||||
success: false,
|
||||
message: err.replaceAll(basename(ctx.request.file.path), "infile.epub").replaceAll(basename(outname), "outfile.kepub.epub")
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
data = await new Promise((resolve, reject) => {
|
||||
const pdfcropmargins = spawn('pdfcropmargins', ['-s', '-u', basename(ctx.request.file.path)], {
|
||||
stdio: 'inherit',
|
||||
} else if (mimetype == 'application/pdf' && ctx.request.body.pdfcropmargins) {
|
||||
const dir = dirname(ctx.request.file.path)
|
||||
const base = basename(ctx.request.file.path, '.pdf')
|
||||
const outfile = resolvepath(join(dir, `${base}_cropped.pdf`))
|
||||
let p = new Promise((resolve, reject) => {
|
||||
let stderr = ''
|
||||
const pdfcropmargins = spawn('pdfcropmargins', ['-s', '-u', '-o', outfile, basename(ctx.request.file.path)], {
|
||||
// stdio: 'inherit',
|
||||
cwd: dirname(ctx.request.file.path)
|
||||
})
|
||||
pdfcropmargins.once('error', function (err) {
|
||||
fs.unlink(ctx.request.file.path, (err) => {
|
||||
if (err) console.error(err)
|
||||
else console.log('Removed file', ctx.request.file.path)
|
||||
})
|
||||
reject('pdfcropmargins error: ' + err)
|
||||
})
|
||||
pdfcropmargins.once('close', (code) => {
|
||||
fs.unlink(ctx.request.file.path, (err) => {
|
||||
if (err) console.error(err)
|
||||
else console.log('Removed file', ctx.request.file.path)
|
||||
})
|
||||
if (code !== 0) {
|
||||
reject('pdfcropmargins error code ' + code)
|
||||
reject('pdfcropmargins error code: ' + code + '\n' + stderr)
|
||||
return
|
||||
}
|
||||
const dir = dirname(ctx.request.file.path);
|
||||
const base = basename(ctx.request.file.path, '.pdf');
|
||||
|
||||
resolve(join(dir, `${base}_cropped.pdf`))
|
||||
|
||||
resolve(outfile)
|
||||
})
|
||||
pdfcropmargins.stdout.on('data', function (str) {
|
||||
stderr += str
|
||||
console.log('pdfcropmargins: ' + str)
|
||||
})
|
||||
pdfcropmargins.stderr.on('data', function (str) {
|
||||
stderr += str
|
||||
console.log('pdfcropmargins: ' + str)
|
||||
})
|
||||
})
|
||||
|
||||
try {
|
||||
data = await p
|
||||
} catch (err) {
|
||||
flash(ctx, {
|
||||
success: false,
|
||||
message: err.replaceAll(basename(ctx.request.file.path), "infile.pdf").replaceAll(outfile, "outfile.pdf")
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
// No conversion
|
||||
data = ctx.request.file.path
|
||||
filename = filename.replace(/\.epub$/i, '.epub').replace(/\.pdf$/i, '.pdf')
|
||||
}
|
||||
|
||||
expireKey(key)
|
||||
@@ -359,7 +492,8 @@ router.post('/upload', upload.single('file'), async ctx => {
|
||||
|
||||
let messages = []
|
||||
if (ctx.request.file) {
|
||||
messages.push('Upload successful! ' + (conversion ? ' Ebook was converted with ' + conversion + ' and sent' : ' Sent')+' to '+(info.agent.includes('Kobo') ? 'a Kobo device.' : (info.agent.includes('Kindle') ? 'a Kindle device.' : 'a device.')))
|
||||
ctx.request.file.skip = true
|
||||
messages.push('Upload successful! ' + (conversion ? 'Ebook was converted with ' + conversion + ' and sent' : 'Sent')+' to '+(info.agent.includes('Kobo') ? 'a Kobo device.' : (info.agent.includes('Kindle') ? 'a Kindle device.' : 'a device.')))
|
||||
messages.push('Filename: ' + filename)
|
||||
}
|
||||
if (url) {
|
||||
@@ -372,7 +506,7 @@ router.post('/upload', upload.single('file'), async ctx => {
|
||||
success: false,
|
||||
key: key
|
||||
})
|
||||
ctx.redirect('back', '/')
|
||||
await next()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -382,7 +516,8 @@ router.post('/upload', upload.single('file'), async ctx => {
|
||||
key: key,
|
||||
url: url
|
||||
})
|
||||
ctx.redirect('back', '/')
|
||||
|
||||
await next()
|
||||
})
|
||||
|
||||
router.delete('/file/:key', async ctx => {
|
||||
@@ -399,6 +534,7 @@ router.get('/status/:key', async ctx => {
|
||||
const key = ctx.params.key.toUpperCase()
|
||||
const info = ctx.keys.get(key)
|
||||
if (!info) {
|
||||
ctx.response.status = 404
|
||||
ctx.body = {error: 'Unknown key'}
|
||||
return
|
||||
}
|
||||
@@ -408,6 +544,7 @@ router.get('/status/:key', async ctx => {
|
||||
return
|
||||
}
|
||||
expireKey(key)
|
||||
ctx.cookies.set('key', key, {overwrite: true, httpOnly: false, sameSite: 'strict', maxAge: expireDelay * 1000})
|
||||
ctx.body = {
|
||||
alive: info.alive,
|
||||
file: info.file ? {
|
||||
@@ -425,19 +562,28 @@ router.get('/receive', async ctx => {
|
||||
router.get('/', async ctx => {
|
||||
const agent = ctx.get('user-agent')
|
||||
console.log(ctx.ip, agent)
|
||||
await sendfile(ctx, agent.includes('Kobo') || agent.includes('Kindle')? 'static/download.html' : 'static/upload.html')
|
||||
await sendfile(ctx, agent.includes('Kobo') || agent.includes('Kindle') || agent.toLowerCase().includes('tolino') ? 'static/download.html' : 'static/upload.html')
|
||||
})
|
||||
|
||||
router.get('/:filename', downloadFile)
|
||||
|
||||
app.use(serve("static"))
|
||||
|
||||
app.use(router.routes())
|
||||
app.use(router.allowedMethods())
|
||||
|
||||
app.use(serve("static"))
|
||||
|
||||
fs.rm('uploads', {recursive: true}, (err) => {
|
||||
if (err) throw err
|
||||
mkdirp('uploads').then (() => {
|
||||
app.listen(port)
|
||||
// app.listen(port)
|
||||
const fn = app.callback()
|
||||
const server = http.createServer(fn)
|
||||
server.on('checkContinue', (req, res) => {
|
||||
console.log("check continue!")
|
||||
fn(req, res)
|
||||
})
|
||||
server.listen(port)
|
||||
console.log('server is listening on port ' + port)
|
||||
})
|
||||
})
|
||||
|
||||
1355
package-lock.json
generated
1355
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,8 @@
|
||||
"description": "Send ebooks to Kobo/Kindle ereaders easily",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index"
|
||||
"start": "node index",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"author": "djazz",
|
||||
"license": "MIT",
|
||||
@@ -12,13 +13,14 @@
|
||||
"@koa/multer": "^3.0.2",
|
||||
"@koa/router": "^12.0.1",
|
||||
"file-type": "^16.5.4",
|
||||
"koa": "^2.15.0",
|
||||
"koa": "^2.15.3",
|
||||
"koa-logger": "^3.2.1",
|
||||
"koa-mount": "^4.0.0",
|
||||
"koa-sendfile": "^3.0.0",
|
||||
"koa-static": "^5.0.0",
|
||||
"mkdirp": "^3.0.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"patch-package": "^8.0.0",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"transliteration": "^2.3.5"
|
||||
}
|
||||
}
|
||||
|
||||
15
patches/@koa+multer+3.0.2.patch
Normal file
15
patches/@koa+multer+3.0.2.patch
Normal file
@@ -0,0 +1,15 @@
|
||||
diff --git a/node_modules/@koa/multer/index.js b/node_modules/@koa/multer/index.js
|
||||
index d5be076..252208e 100644
|
||||
--- a/node_modules/@koa/multer/index.js
|
||||
+++ b/node_modules/@koa/multer/index.js
|
||||
@@ -11,9 +11,7 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
-let originalMulter = require('fix-esm').require('multer');
|
||||
-
|
||||
-if (originalMulter.default) originalMulter = originalMulter.default;
|
||||
+let originalMulter = require('multer');
|
||||
|
||||
function multer(options) {
|
||||
const m = originalMulter(options);
|
||||
@@ -78,12 +78,10 @@ function pollFile () {
|
||||
downloads.style.display = 'none'
|
||||
}
|
||||
var urls = data.urls && data.urls.length > 0 ? data.urls : null
|
||||
if (data.file || urls) {
|
||||
} else {
|
||||
}
|
||||
if (data.file) {
|
||||
downloads.style.display = 'block'
|
||||
downloadlink.textContent = data.file.name
|
||||
downloadlink.href = '/' + encodeURIComponent(data.file.name)
|
||||
} else {
|
||||
downloads.style.display = 'none'
|
||||
}
|
||||
@@ -111,7 +109,7 @@ function generateKey () {
|
||||
if (x.responseText !== 'error' && x.status === 200) {
|
||||
key = x.responseText
|
||||
keyOutput.textContent = key
|
||||
downloadlink.href = './download/' + key
|
||||
downloadlink.href = ''
|
||||
if (pollTimer) clearInterval(pollTimer)
|
||||
pollTimer = setInterval(pollFile, 5 * 1000)
|
||||
} else {
|
||||
|
||||
@@ -104,6 +104,7 @@ input[type="url"], input[type="text"] {
|
||||
#uploadstatus.error {
|
||||
background-color: #FDD;
|
||||
border: 1px solid #F77;
|
||||
white-space: pre;
|
||||
}
|
||||
td {
|
||||
padding: 10px;
|
||||
|
||||
@@ -11,27 +11,28 @@
|
||||
|
||||
<div class="wrapper">
|
||||
<h1 class="center">Send to Kobo/Kindle</h1>
|
||||
|
||||
<form action="./upload" method="post" enctype="multipart/form-data">
|
||||
<form action="./upload" method="post" enctype="multipart/form-data" id="uploadform">
|
||||
<table style="margin: 0 auto;" cellpadding=0 cellspacing=0>
|
||||
<tr><td class="right"><label for="keyinput"><strong>Unique key</strong></label></td><td><input type="text" name="key" id="keyinput" autocomplete="off" pattern="...." placeholder="––––" required style="text-transform: uppercase;" maxlength=4/></td></tr>
|
||||
<tr><td class="right aligntop"><label for="fileinput"><strong>Ebook file</strong></label><br/><em>EPUB, MOBI, PDF,<br/>TXT, CBZ, CBR</em></td><td class="aligntop"><label for="fileinput" id="choosebtn">Choose file</label><input type="file" name="file" id="fileinput" accept=".txt,.epub,.mobi,.pdf,.cbz,.cbr,application/epub+zip,application/epub,application/x-mobipocket-ebook,application/pdf,application/vnd.comicbook+zip,application/vnd.comicbook-rar"/><br/><br/><div id="fileinfo"></div></td></tr>
|
||||
<tr><td class="right"><label for="urlinput"><strong>Send url</strong></label></td><td><input type="url" name="url" id="urlinput" autocomplete="off" style="width: 100%"></td></tr>
|
||||
<tr><td class="right"><label for="kepubify"><strong>Kepubify</strong><br/><em>Kobo only</em></label></td><td><input type="checkbox" name="kepubify" id="kepubify" checked /></td></tr>
|
||||
<tr><td class="right"><label for="kepubify"><strong>pdfCropMargins</strong><br/><em>PDF files only</em></label></td><td><input type="checkbox" name="pdfCropMargins" id="pdfCropMargins" checked /></td></tr>
|
||||
<tr><td class="right"><input type="checkbox" name="kepubify" id="kepubify" checked /></td><td><label for="kepubify"><strong>Kepubify</strong> <em>Kobo only</em></label></td></tr>
|
||||
<tr><td class="right"><input type="checkbox" name="kindlegen" id="kindlegen" checked /></td><td><label for="kindlegen"><strong>KindleGen</strong> <em>Kindle only</em></label></td></tr>
|
||||
<tr><td class="right"><input type="checkbox" name="pdfcropmargins" id="pdfcropmargins" /></td><td><label for="pdfcropmargins"><strong>Crop margins of pdfs</strong></label></td></tr>
|
||||
<tr><td class="right"><input type="checkbox" name="transliteration" id="transliteration" /></td><td><label for="transliteration"><strong>Transliteration of filename</strong></label></td></tr>
|
||||
<tr class="center"><td colspan="2"><input type="submit" value="Upload and send" /></td></tr>
|
||||
</table>
|
||||
<div id="uploadstatus"></div>
|
||||
</form>
|
||||
<div style="padding: 15px; padding-top: 0; text-align: justify;">
|
||||
<p>Go this this page on your Kobo/Kindle ereader and you see a unique key. Enter it in this form and upload an ebook and it will appear as a download link on the ereader.</p>
|
||||
<p>If you send an EPUB file to to a Kindle it will be converted to MOBI with KindleGen. If you send a MOBI file to a Kindle it will be sent unprocessed. If you send an EPUB file and tick the Kepubify checkbox, it will be converted into a Kobo EPUB using Kepubify. If you send a MOBI file to a Kobo, it will not be converted.</p>
|
||||
<p>If you send an EPUB file to to a Kindle it will be converted to MOBI with KindleGen. If you send a MOBI file to a Kindle it will be sent unprocessed. Files sent to Kindle eReaders will have their names stripped of special characters, a limitation of the Kindle browser. If you send an EPUB file and tick the Kepubify checkbox, it will be converted into a Kobo EPUB using Kepubify. If you send a MOBI file to a Kobo, it will not be converted.</p>
|
||||
<p>Your ebook will be stored on the server as long as your Kobo/Kindle is viewing the unique key and is connected to wifi. It will be deleted irrevocably when the key expires about 30 seconds after you close the browser, generate a new key or disable wifi on your ereader.</p>
|
||||
<p>By using this tool you agree that the ebook you upload is processed on the server and stored for a short time.</p>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="center">
|
||||
Created by djazz. Powered by <a href="https://koajs.com/" target="_blank">Koa</a>, <a href="https://pgaskin.net/kepubify/" target="_blank">Kepubify</a>, <a href="https://github.com/abarker/pdfCropMargins" target="_blank">pdfCropMargins</a> and <a href="https://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211" target="_blank">KindleGen</a><br/>
|
||||
Created by djazz. Powered by <a href="https://koajs.com/" target="_blank">Koa</a>, <a href="https://pgaskin.net/kepubify/" target="_blank">Kepubify</a>, <a href="https://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211" target="_blank">KindleGen</a> and <a href="https://github.com/abarker/pdfCropMargins" target="_blank">pdfCropMargins</a><br/>
|
||||
Source code on <a href="https://github.com/daniel-j/send2ereader" target="_blank">Github</a> - <a id="siteurl">https://send.djazz.se</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,6 +40,7 @@
|
||||
<div id="logs"></div>
|
||||
|
||||
<script>
|
||||
var uploadform = document.getElementById('uploadform')
|
||||
var uploadstatus = document.getElementById('uploadstatus')
|
||||
var keyinput = document.getElementById('keyinput')
|
||||
var fileinput = document.getElementById('fileinput')
|
||||
@@ -47,34 +49,49 @@ var fileinfo = document.getElementById('fileinfo')
|
||||
var urlinput = document.getElementById('urlinput')
|
||||
var siteurl = document.getElementById('siteurl')
|
||||
|
||||
var flash = getCookies().flash
|
||||
// deleteCookie('flash')
|
||||
var flashtimer = null
|
||||
|
||||
if (flash) {
|
||||
if (flash.message) {
|
||||
if (flash.success) {
|
||||
uploadstatus.className = " success"
|
||||
uploadstatus.innerHTML = flash.message
|
||||
} else {
|
||||
uploadstatus.className = " error"
|
||||
uploadstatus.textContent = flash.message
|
||||
}
|
||||
uploadstatus.style.opacity = 1
|
||||
}
|
||||
keyinput.value = flash.key || ''
|
||||
urlinput.value = flash.url || ''
|
||||
}
|
||||
uploadstatus.addEventListener('click', function () {
|
||||
function hideUploadStatus() {
|
||||
uploadstatus.style.opacity = 0
|
||||
setTimeout(function () {
|
||||
clearTimeout(flashtimer)
|
||||
flashtimer = setTimeout(function () {
|
||||
uploadstatus.textContent = ''
|
||||
uploadstatus.className = ''
|
||||
}, 500)
|
||||
}
|
||||
|
||||
function handleFlash(flash) {
|
||||
// if (!flash) getCookies().flash
|
||||
console.log(flash)
|
||||
clearTimeout(flashtimer)
|
||||
if (flash) {
|
||||
if (flash.message) {
|
||||
if (flash.success) {
|
||||
uploadstatus.className = " success"
|
||||
uploadstatus.innerHTML = flash.message.trim()
|
||||
} else {
|
||||
uploadstatus.className = " error"
|
||||
uploadstatus.textContent = flash.message.trim()
|
||||
}
|
||||
uploadstatus.style.opacity = 1
|
||||
}
|
||||
if (flash.key) {
|
||||
keyinput.value = flash.key
|
||||
}
|
||||
if (flash.url) {
|
||||
urlinput.value = flash.url
|
||||
}
|
||||
} else {
|
||||
hideUploadStatus()
|
||||
}
|
||||
}
|
||||
|
||||
// handleFlash()
|
||||
|
||||
uploadstatus.addEventListener('click', function () {
|
||||
hideUploadStatus()
|
||||
}, false)
|
||||
|
||||
document.body.addEventListener("drop", function () {
|
||||
log("file dropped")
|
||||
}, false)
|
||||
|
||||
function fileinputChange () {
|
||||
if (!fileinput.files[0] || fileinput.files.length === 0) {
|
||||
@@ -116,6 +133,50 @@ if (isIOS) {
|
||||
fileinput.accept = ''
|
||||
}
|
||||
|
||||
uploadform.addEventListener('submit', function (e) {
|
||||
hideUploadStatus()
|
||||
e.preventDefault()
|
||||
var fd = new FormData(uploadform)
|
||||
var req = new XMLHttpRequest()
|
||||
req.open('POST', uploadform.action, true)
|
||||
req.upload.onprogress = function (e) {
|
||||
if (e.lengthComputable) {
|
||||
console.log("progress: " + e.loaded / e.total)
|
||||
var complete = e.loaded / e.total == 1
|
||||
handleFlash({
|
||||
success: true,
|
||||
message: !complete ? "Uploading file... " + Math.round(100 * e.loaded / e.total) + '%' : "Processing uploaded file... please wait"
|
||||
})
|
||||
}
|
||||
}
|
||||
req.onreadystatechange = function (e) {
|
||||
console.log(req.readyState, req.status)
|
||||
}
|
||||
req.onload = function () {
|
||||
console.log('upload ok', req.status, req.responseText, req.responseType)
|
||||
handleFlash({
|
||||
success: req.status == 200,
|
||||
message: req.responseText
|
||||
})
|
||||
}
|
||||
req.onerror = function () {
|
||||
console.log('upload error', req.status, req.responseText, req.responseType)
|
||||
handleFlash({
|
||||
success: false,
|
||||
message: "Upload error - is the key correct?"
|
||||
})
|
||||
}
|
||||
req.onabort = function () {
|
||||
console.log('aborted', req.status)
|
||||
handleFlash({
|
||||
success: false,
|
||||
message: "Upload aborted"
|
||||
})
|
||||
}
|
||||
req.send(fd)
|
||||
return false
|
||||
}, false)
|
||||
|
||||
siteurl.textContent = window.location.href
|
||||
siteurl.href = siteurl.textContent
|
||||
siteurl.target = '_self'
|
||||
|
||||
Reference in New Issue
Block a user