Skip to content

Commit

Permalink
Merge pull request #16 from thatcherclough/api
Browse files Browse the repository at this point in the history
Introduces the CoverFlow API
  • Loading branch information
thatcherclough authored Jun 17, 2021
2 parents 6592c14 + 87c3dec commit 4eb30c3
Show file tree
Hide file tree
Showing 21 changed files with 630 additions and 107 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
/Pods
*xcworkspace/xcuserdata/*
/videos/
/api/real_api.py
/api/real_data.json
/api/.vscode
.vscode
28 changes: 18 additions & 10 deletions CoverFlow.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -137,20 +137,15 @@
isa = PBXGroup;
children = (
DDDF442725866D380013CCC4 /* Storyboards */,
DDE41A8B2675189B002E8A33 /* ViewControllers */,
DDAEDC97253CF53D002F3175 /* AppDelegate.swift */,
DDAEDC99253CF53D002F3175 /* SceneDelegate.swift */,
DDAEDC9B253CF53D002F3175 /* SettingsViewController.swift */,
DD439AD225609FE700082CEF /* SpotifyController.swift */,
DD439AD025609FDC00082CEF /* AppleMusicController.swift */,
DDFD8BE0253E382C00BBE27A /* PushButtonViewController.swift */,
DD21BD622592D8CE0069304C /* BridgeDiscoveryViewController.swift */,
DD439AD225609FE700082CEF /* SpotifyController.swift */,
DDFD8BDE253E323900BBE27A /* BridgeInfo.swift */,
DDB07A7B25799901003EA4AF /* LocalNetworkPermissionService.swift */,
DDAEDCA0253CF541002F3175 /* Assets.xcassets */,
DDAEDCA5253CF541002F3175 /* Info.plist */,
DDFD8BE2253E479400BBE27A /* LightSelectionViewController.swift */,
DDF07093256C375100353469 /* MusicProviderViewController.swift */,
DDB07A7B25799901003EA4AF /* LocalNetworkPermissionService.swift */,
DDB07A952579AD5C003EA4AF /* MainViewController.swift */,
);
path = CoverFlow;
sourceTree = "<group>";
Expand All @@ -164,6 +159,19 @@
path = Storyboards;
sourceTree = "<group>";
};
DDE41A8B2675189B002E8A33 /* ViewControllers */ = {
isa = PBXGroup;
children = (
DDF07093256C375100353469 /* MusicProviderViewController.swift */,
DDB07A952579AD5C003EA4AF /* MainViewController.swift */,
DDAEDC9B253CF53D002F3175 /* SettingsViewController.swift */,
DD21BD622592D8CE0069304C /* BridgeDiscoveryViewController.swift */,
DDFD8BE0253E382C00BBE27A /* PushButtonViewController.swift */,
DDFD8BE2253E479400BBE27A /* LightSelectionViewController.swift */,
);
path = ViewControllers;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -458,7 +466,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.4;
MARKETING_VERSION = 1.5.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
Expand Down Expand Up @@ -508,7 +516,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.4;
MARKETING_VERSION = 1.5.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1240"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
62 changes: 56 additions & 6 deletions CoverFlow/AppleMusicController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,68 @@
import Foundation
import StoreKit
import MediaPlayer
import Keys

class AppleMusicController {

// MARK: Variables and constructor

let keys = CoverFlowKeys()
var apiKey: String!
var countryCode: String!
let player = MPMusicPlayerController.systemMusicPlayer

init(apiKey: String) {
self.apiKey = apiKey
init() {
getCountryCode()
setApiKey()
}

func getCountryCode() {
countryCode = "us"

DispatchQueue.global(qos: .background).async {
SKCloudServiceController().requestStorefrontCountryCode { countryCode, error in
if countryCode == nil || error != nil {
self.countryCode = "us"
} else {
if countryCode != nil && error == nil {
self.countryCode = countryCode
}
}
}
}

func setApiKey() {
getApiKey { (apiKey) in
self.apiKey = apiKey
}
}

func getApiKey(completion: @escaping (String?) -> ()) {
let url = URL(string: "\(keys.apiBaseUrl)/api/apple_music/key")!
var request = URLRequest(url: url)
request.httpMethod = "GET"

let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return completion(nil)
}
guard let data = data else {
return completion(nil)
}

do {
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
if let key = json["key"] as? String {
return completion(key)
} else {
return completion(nil)
}
}
} catch {
return completion(nil)
}
})
task.resume()
}

// MARK: Functions

func getCurrentAlbumName() -> String {
Expand Down Expand Up @@ -112,7 +148,21 @@ class AppleMusicController {
}
}
} catch {
return completion(nil)
if data.count > 0 {
return completion(nil)
} else {
self.getApiKey { (apiKey) in
if apiKey == nil {
return completion(nil)
} else {
self.apiKey = apiKey

self.getCoverFromAPI(albumName: albumName, artistName: artistName) { (url) in
return completion(url)
}
}
}
}
}
})
task.resume()
Expand Down
134 changes: 72 additions & 62 deletions CoverFlow/SpotifyController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,31 @@
//

import Foundation
import Keys

class SpotifyController: UIResponder, SPTSessionManagerDelegate {

// MARK: Variables and constructor

let keys = CoverFlowKeys()
var accessToken: String!
var refreshToken: String!
var codeVerifier: String!
var sessionManager: SPTSessionManager!

private var clientID: String!
private var clientSecret: String!
private var redirectURI: URL!

init(clientID: String, clientSecret: String, redirectURI: URL) {
init(clientID: String, redirectURI: URL) {
super.init()

self.clientID = clientID
self.clientSecret = clientSecret
self.redirectURI = redirectURI

if let refreshToken = UserDefaults.standard.string(forKey: "refreshToken") {
refreshAccessToken(refreshToken: refreshToken)
} else {
resetAccessAndRefreshTokens()
initSessionManager()
}
}
Expand Down Expand Up @@ -81,17 +82,17 @@ class SpotifyController: UIResponder, SPTSessionManagerDelegate {
}

func getAccessAndRefreshTokens(accessCode: String) {
if clientID != nil && clientSecret != nil && redirectURI != nil && codeVerifier != nil {
getAccessAndRefreshTokens(clientID: clientID, clientSecret: clientSecret, redirectURI: redirectURI, accessCode: accessCode, codeVerifier: codeVerifier) { (data, error) in
if error != nil || data == nil {
if codeVerifier != nil {
getAccessAndRefreshTokens(accessCode: accessCode, codeVerifier: codeVerifier) { data in
if data == nil {
self.resetAccessAndRefreshTokens()
return
} else {
if let accessToken = data!["access_token"] as? String,
let refreshToken = data!["refresh_token"] as? String {
self.setUserDefault(key: "refreshToken", value: refreshToken)
self.accessToken = accessToken
self.refreshToken = refreshToken
UserDefaults.standard.set(refreshToken, forKey: "refreshToken")
return
} else {
self.resetAccessAndRefreshTokens()
Expand All @@ -105,101 +106,110 @@ class SpotifyController: UIResponder, SPTSessionManagerDelegate {
}
}

private func getAccessAndRefreshTokens(clientID: String, clientSecret: String, redirectURI: URL, accessCode: String, codeVerifier: String, completion: @escaping ([String: Any]?, Error?) -> Void) {
let url = URL(string: "https://accounts.spotify.com/api/token")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let spotifyAuthKey = "Basic \((clientID + ":" + clientSecret).data(using: .utf8)!.base64EncodedString())"
request.allHTTPHeaderFields = ["Authorization": spotifyAuthKey, "Content-Type": "application/x-www-form-urlencoded"]
var requestBodyComponents = URLComponents()

requestBodyComponents.queryItems = [
URLQueryItem(name: "client_id", value: clientID),
URLQueryItem(name: "grant_type", value: "authorization_code"),
URLQueryItem(name: "code", value: accessCode),
URLQueryItem(name: "redirect_uri", value: redirectURI.absoluteString),
URLQueryItem(name: "code_verifier", value: codeVerifier),
URLQueryItem(name: "scope", value: "user-read-currently-playing")
private func getAccessAndRefreshTokens(accessCode: String, codeVerifier: String, completion: @escaping ([String:Any]?) -> Void) {
var urlComponents = URLComponents(string: "\(keys.apiBaseUrl)/api/spotify/swap")!
urlComponents.queryItems = [
URLQueryItem(name: "access_code", value: accessCode),
URLQueryItem(name: "code_verifier", value: codeVerifier)
]
request.httpBody = requestBodyComponents.query?.data(using: .utf8)

var request = URLRequest(url: urlComponents.url!)
request.httpMethod = "POST"

let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil else {
return completion(nil, error)
return completion(nil)
}
guard let data = data else {
return completion(nil, nil)
return completion(nil)
}

do {
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
return completion(json, nil)
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
if json["error"] != nil {
return completion(nil)
} else {
return completion(json)
}
} else {
return completion(nil)
}
} catch {
return completion(nil, nil)
return completion(nil)
}
}
task.resume()
}

func refreshAccessToken(refreshToken: String) {
self.refreshToken = refreshToken
if clientID != nil && clientSecret != nil && redirectURI != nil {
refreshAccessToken(clientID: clientID, clientSecret: clientSecret, redirectURI: redirectURI, refreshToken: refreshToken) { (data, error) in
if error != nil || data == nil {
self.resetAccessAndRefreshTokens()

refreshAccessToken(refreshToken: refreshToken) { data in
if data == nil {
self.resetAccessAndRefreshTokens()
return
} else {
if let accessToken = data!["access_token"] as? String,
let refreshToken = data!["refresh_token"] as? String {
self.setUserDefault(key: "refreshToken", value: refreshToken)
self.accessToken = accessToken
self.refreshToken = refreshToken
return
} else {
if let accessToken = data!["access_token"] as? String,
let refreshToken = data!["refresh_token"] as? String {
self.accessToken = accessToken
self.refreshToken = refreshToken
UserDefaults.standard.set(refreshToken, forKey: "refreshToken")
return
} else {
self.resetAccessAndRefreshTokens()
return
}
self.resetAccessAndRefreshTokens()
return
}
}
} else {
resetAccessAndRefreshTokens()
return
}
}

private func refreshAccessToken (clientID: String, clientSecret: String, redirectURI: URL, refreshToken: String, completion: @escaping ([String: Any]?, Error?) -> Void) {
let url = URL(string: "https://accounts.spotify.com/api/token")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let spotifyAuthKey = "Basic \((clientID + ":" + clientSecret).data(using: .utf8)!.base64EncodedString())"
request.allHTTPHeaderFields = ["Authorization": spotifyAuthKey, "Content-Type": "application/x-www-form-urlencoded"]
var requestBodyComponents = URLComponents()

requestBodyComponents.queryItems = [
URLQueryItem(name: "client_id", value: clientID),
URLQueryItem(name: "grant_type", value: "refresh_token"),
private func refreshAccessToken(refreshToken: String, completion: @escaping ([String: Any]?) -> Void) {
var urlComponents = URLComponents(string: "\(keys.apiBaseUrl)/api/spotify/refresh")!
urlComponents.queryItems = [
URLQueryItem(name: "refresh_token", value: refreshToken)
]
request.httpBody = requestBodyComponents.query?.data(using: .utf8)

var request = URLRequest(url: urlComponents.url!)
request.httpMethod = "POST"

let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil else {
return completion(nil, error)
return completion(nil)
}
guard let data = data else {
return completion(nil, nil)
return completion(nil)
}

do {
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
return completion(json, nil)
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
if json["error"] != nil {
return completion(nil)
} else {
return completion(json)
}
} else {
return completion(nil)
}
} catch {
return completion(nil, nil)
return completion(nil)
}
}
task.resume()
}

func setUserDefault(key: String, value: String) {
setUserDefault(key: key, value: value) {
if UserDefaults.standard.string(forKey: key) != value {
self.setUserDefault(key: key, value: value)
}
}
}

func setUserDefault(key: String, value: String?, completion: ()->()) {
UserDefaults.standard.setValue(value, forKey: key)
return completion()
}

public func getCurrentAlbum(completion: @escaping ([String: Any])->()) {
if accessToken == nil {
return completion(["retry": "Access token not set"])
Expand Down
Loading

0 comments on commit 4eb30c3

Please sign in to comment.