#import #import NSString *const TGShareAppleMapsHost = @"maps.apple.com"; NSString *const TGShareAppleMapsPath = @"/maps"; NSString *const TGShareAppleMapsLatLonKey = @"ll"; NSString *const TGShareAppleMapsNameKey = @"q"; NSString *const TGShareAppleMapsAddressKey = @"address"; NSString *const TGShareAppleMapsIdKey = @"auid"; NSString *const TGShareAppleMapsProvider = @"apple"; NSString *const TGShareFoursquareHost = @"foursquare.com"; NSString *const TGShareFoursquareVenuePath = @"/v"; NSString *const TGShareFoursquareVenueEndpointUrl = @"https://api.foursquare.com/v2/venues/"; NSString *const TGShareFoursquareClientId = @"BN3GWQF1OLMLKKQTFL0OADWD1X1WCDNISPPOT1EMMUYZTQV1"; NSString *const TGShareFoursquareClientSecret = @"WEEZHCKI040UVW2KWW5ZXFAZ0FMMHKQ4HQBWXVSX4WXWBWYN"; NSString *const TGShareFoursquareVersion = @"20150326"; NSString *const TGShareFoursquareVenuesCountLimit = @"25"; NSString *const TGShareFoursquareLocale = @"en"; NSString *const TGShareFoursquareProvider = @"foursquare"; NSString *const TGShareGoogleShortenerEndpointUrl = @"https://www.googleapis.com/urlshortener/v1/url"; NSString *const TGShareGoogleAPIKey = @"AIzaSyBCTH4aAdvi0MgDGlGNmQAaFS8GTNBrfj4"; NSString *const TGShareGoogleMapsShortHost = @"goo.gl"; NSString *const TGShareGoogleMapsShortPath = @"/maps"; NSString *const TGShareGoogleMapsHost = @"google.com"; NSString *const TGShareGoogleMapsSearchPath = @"maps/search"; NSString *const TGShareGoogleMapsPlacePath = @"maps/place"; NSString *const TGShareGoogleProvider = @"google"; @implementation TGShareLocationResult - (instancetype)initWithLatitude:(double)latitude longitude:(double)longitude title:(NSString *)title address:(NSString *)address provider:(NSString *)provider venueId:(NSString *)venueId venueType:(NSString *)venueType { self = [super init]; if (self != nil) { _latitude = latitude; _longitude = longitude; _title = title; _address = address; _provider = provider; _venueId = venueId; _venueType = venueType; } return self; } @end @interface TGQueryStringComponent : NSObject { @private NSString *_key; NSString *_value; } @property (readwrite, nonatomic, retain) id key; @property (readwrite, nonatomic, retain) id value; - (id)initWithKey:(id)key value:(id)value; - (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding; @end NSString * TGURLEncodedStringFromStringWithEncoding(NSString *string, NSStringEncoding encoding) { static NSString * const kAFLegalCharactersToBeEscaped = @"?!@#$^&%*+=,:;'\"`<>()[]{}/\\|~ "; NSString *unescapedString = [string stringByReplacingPercentEscapesUsingEncoding:encoding]; if (unescapedString) { string = unescapedString; } return (__bridge NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, NULL, (__bridge CFStringRef)kAFLegalCharactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(encoding)); } @implementation TGQueryStringComponent @synthesize key = _key; @synthesize value = _value; - (id)initWithKey:(id)key value:(id)value { self = [super init]; if (!self) { return nil; } self.key = key; self.value = value; return self; } - (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding { return [NSString stringWithFormat:@"%@=%@", self.key, TGURLEncodedStringFromStringWithEncoding([self.value description], stringEncoding)]; } @end static NSString * TGQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding stringEncoding); static NSArray * TGQueryStringComponentsFromKeyAndValue(NSString *key, id value); NSArray * TGQueryStringComponentsFromKeyAndDictionaryValue(NSString *key, NSDictionary *value); NSArray * TGQueryStringComponentsFromKeyAndArrayValue(NSString *key, NSArray *value); static NSString * TGQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding stringEncoding) { NSMutableArray *mutableComponents = [NSMutableArray array]; for (TGQueryStringComponent *component in TGQueryStringComponentsFromKeyAndValue(nil, parameters)) { [mutableComponents addObject:[component URLEncodedStringValueWithEncoding:stringEncoding]]; } return [mutableComponents componentsJoinedByString:@"&"]; } static NSArray * TGQueryStringComponentsFromKeyAndValue(NSString *key, id value) { NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; if([value isKindOfClass:[NSDictionary class]]) { [mutableQueryStringComponents addObjectsFromArray:TGQueryStringComponentsFromKeyAndDictionaryValue(key, value)]; } else if([value isKindOfClass:[NSArray class]]) { [mutableQueryStringComponents addObjectsFromArray:TGQueryStringComponentsFromKeyAndArrayValue(key, value)]; } else { [mutableQueryStringComponents addObject:[[TGQueryStringComponent alloc] initWithKey:key value:value]]; } return mutableQueryStringComponents; } NSArray * TGQueryStringComponentsFromKeyAndDictionaryValue(NSString *key, NSDictionary *value){ NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; [value enumerateKeysAndObjectsUsingBlock:^(id nestedKey, id nestedValue, __unused BOOL *stop) { [mutableQueryStringComponents addObjectsFromArray:TGQueryStringComponentsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; }]; return mutableQueryStringComponents; } NSArray * TGQueryStringComponentsFromKeyAndArrayValue(NSString *key, NSArray *value) { NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; [value enumerateObjectsUsingBlock:^(id nestedValue, __unused NSUInteger idx, __unused BOOL *stop) { [mutableQueryStringComponents addObjectsFromArray:TGQueryStringComponentsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)]; }]; return mutableQueryStringComponents; } @implementation TGShareLocationSignals + (MTSignal *)locationMessageContentForURL:(NSURL *)url { if ([self isAppleMapsURL:url]) return [self _appleMapsLocationContentForURL:url]; else if ([self isFoursquareURL:url]) return [self _foursquareLocationForURL:url]; return [MTSignal single:nil]; } + (MTSignal *)_appleMapsLocationContentForURL:(NSURL *)url { NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:false]; NSArray *queryItems = urlComponents.queryItems; NSString *latLon = nil; NSString *name = nil; NSString *address = nil; NSString *venueId = nil; for (NSURLQueryItem *queryItem in queryItems) { if ([queryItem.name isEqualToString:TGShareAppleMapsLatLonKey]) { latLon = queryItem.value; } else if ([queryItem.name isEqualToString:TGShareAppleMapsNameKey]) { if (![queryItem.value isEqualToString:latLon]) name = queryItem.value; } else if ([queryItem.name isEqualToString:TGShareAppleMapsAddressKey]) { address = queryItem.value; } else if ([queryItem.name isEqualToString:TGShareAppleMapsIdKey]) { venueId = queryItem.value; } } if (latLon == nil) return [MTSignal fail:nil]; NSArray *coordComponents = [latLon componentsSeparatedByString:@","]; if (coordComponents.count != 2) return [MTSignal fail:nil]; double latitude = [coordComponents.firstObject floatValue]; double longitude = [coordComponents.lastObject floatValue]; return [MTSignal single:[[TGShareLocationResult alloc] initWithLatitude:latitude longitude:longitude title:name address:address provider:TGShareAppleMapsProvider venueId:venueId venueType:@""]]; } + (MTSignal *)_foursquareLocationForURL:(NSURL *)url { NSArray *pathComponents = url.pathComponents; NSString *venueId = nil; for (NSString *component in pathComponents) { if (component.length == 24) { venueId = component; break; } } if (venueId == nil) return [MTSignal fail:nil]; NSString *urlString = [NSString stringWithFormat:@"%@?%@", [TGShareFoursquareVenueEndpointUrl stringByAppendingPathComponent:venueId], TGQueryStringFromParametersWithEncoding([self _defaultParametersForFoursquare], NSUTF8StringEncoding)]; return [[MTHttpRequestOperation dataForHttpUrl:[NSURL URLWithString:urlString]] mapToSignal:^id(MTHttpResponse *response) { NSData *data = response.data; id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; if (![json respondsToSelector:@selector(objectForKey:)]) return nil; NSDictionary *venue = json[@"response"][@"venue"]; if (![venue respondsToSelector:@selector(objectForKey:)]) return nil; NSString *name = venue[@"name"]; NSDictionary *location = venue[@"location"]; NSString *address = location[@"address"]; if (address.length == 0) address = location[@"crossStreet"]; if (address.length == 0) address = location[@"city"]; if (address.length == 0) address = location[@"country"]; if (address.length == 0) address = @""; double latitude = [location[@"lat"] doubleValue]; double longitude = [location[@"lng"] doubleValue]; if (name.length == 0) return [MTSignal fail:nil]; return [MTSignal single:[[TGShareLocationResult alloc] initWithLatitude:latitude longitude:longitude title:name address:address provider:TGShareFoursquareProvider venueId:venueId venueType:@""]]; }]; } + (MTSignal *)_googleMapsLocationForURL:(NSURL *)url { NSString *shortenerUrl = [NSString stringWithFormat:@"%@?fields=longUrl,status&shortUrl=%@&key=%@", TGShareGoogleShortenerEndpointUrl, TGURLEncodedStringFromStringWithEncoding(url.absoluteString, NSUTF8StringEncoding), TGShareGoogleAPIKey]; MTSignal *shortenerSignal = [[MTHttpRequestOperation dataForHttpUrl:[NSURL URLWithString:shortenerUrl]] mapToSignal:^MTSignal *(MTHttpResponse *response) { NSData *data = response.data; id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; if (![json respondsToSelector:@selector(objectForKey:)]) return [MTSignal fail:nil]; NSString *status = json[@"status"]; if (![status isEqualToString:@"OK"]) return [MTSignal fail:nil]; return [MTSignal single:[NSURL URLWithString:json[@"longUrl"]]]; }]; MTSignal *(^processLongUrl)(NSURL *) = ^MTSignal *(NSURL *longUrl) { NSArray *pathComponents = longUrl.pathComponents; bool isSearch = false; double latitude = 0.0; double longitude = 0.0; for (NSString *component in pathComponents) { if ([component isEqualToString:@"search"]) { isSearch = true; } else if ([component isEqualToString:@"place"]) { return [MTSignal fail:nil]; } else if (isSearch && [component containsString:@","]) { NSArray *coordinates = [component componentsSeparatedByString:@","]; if (coordinates.count == 2) { latitude = [coordinates.firstObject doubleValue]; longitude = [coordinates.lastObject doubleValue]; break; } } } if (fabs(latitude) < DBL_EPSILON && fabs(longitude) < DBL_EPSILON) return [MTSignal fail:nil]; return [MTSignal single:[[TGShareLocationResult alloc] initWithLatitude:latitude longitude:longitude title:nil address:nil provider:nil venueId:nil venueType:nil]]; }; MTSignal *signal = nil; if ([self _isShortGoogleMapsURL:url]) { signal = [shortenerSignal mapToSignal:^MTSignal *(NSURL *longUrl) { return processLongUrl(longUrl); }]; } else { signal = processLongUrl(url); } return [signal catch:^MTSignal *(id error) { return [MTSignal single:url.absoluteString]; }]; } + (NSDictionary *)_defaultParametersForFoursquare { return @ { @"v": TGShareFoursquareVersion, @"locale": TGShareFoursquareLocale, @"client_id": TGShareFoursquareClientId, @"client_secret" :TGShareFoursquareClientSecret }; } + (bool)isLocationURL:(NSURL *)url { return [self isAppleMapsURL:url] || [self isFoursquareURL:url]; } + (bool)isAppleMapsURL:(NSURL *)url { return ([url.host isEqualToString:TGShareAppleMapsHost] && [url.path isEqualToString:TGShareAppleMapsPath]); } + (bool)isFoursquareURL:(NSURL *)url { return ([url.host isEqualToString:TGShareFoursquareHost] && [url.path hasPrefix:TGShareFoursquareVenuePath]); } + (bool)_isShortGoogleMapsURL:(NSURL *)url { return ([url.host isEqualToString:TGShareGoogleMapsShortHost] && [url.path hasPrefix:TGShareGoogleMapsShortPath]); } + (bool)_isLongGoogleMapsURL:(NSURL *)url { return ([url.host isEqualToString:TGShareGoogleMapsHost] && ([url.path hasPrefix:TGShareGoogleMapsSearchPath] || [url.path hasPrefix:TGShareGoogleMapsPlacePath])); } + (bool)isGoogleMapsURL:(NSURL *)url { return [self _isShortGoogleMapsURL:url] || [self _isLongGoogleMapsURL:url]; } @end