Skip to content
This repository has been archived by the owner on Feb 26, 2023. It is now read-only.

Commit

Permalink
Publish Kleene
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew Davis committed Jul 1, 2019
0 parents commit a8f45fb
Show file tree
Hide file tree
Showing 396 changed files with 30,305 additions and 0 deletions.
70 changes: 70 additions & 0 deletions .gitignore
@@ -0,0 +1,70 @@
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## Build generated
build/
DerivedData/

## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/

## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint

## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM

## Playgrounds
timeline.xctimeline
playground.xcworkspace

# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
.build/

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/

# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts

Carthage/Build

# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control

fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output

**.DS_Store
Empty file added .gitkeep
Empty file.
10 changes: 10 additions & 0 deletions Apple Music/.swiftlint.yml
@@ -0,0 +1,10 @@
disabled_rules:
- variable_name
- line_length
- nesting

statement_position:
statement_mode: uncuddled_else

cyclomatic_complexity:
ignores_case_statements: true
474 changes: 474 additions & 0 deletions Apple Music/Apple Music.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions Apple Music/Apple Music/Access.swift
@@ -0,0 +1,33 @@
//
// Access.swift
// Apple Music
//
// Created on 4/13/19.
// Copyright © 2019 The Kleene Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation

struct Access: Codable, Equatable {

let token: String
let issuedAt: Date
let expiration: Date

var isExpired: Bool {
return Date() > expiration
}

}
31 changes: 31 additions & 0 deletions Apple Music/Apple Music/AppleMusic.h
@@ -0,0 +1,31 @@
//
// AppleMusic.h
// Apple Music
//
// Created on 5/7/19.
// Copyright © 2019 The Kleene Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#import <UIKit/UIKit.h>

//! Project version number for AppleMusic.
FOUNDATION_EXPORT double AppleMusicVersionNumber;

//! Project version string for AppleMusic.
FOUNDATION_EXPORT const unsigned char AppleMusicVersionString[];

// In this header, you should import all the public headers of your framework using statements like #import <AppleMusic/PublicHeader.h>


219 changes: 219 additions & 0 deletions Apple Music/Apple Music/AppleMusic.swift
@@ -0,0 +1,219 @@
//
// AppleMusic.swift
// Apple Music
//
// Created on 2/6/19.
// Copyright © 2019 The Kleene Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation
import StoreKit
import MediaPlayer
import Essentials

public class AppleMusic {

static let cloudServiceController = SKCloudServiceController()
private static let musicPlayerController = MPMusicPlayerController.applicationQueuePlayer

static var subscribeViewController: SKCloudServiceSetupViewController {
let controller = SKCloudServiceSetupViewController()

controller.load(options: [.action: SKCloudServiceSetupAction.subscribe], completionHandler: nil)

return controller
}

public static var isAuthorized: Bool {
return authorizationStatus == .authorized
}

public static var authorizationStatus: SKCloudServiceAuthorizationStatus {
return SKCloudServiceController.authorizationStatus()
}

static var musicPlaybackTime: TimeInterval {
get {
return musicPlayerController.currentPlaybackTime
}
set {
musicPlayerController.currentPlaybackTime = newValue
}
}

public static func requestAuthorization(handler: @escaping (SKCloudServiceAuthorizationStatus) -> Void) {
SKCloudServiceController.requestAuthorization(handler)
}

static func handleCapabilities(with completion: @escaping (SKCloudServiceCapability, Swift.Error?) -> Void) {
guard isAuthorized else {
return
}

cloudServiceController.requestCapabilities(completionHandler: completion)
}

public static var songs: [MediaSong]? {
guard isAuthorized else {
return nil
}

return MPMediaQuery.songs().items?.compactMap(MediaSong.init)
}
public static var albums: [MediaAlbum]? {
guard isAuthorized else {
return nil
}

return MPMediaQuery.albums().items?.compactMap(MediaAlbum.init)
}
public static var artists: [MediaArtist]? {
guard isAuthorized else {
return nil
}

return MPMediaQuery.artists().items?.compactMap(MediaArtist.init)
}
public static var playlists: [MediaPlaylist]? {
guard isAuthorized else {
return nil
}

return MPMediaQuery.playlists().collections?.compactMap(MediaPlaylist.init)
}

static let apiURL = URL(string: "https://api.music.apple.com/v1/")!

public static func handleSearch(term: String, limit: UInt = 25, with handler: Handler<Result<[Song], Swift.Error>>) {
guard isAuthorized else {
handler(.failure(Error.notAuthorized))
return
}

cloudServiceController.requestStorefrontCountryCode(completionHandler: { (storefront, error) in
guard let storefront = storefront else {
handler(.failure(error ?? Error.storefrontProblem))
return
}

TokenManager.handleDeveloperToken(with: Handler { result in
switch result {
case .failure(let error):
handler(.failure(error))

case .success(let token):
let headers = [
"Authorization": "Bearer \(token)"
]
let parameters = [
"term": term.replacingOccurrences(of: " ", with: "+"),
"limit": "\(max(1, min(25, limit)))"
]

HTTP.request(apiURL, endpoint: "catalog/\(storefront)/search", headers: headers, parameters: parameters, with: { (data, _, error) in
guard let data = data else {
handler(.failure(error ?? Error.apiProblem))
return
}

do {
let searchResponse = try JSONDecoder().decode(SearchResponse.self, from: data)
let songs = searchResponse.results.songs?.data

handler(.success(songs ?? []))
}
catch {
handler(.failure(error))
}
})
}
})
})
}

public static func addToLibrary(song: Song, with handler: Handler<Result<Int, Swift.Error>>) {
TokenManager.handleTokens(with: Handler { result in
do {
let (developerToken, userToken) = try result.get()
let headers = [
"Authorization": "Bearer \(developerToken)",
"Music-User-Token": userToken
]
let parameters = ["ids[songs]": song.id]

HTTP.request(method: .post, apiURL, endpoint: "me/library", headers: headers, parameters: parameters, with: { (_, response, _) in
if let httpResponse = response as? HTTPURLResponse {
guard httpResponse.statusCode == 202 else {
handler(.failure(NSError(domain: "Apple Music", code: httpResponse.statusCode, userInfo: nil)))
return
}

handler(.success(httpResponse.statusCode))
}
else {
handler(.failure(Error.nonHTTPResponse))
}
})
}
catch {
handler(.failure(error))
}
})
}

static func playbackDuration() -> Double {
return musicPlayerController.nowPlayingItem?.playbackDuration ?? 0
}

}

extension AppleMusic {

enum Error: String, Swift.Error {

case apiProblem = "There was a problem with the Apple Music API."
case nonHTTPResponse = "The Apple Music API returned a non-HTTP response."
case notAuthorized = "Kleene is not authorized to view Apple Music content."
case storefrontProblem = "There was a problem getting the Apple Music storefront."
case swappingError = "There was an error retrieving an Apple Music Developer token."
case userTokenError = "There was an error retrieving the user token for Apple Music."

}

struct Artwork: Codable {

let width, height: Int
let url: String

}

enum Kind: String, Codable {

case song
case album
case playlist

}

enum ResourceType: String, Codable {

case libraryAlbums = "library-albums"
case librarySongs = "library-songs"
case libraryArtists = "library-artists"
case libraryPlaylists = "library-playlists"

}

}
22 changes: 22 additions & 0 deletions Apple Music/Apple Music/Info.plist
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>

0 comments on commit a8f45fb

Please sign in to comment.