diff --git a/App/Milkshake.dmg b/App/Milkshake.dmg index f3910ad..cf20b28 100644 Binary files a/App/Milkshake.dmg and b/App/Milkshake.dmg differ diff --git a/Milkshake.xcodeproj/project.pbxproj b/Milkshake.xcodeproj/project.pbxproj index 48d1a9f..ec3c987 100644 --- a/Milkshake.xcodeproj/project.pbxproj +++ b/Milkshake.xcodeproj/project.pbxproj @@ -79,6 +79,7 @@ 05ED21131FD6ED8F00123317 /* MusicItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05ED21121FD6ED8E00123317 /* MusicItem.swift */; }; 05ED21171FDA2CF600123317 /* album-500.png in Resources */ = {isa = PBXBuildFile; fileRef = 05ED21161FDA2CF600123317 /* album-500.png */; }; 05ED21191FDC423A00123317 /* FlatButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05ED21181FDC423A00123317 /* FlatButton.swift */; }; + 05F3C2B828A49F28007387A5 /* constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05F3C2B728A49F28007387A5 /* constants.swift */; }; 48F749471FCCEBD000F0A853 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F749461FCCEBD000F0A853 /* AppDelegate.swift */; }; 48F749491FCCEBD000F0A853 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F749481FCCEBD000F0A853 /* MainViewController.swift */; }; 48F7494B1FCCEBD000F0A853 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 48F7494A1FCCEBD000F0A853 /* Assets.xcassets */; }; @@ -164,6 +165,7 @@ 05ED21121FD6ED8E00123317 /* MusicItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicItem.swift; sourceTree = ""; }; 05ED21161FDA2CF600123317 /* album-500.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "album-500.png"; sourceTree = ""; }; 05ED21181FDC423A00123317 /* FlatButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlatButton.swift; sourceTree = ""; }; + 05F3C2B728A49F28007387A5 /* constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = constants.swift; sourceTree = ""; }; 48F749431FCCEBD000F0A853 /* Milkshake.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Milkshake.app; sourceTree = BUILT_PRODUCTS_DIR; }; 48F749461FCCEBD000F0A853 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 48F749481FCCEBD000F0A853 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; @@ -238,13 +240,6 @@ name = classes; sourceTree = ""; }; - 05ED21051FD3629A00123317 /* test */ = { - isa = PBXGroup; - children = ( - ); - name = test; - sourceTree = ""; - }; 153A7C2C805AFCDA84A9FF61 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -278,8 +273,8 @@ children = ( 05D3420628A34A320039D620 /* imported */, 05B86E091FE388B9009EEC4F /* Milkshake.entitlements */, - 05ED21051FD3629A00123317 /* test */, 48F749461FCCEBD000F0A853 /* AppDelegate.swift */, + 05F3C2B728A49F28007387A5 /* constants.swift */, 05ED20D31FCCFA3000123317 /* classes */, 48F749561FCCEBFF00F0A853 /* views */, 48F749551FCCEBF800F0A853 /* controllers */, @@ -578,6 +573,7 @@ 05ED20E51FCD182E00123317 /* Util.swift in Sources */, 05ED20FE1FCFABC200123317 /* SearchTableRowView.swift in Sources */, 05ED20FC1FCFA80F00123317 /* MyTableView.swift in Sources */, + 05F3C2B828A49F28007387A5 /* constants.swift in Sources */, 059C04B72027E6980007E456 /* TapManager.swift in Sources */, 054A311D1FF5E3190022D06E /* ArtTableCellView.swift in Sources */, 05ED20E71FCD2FBD00123317 /* SearchTableCellView.swift in Sources */, diff --git a/Milkshake.xcworkspace/xcuserdata/dean.xcuserdatad/UserInterfaceState.xcuserstate b/Milkshake.xcworkspace/xcuserdata/dean.xcuserdatad/UserInterfaceState.xcuserstate index 5399e77..57c28b0 100644 Binary files a/Milkshake.xcworkspace/xcuserdata/dean.xcuserdatad/UserInterfaceState.xcuserstate and b/Milkshake.xcworkspace/xcuserdata/dean.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Milkshake.xcworkspace/xcuserdata/dean.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Milkshake.xcworkspace/xcuserdata/dean.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 14fb356..08b0bbe 100644 --- a/Milkshake.xcworkspace/xcuserdata/dean.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Milkshake.xcworkspace/xcuserdata/dean.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -15,10 +15,10 @@ timestampString = "681811592.794281" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "114" - endingLineNumber = "114" - landmarkName = "unknown" - landmarkType = "0"> + startingLineNumber = "103" + endingLineNumber = "103" + landmarkName = "request(_:params:callbackHandler:)" + landmarkType = "7"> - - - - + startingLineNumber = "150" + endingLineNumber = "150" + landmarkName = "auth(username:pass:callbackHandler:)" + landmarkType = "7"> + startingLineNumber = "193" + endingLineNumber = "193" + landmarkName = "partnerAuthUserLogin(username:password:partnerAuthToken:partnerId:syncTime:callbackHandler:)" + landmarkType = "7"> @@ -100,5 +83,21 @@ type = "4"> + + + + diff --git a/Milkshake/API.swift b/Milkshake/API.swift index 18f566d..ee63047 100644 --- a/Milkshake/API.swift +++ b/Milkshake/API.swift @@ -21,46 +21,36 @@ class API: NSObject { var base_cookie = "_ga=GA1.2.34567890.1234567890;csrftoken=0123456789abcdef;_gid=GA1.2.1234567890.1234567890; _uetsid=_uetff68c25a;" var cookies = "" + // Used for parnerAuth and userAuth login func requestTuner(_ url:String, params:[String: Any], encrypted:Bool, callbackHandler: @escaping(_ Dictionary:[String:AnyObject]) -> ()) { - + var jsonData: Data; + do { + jsonData = try JSONSerialization.data(withJSONObject: params) + } catch { + print(error.localizedDescription) + //XXX Call error + return + } + var request = URLRequest(url: URL(string: url)!) + request.httpMethod = HTTPMethod.post.rawValue + request.setValue("application/json", forHTTPHeaderField: "Content-Type") if encrypted { - var jsonData2: Data; - do { - jsonData2 = try JSONSerialization.data(withJSONObject: params) - } catch { - print(error.localizedDescription) - //XXX Call error - return - } - var request = URLRequest(url: URL(string: url)!) - request.httpMethod = HTTPMethod.post.rawValue - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - - let encryptedBody = PandoraEncryptData(jsonData2, "6#26FRL$ZWD") + let encryptedBody = PandoraEncryptData(jsonData, Constants.encryptPassword) request.httpBody = encryptedBody - - Alamofire.request(request).responseJSON { (response) in - callbackHandler((response.result.value as? [String: AnyObject])!) - } - } else { - Alamofire.request( - url, - method: .post, - parameters: params, - encoding: JSONEncoding.default - // headers: headers - ) - .responseJSON { response in - if let responseValue = response.result.value { - print(responseValue) - let rv = responseValue as! [String: AnyObject] - if (rv["stat"] as? String) == "ok" { - callbackHandler((response.result.value as? [String: AnyObject])!) - } + } + else { + request.httpBody = jsonData + } + Alamofire.request(request).responseJSON { response in + if let responseValue = response.result.value { + let rv = responseValue as! [String: AnyObject] + if (rv["stat"] as? String) == "ok" { + callbackHandler((response.result.value as? [String: AnyObject])!) } } } } + // Base network request function called by all API methods // footnote (fn1) If the X_Auth token is expired, repeat the request once. // footnote (fn2) If a request fails, repeat the request once @@ -86,7 +76,6 @@ class API: NSObject { headers: headers ) .responseJSON { response in - if let responseValue = response.result.value { if let responseCookie = HTTPCookieStorage.shared.cookies { self.parseCookie(cookies: responseCookie) @@ -160,7 +149,7 @@ class API: NSObject { "password": pass, "keepLoggedIn": NSNumber(value: true) ] - let url: String = "https://www.pandora.com/api/v1/auth/login" + let url: String = "\(Constants.pandoraApiUrlV1)/auth/login" self.request(url, params: parameters, callbackHandler: callbackHandler) } @@ -171,19 +160,19 @@ class API: NSObject { "password": "", "keepLoggedIn": true ] - let url: String = "https://www.pandora.com/api/v1/auth/login" + let url: String = "\(Constants.pandoraApiUrlV1)/auth/login" self.request(url, params: params, callbackHandler: callbackHandler) } func partnerAuthPartnerLogin(callbackHandler:@escaping(_ Dictionary:[String:AnyObject]) ->()){ let params: [String: String] = [ - "username": "android", - "password": "AC7IBG09A3DTSYM4R41UJWL07VLN8JI7", - "deviceModel": "android-generic", - "version": "5", + "username": Constants.username, + "password": Constants.password, + "deviceModel": Constants.deviceModel, + "version": Constants.version, ] - let url: String = "https://tuner.pandora.com:443/services/json/?method=auth.partnerLogin" + let url: String = "\(Constants.tunerUrl)?method=auth.partnerLogin" self.requestTuner(url, params: params, encrypted: false, callbackHandler: callbackHandler) } @@ -194,10 +183,8 @@ class API: NSObject { "password": password, "partnerAuthToken": partnerAuthToken, "syncTime": syncTime, -// "returnIsSubscriber": true, ] - var urlParams = URLComponents(string: "https://tuner.pandora.com/")! - urlParams.path = "/services/json/" + var urlParams = URLComponents(string: "\(Constants.tunerUrl)")! urlParams.queryItems = [ URLQueryItem(name: "method", value: "auth.userLogin"), URLQueryItem(name: "auth_token", value: partnerAuthToken), @@ -211,7 +198,7 @@ class API: NSObject { let params: [String: Any] = [ "forceActive": true ] - let url: String = "https://www.pandora.com/api/v1/station/playbackResumed" + let url: String = "\(Constants.pandoraApiUrlV1)/station/playbackResumed" self.request(url, params: params, callbackHandler: callbackHandler) } @@ -240,7 +227,7 @@ class API: NSObject { // "pandoraId": pid, // "sourcePandoraId": sid, // ] -// self.request("https://www.pandora.com/api/v1/ondemand/getAudioPlaybackInfo", params: params, callbackHandler: callbackHandler) +// self.request("\(Constants.pandoraApiUrlV1)/ondemand/getAudioPlaybackInfo", params: params, callbackHandler: callbackHandler) // } func getAudioPlaybackInfoPandoraId(item:MusicItem, callbackHandler: @escaping(_ Dictionary:[String:AnyObject]) -> ()) { @@ -248,7 +235,7 @@ class API: NSObject { "pandoraId": item.pandoraId!, "sourcePandoraId": item.albumId!, ] - self.request("https://www.pandora.com/api/v1/ondemand/getAudioPlaybackInfo", params: params) { (response) in + self.request("\(Constants.pandoraApiUrlV1)/ondemand/getAudioPlaybackInfo", params: params) { (response) in let returnDict: [String: Any] = [ "musicItem": item, "response": response @@ -265,7 +252,7 @@ class API: NSObject { let params: [String: Any] = [ "token": token ] - self.request("https://www.pandora.com/api/v1/music/album", params: params) { (response) in + self.request("\(Constants.pandoraApiUrlV1)/music/album", params: params) { (response) in self.callbackAlbum(results:response, callbackHandler: callbackHandler ) } } @@ -306,24 +293,6 @@ class API: NSObject { self.request("https://www.pandora.com/api/v4/catalog/annotateObjectsSimple", params: params, callbackHandler: callbackHandler) } - // Deprecated? we should just be calling catalogDetails now for fetching artist details. Remove below comment block in future: - //1 - /* - func artistToken(token:String, callbackHandler: @escaping(_ Dictionary:[String: AnyObject]) -> ()) { - let params: [String: Any] = [ - "token": token - ] - self.request("https://www.pandora.com/api/v1/music/artist", params: params) { (response) in - self.callbackArtistToken(results:response, callbackHandler: callbackHandler ) - } - } - //2 - func callbackArtistToken(results:[String: Any], callbackHandler: @escaping(_ Dictionary:[String:AnyObject]) -> ()) { - let pandoraId = results["pandoraId"] as! String - self.catalogDetails(pandoraId: pandoraId, callbackHandler: callbackHandler) - } - */ - // Get artist details func catalogDetails(pandoraId:String, callbackHandler: @escaping(_ Dictionary:[String: AnyObject]) -> ()) { let params: [String: Any] = [ @@ -334,13 +303,6 @@ class API: NSObject { } } - //4 part of deprecation, just call catalogDetails now - /* - func callbackCatalogDetails(catalogResults:[String: AnyObject], artistResults:[String: Any], callbackHandler: @escaping(_ Dictionary:[String: AnyObject]) -> ()) { - callbackHandler(catalogResults); - } - */ - // Token is needed to perform API requests to fetch details of an artist or album // The token is nested in the ShareableUrlPath, this function extracts it //xxx refactor to use willSet and didSet @@ -376,7 +338,7 @@ class API: NSObject { let params: [String: Any] = [ "pageSize": 250, ] - self.request("https://www.pandora.com/api/v1/station/getStations", params: params, callbackHandler: callbackHandler) + self.request("\(Constants.pandoraApiUrlV1)/station/getStations", params: params, callbackHandler: callbackHandler) } // Get music tracks of station func getPlaylistFragment(stationId:String, isStationStart:Bool, lastPlayedTrackToken:String?, callbackHandler: @escaping(_ Dictionary:[String: AnyObject]) -> ()) { @@ -392,7 +354,7 @@ class API: NSObject { if lastPlayedTrackToken != nil { params["lastPlayedTrackToken"] = lastPlayedTrackToken } - self.request("https://www.pandora.com/api/v1/playlist/getFragment", params: params, callbackHandler: callbackHandler) + self.request("\(Constants.pandoraApiUrlV1)/playlist/getFragment", params: params, callbackHandler: callbackHandler) } func addFeedback(trackToken:String, isPositive:Bool, callbackHandler: @escaping(_ Dictionary:[String: AnyObject]) -> ()) { @@ -400,7 +362,7 @@ class API: NSObject { "trackToken": trackToken, "isPositive": isPositive, ] - self.request("https://www.pandora.com/api/v1/station/addFeedback", params: params, callbackHandler: callbackHandler) + self.request("\(Constants.pandoraApiUrlV1)/station/addFeedback", params: params, callbackHandler: callbackHandler) } func deleteFeedback(trackToken:String, isPositive:Bool, callbackHandler: @escaping(_ Dictionary:[String: AnyObject]) -> ()) { @@ -408,14 +370,14 @@ class API: NSObject { "trackToken": trackToken, "isPositive": isPositive, ] - self.request("https://www.pandora.com/api/v1/station/deleteFeedback", params: params, callbackHandler: callbackHandler) + self.request("\(Constants.pandoraApiUrlV1)/station/deleteFeedback", params: params, callbackHandler: callbackHandler) } func trackStarted(trackToken: String, callbackHandler: @escaping(_ Dictionary:[String: AnyObject]) -> ()) { let params: [String: Any] = [ "trackToken": trackToken ] - self.request("https://www.pandora.com/api/v1/station/trackStarted", params: params, callbackHandler: callbackHandler) + self.request("\(Constants.pandoraApiUrlV1)/station/trackStarted", params: params, callbackHandler: callbackHandler) } func createStation(pandoraId:String, callbackHandler: @escaping(_ Dictionary:[String: AnyObject]) -> ()) { @@ -425,7 +387,7 @@ class API: NSObject { "lineId": "", "creationSource": "" ] - self.request("https://www.pandora.com/api/v1/station/createStation", params: params, callbackHandler: callbackHandler); + self.request("\(Constants.pandoraApiUrlV1)/station/createStation", params: params, callbackHandler: callbackHandler); } /* @@ -443,7 +405,7 @@ class API: NSObject { "annotationLimit": 100 ] ] - self.request("https://www.pandora.com/api/v5/collections/getSortedPlaylists", params: params, callbackHandler: callbackHandler) + self.request("\(Constants.pandoraApiUrlV5)/collections/getSortedPlaylists", params: params, callbackHandler: callbackHandler) } func getTracks(pandoraId:String, callbackHandler: @escaping(_ Dictionary:[String: AnyObject]) -> ()) { @@ -456,7 +418,7 @@ class API: NSObject { "annotationLimit": 100 ] ] - self.request("https://www.pandora.com/api/v6/playlists/getTracks", params: params, callbackHandler: callbackHandler) + self.request("\(Constants.pandoraApiUrlV6)/playlists/getTracks", params: params, callbackHandler: callbackHandler) } } diff --git a/Milkshake/Info.plist b/Milkshake/Info.plist index 83c36ab..aa51543 100644 --- a/Milkshake/Info.plist +++ b/Milkshake/Info.plist @@ -19,9 +19,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.1.0 + 1.1.1 CFBundleVersion - 38 + 39 LSApplicationCategoryType public.app-category.music LSMinimumSystemVersion diff --git a/Milkshake/LoginViewController.swift b/Milkshake/LoginViewController.swift index b0828e9..81c7f56 100644 --- a/Milkshake/LoginViewController.swift +++ b/Milkshake/LoginViewController.swift @@ -41,7 +41,6 @@ class LoginViewController: NSViewController { usernameField.stringValue = username passwordField.stringValue = password // self.loginAction(self) // auto login - } self.usernameField.delegate = self self.passwordField.delegate = self @@ -49,27 +48,14 @@ class LoginViewController: NSViewController { self.passwordField.nextKeyView = self.rememberButton self.rememberButton.nextKeyView = self.loginButton self.loginButton.nextKeyView = self.usernameField - } - - func callbackLogin(results: [String: AnyObject]) { - if let errorCode = results["errorCode"] as? Int { - if errorCode >= 0 { - let errorMessage = results["message"] as! String - errorTextField.stringValue = errorMessage - } - } else { - self.delegate?.handleSuccessLogin(results: results) - } - } - + } func callbackPartnerAuth(results: [String: AnyObject]) { let token = results["result"]!["partnerAuthToken"] as! String let partnerId = results["result"]!["partnerId"] as! String let syncTimeEnc = results["result"]!["syncTime"] as! String - let syncTime = PandoraDecryptTime(syncTimeEnc,"R=U!LH$O2B#") - + let syncTime = PandoraDecryptTime(syncTimeEnc, Constants.decryptPassword) self.appDelegate.api.partnerAuthUserLogin(username: self.usernameField.stringValue, password: self.passwordField.stringValue, partnerAuthToken: token, partnerId: partnerId, syncTime: syncTime, callbackHandler: callbackPartnerAuthUserLogin); } @@ -94,7 +80,5 @@ class LoginViewController: NSViewController { } self.appDelegate.api.partnerAuthPartnerLogin(callbackHandler: callbackPartnerAuth); - -// self.appDelegate.api.auth(username: self.usernameField.stringValue, pass: self.passwordField.stringValue, callbackHandler: callbackLogin); } } diff --git a/Milkshake/PlayerViewController.xib b/Milkshake/PlayerViewController.xib index dc551b3..0a32b87 100644 --- a/Milkshake/PlayerViewController.xib +++ b/Milkshake/PlayerViewController.xib @@ -1,8 +1,8 @@ - + - + @@ -25,7 +25,7 @@ - + diff --git a/Milkshake/Util.swift b/Milkshake/Util.swift index 0b08b03..e740cdc 100644 --- a/Milkshake/Util.swift +++ b/Milkshake/Util.swift @@ -47,16 +47,16 @@ class Util: NSObject { // Parse API search results class func parseSearchIntoItems(results: [String: AnyObject]) -> [MusicItem] { var items: [MusicItem] = [] -// print(results) + // print(results) if let resultsOrder = results["results"] as? [String] { for key in resultsOrder { let row = results["annotations"]![key]! as! [String: AnyObject] let iconDict = row["icon"] as? [String: String] - let icon = iconDict?["thorId"] ?? "" + let icon = iconDict?["thorId"] ?? "" //iconDict?["artUrl"] ?? "" let musicItem = MusicItem() if icon != "" { - musicItem.albumArt = "https://content-images.p-cdn.com/"+(icon) + musicItem.albumArt = "https://content-images.p-cdn.com/\(icon)/_250W_250H.jpg" } musicItem.name = row["name"] as? String @@ -71,9 +71,7 @@ class Util: NSObject { musicItem.albumId = row["albumId"] as? String musicItem.shareableUrlPath = row["shareableUrlPath"] as? String musicItem.cellType = CellType.SEARCH - -// print(row) - + // print(row) if let rightsDict = row["rightsInfo"] as? [String: AnyObject] { musicItem.hasInteractive = rightsDict["hasInteractive"] as? Bool ?? false } diff --git a/Milkshake/constants.swift b/Milkshake/constants.swift new file mode 100644 index 0000000..7ad6356 --- /dev/null +++ b/Milkshake/constants.swift @@ -0,0 +1,23 @@ +// +// constants.swift +// Milkshake +// +// Created by Dean Liu on 8/10/22. +// Copyright © 2022 Dean Liu. All rights reserved. +// + +import Foundation + +struct Constants { + static let username = "android" + static let password = "AC7IBG09A3DTSYM4R41UJWL07VLN8JI7" + static let encryptPassword = "6#26FRL$ZWD" + static let decryptPassword = "R=U!LH$O2B#" + static let deviceModel = "android-generic" + static let version = "5" + static let tunerUrl = "https://tuner.pandora.com:443/services/json"; + + static let pandoraApiUrlV1 = "https://www.pandora.com/api/v1"; + static let pandoraApiUrlV5 = "https://www.pandora.com/api/v5"; + static let pandoraApiUrlV6 = "https://www.pandora.com/api/v6"; +} diff --git a/Pods/Pods.xcodeproj/project.pbxproj b/Pods/Pods.xcodeproj/project.pbxproj index 818d487..7c9c226 100644 --- a/Pods/Pods.xcodeproj/project.pbxproj +++ b/Pods/Pods.xcodeproj/project.pbxproj @@ -121,12 +121,12 @@ 048DAEFD1705F8AF05A01898C7C3BF9B /* Locksmith-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Locksmith-umbrella.h"; sourceTree = ""; }; 06A42CB10E207017DBCE54C061F78450 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 06AFFC6D0E49C06F52D79295DEF1F3DD /* ImageProcessor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageProcessor.swift; path = Sources/ImageProcessor.swift; sourceTree = ""; }; - 08C5613F90802A319FB2161FF002B3E5 /* HotKey.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = HotKey.framework; path = HotKey.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 08C5613F90802A319FB2161FF002B3E5 /* HotKey.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = HotKey.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 159C5B9486544C479ED8E6E52781CFC1 /* Validation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Validation.swift; path = Source/Validation.swift; sourceTree = ""; }; 1871F232C6FADDDA34D54744E61FD20C /* Pods-Milkshake.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-Milkshake.release.xcconfig"; sourceTree = ""; }; 18E622A8A57379A4365E2AD18B14107A /* Kingfisher-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Kingfisher-dummy.m"; sourceTree = ""; }; 18FCF7EB046D9493DD1F17EED5E37109 /* ImageCache.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageCache.swift; path = Sources/ImageCache.swift; sourceTree = ""; }; - 1B2A17B5DFE21F3148CB27C1892F74EE /* Locksmith.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; path = Locksmith.modulemap; sourceTree = ""; }; + 1B2A17B5DFE21F3148CB27C1892F74EE /* Locksmith.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = Locksmith.modulemap; sourceTree = ""; }; 227BFEBA798F48D45EE6FCF6446EFBB7 /* Pods-Milkshake-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-Milkshake-frameworks.sh"; sourceTree = ""; }; 2361B03AA743480291EB582ABE117375 /* SessionDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionDelegate.swift; path = Source/SessionDelegate.swift; sourceTree = ""; }; 239CB161BF1276CB55EEBE6B06236ADF /* Dictionary_Initializers.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Dictionary_Initializers.swift; path = Source/Dictionary_Initializers.swift; sourceTree = ""; }; @@ -139,20 +139,20 @@ 2CDB5FA03E90668A1FFBAC1411A94D7D /* Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Alamofire.swift; path = Source/Alamofire.swift; sourceTree = ""; }; 2D2612A72B33CF5131EA0C018E7AC9CD /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 2D52126E917872787B5E10847A2F3BDE /* ImageDownloader.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDownloader.swift; path = Sources/ImageDownloader.swift; sourceTree = ""; }; - 3015944A42ACA8D17D9B8D2265A6A317 /* Alamofire.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Alamofire.framework; path = Alamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3015944A42ACA8D17D9B8D2265A6A317 /* Alamofire.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Alamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3110F9AB4047C02EBF454C5DB1A732F8 /* Resource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Resource.swift; path = Sources/Resource.swift; sourceTree = ""; }; 32020155E377811300C6A0231AB8B555 /* Result.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Result.swift; path = Source/Result.swift; sourceTree = ""; }; 3458560AA6689759915A1C8BB0BC3FBF /* Locksmith-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Locksmith-dummy.m"; sourceTree = ""; }; 38555AF10961E9171277B76F5A83B01E /* Request.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Request.swift; path = Source/Request.swift; sourceTree = ""; }; 3C4314FDCDBFE748FEFFC402C4126111 /* Locksmith.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = Locksmith.h; path = Source/Locksmith.h; sourceTree = ""; }; 3F35A6AA4FD69D42DFE58D459A8EBB39 /* ServerTrustPolicy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ServerTrustPolicy.swift; path = Source/ServerTrustPolicy.swift; sourceTree = ""; }; - 438D130C0142E4010435B348877FF44E /* Kingfisher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Kingfisher.framework; path = Kingfisher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 438D130C0142E4010435B348877FF44E /* Kingfisher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Kingfisher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 45D5A1C67E898D363D1674C6C64AA6E6 /* HotKey-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "HotKey-umbrella.h"; sourceTree = ""; }; 46D33A9DAEC09481A61470841478FA87 /* Kingfisher.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Kingfisher.xcconfig; sourceTree = ""; }; 47D378B843AD6B4E1CA5B84E022B60B2 /* Pods-Milkshake.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-Milkshake.debug.xcconfig"; sourceTree = ""; }; 4B18352C4668DD96368DE8FCCDAE105E /* DispatchQueue+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DispatchQueue+Alamofire.swift"; path = "Source/DispatchQueue+Alamofire.swift"; sourceTree = ""; }; 517972B18C1B78E77280F63F72911CE9 /* KingfisherOptionsInfo.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KingfisherOptionsInfo.swift; path = Sources/KingfisherOptionsInfo.swift; sourceTree = ""; }; - 5340AACCBD01D78F38C60747C40C5232 /* Alamofire.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; path = Alamofire.modulemap; sourceTree = ""; }; + 5340AACCBD01D78F38C60747C40C5232 /* Alamofire.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = Alamofire.modulemap; sourceTree = ""; }; 54363F25AD45B035A5FA824A8051AEDF /* LocksmithAccessibleOption.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LocksmithAccessibleOption.swift; path = Source/LocksmithAccessibleOption.swift; sourceTree = ""; }; 57FCA9255A25D4B22AA44EB81DB79785 /* Response.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Response.swift; path = Source/Response.swift; sourceTree = ""; }; 5D66781E874767D1D3CBF9BEB3977CFE /* Pods-Milkshake-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-Milkshake-umbrella.h"; sourceTree = ""; }; @@ -161,25 +161,25 @@ 6247D03690E24CB6DB4108995749DBFF /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; 628007C2F94856D27E933B2F863D7467 /* Locksmith.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Locksmith.xcconfig; sourceTree = ""; }; 6492B3A0A824073DCCAA644A88B3CF79 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 65E3058C9896FD351AFF7282B03B34C5 /* HotKey.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; path = HotKey.modulemap; sourceTree = ""; }; - 66473EBAE924EB9538A8956B34B81D19 /* Pods-Milkshake.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; path = "Pods-Milkshake.modulemap"; sourceTree = ""; }; + 65E3058C9896FD351AFF7282B03B34C5 /* HotKey.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = HotKey.modulemap; sourceTree = ""; }; + 66473EBAE924EB9538A8956B34B81D19 /* Pods-Milkshake.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = "Pods-Milkshake.modulemap"; sourceTree = ""; }; 6A8FA1AF31AA74949A9DB88E3929EACA /* Timeline.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Timeline.swift; path = Source/Timeline.swift; sourceTree = ""; }; 6BD0661D944BC05901B936BE700659A7 /* MultipartFormData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MultipartFormData.swift; path = Source/MultipartFormData.swift; sourceTree = ""; }; 6C2D81DB3B969C2CEF2FB8CEE51F1A79 /* Locksmith-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Locksmith-prefix.pch"; sourceTree = ""; }; - 6D4B46F08DE193C6D1BD48401FF04AF5 /* Kingfisher.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; path = Kingfisher.modulemap; sourceTree = ""; }; + 6D4B46F08DE193C6D1BD48401FF04AF5 /* Kingfisher.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = Kingfisher.modulemap; sourceTree = ""; }; 70AD8ACA3683D474861AF6E729AD6243 /* Pods-Milkshake-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-Milkshake-acknowledgements.plist"; sourceTree = ""; }; 718813F2EFABD8C41859B844A131066F /* LocksmithSecurityClass.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LocksmithSecurityClass.swift; path = Source/LocksmithSecurityClass.swift; sourceTree = ""; }; - 7297AC98AE78B1A9CB3C5BC060009B9B /* Locksmith.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Locksmith.framework; path = Locksmith.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7297AC98AE78B1A9CB3C5BC060009B9B /* Locksmith.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Locksmith.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 869C8AF1597827D05CEA41A9450B244F /* Locksmith.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Locksmith.swift; path = Source/Locksmith.swift; sourceTree = ""; }; 89BA1688BA33AC01D241D316CBCD779D /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/System/Library/Frameworks/Carbon.framework; sourceTree = DEVELOPER_DIR; }; 89E43BADCDE4ABF7FFE668A54F176BD4 /* Alamofire.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Alamofire.xcconfig; sourceTree = ""; }; 8B1DB20A5040F92F41E8725D9105C9DB /* HotKey-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "HotKey-prefix.pch"; sourceTree = ""; }; 910AD5EC59EB5EDE81028365F7F456B0 /* Placeholder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Placeholder.swift; path = Sources/Placeholder.swift; sourceTree = ""; }; - 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 94BE5A0837AA871B1ECADD64F88E6D67 /* LocksmithInternetAuthenticationType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LocksmithInternetAuthenticationType.swift; path = Source/LocksmithInternetAuthenticationType.swift; sourceTree = ""; }; 969A714340A6B7CFDBB5180C11B33BFD /* Indicator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Indicator.swift; path = Sources/Indicator.swift; sourceTree = ""; }; 977E4084EFDFBE0AD79765F4B8B4356B /* Image.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Image.swift; path = Sources/Image.swift; sourceTree = ""; }; - 97AF3F982891872E4A424D5C3FE731D2 /* Pods_Milkshake.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_Milkshake.framework; path = "Pods-Milkshake.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; + 97AF3F982891872E4A424D5C3FE731D2 /* Pods_Milkshake.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Milkshake.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 97D82231D4514CB52C761C62ED2742D1 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/System/Library/Frameworks/Cocoa.framework; sourceTree = DEVELOPER_DIR; }; 9AFF13B8F5DE98911AD21ECE4E9BE92A /* String+MD5.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "String+MD5.swift"; path = "Sources/String+MD5.swift"; sourceTree = ""; }; A70D2C1653474D6320926764796FFE69 /* ResponseSerialization.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ResponseSerialization.swift; path = Source/ResponseSerialization.swift; sourceTree = ""; }; @@ -317,7 +317,6 @@ 718813F2EFABD8C41859B844A131066F /* LocksmithSecurityClass.swift */, FEA4CB4597CC9EBA7EF27615C4D24450 /* Support Files */, ); - name = Locksmith; path = Locksmith; sourceTree = ""; }; @@ -378,7 +377,6 @@ 159C5B9486544C479ED8E6E52781CFC1 /* Validation.swift */, 0F7B026E4C4227B0C7D9F51E2CA66258 /* Support Files */, ); - name = Alamofire; path = Alamofire; sourceTree = ""; }; @@ -404,7 +402,6 @@ B5CB373E1AD6F9C40CC088F2A920A4CE /* NSEventModifierFlags+HotKey.swift */, 2AF66D1F7180837BA00CEBB31815F213 /* Support Files */, ); - name = HotKey; path = HotKey; sourceTree = ""; }; @@ -461,7 +458,6 @@ F847EB7DCFDFE4472136F05D4BEFB1E9 /* ThreadHelper.swift */, 87F05D78BDC106BE5B0903374DD00794 /* Support Files */, ); - name = Kingfisher; path = Kingfisher; sourceTree = ""; }; @@ -638,10 +634,11 @@ }; buildConfigurationList = 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = 7DB346D0F39D3F0E887471402A8071AB; productRefGroup = 4F2CD48B171D6451D0B80C301262369E /* Products */; diff --git a/README.md b/README.md index a87ace6..8acb7e2 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Milkshake is a Mac OS X [Pandora](https://www.pandora.com) client. If you don't have a Pandora account, you can [sign up here](https://www.pandora.com/account/register) # Download v1.1.0 -[.DMG Installer](https://github.com/skiptomyliu/milkshake/raw/master/App/Milkshake.dmg) +[💾 DMG Installer](https://github.com/skiptomyliu/milkshake/raw/master/App/Milkshake.dmg) # Important note