mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-03 00:06:46 +01:00
Update:Experimental RSS feed setting custom slugs with default to library item id #553
This commit is contained in:
parent
55c40658f2
commit
195a30096f
@ -6,18 +6,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</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 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 class="w-full">
|
<div v-if="currentFeedUrl" class="w-full">
|
||||||
<p class="text-lg font-semibold mb-4">Podcast RSS Feed is Open</p>
|
<p class="text-lg font-semibold mb-4">Podcast RSS Feed is Open</p>
|
||||||
|
|
||||||
<div class="w-full relative">
|
<div class="w-full relative">
|
||||||
<ui-text-input v-model="feedUrl" readonly />
|
<ui-text-input v-model="currentFeedUrl" 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(feedUrl)">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(currentFeedUrl)">content_copy</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="w-full">
|
||||||
|
<p class="text-lg font-semibold mb-4">Open RSS Feed</p>
|
||||||
|
|
||||||
|
<div class="w-full relative">
|
||||||
|
<ui-text-input-with-label v-model="newFeedSlug" label="RSS Feed Slug" />
|
||||||
|
<p class="text-xs text-gray-400 py-0.5 px-1">Feed will be {{ demoFeedUrl }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="userIsAdminOrUp" class="flex items-center pt-6">
|
<div v-show="userIsAdminOrUp" class="flex items-center pt-6">
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-btn color="error" small @click="closeFeed">Close RSS Feed</ui-btn>
|
<ui-btn v-if="currentFeedUrl" color="error" small @click="closeFeed">Close RSS Feed</ui-btn>
|
||||||
|
<ui-btn v-else color="success" small @click="openFeed">Open RSS Feed</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
@ -35,7 +44,9 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
processing: false
|
processing: false,
|
||||||
|
newFeedSlug: null,
|
||||||
|
currentFeedUrl: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -57,6 +68,9 @@ export default {
|
|||||||
this.$emit('input', val)
|
this.$emit('input', val)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
libraryItemId() {
|
||||||
|
return this.libraryItem.id
|
||||||
|
},
|
||||||
media() {
|
media() {
|
||||||
return this.libraryItem.media || {}
|
return this.libraryItem.media || {}
|
||||||
},
|
},
|
||||||
@ -68,9 +82,48 @@ export default {
|
|||||||
},
|
},
|
||||||
userIsAdminOrUp() {
|
userIsAdminOrUp() {
|
||||||
return this.$store.getters['user/getIsAdminOrUp']
|
return this.$store.getters['user/getIsAdminOrUp']
|
||||||
|
},
|
||||||
|
demoFeedUrl() {
|
||||||
|
return `${window.origin}/feed/${this.newFeedSlug}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
openFeed() {
|
||||||
|
if (!this.newFeedSlug) {
|
||||||
|
this.$toast.error('Must set a feed slug')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var sanitized = this.$sanitizeSlug(this.newFeedSlug)
|
||||||
|
if (this.newFeedSlug !== sanitized) {
|
||||||
|
this.newFeedSlug = sanitized
|
||||||
|
this.$toast.warning('Slug had to be modified - Run again')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
serverAddress: window.origin,
|
||||||
|
slug: this.newFeedSlug
|
||||||
|
}
|
||||||
|
if (this.$isDev) payload.serverAddress = 'http://localhost:3333'
|
||||||
|
|
||||||
|
console.log('Payload', payload)
|
||||||
|
this.$axios
|
||||||
|
.$post(`/api/podcasts/${this.libraryItemId}/open-feed`, 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to open RSS Feed', error)
|
||||||
|
this.$toast.error()
|
||||||
|
})
|
||||||
|
},
|
||||||
copyToClipboard(str) {
|
copyToClipboard(str) {
|
||||||
this.$copyToClipboard(str, this)
|
this.$copyToClipboard(str, this)
|
||||||
},
|
},
|
||||||
@ -89,7 +142,11 @@ export default {
|
|||||||
this.$toast.error()
|
this.$toast.error()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
init() {}
|
init() {
|
||||||
|
if (!this.libraryItem) return
|
||||||
|
this.newFeedSlug = this.libraryItem.id
|
||||||
|
this.currentFeedUrl = this.feedUrl
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted() {}
|
mounted() {}
|
||||||
}
|
}
|
||||||
|
@ -492,33 +492,7 @@ export default {
|
|||||||
this.$store.commit('globals/setShowUserCollectionsModal', true)
|
this.$store.commit('globals/setShowUserCollectionsModal', true)
|
||||||
},
|
},
|
||||||
clickRSSFeed() {
|
clickRSSFeed() {
|
||||||
if (!this.rssFeedUrl) {
|
this.showRssFeedModal = true
|
||||||
if (confirm(`Are you sure you want to open an RSS Feed for this podcast?`)) {
|
|
||||||
this.openRSSFeed()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.showRssFeedModal = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openRSSFeed() {
|
|
||||||
const payload = {
|
|
||||||
serverAddress: window.origin
|
|
||||||
}
|
|
||||||
if (this.$isDev) payload.serverAddress = 'http://localhost:3333'
|
|
||||||
|
|
||||||
console.log('Payload', payload)
|
|
||||||
this.$axios
|
|
||||||
.$post(`/api/podcasts/${this.libraryItemId}/open-feed`, payload)
|
|
||||||
.then((data) => {
|
|
||||||
if (data.success) {
|
|
||||||
console.log('Opened RSS Feed', data)
|
|
||||||
this.rssFeedUrl = data.feedUrl
|
|
||||||
this.showRssFeedModal = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Failed to open RSS Feed', error)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
episodeDownloadQueued(episodeDownload) {
|
episodeDownloadQueued(episodeDownload) {
|
||||||
if (episodeDownload.libraryItemId === this.libraryItemId) {
|
if (episodeDownload.libraryItemId === this.libraryItemId) {
|
||||||
|
@ -125,6 +125,31 @@ Vue.prototype.$sanitizeFilename = (input, replacement = '') => {
|
|||||||
return sanitized
|
return sanitized
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SOURCE: https://gist.github.com/spyesx/561b1d65d4afb595f295
|
||||||
|
// modified: allowed underscores
|
||||||
|
Vue.prototype.$sanitizeSlug = (str) => {
|
||||||
|
if (!str) return ''
|
||||||
|
|
||||||
|
str = str.replace(/^\s+|\s+$/g, '') // trim
|
||||||
|
str = str.toLowerCase()
|
||||||
|
|
||||||
|
// remove accents, swap ñ for n, etc
|
||||||
|
var from = "àáäâèéëêìíïîòóöôùúüûñçěščřžýúůďťň·/,:;"
|
||||||
|
var to = "aaaaeeeeiiiioooouuuuncescrzyuudtn-----"
|
||||||
|
|
||||||
|
for (var i = 0, l = from.length; i < l; i++) {
|
||||||
|
str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str.replace('.', '-') // replace a dot by a dash
|
||||||
|
.replace(/[^a-z0-9 -_]/g, '') // remove invalid chars
|
||||||
|
.replace(/\s+/g, '-') // collapse whitespace and replace by a dash
|
||||||
|
.replace(/-+/g, '-') // collapse dashes
|
||||||
|
.replace(/\//g, '') // collapse all forward-slashes
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
Vue.prototype.$copyToClipboard = (str, ctx) => {
|
Vue.prototype.$copyToClipboard = (str, ctx) => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (!navigator.clipboard) {
|
if (!navigator.clipboard) {
|
||||||
|
@ -173,6 +173,12 @@ class PodcastController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const feedData = this.rssFeedManager.openPodcastFeed(req.user, req.libraryItem, req.body)
|
const feedData = this.rssFeedManager.openPodcastFeed(req.user, req.libraryItem, req.body)
|
||||||
|
if (feedData.error) {
|
||||||
|
return res.json({
|
||||||
|
success: false,
|
||||||
|
error: feedData.error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
|
@ -59,23 +59,23 @@ class RssFeedManager {
|
|||||||
readStream.pipe(res)
|
readStream.pipe(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
openFeed(userId, feedId, libraryItem, serverAddress) {
|
openFeed(userId, slug, libraryItem, serverAddress) {
|
||||||
const podcast = libraryItem.media
|
const podcast = libraryItem.media
|
||||||
|
|
||||||
const feedUrl = `${serverAddress}/feed/${feedId}`
|
const feedUrl = `${serverAddress}/feed/${slug}`
|
||||||
// Removed Podcast npm package and ip package
|
// Removed Podcast npm package and ip package
|
||||||
const feed = new Podcast({
|
const feed = new Podcast({
|
||||||
title: podcast.metadata.title,
|
title: podcast.metadata.title,
|
||||||
description: podcast.metadata.description,
|
description: podcast.metadata.description,
|
||||||
feedUrl,
|
feedUrl,
|
||||||
siteUrl: serverAddress,
|
siteUrl: serverAddress,
|
||||||
imageUrl: podcast.coverPath ? `${serverAddress}/feed/${feedId}/cover` : `${serverAddress}/Logo.png`,
|
imageUrl: podcast.coverPath ? `${serverAddress}/feed/${slug}/cover` : `${serverAddress}/Logo.png`,
|
||||||
author: podcast.metadata.author || 'advplyr',
|
author: podcast.metadata.author || 'advplyr',
|
||||||
language: 'en'
|
language: 'en'
|
||||||
})
|
})
|
||||||
podcast.episodes.forEach((episode) => {
|
podcast.episodes.forEach((episode) => {
|
||||||
var contentUrl = episode.audioTrack.contentUrl.replace(/\\/g, '/')
|
var contentUrl = episode.audioTrack.contentUrl.replace(/\\/g, '/')
|
||||||
contentUrl = contentUrl.replace(`/s/item/${libraryItem.id}`, `/feed/${feedId}/item`)
|
contentUrl = contentUrl.replace(`/s/item/${libraryItem.id}`, `/feed/${slug}/item`)
|
||||||
|
|
||||||
feed.addItem({
|
feed.addItem({
|
||||||
title: episode.title,
|
title: episode.title,
|
||||||
@ -92,7 +92,8 @@ class RssFeedManager {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const feedData = {
|
const feedData = {
|
||||||
id: feedId,
|
id: slug,
|
||||||
|
slug,
|
||||||
userId,
|
userId,
|
||||||
libraryItemId: libraryItem.id,
|
libraryItemId: libraryItem.id,
|
||||||
libraryItemPath: libraryItem.path,
|
libraryItemPath: libraryItem.path,
|
||||||
@ -101,14 +102,22 @@ class RssFeedManager {
|
|||||||
feedUrl,
|
feedUrl,
|
||||||
feed
|
feed
|
||||||
}
|
}
|
||||||
this.feeds[feedId] = feedData
|
this.feeds[slug] = feedData
|
||||||
return feedData
|
return feedData
|
||||||
}
|
}
|
||||||
|
|
||||||
openPodcastFeed(user, libraryItem, options) {
|
openPodcastFeed(user, libraryItem, options) {
|
||||||
const serverAddress = options.serverAddress
|
const serverAddress = options.serverAddress
|
||||||
const feedId = getId('feed')
|
const slug = options.slug
|
||||||
const feedData = this.openFeed(user.id, feedId, libraryItem, serverAddress)
|
|
||||||
|
if (this.feeds[slug]) {
|
||||||
|
Logger.error(`[RssFeedManager] Slug already in use`)
|
||||||
|
return {
|
||||||
|
error: `Slug "${slug}" already in use`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const feedData = this.openFeed(user.id, slug, libraryItem, serverAddress)
|
||||||
Logger.debug(`[RssFeedManager] Opened podcast feed ${feedData.feedUrl}`)
|
Logger.debug(`[RssFeedManager] Opened podcast feed ${feedData.feedUrl}`)
|
||||||
this.emitter('rss_feed_open', { libraryItemId: libraryItem.id, feedUrl: feedData.feedUrl })
|
this.emitter('rss_feed_open', { libraryItemId: libraryItem.id, feedUrl: feedData.feedUrl })
|
||||||
return feedData
|
return feedData
|
||||||
|
Loading…
Reference in New Issue
Block a user