mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-07 18:01:42 +00:00
Added support for authenticated podcast feeds
This commit is contained in:
parent
329e3c7179
commit
66fee7d39d
6 changed files with 110 additions and 8 deletions
|
|
@ -29,15 +29,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
<div class="md:w-1/4 p-2">
|
<div class="w-full md:w-1/4 p-2">
|
||||||
<ui-dropdown :label="$strings.LabelPodcastType" v-model="podcast.type" :items="podcastTypes" small />
|
<ui-dropdown :label="$strings.LabelPodcastType" v-model="podcast.type" :items="podcastTypes" small />
|
||||||
</div>
|
</div>
|
||||||
<div class="md:w-1/4 p-2">
|
<div class="w-full md:w-1/4 p-2">
|
||||||
<ui-text-input-with-label v-model="podcast.language" :label="$strings.LabelLanguage" />
|
<ui-text-input-with-label v-model="podcast.language" :label="$strings.LabelLanguage" />
|
||||||
</div>
|
</div>
|
||||||
<div class="md:w-1/4 px-2 pt-7">
|
<div class="w-full md:w-1/4 px-2 pt-7">
|
||||||
<ui-checkbox v-model="podcast.explicit" :label="$strings.LabelExplicit" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
|
<ui-checkbox v-model="podcast.explicit" :label="$strings.LabelExplicit" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="w-full md:w-1/4 px-2 pt-7">
|
||||||
|
<ui-checkbox v-model="podcast.isAuthenticatedFeed" :label="$strings.LabelAuthenticatedFeed" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-2 w-full">
|
<div class="p-2 w-full">
|
||||||
<ui-textarea-with-label v-model="podcast.description" :label="$strings.LabelDescription" :rows="3" />
|
<ui-textarea-with-label v-model="podcast.description" :label="$strings.LabelDescription" :rows="3" />
|
||||||
|
|
@ -96,6 +99,7 @@ export default {
|
||||||
autoDownloadEpisodes: false,
|
autoDownloadEpisodes: false,
|
||||||
language: '',
|
language: '',
|
||||||
explicit: false,
|
explicit: false,
|
||||||
|
isAuthenticatedFeed: false,
|
||||||
type: ''
|
type: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -194,6 +198,7 @@ export default {
|
||||||
itunesArtistId: this.podcast.itunesArtistId,
|
itunesArtistId: this.podcast.itunesArtistId,
|
||||||
language: this.podcast.language,
|
language: this.podcast.language,
|
||||||
explicit: this.podcast.explicit,
|
explicit: this.podcast.explicit,
|
||||||
|
isAuthenticatedFeed: this.podcast.isAuthenticatedFeed,
|
||||||
type: this.podcast.type
|
type: this.podcast.type
|
||||||
},
|
},
|
||||||
autoDownloadEpisodes: this.podcast.autoDownloadEpisodes
|
autoDownloadEpisodes: this.podcast.autoDownloadEpisodes
|
||||||
|
|
@ -234,6 +239,7 @@ export default {
|
||||||
this.podcast.type = this._podcastData.type || this.feedMetadata.type || 'episodic'
|
this.podcast.type = this._podcastData.type || this.feedMetadata.type || 'episodic'
|
||||||
|
|
||||||
this.podcast.explicit = this._podcastData.explicit || this.feedMetadata.explicit === 'yes' || this.feedMetadata.explicit == 'true'
|
this.podcast.explicit = this._podcastData.explicit || this.feedMetadata.explicit === 'yes' || this.feedMetadata.explicit == 'true'
|
||||||
|
this.podcast.isAuthenticatedFeed = this._podcastData.isAuthenticatedFeed || false
|
||||||
if (this.folderItems[0]) {
|
if (this.folderItems[0]) {
|
||||||
this.selectedFolderId = this.folderItems[0].value
|
this.selectedFolderId = this.folderItems[0].value
|
||||||
this.folderUpdated()
|
this.folderUpdated()
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,9 @@
|
||||||
<ui-text-input-with-label ref="languageInput" v-model="details.language" :label="$strings.LabelLanguage" trim-whitespace @input="handleInputChange" />
|
<ui-text-input-with-label ref="languageInput" v-model="details.language" :label="$strings.LabelLanguage" trim-whitespace @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="grow px-1 pt-6">
|
<div class="grow px-1 pt-6">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center space-x-4">
|
||||||
<ui-checkbox v-model="details.explicit" :label="$strings.LabelExplicit" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" @input="handleInputChange" />
|
<ui-checkbox v-model="details.explicit" :label="$strings.LabelExplicit" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" @input="handleInputChange" />
|
||||||
|
<ui-checkbox v-model="details.isAuthenticatedFeed" :label="$strings.LabelAuthenticatedFeed" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -70,6 +71,7 @@ export default {
|
||||||
itunesId: null,
|
itunesId: null,
|
||||||
itunesArtistId: null,
|
itunesArtistId: null,
|
||||||
explicit: false,
|
explicit: false,
|
||||||
|
isAuthenticatedFeed: false,
|
||||||
language: null,
|
language: null,
|
||||||
type: null
|
type: null
|
||||||
},
|
},
|
||||||
|
|
@ -241,6 +243,7 @@ export default {
|
||||||
this.details.itunesArtistId = this.mediaMetadata.itunesArtistId || ''
|
this.details.itunesArtistId = this.mediaMetadata.itunesArtistId || ''
|
||||||
this.details.language = this.mediaMetadata.language || ''
|
this.details.language = this.mediaMetadata.language || ''
|
||||||
this.details.explicit = !!this.mediaMetadata.explicit
|
this.details.explicit = !!this.mediaMetadata.explicit
|
||||||
|
this.details.isAuthenticatedFeed = !!this.mediaMetadata.isAuthenticatedFeed
|
||||||
this.details.type = this.mediaMetadata.type || 'episodic'
|
this.details.type = this.mediaMetadata.type || 'episodic'
|
||||||
|
|
||||||
this.newTags = [...(this.media.tags || [])]
|
this.newTags = [...(this.media.tags || [])]
|
||||||
|
|
|
||||||
|
|
@ -366,6 +366,7 @@
|
||||||
"LabelExplicit": "Explicit",
|
"LabelExplicit": "Explicit",
|
||||||
"LabelExplicitChecked": "Explicit (checked)",
|
"LabelExplicitChecked": "Explicit (checked)",
|
||||||
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
|
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
|
||||||
|
"LabelAuthenticatedFeed": "Authenticated Feed",
|
||||||
"LabelExportOPML": "Export OPML",
|
"LabelExportOPML": "Export OPML",
|
||||||
"LabelFeedURL": "Feed URL",
|
"LabelFeedURL": "Feed URL",
|
||||||
"LabelFetchingMetadata": "Fetching Metadata",
|
"LabelFetchingMetadata": "Fetching Metadata",
|
||||||
|
|
|
||||||
68
server/migrations/v2.30.1-add-is-authenticated-feed.js
Normal file
68
server/migrations/v2.30.1-add-is-authenticated-feed.js
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
/**
|
||||||
|
* @typedef MigrationContext
|
||||||
|
* @property {import('sequelize').QueryInterface} queryInterface - a suquelize QueryInterface object.
|
||||||
|
* @property {import('../Logger')} logger - a Logger object.
|
||||||
|
*
|
||||||
|
* @typedef MigrationOptions
|
||||||
|
* @property {MigrationContext} context - an object containing the migration context.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const migrationVersion = '2.30.1'
|
||||||
|
const migrationName = `${migrationVersion}-add-is-authenticated-feed`
|
||||||
|
const loggerPrefix = `[${migrationVersion} migration]`
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This upward migration adds the isAuthenticatedFeed column to the podcasts table.
|
||||||
|
*
|
||||||
|
* @param {MigrationOptions} options - an object containing the migration context.
|
||||||
|
* @returns {Promise<void>} - A promise that resolves when the migration is complete.
|
||||||
|
*/
|
||||||
|
async function up({ context: { queryInterface, logger } }) {
|
||||||
|
// Upwards migration script
|
||||||
|
logger.info(`${loggerPrefix} UPGRADE BEGIN: ${migrationName}`)
|
||||||
|
|
||||||
|
const DataTypes = queryInterface.sequelize.Sequelize.DataTypes
|
||||||
|
|
||||||
|
// Check if column exists
|
||||||
|
const tableDescription = await queryInterface.describeTable('podcasts')
|
||||||
|
if (tableDescription.isAuthenticatedFeed) {
|
||||||
|
logger.info(`${loggerPrefix} column "isAuthenticatedFeed" already exists in "podcasts" table`)
|
||||||
|
} else {
|
||||||
|
// Add column
|
||||||
|
logger.info(`${loggerPrefix} adding column "isAuthenticatedFeed" to "podcasts" table`)
|
||||||
|
await queryInterface.addColumn('podcasts', 'isAuthenticatedFeed', {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
defaultValue: false,
|
||||||
|
allowNull: false
|
||||||
|
})
|
||||||
|
logger.info(`${loggerPrefix} added column "isAuthenticatedFeed" to "podcasts" table`)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`${loggerPrefix} UPGRADE COMPLETE: ${migrationName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This downward migration removes the isAuthenticatedFeed column from the podcasts table.
|
||||||
|
*
|
||||||
|
* @param {MigrationOptions} options - an object containing the migration context.
|
||||||
|
* @returns {Promise<void>} - A promise that resolves when the migration is complete.
|
||||||
|
*/
|
||||||
|
async function down({ context: { queryInterface, logger } }) {
|
||||||
|
// Downwards migration script
|
||||||
|
logger.info(`${loggerPrefix} DOWNGRADE BEGIN: ${migrationName}`)
|
||||||
|
|
||||||
|
// Check if column exists
|
||||||
|
const tableDescription = await queryInterface.describeTable('podcasts')
|
||||||
|
if (!tableDescription.isAuthenticatedFeed) {
|
||||||
|
logger.info(`${loggerPrefix} column "isAuthenticatedFeed" does not exist in "podcasts" table`)
|
||||||
|
} else {
|
||||||
|
// Remove column
|
||||||
|
logger.info(`${loggerPrefix} removing column "isAuthenticatedFeed" from "podcasts" table`)
|
||||||
|
await queryInterface.removeColumn('podcasts', 'isAuthenticatedFeed')
|
||||||
|
logger.info(`${loggerPrefix} removed column "isAuthenticatedFeed" from "podcasts" table`)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`${loggerPrefix} DOWNGRADE COMPLETE: ${migrationName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { up, down }
|
||||||
|
|
@ -44,6 +44,8 @@ class Podcast extends Model {
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
this.explicit
|
this.explicit
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
|
this.isAuthenticatedFeed
|
||||||
|
/** @type {boolean} */
|
||||||
this.autoDownloadEpisodes
|
this.autoDownloadEpisodes
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
this.autoDownloadSchedule
|
this.autoDownloadSchedule
|
||||||
|
|
@ -104,6 +106,7 @@ class Podcast extends Model {
|
||||||
language: typeof payload.metadata.language === 'string' ? payload.metadata.language : null,
|
language: typeof payload.metadata.language === 'string' ? payload.metadata.language : null,
|
||||||
podcastType: typeof payload.metadata.type === 'string' ? payload.metadata.type : null,
|
podcastType: typeof payload.metadata.type === 'string' ? payload.metadata.type : null,
|
||||||
explicit: !!payload.metadata.explicit,
|
explicit: !!payload.metadata.explicit,
|
||||||
|
isAuthenticatedFeed: !!payload.metadata.isAuthenticatedFeed,
|
||||||
autoDownloadEpisodes: !!payload.autoDownloadEpisodes,
|
autoDownloadEpisodes: !!payload.autoDownloadEpisodes,
|
||||||
autoDownloadSchedule: autoDownloadSchedule || global.ServerSettings.podcastEpisodeSchedule,
|
autoDownloadSchedule: autoDownloadSchedule || global.ServerSettings.podcastEpisodeSchedule,
|
||||||
lastEpisodeCheck: new Date(),
|
lastEpisodeCheck: new Date(),
|
||||||
|
|
@ -141,6 +144,7 @@ class Podcast extends Model {
|
||||||
language: DataTypes.STRING,
|
language: DataTypes.STRING,
|
||||||
podcastType: DataTypes.STRING,
|
podcastType: DataTypes.STRING,
|
||||||
explicit: DataTypes.BOOLEAN,
|
explicit: DataTypes.BOOLEAN,
|
||||||
|
isAuthenticatedFeed: DataTypes.BOOLEAN,
|
||||||
|
|
||||||
autoDownloadEpisodes: DataTypes.BOOLEAN,
|
autoDownloadEpisodes: DataTypes.BOOLEAN,
|
||||||
autoDownloadSchedule: DataTypes.STRING,
|
autoDownloadSchedule: DataTypes.STRING,
|
||||||
|
|
@ -252,6 +256,11 @@ class Podcast extends Model {
|
||||||
hasUpdates = true
|
hasUpdates = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (payload.metadata.isAuthenticatedFeed !== undefined && payload.metadata.isAuthenticatedFeed !== this.isAuthenticatedFeed) {
|
||||||
|
this.isAuthenticatedFeed = !!payload.metadata.isAuthenticatedFeed
|
||||||
|
hasUpdates = true
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(payload.metadata.genres) && !payload.metadata.genres.some((item) => typeof item !== 'string') && JSON.stringify(this.genres) !== JSON.stringify(payload.metadata.genres)) {
|
if (Array.isArray(payload.metadata.genres) && !payload.metadata.genres.some((item) => typeof item !== 'string') && JSON.stringify(this.genres) !== JSON.stringify(payload.metadata.genres)) {
|
||||||
this.genres = payload.metadata.genres
|
this.genres = payload.metadata.genres
|
||||||
this.changed('genres', true)
|
this.changed('genres', true)
|
||||||
|
|
@ -407,6 +416,7 @@ class Podcast extends Model {
|
||||||
itunesId: this.itunesId,
|
itunesId: this.itunesId,
|
||||||
itunesArtistId: this.itunesArtistId,
|
itunesArtistId: this.itunesArtistId,
|
||||||
explicit: this.explicit,
|
explicit: this.explicit,
|
||||||
|
isAuthenticatedFeed: this.isAuthenticatedFeed,
|
||||||
language: this.language,
|
language: this.language,
|
||||||
type: this.podcastType
|
type: this.podcastType
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,19 +108,33 @@ module.exports.downloadPodcastEpisode = (podcastEpisodeDownload) => {
|
||||||
// See: https://github.com/advplyr/audiobookshelf/issues/4401 (requires no iTMS user agent)
|
// See: https://github.com/advplyr/audiobookshelf/issues/4401 (requires no iTMS user agent)
|
||||||
const userAgents = ['audiobookshelf (+https://audiobookshelf.org; like iTMS)', 'audiobookshelf (+https://audiobookshelf.org)']
|
const userAgents = ['audiobookshelf (+https://audiobookshelf.org; like iTMS)', 'audiobookshelf (+https://audiobookshelf.org)']
|
||||||
|
|
||||||
|
let refererHeader = null
|
||||||
|
const feedURL = podcastEpisodeDownload.libraryItem?.media?.feedURL
|
||||||
|
const isAuthenticatedFeed = podcastEpisodeDownload.libraryItem?.media?.isAuthenticatedFeed || false
|
||||||
|
|
||||||
|
if (feedURL && isAuthenticatedFeed) {
|
||||||
|
refererHeader = feedURL
|
||||||
|
}
|
||||||
|
|
||||||
let response = null
|
let response = null
|
||||||
let lastError = null
|
let lastError = null
|
||||||
|
|
||||||
for (const userAgent of userAgents) {
|
for (const userAgent of userAgents) {
|
||||||
try {
|
try {
|
||||||
|
const headers = {
|
||||||
|
Accept: '*/*',
|
||||||
|
'User-Agent': userAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refererHeader) {
|
||||||
|
headers['Referer'] = refererHeader
|
||||||
|
}
|
||||||
|
|
||||||
response = await axios({
|
response = await axios({
|
||||||
url: podcastEpisodeDownload.url,
|
url: podcastEpisodeDownload.url,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
responseType: 'stream',
|
responseType: 'stream',
|
||||||
headers: {
|
headers,
|
||||||
Accept: '*/*',
|
|
||||||
'User-Agent': userAgent
|
|
||||||
},
|
|
||||||
timeout: global.PodcastDownloadTimeout
|
timeout: global.PodcastDownloadTimeout
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue