mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-02-13 21:59:40 +00:00
Merge master
This commit is contained in:
commit
ab14b561f5
147 changed files with 4669 additions and 5036 deletions
|
|
@ -1,4 +1,3 @@
|
|||
const sequelize = require('sequelize')
|
||||
const express = require('express')
|
||||
const Path = require('path')
|
||||
|
||||
|
|
@ -40,6 +39,7 @@ class ApiRouter {
|
|||
this.playbackSessionManager = Server.playbackSessionManager
|
||||
this.abMergeManager = Server.abMergeManager
|
||||
this.backupManager = Server.backupManager
|
||||
/** @type {import('../Watcher')} */
|
||||
this.watcher = Server.watcher
|
||||
this.podcastManager = Server.podcastManager
|
||||
this.audioMetadataManager = Server.audioMetadataManager
|
||||
|
|
@ -47,7 +47,6 @@ class ApiRouter {
|
|||
this.cronManager = Server.cronManager
|
||||
this.notificationManager = Server.notificationManager
|
||||
this.emailManager = Server.emailManager
|
||||
this.taskManager = Server.taskManager
|
||||
|
||||
this.router = express()
|
||||
this.router.disable('x-powered-by')
|
||||
|
|
@ -84,6 +83,7 @@ class ApiRouter {
|
|||
this.router.get('/libraries/:id/recent-episodes', LibraryController.middleware.bind(this), LibraryController.getRecentEpisodes.bind(this))
|
||||
this.router.get('/libraries/:id/opml', LibraryController.middleware.bind(this), LibraryController.getOPMLFile.bind(this))
|
||||
this.router.post('/libraries/order', LibraryController.reorder.bind(this))
|
||||
this.router.post('/libraries/:id/remove-metadata', LibraryController.middleware.bind(this), LibraryController.removeAllMetadataFiles.bind(this))
|
||||
|
||||
//
|
||||
// Item Routes
|
||||
|
|
@ -202,6 +202,8 @@ class ApiRouter {
|
|||
this.router.delete('/authors/:id', AuthorController.middleware.bind(this), AuthorController.delete.bind(this))
|
||||
this.router.post('/authors/:id/match', AuthorController.middleware.bind(this), AuthorController.match.bind(this))
|
||||
this.router.get('/authors/:id/image', AuthorController.middleware.bind(this), AuthorController.getImage.bind(this))
|
||||
this.router.post('/authors/:id/image', AuthorController.middleware.bind(this), AuthorController.uploadImage.bind(this))
|
||||
this.router.delete('/authors/:id/image', AuthorController.middleware.bind(this), AuthorController.deleteImage.bind(this))
|
||||
|
||||
//
|
||||
// Series Routes
|
||||
|
|
@ -253,11 +255,11 @@ class ApiRouter {
|
|||
//
|
||||
// Email Routes (Admin and up)
|
||||
//
|
||||
this.router.get('/emails/settings', EmailController.middleware.bind(this), EmailController.getSettings.bind(this))
|
||||
this.router.patch('/emails/settings', EmailController.middleware.bind(this), EmailController.updateSettings.bind(this))
|
||||
this.router.post('/emails/test', EmailController.middleware.bind(this), EmailController.sendTest.bind(this))
|
||||
this.router.post('/emails/ereader-devices', EmailController.middleware.bind(this), EmailController.updateEReaderDevices.bind(this))
|
||||
this.router.post('/emails/send-ebook-to-device', EmailController.middleware.bind(this), EmailController.sendEBookToDevice.bind(this))
|
||||
this.router.get('/emails/settings', EmailController.adminMiddleware.bind(this), EmailController.getSettings.bind(this))
|
||||
this.router.patch('/emails/settings', EmailController.adminMiddleware.bind(this), EmailController.updateSettings.bind(this))
|
||||
this.router.post('/emails/test', EmailController.adminMiddleware.bind(this), EmailController.sendTest.bind(this))
|
||||
this.router.post('/emails/ereader-devices', EmailController.adminMiddleware.bind(this), EmailController.updateEReaderDevices.bind(this))
|
||||
this.router.post('/emails/send-ebook-to-device', EmailController.sendEBookToDevice.bind(this))
|
||||
|
||||
//
|
||||
// Search Routes
|
||||
|
|
@ -308,6 +310,7 @@ class ApiRouter {
|
|||
this.router.delete('/genres/:genre', MiscController.deleteGenre.bind(this))
|
||||
this.router.post('/validate-cron', MiscController.validateCronExpression.bind(this))
|
||||
this.router.get('/auth-settings', MiscController.getAuthSettings.bind(this))
|
||||
this.router.post('/watcher/update', MiscController.updateWatchedPath.bind(this))
|
||||
}
|
||||
|
||||
async getDirectories(dir, relpath, excludedDirs, level = 0) {
|
||||
|
|
|
|||
|
|
@ -27,28 +27,60 @@ class HlsRouter {
|
|||
return Number(num_part)
|
||||
}
|
||||
|
||||
async streamFileRequest(req, res) {
|
||||
var streamId = req.params.stream
|
||||
var fullFilePath = Path.join(this.playbackSessionManager.StreamsPath, streamId, req.params.file)
|
||||
/**
|
||||
* Ensure filepath is inside streamDir
|
||||
* Used to prevent arbitrary file reads
|
||||
* @see https://nodejs.org/api/path.html#pathrelativefrom-to
|
||||
*
|
||||
* @param {string} streamDir
|
||||
* @param {string} filepath
|
||||
* @returns {boolean}
|
||||
*/
|
||||
validateStreamFilePath(streamDir, filepath) {
|
||||
const relative = Path.relative(streamDir, filepath)
|
||||
return relative && !relative.startsWith('..') && !Path.isAbsolute(relative)
|
||||
}
|
||||
|
||||
var exists = await fs.pathExists(fullFilePath)
|
||||
if (!exists) {
|
||||
/**
|
||||
* GET /hls/:stream/:file
|
||||
* File must have extname .ts or .m3u8
|
||||
*
|
||||
* @param {express.Request} req
|
||||
* @param {express.Response} res
|
||||
*/
|
||||
async streamFileRequest(req, res) {
|
||||
const streamId = req.params.stream
|
||||
// Ensure stream is open
|
||||
const stream = this.playbackSessionManager.getStream(streamId)
|
||||
if (!stream) {
|
||||
Logger.error(`[HlsRouter] Stream "${streamId}" does not exist`)
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
// Ensure stream filepath is valid
|
||||
const streamDir = Path.join(this.playbackSessionManager.StreamsPath, streamId)
|
||||
const fullFilePath = Path.join(streamDir, req.params.file)
|
||||
if (!this.validateStreamFilePath(streamDir, fullFilePath)) {
|
||||
Logger.error(`[HlsRouter] Invalid file parameter "${req.params.file}"`)
|
||||
return res.sendStatus(400)
|
||||
}
|
||||
|
||||
const fileExt = Path.extname(req.params.file)
|
||||
if (fileExt !== '.ts' && fileExt !== '.m3u8') {
|
||||
Logger.error(`[HlsRouter] Invalid file parameter "${req.params.file}" extname. Must be .ts or .m3u8`)
|
||||
return res.sendStatus(400)
|
||||
}
|
||||
|
||||
if (!(await fs.pathExists(fullFilePath))) {
|
||||
Logger.warn('File path does not exist', fullFilePath)
|
||||
|
||||
var fileExt = Path.extname(req.params.file)
|
||||
if (fileExt === '.ts' || fileExt === '.m4s') {
|
||||
var segNum = this.parseSegmentFilename(req.params.file)
|
||||
var stream = this.playbackSessionManager.getStream(streamId)
|
||||
if (!stream) {
|
||||
Logger.error(`[HlsRouter] Stream ${streamId} does not exist`)
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
if (fileExt === '.ts') {
|
||||
const segNum = this.parseSegmentFilename(req.params.file)
|
||||
|
||||
if (stream.isResetting) {
|
||||
Logger.info(`[HlsRouter] Stream ${streamId} is currently resetting`)
|
||||
return res.sendStatus(404)
|
||||
} else {
|
||||
var startTimeForReset = await stream.checkSegmentNumberRequest(segNum)
|
||||
const startTimeForReset = await stream.checkSegmentNumberRequest(segNum)
|
||||
if (startTimeForReset) {
|
||||
// HLS.js will restart the stream at the new time
|
||||
Logger.info(`[HlsRouter] Resetting Stream - notify client @${startTimeForReset}s`)
|
||||
|
|
@ -56,13 +88,12 @@ class HlsRouter {
|
|||
startTime: startTimeForReset,
|
||||
streamId: stream.id
|
||||
})
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
// Logger.info('Sending file', fullFilePath)
|
||||
res.sendFile(fullFilePath)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue