VoiceMac/Classes/VoiceBase/MGMInstance.m
GRMrGecko fdfed8631b Google Login Fixes
Google has updated their login system and VoiceBase needed an update to work with it.
2013-10-30 13:28:27 -05:00

959 lines
42 KiB
Objective-C

//
// MGMInstance.m
// VoiceBase
//
// Created by Mr. Gecko on 8/15/10.
// Copyright (c) 2011 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/
//
// Permission to use, copy, modify, and/or distribute this software for any purpose
// with or without fee is hereby granted, provided that the above copyright notice
// and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
// OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
// DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
// ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
#import "MGMInstance.h"
#import "MGMDelegateInfo.h"
#import "MGMInbox.h"
#import "MGMContacts.h"
#import "MGMAddressBook.h"
#import "MGMAddons.h"
#import "MGMXML.h"
#import <MGMUsers/MGMUsers.h>
NSString * const MGMVoiceBaseCopyright = @"Copyright (c) 2011 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/";
NSString * const MGMVoiceIndexURL = @"https://www.google.com/voice/";
NSString * const MGMLoginURL = @"https://accounts.google.com/ServiceLoginAuth";
NSString * const MGMXPCPath = @"/voice/xpc/?xpc=%7B%22cn%22%3A%22i70avDIMsA%22%2C%22tp%22%3Anull%2C%22pru%22%3A%22https%3A%2F%2Fwww.google.com%2Fvoice%2Fxpc%2Frelay%22%2C%22ppu%22%3A%22https%3A%2F%2Fwww.google.com%2Fvoice%2Fxpc%2Fblank%2F%22%2C%22lpu%22%3A%22https%3A%2F%2Fclients4.google.com%2Fvoice%2Fxpc%2Fblank%2F%22%7D";
NSString * const MGMCheckPath = @"/voice/xpc/checkMessages?r=%@";
NSString * const MGMCreditURL = @"https://www.google.com/voice/settings/billingcredit/";
NSString * const MGMPhonesURL = @"https://www.google.com/voice/settings/tab/phones";
NSString * const MGMCallURL = @"https://www.google.com/voice/call/connect/";
NSString * const MGMCallCancelURL = @"https://www.google.com/voice/call/cancel/";
NSString * const MGMPostMethod = @"POST";
NSString * const MGMURLForm = @"application/x-www-form-urlencoded";
NSString * const MGMContentType = @"content-type";
NSString * const MGMPhoneNumber = @"phoneNumber";
NSString * const MGMPhone = @"phone";
NSString * const MGMName = @"name";
NSString * const MGMType = @"type";
NSString * const MGMSContactsSourceKey = @"MGMContactsSource";
NSString * const MGMSContactsActionKey = @"MGMContactsAction";
NSString * const MGMUCAll = @"all";
NSString * const MGMUCInbox = @"inbox";
NSString * const MGMUCMissed = @"missed";
NSString * const MGMUCPlaced = @"placed";
NSString * const MGMUCReceived = @"received";
NSString * const MGMUCRecorded = @"recorded";
NSString * const MGMUCSMS = @"sms";
NSString * const MGMUCSpam = @"spam";
NSString * const MGMUCStarred = @"starred";
NSString * const MGMUCTrash = @"trash";
NSString * const MGMUCUnread = @"unread";
NSString * const MGMUCVoicemail = @"voicemail";
const BOOL MGMInstanceInvisible = YES;
@implementation MGMInstance
+ (id)instanceWithUser:(MGMUser *)theUser delegate:(id)theDelegate {
return [[[self alloc] initWithUser:theUser delegate:theDelegate isCheck:NO] autorelease];
}
+ (id)instanceWithUser:(MGMUser *)theUser delegate:(id)theDelegate isCheck:(BOOL)isCheck {
return [[[self alloc] initWithUser:theUser delegate:theDelegate isCheck:isCheck] autorelease];
}
- (id)initWithUser:(MGMUser *)theUser delegate:(id)theDelegate isCheck:(BOOL)isCheck {
if ((self = [super init])) {
checkingAccount = isCheck;
loggedIn = NO;
webLoginTries = 0;
delegate = theDelegate;
user = [theUser retain];
cookeStorage = [[theUser cookieStorage] retain];
connectionManager = [[MGMURLConnectionManager managerWithCookieStorage:cookeStorage] retain];
[self registerSettings];
inbox = [[MGMInbox inboxWithInstance:self] retain];
if (!checkingAccount)
contacts = [[MGMContacts contactsWithClass:NSClassFromString([user settingForKey:MGMSContactsSourceKey]) delegate:self] retain];
MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:MGMVoiceIndexURL]] delegate:self];
[handler setFailWithError:@selector(index:didFailWithError:)];
[handler setFinish:@selector(indexDidFinish:)];
[handler setInvisible:MGMInstanceInvisible];
[connectionManager addHandler:handler];
}
return self;
}
- (void)dealloc {
[connectionManager cancelAll];
[connectionManager release];
[user release];
[cookeStorage release];
[inbox release];
[contacts release];
[XPCURL release];
[XPCCD release];
[rnr_se release];
[userName release];
[userNumber release];
[userAreacode release];
[userPhoneNumbers release];
[checkTimer invalidate];
[checkTimer release];
checkTimer = nil;
[unreadCounts release];
[creditTimer invalidate];
[creditTimer release];
creditTimer = nil;
[super dealloc];
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@ Number: %@ Account: %@", [super description], userNumber, userName];
}
- (void)stop {
if (!checkingAccount)
[contacts stop];
[inbox stop];
[connectionManager cancelAll];
[checkTimer invalidate];
[checkTimer release];
checkTimer = nil;
[creditTimer invalidate];
[creditTimer release];
creditTimer = nil;
}
- (void)registerSettings {
NSMutableDictionary *settings = [NSMutableDictionary dictionary];
[settings setObject:NSStringFromClass([MGMAddressBook class]) forKey:MGMSContactsSourceKey];
[settings setObject:[NSNumber numberWithInt:0] forKey:MGMSContactsActionKey];
[user registerSettings:settings];
}
- (void)setDelegate:(id)theDelegate {
delegate = theDelegate;
}
- (id<MGMInstanceDelegate>)delegate {
return delegate;
}
- (MGMUser *)user {
return user;
}
- (MGMHTTPCookieStorage *)cookieStorage {
return cookeStorage;
}
- (MGMURLConnectionManager *)connectionManager {
return connectionManager;
}
- (MGMInbox *)inbox {
return inbox;
}
- (MGMContacts *)contacts {
return contacts;
}
- (void)updatedContacts {
if (delegate!=nil && [delegate respondsToSelector:@selector(updatedContacts)]) [delegate updatedContacts];
}
- (NSString *)XPCURL {
return XPCURL;
}
- (NSString *)XPCCD {
return XPCCD;
}
- (NSString *)rnr_se {
return rnr_se;
}
- (NSString *)userName {
return userName;
}
- (NSString *)userNumber {
return userNumber;
}
- (NSString *)userAreaCode {
return userAreacode;
}
- (NSString *)areaCode {
return userAreacode;
}
- (NSArray *)userPhoneNumbers {
return userPhoneNumbers;
}
- (NSDictionary *)unreadCounts {
return unreadCounts;
}
- (void)index:(MGMURLBasicHandler *)theHandler didFailWithError:(NSError *)theError {
if (delegate!=nil && [delegate respondsToSelector:@selector(loginError:)]) {
[delegate loginError:theError];
} else {
NSLog(@"Login Error: %@", theError);
}
}
- (void)indexDidFinish:(MGMURLBasicHandler *)theHandler {
NSString *returnedString = [theHandler string];
if ([returnedString containsString:@"<title>Redirecting</title>"]) {
NSRange range;
NSString *redirectURL = MGMVoiceIndexURL;
range = [returnedString rangeOfString:@"http-equiv="];
if (range.location!=NSNotFound) {
NSString *string = [returnedString substringFromIndex:range.location + range.length];
range = [string rangeOfString:@"url="];
if (range.location==NSNotFound) {
NSLog(@"failed 683476");
} else {
string = [string substringFromIndex:range.location + range.length];
range = [string rangeOfString:@"\""];
if (range.location==NSNotFound) {
range = [string rangeOfString:@"'"];
}
if (range.location==NSNotFound) {
NSLog(@"failed 683476");
} else {
string = [string substringWithRange:NSMakeRange(0, range.location)];
string = [string flattenHTML];
string = [string replace:@"\"" with:@""];
string = [string replace:@"'" with:@""];
//string = [string stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
redirectURL = [string replace:@"&amp;amp;" with:@"&"];
}
}
}
//NSLog(@"Redirecting to %@", redirectURL);
MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:redirectURL]] delegate:self];
[handler setFailWithError:@selector(index:didFailWithError:)];
[handler setFinish:@selector(indexDidFinish:)];
[handler setInvisible:MGMInstanceInvisible];
[connectionManager addHandler:handler];
} else if ([returnedString containsString:@"verification code"]) {
[verificationParameters release];
verificationParameters = [NSMutableDictionary new];
NSRange formRange = [returnedString rangeOfString:@"<form"];
NSRange formEndRange = [returnedString rangeOfString:@"</form>"];
if (formRange.location==NSNotFound || formEndRange.location==NSNotFound) {
NSError *error = [NSError errorWithDomain:@"com.MrGeckosMedia.MGMInstance.Login" code:56 userInfo:[NSDictionary dictionaryWithObject:@"Unable to login. There is a bug with 2 step verification." forKey:NSLocalizedDescriptionKey]];
if (delegate!=nil && [delegate respondsToSelector:@selector(loginError:)]) {
[delegate loginError:error];
} else {
NSLog(@"Login Error: %@", error);
}
return;
}
NSString *form = [returnedString substringWithRange:NSMakeRange(formRange.location, (formEndRange.location+formEndRange.length)-formRange.location)];
NSString *loginURL = [@"" retain];
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSRange actionRange = [form rangeOfString:@"action="];
if (actionRange.location!=NSNotFound) {
NSString *end = [form substringWithRange:NSMakeRange(actionRange.location+actionRange.length, 1)];
actionRange.location += 1;
NSString *string = [form substringFromIndex:actionRange.location+actionRange.length];
actionRange = [string rangeOfString:end];
[loginURL release];
loginURL = [[string substringWithRange:NSMakeRange(0, actionRange.location)] copy];
}
[pool drain];
[verificationURL release];
verificationURL = [[NSURL URLWithString:loginURL relativeToURL:[[theHandler response] URL]] retain];
[loginURL release];
NSRange range = NSMakeRange(0, [form length]);
while (range.length>1) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSRange inputRange = [form rangeOfString:@"<input " options:NSCaseInsensitiveSearch range:range];
if (inputRange.location!=NSNotFound) {
range.location = inputRange.location+inputRange.length;
range.length = [form length]-range.location;
NSRange endInputRange = [form rangeOfString:@">" options:NSCaseInsensitiveSearch range:range];
if (endInputRange.location==NSNotFound)
endInputRange.length = range.length;
else
endInputRange.length = endInputRange.location-range.location;
endInputRange.location = range.location;
NSRange nameRange = [form rangeOfString:@"name=" options:NSCaseInsensitiveSearch range:endInputRange];
if (nameRange.location==NSNotFound)
continue;
NSString *end = [form substringWithRange:NSMakeRange(nameRange.location+nameRange.length, 1)];
nameRange.location += 1;
NSRange endRange = nameRange;
endRange.location = nameRange.location+nameRange.length;
endRange.length = [form length]-endRange.location;
endRange = [form rangeOfString:end options:NSCaseInsensitiveSearch range:endRange];
if (endRange.location==NSNotFound)
continue;
NSString *name = [form substringWithRange:NSMakeRange(nameRange.location+nameRange.length, endRange.location-(nameRange.location+nameRange.length))];
range.location = inputRange.location+inputRange.length;
range.length = [form length]-range.location;
NSRange valueRange = [form rangeOfString:@"value=" options:NSCaseInsensitiveSearch range:endInputRange];
if (valueRange.location==NSNotFound)
continue;
end = [form substringWithRange:NSMakeRange(valueRange.location+valueRange.length, 1)];
valueRange.location += 1;
endRange = valueRange;
endRange.location = valueRange.location+valueRange.length;
endRange.length = [form length]-endRange.location;
endRange = [form rangeOfString:end options:NSCaseInsensitiveSearch range:endRange];
if (endRange.location==NSNotFound)
continue;
NSString *value = [form substringWithRange:NSMakeRange(valueRange.location+valueRange.length, endRange.location-(valueRange.location+valueRange.length))];
[verificationParameters setObject:value forKey:name];
} else {
break;
}
[pool drain];
}
if ([delegate respondsToSelector:@selector(loginVerificationRequested)]) {
#if MGMInstanceDebug
NSLog(@"%@", verificationParameters);
#endif
[delegate loginVerificationRequested];
} else {
NSError *error = [NSError errorWithDomain:@"com.MrGeckosMedia.MGMInstance.Login" code:54 userInfo:[NSDictionary dictionaryWithObject:@"Unable to login. The application does not implument with 2 step verification." forKey:NSLocalizedDescriptionKey]];
if (delegate!=nil && [delegate respondsToSelector:@selector(loginError:)]) {
[delegate loginError:error];
} else {
NSLog(@"Login Error: %@", error);
}
return;
}
} else if ([returnedString containsString:@"onload=\"autoSubmit()\""]) {
NSRange actionRange = [returnedString rangeOfString:@"<form"];
NSString *loginURL = [MGMLoginURL copy];
if (actionRange.location!=NSNotFound) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSString *string = [returnedString substringFromIndex:actionRange.location+actionRange.length];
actionRange = [string rangeOfString:@"action="];
if (actionRange.location!=NSNotFound) {
NSString *end = [string substringWithRange:NSMakeRange(actionRange.location+actionRange.length, 1)];
actionRange.location += 1;
string = [string substringFromIndex:actionRange.location+actionRange.length];
actionRange = [string rangeOfString:end];
[loginURL release];
loginURL = [[string substringWithRange:NSMakeRange(0, actionRange.location)] copy];
}
[pool drain];
}
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:loginURL]];
[loginURL release];
[request setHTTPMethod:MGMPostMethod];
[request setValue:MGMURLForm forHTTPHeaderField:MGMContentType];
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
NSRange range = NSMakeRange(0, [returnedString length]);
while (range.length>1) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSRange inputRange = [returnedString rangeOfString:@"<input " options:NSCaseInsensitiveSearch range:range];
if (inputRange.location!=NSNotFound) {
range.location = inputRange.location+inputRange.length;
range.length = [returnedString length]-range.location;
NSRange endInputRange = [returnedString rangeOfString:@">" options:NSCaseInsensitiveSearch range:range];
if (endInputRange.location==NSNotFound)
endInputRange.length = range.length;
else
endInputRange.length = endInputRange.location-range.location;
endInputRange.location = range.location;
NSRange nameRange = [returnedString rangeOfString:@"name=" options:NSCaseInsensitiveSearch range:endInputRange];
if (nameRange.location==NSNotFound)
continue;
NSString *end = [returnedString substringWithRange:NSMakeRange(nameRange.location+nameRange.length, 1)];
nameRange.location += 1;
NSRange endRange = nameRange;
endRange.location = nameRange.location+nameRange.length;
endRange.length = [returnedString length]-endRange.location;
endRange = [returnedString rangeOfString:end options:NSCaseInsensitiveSearch range:endRange];
if (endRange.location==NSNotFound)
continue;
NSString *name = [returnedString substringWithRange:NSMakeRange(nameRange.location+nameRange.length, endRange.location-(nameRange.location+nameRange.length))];
range.location = inputRange.location+inputRange.length;
range.length = [returnedString length]-range.location;
NSRange valueRange = [returnedString rangeOfString:@"value=" options:NSCaseInsensitiveSearch range:endInputRange];
if (valueRange.location==NSNotFound)
continue;
end = [returnedString substringWithRange:NSMakeRange(valueRange.location+valueRange.length, 1)];
valueRange.location += 1;
endRange = valueRange;
endRange.location = valueRange.location+valueRange.length;
endRange.length = [returnedString length]-endRange.location;
endRange = [returnedString rangeOfString:end options:NSCaseInsensitiveSearch range:endRange];
if (endRange.location==NSNotFound)
continue;
NSString *value = [returnedString substringWithRange:NSMakeRange(valueRange.location+valueRange.length, endRange.location-(valueRange.location+valueRange.length))];
[parameters setObject:value forKey:name];
} else {
break;
}
[pool drain];
}
#if MGMInstanceDebug
NSLog(@"%@", parameters);
#endif
NSArray *parametersKeys = [parameters allKeys];
NSMutableString *bodyString = [NSMutableString string];
for (int i=0; i<[parametersKeys count]; i++) {
if (i!=0)
[bodyString appendString:@"&"];
[bodyString appendFormat:@"%@=%@", [[parametersKeys objectAtIndex:i] addPercentEscapes], [[parameters objectForKey:[parametersKeys objectAtIndex:i]] addPercentEscapes]];
}
[request setHTTPBody:[bodyString dataUsingEncoding:NSUTF8StringEncoding]];
MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:request delegate:self];
[handler setFailWithError:@selector(index:didFailWithError:)];
[handler setFinish:@selector(indexDidFinish:)];
[handler setInvisible:MGMInstanceInvisible];
[connectionManager addHandler:handler];
} else if ([returnedString containsString:@"id=\"gaia_loginform\""]) {
if (webLoginTries>2) {
NSError *error = [NSError errorWithDomain:@"com.MrGeckosMedia.MGMInstance.Login" code:1 userInfo:[NSDictionary dictionaryWithObject:@"Unable to login. Please check your Credentials." forKey:NSLocalizedDescriptionKey]];
if (delegate!=nil && [delegate respondsToSelector:@selector(loginError:)]) {
[delegate loginError:error];
} else {
NSLog(@"Login Error: %@", error);
}
return;
}
webLoginTries++;
NSRange actionRange = [returnedString rangeOfString:@"<form"];
NSString *loginURL = [MGMLoginURL copy];
if (actionRange.location!=NSNotFound) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSString *string = [returnedString substringFromIndex:actionRange.location+actionRange.length];
actionRange = [string rangeOfString:@"action="];
if (actionRange.location!=NSNotFound) {
NSString *end = [string substringWithRange:NSMakeRange(actionRange.location+actionRange.length, 1)];
actionRange.location += 1;
string = [string substringFromIndex:actionRange.location+actionRange.length];
actionRange = [string rangeOfString:end];
[loginURL release];
loginURL = [[string substringWithRange:NSMakeRange(0, actionRange.location)] copy];
}
[pool drain];
}
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:loginURL]];
[loginURL release];
[request setHTTPMethod:MGMPostMethod];
[request setValue:MGMURLForm forHTTPHeaderField:MGMContentType];
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
NSRange range = NSMakeRange(0, [returnedString length]);
while (range.length>1) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSRange inputRange = [returnedString rangeOfString:@"<input " options:NSCaseInsensitiveSearch range:range];
if (inputRange.location!=NSNotFound) {
range.location = inputRange.location+inputRange.length;
range.length = [returnedString length]-range.location;
NSRange endInputRange = [returnedString rangeOfString:@">" options:NSCaseInsensitiveSearch range:range];
if (endInputRange.location==NSNotFound)
endInputRange.length = range.length;
else
endInputRange.length = endInputRange.location-range.location;
endInputRange.location = range.location;
NSRange nameRange = [returnedString rangeOfString:@"name=" options:NSCaseInsensitiveSearch range:endInputRange];
if (nameRange.location==NSNotFound)
continue;
NSString *end = [returnedString substringWithRange:NSMakeRange(nameRange.location+nameRange.length, 1)];
nameRange.location += 1;
NSRange endRange = nameRange;
endRange.location = nameRange.location+nameRange.length;
endRange.length = [returnedString length]-endRange.location;
endRange = [returnedString rangeOfString:end options:NSCaseInsensitiveSearch range:endRange];
if (endRange.location==NSNotFound)
continue;
NSString *name = [returnedString substringWithRange:NSMakeRange(nameRange.location+nameRange.length, endRange.location-(nameRange.location+nameRange.length))];
range.location = inputRange.location+inputRange.length;
range.length = [returnedString length]-range.location;
NSRange valueRange = [returnedString rangeOfString:@"value=" options:NSCaseInsensitiveSearch range:endInputRange];
if (valueRange.location==NSNotFound)
continue;
end = [returnedString substringWithRange:NSMakeRange(valueRange.location+valueRange.length, 1)];
valueRange.location += 1;
endRange = valueRange;
endRange.location = valueRange.location+valueRange.length;
endRange.length = [returnedString length]-endRange.location;
endRange = [returnedString rangeOfString:end options:NSCaseInsensitiveSearch range:endRange];
if (endRange.location==NSNotFound)
continue;
NSString *value = [returnedString substringWithRange:NSMakeRange(valueRange.location+valueRange.length, endRange.location-(valueRange.location+valueRange.length))];
[parameters setObject:value forKey:name];
} else {
break;
}
[pool drain];
}
if ([parameters objectForKey:@"PersistentCookie"]!=nil)
[parameters setObject:@"yes" forKey:@"PersistentCookie"];
if ([[parameters objectForKey:@"Email"] isEqual:@""])
[parameters setObject:(webLoginTries==2 ? [[user settingForKey:MGMUserName] stringByAppendingString:@"@gmail.com"] : [user settingForKey:MGMUserName]) forKey:@"Email"];
#if MGMInstanceDebug
NSLog(@"%@", parameters);
#endif
[parameters setObject:[user password] forKey:@"Passwd"];
NSArray *parametersKeys = [parameters allKeys];
NSMutableString *bodyString = [NSMutableString string];
for (int i=0; i<[parametersKeys count]; i++) {
if (i!=0)
[bodyString appendString:@"&"];
[bodyString appendFormat:@"%@=%@", [[parametersKeys objectAtIndex:i] addPercentEscapes], [[parameters objectForKey:[parametersKeys objectAtIndex:i]] addPercentEscapes]];
}
[request setHTTPBody:[bodyString dataUsingEncoding:NSUTF8StringEncoding]];
MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:request delegate:self];
[handler setFailWithError:@selector(index:didFailWithError:)];
[handler setFinish:@selector(indexDidFinish:)];
[handler setInvisible:MGMInstanceInvisible];
[connectionManager addHandler:handler];
} else if ([[[[theHandler request] URL] relativeString] containsString:@"NewPrivacyPolicy"]) {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://accounts.google.com/b/0/NewPrivacyPolicy"]];
[request setHTTPMethod:MGMPostMethod];
[request setValue:MGMURLForm forHTTPHeaderField:MGMContentType];
[request setHTTPBody:[@"service=grandcentral&continue=https://www.google.com/voice&submitbutton=OK,%20got%20it" dataUsingEncoding:NSUTF8StringEncoding]];
MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:request delegate:self];
[handler setFailWithError:@selector(index:didFailWithError:)];
[handler setFinish:@selector(indexDidFinish:)];
[handler setInvisible:MGMInstanceInvisible];
[connectionManager addHandler:handler];
} else {
NSString *string, *guser = @"", *phonesInfo = @"";
NSRange range;
#if MGMInstanceDebug
NSLog(@"Parsing rnr_se");
#endif
range = [returnedString rangeOfString:@"'_rnr_se': '"];
if (range.location!=NSNotFound) {
string = [returnedString substringFromIndex:range.location+range.length];
range = [string rangeOfString:@"',"];
if (range.location==NSNotFound) {
NSLog(@"failed 0001");
} else {
[rnr_se release];
rnr_se = [[[string substringWithRange:NSMakeRange(0, range.location)] addPercentEscapes] copy];
}
}
#if MGMInstanceDebug
NSLog(@"rnr_se = %@", rnr_se);
NSLog(@"Parsing Right Header");
#endif
range = [returnedString rangeOfString:@"<div id=guser"];
if (range.location!=NSNotFound) {
string = [returnedString substringFromIndex:range.location+range.length];
range = [string rangeOfString:@">"];
if (range.location==NSNotFound) {
NSLog(@"failed 0002");
} else {
string = [string substringFromIndex:range.location+range.length];
range = [string rangeOfString:@"</div>"];
if (range.location==NSNotFound) {
NSLog(@"failed 0002.1");
} else {
guser = [string substringWithRange:NSMakeRange(0, range.location)];
}
}
}
#if MGMInstanceDebug
NSLog(@"Parsing User Name");
#endif
range = [guser rangeOfString:@"<b"];
if (range.location!=NSNotFound) {
string = [guser substringFromIndex:range.location+range.length];
range = [string rangeOfString:@">"];
if (range.location==NSNotFound) {
NSLog(@"failed 0003");
} else {
string = [string substringFromIndex:range.location+range.length];
range = [string rangeOfString:@"</b>"];
if (range.location==NSNotFound) {
NSLog(@"failed 0003.1");
} else {
[userName release];
userName = [[string substringWithRange:NSMakeRange(0, range.location)] copy];
}
}
}
#if MGMInstanceDebug
NSLog(@"User Name = %@", userName);
NSLog(@"Parsing Google Number Header");
#endif
range = [returnedString rangeOfString:@"gc-header-info"];
if (range.location!=NSNotFound) {
string = [returnedString substringFromIndex:range.location+range.length];
range = [string rangeOfString:@">"];
if (range.location==NSNotFound) {
NSLog(@"failed 0002");
} else {
string = [string substringFromIndex:range.location+range.length];
range = [string rangeOfString:@"</div>"];
if (range.location==NSNotFound) {
NSLog(@"failed 0002.1");
} else {
guser = [string substringWithRange:NSMakeRange(0, range.location)];
}
}
}
#if MGMInstanceDebug
NSLog(@"Parsing Google Number");
#endif
range = [guser rangeOfString:@"href=\"#phones\""];
if (range.location!=NSNotFound) {
string = [guser substringFromIndex:range.location+range.length];
range = [string rangeOfString:@">"];
if (range.location==NSNotFound) {
NSLog(@"failed 0004");
} else {
string = [string substringFromIndex:range.location+range.length];
range = [string rangeOfString:@"</a>"];
if (range.location==NSNotFound) {
NSLog(@"failed 0004.1");
} else {
[userNumber release];
userNumber = [[[string substringWithRange:NSMakeRange(0, range.location)] phoneFormat] copy];
}
}
}
if ([returnedString containsString:@"cookie functionality"]) {
NSError *error = [NSError errorWithDomain:@"com.MrGeckosMedia.MGMInstance.Login" code:3 userInfo:[NSDictionary dictionaryWithObject:@"There is a problem with VoiceMac's Cookie system, please contact the developer via the help menu." forKey:NSLocalizedDescriptionKey]];
if (delegate!=nil && [delegate respondsToSelector:@selector(loginError:)]) {
[delegate loginError:error];
} else {
NSLog(@"Login Error: %@", error);
}
return;
} else if (![returnedString containsString:@"gc-header-did-display"] && ![userNumber isPhoneComplete]) {
NSError *error = [NSError errorWithDomain:@"com.MrGeckosMedia.MGMInstance.Login" code:2 userInfo:[NSDictionary dictionaryWithObject:@"Your Google Account does not appear to have a Google Number, please visit voice.google.com and setup one before continuing." forKey:NSLocalizedDescriptionKey]];
if (delegate!=nil && [delegate respondsToSelector:@selector(loginError:)]) {
[delegate loginError:error];
} else {
NSLog(@"Login Error: %@", error);
}
return;
}
[userAreacode release];
userAreacode = [[userNumber areaCode] copy];
#if MGMInstanceDebug
NSLog(@"Google Number = %@", userNumber);
NSLog(@"Areacode = %@", userAreacode);
NSLog(@"Parsing User Phones");
#endif
range = [returnedString rangeOfString:@"'phones': "];
if (range.location!=NSNotFound) {
string = [returnedString substringFromIndex:range.location+range.length];
range = [string rangeOfString:@",\n"];
if (range.location==NSNotFound) {
NSLog(@"failed 0005");
} else {
phonesInfo = [string substringWithRange:NSMakeRange(0, range.location)];
}
}
[self parseUserPhones:[phonesInfo parseJSON]];
#if MGMInstanceDebug
NSLog(@"User Phones = %@", userPhoneNumbers);
NSLog(@"Parsing XPCURL");
#endif
range = [returnedString rangeOfString:@"'xpcUrl': '"];
if (range.location!=NSNotFound) {
string = [returnedString substringFromIndex:range.location+range.length];
range = [string rangeOfString:@"'"];
if (range.location==NSNotFound) {
NSLog(@"failed 0008");
} else {
[XPCURL release];
XPCURL = [[string substringWithRange:NSMakeRange(0, range.location)] copy];
}
}
#if MGMInstanceDebug
NSLog(@"XPCURL = %@", XPCURL);
#endif
loggedIn = YES;
if (!checkingAccount) {
[contacts updateContacts];
[checkTimer invalidate];
[checkTimer release];
checkTimer = [[NSTimer scheduledTimerWithTimeInterval:15.0 target:self selector:@selector(checkTimer) userInfo:nil repeats:YES] retain];
[checkTimer fire];
[creditTimer invalidate];
[creditTimer release];
creditTimer = [[NSTimer scheduledTimerWithTimeInterval:60.0 target:self selector:@selector(creditTimer) userInfo:nil repeats:YES] retain];
[creditTimer fire];
}
if (delegate!=nil && [delegate respondsToSelector:@selector(loginSuccessful)]) [delegate loginSuccessful];
}
}
- (void)cancelVerification {
[verificationParameters release];
verificationParameters = nil;
NSError *error = [NSError errorWithDomain:@"com.MrGeckosMedia.MGMInstance.Login" code:54 userInfo:[NSDictionary dictionaryWithObject:@"Unable to login. Verification was canceled." forKey:NSLocalizedDescriptionKey]];
if (delegate!=nil && [delegate respondsToSelector:@selector(loginError:)]) {
[delegate loginError:error];
} else {
NSLog(@"Login Error: %@", error);
}
}
- (void)verifyWithCode:(NSString *)theCode {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:verificationURL];
[request setHTTPMethod:MGMPostMethod];
[request setValue:MGMURLForm forHTTPHeaderField:MGMContentType];
[verificationParameters setObject:theCode forKey:@"smsUserPin"];
[verificationParameters setObject:@"1" forKey:@"pstMsg"];
[verificationParameters removeObjectForKey:@"retry"];
[verificationParameters removeObjectForKey:@"smsSend"];
NSArray *parametersKeys = [verificationParameters allKeys];
NSMutableString *bodyString = [NSMutableString string];
for (int i=0; i<[parametersKeys count]; i++) {
if (i!=0)
[bodyString appendString:@"&"];
[bodyString appendFormat:@"%@=%@", [[parametersKeys objectAtIndex:i] addPercentEscapes], [[verificationParameters objectForKey:[parametersKeys objectAtIndex:i]] addPercentEscapes]];
}
[request setHTTPBody:[bodyString dataUsingEncoding:NSUTF8StringEncoding]];
MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:request delegate:self];
[handler setFailWithError:@selector(index:didFailWithError:)];
[handler setFinish:@selector(indexDidFinish:)];
[handler setInvisible:MGMInstanceInvisible];
[connectionManager addHandler:handler];
[verificationParameters release];
verificationParameters = nil;
}
- (BOOL)isLoggedIn {
return loggedIn;
}
- (void)checkPhones {
MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:MGMPhonesURL]] delegate:self];
[handler setFinish:@selector(phonesFinished:)];
[handler setInvisible:MGMInstanceInvisible];
[connectionManager addHandler:handler];
}
- (void)phonesFinished:(MGMURLBasicHandler *)theHandler {
MGMXMLElement *XML = [(MGMXMLDocument *)[[[MGMXMLDocument alloc] initWithData:[theHandler data] options:MGMXMLDocumentTidyXML error:nil] autorelease] rootElement];
NSDictionary *info = [[[[XML elementsForName:@"json"] objectAtIndex:0] stringValue] parseJSON];
[self parseUserPhones:[info objectForKey:@"phones"]];
if ([delegate respondsToSelector:@selector(updatedUserPhones)]) [delegate updatedUserPhones];
}
- (void)parseUserPhones:(NSDictionary *)thePhones {
if (thePhones==nil)
return;
NSArray *phones = [thePhones allKeys];
[userPhoneNumbers release];
userPhoneNumbers = [NSMutableArray new];
for (int i=0; i<[phones count]; i++) {
NSDictionary *phoneInfo = [thePhones objectForKey:[phones objectAtIndex:i]];
if ([[phoneInfo objectForKey:@"verified"] intValue]==1) {
NSMutableDictionary *phone = [NSMutableDictionary dictionary];
[phone setObject:[[phoneInfo objectForKey:MGMPhoneNumber] phoneFormat] forKey:MGMPhoneNumber];
[phone setObject:[[phoneInfo objectForKey:MGMName] flattenHTML] forKey:MGMName];
[phone setObject:[phoneInfo objectForKey:MGMType] forKey:MGMType];
[userPhoneNumbers addObject:phone];
}
}
}
- (void)xpcFinished:(MGMURLBasicHandler *)theHandler {
NSString *returnedString = [theHandler string];
NSRange range = [returnedString rangeOfString:@"new _cd('"];
if (range.location!=NSNotFound) {
NSString *string = [returnedString substringFromIndex:range.location+range.length];
range = [string rangeOfString:@"'"];
if (range.location==NSNotFound) NSLog(@"failed 0009");
[XPCCD release];
XPCCD = [[[string substringWithRange:NSMakeRange(0, range.location)] addPercentEscapes] copy];
}
#if MGMInstanceDebug
NSLog(@"XPCCD = %@", XPCCD);
#endif
[checkTimer fire];
}
- (void)checkTimer {
if (XPCCD) {
MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:[XPCURL stringByAppendingString:MGMCheckPath], XPCCD]]] delegate:self];
[handler setFinish:@selector(checkFinished:)];
[handler setInvisible:MGMInstanceInvisible];
[connectionManager addHandler:handler];
} else {
MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[XPCURL stringByAppendingString:MGMXPCPath]]] delegate:self];
[handler setFinish:@selector(xpcFinished:)];
[handler setInvisible:MGMInstanceInvisible];
[connectionManager addHandler:handler];
}
}
- (void)checkFinished:(MGMURLBasicHandler *)theHandler {
NSDictionary *returnDic = [[theHandler data] parseJSON];
if (returnDic!=nil) {
if ([[returnDic objectForKey:@"ok"] intValue]!=0) {
NSDictionary *currentUnreadCounts = [[returnDic objectForKey:@"data"] objectForKey:@"unreadCounts"];
#if MGMInstanceDebug
NSLog(@"unreadCounts = %@", currentUnreadCounts);
#endif
int inboxCount = [[currentUnreadCounts objectForKey:MGMUCInbox] intValue];
//int recordedCount = [[currentUnreadCounts objectForKey:MGMUCRecorded] intValue];
int voicemailCount = [[currentUnreadCounts objectForKey:MGMUCVoicemail] intValue];
int smsCount = [[currentUnreadCounts objectForKey:MGMUCSMS] intValue];
if ([[unreadCounts objectForKey:MGMUCInbox] intValue]!=inboxCount)
if (delegate!=nil && [delegate respondsToSelector:@selector(updateUnreadCount:)]) [delegate updateUnreadCount:inboxCount];
if (voicemailCount>0)
if (delegate!=nil && [delegate respondsToSelector:@selector(updateVoicemail)]) [delegate updateVoicemail];
if (smsCount>0)
if (delegate!=nil && [delegate respondsToSelector:@selector(updateSMS)]) [delegate updateSMS];
[unreadCounts release];
unreadCounts = [currentUnreadCounts copy];
}
}
}
- (void)creditTimer {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:MGMCreditURL]];
[request setHTTPMethod:MGMPostMethod];
[request setValue:MGMURLForm forHTTPHeaderField:MGMContentType];
[request setHTTPBody:[[NSString stringWithFormat:@"_rnr_se=%@", rnr_se] dataUsingEncoding:NSUTF8StringEncoding]];
MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:request delegate:self];
[handler setFinish:@selector(creditFinished:)];
[handler setInvisible:MGMInstanceInvisible];
[connectionManager addHandler:handler];
}
- (void)creditFinished:(MGMURLBasicHandler *)theHandler {
NSString *credit = [[[theHandler data] parseJSON] objectForKey:@"formattedCredit"];
#if MGMInstanceDebug
NSLog(@"Credit = %@", credit);
#endif
if (delegate!=nil && [delegate respondsToSelector:@selector(updateCredit:)]) [delegate updateCredit:credit];
}
- (void)placeCall:(NSString *)thePhoneNumber usingPhone:(int)thePhone delegate:(id)theDelegate {
[self placeCall:thePhoneNumber usingPhone:thePhone delegate:theDelegate didFailWithError:@selector(call:didFailWithError:) didFinish:@selector(callDidFinish:)];
}
- (void)placeCall:(NSString *)thePhoneNumber usingPhone:(int)thePhone delegate:(id)theDelegate didFailWithError:(SEL)didFailWithError didFinish:(SEL)didFinish {
MGMDelegateInfo *info = [MGMDelegateInfo infoWithDelegate:theDelegate];
[info setFinish:didFinish];
[info setFailWithError:didFailWithError];
[info setPhoneNumbers:[NSArray arrayWithObject:thePhoneNumber]];
[info setPhone:[userPhoneNumbers objectAtIndex:thePhone]];
if (thePhoneNumber==nil || [thePhoneNumber isEqual:@""]) {
NSMethodSignature *signature = [theDelegate methodSignatureForSelector:didFailWithError];
if (signature!=nil) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:didFailWithError];
[invocation setArgument:&info atIndex:2];
NSError *error = [NSError errorWithDomain:@"com.MrGeckosMedia.MGMInstance.Call" code:1 userInfo:nil];
[invocation setArgument:&error atIndex:3];
[invocation invokeWithTarget:theDelegate];
}
return;
}
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:MGMCallURL]];
[request setHTTPMethod:MGMPostMethod];
[request setValue:MGMURLForm forHTTPHeaderField:MGMContentType];
[request setHTTPBody:[[NSString stringWithFormat:@"outgoingNumber=%@&forwardingNumber=%@&subscriberNumber=undefined&phoneType=%@&remember=1&_rnr_se=%@", thePhoneNumber, [[userPhoneNumbers objectAtIndex:thePhone] objectForKey:MGMPhoneNumber], [[userPhoneNumbers objectAtIndex:thePhone] objectForKey:MGMType], rnr_se] dataUsingEncoding:NSUTF8StringEncoding]];
MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:request delegate:self];
[handler setFailWithError:@selector(call:didFailWithError:)];
[handler setFinish:@selector(callDidFinish:)];
[handler setInvisible:MGMInstanceInvisible];
[handler setObject:info];
[connectionManager addHandler:handler];
}
- (void)cancelCallWithDelegate:(id)theDelegate {
[self cancelCallWithDelegate:theDelegate didFailWithError:@selector(callCancel:didFailWithError:) didFinish:@selector(callCancelDidFinish:)];
}
- (void)cancelCallWithDelegate:(id)theDelegate didFailWithError:(SEL)didFailWithError didFinish:(SEL)didFinish {
MGMDelegateInfo *info = [MGMDelegateInfo infoWithDelegate:theDelegate];
[info setFinish:didFinish];
[info setFailWithError:didFailWithError];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:MGMCallCancelURL]];
[request setHTTPMethod:MGMPostMethod];
[request setValue:MGMURLForm forHTTPHeaderField:MGMContentType];
[request setHTTPBody:[[NSString stringWithFormat:@"outgoingNumber=undefined&forwardingNumber=undefined&cancelType=C2C&_rnr_se=%@", rnr_se] dataUsingEncoding:NSUTF8StringEncoding]];
MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:request delegate:self];
[handler setFailWithError:@selector(call:didFailWithError:)];
[handler setFinish:@selector(callDidFinish:)];
[handler setInvisible:MGMInstanceInvisible];
[handler setObject:info];
[connectionManager addHandler:handler];
}
- (void)call:(MGMURLBasicHandler *)theHandler didFailWithError:(NSError *)theError {
MGMDelegateInfo *info = [theHandler object];
BOOL displayError = YES;
if ([info failWithError]!=nil) {
NSMethodSignature *signature = [[info delegate] methodSignatureForSelector:[info failWithError]];
if (signature!=nil) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:[info failWithError]];
[invocation setArgument:&info atIndex:2];
[invocation setArgument:&theError atIndex:3];
[invocation invokeWithTarget:[info delegate]];
displayError = NO;
}
}
if (displayError)
NSLog(@"MGMInstance Call Error: %@", theError);
}
- (void)callDidFinish:(MGMURLBasicHandler *)theHandler {
NSDictionary *infoDic = [[theHandler data] parseJSON];
MGMDelegateInfo *thisInfo = [theHandler object];
if ([[infoDic objectForKey:@"ok"] boolValue]) {
#if MGMInstanceDebug
NSLog(@"MGMInstance Did Call %@", infoDic);
#endif
if ([thisInfo finish]!=nil) {
NSMethodSignature *signature = [[thisInfo delegate] methodSignatureForSelector:[thisInfo finish]];
if (signature!=nil) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:[thisInfo finish]];
[invocation setArgument:&thisInfo atIndex:2];
[invocation invokeWithTarget:[thisInfo delegate]];
}
}
} else {
NSDictionary *info = nil;
if ([infoDic objectForKey:@"error"]!=nil)
info = [NSDictionary dictionaryWithObject:[infoDic objectForKey:@"error"] forKey:NSLocalizedDescriptionKey];
NSError *error = [NSError errorWithDomain:@"com.MrGeckosMedia.VoiceBase.Call" code:1 userInfo:info];
if ([thisInfo failWithError]!=nil) {
NSMethodSignature *signature = [[thisInfo delegate] methodSignatureForSelector:[thisInfo failWithError]];
if (signature!=nil) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:[thisInfo failWithError]];
[invocation setArgument:&thisInfo atIndex:2];
[invocation setArgument:&error atIndex:3];
[invocation invokeWithTarget:[thisInfo delegate]];
} else {
NSLog(@"MGMInstance Call Error: %@", error);
}
} else {
NSLog(@"MGMInstance Call Error: %@", error);
}
}
}
@end