mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-10 11:21:36 +00:00
RSS Manager code
This commit is contained in:
parent
2ada293e8a
commit
aefcc4490f
7 changed files with 508 additions and 233 deletions
|
|
@ -10,7 +10,15 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex mt-2">
|
||||
<div class="w-full relative">
|
||||
<ui-text-input-with-label ref="feedUrlInput" v-model="details.feedUrl" :label="$strings.LabelRSSFeedURL" class="mt-2" />
|
||||
<div v-if="details.feedHealthy != null" class="material-icons absolute right-2 bottom-1 p-0.5">
|
||||
<widgets-feed-healthy-indicator :value="details.feedHealthy"></widgets-feed-healthy-indicator>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="details.lastSuccessfulFetchAt" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelFeedLastSuccessfulCheck }}: {{$dateDistanceFromNow(details.lastSuccessfulFetchAt)}}</p>
|
||||
|
||||
<ui-textarea-with-label ref="descriptionInput" v-model="details.description" :rows="3" :label="$strings.LabelDescription" class="mt-2" />
|
||||
|
||||
|
|
@ -71,7 +79,9 @@ export default {
|
|||
itunesArtistId: null,
|
||||
explicit: false,
|
||||
language: null,
|
||||
type: null
|
||||
type: null,
|
||||
feedHealthy: false,
|
||||
lastSuccessfulFetchAt: null
|
||||
},
|
||||
newTags: []
|
||||
}
|
||||
|
|
@ -229,6 +239,8 @@ export default {
|
|||
this.details.language = this.mediaMetadata.language || ''
|
||||
this.details.explicit = !!this.mediaMetadata.explicit
|
||||
this.details.type = this.mediaMetadata.type || 'episodic'
|
||||
this.details.feedHealthy = !!this.mediaMetadata.feedHealthy
|
||||
this.details.lastSuccessfulFetchAt = this.mediaMetadata.lastSuccessfulFetchAt || null
|
||||
|
||||
this.newTags = [...(this.media.tags || [])]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,118 @@
|
|||
<template>
|
||||
<div>
|
||||
<app-settings-content :header-text="$strings.HeaderRSSFeeds">
|
||||
<div class="w-full py-2">
|
||||
<div class="flex -mb-px">
|
||||
<div
|
||||
class="w-1/2 h-8 rounded-tl-md relative border border-black-200 flex items-center justify-center cursor-pointer"
|
||||
:class="!showIncomingFeedsView ? 'text-white bg-bg hover:bg-opacity-60 border-b-bg' : 'text-gray-400 hover:text-gray-300 bg-primary bg-opacity-70 hover:bg-opacity-60'"
|
||||
@click="showIncomingFeedsView = false">
|
||||
<p class="text-sm">Opened Feeds</p>
|
||||
</div>
|
||||
<div
|
||||
class="w-1/2 h-8 rounded-tr-md relative border border-black-200 flex items-center justify-center -ml-px cursor-pointer"
|
||||
:class="showIncomingFeedsView ? 'text-white bg-bg hover:bg-opacity-60 border-b-bg' : 'text-gray-400 hover:text-gray-300 bg-primary bg-opacity-70 hover:bg-opacity-60'"
|
||||
@click="showIncomingFeedsView = true">
|
||||
<p class="text-sm">Subscribed Feeds</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-2 py-4 md:p-4 border border-black-200 rounded-b-md mr-px" style="min-height: 280px">
|
||||
<template v-if="showIncomingFeedsView">
|
||||
<div v-if="incomingFeeds.length" class="block max-w-full">
|
||||
<div class="flex -mx-1 items-center mb-3">
|
||||
<div class="w-3/4 px-1">
|
||||
<form @submit.prevent="incomingFeedsSubmit" class="flex flex-grow">
|
||||
<ui-text-input v-model="incomingFeedsSearch" @input="incomingFeedsInputUpdate" type="search" :placeholder="$strings.PlaceholderSearchTitle" class="flex-grow text-sm md:text-base" />
|
||||
</form>
|
||||
</div>
|
||||
<div class="flex-grow px-1">
|
||||
<ui-checkbox v-model="incomingFeedShowOnlyUnhealthy" :label="$strings.LabelFeedShowOnlyUnhealthy" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="rssFeedsTable text-xs">
|
||||
<tr class="bg-primary bg-opacity-40 h-12">
|
||||
<th class="w-16 min-w-16"></th>
|
||||
<th class="w-48 max-w-64 min-w-24 text-left truncate">{{ $strings.LabelTitle }}/{{ $strings.LabelFeedURL }}</th>
|
||||
<th class="w-24 min-w-16 text-left">{{ $strings.LabelFeedLastChecked }}</th>
|
||||
<th class="w-24 min-w-16 text-left">{{ $strings.LabelFeedLastSuccessfulCheck }}</th>
|
||||
<th class="w-16 min-w-16 text-left">{{ $strings.LabelFeedHealthy }}</th>
|
||||
<th class="w-24 min-w-16 text-left">{{ $strings.LabelFeedNextAutomaticCheck }}</th>
|
||||
<th class="w-16 text-center"></th>
|
||||
</tr>
|
||||
|
||||
<tr v-for="incomingFeed in incomingFeedsList" :key="incomingFeed.id" class="cursor-pointer h-12">
|
||||
<!-- -->
|
||||
<td>
|
||||
<covers-preview-cover v-if="incomingFeed.coverPath" :width="50"
|
||||
:src="$store.getters['globals/getLibraryItemCoverSrcById'](incomingFeed.id)"
|
||||
:book-cover-aspect-ratio="bookCoverAspectRatio" :show-resolution="false"/>
|
||||
<img v-else :src="noCoverUrl" class="h-full w-full"/>
|
||||
</td>
|
||||
<!-- -->
|
||||
<td class="w-48 max-w-64 min-w-24 text-left truncate">
|
||||
<p class="truncate">{{ incomingFeed.metadata.title }}</p>
|
||||
<p class="truncate text-xs text-gray-300">{{ incomingFeed.metadata.feedUrl }}</p>
|
||||
</td>
|
||||
<!-- -->
|
||||
<td class="text-left">
|
||||
<ui-tooltip v-if="incomingFeed.lastEpisodeCheck" direction="top"
|
||||
:text="$formatDatetime(incomingFeed.lastEpisodeCheck, dateFormat, timeFormat)">
|
||||
<p class="text-gray-200">{{ $dateDistanceFromNow(incomingFeed.lastEpisodeCheck) }}</p>
|
||||
</ui-tooltip>
|
||||
</td>
|
||||
<!-- -->
|
||||
<td class="text-left">
|
||||
<ui-tooltip v-if="incomingFeed.metadata.lastSuccessfulFetchAt" direction="top"
|
||||
:text="$formatDatetime(incomingFeed.metadata.lastSuccessfulFetchAt, dateFormat, timeFormat)">
|
||||
<p class="text-gray-200">{{
|
||||
$dateDistanceFromNow(incomingFeed.metadata.lastSuccessfulFetchAt)
|
||||
}}</p>
|
||||
</ui-tooltip>
|
||||
<p class="text-gray-200" v-else>{{ $strings.MessageNoAvailable }}</p>
|
||||
</td>
|
||||
<!-- -->
|
||||
<td class="text-center leading-none lg:table-cell">
|
||||
<widgets-feed-healthy-indicator :value="!!incomingFeed.metadata.feedHealthy" />
|
||||
</td>
|
||||
<!-- -->
|
||||
<td class="text-left">
|
||||
<ui-tooltip v-if="incomingFeed.autoDownloadEpisodes" direction="top"
|
||||
:text="`${$strings.LabelCronExpression}: ${incomingFeed.autoDownloadSchedule}`">
|
||||
<p class="text-gray-200">
|
||||
{{ nextRun(incomingFeed.autoDownloadSchedule) }}
|
||||
</p>
|
||||
</ui-tooltip>
|
||||
</td>
|
||||
<!-- -->
|
||||
<td>
|
||||
<div class="w-full flex flex-row items-center justify-center">
|
||||
<ui-tooltip direction="top"
|
||||
:text="$strings.ButtonCopyFeedURL">
|
||||
<button class="inline-flex material-icons text-xl mx-1 mt-1 text-white/70 hover:text-white/100"
|
||||
@click.stop="copyToClipboard(incomingFeed.metadata.feedUrl)">content_copy
|
||||
</button>
|
||||
</ui-tooltip>
|
||||
<ui-tooltip direction="top" :text="$strings.ButtonForceReCheckFeed">
|
||||
<button class="inline-flex material-icons text-xl mx-1 mt-1 text-white/70 hover:text-white/100"
|
||||
@click.stop="forceRecheckFeed(incomingFeed)"
|
||||
:disabled="incomingFeed.isLoading">
|
||||
<span v-if="incomingFeed.isLoading" class="material-icons">hourglass_empty</span>
|
||||
<span v-else class="material-icons">autorenew</span>
|
||||
</button>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="feeds.length" class="block max-w-full">
|
||||
<form @submit.prevent="feedsSubmit" class="flex flex-grow">
|
||||
<ui-text-input v-model="feedsSearch" @input="feedsInputUpdate" type="search" :placeholder="$strings.PlaceholderSearchTitle" class="flex-grow mb-3 text-sm md:text-base" />
|
||||
</form>
|
||||
|
||||
<table class="rssFeedsTable text-xs">
|
||||
<tr class="bg-primary bg-opacity-40 h-12">
|
||||
<th class="w-16 min-w-16"></th>
|
||||
|
|
@ -9,12 +120,15 @@
|
|||
<th class="w-48 min-w-24 text-left hidden xl:table-cell">{{ $strings.LabelSlug }}</th>
|
||||
<th class="w-24 min-w-16 text-left hidden md:table-cell">{{ $strings.LabelType }}</th>
|
||||
<th class="w-16 min-w-16 text-center">{{ $strings.HeaderEpisodes }}</th>
|
||||
<th class="w-16 min-w-16 text-center hidden lg:table-cell">{{ $strings.LabelRSSFeedPreventIndexing }}</th>
|
||||
<th class="w-16 min-w-16 text-center hidden lg:table-cell">{{
|
||||
$strings.LabelRSSFeedPreventIndexing
|
||||
}}
|
||||
</th>
|
||||
<th class="w-48 min-w-24 flex-grow hidden md:table-cell">{{ $strings.LabelLastUpdate }}</th>
|
||||
<th class="w-16 text-left"></th>
|
||||
</tr>
|
||||
|
||||
<tr v-for="feed in feeds" :key="feed.id" class="cursor-pointer h-12" @click="showFeed(feed)">
|
||||
<tr v-for="feed in feedsList" :key="feed.id" class="cursor-pointer h-12" @click="showFeed(feed)">
|
||||
<!-- -->
|
||||
<td>
|
||||
<img :src="coverUrl(feed)" class="h-full w-full"/>
|
||||
|
|
@ -43,17 +157,22 @@
|
|||
</td>
|
||||
<!-- -->
|
||||
<td class="text-center hidden md:table-cell">
|
||||
<ui-tooltip v-if="feed.updatedAt" direction="top" :text="$formatDatetime(feed.updatedAt, dateFormat, timeFormat)">
|
||||
<ui-tooltip v-if="feed.updatedAt" direction="top"
|
||||
:text="$formatDatetime(feed.updatedAt, dateFormat, timeFormat)">
|
||||
<p class="text-gray-200">{{ $dateDistanceFromNow(feed.updatedAt) }}</p>
|
||||
</ui-tooltip>
|
||||
</td>
|
||||
<!-- -->
|
||||
<td class="text-center">
|
||||
<ui-icon-btn icon="delete" class="mx-0.5" :size="7" bg-color="error" outlined @click.stop="deleteFeedClick(feed)" />
|
||||
<ui-icon-btn icon="delete" class="mx-0.5" :size="7" bg-color="error" outlined
|
||||
@click.stop="deleteFeedClick(feed)"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</app-settings-content>
|
||||
<modals-rssfeed-view-feed-modal v-model="showFeedModal" :feed="selectedFeed"/>
|
||||
</div>
|
||||
|
|
@ -63,9 +182,18 @@
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
showIncomingFeedsView: false,
|
||||
showFeedModal: false,
|
||||
selectedFeed: null,
|
||||
feeds: []
|
||||
feeds: [],
|
||||
incomingFeeds: [],
|
||||
feedsSearch: null,
|
||||
feedsSearchTimeout: null,
|
||||
feedsSearchText: null,
|
||||
incomingFeedsSearch: null,
|
||||
incomingFeedsSearchTimeout: null,
|
||||
incomingFeedsSearchText: null,
|
||||
incomingFeedShowOnlyUnhealthy: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -74,13 +202,63 @@ export default {
|
|||
},
|
||||
timeFormat() {
|
||||
return this.$store.state.serverSettings.timeFormat
|
||||
}
|
||||
},
|
||||
noCoverUrl() {
|
||||
return `${this.$config.routerBasePath}/Logo.png`
|
||||
},
|
||||
bookCoverAspectRatio() {
|
||||
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
||||
},
|
||||
feedsList() {
|
||||
return this.feeds.filter((feed) => {
|
||||
if (!this.feedsSearchText) return true
|
||||
return feed?.meta?.title?.toLowerCase().includes(this.feedsSearchText) || feed?.slug?.toLowerCase().includes(this.feedsSearchText)
|
||||
})
|
||||
},
|
||||
incomingFeedsSorted() {
|
||||
return this.incomingFeedShowOnlyUnhealthy
|
||||
? this.incomingFeeds.filter(incomingFeed => !incomingFeed.metadata.feedHealthy)
|
||||
: this.incomingFeeds;
|
||||
},
|
||||
incomingFeedsList() {
|
||||
return this.incomingFeedsSorted.filter((incomingFeed) => {
|
||||
if (!this.incomingFeedsSearchText) return true
|
||||
if (this.incomingFeedShowOnlyUnhealthy && incomingFeed?.metadata?.feedHealthy) return false
|
||||
return incomingFeed?.metadata?.title?.toLowerCase().includes(this.incomingFeedsSearchText) ||
|
||||
incomingFeed?.metadata?.feedUrl?.toLowerCase().includes(this.incomingFeedsSearchText)
|
||||
})
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
feedsSubmit() {},
|
||||
feedsInputUpdate() {
|
||||
clearTimeout(this.feedsSearchTimeout)
|
||||
this.feedsSearchTimeout = setTimeout(() => {
|
||||
if (!this.feedsSearch || !this.feedsSearch.trim()) {
|
||||
this.feedsSearchText = ''
|
||||
return
|
||||
}
|
||||
this.feedsSearchText = this.feedsSearch.toLowerCase().trim()
|
||||
}, 500)
|
||||
},
|
||||
incomingFeedsSubmit() {},
|
||||
incomingFeedsInputUpdate() {
|
||||
clearTimeout(this.incomingFeedsSearchTimeout)
|
||||
this.incomingFeedsSearchTimeout = setTimeout(() => {
|
||||
if (!this.incomingFeedsSearch || !this.incomingFeedsSearch.trim()) {
|
||||
this.incomingFeedsSearchText = ''
|
||||
return
|
||||
}
|
||||
this.incomingFeedsSearchText = this.incomingFeedsSearch.toLowerCase().trim()
|
||||
}, 500)
|
||||
},
|
||||
showFeed(feed) {
|
||||
this.selectedFeed = feed
|
||||
this.showFeedModal = true
|
||||
},
|
||||
copyToClipboard(str) {
|
||||
this.$copyToClipboard(str, this)
|
||||
},
|
||||
deleteFeedClick(feed) {
|
||||
const payload = {
|
||||
message: this.$strings.MessageConfirmCloseFeed,
|
||||
|
|
@ -117,9 +295,40 @@ export default {
|
|||
return this.$strings.LabelUnknown
|
||||
},
|
||||
coverUrl(feed) {
|
||||
if (!feed.coverPath) return `${this.$config.routerBasePath}/Logo.png`
|
||||
if (!feed.coverPath) return this.noCoverUrl
|
||||
return `${feed.feedUrl}/cover`
|
||||
},
|
||||
nextRun(cronExpression) {
|
||||
if (!cronExpression) return ''
|
||||
const parsed = this.$getNextScheduledDate(cronExpression)
|
||||
return this.$formatJsDatetime(parsed, this.$store.state.serverSettings.dateFormat, this.$store.state.serverSettings.timeFormat) || ''
|
||||
},
|
||||
async forceRecheckFeed(podcast) {
|
||||
podcast.isLoading = true
|
||||
let podcastResult;
|
||||
|
||||
try {
|
||||
podcastResult = await this.$axios.$get(`/api/podcasts/${podcast.id}/check-feed-url`)
|
||||
|
||||
if (!podcastResult?.feedHealthy) {
|
||||
this.$toast.error('Podcast feed url is not healthy')
|
||||
} else {
|
||||
this.$toast.success('Podcast feed url is healthy')
|
||||
}
|
||||
|
||||
podcast.lastEpisodeCheck = Date.parse(podcastResult.lastEpisodeCheck)
|
||||
if (podcastResult.lastSuccessfulFetchAt) {
|
||||
podcast.metadata.lastSuccessfulFetchAt = Date.parse(podcastResult.lastSuccessfulFetchAt)
|
||||
}
|
||||
podcast.metadata.feedHealthy = podcastResult.feedHealthy
|
||||
} catch (error) {
|
||||
console.error('Podcast feed url is not healthy', error)
|
||||
this.$toast.error('Podcast feed url is not healthy')
|
||||
podcastResult = null
|
||||
} finally {
|
||||
podcast.isLoading = false
|
||||
}
|
||||
},
|
||||
async loadFeeds() {
|
||||
const data = await this.$axios.$get(`/api/feeds`).catch((err) => {
|
||||
console.error('Failed to load RSS feeds', err)
|
||||
|
|
@ -131,8 +340,21 @@ export default {
|
|||
}
|
||||
this.feeds = data.feeds
|
||||
},
|
||||
async loadIncomingFeeds() {
|
||||
const data = await this.$axios.$get(`/api/podcasts/incomingFeeds`).catch((err) => {
|
||||
console.error('Failed to load incoming RSS feeds', err)
|
||||
return null
|
||||
})
|
||||
if (!data) {
|
||||
this.$toast.error('Failed to load incoming RSS feeds')
|
||||
return
|
||||
}
|
||||
|
||||
this.incomingFeeds = data.podcasts.map(podcast => ({...podcast, isLoading: false}));
|
||||
},
|
||||
init() {
|
||||
this.loadFeeds()
|
||||
this.loadIncomingFeeds()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"ButtonCloseFeed": "Close Feed",
|
||||
"ButtonCollections": "Collections",
|
||||
"ButtonConfigureScanner": "Configure Scanner",
|
||||
"ButtonCopyFeedURL": "Copy Feed URL",
|
||||
"ButtonCreate": "Create",
|
||||
"ButtonCreateBackup": "Create Backup",
|
||||
"ButtonDelete": "Delete",
|
||||
|
|
@ -24,6 +25,7 @@
|
|||
"ButtonEdit": "Edit",
|
||||
"ButtonEditChapters": "Edit Chapters",
|
||||
"ButtonEditPodcast": "Edit Podcast",
|
||||
"ButtonForceReCheckFeed": "Force Re-Check Feed",
|
||||
"ButtonForceReScan": "Force Re-Scan",
|
||||
"ButtonFullPath": "Full Path",
|
||||
"ButtonHide": "Hide",
|
||||
|
|
@ -246,7 +248,14 @@
|
|||
"LabelEpisodeType": "Episode Type",
|
||||
"LabelExample": "Example",
|
||||
"LabelExplicit": "Explicit",
|
||||
"LabelFeedHealthy": "Feed Healthy",
|
||||
"LabelFeedURL": "Feed URL",
|
||||
"LabelFeedLastChecked": "Last Checked",
|
||||
"LabelFeedLastSuccessfulCheck": "Last Successful Check",
|
||||
"LabelFeedShowOnlyUnhealthy": "Show only unhealthy",
|
||||
"LabelFeedNextAutomaticCheck": "Next Automatic Check",
|
||||
"LabelFeedNotWorking": "Feed is returning errors, check the logs for more information",
|
||||
"LabelFeedWorking": "Feed working as expected",
|
||||
"LabelFile": "File",
|
||||
"LabelFileBirthtime": "File Birthtime",
|
||||
"LabelFileModified": "File Modified",
|
||||
|
|
@ -572,6 +581,7 @@
|
|||
"MessageMatchBooksDescription": "will attempt to match books in the library with a book from the selected search provider and fill in empty details and cover art. Does not overwrite details.",
|
||||
"MessageNoAudioTracks": "No audio tracks",
|
||||
"MessageNoAuthors": "No Authors",
|
||||
"MessageNoAvailable": "N/A",
|
||||
"MessageNoBackups": "No Backups",
|
||||
"MessageNoBookmarks": "No Bookmarks",
|
||||
"MessageNoChapters": "No Chapters",
|
||||
|
|
@ -642,6 +652,7 @@
|
|||
"PlaceholderNewPlaylist": "New playlist name",
|
||||
"PlaceholderSearch": "Search..",
|
||||
"PlaceholderSearchEpisode": "Search episode..",
|
||||
"PlaceholderSearchTitle": "Search title..",
|
||||
"ToastAccountUpdateFailed": "Failed to update account",
|
||||
"ToastAccountUpdateSuccess": "Account updated",
|
||||
"ToastAuthorImageRemoveFailed": "Failed to remove image",
|
||||
|
|
|
|||
|
|
@ -127,14 +127,32 @@ class PodcastController {
|
|||
const podcast = await getPodcastFeed(libraryItem.media.metadata.feedUrl)
|
||||
|
||||
if (!podcast) {
|
||||
this.podcastManager.setFeedHealthStatus(libraryItem, false)
|
||||
this.podcastManager.setFeedHealthStatus(libraryItem.media.id, false)
|
||||
return res.status(404).send('Podcast RSS feed request failed or invalid response data')
|
||||
}
|
||||
|
||||
this.podcastManager.setFeedHealthStatus(libraryItem, true)
|
||||
this.podcastManager.setFeedHealthStatus(libraryItem.media.id, true)
|
||||
res.json({ podcast })
|
||||
}
|
||||
|
||||
async checkPodcastFeedUrl(req, res) {
|
||||
const podcastId = req.params.id;
|
||||
|
||||
try {
|
||||
const podcast = await Database.podcastModel.findByPk(req.params.id)
|
||||
|
||||
const podcastResult = await getPodcastFeed(podcast.feedURL);
|
||||
const podcastNewStatus = await this.podcastManager.setFeedHealthStatus(podcastId, !!podcastResult);
|
||||
|
||||
Logger.info(podcastNewStatus);
|
||||
|
||||
return res.json(podcastNewStatus);
|
||||
} catch (error) {
|
||||
Logger.error(`[PodcastController] checkPodcastFeed: Error checking podcast feed for podcast ${podcastId}`, error)
|
||||
res.status(500).json({ error: 'An error occurred while checking the podcast feed.' });
|
||||
}
|
||||
}
|
||||
|
||||
async getFeedsFromOPMLText(req, res) {
|
||||
if (!req.body.opmlText) {
|
||||
return res.sendStatus(400)
|
||||
|
|
|
|||
|
|
@ -122,7 +122,8 @@ class Podcast extends Model {
|
|||
|
||||
static async getAllIncomingFeeds() {
|
||||
const podcasts = await this.findAll()
|
||||
return podcasts.map(p => this.getFromOld({metadata: p.dataValues}))
|
||||
const podcastsFiltered = podcasts.filter(p => p.dataValues.feedURL !== null);
|
||||
return podcastsFiltered.map(p => this.getOldPodcast({media: p.dataValues}))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ class PodcastMetadata {
|
|||
this.explicit = false
|
||||
this.language = null
|
||||
this.type = null
|
||||
this.lastSuccessfulFetchAt = null
|
||||
this.feedHealthy = null
|
||||
|
||||
if (metadata) {
|
||||
this.construct(metadata)
|
||||
|
|
@ -36,6 +38,8 @@ class PodcastMetadata {
|
|||
this.explicit = metadata.explicit
|
||||
this.language = metadata.language || null
|
||||
this.type = metadata.type || 'episodic'
|
||||
this.lastSuccessfulFetchAt = metadata.lastSuccessfulFetchAt || null
|
||||
this.feedHealthy = metadata.feedHealthy || null
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
|
|
@ -52,7 +56,9 @@ class PodcastMetadata {
|
|||
itunesArtistId: this.itunesArtistId,
|
||||
explicit: this.explicit,
|
||||
language: this.language,
|
||||
type: this.type
|
||||
type: this.type,
|
||||
lastSuccessfulFetchAt: this.lastSuccessfulFetchAt,
|
||||
feedHealthy: this.feedHealthy
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +77,9 @@ class PodcastMetadata {
|
|||
itunesArtistId: this.itunesArtistId,
|
||||
explicit: this.explicit,
|
||||
language: this.language,
|
||||
type: this.type
|
||||
type: this.type,
|
||||
lastSuccessfulFetchAt: this.lastSuccessfulFetchAt,
|
||||
feedHealthy: this.feedHealthy
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,6 +128,8 @@ class PodcastMetadata {
|
|||
if (mediaMetadata.genres && mediaMetadata.genres.length) {
|
||||
this.genres = [...mediaMetadata.genres]
|
||||
}
|
||||
this.lastSuccessfulFetchAt = mediaMetadata.lastSuccessfulFetchAt || null
|
||||
this.feedHealthy = mediaMetadata.feedHealthy || null
|
||||
}
|
||||
|
||||
update(payload) {
|
||||
|
|
|
|||
|
|
@ -229,6 +229,7 @@ class ApiRouter {
|
|||
this.router.post('/podcasts/feed', PodcastController.getPodcastFeed.bind(this))
|
||||
this.router.get('/podcasts/incomingFeeds', PodcastController.getPodcastsWithIncomingFeeds.bind(this))
|
||||
this.router.get('/podcasts/:id/feed', PodcastController.middleware.bind(this), PodcastController.checkPodcastFeed.bind(this))
|
||||
this.router.get('/podcasts/:id/check-feed-url', PodcastController.checkPodcastFeedUrl.bind(this))
|
||||
this.router.post('/podcasts/opml', PodcastController.getFeedsFromOPMLText.bind(this))
|
||||
this.router.get('/podcasts/:id/checknew', PodcastController.middleware.bind(this), PodcastController.checkNewEpisodes.bind(this))
|
||||
this.router.get('/podcasts/:id/downloads', PodcastController.middleware.bind(this), PodcastController.getEpisodeDownloads.bind(this))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue