mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-03 00:06:46 +01:00
Remove unused ebook routes
This commit is contained in:
parent
ef954ee68f
commit
05b102722b
@ -35,7 +35,6 @@ const AudioMetadataMangaer = require('./managers/AudioMetadataManager')
|
|||||||
const RssFeedManager = require('./managers/RssFeedManager')
|
const RssFeedManager = require('./managers/RssFeedManager')
|
||||||
const CronManager = require('./managers/CronManager')
|
const CronManager = require('./managers/CronManager')
|
||||||
const TaskManager = require('./managers/TaskManager')
|
const TaskManager = require('./managers/TaskManager')
|
||||||
const EBookManager = require('./managers/EBookManager')
|
|
||||||
|
|
||||||
class Server {
|
class Server {
|
||||||
constructor(SOURCE, PORT, HOST, UID, GID, CONFIG_PATH, METADATA_PATH, ROUTER_BASE_PATH) {
|
constructor(SOURCE, PORT, HOST, UID, GID, CONFIG_PATH, METADATA_PATH, ROUTER_BASE_PATH) {
|
||||||
@ -75,7 +74,6 @@ class Server {
|
|||||||
this.podcastManager = new PodcastManager(this.db, this.watcher, this.notificationManager, this.taskManager)
|
this.podcastManager = new PodcastManager(this.db, this.watcher, this.notificationManager, this.taskManager)
|
||||||
this.audioMetadataManager = new AudioMetadataMangaer(this.db, this.taskManager)
|
this.audioMetadataManager = new AudioMetadataMangaer(this.db, this.taskManager)
|
||||||
this.rssFeedManager = new RssFeedManager(this.db)
|
this.rssFeedManager = new RssFeedManager(this.db)
|
||||||
this.eBookManager = new EBookManager(this.db)
|
|
||||||
|
|
||||||
this.scanner = new Scanner(this.db, this.coverManager)
|
this.scanner = new Scanner(this.db, this.coverManager)
|
||||||
this.cronManager = new CronManager(this.db, this.scanner, this.podcastManager)
|
this.cronManager = new CronManager(this.db, this.scanner, this.podcastManager)
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
const Logger = require('../Logger')
|
|
||||||
const { isNullOrNaN } = require('../utils/index')
|
|
||||||
|
|
||||||
class EBookController {
|
|
||||||
constructor() { }
|
|
||||||
|
|
||||||
async getEbookInfo(req, res) {
|
|
||||||
const isDev = req.query.dev == 1
|
|
||||||
const json = await this.eBookManager.getBookInfo(req.libraryItem, req.user, isDev)
|
|
||||||
res.json(json)
|
|
||||||
}
|
|
||||||
|
|
||||||
async getEbookPage(req, res) {
|
|
||||||
if (isNullOrNaN(req.params.page)) {
|
|
||||||
return res.status(400).send('Invalid page params')
|
|
||||||
}
|
|
||||||
const isDev = req.query.dev == 1
|
|
||||||
const pageIndex = Number(req.params.page)
|
|
||||||
const page = await this.eBookManager.getBookPage(req.libraryItem, req.user, pageIndex, isDev)
|
|
||||||
if (!page) {
|
|
||||||
return res.status(500).send('Failed to get page')
|
|
||||||
}
|
|
||||||
|
|
||||||
res.send(page)
|
|
||||||
}
|
|
||||||
|
|
||||||
async getEbookResource(req, res) {
|
|
||||||
if (!req.query.path) {
|
|
||||||
return res.status(400).send('Invalid query path')
|
|
||||||
}
|
|
||||||
const isDev = req.query.dev == 1
|
|
||||||
this.eBookManager.getBookResource(req.libraryItem, req.user, req.query.path, isDev, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
middleware(req, res, next) {
|
|
||||||
const item = this.db.libraryItems.find(li => li.id === req.params.id)
|
|
||||||
if (!item || !item.media) return res.sendStatus(404)
|
|
||||||
|
|
||||||
// Check user can access this library item
|
|
||||||
if (!req.user.checkCanAccessLibraryItem(item)) {
|
|
||||||
return res.sendStatus(403)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!item.isBook || !item.media.ebookFile) {
|
|
||||||
return res.status(400).send('Invalid ebook library item')
|
|
||||||
}
|
|
||||||
|
|
||||||
req.libraryItem = item
|
|
||||||
next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = new EBookController()
|
|
@ -1,80 +0,0 @@
|
|||||||
const Logger = require('../Logger')
|
|
||||||
const StreamZip = require('../libs/nodeStreamZip')
|
|
||||||
|
|
||||||
const parseEpub = require('../utils/parsers/parseEpub')
|
|
||||||
|
|
||||||
class EBookManager {
|
|
||||||
constructor() {
|
|
||||||
this.extractedEpubs = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
async extractBookData(libraryItem, user, isDev = false) {
|
|
||||||
if (!libraryItem || !libraryItem.isBook || !libraryItem.media.ebookFile) return null
|
|
||||||
|
|
||||||
if (this.extractedEpubs[libraryItem.id]) return this.extractedEpubs[libraryItem.id]
|
|
||||||
|
|
||||||
const ebookFile = libraryItem.media.ebookFile
|
|
||||||
if (!ebookFile.isEpub) {
|
|
||||||
Logger.error(`[EBookManager] get book data is not supported for format ${ebookFile.ebookFormat}`)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
this.extractedEpubs[libraryItem.id] = await parseEpub.parse(ebookFile, libraryItem.id, user.token, isDev)
|
|
||||||
|
|
||||||
return this.extractedEpubs[libraryItem.id]
|
|
||||||
}
|
|
||||||
|
|
||||||
async getBookInfo(libraryItem, user, isDev = false) {
|
|
||||||
if (!libraryItem || !libraryItem.isBook || !libraryItem.media.ebookFile) return null
|
|
||||||
|
|
||||||
const bookData = await this.extractBookData(libraryItem, user, isDev)
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: libraryItem.media.metadata.title,
|
|
||||||
pages: bookData.pages.length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getBookPage(libraryItem, user, pageIndex, isDev = false) {
|
|
||||||
if (!libraryItem || !libraryItem.isBook || !libraryItem.media.ebookFile) return null
|
|
||||||
|
|
||||||
const bookData = await this.extractBookData(libraryItem, user, isDev)
|
|
||||||
|
|
||||||
const pageObj = bookData.pages[pageIndex]
|
|
||||||
|
|
||||||
if (!pageObj) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsed = await parseEpub.parsePage(pageObj.path, bookData, libraryItem.id, user.token, isDev)
|
|
||||||
|
|
||||||
if (parsed.error) {
|
|
||||||
Logger.error(`[EBookManager] Failed to parse epub page at "${pageObj.path}"`, parsed.error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsed.html
|
|
||||||
}
|
|
||||||
|
|
||||||
async getBookResource(libraryItem, user, resourcePath, isDev = false, res) {
|
|
||||||
if (!libraryItem || !libraryItem.isBook || !libraryItem.media.ebookFile) return res.sendStatus(500)
|
|
||||||
const bookData = await this.extractBookData(libraryItem, user, isDev)
|
|
||||||
const resourceItem = bookData.resources.find(r => r.path === resourcePath)
|
|
||||||
|
|
||||||
if (!resourceItem) {
|
|
||||||
return res.status(404).send('Resource not found')
|
|
||||||
}
|
|
||||||
|
|
||||||
const zip = new StreamZip.async({ file: bookData.filepath })
|
|
||||||
const stm = await zip.stream(resourceItem.path)
|
|
||||||
|
|
||||||
res.set('content-type', resourceItem['media-type'])
|
|
||||||
|
|
||||||
stm.pipe(res)
|
|
||||||
stm.on('end', () => {
|
|
||||||
zip.close()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
module.exports = EBookManager
|
|
@ -24,7 +24,6 @@ const SearchController = require('../controllers/SearchController')
|
|||||||
const CacheController = require('../controllers/CacheController')
|
const CacheController = require('../controllers/CacheController')
|
||||||
const ToolsController = require('../controllers/ToolsController')
|
const ToolsController = require('../controllers/ToolsController')
|
||||||
const RSSFeedController = require('../controllers/RSSFeedController')
|
const RSSFeedController = require('../controllers/RSSFeedController')
|
||||||
const EBookController = require('../controllers/EBookController')
|
|
||||||
const MiscController = require('../controllers/MiscController')
|
const MiscController = require('../controllers/MiscController')
|
||||||
|
|
||||||
const BookFinder = require('../finders/BookFinder')
|
const BookFinder = require('../finders/BookFinder')
|
||||||
@ -52,7 +51,6 @@ class ApiRouter {
|
|||||||
this.cronManager = Server.cronManager
|
this.cronManager = Server.cronManager
|
||||||
this.notificationManager = Server.notificationManager
|
this.notificationManager = Server.notificationManager
|
||||||
this.taskManager = Server.taskManager
|
this.taskManager = Server.taskManager
|
||||||
this.eBookManager = Server.eBookManager
|
|
||||||
|
|
||||||
this.bookFinder = new BookFinder()
|
this.bookFinder = new BookFinder()
|
||||||
this.authorFinder = new AuthorFinder()
|
this.authorFinder = new AuthorFinder()
|
||||||
@ -284,13 +282,6 @@ class ApiRouter {
|
|||||||
this.router.post('/feeds/series/:seriesId/open', RSSFeedController.middleware.bind(this), RSSFeedController.openRSSFeedForSeries.bind(this))
|
this.router.post('/feeds/series/:seriesId/open', RSSFeedController.middleware.bind(this), RSSFeedController.openRSSFeedForSeries.bind(this))
|
||||||
this.router.post('/feeds/:id/close', RSSFeedController.middleware.bind(this), RSSFeedController.closeRSSFeed.bind(this))
|
this.router.post('/feeds/:id/close', RSSFeedController.middleware.bind(this), RSSFeedController.closeRSSFeed.bind(this))
|
||||||
|
|
||||||
//
|
|
||||||
// EBook Routes
|
|
||||||
//
|
|
||||||
this.router.get('/ebooks/:id/info', EBookController.middleware.bind(this), EBookController.getEbookInfo.bind(this))
|
|
||||||
this.router.get('/ebooks/:id/page/:page', EBookController.middleware.bind(this), EBookController.getEbookPage.bind(this))
|
|
||||||
this.router.get('/ebooks/:id/resource', EBookController.middleware.bind(this), EBookController.getEbookResource.bind(this))
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Misc Routes
|
// Misc Routes
|
||||||
//
|
//
|
||||||
|
@ -1,226 +0,0 @@
|
|||||||
|
|
||||||
const Path = require('path')
|
|
||||||
const h = require('htmlparser2')
|
|
||||||
const ds = require('dom-serializer')
|
|
||||||
|
|
||||||
const Logger = require('../../Logger')
|
|
||||||
const StreamZip = require('../../libs/nodeStreamZip')
|
|
||||||
const css = require('../../libs/css')
|
|
||||||
|
|
||||||
const { xmlToJSON } = require('../index.js')
|
|
||||||
|
|
||||||
module.exports.parse = async (ebookFile, libraryItemId, token, isDev) => {
|
|
||||||
const zip = new StreamZip.async({ file: ebookFile.metadata.path })
|
|
||||||
const containerXml = await zip.entryData('META-INF/container.xml')
|
|
||||||
const containerJson = await xmlToJSON(containerXml.toString('utf8'))
|
|
||||||
|
|
||||||
const packageOpfPath = containerJson.container.rootfiles[0].rootfile[0].$['full-path']
|
|
||||||
const packageOpfDir = Path.dirname(packageOpfPath)
|
|
||||||
|
|
||||||
const packageDoc = await zip.entryData(packageOpfPath)
|
|
||||||
const packageJson = await xmlToJSON(packageDoc.toString('utf8'))
|
|
||||||
|
|
||||||
const pages = []
|
|
||||||
|
|
||||||
let manifestItems = packageJson.package.manifest[0].item.map(item => item.$)
|
|
||||||
const spineItems = packageJson.package.spine[0].itemref.map(ref => ref.$.idref)
|
|
||||||
for (const spineItem of spineItems) {
|
|
||||||
const mi = manifestItems.find(i => i.id === spineItem)
|
|
||||||
if (mi) {
|
|
||||||
manifestItems = manifestItems.filter(_mi => _mi.id !== mi.id) // Remove from manifest items
|
|
||||||
|
|
||||||
mi.path = Path.posix.join(packageOpfDir, mi.href)
|
|
||||||
pages.push(mi)
|
|
||||||
} else {
|
|
||||||
Logger.error('[parseEpub] Invalid spine item', spineItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const stylesheets = []
|
|
||||||
const resources = []
|
|
||||||
|
|
||||||
for (const manifestItem of manifestItems) {
|
|
||||||
manifestItem.path = Path.posix.join(packageOpfDir, manifestItem.href)
|
|
||||||
|
|
||||||
if (manifestItem['media-type'] === 'text/css') {
|
|
||||||
const stylesheetData = await zip.entryData(manifestItem.path)
|
|
||||||
const modifiedCss = this.parseStylesheet(stylesheetData.toString('utf8'), manifestItem.path, libraryItemId, token, isDev)
|
|
||||||
if (modifiedCss) {
|
|
||||||
manifestItem.style = modifiedCss
|
|
||||||
stylesheets.push(manifestItem)
|
|
||||||
} else {
|
|
||||||
Logger.error(`[parseEpub] Invalid stylesheet "${manifestItem.path}"`)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resources.push(manifestItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await zip.close()
|
|
||||||
|
|
||||||
return {
|
|
||||||
filepath: ebookFile.metadata.path,
|
|
||||||
epubVersion: packageJson.package.$.version,
|
|
||||||
packageDir: packageOpfDir,
|
|
||||||
resources,
|
|
||||||
stylesheets,
|
|
||||||
pages
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.parsePage = async (pagePath, bookData, libraryItemId, token, isDev) => {
|
|
||||||
const pageDir = Path.dirname(pagePath)
|
|
||||||
|
|
||||||
const zip = new StreamZip.async({ file: bookData.filepath })
|
|
||||||
const pageData = await zip.entryData(pagePath)
|
|
||||||
await zip.close()
|
|
||||||
const rawHtml = pageData.toString('utf8')
|
|
||||||
|
|
||||||
const results = {}
|
|
||||||
|
|
||||||
const dh = new h.DomHandler((err, dom) => {
|
|
||||||
if (err) return results.error = err
|
|
||||||
|
|
||||||
// Get stylesheets
|
|
||||||
const isStylesheetLink = (elem) => elem.type == 'tag' && elem.name.toLowerCase() === 'link' && elem.attribs.rel === 'stylesheet' && elem.attribs.type === 'text/css'
|
|
||||||
const stylesheets = h.DomUtils.findAll(isStylesheetLink, dom)
|
|
||||||
|
|
||||||
// Get body tag
|
|
||||||
const isBodyTag = (elem) => elem.type == 'tag' && elem.name.toLowerCase() == 'body'
|
|
||||||
const body = h.DomUtils.findOne(isBodyTag, dom)
|
|
||||||
|
|
||||||
// Get all svg elements
|
|
||||||
const isSvgTag = (name) => ['svg'].includes((name || '').toLowerCase())
|
|
||||||
const svgElements = h.DomUtils.getElementsByTagName(isSvgTag, body.children)
|
|
||||||
svgElements.forEach((el) => {
|
|
||||||
if (el.attribs.class) el.attribs.class += ' abs-svg-scale'
|
|
||||||
else el.attribs.class = 'abs-svg-scale'
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get all img elements
|
|
||||||
const isImageTag = (name) => ['img', 'image'].includes((name || '').toLowerCase())
|
|
||||||
const imgElements = h.DomUtils.getElementsByTagName(isImageTag, body.children)
|
|
||||||
|
|
||||||
imgElements.forEach(el => {
|
|
||||||
if (!el.attribs.src && !el.attribs['xlink:href']) {
|
|
||||||
Logger.warn('[parseEpub] parsePage: Invalid img element attribs', el.attribs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (el.attribs.class) el.attribs.class += ' abs-image-scale'
|
|
||||||
else el.attribs.class = 'abs-image-scale'
|
|
||||||
|
|
||||||
const srcKey = el.attribs.src ? 'src' : 'xlink:href'
|
|
||||||
const src = encodeURIComponent(Path.posix.join(pageDir, el.attribs[srcKey]))
|
|
||||||
|
|
||||||
const basePath = isDev ? 'http://localhost:3333' : ''
|
|
||||||
el.attribs[srcKey] = `${basePath}/api/ebooks/${libraryItemId}/resource?path=${src}&token=${token}`
|
|
||||||
})
|
|
||||||
|
|
||||||
let finalHtml = '<div class="abs-page-content" style="max-height: unset; margin-left: 15% !important; margin-right: 15% !important;">'
|
|
||||||
|
|
||||||
stylesheets.forEach((el) => {
|
|
||||||
const href = Path.posix.join(pageDir, el.attribs.href)
|
|
||||||
const ssObj = bookData.stylesheets.find(sso => sso.path === href)
|
|
||||||
|
|
||||||
// find @import css and add it
|
|
||||||
const importSheets = getStylesheetImports(ssObj.style, bookData.stylesheets)
|
|
||||||
if (importSheets) {
|
|
||||||
importSheets.forEach((sheet) => {
|
|
||||||
finalHtml += `<style>${sheet.style}</style>\n`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ssObj) {
|
|
||||||
Logger.warn('[parseEpub] parsePage: Stylesheet object not found for href', href)
|
|
||||||
} else {
|
|
||||||
finalHtml += `<style>${ssObj.style}</style>\n`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
finalHtml += `<style>
|
|
||||||
.abs-image-scale { max-width: 100%; object-fit: contain; object-position: top center; max-height: 100vh; }
|
|
||||||
.abs-svg-scale { width: auto; max-height: 80vh; }
|
|
||||||
</style>\n`
|
|
||||||
|
|
||||||
finalHtml += ds.render(body.children)
|
|
||||||
|
|
||||||
finalHtml += '\n</div>'
|
|
||||||
|
|
||||||
results.html = finalHtml
|
|
||||||
})
|
|
||||||
|
|
||||||
const parser = new h.Parser(dh)
|
|
||||||
parser.write(rawHtml)
|
|
||||||
parser.end()
|
|
||||||
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.parseStylesheet = (rawCss, stylesheetPath, libraryItemId, token, isDev) => {
|
|
||||||
try {
|
|
||||||
const stylesheetDir = Path.dirname(stylesheetPath)
|
|
||||||
|
|
||||||
const res = css.parse(rawCss)
|
|
||||||
|
|
||||||
res.stylesheet.rules.forEach((rule) => {
|
|
||||||
if (rule.type === 'rule') {
|
|
||||||
rule.selectors = rule.selectors.map(s => s === 'body' ? '.abs-page-content' : `.abs-page-content ${s}`)
|
|
||||||
} else if (rule.type === 'font-face' && rule.declarations) {
|
|
||||||
rule.declarations = rule.declarations.map(dec => {
|
|
||||||
if (dec.property === 'src') {
|
|
||||||
const match = dec.value.trim().split(' ').shift().match(/url\((.+)\)/)
|
|
||||||
if (match && match[1]) {
|
|
||||||
const fontPath = Path.posix.join(stylesheetDir, match[1])
|
|
||||||
const newSrc = encodeURIComponent(fontPath)
|
|
||||||
|
|
||||||
const basePath = isDev ? 'http://localhost:3333' : ''
|
|
||||||
dec.value = dec.value.replace(match[1], `"${basePath}/api/ebooks/${libraryItemId}/resource?path=${newSrc}&token=${token}"`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dec
|
|
||||||
})
|
|
||||||
} else if (rule.type === 'import') {
|
|
||||||
const importUrl = rule.import
|
|
||||||
const match = importUrl.match(/\"(.*)\"/)
|
|
||||||
const path = match ? match[1] || '' : ''
|
|
||||||
if (path) {
|
|
||||||
// const newSrc = encodeURIComponent(Path.posix.join(stylesheetDir, path))
|
|
||||||
// const basePath = isDev ? 'http://localhost:3333' : ''
|
|
||||||
// const newPath = `"${basePath}/api/ebooks/${libraryItemId}/resource?path=${newSrc}&token=${token}"`
|
|
||||||
// rule.import = rule.import.replace(path, newPath)
|
|
||||||
|
|
||||||
rule.import = Path.posix.join(stylesheetDir, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return css.stringify(res)
|
|
||||||
} catch (error) {
|
|
||||||
Logger.error('[parseEpub] parseStylesheet: Failed', error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStylesheetImports(rawCss, stylesheets) {
|
|
||||||
try {
|
|
||||||
const res = css.parse(rawCss)
|
|
||||||
|
|
||||||
const imports = []
|
|
||||||
res.stylesheet.rules.forEach((rule) => {
|
|
||||||
if (rule.type === 'import') {
|
|
||||||
const importUrl = rule.import.replaceAll('"', '')
|
|
||||||
const sheet = stylesheets.find(s => s.path === importUrl)
|
|
||||||
if (sheet) imports.push(sheet)
|
|
||||||
else {
|
|
||||||
Logger.error('[parseEpub] getStylesheetImports: Sheet not found', stylesheets)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return imports
|
|
||||||
} catch (error) {
|
|
||||||
Logger.error('[parseEpub] getStylesheetImports: Failed', error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user