diff --git a/Sources/FeedKit/FeedNamespace.swift b/Sources/FeedKit/FeedNamespace.swift index 6d5ac995..800e2e3d 100644 --- a/Sources/FeedKit/FeedNamespace.swift +++ b/Sources/FeedKit/FeedNamespace.swift @@ -57,6 +57,8 @@ enum FeedNamespace: CaseIterable { /// Represents the Atom feed namespace, typically used for syndication /// in the Atom format. case atom + /// Represents the podcast namespace. + case podcast // MARK: Internal @@ -81,6 +83,8 @@ enum FeedNamespace: CaseIterable { "xmlns:yt" case .atom: "xmlns:atom" + case .podcast: + "xmlns:podcast" } } @@ -105,6 +109,8 @@ enum FeedNamespace: CaseIterable { "http://www.youtube.com/xml/schemas/2015" case .atom: "http://www.w3.org/2005/Atom" + case .podcast: + "https://podcastindex.org/namespace/1.0" } } } @@ -145,6 +151,9 @@ extension FeedNamespace { case .atom: feed.channel?.atom != nil + + case .podcast: + feed.channel?.podcast != nil } } diff --git a/Sources/FeedKit/Feeds/RSS/RSSFeedChannel.swift b/Sources/FeedKit/Feeds/RSS/RSSFeedChannel.swift index 65dde920..1d076ef5 100644 --- a/Sources/FeedKit/Feeds/RSS/RSSFeedChannel.swift +++ b/Sources/FeedKit/Feeds/RSS/RSSFeedChannel.swift @@ -60,7 +60,8 @@ public struct RSSFeedChannel { dublinCore: DublinCore? = nil, iTunes: ITunes? = nil, syndication: Syndication? = nil, - atom: Atom? = nil + atom: Atom? = nil, + podcast: Podcast? = nil ) { self.title = title self.link = link @@ -86,6 +87,7 @@ public struct RSSFeedChannel { self.iTunes = iTunes self.syndication = syndication self.atom = atom + self.podcast = podcast } // MARK: Public @@ -307,6 +309,10 @@ public struct RSSFeedChannel { /// and hub information. /// See https://www.w3.org/TR/websub/#discovery public var atom: Atom? + + /// A wholistic RSS namespace for podcasting that is meant to synthesize the fragmented world of podcast namespaces. + /// See https://github.com/Podcastindex-org/podcast-namespace + public var podcast: Podcast? } // MARK: - Sendable @@ -349,6 +355,7 @@ extension RSSFeedChannel: Codable { case iTunes = "itunes" case syndication = "sy" case atom + case podcast } public init(from decoder: any Decoder) throws { @@ -378,6 +385,7 @@ extension RSSFeedChannel: Codable { iTunes = try container.decodeIfPresent(ITunes.self, forKey: CodingKeys.iTunes) syndication = try container.decodeIfPresent(Syndication.self, forKey: CodingKeys.syndication) atom = try container.decodeIfPresent(Atom.self, forKey: CodingKeys.atom) + podcast = try container.decodeIfPresent(Podcast.self, forKey: CodingKeys.podcast) } public func encode(to encoder: any Encoder) throws { @@ -407,5 +415,6 @@ extension RSSFeedChannel: Codable { try container.encodeIfPresent(iTunes, forKey: CodingKeys.iTunes) try container.encodeIfPresent(syndication, forKey: CodingKeys.syndication) try container.encodeIfPresent(atom, forKey: CodingKeys.atom) + try container.encodeIfPresent(podcast, forKey: CodingKeys.podcast) } } diff --git a/Sources/FeedKit/Namespaces/Podcast/Podcast.swift b/Sources/FeedKit/Namespaces/Podcast/Podcast.swift new file mode 100644 index 00000000..47c304be --- /dev/null +++ b/Sources/FeedKit/Namespaces/Podcast/Podcast.swift @@ -0,0 +1,58 @@ +import Foundation +import XMLKit + +/// The podcast namespace +/// +/// See https://github.com/Podcastindex-org/podcast-namespace +public struct Podcast { + // MARK: Lifecycle + + public init( + guid: String? = nil + + ) { + self.guid = guid + } + + // MARK: Public + /// This element is used to declare a unique, global identifier for a podcast. + /// See https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/tags/guid.md + public var guid: String? +} + +// MARK: - XMLNamespaceDecodable + +extension Podcast: XMLNamespaceCodable {} + +// MARK: - Sendable + +extension Podcast: Sendable {} + +// MARK: - Equatable + +extension Podcast: Equatable {} + +// MARK: - Hashable + +extension Podcast: Hashable {} + +// MARK: - Codable + +extension Podcast: Codable { + private enum CodingKeys: String, CodingKey { + case guid = "podcast:guid" + } + + public init(from decoder: any Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container( + keyedBy: CodingKeys.self) + + guid = try container.decodeIfPresent(String.self, forKey: CodingKeys.guid) + } + + public func encode(to encoder: any Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encodeIfPresent(guid, forKey: CodingKeys.guid) + } +}