Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import PackageDescription

let package = Package(
name: "LetterboxdAPI",
platforms: [.iOS(.v13), .macOS(.v10_15), .tvOS(.v13), .watchOS(.v6)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
Expand Down
106 changes: 106 additions & 0 deletions Sources/LetterboxdAPI/LetterboxdAPI+Film.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//
// LetterboxdAPI+Film.swift
// LetterboxdAPI
//
// Created by Gianpiero Spinelli.
//

import Foundation

public extension LetterboxdAPI {
/// A cursored window over the list of films.
func getFilms(parameters: [String: String] = [:], completion: @escaping (Result<FilmResponse, Error>) -> Void) {
let url = URLBuilder.url(path: "/films", body: nil, params: parameters)

guard let request = generateRequest(url: url, method: .get) else {
completion(.failure(LetterboxdAPIError.generatingRequest))
return
}

processRequest(request: request, completion: completion)
}

/// Get details about a film by ID.
func getFilm(withId id: String, completion: @escaping (Result<Film, Error>) -> Void) {
let url = URLBuilder.url(path: "/film/\(id)", body: nil)

guard let request = generateRequest(url: url, method: .get) else {
completion(.failure(LetterboxdAPIError.generatingRequest))
return
}

processRequest(request: request, completion: completion)
}

/// Get availability data for a film by ID. Only available to first-party API clients.
func getFilmAvailability(withId id: String, completion: @escaping (Result<FilmAvailabilityResponse, Error>) -> Void) {
let url = URLBuilder.url(path: "/film/\(id)/availability", body: nil)

guard let request = generateRequest(url: url, method: .get) else {
completion(.failure(LetterboxdAPIError.generatingRequest))
return
}

processRequest(request: request, completion: completion)
}

/// Get statistical data about a film by ID.
func getFilmStatistics(withId id: String, completion: @escaping (Result<FilmStatistics, Error>) -> Void) {
let url = URLBuilder.url(path: "/film/\(id)/statistics", body: nil)

guard let request = generateRequest(url: url, method: .get) else {
completion(.failure(LetterboxdAPIError.generatingRequest))
return
}

processRequest(request: request, completion: completion)
}

/// Get a list of countries supported by the /films endpoint
func getCountries(completion: @escaping (Result<CountryResponse, Error>) -> Void) {
let url = URLBuilder.url(path: "/films/countries", body: nil)

guard let request = generateRequest(url: url, method: .get) else {
completion(.failure(LetterboxdAPIError.generatingRequest))
return
}

processRequest(request: request, completion: completion)
}

/// Get a list of services supported by the /films endpoint.
func getFilmServices(completion: @escaping (Result<FilmServicesResponse, Error>) -> Void) {
let url = URLBuilder.url(path: "/films/film-services", body: nil)

guard let request = generateRequest(url: url, method: .get) else {
completion(.failure(LetterboxdAPIError.generatingRequest))
return
}

processRequest(request: request, completion: completion)
}

/// Get a list of genres supported by the /films endpoint.
func getFilmGenres(completion: @escaping (Result<GenresResponse, Error>) -> Void) {
let url = URLBuilder.url(path: "/films/genres", body: nil)

guard let request = generateRequest(url: url, method: .get) else {
completion(.failure(LetterboxdAPIError.generatingRequest))
return
}

processRequest(request: request, completion: completion)
}

/// Get a list of languages supported by the /films endpoint
func getFilmLanguages(completion: @escaping (Result<LanguagesResponse, Error>) -> Void) {
let url = URLBuilder.url(path: "/films/languages", body: nil)

guard let request = generateRequest(url: url, method: .get) else {
completion(.failure(LetterboxdAPIError.generatingRequest))
return
}

processRequest(request: request, completion: completion)
}
}
4 changes: 2 additions & 2 deletions Sources/LetterboxdAPI/LetterboxdAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ public class LetterboxdAPI {
task.resume()
}

private func generateRequest(url: URL, method: HTTPMethod) -> URLRequest? {
internal func generateRequest(url: URL, method: HTTPMethod) -> URLRequest? {
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
return request
}

@discardableResult
private func processRequest<R: Decodable>(request: URLRequest, completion: @escaping (Result<R, Error>) -> Void) -> URLSessionTask {
internal func processRequest<R: Decodable>(request: URLRequest, completion: @escaping (Result<R, Error>) -> Void) -> URLSessionTask {
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil, let data = data else {
completion(.failure(error!))
Expand Down
15 changes: 15 additions & 0 deletions Sources/LetterboxdAPI/Models/ContributionSummary.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// ContributorSummary.swift
// LetterboxdAPI
//
// Created by Gianpiero Spinelli.
//

import Foundation

public struct ContributorSummary: Decodable {
public var id: String
public var name: String
public var characterName: String?
public var tmdbid: String?
}
16 changes: 16 additions & 0 deletions Sources/LetterboxdAPI/Models/Country.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// Country.swift
// LetterboxdAPI
//
// Created by Gianpiero Spinelli.
//

import Foundation

public struct Country: Decodable {
/// The ISO 3166-1 defined code of the country.
public var code: String

/// The name of the country.
public var name: String
}
59 changes: 59 additions & 0 deletions Sources/LetterboxdAPI/Models/Film.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// Film.swift
// LetterboxdAPI
//
// Created by Gianpiero Spinelli.
//

import Foundation

public struct Film: Decodable {
public var id: String
public var name: String
public var originalName: String

/// The other names by which the film is known (including alternative titles and/or foreign translations).
public var alternativeNames: [String]
public var releaseYear: Int
public var directors: [ContributorSummary]
public var poster: Image
public var adultPoster: Image

/// The film’s position in the official Letterboxd Top 250 list of narrative feature films, `nil` if the film is not in the list.
public var top250Position: Int?

/// `true` if the film is in TMDb’s ‘Adult’ category.
public var adult: Bool

/// The LID of the collection containing this film.
public var filmCollectionId: String

/// A list of relevant URLs for this entity, on Letterboxd and external sites.
public var links: [Link]

/// The tagline for the film.
public var tagline: String

/// A synopsis of the film.
public var description: String

/// The film’s duration (in minutes).
public var runTime: Int

/// The film’s backdrop image (16:9 ratio in multiple sizes).
public var backdrop: Image

/// The backdrop’s vertical focal point, expressed as a proportion of the image’s height, using values between 0.0 and 1.0. Use when cropping the image into a shorter space, such as in the page for a film on the Letterboxd site.
public var backdropFocalPoint: Float

/// The film’s trailer.
public var trailer: FilmTrailer

/// The film’s genres.
public var genres: [Genre]
public var countries: [Country]
public var languages: [Language]
public var contributions: [FilmContributions]
public var news: [NewsItem]
public var recentStories: [LetterboxdStory]
}
152 changes: 152 additions & 0 deletions Sources/LetterboxdAPI/Models/FilmAvailability.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
//
// File.swift
//
//
// Created by Gianpiero Spinelli on 01/10/21.
//

import Foundation

public struct FilmAvailability: Decodable {
public enum Country: String, Decodable {
case AIA = "AIA"
case ARE = "ARE"
case ARG = "ARG"
case ARM = "ARM"
case ATG = "ATG"
case AUS = "AUS"
case AUT = "AUT"
case AZE = "AZE"
case BEL = "BEL"
case BFA = "BFA"
case BGR = "BGR"
case BHR = "BHR"
case BHS = "BHS"
case BLR = "BLR"
case BLZ = "BLZ"
case BMU = "BMU"
case BOL = "BOL"
case BRA = "BRA"
case BRB = "BRB"
case BRN = "BRN"
case BWA = "BWA"
case CAN = "CAN"
case CHE = "CHE"
case CHL = "CHL"
case CHN = "CHN"
case COL = "COL"
case CPV = "CPV"
case CRI = "CRI"
case CYM = "CYM"
case CYP = "CYP"
case CZE = "CZE"
case DEU = "DEU"
case DMA = "DMA"
case DNK = "DNK"
case DOM = "DOM"
case ECU = "ECU"
case EGY = "EGY"
case ESP = "ESP"
case EST = "EST"
case FIN = "FIN"
case FJI = "FJI"
case FRA = "FRA"
case FSM = "FSM"
case GBR = "GBR"
case GHA = "GHA"
case GMB = "GMB"
case GNB = "GNB"
case GRC = "GRC"
case GRD = "GRD"
case GTM = "GTM"
case HKG = "HKG"
case HND = "HND"
case HUN = "HUN"
case IDN = "IDN"
case IND = "IND"
case IRL = "IRL"
case ISR = "ISR"
case ITA = "ITA"
case JOR = "JOR"
case JPN = "JPN"
case KAZ = "KAZ"
case KEN = "KEN"
case KGZ = "KGZ"
case KHM = "KHM"
case KNA = "KNA"
case KOR = "KOR"
case LAO = "LAO"
case LBN = "LBN"
case LKA = "LKA"
case LTU = "LTU"
case LUX = "LUX"
case LVA = "LVA"
case MAC = "MAC"
case MDA = "MDA"
case MEX = "MEX"
case MLT = "MLT"
case MNG = "MNG"
case MOZ = "MOZ"
case MUS = "MUS"
case MYS = "MYS"
case NAM = "NAM"
case NER = "NER"
case NGA = "NGA"
case NIC = "NIC"
case NLD = "NLD"
case NOR = "NOR"
case NPL = "NPL"
case NZL = "NZL"
case OMN = "OMN"
case PAN = "PAN"
case PER = "PER"
case PHL = "PHL"
case PNG = "PNG"
case POL = "POL"
case PRT = "PRT"
case PRY = "PRY"
case QAT = "QAT"
case ROU = "ROU"
case RUS = "RUS"
case SAU = "SAU"
case SGP = "SGP"
case SLV = "SLV"
case SVK = "SVK"
case SVN = "SVN"
case SWE = "SWE"
case SWZ = "SWZ"
case THA = "THA"
case TJK = "TJK"
case TKM = "TKM"
case TTO = "TTO"
case TUR = "TUR"
case TWN = "TWN"
case UGA = "UGA"
case UKR = "UKR"
case USA = "USA"
case UZB = "UZB"
case VEN = "VEN"
case VGB = "VGB"
case VNM = "VNM"
case ZAF = "ZAF"
case ZWE = "ZWE"
}

/// The name of the service.
public var displayName: String

/// The URL of the thumbnail image for the service.
public var icon: String

/// The regional store for the service. Not all countries are supported on all services.
public var country: Country

/// The unique ID (if any) for the film on this service.
public var id: String?

/// The URL for the film on this service.
public var url: String

/// The types of the availability, possible options included buy, rent and stream
public var types: [String]
}
15 changes: 15 additions & 0 deletions Sources/LetterboxdAPI/Models/FilmContributions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// FilmContributions.swift
// LetterboxdAPI
//
// Created by Gianpiero Spinelli.
//

import Foundation

public struct FilmContributions: Decodable {
public var type: String

/// The list of contributors of the specified type for the film.
public var contributors: [ContributorSummary]
}
Loading