+
-
@@ -96,6 +99,7 @@ export default {
autoDownloadEpisodes: false,
language: '',
explicit: false,
+ isAuthenticatedFeed: false,
type: ''
}
}
@@ -194,6 +198,7 @@ export default {
itunesArtistId: this.podcast.itunesArtistId,
language: this.podcast.language,
explicit: this.podcast.explicit,
+ isAuthenticatedFeed: this.podcast.isAuthenticatedFeed,
type: this.podcast.type
},
autoDownloadEpisodes: this.podcast.autoDownloadEpisodes
@@ -234,6 +239,7 @@ export default {
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.isAuthenticatedFeed = this._podcastData.isAuthenticatedFeed || false
if (this.folderItems[0]) {
this.selectedFolderId = this.folderItems[0].value
this.folderUpdated()
diff --git a/client/components/widgets/PodcastDetailsEdit.vue b/client/components/widgets/PodcastDetailsEdit.vue
index 0be67df32..215a5b2f1 100644
--- a/client/components/widgets/PodcastDetailsEdit.vue
+++ b/client/components/widgets/PodcastDetailsEdit.vue
@@ -34,8 +34,9 @@
@@ -70,6 +71,7 @@ export default {
itunesId: null,
itunesArtistId: null,
explicit: false,
+ isAuthenticatedFeed: false,
language: null,
type: null
},
@@ -241,6 +243,7 @@ export default {
this.details.itunesArtistId = this.mediaMetadata.itunesArtistId || ''
this.details.language = this.mediaMetadata.language || ''
this.details.explicit = !!this.mediaMetadata.explicit
+ this.details.isAuthenticatedFeed = !!this.mediaMetadata.isAuthenticatedFeed
this.details.type = this.mediaMetadata.type || 'episodic'
this.newTags = [...(this.media.tags || [])]
diff --git a/client/strings/en-us.json b/client/strings/en-us.json
index fb2bcb281..5887aac85 100644
--- a/client/strings/en-us.json
+++ b/client/strings/en-us.json
@@ -366,6 +366,7 @@
"LabelExplicit": "Explicit",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
+ "LabelAuthenticatedFeed": "Authenticated Feed",
"LabelExportOPML": "Export OPML",
"LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata",
diff --git a/server/migrations/v2.30.1-add-is-authenticated-feed.js b/server/migrations/v2.30.1-add-is-authenticated-feed.js
new file mode 100644
index 000000000..319b3295e
--- /dev/null
+++ b/server/migrations/v2.30.1-add-is-authenticated-feed.js
@@ -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
} - 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} - 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 }
diff --git a/server/models/Podcast.js b/server/models/Podcast.js
index a96e1dd02..b8874d15f 100644
--- a/server/models/Podcast.js
+++ b/server/models/Podcast.js
@@ -44,6 +44,8 @@ class Podcast extends Model {
/** @type {boolean} */
this.explicit
/** @type {boolean} */
+ this.isAuthenticatedFeed
+ /** @type {boolean} */
this.autoDownloadEpisodes
/** @type {string} */
this.autoDownloadSchedule
@@ -104,6 +106,7 @@ class Podcast extends Model {
language: typeof payload.metadata.language === 'string' ? payload.metadata.language : null,
podcastType: typeof payload.metadata.type === 'string' ? payload.metadata.type : null,
explicit: !!payload.metadata.explicit,
+ isAuthenticatedFeed: !!payload.metadata.isAuthenticatedFeed,
autoDownloadEpisodes: !!payload.autoDownloadEpisodes,
autoDownloadSchedule: autoDownloadSchedule || global.ServerSettings.podcastEpisodeSchedule,
lastEpisodeCheck: new Date(),
@@ -141,6 +144,7 @@ class Podcast extends Model {
language: DataTypes.STRING,
podcastType: DataTypes.STRING,
explicit: DataTypes.BOOLEAN,
+ isAuthenticatedFeed: DataTypes.BOOLEAN,
autoDownloadEpisodes: DataTypes.BOOLEAN,
autoDownloadSchedule: DataTypes.STRING,
@@ -252,6 +256,11 @@ class Podcast extends Model {
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)) {
this.genres = payload.metadata.genres
this.changed('genres', true)
@@ -407,6 +416,7 @@ class Podcast extends Model {
itunesId: this.itunesId,
itunesArtistId: this.itunesArtistId,
explicit: this.explicit,
+ isAuthenticatedFeed: this.isAuthenticatedFeed,
language: this.language,
type: this.podcastType
}
diff --git a/server/utils/ffmpegHelpers.js b/server/utils/ffmpegHelpers.js
index 80832cc77..17cc1c200 100644
--- a/server/utils/ffmpegHelpers.js
+++ b/server/utils/ffmpegHelpers.js
@@ -108,19 +108,33 @@ module.exports.downloadPodcastEpisode = (podcastEpisodeDownload) => {
// 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)']
+ 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 lastError = null
for (const userAgent of userAgents) {
try {
+ const headers = {
+ Accept: '*/*',
+ 'User-Agent': userAgent
+ }
+
+ if (refererHeader) {
+ headers['Referer'] = refererHeader
+ }
+
response = await axios({
url: podcastEpisodeDownload.url,
method: 'GET',
responseType: 'stream',
- headers: {
- Accept: '*/*',
- 'User-Agent': userAgent
- },
+ headers,
timeout: global.PodcastDownloadTimeout
})