mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-03 00:06:46 +01:00
Update:RSS feed API routes
This commit is contained in:
parent
775dedc338
commit
e803dcd325
@ -6,13 +6,13 @@
|
||||
</div>
|
||||
</template>
|
||||
<div ref="wrapper" class="px-8 py-6 w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden">
|
||||
<div v-if="currentFeedUrl" class="w-full">
|
||||
<div v-if="currentFeed" class="w-full">
|
||||
<p class="text-lg font-semibold mb-4">{{ $strings.HeaderRSSFeedIsOpen }}</p>
|
||||
|
||||
<div class="w-full relative">
|
||||
<ui-text-input v-model="currentFeedUrl" readonly />
|
||||
<ui-text-input v-model="currentFeed.feedUrl" readonly />
|
||||
|
||||
<span class="material-icons absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(currentFeedUrl)">content_copy</span>
|
||||
<span class="material-icons absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(currentFeed.feedUrl)">content_copy</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="w-full">
|
||||
@ -28,7 +28,7 @@
|
||||
</div>
|
||||
<div v-show="userIsAdminOrUp" class="flex items-center pt-6">
|
||||
<div class="flex-grow" />
|
||||
<ui-btn v-if="currentFeedUrl" color="error" small @click="closeFeed">{{ $strings.ButtonCloseFeed }}</ui-btn>
|
||||
<ui-btn v-if="currentFeed" color="error" small @click="closeFeed">{{ $strings.ButtonCloseFeed }}</ui-btn>
|
||||
<ui-btn v-else color="success" small @click="openFeed">{{ $strings.ButtonOpenFeed }}</ui-btn>
|
||||
</div>
|
||||
</div>
|
||||
@ -43,13 +43,16 @@ export default {
|
||||
type: Object,
|
||||
default: () => null
|
||||
},
|
||||
feedUrl: String
|
||||
feed: {
|
||||
type: Object,
|
||||
default: () => null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
processing: false,
|
||||
newFeedSlug: null,
|
||||
currentFeedUrl: null
|
||||
currentFeed: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -106,7 +109,7 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
var sanitized = this.$sanitizeSlug(this.newFeedSlug)
|
||||
const sanitized = this.$sanitizeSlug(this.newFeedSlug)
|
||||
if (this.newFeedSlug !== sanitized) {
|
||||
this.newFeedSlug = sanitized
|
||||
this.$toast.warning('Slug had to be modified - Run again')
|
||||
@ -121,19 +124,15 @@ export default {
|
||||
|
||||
console.log('Payload', payload)
|
||||
this.$axios
|
||||
.$post(`/api/items/${this.libraryItemId}/open-feed`, payload)
|
||||
.$post(`/api/feeds/item/${this.libraryItemId}/open`, payload)
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
console.log('Opened RSS Feed', data)
|
||||
this.currentFeedUrl = data.feedUrl
|
||||
} else {
|
||||
const errorMsg = data.error || 'Unknown error'
|
||||
this.$toast.error(errorMsg)
|
||||
}
|
||||
this.currentFeed = data.feed
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to open RSS Feed', error)
|
||||
this.$toast.error()
|
||||
const errorMsg = error.response ? error.response.data : null
|
||||
this.$toast.error(errorMsg || 'Failed to open RSS Feed')
|
||||
})
|
||||
},
|
||||
copyToClipboard(str) {
|
||||
@ -142,22 +141,23 @@ export default {
|
||||
closeFeed() {
|
||||
this.processing = true
|
||||
this.$axios
|
||||
.$post(`/api/items/${this.libraryItem.id}/close-feed`)
|
||||
.$post(`/api/feeds/${this.currentFeed.id}/close`)
|
||||
.then(() => {
|
||||
this.$toast.success(this.$strings.ToastRSSFeedCloseSuccess)
|
||||
this.show = false
|
||||
this.processing = false
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to close RSS feed', error)
|
||||
this.processing = false
|
||||
this.$toast.error(this.$strings.ToastRSSFeedCloseFailed)
|
||||
})
|
||||
.finally(() => {
|
||||
this.processing = false
|
||||
})
|
||||
},
|
||||
init() {
|
||||
if (!this.libraryItem) return
|
||||
this.newFeedSlug = this.libraryItem.id
|
||||
this.currentFeedUrl = this.feedUrl
|
||||
this.currentFeed = this.feed
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
|
@ -174,7 +174,7 @@
|
||||
|
||||
<!-- RSS feed -->
|
||||
<ui-tooltip v-if="showRssFeedBtn" :text="$strings.LabelOpenRSSFeed" direction="top">
|
||||
<ui-icon-btn icon="rss_feed" class="mx-0.5" :bg-color="rssFeedUrl ? 'success' : 'primary'" outlined @click="clickRSSFeed" />
|
||||
<ui-icon-btn icon="rss_feed" class="mx-0.5" :bg-color="rssFeed ? 'success' : 'primary'" outlined @click="clickRSSFeed" />
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
|
||||
@ -200,7 +200,7 @@
|
||||
</div>
|
||||
|
||||
<modals-podcast-episode-feed v-model="showPodcastEpisodeFeed" :library-item="libraryItem" :episodes="podcastFeedEpisodes" />
|
||||
<modals-rssfeed-view-modal v-model="showRssFeedModal" :library-item="libraryItem" :feed-url="rssFeedUrl" />
|
||||
<modals-rssfeed-view-modal v-model="showRssFeedModal" :library-item="libraryItem" :feed="rssFeed" />
|
||||
<modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :library-item-id="libraryItemId" hide-create @select="selectBookmark" />
|
||||
</div>
|
||||
</template>
|
||||
@ -223,7 +223,7 @@ export default {
|
||||
}
|
||||
return {
|
||||
libraryItem: item,
|
||||
rssFeedUrl: item.rssFeedUrl || null
|
||||
rssFeed: item.rssFeed || null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@ -432,10 +432,10 @@ export default {
|
||||
return this.$store.getters['user/getUserCanDownload']
|
||||
},
|
||||
showRssFeedBtn() {
|
||||
if (!this.rssFeedUrl && !this.podcastEpisodes.length && !this.tracks.length) return false // Cannot open RSS feed with no episodes/tracks
|
||||
if (!this.rssFeed && !this.podcastEpisodes.length && !this.tracks.length) return false // Cannot open RSS feed with no episodes/tracks
|
||||
|
||||
// If rss feed is open then show feed url to users otherwise just show to admins
|
||||
return this.userIsAdminOrUp || this.rssFeedUrl
|
||||
return this.userIsAdminOrUp || this.rssFeed
|
||||
},
|
||||
showQueueBtn() {
|
||||
if (!this.isBook) return false
|
||||
@ -655,13 +655,13 @@ export default {
|
||||
rssFeedOpen(data) {
|
||||
if (data.entityId === this.libraryItemId) {
|
||||
console.log('RSS Feed Opened', data)
|
||||
this.rssFeedUrl = data.feedUrl
|
||||
this.rssFeed = data
|
||||
}
|
||||
},
|
||||
rssFeedClosed(data) {
|
||||
if (data.entityId === this.libraryItemId) {
|
||||
console.log('RSS Feed Closed', data)
|
||||
this.rssFeedUrl = null
|
||||
this.rssFeed = null
|
||||
}
|
||||
},
|
||||
queueBtnClick() {
|
||||
|
@ -21,8 +21,8 @@ class LibraryItemController {
|
||||
}
|
||||
|
||||
if (includeEntities.includes('rssfeed')) {
|
||||
var feedData = this.rssFeedManager.findFeedForItem(item.id)
|
||||
item.rssFeedUrl = feedData ? feedData.feedUrl : null
|
||||
const feedData = this.rssFeedManager.findFeedForItem(item.id)
|
||||
item.rssFeed = feedData ? feedData.toJSONMinified() : null
|
||||
}
|
||||
|
||||
if (item.mediaType == 'book') {
|
||||
@ -432,38 +432,6 @@ class LibraryItemController {
|
||||
})
|
||||
}
|
||||
|
||||
// POST: api/items/:id/open-feed
|
||||
async openRSSFeed(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[LibraryItemController] Non-admin user attempted to open RSS feed`, req.user.username)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
const feedData = await this.rssFeedManager.openFeedForItem(req.user, req.libraryItem, req.body)
|
||||
if (feedData.error) {
|
||||
return res.json({
|
||||
success: false,
|
||||
error: feedData.error
|
||||
})
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
feedUrl: feedData.feedUrl
|
||||
})
|
||||
}
|
||||
|
||||
async closeRSSFeed(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[LibraryItemController] Non-admin user attempted to close RSS feed`, req.user.username)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
await this.rssFeedManager.closeFeedForItem(req.params.id)
|
||||
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
async toneScan(req, res) {
|
||||
if (!req.libraryItem.media.audioFiles.length) {
|
||||
return res.sendStatus(404)
|
||||
@ -481,7 +449,7 @@ class LibraryItemController {
|
||||
}
|
||||
|
||||
middleware(req, res, next) {
|
||||
var item = this.db.libraryItems.find(li => li.id === req.params.id)
|
||||
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
|
||||
|
68
server/controllers/RSSFeedController.js
Normal file
68
server/controllers/RSSFeedController.js
Normal file
@ -0,0 +1,68 @@
|
||||
const Logger = require('../Logger')
|
||||
const SocketAuthority = require('../SocketAuthority')
|
||||
|
||||
class RSSFeedController {
|
||||
constructor() { }
|
||||
|
||||
// POST: api/feeds/item/:itemId/open
|
||||
async openRSSFeedForItem(req, res) {
|
||||
const options = req.body || {}
|
||||
|
||||
const item = this.db.libraryItems.find(li => li.id === req.params.itemId)
|
||||
if (!item) return res.sendStatus(404)
|
||||
|
||||
// Check user can access this library item
|
||||
if (!req.user.checkCanAccessLibraryItem(item)) {
|
||||
Logger.error(`[RSSFeedController] User "${req.user.username}" attempted to open an RSS feed for item "${item.media.metadata.title}" that they don\'t have access to`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
// Check request body options exist
|
||||
if (!options.serverAddress || !options.slug) {
|
||||
Logger.error(`[RSSFeedController] Invalid request body to open RSS feed`)
|
||||
return res.status(400).send('Invalid request body')
|
||||
}
|
||||
|
||||
// Check item has audio tracks
|
||||
if (!item.media.numTracks) {
|
||||
Logger.error(`[RSSFeedController] Cannot open RSS feed for item "${item.media.metadata.title}" because it has no audio tracks`)
|
||||
return res.status(400).send('Item has no audio tracks')
|
||||
}
|
||||
|
||||
// Check that this slug is not being used for another feed (slug will also be the Feed id)
|
||||
if (this.rssFeedManager.feeds[options.slug]) {
|
||||
Logger.error(`[RSSFeedController] Cannot open RSS feed because slug "${options.slug}" is already in use`)
|
||||
return res.status(400).send('Slug already in use')
|
||||
}
|
||||
|
||||
const feed = await this.rssFeedManager.openFeedForItem(req.user, item, req.body)
|
||||
res.json({
|
||||
feed: feed.toJSONMinified()
|
||||
})
|
||||
}
|
||||
|
||||
// POST: api/feeds/:id/close
|
||||
async closeRSSFeed(req, res) {
|
||||
await this.rssFeedManager.closeRssFeed(req.params.id)
|
||||
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
middleware(req, res, next) {
|
||||
if (!req.user.isAdminOrUp) { // Only admins can manage rss feeds
|
||||
Logger.error(`[RSSFeedController] Non-admin user attempted to make a request to an RSS feed route`, req.user.username)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
if (req.params.id) {
|
||||
const feed = this.rssFeedManager.findFeed(req.params.id)
|
||||
if (!feed) {
|
||||
Logger.error(`[RSSFeedController] RSS feed not found with id "${req.params.id}"`)
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
||||
module.exports = new RSSFeedController()
|
@ -18,10 +18,10 @@ class RssFeedManager {
|
||||
}
|
||||
|
||||
async init() {
|
||||
var feedObjects = await this.db.getAllEntities('feed')
|
||||
const feedObjects = await this.db.getAllEntities('feed')
|
||||
if (feedObjects && feedObjects.length) {
|
||||
feedObjects.forEach((feedObj) => {
|
||||
var feed = new Feed(feedObj)
|
||||
const feed = new Feed(feedObj)
|
||||
this.feeds[feed.id] = feed
|
||||
Logger.info(`[RssFeedManager] Opened rss feed ${feed.feedUrl}`)
|
||||
})
|
||||
@ -32,8 +32,12 @@ class RssFeedManager {
|
||||
return Object.values(this.feeds).find(feed => feed.entityId === libraryItemId)
|
||||
}
|
||||
|
||||
findFeed(feedId) {
|
||||
return this.feeds[feedId] || null
|
||||
}
|
||||
|
||||
async getFeed(req, res) {
|
||||
var feed = this.feeds[req.params.id]
|
||||
const feed = this.feeds[req.params.id]
|
||||
if (!feed) {
|
||||
Logger.debug(`[RssFeedManager] Feed not found ${req.params.id}`)
|
||||
res.sendStatus(404)
|
||||
@ -49,19 +53,19 @@ class RssFeedManager {
|
||||
}
|
||||
}
|
||||
|
||||
var xml = feed.buildXml()
|
||||
const xml = feed.buildXml()
|
||||
res.set('Content-Type', 'text/xml')
|
||||
res.send(xml)
|
||||
}
|
||||
|
||||
getFeedItem(req, res) {
|
||||
var feed = this.feeds[req.params.id]
|
||||
const feed = this.feeds[req.params.id]
|
||||
if (!feed) {
|
||||
Logger.debug(`[RssFeedManager] Feed not found ${req.params.id}`)
|
||||
res.sendStatus(404)
|
||||
return
|
||||
}
|
||||
var episodePath = feed.getEpisodePath(req.params.episodeId)
|
||||
const episodePath = feed.getEpisodePath(req.params.episodeId)
|
||||
if (!episodePath) {
|
||||
Logger.error(`[RssFeedManager] Feed episode not found ${req.params.episodeId}`)
|
||||
res.sendStatus(404)
|
||||
@ -71,7 +75,7 @@ class RssFeedManager {
|
||||
}
|
||||
|
||||
getFeedCover(req, res) {
|
||||
var feed = this.feeds[req.params.id]
|
||||
const feed = this.feeds[req.params.id]
|
||||
if (!feed) {
|
||||
Logger.debug(`[RssFeedManager] Feed not found ${req.params.id}`)
|
||||
res.sendStatus(404)
|
||||
@ -85,7 +89,7 @@ class RssFeedManager {
|
||||
|
||||
const extname = Path.extname(feed.coverPath).toLowerCase().slice(1)
|
||||
res.type(`image/${extname}`)
|
||||
var readStream = fs.createReadStream(feed.coverPath)
|
||||
const readStream = fs.createReadStream(feed.coverPath)
|
||||
readStream.pipe(res)
|
||||
}
|
||||
|
||||
@ -93,32 +97,25 @@ class RssFeedManager {
|
||||
const serverAddress = options.serverAddress
|
||||
const slug = options.slug
|
||||
|
||||
if (this.feeds[slug]) {
|
||||
Logger.error(`[RssFeedManager] Slug already in use`)
|
||||
return {
|
||||
error: `Slug "${slug}" already in use`
|
||||
}
|
||||
}
|
||||
|
||||
const feed = new Feed()
|
||||
feed.setFromItem(user.id, slug, libraryItem, serverAddress)
|
||||
this.feeds[feed.id] = feed
|
||||
|
||||
Logger.debug(`[RssFeedManager] Opened RSS feed ${feed.feedUrl}`)
|
||||
Logger.debug(`[RssFeedManager] Opened RSS feed "${feed.feedUrl}"`)
|
||||
await this.db.insertEntity('feed', feed)
|
||||
SocketAuthority.emitter('rss_feed_open', feed.toJSONMinified())
|
||||
return feed
|
||||
}
|
||||
|
||||
closeFeedForItem(libraryItemId) {
|
||||
var feed = this.findFeedForItem(libraryItemId)
|
||||
const feed = this.findFeedForItem(libraryItemId)
|
||||
if (!feed) return
|
||||
return this.closeRssFeed(feed.id)
|
||||
}
|
||||
|
||||
async closeRssFeed(id) {
|
||||
if (!this.feeds[id]) return
|
||||
var feed = this.feeds[id]
|
||||
const feed = this.feeds[id]
|
||||
await this.db.removeEntity('feed', id)
|
||||
SocketAuthority.emitter('rss_feed_closed', feed.toJSONMinified())
|
||||
delete this.feeds[id]
|
||||
|
@ -23,6 +23,7 @@ const NotificationController = require('../controllers/NotificationController')
|
||||
const SearchController = require('../controllers/SearchController')
|
||||
const CacheController = require('../controllers/CacheController')
|
||||
const ToolsController = require('../controllers/ToolsController')
|
||||
const RSSFeedController = require('../controllers/RSSFeedController')
|
||||
const MiscController = require('../controllers/MiscController')
|
||||
|
||||
const BookFinder = require('../finders/BookFinder')
|
||||
@ -104,8 +105,6 @@ class ApiRouter {
|
||||
this.router.get('/items/:id/scan', LibraryItemController.middleware.bind(this), LibraryItemController.scan.bind(this))
|
||||
this.router.get('/items/:id/tone-object', LibraryItemController.middleware.bind(this), LibraryItemController.getToneMetadataObject.bind(this))
|
||||
this.router.post('/items/:id/chapters', LibraryItemController.middleware.bind(this), LibraryItemController.updateMediaChapters.bind(this))
|
||||
this.router.post('/items/:id/open-feed', LibraryItemController.middleware.bind(this), LibraryItemController.openRSSFeed.bind(this))
|
||||
this.router.post('/items/:id/close-feed', LibraryItemController.middleware.bind(this), LibraryItemController.closeRSSFeed.bind(this))
|
||||
this.router.post('/items/:id/tone-scan/:index?', LibraryItemController.middleware.bind(this), LibraryItemController.toneScan.bind(this))
|
||||
|
||||
this.router.post('/items/batch/delete', LibraryItemController.batchDelete.bind(this))
|
||||
@ -231,7 +230,7 @@ class ApiRouter {
|
||||
this.router.delete('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.removeEpisode.bind(this))
|
||||
|
||||
//
|
||||
// Notification Routes
|
||||
// Notification Routes (Admin and up)
|
||||
//
|
||||
this.router.get('/notifications', NotificationController.middleware.bind(this), NotificationController.get.bind(this))
|
||||
this.router.patch('/notifications', NotificationController.middleware.bind(this), NotificationController.update.bind(this))
|
||||
@ -252,18 +251,24 @@ class ApiRouter {
|
||||
this.router.get('/search/chapters', SearchController.findChapters.bind(this))
|
||||
|
||||
//
|
||||
// Cache Routes
|
||||
// Cache Routes (Admin and up)
|
||||
//
|
||||
this.router.post('/cache/purge', CacheController.purgeCache.bind(this))
|
||||
this.router.post('/cache/items/purge', CacheController.purgeItemsCache.bind(this))
|
||||
|
||||
//
|
||||
// Tools Routes
|
||||
// Tools Routes (Admin and up)
|
||||
//
|
||||
this.router.post('/tools/item/:id/encode-m4b', ToolsController.itemMiddleware.bind(this), ToolsController.encodeM4b.bind(this))
|
||||
this.router.delete('/tools/item/:id/encode-m4b', ToolsController.itemMiddleware.bind(this), ToolsController.cancelM4bEncode.bind(this))
|
||||
this.router.post('/tools/item/:id/embed-metadata', ToolsController.itemMiddleware.bind(this), ToolsController.embedAudioFileMetadata.bind(this))
|
||||
|
||||
//
|
||||
// RSS Feed Routes (Admin and up)
|
||||
//
|
||||
this.router.post('/feeds/item/:itemId/open', RSSFeedController.middleware.bind(this), RSSFeedController.openRSSFeedForItem.bind(this))
|
||||
this.router.post('/feeds/:id/close', RSSFeedController.middleware.bind(this), RSSFeedController.closeRSSFeed.bind(this))
|
||||
|
||||
//
|
||||
// Misc Routes
|
||||
//
|
||||
|
Loading…
Reference in New Issue
Block a user