diff --git a/Pod/Classes/Categories/NSDictionary+SnapchatKit.m b/Pod/Classes/Categories/NSDictionary+SnapchatKit.m index 52f0099..b6d93eb 100755 --- a/Pod/Classes/Categories/NSDictionary+SnapchatKit.m +++ b/Pod/Classes/Categories/NSDictionary+SnapchatKit.m @@ -39,7 +39,7 @@ - (NSArray *)split:(NSUInteger)entryLimit { } - (NSDictionary *)dictionaryByReplacingValuesForKeys:(NSDictionary *)dictionary { - if (!dictionary || !self) return self; + if (!dictionary || !dictionary.allKeys.count || !self) return self; NSMutableDictionary *m = self.mutableCopy; for (NSString *key in dictionary.allKeys) @@ -49,7 +49,7 @@ - (NSDictionary *)dictionaryByReplacingValuesForKeys:(NSDictionary *)dictionary } - (NSDictionary *)dictionaryByReplacingKeysWithNewKeys:(NSDictionary *)oldKeysToNewKeys { - if (!oldKeysToNewKeys || !self) return self; + if (!oldKeysToNewKeys || !oldKeysToNewKeys.allKeys.count || !self) return self; NSMutableDictionary *m = self.mutableCopy; [oldKeysToNewKeys enumerateKeysAndObjectsUsingBlock:^(NSString *oldKey, NSString *newKey, BOOL *stop) { diff --git a/Pod/Classes/Model/SKMessage.m b/Pod/Classes/Model/SKMessage.m index 426cd93..8a6422a 100755 --- a/Pod/Classes/Model/SKMessage.m +++ b/Pod/Classes/Model/SKMessage.m @@ -65,7 +65,9 @@ - (id)initWithDictionary:(NSDictionary *)json { _mediaIV = media[@"iv"]; _mediaKey = media[@"key"]; _mediaType = media[@"media_type"]; - if (![self.mediaType isEqualToString:@"VIDEO"]) + if (!self.mediaType) + _mediaType = @"UNSPECIFIED"; + else if (![self.mediaType isEqualToString:@"VIDEO"]) NSLog(@"New media type: %@", self.mediaType); break; diff --git a/Pod/Classes/Model/SKRequest.h b/Pod/Classes/Model/SKRequest.h index 9c1f47a..bac4fbb 100755 --- a/Pod/Classes/Model/SKRequest.h +++ b/Pod/Classes/Model/SKRequest.h @@ -17,16 +17,25 @@ /** @brief Replaces header field values. Useful for changing the user-agent on the fly. - @discussion Pass \c nil to remove previous overrides. */ -+ (void)overrideHeaderValues:(NSDictionary *)headers; + @discussion Pass \c nil to remove previous overrides. [SKRequest overrideHeaderValues:forEndpoint:] takes precedence over this method and affects every request, but otherwise functions the same. + @param headers A dictionary of header-value key-value pairs, i.e. @code @{@"User-Agent": @"iOS 2.0"} @endcode */ ++ (void)overrideHeaderValuesGlobally:(NSDictionary *)headers; + +/** @brief Replaces header field values for a specific endpoint. + @discussion Pass \c nil to \c headers to remove previous overrides for a given endpoint. See [SKRequest overrideHeaderValues:] for more information. + This method takes precedence over [SKRequest overrideHeaderValuesGlobally:] and is used before [SKRequest overrideEndpoints:], so make sure to override the original endpoint. + @param headers A dictionary of header-value key-value pairs, i.e. @code @{@"User-Agent": @"iOS 2.0"} @endcode + @param endpoint The endpoint whose header values you wish to override. */ ++ (void)overrideHeaderValues:(NSDictionary *)headers forEndpoint:(NSString *)endpoint; /** @brief Replaces values for specific query keys in the scope of the given endpoint. - @discussion Pass \c nil to \e queries to remove previous overrides for \e endpoint. This method takes precedence over \c overrideValuesForKeysGlobally: and is used before \c overrideEndpoints:. + @discussion Pass \c nil to \e queries to remove previous overrides for \e endpoint. + This method takes precedence over [SKRequest overrideValuesForKeysGlobally:] and is used before [SKRequest overrideEndpoints:], so make sure to override the original endpoint. So making a request to \b /bq/add_everyone with this query: @code @{@"username": @"ThePantsThief", @"acton": @"add_everyone_duh" @"r_u_sure": @YES} @endcode - having called \c overrideEndpoints: with this dictionary: @code + having called [SKRequest overrideEndpoints:] with this dictionary: @code @{@"/bq/add_everyone": @"/bq/unfriend_everyone"} @endcode and having called \c overrideValuesForKeys:forEndpoint: for the \b /bq/add_everyone endpoint with this dictionary: @code @{@"action": @"unfriend_everyone_pls", @@ -36,16 +45,17 @@ @"acton": @"unfriend_everyone_pls" @"r_u_sure": @NO} @endcode without affecting the value of \c \@"action" in any other requests. - If you \a did want to override that value in \a every request, you would pass @code @{@"action": newValue} @endcode to \c overrideValuesForKeysGlobally:. + If you \a did want to override that value in \a every request, you would pass @code @{@"action": newValue} @endcode to [SKRequest overrideValuesForKeysGlobally:]. */ + (void)overrideValuesForKeys:(NSDictionary *)queries forEndpoint:(NSString *)endpoint; /** @brief Replaces values for specific query keys in every request. - @discussion Pass \c nil to remove previous overrides. \c overrideValuesForKeys:forEndpoint: takes precedence over this method and affects every request, but otherwise functions the same. */ + @discussion Pass \c nil to remove previous overrides. [SKRequest overrideValuesForKeys:forEndpoint:] takes precedence over this method and affects every request, but otherwise functions the same. */ + (void)overrideValuesForKeysGlobally:(NSDictionary *)queries; /** @brief Replaces endpoint \c some_endpoint with \e endpoints[some_endpoint]. - @discussion \c overrideValuesForKeys:forEndpoint: takes precedence over this method. This method will replace all ekeys in a request query with the given values in \c endpoints. */ + @discussion [SKRequest overrideValuesForKeys:forEndpoint:] and [SKRequest overrideHeaderValues:forEndpoint:] take precedence over this method. + This method will replace all ekeys in a request query with the given values in \c endpoints. */ + (void)overrideEndpoints:(NSDictionary *)endpoints; /** diff --git a/Pod/Classes/Model/SKRequest.m b/Pod/Classes/Model/SKRequest.m index e75b9c5..35141fb 100755 --- a/Pod/Classes/Model/SKRequest.m +++ b/Pod/Classes/Model/SKRequest.m @@ -16,27 +16,36 @@ @implementation SKRequest #pragma mark Request overrides -static NSDictionary *headerOverrides; -static NSMutableDictionary *scopeKeyValueOverrides; -static NSDictionary *globalKeyValueOverrides; -static NSDictionary *endpointOverrides; +static NSDictionary *headerOverrides; +static NSDictionary *globalParamOverrides; +static NSMutableDictionary *scopeParamOverrides; +static NSMutableDictionary *scopeHeaderOverrides; +static NSDictionary *endpointOverrides; -+ (void)overrideHeaderValues:(NSDictionary *)headers { ++ (void)overrideHeaderValuesGlobally:(NSDictionary *)headers { NSParameterAssert([headers isKindOfClass:[NSDictionary class]] || !headers); headerOverrides = headers; } ++ (void)overrideHeaderValues:(NSDictionary *)headers forEndpoint:(NSString *)endpoint { + NSParameterAssert([headers isKindOfClass:[NSDictionary class]] || !headers); NSParameterAssert(endpoint); + if (!scopeHeaderOverrides) + scopeHeaderOverrides = [NSMutableDictionary dictionary]; + + scopeHeaderOverrides[endpoint] = headers; +} + + (void)overrideValuesForKeys:(NSDictionary *)queries forEndpoint:(NSString *)endpoint { NSParameterAssert([queries isKindOfClass:[NSDictionary class]] || !queries); NSParameterAssert(endpoint); - if (!scopeKeyValueOverrides) - scopeKeyValueOverrides = [NSMutableDictionary dictionary]; + if (!scopeParamOverrides) + scopeParamOverrides = [NSMutableDictionary dictionary]; - scopeKeyValueOverrides[endpoint] = queries; + scopeParamOverrides[endpoint] = queries; } + (void)overrideValuesForKeysGlobally:(NSDictionary *)queries { NSParameterAssert([queries isKindOfClass:[NSDictionary class]] || !queries); - globalKeyValueOverrides = queries; + globalParamOverrides = queries; } + (void)overrideEndpoints:(NSDictionary *)endpoints { @@ -45,14 +54,21 @@ + (void)overrideEndpoints:(NSDictionary *)endpoints { void SKRequestApplyOverrides(NSString **endpoint, NSDictionary **params) { if (params != NULL) { - *params = [*params dictionaryByReplacingValuesForKeys:globalKeyValueOverrides]; - *params = [*params dictionaryByReplacingValuesForKeys:scopeKeyValueOverrides[*endpoint]]; + *params = [*params dictionaryByReplacingValuesForKeys:globalParamOverrides]; + *params = [*params dictionaryByReplacingValuesForKeys:scopeParamOverrides[*endpoint]]; } if (*endpoint) { *endpoint = endpointOverrides[*endpoint] ?: *endpoint; } } +NSDictionary * SKRequestApplyHeaderOverrides(NSDictionary *httpHeaders, NSString *endpoint) { + if (!httpHeaders) return @{}; + httpHeaders = [httpHeaders dictionaryByReplacingValuesForKeys:headerOverrides]; + httpHeaders = [httpHeaders dictionaryByReplacingValuesForKeys:scopeHeaderOverrides[endpoint]]; + return httpHeaders; +} + #pragma mark Convenience + (NSDictionary *)parseJSON:(NSData *)jsonData { @@ -126,6 +142,7 @@ - (id)initWithHeaderFields:(NSDictionary *)httpHeaders { - (id)initWithPOSTEndpoint:(NSString *)endpoint token:(NSString *)token query:(NSDictionary *)params headers:(NSDictionary *)httpHeaders ts:(NSString *)timestamp { if (!token) token = SKConsts.staticToken; + httpHeaders = SKRequestApplyHeaderOverrides(httpHeaders, endpoint); self = [self initWithHeaderFields:httpHeaders]; if (self) { @@ -171,6 +188,8 @@ - (id)initWithPOSTEndpoint:(NSString *)endpoint token:(NSString *)token query:(N } - (id)initWithGETEndpoint:(NSString *)endpoint headers:(NSDictionary *)httpHeaders { + httpHeaders = SKRequestApplyHeaderOverrides(httpHeaders, endpoint); + self = [self initWithHeaderFields:httpHeaders]; if (self) { SKRequestApplyOverrides(&endpoint, NULL); diff --git a/Pod/Classes/Networking/SKClient.m b/Pod/Classes/Networking/SKClient.m index 7497ea7..23e1dfa 100755 --- a/Pod/Classes/Networking/SKClient.m +++ b/Pod/Classes/Networking/SKClient.m @@ -270,6 +270,44 @@ - (void)getDeviceToken:(DictionaryBlock)completion { }]; } +/** ptoken value. */ +- (void)getGoogleCloudMessagingIdentifier:(StringBlock)callback { + NSDictionary *postFields = @{@"X-google.message_id": @"google.rpc1", + @"device": @(4343470343591528399), + @"sender": @(191410808405), + @"app_ver": @(706), + @"gcm_ver": @(7097038), + @"app": @"com.snapchat.android", + @"iat": @([[NSDate date] timeIntervalSince1970]), + @"cert": @"49f6badb81d89a9e38d65de76f09355071bd67e7"}; + + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://android.clients.google.com/c2dm/register3"]]; + + request.HTTPMethod = @"POST"; + request.HTTPBody = [[NSString queryStringWithParams:postFields] dataUsingEncoding:NSUTF8StringEncoding]; + [request setValue:@"en" forHTTPHeaderField:@"Accept-Language"]; + [request setValue:@"en_US" forHTTPHeaderField:@"Accept-Locale"]; + [request setValue:@"Android-GCM/1.5 (A116 _Quad KOT49H)" forHTTPHeaderField:SKHeaders.userAgent]; + [request setValue:@"com.snapchat.android" forHTTPHeaderField:@"app"]; + [request setValue:@"AidLogin 4343470343591528399:5885638743641649694" forHTTPHeaderField:@"Authorization"]; + [request setValue:@"7097038" forHTTPHeaderField:@"Gcm-ver"]; + [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; + + NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; + NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (error) { + callback(nil, error); + } else if (data) { + NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + string = [string matchGroupAtIndex:1 forRegex:@"token=([\\w\\.-]+)"]; + callback(string, nil); + } else { + callback(nil, [SKRequest errorWithMessage:@"Unknown error" code:[(NSHTTPURLResponse *)response statusCode]]); + } + }]; + [dataTask resume]; +} + /** Attestation, courtesy of \c casper.io. Using AFNetworking due to an unknown issue with NSURLRequest causing logins to fail 50% of the time */ - (void)getAttestationUsingAFNetworkingWithUsername:(NSString *)username password:(NSString *)password ts:(NSString *)ts callback:(StringBlock)completion { NSString *hashString = [NSString stringWithFormat:@"%@|%@|%@|%@", username, password, ts, SKEPAccount.login]; @@ -341,54 +379,60 @@ - (void)signInWithUsername:(NSString *)username password:(NSString *)password gm if (error2 || !attestation) { completion(nil, [SKRequest errorWithMessage:@"Could not retrieve attestation." code:error2.code?:1]); } else { - [self getDeviceToken:^(NSDictionary *dict, NSError *error3) { - if (error3 || !dict) { - completion(nil, [SKRequest errorWithMessage:@"Could not retrieve Snapchat device token." code:error3.code?:1]); - } else { - - _googleAuthToken = gauth; - _googleAttestation = attestation; - _deviceToken1i = dict[SKConsts.deviceToken1i]; - _deviceToken1v = dict[SKConsts.deviceToken1v]; - - NSString *req_token = [NSString hashSCString:SKConsts.staticToken and:timestamp]; - NSString *string = [NSString stringWithFormat:@"%@|%@|%@|%@", username, password, timestamp, req_token]; - NSString *deviceSig = [[NSString hashHMac:string key:self.deviceToken1v] substringWithRange:NSMakeRange(0, 20)]; - - NSDictionary *post = @{@"username": username, - @"password": password, - @"height": @(kScreenHeight), - @"width": @(kScreenWidth), - @"max_video_width": @480, - @"max_video_height": @640, - @"application_id": @"com.snapchat.android", - @"is_two_fa": @"false", - @"ptoken": @"ie", - @"pre_auth": @"", - @"sflag": @1, - @"dsig": deviceSig, - @"dtoken1i": self.deviceToken1i, - @"attestation": self.googleAttestation, - @"timestamp": timestamp}; - - NSDictionary *headers = @{SKHeaders.clientAuthToken: [NSString stringWithFormat:@"Bearer %@", self.googleAuthToken]}; - SKRequest *request = [[SKRequest alloc] initWithPOSTEndpoint:SKEPAccount.login token:nil query:post headers:headers ts:timestamp]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; - - NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error4) { - [self handleError:error4 data:data response:response completion:^(NSDictionary *json, NSError *jsonerror) { - dispatch_async(dispatch_get_main_queue(), ^{ - if (!jsonerror) { - self.currentSession = [SKSession sessionWithJSONResponse:json]; - _authToken = self.currentSession.authToken; - completion(json, nil); - } else { - completion(nil, jsonerror); - } - }); - }]; + [self getGoogleCloudMessagingIdentifier:^(NSString *ptoken, NSError *error3) { + if (!error3) { + [self getDeviceToken:^(NSDictionary *dict, NSError *error4) { + if (error4 || !dict) { + completion(nil, [SKRequest errorWithMessage:@"Could not retrieve Snapchat device token." code:error4.code?:1]); + } else { + + _googleAuthToken = gauth; + _googleAttestation = attestation; + _deviceToken1i = dict[SKConsts.deviceToken1i]; + _deviceToken1v = dict[SKConsts.deviceToken1v]; + + NSString *req_token = [NSString hashSCString:SKConsts.staticToken and:timestamp]; + NSString *string = [NSString stringWithFormat:@"%@|%@|%@|%@", username, password, timestamp, req_token]; + NSString *deviceSig = [[NSString hashHMac:string key:self.deviceToken1v] substringWithRange:NSMakeRange(0, 20)]; + + NSDictionary *post = @{@"username": username, + @"password": password, + @"height": @(kScreenHeight), + @"width": @(kScreenWidth), + @"max_video_width": @480, + @"max_video_height": @640, + @"application_id": @"com.snapchat.android", + @"is_two_fa": @"false", + @"ptoken": ptoken ?: @"ie", + @"pre_auth": @"", + @"sflag": @1, + @"dsig": deviceSig, + @"dtoken1i": self.deviceToken1i, + @"attestation": self.googleAttestation, + @"timestamp": timestamp}; + + NSDictionary *headers = @{SKHeaders.clientAuthToken: [NSString stringWithFormat:@"Bearer %@", self.googleAuthToken]}; + SKRequest *request = [[SKRequest alloc] initWithPOSTEndpoint:SKEPAccount.login token:nil query:post headers:headers ts:timestamp]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; + + NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error5) { + [self handleError:error5 data:data response:response completion:^(NSDictionary *json, NSError *jsonerror) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (!jsonerror) { + self.currentSession = [SKSession sessionWithJSONResponse:json]; + _authToken = self.currentSession.authToken; + completion(json, nil); + } else { + completion(nil, jsonerror); + } + }); + }]; + }]; + [dataTask resume]; + } }]; - [dataTask resume]; + } else { + completion(nil, [SKRequest errorWithMessage:@"Could not retrieve Snapchat push token." code:error3.code?:1]); } }]; }