Update:Experimental RSS feed setting custom slugs with default to library item id #553

This commit is contained in:
advplyr 2022-05-03 18:52:34 -05:00
parent 55c40658f2
commit 195a30096f
5 changed files with 112 additions and 41 deletions

View File

@ -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() {}
} }

View File

@ -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) {

View File

@ -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) {

View File

@ -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,

View File

@ -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