krz/moonrise

Night-sky companion for iPhone, Apple Watch and Vision Pro: what's up tonight, the Moon, planets, launches and missions, and a Library of the solar system. SwiftUI app with a Cloudflare Workers backend. astronomy cloudflare-workers ios night-sky space swiftui visionos watchos

Packages/MoonriseKit/Sources/Feeds/News.swift

46 lines · 2005 bytes

5 symbols in this file
 1import Foundation
 2
 3/// Space news from the Spaceflight News API (The Space Devs): headline, summary and a link to the article.
 4public struct NewsArticle: Sendable, Identifiable, Hashable, Decodable {
 5    public var id: Int
 6    public var title: String
 7    public var summary: String
 8    public var url: URL
 9    public var image: URL?
10    public var site: String
11    public var published: Date
12
13    public static let url = URL(string: "https://api.spaceflightnewsapi.net/v4/articles/?limit=40")!
14
15    public static func latest(session: URLSession = .shared) async throws -> [NewsArticle] {
16        try decode(await session.data(from: url).0)
17    }
18
19    /// Decodes a page, leaving out Astronomy Picture of the Day posts, whose summaries are page navigation.
20    static func decode(_ data: Data) throws -> [NewsArticle] {
21        let d = JSONDecoder()
22        d.dateDecodingStrategy = .iso8601
23        return try d.decode(Page.self, from: data).results.filter { !$0.title.hasPrefix("APOD:") }
24    }
25
26    private struct Page: Decodable { var results: [NewsArticle] }
27
28    private enum CodingKeys: String, CodingKey {
29        case id, title, summary, url
30        case image = "image_url", site = "news_site", published = "published_at"
31    }
32
33    public init(from decoder: Decoder) throws {
34        let c = try decoder.container(keyedBy: CodingKeys.self)
35        id = try c.decode(Int.self, forKey: .id)
36        title = try c.decode(String.self, forKey: .title)
37        summary = try c.decode(String.self, forKey: .summary).trimmingCharacters(in: .whitespacesAndNewlines)
38        url = try c.decode(URL.self, forKey: .url)
39        site = try c.decode(String.self, forKey: .site)
40        published = try c.decode(Date.self, forKey: .published)
41        // Some sites give http image URLs, which App Transport Security blocks.
42        image = (try? c.decodeIfPresent(String.self, forKey: .image)).flatMap { s in
43            URL(string: s.hasPrefix("http://") ? "https://" + s.dropFirst(7) : s)
44        }
45    }
46}