mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-08 00:08:14 +01:00
Add:Podcast option to quick match all unmatched episodes
This commit is contained in:
parent
1609f1a499
commit
49c581ed35
@ -1,8 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full py-6">
|
<div class="w-full py-6">
|
||||||
|
<p class="text-lg mb-2 font-semibold md:hidden">{{ $strings.HeaderEpisodes }}</p>
|
||||||
<div class="flex items-center mb-4">
|
<div class="flex items-center mb-4">
|
||||||
<p class="text-lg mb-0 font-semibold">{{ $strings.HeaderEpisodes }}</p>
|
<p class="text-lg mb-0 font-semibold hidden md:block">{{ $strings.HeaderEpisodes }}</p>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow hidden md:block" />
|
||||||
<template v-if="isSelectionMode">
|
<template v-if="isSelectionMode">
|
||||||
<ui-tooltip :text="selectedIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="bottom">
|
<ui-tooltip :text="selectedIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="bottom">
|
||||||
<ui-read-icon-btn :disabled="processing" :is-read="selectedIsFinished" @click="toggleBatchFinished" class="mx-1.5" />
|
<ui-read-icon-btn :disabled="processing" :is-read="selectedIsFinished" @click="toggleBatchFinished" class="mx-1.5" />
|
||||||
@ -11,8 +12,10 @@
|
|||||||
<ui-btn :disabled="processing" small class="ml-2 h-9" @click="clearSelected">{{ $strings.ButtonCancel }}</ui-btn>
|
<ui-btn :disabled="processing" small class="ml-2 h-9" @click="clearSelected">{{ $strings.ButtonCancel }}</ui-btn>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<controls-filter-select v-model="filterKey" :items="filterItems" class="w-32 md:w-36 h-9 ml-1 sm:ml-4" />
|
<controls-filter-select v-model="filterKey" :items="filterItems" class="w-36 h-9 sm:ml-4" />
|
||||||
<controls-sort-select v-model="sortKey" :descending.sync="sortDesc" :items="sortItems" class="w-32 sm:w-44 md:w-48 h-9 ml-1 sm:ml-4" />
|
<controls-sort-select v-model="sortKey" :descending.sync="sortDesc" :items="sortItems" class="w-44 md:w-48 h-9 ml-1 sm:ml-4" />
|
||||||
|
<div class="flex-grow md:hidden" />
|
||||||
|
<ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" class="ml-1" @action="contextMenuAction" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="!episodes.length" class="py-4 text-center text-lg">{{ $strings.MessageNoEpisodes }}</p>
|
<p v-if="!episodes.length" class="py-4 text-center text-lg">{{ $strings.MessageNoEpisodes }}</p>
|
||||||
@ -42,15 +45,27 @@ export default {
|
|||||||
showPodcastRemoveModal: false,
|
showPodcastRemoveModal: false,
|
||||||
selectedEpisodes: [],
|
selectedEpisodes: [],
|
||||||
episodesToRemove: [],
|
episodesToRemove: [],
|
||||||
processing: false
|
processing: false,
|
||||||
|
quickMatchingEpisodes: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
libraryItem() {
|
libraryItem: {
|
||||||
this.init()
|
handler() {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
contextMenuItems() {
|
||||||
|
if (!this.userIsAdminOrUp) return []
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: 'Quick match all episodes',
|
||||||
|
action: 'quick-match-episodes'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
sortItems() {
|
sortItems() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -94,8 +109,8 @@ export default {
|
|||||||
isSelectionMode() {
|
isSelectionMode() {
|
||||||
return this.selectedEpisodes.length > 0
|
return this.selectedEpisodes.length > 0
|
||||||
},
|
},
|
||||||
userCanUpdate() {
|
userIsAdminOrUp() {
|
||||||
return this.$store.getters['user/getUserCanUpdate']
|
return this.$store.getters['user/getIsAdminOrUp']
|
||||||
},
|
},
|
||||||
media() {
|
media() {
|
||||||
return this.libraryItem.media || {}
|
return this.libraryItem.media || {}
|
||||||
@ -131,6 +146,44 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
contextMenuAction(action) {
|
||||||
|
if (action === 'quick-match-episodes') {
|
||||||
|
if (this.quickMatchingEpisodes) return
|
||||||
|
|
||||||
|
this.quickMatchAllEpisodes()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
quickMatchAllEpisodes() {
|
||||||
|
if (!this.mediaMetadata.feedUrl) {
|
||||||
|
this.$toast.error(this.$strings.MessagePodcastHasNoRSSFeedForMatching)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.quickMatchingEpisodes = true
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
message: 'Quick matching episodes will overwrite details if a match is found. Only unmatched episodes will be updated. Are you sure?',
|
||||||
|
callback: (confirmed) => {
|
||||||
|
if (confirmed) {
|
||||||
|
this.$axios
|
||||||
|
.$post(`/api/podcasts/${this.libraryItem.id}/match-episodes?override=1`)
|
||||||
|
.then((data) => {
|
||||||
|
if (data.numEpisodesUpdated) {
|
||||||
|
this.$toast.success(`${data.numEpisodesUpdated} episodes updated`)
|
||||||
|
} else {
|
||||||
|
this.$toast.info('No changes were made')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to request match episodes', error)
|
||||||
|
this.$toast.error('Failed to match episodes')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.quickMatchingEpisodes = false
|
||||||
|
},
|
||||||
|
type: 'yesNo'
|
||||||
|
}
|
||||||
|
this.$store.commit('globals/setConfirmPrompt', payload)
|
||||||
|
},
|
||||||
addToPlaylist(episode) {
|
addToPlaylist(episode) {
|
||||||
this.$store.commit('globals/setSelectedPlaylistItems', [{ libraryItem: this.libraryItem, episode }])
|
this.$store.commit('globals/setSelectedPlaylistItems', [{ libraryItem: this.libraryItem, episode }])
|
||||||
this.$store.commit('globals/setShowPlaylistsModal', true)
|
this.$store.commit('globals/setShowPlaylistsModal', true)
|
||||||
|
@ -173,7 +173,7 @@ class PodcastController {
|
|||||||
async downloadEpisodes(req, res) {
|
async downloadEpisodes(req, res) {
|
||||||
if (!req.user.isAdminOrUp) {
|
if (!req.user.isAdminOrUp) {
|
||||||
Logger.error(`[PodcastController] Non-admin user attempted to download episodes`, req.user)
|
Logger.error(`[PodcastController] Non-admin user attempted to download episodes`, req.user)
|
||||||
return res.sendStatus(500)
|
return res.sendStatus(403)
|
||||||
}
|
}
|
||||||
var libraryItem = req.libraryItem
|
var libraryItem = req.libraryItem
|
||||||
|
|
||||||
@ -186,8 +186,27 @@ class PodcastController {
|
|||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST: api/podcasts/:id/match-episodes
|
||||||
|
async quickMatchEpisodes(req, res) {
|
||||||
|
if (!req.user.isAdminOrUp) {
|
||||||
|
Logger.error(`[PodcastController] Non-admin user attempted to download episodes`, req.user)
|
||||||
|
return res.sendStatus(403)
|
||||||
|
}
|
||||||
|
|
||||||
|
const overrideDetails = req.query.override === '1'
|
||||||
|
const episodesUpdated = await this.scanner.quickMatchPodcastEpisodes(req.libraryItem, { overrideDetails })
|
||||||
|
if (episodesUpdated) {
|
||||||
|
await this.db.updateLibraryItem(req.libraryItem)
|
||||||
|
SocketAuthority.emitter('item_updated', req.libraryItem.toJSONExpanded())
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
numEpisodesUpdated: episodesUpdated
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async updateEpisode(req, res) {
|
async updateEpisode(req, res) {
|
||||||
var libraryItem = req.libraryItem
|
const libraryItem = req.libraryItem
|
||||||
|
|
||||||
var episodeId = req.params.episodeId
|
var episodeId = req.params.episodeId
|
||||||
if (!libraryItem.media.checkHasEpisode(episodeId)) {
|
if (!libraryItem.media.checkHasEpisode(episodeId)) {
|
||||||
@ -237,7 +256,7 @@ class PodcastController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
middleware(req, res, next) {
|
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)
|
if (!item || !item.media) return res.sendStatus(404)
|
||||||
|
|
||||||
if (!item.isPodcast) {
|
if (!item.isPodcast) {
|
||||||
|
@ -229,6 +229,7 @@ class ApiRouter {
|
|||||||
this.router.get('/podcasts/:id/clear-queue', PodcastController.middleware.bind(this), PodcastController.clearEpisodeDownloadQueue.bind(this))
|
this.router.get('/podcasts/:id/clear-queue', PodcastController.middleware.bind(this), PodcastController.clearEpisodeDownloadQueue.bind(this))
|
||||||
this.router.get('/podcasts/:id/search-episode', PodcastController.middleware.bind(this), PodcastController.findEpisode.bind(this))
|
this.router.get('/podcasts/:id/search-episode', PodcastController.middleware.bind(this), PodcastController.findEpisode.bind(this))
|
||||||
this.router.post('/podcasts/:id/download-episodes', PodcastController.middleware.bind(this), PodcastController.downloadEpisodes.bind(this))
|
this.router.post('/podcasts/:id/download-episodes', PodcastController.middleware.bind(this), PodcastController.downloadEpisodes.bind(this))
|
||||||
|
this.router.post('/podcasts/:id/match-episodes', PodcastController.middleware.bind(this), PodcastController.quickMatchEpisodes.bind(this))
|
||||||
this.router.patch('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.updateEpisode.bind(this))
|
this.router.patch('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.updateEpisode.bind(this))
|
||||||
this.router.delete('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.removeEpisode.bind(this))
|
this.router.delete('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.removeEpisode.bind(this))
|
||||||
|
|
||||||
|
@ -880,15 +880,15 @@ class Scanner {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var episodesWereUpdated = false
|
let numEpisodesUpdated = 0
|
||||||
for (const episode of episodesToQuickMatch) {
|
for (const episode of episodesToQuickMatch) {
|
||||||
const episodeMatches = findMatchingEpisodesInFeed(feed, episode.title)
|
const episodeMatches = findMatchingEpisodesInFeed(feed, episode.title)
|
||||||
if (episodeMatches && episodeMatches.length) {
|
if (episodeMatches && episodeMatches.length) {
|
||||||
const wasUpdated = this.updateEpisodeWithMatch(libraryItem, episode, episodeMatches[0].episode, options)
|
const wasUpdated = this.updateEpisodeWithMatch(libraryItem, episode, episodeMatches[0].episode, options)
|
||||||
if (wasUpdated) episodesWereUpdated = true
|
if (wasUpdated) numEpisodesUpdated++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return episodesWereUpdated
|
return numEpisodesUpdated
|
||||||
}
|
}
|
||||||
|
|
||||||
updateEpisodeWithMatch(libraryItem, episode, episodeToMatch, options = {}) {
|
updateEpisodeWithMatch(libraryItem, episode, episodeToMatch, options = {}) {
|
||||||
|
Loading…
Reference in New Issue
Block a user