├── .gitattributes ├── .gitignore ├── .npmignore ├── Logo-Bouncy-White.png ├── README.md ├── index.js ├── ios ├── AppleMusicAPI.m ├── AppleMusicAPI.swift ├── AppleMusicApiError.swift ├── Cider │ ├── Client │ │ ├── CiderClient.swift │ │ ├── CiderUrlBuilder.swift │ │ ├── Include.swift │ │ └── UrlFetcher.swift │ ├── JsonModels │ │ ├── LibraryPlaylistCreationRequest.swift │ │ └── LibraryPlaylistRequestTrack.swift │ └── ResourceObjects │ │ ├── MediaType.swift │ │ └── Storefront.swift ├── ReactNativeAppleMusic-Bridging-Header.h └── ReactNativeAppleMusic.xcodeproj │ └── project.pbxproj ├── package.json ├── react-native-apple-music.podspec └── src └── AppleMusic.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # node.js 6 | # 7 | node_modules/ 8 | npm-debug.log 9 | yarn-error.log 10 | 11 | # Xcode 12 | # 13 | build/ 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata 23 | *.xccheckout 24 | *.moved-aside 25 | DerivedData 26 | *.hmap 27 | *.ipa 28 | *.xcuserstate 29 | project.xcworkspace 30 | 31 | # Android/IntelliJ 32 | # 33 | build/ 34 | .idea 35 | .gradle 36 | local.properties 37 | *.iml 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemonadd-UG/react-native-apple-music/27236eca4514934a5cff17b356f2f24f60e63947/.npmignore -------------------------------------------------------------------------------- /Logo-Bouncy-White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemonadd-UG/react-native-apple-music/27236eca4514934a5cff17b356f2f24f60e63947/Logo-Bouncy-White.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | PRs Welcome 7 | 8 |

9 | 10 | 11 | React Native Apple Music by Bouncy 12 |

13 | 14 | The Apple Music API is a web service that lets you access information about the media found in the Apple Music Catalog. Here's what each one includes: 15 | 16 | 17 | Use this service to retrieve information about albums, songs, artists, playlists, music videos, Apple Music stations, ratings, charts, recommendations, and the user's most recently played content. With proper authorization from the user, you can also create or modify playlists and apply ratings to the user's content. 18 | 19 | Here's a quick overview of functionalities supported: 20 | 21 | * Get the Charts of an specific Country or global 22 | * Get recommendations for an user 23 | * Get the user's most recently played content 24 | * Create a Playlist 25 | * Modify Users Playlists 26 | 27 | ## 📖 Getting started 28 | 29 | `npm install @bouncyapp/react-native-apple-music --save` 30 | 31 | or 32 | 33 | `yarn add @bouncyapp/react-native-apple-music` 34 | 35 | ### Mostly automatic installation 36 | 37 | `react-native link @bouncyapp/react-native-apple-music --platforms ios` 38 | 39 | `cd ios && pod install && cd ..` 40 | 41 | ## 💻 Usage 42 | 43 | ```javascript 44 | import AppleMusic from '@bouncyapp/react-native-apple-music'; 45 | 46 | ``` 47 | 48 | ## 💡 Initialization/Authorization Methods 49 | 50 | All functions returning a Promise that resolves to the result. 51 | 52 | - **initialize**( *keyID*, *teamID*, *key* ) 53 | 54 | Initializes the Apple Music module and resumes a logged in session if there is one. This must be the first method you call when using this module. 55 | 56 | - *Parameters* 57 | 58 | - **keyID** - (*Required*) an object with options to pass to the Spotify Module 59 | - **teamID** - (*Required*) Your Apple Developer Team Id 60 | - **key** - (*Required*) You need to create an Key at Apple's Certificates, Identifiers & Profiles Page with MusicKit as Enabled services 61 | 62 | - *Returns* 63 | 64 | - A *Promise* that resolves to a boolean when the module finishes initialization, indicating whether or not a session was automatically logged back in 65 | 66 | - **login_basic**() 67 | 68 | Login every Apple User with your Key. 69 | User can call non-personlised api calls like getCharts etc. 70 | 71 | - *Returns* 72 | 73 | - A Promise that resolves to a boolean, indicating whether or not the user was logged in 74 | 75 | 76 | - **login**() (*Apple Music Subscription Required*) 77 | 78 | Login user with Apple Music Subscription 79 | 80 | - *Returns* 81 | 82 | - A Promise that resolves to a boolean, indicating whether or not the user was logged in 83 | 84 | 85 | - **getICloudID**() 86 | 87 | Get the iCloud ID of my Apple Account 88 | 89 | - *Returns* 90 | 91 | - A Promise that resolves the iCloud Id 92 | 93 | 94 | - **getSongCharts**() 95 | 96 | Get the current Apple Music Charts 97 | 98 | - *Returns* 99 | 100 | - A Promise that resolves the Apple Music Charts 101 | 102 | 103 | - **getAlbumCharts**() 104 | 105 | Get the current Apple Music Album Charts 106 | 107 | - *Returns* 108 | 109 | - A Promise that resolves the Apple Music Album Charts 110 | 111 | 112 | - **searchSong**(*query*) 113 | 114 | Search for Songs at Apple Music 115 | 116 | - *Returns* 117 | 118 | - A Promise that resolves an Array with Songs 119 | 120 | 121 | - **searchAlbum**(*query*) 122 | 123 | Search for Albums at Apple Music 124 | 125 | - *Returns* 126 | 127 | - A Promise that resolves an Array with Albums 128 | 129 | 130 | - **searchArtist**(*query*) 131 | 132 | Search for Artists at Apple Music 133 | 134 | - *Returns* 135 | 136 | - A Promise that resolves an Array with Artists 137 | 138 | 139 | - **searchPlaylist**(*query*) 140 | 141 | Search for Playlists at Apple Music 142 | 143 | - *Returns* 144 | 145 | - A Promise that resolves an Array with Playlists 146 | 147 | 148 | - **getSong**(*id*) 149 | 150 | Get an specific song by id 151 | 152 | - *Returns* 153 | 154 | - A Promise that resolves the requested song 155 | 156 | - **getSongs**(*[ids]*) 157 | 158 | Get songs by id 159 | 160 | - *Returns* 161 | 162 | - A Promise that resolves the requested songs 163 | 164 | - **getSongWithIsrc**(*isrc*) 165 | 166 | Get an specific song by isrc 167 | 168 | - *Returns* 169 | 170 | - A Promise that resolves the requested song 171 | 172 | - **getAlbum**(*id*) 173 | 174 | Get an specific album by id 175 | 176 | - *Returns* 177 | 178 | - A Promise that resolves the requested album 179 | 180 | - **getPlaylist**(*id*) 181 | 182 | Get an specific Catalog Playlist by id 183 | 184 | - *Returns* 185 | 186 | - A Promise that resolves the requested playlist 187 | 188 | - **getArtist**(*id*) 189 | 190 | Get an specific artist by id 191 | 192 | - *Returns* 193 | 194 | - A Promise that resolves the requested artist 195 | 196 | 197 | - **getUserPlaylists**() (*Apple Music Subscription Required*) 198 | 199 | Get the Playlists of my Apple Music Account 200 | 201 | - *Returns* 202 | 203 | - A Promise that resolves an Array with Playlists 204 | 205 | - **getUserPlaylist**(*id*) (*Apple Music Subscription Required*) 206 | 207 | Get a Playlist of my Apple Music Account 208 | 209 | - *Returns* 210 | 211 | - A Promise that resolves to the requested Playlist 212 | 213 | 214 | - **recentPlayed**() (*Apple Music Subscription Required*) 215 | 216 | Get the recently played songs, albums, artists of my Apple Music Account 217 | 218 | - *Returns* 219 | 220 | - A Promise that resolves an Array with songs, albums, artists 221 | 222 | 223 | - **getHeavyRotation**() (*Apple Music Subscription Required*) 224 | 225 | Heavy Rotation is a collection of albums and playlists selected based on your iPhone listening habits. 226 | 227 | - *Returns* 228 | 229 | - A Promise that resolves an Array with songs, albums, artists 230 | 231 | 232 | - **getRecommendations**() (*Apple Music Subscription Required*) 233 | 234 | Get songs, albums, artists recommendations of my Apple Music Account 235 | 236 | - *Returns* 237 | 238 | - A Promise that resolves an Array with songs, albums, artists 239 | 240 | 241 | - **addToPlaylist**(*playlistId*, *songId*) (*Apple Music Subscription Required*) 242 | 243 | Add the song to the playlist in my Apple Music Account 244 | 245 | - *Returns* 246 | 247 | - A Promise that resolves to "204" if successfull 248 | 249 | 250 | 251 | 252 | 253 | ## TODO 254 | 255 | * Return a user session after login 256 | 257 | 258 | ## ✨ Credits 259 | 260 | 261 | ## 🤔 How to contribute 262 | Have an idea? Found a bug? Please raise to [ISSUES](https://github.com/Lemonadd-UG/react-native-apple-music/issues). 263 | Contributions are welcome and are greatly appreciated! Every little bit helps, and credit will always be given. 264 | 265 | ## 💖 Support Bouncy 266 | 267 | [AppStore](https://apps.apple.com/us/app/bouncy-social-music-plattform/id1435616268?ls=1) 268 | 269 | [Google Play Store](https://play.google.com/store/apps/details?id=com.thebouncyapp) 270 | 271 | 272 | Thanks! ❤️ 273 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { NativeModules } from 'react-native'; 2 | import AppleMusic from './src/AppleMusic'; 3 | 4 | export default AppleMusic; 5 | -------------------------------------------------------------------------------- /ios/AppleMusicAPI.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import 4 | 5 | @interface RCT_EXTERN_MODULE(AppleMusicAPI, NSObject) 6 | 7 | //Api initialization 8 | RCT_EXTERN_METHOD(setValsAndInit: (NSString)keyID 9 | devTeamID:(NSString)devTeamID 10 | privateKey:(NSString)privateKey) 11 | 12 | RCT_EXTERN_METHOD(isReadyForBasicRequests: (RCTPromiseResolveBlock)resolve 13 | reject:(RCTPromiseRejectBlock)reject) 14 | 15 | RCT_EXTERN_METHOD(isReadyForUserRequests: (RCTPromiseResolveBlock)resolve 16 | reject:(RCTPromiseRejectBlock)reject) 17 | 18 | 19 | RCT_EXTERN_METHOD(getUserSubscriptionStatus: (RCTPromiseResolveBlock)resolve 20 | reject:(RCTPromiseRejectBlock)reject) 21 | 22 | RCT_EXTERN_METHOD(askUserForPermission: (RCTPromiseResolveBlock)resolve 23 | reject:(RCTPromiseRejectBlock)reject) 24 | 25 | RCT_EXTERN_METHOD(requestUserToken: (RCTPromiseResolveBlock)resolve 26 | reject:(RCTPromiseRejectBlock)reject) 27 | 28 | RCT_EXTERN_METHOD(getDeveloperToken: (RCTPromiseResolveBlock)resolve 29 | reject:(RCTPromiseRejectBlock)reject) 30 | 31 | RCT_EXTERN_METHOD(getUserToken: (RCTPromiseResolveBlock)resolve 32 | reject:(RCTPromiseRejectBlock)reject) 33 | 34 | //Api functions 35 | //No login 36 | RCT_EXTERN_METHOD(searchForTerm: (NSString)term 37 | offset:(int)offset 38 | type:(NSString)type 39 | resolve:(RCTPromiseResolveBlock)resolve 40 | reject:(RCTPromiseRejectBlock)reject) 41 | 42 | RCT_EXTERN_METHOD(getSong: (NSString)id 43 | resolve:(RCTPromiseResolveBlock)resolve 44 | reject:(RCTPromiseRejectBlock)reject) 45 | 46 | RCT_EXTERN_METHOD(getAlbum: (NSString)id 47 | resolve:(RCTPromiseResolveBlock)resolve 48 | reject:(RCTPromiseRejectBlock)reject) 49 | 50 | RCT_EXTERN_METHOD(getArtist: (NSString)id 51 | resolve:(RCTPromiseResolveBlock)resolve 52 | reject:(RCTPromiseRejectBlock)reject) 53 | 54 | 55 | RCT_EXTERN_METHOD(getCharts: (RCTPromiseResolveBlock)resolve 56 | reject:(RCTPromiseRejectBlock)reject) 57 | 58 | 59 | RCT_EXTERN_METHOD(getPlaylist: (NSString)id 60 | resolve:(RCTPromiseResolveBlock)resolve 61 | reject:(RCTPromiseRejectBlock)reject) 62 | 63 | RCT_EXTERN_METHOD(getSongs: (NSArray)ids 64 | resolve:(RCTPromiseResolveBlock)resolve 65 | reject:(RCTPromiseRejectBlock)reject) 66 | 67 | RCT_EXTERN_METHOD(getSongWithIsrc: (NSString)isrc 68 | resolve:(RCTPromiseResolveBlock)resolve 69 | reject:(RCTPromiseRejectBlock)reject) 70 | 71 | 72 | //login 73 | RCT_EXTERN_METHOD(getHeavyRotation: (RCTPromiseResolveBlock)resolve 74 | reject:(RCTPromiseRejectBlock)reject) 75 | 76 | RCT_EXTERN_METHOD(getRecentPlayed: (RCTPromiseResolveBlock)resolve 77 | reject:(RCTPromiseRejectBlock)reject) 78 | 79 | RCT_EXTERN_METHOD(getUserRecommendations: (RCTPromiseResolveBlock)resolve 80 | reject:(RCTPromiseRejectBlock)reject) 81 | 82 | RCT_EXTERN_METHOD(getAllUserPlaylists: (RCTPromiseResolveBlock)resolve 83 | reject:(RCTPromiseRejectBlock)reject) 84 | 85 | 86 | RCT_EXTERN_METHOD(getUserPlaylist: (NSString)id 87 | resolve:(RCTPromiseResolveBlock)resolve 88 | reject:(RCTPromiseRejectBlock)reject) 89 | 90 | RCT_EXTERN_METHOD(addToPlaylist: (NSString)playlistId 91 | mediaId:(NSString)mediaId 92 | resolve:(RCTPromiseResolveBlock)resolve 93 | reject:(RCTPromiseRejectBlock)reject) 94 | 95 | RCT_EXTERN_METHOD(newPlaylist: (NSString)name 96 | description:(NSString)description 97 | trackIds:(NSArray)trackIds 98 | resolve:(RCTPromiseResolveBlock)resolve 99 | reject:(RCTPromiseRejectBlock)reject) 100 | 101 | //other 102 | RCT_EXTERN_METHOD(getUserRecordID: (RCTPromiseResolveBlock)resolve 103 | reject:(RCTPromiseRejectBlock)reject) 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /ios/AppleMusicAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppleMusicAPI.swift 3 | // AppleMusicApi-Test 4 | // 5 | // Created by Janik Steegmüller on 07.07.19. 6 | // Copyright © 2020 Janik Steegmüller. All rights reserved. 7 | // 8 | import Foundation 9 | import CupertinoJWT 10 | import StoreKit 11 | import PromiseKit 12 | import CloudKit 13 | 14 | 15 | @objc(AppleMusicAPI) 16 | class AppleMusicAPI: NSObject { 17 | 18 | //MARK: Variables 19 | // Calculating token 20 | private var keyID: String? 21 | private var devTeamID: String? 22 | private var privateKey: String? 23 | 24 | // Tokens 25 | private var devToken: String? 26 | private var userToken: String? 27 | 28 | // Client for communication with AppleMusic 29 | private var client: CiderClient? 30 | 31 | // CloudController for gaining rights for user specific AppleMusic 32 | private var controller: SKCloudServiceController = SKCloudServiceController() 33 | private var musicLibraryPermissionGranted: Bool? 34 | 35 | 36 | @objc 37 | public func isReadyForBasicRequests(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 38 | var isInit: Bool 39 | isInit = (devToken != nil && client != nil) 40 | if(isInit){ 41 | resolve(true) 42 | } 43 | else{ 44 | reject("Error", "Not ready, please init!" , AppleMusicApiError("Please init first!")) 45 | } 46 | } 47 | 48 | @objc 49 | public func isReadyForUserRequests(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 50 | var isInit: Bool 51 | isInit = (devToken != nil && client != nil && userToken != nil) 52 | if(isInit){ 53 | resolve(true) 54 | } 55 | else{ 56 | reject("Error", "Not ready, please init!" , AppleMusicApiError("Please init first!")) 57 | } 58 | } 59 | 60 | 61 | /** 62 | Initialization of API 63 | - Parameter keyID: ID of Apple Music key from Apple Developer 64 | - Parameter devTeamID: ID of the development-team 65 | - Parameter privateKey: The private-key associated with the keyID 66 | */ 67 | @objc 68 | public func setValsAndInit(_ keyID: String, devTeamID: String, privateKey: String) { 69 | self.keyID = keyID 70 | self.devTeamID = devTeamID 71 | self.privateKey = privateKey 72 | 73 | self.calcDeveloperToken() 74 | 75 | self.musicLibraryPermissionGranted = self.checkIfMusicLibraryPermissionGranted() 76 | client = CiderClient(storefront: .germany, developerToken: self.devToken!) 77 | } 78 | 79 | //MARK: Calculating and requesting tokens 80 | 81 | /** 82 | Calculating of the developer-token with the SwiftJWT helper class 83 | - Throws: JWTError if private-key is malformed 84 | */ 85 | private func calcDeveloperToken() { 86 | if (self.devToken == nil && self.privateKey != nil && self.keyID != nil && self.devTeamID != nil) { 87 | // Assign developer information and token expiration setting 88 | let jwt = JWT(keyID: self.keyID!, teamID: self.devTeamID!, issueDate: Date(), expireDuration: 15777000) 89 | do { 90 | self.devToken = try jwt.sign(with: self.privateKey!) 91 | // Use the token in the authorization header in your requests connecting to Apple’s API server. 92 | // e.g. urlRequest.addValue(_ value: "bearer \(token)", forHTTPHeaderField field: "authorization") 93 | } catch { 94 | print(error.localizedDescription) 95 | } 96 | } 97 | } 98 | 99 | /** 100 | Get developer token 101 | */ 102 | @objc 103 | public func getDeveloperToken(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 104 | if(devToken != nil){ 105 | resolve(devToken) 106 | } else { 107 | reject("Error", "No dev token available" , AppleMusicApiError("Please init first!")) 108 | } 109 | } 110 | 111 | /** 112 | Get user token 113 | */ 114 | @objc 115 | public func getUserToken(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 116 | if(userToken != nil){ 117 | resolve(userToken) 118 | } else { 119 | reject("Error", "No user token available" , AppleMusicApiError("Please init first!")) 120 | } 121 | } 122 | 123 | 124 | func requestUserTokenPromise() -> Promise { 125 | return Promise { promise in 126 | self.controller.requestUserToken(forDeveloperToken: self.devToken!) { token, error in 127 | if (error == nil) { 128 | self.userToken = token; 129 | promise.fulfill(()); 130 | } else { 131 | promise.reject(error!) 132 | } 133 | } 134 | } 135 | } 136 | 137 | // MARK: Permission handling 138 | /** 139 | Check if we have the rights to access the users music-library 140 | - Returns: true if access granted/ false if otherwise 141 | */ 142 | public func checkIfMusicLibraryPermissionGranted() -> Bool { 143 | switch SKCloudServiceController.authorizationStatus() { 144 | case .authorized: 145 | return true 146 | default: 147 | return false 148 | } 149 | } 150 | 151 | 152 | /** 153 | Ask user for rights to access the music-library, will show an promt yes/no with 154 | Privacy - Media Library Usage Description-text 155 | */ 156 | private func askUserForMusicLibPermission() -> Promise { 157 | return Promise { promise in 158 | SKCloudServiceController.requestAuthorization { result in 159 | switch result { 160 | case .authorized: 161 | self.musicLibraryPermissionGranted = true; 162 | promise.fulfill("authorized") 163 | case .denied: 164 | self.musicLibraryPermissionGranted = false; 165 | promise.fulfill("denied") 166 | case .restricted: 167 | self.musicLibraryPermissionGranted = false; 168 | promise.fulfill("restricted") 169 | default: 170 | self.musicLibraryPermissionGranted = false; 171 | promise.reject(AppleMusicApiError("The authorization type cannot be determined.")) 172 | } 173 | } 174 | } 175 | } 176 | 177 | /** 178 | Check Apple Music subscription-status 179 | 180 | */ 181 | private func checkSKCloudServiceCapability() -> Promise { 182 | return Promise { promise in 183 | controller.requestCapabilities { (capabilities: SKCloudServiceCapability, error: Error?) in 184 | if (error == nil) { 185 | if capabilities.contains(.musicCatalogPlayback){ 186 | promise.fulfill(()) 187 | } else { 188 | promise.reject(AppleMusicApiError("User has no subscription")) 189 | } 190 | } else { 191 | promise.reject(error!) 192 | } 193 | } 194 | } 195 | } 196 | 197 | @objc 198 | public func getUserSubscriptionStatus(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 199 | firstly{ 200 | checkSKCloudServiceCapability(); 201 | }.done{ 202 | resolve("Subscribed"); 203 | }.catch{ error in 204 | reject("Error", error.localizedDescription, error ); 205 | } 206 | } 207 | 208 | @objc 209 | public func askUserForPermission(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 210 | firstly{ 211 | askUserForMusicLibPermission() 212 | }.done{ result in 213 | resolve(result); 214 | }.catch{ error in 215 | reject("Error", error.localizedDescription, error ); 216 | } 217 | } 218 | 219 | @objc 220 | public func requestUserToken(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 221 | firstly{ 222 | requestUserTokenPromise() 223 | }.done{ 224 | self.client = CiderClient(storefront: .germany, developerToken: self.devToken!, userToken: self.userToken!) 225 | resolve("Ready to go"); 226 | }.catch{ error in 227 | reject("Error", error.localizedDescription, error ); 228 | } 229 | } 230 | 231 | // MARK: Actual functions from the API 232 | /** 233 | Search for term and return result with callback 234 | - Parameter searchString: The term to search 235 | - Parameter offset: The offset (page) you want to receive 236 | - Parameter callback: Callback for ReactNative 237 | */ 238 | @objc 239 | public func searchForTerm(_ searchString: String, offset: Int, type: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 240 | if (client != nil) { 241 | let type = MediaType.init(rawValue: type); 242 | client!.searchJsonString(term: searchString, limit: 24, offset: offset, types: [type!]) { results, error in 243 | if (error == nil) { 244 | resolve(self.jsonToDic(json: results)) 245 | } else { 246 | reject("error", error?.localizedDescription, error) 247 | } 248 | } 249 | } 250 | } 251 | 252 | /** 253 | Return information about a multiple songs 254 | - Parameter id: The id of the song 255 | */ 256 | @objc 257 | public func getSongs(_ ids: [String], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 258 | if (client != nil) { 259 | client!.getObjects(mediaType: .songs, ids: ids) { result, error in 260 | if (error == nil) { 261 | resolve(self.jsonToDic(json: result)) 262 | } else { 263 | reject("error", error?.localizedDescription, error) 264 | } 265 | } 266 | } 267 | } 268 | 269 | 270 | /** 271 | Return information about a specific song 272 | - Parameter id: The id of the song 273 | - Parameter callback: Callback for ReactNative 274 | */ 275 | @objc 276 | public func getSong(_ id: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 277 | if (client != nil) { 278 | client!.getCatalogObjectWithId(mediaType: .songs, id: id, include: nil) { results, error in 279 | if (error == nil) { 280 | resolve(self.jsonToDic(json: results)) 281 | } else { 282 | reject("error", error?.localizedDescription, error) 283 | } 284 | } 285 | } 286 | } 287 | 288 | /** 289 | Return information about a specific playlist 290 | - Parameter id: The id of the song 291 | */ 292 | @objc 293 | public func getPlaylist(_ id: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 294 | if (client != nil) { 295 | client!.getCatalogObjectWithId(mediaType: .playlists, id: id, include: nil) { result, error in 296 | if (error == nil) { 297 | resolve(self.jsonToDic(json: result)) 298 | } else { 299 | reject("error", error?.localizedDescription, error) 300 | } 301 | } 302 | } 303 | } 304 | 305 | /** 306 | Return information about a specific album 307 | - Parameter id: The id of the album 308 | - Parameter callback: Callback for ReactNative 309 | */ 310 | @objc 311 | public func getAlbum(_ id: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 312 | if (client != nil) { 313 | client!.getCatalogObjectWithId(mediaType: .albums, id: id, include: nil) { results, error in 314 | if (error == nil) { 315 | resolve(self.jsonToDic(json: results)) 316 | } else { 317 | reject("error", error?.localizedDescription, error) 318 | } 319 | } 320 | } 321 | } 322 | 323 | /** 324 | Return information about a specific artist 325 | - Parameter id: The id of the artist 326 | - Parameter callback: Callback for ReactNative 327 | */ 328 | @objc 329 | public func getArtist(_ id: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 330 | if (client != nil) { 331 | client!.getCatalogObjectWithId(mediaType: .artists, id: id, include: nil) { results, error in 332 | if (error == nil) { 333 | resolve(self.jsonToDic(json: results)) 334 | } else { 335 | reject("error", error?.localizedDescription, error) 336 | } 337 | } 338 | } 339 | } 340 | 341 | /** 342 | Get the heavyRotation ( recently heard songs/playlist/etc.) 343 | - Parameter callback: Callback for ReactNative 344 | */ 345 | @objc 346 | public func getHeavyRotation(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 347 | if (client != nil) { 348 | client!.heavyRotationJsonString(limit: 10) { results, error in 349 | if (error == nil) { 350 | resolve(self.jsonToDic(json: results)) 351 | } else { 352 | reject("error", error?.localizedDescription, error) 353 | } 354 | } 355 | } 356 | } 357 | 358 | /** 359 | Get the recentPlayed ( recently heard songs/playlist/etc.) 360 | - Parameter callback: Callback for ReactNative 361 | */ 362 | @objc 363 | public func getRecentPlayed(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 364 | if (client != nil) { 365 | client!.recentPlayedJsonString(limit: 10) { results, error in 366 | if (error == nil) { 367 | resolve(self.jsonToDic(json: results)) 368 | } else { 369 | reject("error", error?.localizedDescription, error) 370 | } 371 | } 372 | } 373 | } 374 | 375 | /** 376 | Get the charts 377 | - Parameter callback: Callback for ReactNative 378 | */ 379 | @objc 380 | public func getCharts(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 381 | if (client != nil) { 382 | client!.chartsJsonString(limit: 50, types: [.albums, .songs]) { results, error in 383 | if (error == nil) { 384 | resolve(self.jsonToDic(json: results)) 385 | } else { 386 | reject("error", error?.localizedDescription, error) 387 | } 388 | } 389 | } 390 | } 391 | 392 | /** 393 | Get all user playlists 394 | - Parameter callback: Callback for ReactNative 395 | */ 396 | @objc 397 | public func getAllUserPlaylists(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 398 | if (client != nil) { 399 | client!.getAllUserPlaylistsJsonString(limit: 50) { results, error in 400 | if (error == nil) { 401 | resolve(self.jsonToDic(json: results)) 402 | } else { 403 | reject("error", error?.localizedDescription, error) 404 | } 405 | } 406 | } 407 | } 408 | 409 | @objc 410 | public func getUserPlaylist(_ id: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock){ 411 | if (client != nil) { 412 | client!.getUserPlaylistJsonString(id: id){ result, error in 413 | if (error == nil) { 414 | resolve(self.jsonToDic(json: result)) 415 | } else{ 416 | reject("error", error?.localizedDescription, error) 417 | } 418 | } 419 | } 420 | } 421 | 422 | /** 423 | Get user recommendations 424 | - Parameter callback: Callback for ReactNative 425 | */ 426 | @objc 427 | public func getUserRecommendations(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 428 | if (client != nil) { 429 | client!.recommendationsJsonString { results, error in 430 | if (error == nil) { 431 | resolve(self.jsonToDic(json: results)) 432 | } else { 433 | reject("error", error?.localizedDescription, error) 434 | } 435 | } 436 | } 437 | } 438 | 439 | @objc 440 | public func addToPlaylist(_ playlistId: String, mediaId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock){ 441 | if ( client != nil) { 442 | client!.addToPlaylist(playlistId: playlistId, mediaId: mediaId, mediaType: .songs) { (result, error) in 443 | if( error == nil) { 444 | resolve(result) 445 | } else { 446 | reject("error", error?.localizedDescription, error) 447 | } 448 | } 449 | } 450 | } 451 | 452 | @objc 453 | public func newPlaylist(_ name: String, description: String, trackIds: [String], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 454 | if ( client != nil) { 455 | client!.newPlaylist(name: name, description: description, trackIds: trackIds){ (result, error) in 456 | if( error == nil) { 457 | resolve(result) 458 | } else { 459 | reject("error", error?.localizedDescription, error) 460 | } 461 | } 462 | } 463 | } 464 | 465 | @objc 466 | public func getSongWithIsrc(_ isrc: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 467 | if (client != nil) { 468 | client!.fetchIsrcJsonString(mediaType: .songs, isrc: isrc) { result, error in 469 | if (error == nil) { 470 | resolve(self.jsonToDic(json: result)) 471 | } else{ 472 | reject("error", error?.localizedDescription, error) 473 | } 474 | } 475 | } 476 | } 477 | 478 | 479 | @objc 480 | public func getUserRecordID(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { 481 | CKContainer.default().fetchUserRecordID { recordID, error in 482 | if let recordIDName = recordID?.recordName { 483 | resolve(recordIDName) 484 | } else { 485 | reject("error",error?.localizedDescription, error) 486 | } 487 | } 488 | } 489 | 490 | private func jsonToDic(json: String) -> [String: Any]? { 491 | if let jsonData = json.data(using: .utf8){ 492 | do { 493 | return try JSONSerialization.jsonObject(with: jsonData) as? [String: Any] 494 | } catch { 495 | print(error.localizedDescription) 496 | } 497 | } 498 | return nil; 499 | } 500 | 501 | //required for ReactNative 502 | @objc 503 | static func requiresMainQueueSetup() -> Bool { 504 | return true 505 | } 506 | 507 | // MARK: Claims for Apple Music JWT 508 | struct AppleMusicClaims: Codable { 509 | let iss: String 510 | let iat: Date 511 | let exp: Date 512 | } 513 | 514 | } 515 | -------------------------------------------------------------------------------- /ios/AppleMusicApiError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppleMusicApiError.swift 3 | // AwesomeApp 4 | // 5 | // Created by Janik Steegmüller on 19.12.19. 6 | // Copyright © 2020 Janik Steegmüller. All rights reserved. 7 | // 8 | struct AppleMusicApiError: Error { 9 | let message: String 10 | 11 | init(_ message: String) { 12 | self.message = message 13 | } 14 | 15 | public var localizedDescription: String { 16 | return message 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ios/Cider/Client/CiderClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CiderClient.swift 3 | // Cider 4 | // 5 | // Created by Scott Hoyt on 8/4/17. 6 | // Copyright © 2017 Scott Hoyt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// A client for submitting requests to the Apple Music API. 12 | public struct CiderClient { 13 | private let urlBuilder: UrlBuilder 14 | private let fetcher: UrlFetcher 15 | 16 | // MARK: URLFetcher 17 | 18 | /** 19 | Default `UrlFetcher` 20 | 21 | A `URLSession` with the default `URLSessionConfiguration` 22 | */ 23 | public static var defaultURLFetcher: UrlFetcher { 24 | return URLSession(configuration: URLSessionConfiguration.default) 25 | } 26 | 27 | // MARK: Initialization 28 | 29 | init(urlBuilder: UrlBuilder, urlFetcher: UrlFetcher = CiderClient.defaultURLFetcher) { 30 | self.urlBuilder = urlBuilder 31 | self.fetcher = urlFetcher 32 | } 33 | 34 | /** 35 | Initialize a `CiderClient` 36 | 37 | - parameters: 38 | - storefront: The `Storefront` to submit requests to. 39 | - developerToken: The Apple Music developer token to use in requests. 40 | - userToken: The Apple Music user token to use in requests. 41 | - urlFetcher: The `UrlFetcher` to use for processing requests. Defaults to a `URLSession` with the default `URLSessionConfiguration`. 42 | */ 43 | public init(storefront: Storefront, developerToken: String, urlFetcher: UrlFetcher = CiderClient.defaultURLFetcher) { 44 | let urlBuilder = CiderUrlBuilder(storefront: storefront, developerToken: developerToken) 45 | self.init(urlBuilder: urlBuilder, urlFetcher: urlFetcher) 46 | } 47 | 48 | public init(storefront: Storefront, developerToken: String, userToken: String, urlFetcher: UrlFetcher = CiderClient.defaultURLFetcher) { 49 | let urlBuilder = CiderUrlBuilder(storefront: storefront, developerToken: developerToken, userToken: userToken) 50 | self.init(urlBuilder: urlBuilder, urlFetcher: urlFetcher) 51 | } 52 | 53 | 54 | // MARK: Search 55 | 56 | /** 57 | Get user recommendations 58 | 59 | */ 60 | public func recommendationsJsonString(limit: Int? = nil, offset: Int? = nil, types: [MediaType]? = nil, completion: @escaping (String, Error?) -> Void) { 61 | let request = urlBuilder.userRecommendationsRequest(limit: limit, offset: offset, types: types) 62 | fetcher.fetch(request: request){ (data, error, response) in 63 | guard let data = data else { 64 | completion("", error) 65 | return 66 | } 67 | completion(String.init(data: data, encoding: .utf8) ?? "error decoding", nil) 68 | } 69 | } 70 | 71 | 72 | /** 73 | Get heavy-rotation as json from user 74 | 75 | */ 76 | public func heavyRotationJsonString(limit: Int? = nil, offset: Int? = nil, completion: @escaping (String, Error?) -> Void) { 77 | let request = urlBuilder.heavyRotationRequest(limit: limit, offset: offset) 78 | fetcher.fetch(request: request){ (data, error, response) in 79 | guard let data = data else { 80 | completion("", error) 81 | return 82 | } 83 | completion(String.init(data: data, encoding: .utf8) ?? "error decoding", nil) 84 | } 85 | } 86 | 87 | /** 88 | Fetch media via isrc 89 | 90 | */ 91 | public func fetchIsrcJsonString(mediaType: MediaType, isrc: String, include: [Include]? = nil, completion: @escaping (String, Error?) -> Void) { 92 | let request = urlBuilder.fetchIsrcRequest(mediaType: mediaType, isrc: isrc, include: include) 93 | fetcher.fetch(request: request) { (data, error, response) in 94 | guard let data = data else { 95 | completion("", error) 96 | return 97 | } 98 | completion(String.init(data: data, encoding: .utf8) ?? "error decoding", nil) 99 | } 100 | } 101 | 102 | public func getObjects(mediaType: MediaType, ids: [String], include: [Include]? = nil, completion: @escaping (String, Error?) -> Void) { 103 | let request = urlBuilder.getObjectsRequest(mediaType: mediaType, ids: ids, include: include) 104 | fetcher.fetch(request: request) { (data, error, response) in 105 | guard let data = data else { 106 | completion("", error) 107 | return 108 | } 109 | completion(String.init(data: data, encoding: .utf8) ?? "error decoding", nil) 110 | } 111 | } 112 | 113 | /** 114 | Get recent played as json from user 115 | 116 | */ 117 | public func recentPlayedJsonString(limit: Int? = nil, offset: Int? = nil, completion: @escaping (String, Error?) -> Void) { 118 | let request = urlBuilder.recentPlayedRequest(limit: limit, offset: offset) 119 | fetcher.fetch(request: request){ (data, error, response) in 120 | guard let data = data else { 121 | completion("", error) 122 | return 123 | } 124 | completion(String.init(data: data, encoding: .utf8) ?? "error decoding", nil) 125 | } 126 | } 127 | 128 | /** 129 | Get all playlists from user as json 130 | 131 | */ 132 | public func getAllUserPlaylistsJsonString(limit: Int? = nil, offset: Int? = nil, completion: @escaping (String, Error?) -> Void) { 133 | let request = urlBuilder.allUserPlaylistsRequest(limit: limit, offset: offset) 134 | fetcher.fetch(request: request){ (data, error, response) in 135 | guard let data = data else { 136 | completion("", error) 137 | return 138 | } 139 | completion(String.init(data: data, encoding: .utf8) ?? "error decoding", nil) 140 | } 141 | } 142 | 143 | public func getUserPlaylistJsonString(id: String, completion: @escaping (String, Error?) -> Void) { 144 | let request = urlBuilder.userPlaylistRequest(id: id) 145 | fetcher.fetch(request: request) { (data, error, response) in 146 | guard let data = data else { 147 | completion("", error) 148 | return 149 | } 150 | completion(String.init(data: data, encoding: .utf8) ?? "error decoding", nil) 151 | } 152 | } 153 | 154 | 155 | /** 156 | Get charts as json 157 | 158 | */ 159 | public func chartsJsonString(limit: Int? = nil, offset: Int? = nil, types: [MediaType]? = nil, completion: @escaping (String, Error?) -> Void) { 160 | let request = urlBuilder.chartsRequest(limit: limit, offset: offset, types: types) 161 | fetcher.fetch(request: request){ (data, error, response) in 162 | guard let data = data else { 163 | completion("", error) 164 | return 165 | } 166 | completion(String.init(data: data, encoding: .utf8) ?? "error decoding", nil) 167 | } 168 | } 169 | 170 | /** 171 | Get search as json 172 | 173 | */ 174 | public func searchJsonString(term: String, limit: Int? = nil, offset: Int? = nil, types: [MediaType]? = nil, completion: @escaping (String, Error?) -> Void) { 175 | let request = urlBuilder.searchRequest(term: term, limit: limit, offset: offset, types: types) 176 | fetcher.fetch(request: request){ (data, error, response) in 177 | guard let data = data else { 178 | completion("", error) 179 | return 180 | } 181 | completion(String.init(data: data, encoding: .utf8) ?? "error decoding", nil) 182 | } 183 | } 184 | 185 | public func addToPlaylist(playlistId: String, mediaId: String, mediaType: MediaType, completion: @escaping (String, Error?) -> Void){ 186 | let request = urlBuilder.addToPlaylistRequest(playlistId: playlistId, mediaId: mediaId, mediaType: mediaType) 187 | fetcher.fetch(request: request) {(data, error, response) in 188 | guard data != nil else { 189 | completion("", error) 190 | return 191 | } 192 | let httpResponse: HTTPURLResponse = response as! HTTPURLResponse 193 | completion(String(httpResponse.statusCode) , nil) 194 | } 195 | } 196 | 197 | public func newPlaylist(name: String, description: String, trackIds: [String], completion: @escaping (String, Error?) -> Void){ 198 | let request = urlBuilder.newPlaylistRequest(name: name, description: description, trackIds: trackIds) 199 | fetcher.fetch(request: request) {(data, error, response) in 200 | guard data != nil else { 201 | completion("", error) 202 | return 203 | } 204 | let httpResponse: HTTPURLResponse = response as! HTTPURLResponse 205 | completion(String(httpResponse.statusCode) , nil) 206 | } 207 | } 208 | 209 | public func getCatalogObjectWithId(mediaType: MediaType, id: String, include:[Include]? ,completion: @escaping (String, Error?) -> Void){ 210 | let request = urlBuilder.fetchRequest(mediaType: mediaType, id: id, include: include) 211 | fetcher.fetch(request: request){ (data, error, response) in 212 | guard let data = data else { 213 | completion("", error) 214 | return 215 | } 216 | completion(String.init(data: data, encoding: .utf8) ?? "error decoding", nil) 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /ios/Cider/Client/CiderUrlBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CiderUrlBuilder.swift 3 | // Cider 4 | // 5 | // Created by Scott Hoyt on 8/1/17. 6 | // Copyright © 2017 Scott Hoyt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol UrlBuilder { 12 | func searchRequest(term: String, limit: Int?, offset: Int?, types: [MediaType]?) -> URLRequest 13 | func searchHintsRequest(term: String, limit: Int?, types: [MediaType]?) -> URLRequest 14 | func fetchRequest(mediaType: MediaType, id: String, include: [Include]?) -> URLRequest 15 | func relationshipRequest(path: String, limit: Int?, offset: Int?) -> URLRequest 16 | func heavyRotationRequest(limit: Int?, offset: Int?) -> URLRequest 17 | func recentPlayedRequest(limit: Int?, offset: Int?) -> URLRequest 18 | func chartsRequest(limit: Int?, offset: Int?, types: [MediaType]?) -> URLRequest 19 | func allUserPlaylistsRequest(limit: Int?, offset: Int?) -> URLRequest 20 | func userPlaylistRequest(id: String) -> URLRequest 21 | func userRecommendationsRequest(limit: Int?, offset: Int?, types: [MediaType]?) -> URLRequest 22 | func fetchIsrcRequest(mediaType: MediaType, isrc: String, include: [Include]?) -> URLRequest 23 | func addToPlaylistRequest(playlistId: String, mediaId: String ,mediaType: MediaType) -> URLRequest 24 | func getObjectsRequest(mediaType: MediaType, ids: [String], include:[Include]?) -> URLRequest 25 | func newPlaylistRequest(name: String, description: String, trackIds: [String]) -> URLRequest 26 | } 27 | 28 | public enum CiderUrlBuilderError: Error { 29 | case noUserToken 30 | } 31 | 32 | // MARK: - Constants 33 | 34 | private struct AppleMusicApi { 35 | // Base 36 | static let baseURLScheme = "https" 37 | static let baseURLString = "api.music.apple.com" 38 | static let baseURLApiVersion = "/v1" 39 | 40 | // Search 41 | static let searchPath = "v1/catalog/{storefront}/search" 42 | static let searchHintPath = "v1/catalog/{storefront}/search/hints" 43 | 44 | // Parameteres 45 | static let termParameter = "term" 46 | static let limitParameter = "limit" 47 | static let offsetParameter = "offset" 48 | static let typesParameter = "types" 49 | static let filterParameter = "filter[isrc]" 50 | 51 | // Fetch 52 | static let getCatalogObjectWithIdPath = "v1/catalog/{storefront}/{mediaType}/{id}" 53 | static let fetchInclude = "include" 54 | static let getCatalogObjectPath = "v1/catalog/{storefront}/{mediaType}" 55 | static let ids = "ids" 56 | 57 | // User-specific heavy-rotation https://api.music.apple.com/v1/me/history/heavy-rotation 58 | static let heavyRotationPath = "v1/me/history/heavy-rotation" 59 | 60 | // User-specific recent https://api.music.apple.com/v1/me/recent/played 61 | static let recentPlayedPath = "v1/me/recent/played" 62 | 63 | // Private user playlists 64 | static let userPlaylistsPath = "v1/me/library/playlists" 65 | static let userPlaylistPath = "v1/me/library/playlists/{id}" 66 | 67 | //Charts of specific country https://api.music.apple.com/v1/catalog/{storefront}/charts 68 | static let chartsPath = "v1/catalog/us/charts" 69 | 70 | //User-specific recommendations 71 | static let recommendationsPath = "v1/me/recommendations" 72 | 73 | //Add song to playlist 74 | static let addMediaToPlaylistPath = "v1/me/library/playlists/{id}/tracks" 75 | 76 | } 77 | 78 | // MARK: - UrlBuilder 79 | 80 | struct CiderUrlBuilder: UrlBuilder { 81 | 82 | // MARK: Inputs 83 | 84 | let storefront: Storefront 85 | let developerToken: String 86 | var userToken: String? 87 | private let cachePolicy = URLRequest.CachePolicy.useProtocolCachePolicy 88 | private let timeout: TimeInterval = 10 89 | 90 | // MARK: Init 91 | 92 | init(storefront: Storefront, developerToken: String, userToken: String) { 93 | self.storefront = storefront 94 | self.developerToken = developerToken 95 | self.userToken = userToken 96 | } 97 | 98 | init(storefront: Storefront, developerToken: String) { 99 | self.storefront = storefront 100 | self.developerToken = developerToken 101 | } 102 | 103 | private var baseApiUrl: URL { 104 | var components = URLComponents() 105 | 106 | components.scheme = AppleMusicApi.baseURLScheme 107 | components.host = AppleMusicApi.baseURLString 108 | 109 | return components.url! 110 | } 111 | 112 | // MARK: Construct urls 113 | 114 | private func recommendationsUrl( limit: Int?, offset: Int?, types: [MediaType]?) -> URL { 115 | var components = URLComponents() 116 | 117 | components.path = AppleMusicApi.recommendationsPath 118 | 119 | components.apply(limit: limit) 120 | components.apply(offset: offset) 121 | components.apply(mediaTypes: types) 122 | 123 | return components.url(relativeTo: baseApiUrl)! 124 | } 125 | 126 | private func heavyRotationUrl( limit: Int?, offset: Int?) -> URL { 127 | var components = URLComponents() 128 | 129 | components.path = AppleMusicApi.heavyRotationPath 130 | 131 | components.apply(limit: limit) 132 | components.apply(offset: offset) 133 | 134 | return components.url(relativeTo: baseApiUrl)! 135 | } 136 | 137 | private func userPlaylistUrl( id: String) -> URL { 138 | var components = URLComponents() 139 | 140 | components.path = AppleMusicApi.userPlaylistPath.addId(id) 141 | components.apply(include: [.tracks]) 142 | 143 | return components.url(relativeTo: baseApiUrl)! 144 | } 145 | 146 | private func allUserPlaylistsUrl( limit: Int?, offset: Int?) -> URL { 147 | var components = URLComponents() 148 | 149 | components.path = AppleMusicApi.userPlaylistsPath 150 | 151 | components.apply(limit: limit) 152 | components.apply(offset: offset) 153 | 154 | return components.url(relativeTo: baseApiUrl)! 155 | } 156 | 157 | 158 | private func recentPlayedUrl( limit: Int?, offset: Int?) -> URL { 159 | var components = URLComponents() 160 | 161 | components.path = AppleMusicApi.recentPlayedPath 162 | 163 | components.apply(limit: limit) 164 | components.apply(offset: offset) 165 | 166 | return components.url(relativeTo: baseApiUrl)! 167 | } 168 | 169 | 170 | private func seachUrl(term: String, limit: Int?, offset: Int?, types: [MediaType]?) -> URL { 171 | 172 | // Construct url path 173 | 174 | var components = URLComponents() 175 | 176 | components.path = AppleMusicApi.searchPath.addStorefront(storefront) 177 | 178 | // Construct Query 179 | components.apply(searchTerm: term) 180 | components.apply(limit: limit) 181 | components.apply(offset: offset) 182 | components.apply(mediaTypes: types) 183 | 184 | // Construct final url 185 | return components.url(relativeTo: baseApiUrl)! 186 | } 187 | 188 | private func chartsUrl(limit: Int?, offset: Int?, types: [MediaType]?) -> URL { 189 | 190 | var components = URLComponents() 191 | 192 | components.path = AppleMusicApi.chartsPath 193 | 194 | components.apply(limit: limit) 195 | components.apply(mediaTypes: types) 196 | components.apply(offset: offset) 197 | 198 | return components.url(relativeTo: baseApiUrl)! 199 | } 200 | 201 | private func searchHintsUrl(term: String, limit: Int?, types: [MediaType]?) -> URL { 202 | 203 | // Construct url path 204 | 205 | var components = URLComponents() 206 | 207 | components.path = AppleMusicApi.searchHintPath.addStorefront(storefront) 208 | 209 | // Construct Query 210 | components.apply(searchTerm: term) 211 | components.apply(limit: limit) 212 | components.apply(mediaTypes: types) 213 | 214 | // Construct final url 215 | return components.url(relativeTo: baseApiUrl)! 216 | } 217 | 218 | private func fetchUrl(mediaType: MediaType, id: String, include: [Include]?) -> URL { 219 | var components = URLComponents() 220 | 221 | components.path = AppleMusicApi.getCatalogObjectWithIdPath.addStorefront(storefront).addMediaType(mediaType).addId(id) 222 | components.apply(include: include) 223 | 224 | return components.url(relativeTo: baseApiUrl)!.absoluteURL 225 | } 226 | 227 | private func fetchIsrcUrl(mediaType: MediaType, isrc: String, include: [Include]?) -> URL { 228 | var components = URLComponents() 229 | 230 | components.path = AppleMusicApi.getCatalogObjectPath.addStorefront(storefront).addMediaType(mediaType) 231 | components.apply(isrc: isrc) 232 | 233 | return components.url(relativeTo: baseApiUrl)!.absoluteURL 234 | } 235 | 236 | private func getObjectsUrl(mediaType: MediaType, ids: [String], include: [Include]?) -> URL { 237 | var componets = URLComponents() 238 | 239 | componets.path = AppleMusicApi.getCatalogObjectPath.addStorefront(storefront).addMediaType(mediaType) 240 | componets.apply(ids: ids) 241 | componets.apply(include: include) 242 | 243 | return componets.url(relativeTo: baseApiUrl)!.absoluteURL 244 | } 245 | 246 | private func relationshipUrl(path: String, limit: Int?, offset: Int?) -> URL { 247 | var components = URLComponents() 248 | 249 | components.path = path 250 | components.apply(limit: limit) 251 | components.apply(offset: offset) 252 | 253 | return components.url(relativeTo: baseApiUrl)!.absoluteURL 254 | } 255 | 256 | private func addToPlaylistUrl(playlistId: String) -> URL { 257 | var compoments = URLComponents() 258 | 259 | compoments.path = AppleMusicApi.addMediaToPlaylistPath.addId(playlistId) 260 | 261 | return compoments.url(relativeTo: baseApiUrl)!.absoluteURL 262 | } 263 | 264 | private func createPlaylistUrl() -> URL { 265 | var components = URLComponents() 266 | 267 | components.path = AppleMusicApi.userPlaylistsPath 268 | 269 | return components.url(relativeTo: baseApiUrl)!.absoluteURL 270 | } 271 | 272 | // MARK: Construct requests 273 | 274 | func userRecommendationsRequest(limit: Int?, offset: Int?, types: [MediaType]?) -> URLRequest { 275 | let url = recommendationsUrl(limit: limit, offset: offset, types: types) 276 | return constructRequestWithUserAuth(url: url) 277 | } 278 | 279 | func heavyRotationRequest(limit: Int?, offset: Int?) -> URLRequest { 280 | let url = heavyRotationUrl(limit: limit, offset: offset) 281 | return constructRequestWithUserAuth(url: url) 282 | } 283 | 284 | func allUserPlaylistsRequest(limit: Int?, offset: Int?) -> URLRequest { 285 | let url = allUserPlaylistsUrl(limit: limit, offset: offset) 286 | return constructRequestWithUserAuth(url: url) 287 | } 288 | 289 | func userPlaylistRequest(id: String) -> URLRequest { 290 | let url = userPlaylistUrl(id: id) 291 | return constructRequestWithUserAuth(url: url) 292 | } 293 | 294 | func chartsRequest(limit: Int?, offset: Int?, types: [MediaType]?) -> URLRequest { 295 | let url = chartsUrl(limit: limit, offset: offset, types: types) 296 | return constructRequest(url: url) 297 | } 298 | 299 | func recentPlayedRequest(limit: Int?, offset: Int?) -> URLRequest { 300 | let url = recentPlayedUrl(limit: limit, offset: offset) 301 | return constructRequestWithUserAuth(url: url) 302 | } 303 | 304 | func searchRequest(term: String, limit: Int?, offset: Int?, types: [MediaType]?) -> URLRequest { 305 | let url = seachUrl(term: term, limit: limit, offset: offset, types: types) 306 | return constructRequest(url: url) 307 | } 308 | 309 | func fetchIsrcRequest(mediaType: MediaType, isrc: String, include: [Include]?) -> URLRequest { 310 | let url = fetchIsrcUrl(mediaType: mediaType, isrc: isrc, include: include) 311 | return constructRequest(url: url) 312 | } 313 | 314 | func getObjectsRequest(mediaType: MediaType, ids: [String], include:[Include]?) -> URLRequest { 315 | let url = getObjectsUrl(mediaType: mediaType, ids: ids, include: include) 316 | return constructRequest(url: url) 317 | } 318 | 319 | func searchHintsRequest(term: String, limit: Int?, types: [MediaType]?) -> URLRequest { 320 | let url = searchHintsUrl(term: term, limit: limit, types: types) 321 | return constructRequest(url: url) 322 | } 323 | 324 | func fetchRequest(mediaType: MediaType, id: String, include: [Include]?) -> URLRequest { 325 | let url = fetchUrl(mediaType: mediaType, id: id, include: include) 326 | return constructRequest(url: url) 327 | } 328 | 329 | func relationshipRequest(path: String, limit: Int?, offset: Int?) -> URLRequest { 330 | let url = relationshipUrl(path: path, limit: limit, offset: offset) 331 | return constructRequest(url: url) 332 | } 333 | 334 | func newPlaylistRequest(name: String, description: String, trackIds: [String]) -> URLRequest { 335 | let url = createPlaylistUrl() 336 | let attributes = LibraryPlaylistCreationRequest.Attributes(description: description, name: name) 337 | var data: [LibraryPlaylistCreationRequest.LibraryPlaylistRequestTrack] = [] 338 | for id in trackIds { 339 | let track = LibraryPlaylistCreationRequest.LibraryPlaylistRequestTrack(id: id, type: .songs) 340 | data.append(track) 341 | } 342 | let trackData = LibraryPlaylistCreationRequest.Data(data: data) 343 | let relationships = LibraryPlaylistCreationRequest.Relationships(tracks: trackData) 344 | 345 | let post = LibraryPlaylistCreationRequest(attributes: attributes, relationships: relationships) 346 | 347 | return constructLibraryPlaylistCreationRequest(url: url, playlist: post) 348 | } 349 | 350 | func addToPlaylistRequest(playlistId: String, mediaId: String ,mediaType: MediaType) -> URLRequest { 351 | let url = addToPlaylistUrl(playlistId: playlistId) 352 | let data = [LibraryPlaylistRequestTrack.Media(id: mediaId, type: mediaType)] 353 | 354 | return constructLibraryPlaylistRequest(url: url, media: LibraryPlaylistRequestTrack(data: data)) 355 | } 356 | 357 | func constructLibraryPlaylistRequest(url: URL, media: LibraryPlaylistRequestTrack) -> URLRequest { 358 | var request = URLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeout) 359 | request = addAuth(request: request) 360 | request = try! addUserToken(request: request) 361 | 362 | request.httpMethod = "POST" 363 | request.httpBody = try! JSONEncoder().encode(media) 364 | 365 | return request 366 | } 367 | 368 | func constructLibraryPlaylistCreationRequest(url: URL, playlist: LibraryPlaylistCreationRequest) -> URLRequest { 369 | var request = URLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeout) 370 | request = addAuth(request: request) 371 | request = try! addUserToken(request: request) 372 | 373 | request.httpMethod = "POST" 374 | request.httpBody = try! JSONEncoder().encode(playlist) 375 | 376 | return request 377 | } 378 | 379 | private func constructRequest(url: URL) -> URLRequest { 380 | var request = URLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeout) 381 | request = addAuth(request: request) 382 | 383 | return request 384 | } 385 | 386 | private func constructRequestWithUserAuth(url: URL) -> URLRequest { 387 | var request = URLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeout) 388 | request = addAuth(request: request) 389 | request = try! addUserToken(request: request) 390 | 391 | return request 392 | } 393 | 394 | // MARK: Add authentication 395 | 396 | private func addAuth(request: URLRequest) -> URLRequest { 397 | var request = request 398 | 399 | let authHeader = "Bearer \(developerToken)" 400 | request.setValue(authHeader, forHTTPHeaderField: "Authorization") 401 | 402 | return request 403 | } 404 | 405 | // TODO: Make this private once we add a request that needs it and can test via that vector. 406 | func addUserToken(request: URLRequest) throws -> URLRequest { 407 | guard let userToken = userToken else { 408 | throw CiderUrlBuilderError.noUserToken 409 | } 410 | 411 | var request = request 412 | request.setValue(userToken, forHTTPHeaderField: "Music-User-Token") 413 | 414 | return request 415 | } 416 | } 417 | 418 | // MARK: - Helpers 419 | 420 | private extension String { 421 | func replaceSpacesWithPluses() -> String { 422 | return replacingOccurrences(of: " ", with: "+") 423 | } 424 | 425 | func addStorefront(_ storefront: Storefront) -> String { 426 | return replacingOccurrences(of: "{storefront}", with: storefront.rawValue) 427 | } 428 | 429 | func addId(_ id: String) -> String { 430 | return replacingOccurrences(of: "{id}", with: id) 431 | } 432 | 433 | func addMediaType(_ mediaType: MediaType) -> String { 434 | return replacingOccurrences(of: "{mediaType}", with: mediaType.rawValue) 435 | } 436 | } 437 | 438 | private extension URLComponents { 439 | mutating func createQueryItemsIfNeeded() { 440 | if queryItems == nil { 441 | queryItems = [] 442 | } 443 | } 444 | 445 | mutating func apply(searchTerm: String) { 446 | createQueryItemsIfNeeded() 447 | queryItems?.append(URLQueryItem(name: AppleMusicApi.termParameter, value: searchTerm.replaceSpacesWithPluses())) 448 | } 449 | 450 | mutating func apply(mediaTypes: [MediaType]?) { 451 | guard let mediaTypes = mediaTypes else { return } 452 | 453 | createQueryItemsIfNeeded() 454 | queryItems?.append(URLQueryItem(name: AppleMusicApi.typesParameter, value: mediaTypes.map { $0.rawValue }.joined(separator: ","))) 455 | } 456 | 457 | mutating func apply(isrc: String){ 458 | createQueryItemsIfNeeded() 459 | queryItems?.append(URLQueryItem(name: AppleMusicApi.filterParameter, value: "\(isrc)")) 460 | } 461 | 462 | mutating func apply(limit: Int?) { 463 | guard let limit = limit else { return } 464 | 465 | createQueryItemsIfNeeded() 466 | queryItems?.append(URLQueryItem(name: AppleMusicApi.limitParameter, value: "\(limit)")) 467 | } 468 | 469 | mutating func apply(offset: Int?) { 470 | guard let offset = offset else { return } 471 | 472 | createQueryItemsIfNeeded() 473 | queryItems?.append(URLQueryItem(name: AppleMusicApi.offsetParameter, value: "\(offset)")) 474 | } 475 | 476 | mutating func apply(ids: [String]) { 477 | createQueryItemsIfNeeded() 478 | queryItems?.append(URLQueryItem(name: AppleMusicApi.ids, value: ids.map { $0 }.joined(separator: ","))) 479 | } 480 | 481 | mutating func apply(include: [Include]?) { 482 | guard let include = include else { return } 483 | 484 | createQueryItemsIfNeeded() 485 | queryItems?.append(URLQueryItem(name: AppleMusicApi.fetchInclude, value: include.map { $0.rawValue }.joined(separator: ","))) 486 | } 487 | } 488 | -------------------------------------------------------------------------------- /ios/Cider/Client/Include.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Include.swift 3 | // Cider 4 | // 5 | // Created by Scott Hoyt on 8/9/17. 6 | // Copyright © 2017 Scott Hoyt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Relationships to include with a lookup request. 12 | public enum Include: String { 13 | case albums 14 | case artists 15 | case genres 16 | case tracks 17 | } 18 | -------------------------------------------------------------------------------- /ios/Cider/Client/UrlFetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UrlFetcher.swift 3 | // Cider 4 | // 5 | // Created by Scott Hoyt on 8/27/17. 6 | // Copyright © 2017 Scott Hoyt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// An abstraction for `URL` loading. `URLSession` conforms and other mechanisms for `URL` loading (e.g. Alamofire) can be used by providing conformance. 12 | public protocol UrlFetcher { 13 | func fetch(request: URLRequest, completion: @escaping (Data?, Error?, URLResponse?) -> Void) 14 | } 15 | 16 | extension URLSession: UrlFetcher { 17 | public func fetch(request: URLRequest, completion: @escaping (Data?, Error?, URLResponse?) -> Void) { 18 | let task = dataTask(with: request) { data, response, error in 19 | completion(data, error, response) 20 | } 21 | task.resume() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Cider/JsonModels/LibraryPlaylistCreationRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LibraryPlaylistCreationRequest.swift 3 | // ReactNativeAppleMusic 4 | // 5 | // Created by Janik Steegmüller on 19.04.20. 6 | // Copyright © 2020 Facebook. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct LibraryPlaylistCreationRequest: Codable { 12 | struct Attributes: Codable { 13 | var description: String 14 | var name: String 15 | } 16 | struct Relationships: Codable { 17 | var tracks: Data 18 | } 19 | struct Data: Codable { 20 | var data: Array 21 | } 22 | struct LibraryPlaylistRequestTrack: Codable { 23 | var id: String 24 | var type: MediaType 25 | } 26 | var attributes: Attributes 27 | var relationships: Relationships 28 | } 29 | -------------------------------------------------------------------------------- /ios/Cider/JsonModels/LibraryPlaylistRequestTrack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LibraryPlaylistRequestTrack.swift 3 | // react-native-apple-music 4 | // 5 | // Created by Janik Steegmüller on 21.03.20. 6 | // 7 | 8 | import Foundation 9 | 10 | struct LibraryPlaylistRequestTrack: Codable { 11 | struct Media: Codable { 12 | var id: String 13 | var type: MediaType 14 | } 15 | 16 | var data: Array 17 | } 18 | -------------------------------------------------------------------------------- /ios/Cider/ResourceObjects/MediaType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MediaType.swift 3 | // Cider 4 | // 5 | // Created by Scott Hoyt on 8/2/17. 6 | // Copyright © 2017 Scott Hoyt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum MediaType: String, Codable { 12 | case albums 13 | case songs 14 | case artists 15 | case playlists 16 | case musicVideos = "music-videos" 17 | case curators 18 | case appleCurators = "apple-curators" 19 | } 20 | -------------------------------------------------------------------------------- /ios/Cider/ResourceObjects/Storefront.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Storefront.swift 3 | // Cider 4 | // 5 | // Created by Scott Hoyt on 8/27/17. 6 | // Copyright © 2017 Scott Hoyt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum Storefront: String, Codable { 12 | /// Anguilla 13 | case anguilla = "ai" 14 | 15 | /// Antigua and Barbuda 16 | case antiguaAndBarbuda = "ag" 17 | 18 | /// Argentina 19 | case argentina = "ar" 20 | 21 | /// Armenia 22 | case armenia = "am" 23 | 24 | /// Australia 25 | case australia = "au" 26 | 27 | /// Austria 28 | case austria = "at" 29 | 30 | /// Azerbaijan 31 | case azerbaijan = "az" 32 | 33 | /// Bahrain 34 | case bahrain = "bh" 35 | 36 | /// Barbados 37 | case barbados = "bb" 38 | 39 | /// Belarus 40 | case belarus = "by" 41 | 42 | /// Belgium 43 | case belgium = "be" 44 | 45 | /// Belize 46 | case belize = "bz" 47 | 48 | /// Bermuda 49 | case bermuda = "bm" 50 | 51 | /// Bolivia 52 | case bolivia = "bo" 53 | 54 | /// Botswana 55 | case botswana = "bw" 56 | 57 | /// Brazil 58 | case brazil = "br" 59 | 60 | /// British Virgin Islands 61 | case britishVirginIslands = "vg" 62 | 63 | /// Bulgaria 64 | case bulgaria = "bg" 65 | 66 | /// Cambodia 67 | case cambodia = "kh" 68 | 69 | /// Canada 70 | case canada = "ca" 71 | 72 | /// Cape Verde 73 | case capeVerde = "cv" 74 | 75 | /// Cayman Islands 76 | case caymanIslands = "ky" 77 | 78 | /// Chile 79 | case chile = "cl" 80 | 81 | /// China 82 | case china = "cn" 83 | 84 | /// Colombia 85 | case colombia = "co" 86 | 87 | /// Costa Rica 88 | case costaRica = "cr" 89 | 90 | /// Cyprus 91 | case cyprus = "cy" 92 | 93 | /// Czech Republic 94 | case czechRepublic = "cz" 95 | 96 | /// Denmark 97 | case denmark = "dk" 98 | 99 | /// Dominica 100 | case dominica = "dm" 101 | 102 | /// Dominican Republic 103 | case dominicanRepublic = "do" 104 | 105 | /// Ecuador 106 | case ecuador = "ec" 107 | 108 | /// Egypt 109 | case egypt = "eg" 110 | 111 | /// El Salvador 112 | case elSalvador = "sv" 113 | 114 | /// Estonia 115 | case estonia = "ee" 116 | 117 | /// Fiji 118 | case fiji = "fj" 119 | 120 | /// Finland 121 | case finland = "fi" 122 | 123 | /// France 124 | case france = "fr" 125 | 126 | /// Gambia 127 | case gambia = "gm" 128 | 129 | /// Germany 130 | case germany = "de" 131 | 132 | /// Ghana 133 | case ghana = "gh" 134 | 135 | /// Greece 136 | case greece = "gr" 137 | 138 | /// Grenada 139 | case grenada = "gd" 140 | 141 | /// Guatemala 142 | case guatemala = "gt" 143 | 144 | /// Guinea-Bissau 145 | case guineaBissau = "gw" 146 | 147 | /// Honduras 148 | case honduras = "hn" 149 | 150 | /// Hong Kong 151 | case hongKong = "hk" 152 | 153 | /// Hungary 154 | case hungary = "hu" 155 | 156 | /// India 157 | case india = "in" 158 | 159 | /// Indonesia 160 | case indonesia = "id" 161 | 162 | /// Ireland 163 | case ireland = "ie" 164 | 165 | /// Israel 166 | case israel = "il" 167 | 168 | /// Italy 169 | case italy = "it" 170 | 171 | /// Japan 172 | case japan = "jp" 173 | 174 | /// Jordan 175 | case jordan = "jo" 176 | 177 | /// Kazakhstan 178 | case kazakhstan = "kz" 179 | 180 | /// Kenya 181 | case kenya = "ke" 182 | 183 | /// Korea, Republic of 184 | case koreaRepublicOf = "kr" 185 | 186 | /// Kyrgyzstan 187 | case kyrgyzstan = "kg" 188 | 189 | /// Lao People's Democratic Republic 190 | case laoPeoplesDemocraticRepublic = "la" 191 | 192 | /// Latvia 193 | case latvia = "lv" 194 | 195 | /// Lebanon 196 | case lebanon = "lb" 197 | 198 | /// Lithuania 199 | case lithuania = "lt" 200 | 201 | /// Luxembourg 202 | case luxembourg = "lu" 203 | 204 | /// Macau 205 | case macau = "mo" 206 | 207 | /// Malaysia 208 | case malaysia = "my" 209 | 210 | /// Malta 211 | case malta = "mt" 212 | 213 | /// Mauritius 214 | case mauritius = "mu" 215 | 216 | /// Mexico 217 | case mexico = "mx" 218 | 219 | /// Micronesia, Federated States of 220 | case micronesiaFederatedStatesOf = "fm" 221 | 222 | /// Moldova 223 | case moldova = "md" 224 | 225 | /// Mongolia 226 | case mongolia = "mn" 227 | 228 | /// Nepal 229 | case nepal = "np" 230 | 231 | /// Netherlands 232 | case netherlands = "nl" 233 | 234 | /// New Zealand 235 | case newZealand = "nz" 236 | 237 | /// Nicaragua 238 | case nicaragua = "ni" 239 | 240 | /// Niger 241 | case niger = "ne" 242 | 243 | /// Nigeria 244 | case nigeria = "ng" 245 | 246 | /// Norway 247 | case norway = "no" 248 | 249 | /// Oman 250 | case oman = "om" 251 | 252 | /// Panama 253 | case panama = "pa" 254 | 255 | /// Papua New Guinea 256 | case papuaNewGuinea = "pg" 257 | 258 | /// Paraguay 259 | case paraguay = "py" 260 | 261 | /// Peru 262 | case peru = "pe" 263 | 264 | /// Philippines 265 | case philippines = "ph" 266 | 267 | /// Poland 268 | case poland = "pl" 269 | 270 | /// Portugal 271 | case portugal = "pt" 272 | 273 | /// Romania 274 | case romania = "ro" 275 | 276 | /// Russia 277 | case russia = "ru" 278 | 279 | /// Saudi Arabia 280 | case saudiArabia = "sa" 281 | 282 | /// Singapore 283 | case singapore = "sg" 284 | 285 | /// Slovakia 286 | case slovakia = "sk" 287 | 288 | /// Slovenia 289 | case slovenia = "si" 290 | 291 | /// South Africa 292 | case southAfrica = "za" 293 | 294 | /// Spain 295 | case spain = "es" 296 | 297 | /// Sri Lanka 298 | case sriLanka = "lk" 299 | 300 | /// St. Kitts and Nevis 301 | case stKittsAndNevis = "kn" 302 | 303 | /// Swaziland 304 | case swaziland = "sz" 305 | 306 | /// Sweden 307 | case sweden = "se" 308 | 309 | /// Switzerland 310 | case switzerland = "ch" 311 | 312 | /// Taiwan 313 | case taiwan = "tw" 314 | 315 | /// Tajikistan 316 | case tajikistan = "tj" 317 | 318 | /// Thailand 319 | case thailand = "th" 320 | 321 | /// Trinidad and Tobago 322 | case trinidadAndTobago = "tt" 323 | 324 | /// Turkey 325 | case turkey = "tr" 326 | 327 | /// Turkmenistan 328 | case turkmenistan = "tm" 329 | 330 | /// UAE 331 | case uae = "ae" 332 | 333 | /// Uganda 334 | case uganda = "ug" 335 | 336 | /// Ukraine 337 | case ukraine = "ua" 338 | 339 | /// United Kingdom 340 | case unitedKingdom = "gb" 341 | 342 | /// United States 343 | case unitedStates = "us" 344 | 345 | /// Uzbekistan 346 | case uzbekistan = "uz" 347 | 348 | /// Venezuela 349 | case venezuela = "ve" 350 | 351 | /// Vietnam 352 | case vietnam = "vn" 353 | 354 | /// Zimbabwe 355 | case zimbabwe = "zw" 356 | } 357 | -------------------------------------------------------------------------------- /ios/ReactNativeAppleMusic-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | #import 5 | #import 6 | #import 7 | #import 8 | #import 9 | #import 10 | #import 11 | -------------------------------------------------------------------------------- /ios/ReactNativeAppleMusic.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 497DE230241ABD4B002FE1A4 /* AppleMusicApiError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497DE22D241ABD4B002FE1A4 /* AppleMusicApiError.swift */; }; 11 | 497DE231241ABD4B002FE1A4 /* AppleMusicAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = 497DE22E241ABD4B002FE1A4 /* AppleMusicAPI.m */; }; 12 | 497DE232241ABD4B002FE1A4 /* AppleMusicAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497DE22F241ABD4B002FE1A4 /* AppleMusicAPI.swift */; }; 13 | 497DE27E241AE9D3002FE1A4 /* Storefront.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497DE277241AE9D3002FE1A4 /* Storefront.swift */; }; 14 | 497DE27F241AE9D3002FE1A4 /* MediaType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497DE278241AE9D3002FE1A4 /* MediaType.swift */; }; 15 | 497DE280241AE9D3002FE1A4 /* CiderUrlBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497DE27A241AE9D3002FE1A4 /* CiderUrlBuilder.swift */; }; 16 | 497DE281241AE9D3002FE1A4 /* Include.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497DE27B241AE9D3002FE1A4 /* Include.swift */; }; 17 | 497DE282241AE9D3002FE1A4 /* UrlFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497DE27C241AE9D3002FE1A4 /* UrlFetcher.swift */; }; 18 | 497DE283241AE9D3002FE1A4 /* CiderClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497DE27D241AE9D3002FE1A4 /* CiderClient.swift */; }; 19 | 498DC8E6244C5ACF006A87D6 /* LibraryPlaylistCreationRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498DC8E5244C5ACF006A87D6 /* LibraryPlaylistCreationRequest.swift */; }; 20 | 49AD87D62426317B00C771F3 /* LibraryPlaylistRequestTrack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49AD87D52426317B00C771F3 /* LibraryPlaylistRequestTrack.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXCopyFilesBuildPhase section */ 24 | 58B511D91A9E6C8500147676 /* Copy Files */ = { 25 | isa = PBXCopyFilesBuildPhase; 26 | buildActionMask = 2147483647; 27 | dstPath = "include/$(PRODUCT_NAME)"; 28 | dstSubfolderSpec = 16; 29 | files = ( 30 | ); 31 | name = "Copy Files"; 32 | runOnlyForDeploymentPostprocessing = 0; 33 | }; 34 | /* End PBXCopyFilesBuildPhase section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | 134814201AA4EA6300B7C361 /* libReactNativeAppleMusic.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReactNativeAppleMusic.a; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 497DE22C241ABD4A002FE1A4 /* ReactNativeAppleMusic-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ReactNativeAppleMusic-Bridging-Header.h"; sourceTree = ""; }; 39 | 497DE22D241ABD4B002FE1A4 /* AppleMusicApiError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleMusicApiError.swift; sourceTree = ""; }; 40 | 497DE22E241ABD4B002FE1A4 /* AppleMusicAPI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppleMusicAPI.m; sourceTree = ""; }; 41 | 497DE22F241ABD4B002FE1A4 /* AppleMusicAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleMusicAPI.swift; sourceTree = ""; }; 42 | 497DE277241AE9D3002FE1A4 /* Storefront.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Storefront.swift; sourceTree = ""; }; 43 | 497DE278241AE9D3002FE1A4 /* MediaType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaType.swift; sourceTree = ""; }; 44 | 497DE27A241AE9D3002FE1A4 /* CiderUrlBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CiderUrlBuilder.swift; sourceTree = ""; }; 45 | 497DE27B241AE9D3002FE1A4 /* Include.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Include.swift; sourceTree = ""; }; 46 | 497DE27C241AE9D3002FE1A4 /* UrlFetcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UrlFetcher.swift; sourceTree = ""; }; 47 | 497DE27D241AE9D3002FE1A4 /* CiderClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CiderClient.swift; sourceTree = ""; }; 48 | 498DC8E5244C5ACF006A87D6 /* LibraryPlaylistCreationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryPlaylistCreationRequest.swift; sourceTree = ""; }; 49 | 49AD87D52426317B00C771F3 /* LibraryPlaylistRequestTrack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryPlaylistRequestTrack.swift; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | ); 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | /* End PBXFrameworksBuildPhase section */ 61 | 62 | /* Begin PBXGroup section */ 63 | 134814211AA4EA7D00B7C361 /* Products */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | 134814201AA4EA6300B7C361 /* libReactNativeAppleMusic.a */, 67 | ); 68 | name = Products; 69 | sourceTree = ""; 70 | }; 71 | 497DE275241AE9D3002FE1A4 /* Cider */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 497DE276241AE9D3002FE1A4 /* ResourceObjects */, 75 | 497DE279241AE9D3002FE1A4 /* Client */, 76 | ); 77 | path = Cider; 78 | sourceTree = ""; 79 | }; 80 | 497DE276241AE9D3002FE1A4 /* ResourceObjects */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 497DE277241AE9D3002FE1A4 /* Storefront.swift */, 84 | 497DE278241AE9D3002FE1A4 /* MediaType.swift */, 85 | ); 86 | path = ResourceObjects; 87 | sourceTree = ""; 88 | }; 89 | 497DE279241AE9D3002FE1A4 /* Client */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 497DE27A241AE9D3002FE1A4 /* CiderUrlBuilder.swift */, 93 | 497DE27B241AE9D3002FE1A4 /* Include.swift */, 94 | 497DE27C241AE9D3002FE1A4 /* UrlFetcher.swift */, 95 | 497DE27D241AE9D3002FE1A4 /* CiderClient.swift */, 96 | ); 97 | path = Client; 98 | sourceTree = ""; 99 | }; 100 | 49AD87D42426317B00C771F3 /* JsonModels */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 49AD87D52426317B00C771F3 /* LibraryPlaylistRequestTrack.swift */, 104 | 498DC8E5244C5ACF006A87D6 /* LibraryPlaylistCreationRequest.swift */, 105 | ); 106 | name = JsonModels; 107 | path = Cider/JsonModels; 108 | sourceTree = ""; 109 | }; 110 | 58B511D21A9E6C8500147676 = { 111 | isa = PBXGroup; 112 | children = ( 113 | 49AD87D42426317B00C771F3 /* JsonModels */, 114 | 497DE275241AE9D3002FE1A4 /* Cider */, 115 | 497DE22E241ABD4B002FE1A4 /* AppleMusicAPI.m */, 116 | 497DE22F241ABD4B002FE1A4 /* AppleMusicAPI.swift */, 117 | 497DE22D241ABD4B002FE1A4 /* AppleMusicApiError.swift */, 118 | 497DE22C241ABD4A002FE1A4 /* ReactNativeAppleMusic-Bridging-Header.h */, 119 | 134814211AA4EA7D00B7C361 /* Products */, 120 | ); 121 | sourceTree = ""; 122 | }; 123 | /* End PBXGroup section */ 124 | 125 | /* Begin PBXNativeTarget section */ 126 | 58B511DA1A9E6C8500147676 /* ReactNativeAppleMusic */ = { 127 | isa = PBXNativeTarget; 128 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "ReactNativeAppleMusic" */; 129 | buildPhases = ( 130 | 58B511D71A9E6C8500147676 /* Sources */, 131 | 58B511D81A9E6C8500147676 /* Frameworks */, 132 | 58B511D91A9E6C8500147676 /* Copy Files */, 133 | ); 134 | buildRules = ( 135 | ); 136 | dependencies = ( 137 | ); 138 | name = ReactNativeAppleMusic; 139 | productName = RCTDataManager; 140 | productReference = 134814201AA4EA6300B7C361 /* libReactNativeAppleMusic.a */; 141 | productType = "com.apple.product-type.library.static"; 142 | }; 143 | /* End PBXNativeTarget section */ 144 | 145 | /* Begin PBXProject section */ 146 | 58B511D31A9E6C8500147676 /* Project object */ = { 147 | isa = PBXProject; 148 | attributes = { 149 | LastUpgradeCheck = 0920; 150 | ORGANIZATIONNAME = Facebook; 151 | TargetAttributes = { 152 | 58B511DA1A9E6C8500147676 = { 153 | CreatedOnToolsVersion = 6.1.1; 154 | LastSwiftMigration = 1130; 155 | }; 156 | }; 157 | }; 158 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "ReactNativeAppleMusic" */; 159 | compatibilityVersion = "Xcode 3.2"; 160 | developmentRegion = English; 161 | hasScannedForEncodings = 0; 162 | knownRegions = ( 163 | English, 164 | en, 165 | ); 166 | mainGroup = 58B511D21A9E6C8500147676; 167 | productRefGroup = 58B511D21A9E6C8500147676; 168 | projectDirPath = ""; 169 | projectRoot = ""; 170 | targets = ( 171 | 58B511DA1A9E6C8500147676 /* ReactNativeAppleMusic */, 172 | ); 173 | }; 174 | /* End PBXProject section */ 175 | 176 | /* Begin PBXSourcesBuildPhase section */ 177 | 58B511D71A9E6C8500147676 /* Sources */ = { 178 | isa = PBXSourcesBuildPhase; 179 | buildActionMask = 2147483647; 180 | files = ( 181 | 498DC8E6244C5ACF006A87D6 /* LibraryPlaylistCreationRequest.swift in Sources */, 182 | 497DE283241AE9D3002FE1A4 /* CiderClient.swift in Sources */, 183 | 497DE27F241AE9D3002FE1A4 /* MediaType.swift in Sources */, 184 | 497DE281241AE9D3002FE1A4 /* Include.swift in Sources */, 185 | 497DE230241ABD4B002FE1A4 /* AppleMusicApiError.swift in Sources */, 186 | 497DE27E241AE9D3002FE1A4 /* Storefront.swift in Sources */, 187 | 497DE232241ABD4B002FE1A4 /* AppleMusicAPI.swift in Sources */, 188 | 497DE231241ABD4B002FE1A4 /* AppleMusicAPI.m in Sources */, 189 | 49AD87D62426317B00C771F3 /* LibraryPlaylistRequestTrack.swift in Sources */, 190 | 497DE280241AE9D3002FE1A4 /* CiderUrlBuilder.swift in Sources */, 191 | 497DE282241AE9D3002FE1A4 /* UrlFetcher.swift in Sources */, 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | }; 195 | /* End PBXSourcesBuildPhase section */ 196 | 197 | /* Begin XCBuildConfiguration section */ 198 | 58B511ED1A9E6C8500147676 /* Debug */ = { 199 | isa = XCBuildConfiguration; 200 | buildSettings = { 201 | ALWAYS_SEARCH_USER_PATHS = NO; 202 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 203 | CLANG_CXX_LIBRARY = "libc++"; 204 | CLANG_ENABLE_MODULES = YES; 205 | CLANG_ENABLE_OBJC_ARC = YES; 206 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 207 | CLANG_WARN_BOOL_CONVERSION = YES; 208 | CLANG_WARN_COMMA = YES; 209 | CLANG_WARN_CONSTANT_CONVERSION = YES; 210 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 211 | CLANG_WARN_EMPTY_BODY = YES; 212 | CLANG_WARN_ENUM_CONVERSION = YES; 213 | CLANG_WARN_INFINITE_RECURSION = YES; 214 | CLANG_WARN_INT_CONVERSION = YES; 215 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 216 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 217 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 218 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 219 | CLANG_WARN_STRICT_PROTOTYPES = YES; 220 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 221 | CLANG_WARN_UNREACHABLE_CODE = YES; 222 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 223 | COPY_PHASE_STRIP = NO; 224 | ENABLE_STRICT_OBJC_MSGSEND = YES; 225 | ENABLE_TESTABILITY = YES; 226 | GCC_C_LANGUAGE_STANDARD = gnu99; 227 | GCC_DYNAMIC_NO_PIC = NO; 228 | GCC_NO_COMMON_BLOCKS = YES; 229 | GCC_OPTIMIZATION_LEVEL = 0; 230 | GCC_PREPROCESSOR_DEFINITIONS = ( 231 | "DEBUG=1", 232 | "$(inherited)", 233 | ); 234 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 235 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 236 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 237 | GCC_WARN_UNDECLARED_SELECTOR = YES; 238 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 239 | GCC_WARN_UNUSED_FUNCTION = YES; 240 | GCC_WARN_UNUSED_VARIABLE = YES; 241 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 242 | MTL_ENABLE_DEBUG_INFO = YES; 243 | ONLY_ACTIVE_ARCH = YES; 244 | SDKROOT = iphoneos; 245 | }; 246 | name = Debug; 247 | }; 248 | 58B511EE1A9E6C8500147676 /* Release */ = { 249 | isa = XCBuildConfiguration; 250 | buildSettings = { 251 | ALWAYS_SEARCH_USER_PATHS = NO; 252 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 253 | CLANG_CXX_LIBRARY = "libc++"; 254 | CLANG_ENABLE_MODULES = YES; 255 | CLANG_ENABLE_OBJC_ARC = YES; 256 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 257 | CLANG_WARN_BOOL_CONVERSION = YES; 258 | CLANG_WARN_COMMA = YES; 259 | CLANG_WARN_CONSTANT_CONVERSION = YES; 260 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 261 | CLANG_WARN_EMPTY_BODY = YES; 262 | CLANG_WARN_ENUM_CONVERSION = YES; 263 | CLANG_WARN_INFINITE_RECURSION = YES; 264 | CLANG_WARN_INT_CONVERSION = YES; 265 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 266 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 267 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 268 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 269 | CLANG_WARN_STRICT_PROTOTYPES = YES; 270 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 271 | CLANG_WARN_UNREACHABLE_CODE = YES; 272 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 273 | COPY_PHASE_STRIP = YES; 274 | ENABLE_NS_ASSERTIONS = NO; 275 | ENABLE_STRICT_OBJC_MSGSEND = YES; 276 | GCC_C_LANGUAGE_STANDARD = gnu99; 277 | GCC_NO_COMMON_BLOCKS = YES; 278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 280 | GCC_WARN_UNDECLARED_SELECTOR = YES; 281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 282 | GCC_WARN_UNUSED_FUNCTION = YES; 283 | GCC_WARN_UNUSED_VARIABLE = YES; 284 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 285 | MTL_ENABLE_DEBUG_INFO = NO; 286 | SDKROOT = iphoneos; 287 | VALIDATE_PRODUCT = YES; 288 | }; 289 | name = Release; 290 | }; 291 | 58B511F01A9E6C8500147676 /* Debug */ = { 292 | isa = XCBuildConfiguration; 293 | buildSettings = { 294 | CLANG_ENABLE_MODULES = YES; 295 | HEADER_SEARCH_PATHS = ( 296 | "$(inherited)", 297 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 298 | "$(SRCROOT)/../../../React/**", 299 | "$(SRCROOT)/../../react-native/React/**", 300 | ); 301 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 302 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 303 | OTHER_LDFLAGS = "-ObjC"; 304 | PRODUCT_NAME = ReactNativeAppleMusic; 305 | SKIP_INSTALL = YES; 306 | SWIFT_OBJC_BRIDGING_HEADER = "ReactNativeAppleMusic-Bridging-Header.h"; 307 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 308 | SWIFT_VERSION = 5.0; 309 | }; 310 | name = Debug; 311 | }; 312 | 58B511F11A9E6C8500147676 /* Release */ = { 313 | isa = XCBuildConfiguration; 314 | buildSettings = { 315 | CLANG_ENABLE_MODULES = YES; 316 | HEADER_SEARCH_PATHS = ( 317 | "$(inherited)", 318 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 319 | "$(SRCROOT)/../../../React/**", 320 | "$(SRCROOT)/../../react-native/React/**", 321 | ); 322 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 323 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 324 | OTHER_LDFLAGS = "-ObjC"; 325 | PRODUCT_NAME = ReactNativeAppleMusic; 326 | SKIP_INSTALL = YES; 327 | SWIFT_OBJC_BRIDGING_HEADER = "ReactNativeAppleMusic-Bridging-Header.h"; 328 | SWIFT_VERSION = 5.0; 329 | }; 330 | name = Release; 331 | }; 332 | /* End XCBuildConfiguration section */ 333 | 334 | /* Begin XCConfigurationList section */ 335 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "ReactNativeAppleMusic" */ = { 336 | isa = XCConfigurationList; 337 | buildConfigurations = ( 338 | 58B511ED1A9E6C8500147676 /* Debug */, 339 | 58B511EE1A9E6C8500147676 /* Release */, 340 | ); 341 | defaultConfigurationIsVisible = 0; 342 | defaultConfigurationName = Release; 343 | }; 344 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "ReactNativeAppleMusic" */ = { 345 | isa = XCConfigurationList; 346 | buildConfigurations = ( 347 | 58B511F01A9E6C8500147676 /* Debug */, 348 | 58B511F11A9E6C8500147676 /* Release */, 349 | ); 350 | defaultConfigurationIsVisible = 0; 351 | defaultConfigurationName = Release; 352 | }; 353 | /* End XCConfigurationList section */ 354 | }; 355 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 356 | } 357 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bouncyapp/react-native-apple-music", 3 | "title": "React Native Apple Music", 4 | "version": "1.0.3", 5 | "description": "A react native module for the Apple Music API", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+ssh://git@github.com/Lemonadd-UG/react-native-apple-music.git", 13 | "baseUrl": "https://github.com/Lemonadd-UG/react-native-apple-music" 14 | }, 15 | "keywords": [ 16 | "react-native", 17 | "ios", 18 | "apple music", 19 | "apple music api", 20 | "music", 21 | "react-native-apple-music", 22 | "apple" 23 | ], 24 | "author": "Bouncy", 25 | "license": "MIT", 26 | "licenseFilename": "LICENSE", 27 | "peerDependencies": { 28 | "react": "^16.8.1", 29 | "react-native": ">=0.60.0-rc.0 <1.0.x" 30 | }, 31 | "devDependencies": { 32 | "react": "^16.9.0", 33 | "react-native": "^0.61.5" 34 | }, 35 | "bugs": { 36 | "url": "https://github.com/Lemonadd-UG/react-native-apple-music/issues" 37 | }, 38 | "homepage": "https://github.com/Lemonadd-UG/react-native-apple-music#readme" 39 | } 40 | -------------------------------------------------------------------------------- /react-native-apple-music.podspec: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = "react-native-apple-music" 7 | s.version = package["version"] 8 | s.summary = package["description"] 9 | s.description = <<-DESC 10 | react-native-apple-music 11 | DESC 12 | s.homepage = "https://github.com/Lemonadd-UG/react-native-apple-music" 13 | s.license = "MIT" 14 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" } 15 | s.authors = { "Your Name" => "yourname@email.com" } 16 | s.platforms = { :ios => "11.0" } 17 | s.source = { :git => "https://github.com/Lemonadd-UG/react-native-apple-music.git", :tag => "#{s.version}" } 18 | 19 | s.source_files = "ios/**/*.{h,m,swift}" 20 | s.requires_arc = true 21 | 22 | s.dependency "React" 23 | s.dependency "CupertinoJWT" 24 | s.dependency "PromiseKit", '~> 6.8' 25 | # ... 26 | # s.dependency "..." 27 | end -------------------------------------------------------------------------------- /src/AppleMusic.js: -------------------------------------------------------------------------------- 1 | import { NativeModules, Platform } from 'react-native'; 2 | 3 | 4 | const AppleMusic = NativeModules.AppleMusicAPI; 5 | 6 | if (Platform.OS == 'ios') { 7 | 8 | AppleMusic.initialize = AppleMusic.setValsAndInit 9 | 10 | AppleMusic.getICloudID = AppleMusic.getUserRecordID 11 | 12 | AppleMusic.getSongCharts = AppleMusic.getCharts 13 | 14 | AppleMusic.getAlbumCharts = AppleMusic.getCharts 15 | 16 | AppleMusic.search = AppleMusic.searchForTerm 17 | 18 | AppleMusic.getUserPlaylists = AppleMusic.getAllUserPlaylists 19 | 20 | AppleMusic.recentPlayed = AppleMusic.getRecentPlayed 21 | 22 | AppleMusic.getSong = AppleMusic.getSong 23 | 24 | AppleMusic.getAlbum = AppleMusic.getAlbum 25 | 26 | AppleMusic.getArtist = AppleMusic.getArtist 27 | 28 | AppleMusic.getHeavyRotation = AppleMusic.getHeavyRotation 29 | 30 | AppleMusic.getRecommendations = AppleMusic.getUserRecommendations 31 | 32 | } 33 | export default AppleMusic; 34 | --------------------------------------------------------------------------------