diff --git a/server/Server.js b/server/Server.js index 1f77e54a..f90e68ea 100644 --- a/server/Server.js +++ b/server/Server.js @@ -35,7 +35,6 @@ const AudioMetadataMangaer = require('./managers/AudioMetadataManager') const RssFeedManager = require('./managers/RssFeedManager') const CronManager = require('./managers/CronManager') const TaskManager = require('./managers/TaskManager') -const EBookManager = require('./managers/EBookManager') class Server { 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.audioMetadataManager = new AudioMetadataMangaer(this.db, this.taskManager) this.rssFeedManager = new RssFeedManager(this.db) - this.eBookManager = new EBookManager(this.db) this.scanner = new Scanner(this.db, this.coverManager) this.cronManager = new CronManager(this.db, this.scanner, this.podcastManager) diff --git a/server/controllers/EBookController.js b/server/controllers/EBookController.js deleted file mode 100644 index 743bea16..00000000 --- a/server/controllers/EBookController.js +++ /dev/null @@ -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() \ No newline at end of file diff --git a/server/managers/EBookManager.js b/server/managers/EBookManager.js deleted file mode 100644 index 3dab617d..00000000 --- a/server/managers/EBookManager.js +++ /dev/null @@ -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 \ No newline at end of file diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index 3918bc2c..3693ec52 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -24,7 +24,6 @@ const SearchController = require('../controllers/SearchController') const CacheController = require('../controllers/CacheController') const ToolsController = require('../controllers/ToolsController') const RSSFeedController = require('../controllers/RSSFeedController') -const EBookController = require('../controllers/EBookController') const MiscController = require('../controllers/MiscController') const BookFinder = require('../finders/BookFinder') @@ -52,7 +51,6 @@ class ApiRouter { this.cronManager = Server.cronManager this.notificationManager = Server.notificationManager this.taskManager = Server.taskManager - this.eBookManager = Server.eBookManager this.bookFinder = new BookFinder() 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/: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 // diff --git a/server/utils/parsers/parseEpub.js b/server/utils/parsers/parseEpub.js deleted file mode 100644 index fcdc7aa7..00000000 --- a/server/utils/parsers/parseEpub.js +++ /dev/null @@ -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 = '