mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +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 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) | ||||
|  | ||||
| @ -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 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
 | ||||
|     //
 | ||||
|  | ||||
| @ -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