├── .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 |
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 |
--------------------------------------------------------------------------------