207 lines
5.4 KiB
Swift
207 lines
5.4 KiB
Swift
import Foundation
|
|
|
|
struct Game: Identifiable, Sendable {
|
|
let id: String
|
|
let awayTeam: TeamInfo
|
|
let homeTeam: TeamInfo
|
|
let status: GameStatus
|
|
let gameType: String?
|
|
let startTime: String?
|
|
let venue: String?
|
|
let pitchers: String?
|
|
let gamePk: String?
|
|
let gameDate: String
|
|
let broadcasts: [Broadcast]
|
|
let isBlackedOut: Bool
|
|
|
|
// Rich data from Stats API
|
|
var linescore: StatsLinescore?
|
|
var currentInningDisplay: String?
|
|
var awayPitcherId: Int?
|
|
var homePitcherId: Int?
|
|
|
|
var awayPitcherHeadshotURL: URL? {
|
|
guard let id = awayPitcherId else { return nil }
|
|
return URL(string: "https://img.mlbstatic.com/mlb-photos/image/upload/w_213,q_auto:best/v1/people/\(id)/headshot/67/current")
|
|
}
|
|
|
|
var homePitcherHeadshotURL: URL? {
|
|
guard let id = homePitcherId else { return nil }
|
|
return URL(string: "https://img.mlbstatic.com/mlb-photos/image/upload/w_213,q_auto:best/v1/people/\(id)/headshot/67/current")
|
|
}
|
|
|
|
var displayTitle: String {
|
|
"\(awayTeam.displayName) @ \(homeTeam.displayName)"
|
|
}
|
|
|
|
var scoreDisplay: String? {
|
|
guard let awayScore = awayTeam.score, let homeScore = homeTeam.score else {
|
|
return nil
|
|
}
|
|
return "\(awayScore) - \(homeScore)"
|
|
}
|
|
|
|
var hasStreams: Bool {
|
|
!broadcasts.isEmpty && !isBlackedOut
|
|
}
|
|
|
|
var isLive: Bool { status.isLive }
|
|
var isFinal: Bool { if case .final_ = status { return true }; return false }
|
|
var isSpecialChannel: Bool {
|
|
gamePk == nil
|
|
&& broadcasts.isEmpty
|
|
&& awayTeam.code == homeTeam.code
|
|
&& awayTeam.displayName == homeTeam.displayName
|
|
}
|
|
}
|
|
|
|
struct TeamInfo: Sendable {
|
|
let code: String
|
|
let name: String
|
|
let score: Int?
|
|
var teamId: Int?
|
|
var record: String?
|
|
var divisionRank: String?
|
|
var gamesBack: String?
|
|
var streak: String?
|
|
|
|
var displayName: String {
|
|
if !name.isEmpty { return name }
|
|
return TeamDirectory.name(for: code)
|
|
}
|
|
|
|
var logoURL: URL? {
|
|
if let teamId { return TeamAssets.logoURL(forId: teamId) }
|
|
return TeamAssets.logoURL(for: code)
|
|
}
|
|
|
|
var standingSummary: String? {
|
|
var parts: [String] = []
|
|
if let rank = divisionRank {
|
|
let suffix: String
|
|
switch rank {
|
|
case "1": suffix = "st"
|
|
case "2": suffix = "nd"
|
|
case "3": suffix = "rd"
|
|
default: suffix = "th"
|
|
}
|
|
parts.append("\(rank)\(suffix)")
|
|
}
|
|
if let gb = gamesBack {
|
|
parts.append("\(gb) GB")
|
|
}
|
|
if let streak {
|
|
parts.append(streak)
|
|
}
|
|
return parts.isEmpty ? nil : parts.joined(separator: " · ")
|
|
}
|
|
}
|
|
|
|
struct Broadcast: Identifiable, Sendable {
|
|
let id: String
|
|
let teamCode: String
|
|
let name: String
|
|
let mediaId: String
|
|
let streamURL: String
|
|
|
|
var displayLabel: String {
|
|
"\(teamCode): \(name)"
|
|
}
|
|
}
|
|
|
|
enum GameStatus: Sendable {
|
|
case scheduled(String)
|
|
case live(String?)
|
|
case final_
|
|
case unknown
|
|
|
|
var label: String {
|
|
switch self {
|
|
case .scheduled(let time): time
|
|
case .live(let info): info ?? "Live"
|
|
case .final_: "Final"
|
|
case .unknown: ""
|
|
}
|
|
}
|
|
|
|
var isLive: Bool {
|
|
if case .live = self { return true }
|
|
return false
|
|
}
|
|
|
|
var isScheduled: Bool {
|
|
if case .scheduled = self { return true }
|
|
return false
|
|
}
|
|
}
|
|
|
|
struct StreamConfig: Sendable {
|
|
let mediaId: String?
|
|
let team: String?
|
|
let resolution: String
|
|
let date: String?
|
|
let level: String?
|
|
let skip: String?
|
|
let audioTrack: String?
|
|
let mediaType: String?
|
|
let game: Int?
|
|
|
|
init(
|
|
mediaId: String,
|
|
resolution: String = "best",
|
|
audioTrack: String? = nil
|
|
) {
|
|
self.mediaId = mediaId
|
|
self.team = nil
|
|
self.resolution = resolution
|
|
self.date = nil
|
|
self.level = nil
|
|
self.skip = nil
|
|
self.audioTrack = audioTrack
|
|
self.mediaType = nil
|
|
self.game = nil
|
|
}
|
|
|
|
init(
|
|
team: String,
|
|
resolution: String = "best",
|
|
date: String? = nil,
|
|
level: String? = nil,
|
|
skip: String? = nil,
|
|
audioTrack: String? = nil,
|
|
mediaType: String? = nil,
|
|
game: Int? = nil
|
|
) {
|
|
self.mediaId = nil
|
|
self.team = team
|
|
self.resolution = resolution
|
|
self.date = date
|
|
self.level = level
|
|
self.skip = skip
|
|
self.audioTrack = audioTrack
|
|
self.mediaType = mediaType
|
|
self.game = game
|
|
}
|
|
}
|
|
|
|
enum TeamDirectory {
|
|
static let teams: [String: String] = [
|
|
"ARI": "D-backs", "AZ": "D-backs",
|
|
"ATL": "Braves", "BAL": "Orioles", "BOS": "Red Sox",
|
|
"CHC": "Cubs", "CWS": "White Sox", "CIN": "Reds",
|
|
"CLE": "Guardians", "COL": "Rockies", "DET": "Tigers",
|
|
"HOU": "Astros", "KC": "Royals", "LAA": "Angels",
|
|
"LAD": "Dodgers", "MIA": "Marlins", "MIL": "Brewers",
|
|
"MIN": "Twins", "NYM": "Mets", "NYY": "Yankees",
|
|
"OAK": "Athletics", "ATH": "Athletics",
|
|
"PHI": "Phillies", "PIT": "Pirates", "SD": "Padres",
|
|
"SF": "Giants", "SEA": "Mariners", "STL": "Cardinals",
|
|
"TB": "Rays", "TEX": "Rangers", "TOR": "Blue Jays",
|
|
"WSH": "Nationals",
|
|
]
|
|
|
|
static func name(for code: String) -> String {
|
|
teams[code.uppercased()] ?? code
|
|
}
|
|
}
|