extendi-instagram 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +6 -0
  5. data/.yardopts +9 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE.md +30 -0
  8. data/PATENTS.md +23 -0
  9. data/README.md +260 -0
  10. data/Rakefile +27 -0
  11. data/instagram.gemspec +50 -0
  12. data/lib/faraday/loud_logger.rb +78 -0
  13. data/lib/faraday/oauth2.rb +45 -0
  14. data/lib/faraday/raise_http_exception.rb +73 -0
  15. data/lib/instagram/api.rb +31 -0
  16. data/lib/instagram/client/comments.rb +62 -0
  17. data/lib/instagram/client/embedding.rb +28 -0
  18. data/lib/instagram/client/geographies.rb +29 -0
  19. data/lib/instagram/client/likes.rb +58 -0
  20. data/lib/instagram/client/locations.rb +75 -0
  21. data/lib/instagram/client/media.rb +82 -0
  22. data/lib/instagram/client/subscriptions.rb +211 -0
  23. data/lib/instagram/client/tags.rb +59 -0
  24. data/lib/instagram/client/users.rb +310 -0
  25. data/lib/instagram/client/utils.rb +28 -0
  26. data/lib/instagram/client.rb +21 -0
  27. data/lib/instagram/configuration.rb +125 -0
  28. data/lib/instagram/connection.rb +31 -0
  29. data/lib/instagram/error.rb +34 -0
  30. data/lib/instagram/oauth.rb +36 -0
  31. data/lib/instagram/request.rb +83 -0
  32. data/lib/instagram/response.rb +22 -0
  33. data/lib/instagram/version.rb +3 -0
  34. data/lib/instagram.rb +27 -0
  35. data/spec/faraday/response_spec.rb +101 -0
  36. data/spec/fixtures/access_token.json +9 -0
  37. data/spec/fixtures/approve_user.json +8 -0
  38. data/spec/fixtures/block_user.json +8 -0
  39. data/spec/fixtures/deny_user.json +8 -0
  40. data/spec/fixtures/follow_user.json +8 -0
  41. data/spec/fixtures/followed_by.json +1 -0
  42. data/spec/fixtures/follows.json +1 -0
  43. data/spec/fixtures/geography_recent_media.json +1 -0
  44. data/spec/fixtures/liked_media.json +1 -0
  45. data/spec/fixtures/location.json +1 -0
  46. data/spec/fixtures/location_recent_media.json +1 -0
  47. data/spec/fixtures/location_search.json +1 -0
  48. data/spec/fixtures/location_search_facebook.json +1 -0
  49. data/spec/fixtures/media.json +1 -0
  50. data/spec/fixtures/media_comment.json +1 -0
  51. data/spec/fixtures/media_comment_deleted.json +1 -0
  52. data/spec/fixtures/media_comments.json +1 -0
  53. data/spec/fixtures/media_liked.json +1 -0
  54. data/spec/fixtures/media_likes.json +1 -0
  55. data/spec/fixtures/media_popular.json +1 -0
  56. data/spec/fixtures/media_search.json +1 -0
  57. data/spec/fixtures/media_shortcode.json +1 -0
  58. data/spec/fixtures/media_unliked.json +1 -0
  59. data/spec/fixtures/mikeyk.json +1 -0
  60. data/spec/fixtures/oembed.json +14 -0
  61. data/spec/fixtures/recent_media.json +1 -0
  62. data/spec/fixtures/relationship.json +9 -0
  63. data/spec/fixtures/requested_by.json +12 -0
  64. data/spec/fixtures/shayne.json +1 -0
  65. data/spec/fixtures/subscription.json +12 -0
  66. data/spec/fixtures/subscription_deleted.json +1 -0
  67. data/spec/fixtures/subscription_payload.json +14 -0
  68. data/spec/fixtures/subscriptions.json +22 -0
  69. data/spec/fixtures/tag.json +1 -0
  70. data/spec/fixtures/tag_recent_media.json +1 -0
  71. data/spec/fixtures/tag_search.json +1 -0
  72. data/spec/fixtures/unblock_user.json +8 -0
  73. data/spec/fixtures/unfollow_user.json +8 -0
  74. data/spec/fixtures/user_media_feed.json +1 -0
  75. data/spec/fixtures/user_search.json +1 -0
  76. data/spec/instagram/api_spec.rb +285 -0
  77. data/spec/instagram/client/comments_spec.rb +71 -0
  78. data/spec/instagram/client/embedding_spec.rb +36 -0
  79. data/spec/instagram/client/geography_spec.rb +37 -0
  80. data/spec/instagram/client/likes_spec.rb +66 -0
  81. data/spec/instagram/client/locations_spec.rb +127 -0
  82. data/spec/instagram/client/media_spec.rb +99 -0
  83. data/spec/instagram/client/subscriptions_spec.rb +174 -0
  84. data/spec/instagram/client/tags_spec.rb +79 -0
  85. data/spec/instagram/client/users_spec.rb +432 -0
  86. data/spec/instagram/client/utils_spec.rb +32 -0
  87. data/spec/instagram/client_spec.rb +23 -0
  88. data/spec/instagram/request_spec.rb +56 -0
  89. data/spec/instagram_spec.rb +109 -0
  90. data/spec/spec_helper.rb +71 -0
  91. metadata +322 -0
@@ -0,0 +1,78 @@
1
+ require 'faraday'
2
+ #if using typhoeus as the adapter uncomment these two requires to avoid seeing "Ethon::Errors::InvalidOption: The option: disable_ssl_peer_verification is invalid." (https://github.com/typhoeus/typhoeus/issues/270)
3
+ #require 'typhoeus'
4
+ #require 'typhoeus/adapters/faraday'
5
+
6
+ # @private
7
+ module FaradayMiddleware
8
+ # @private
9
+ class LoudLogger < Faraday::Middleware
10
+ extend Forwardable
11
+ def_delegators :@logger, :debug, :info, :warn, :error, :fatal
12
+
13
+ def initialize(app, options = {})
14
+ @app = app
15
+ @logger = options.fetch(:logger) {
16
+ require 'logger'
17
+ ::Logger.new($stdout)
18
+ }
19
+ end
20
+
21
+ def call(env)
22
+ start_time = Time.now
23
+ info { request_info(env) }
24
+ debug { request_debug(env) }
25
+ @app.call(env).on_complete do
26
+ end_time = Time.now
27
+ response_time = end_time - start_time
28
+ info { response_info(env, response_time) }
29
+ debug { response_debug(env) }
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def filter(output)
36
+ if ENV['INSTAGRAM_GEM_REDACT']
37
+ output = output.to_s.gsub(/client_id=[a-zA-Z0-9]*/,'client_id=[CLIENT-ID]')
38
+ output = output.to_s.gsub(/access_token=[a-zA-Z0-9]*/,'access_token=[ACCESS-TOKEN]')
39
+ else
40
+ output
41
+ end
42
+ end
43
+
44
+ def request_info(env)
45
+ "Started %s request to: %s" % [ env[:method].to_s.upcase, filter(env[:url]) ]
46
+ end
47
+
48
+ def response_info(env, response_time)
49
+ "Response from %s; Status: %d; Time: %.1fms" % [ filter(env[:url]), env[:status], (response_time * 1_000.0) ]
50
+ end
51
+
52
+ def request_debug(env)
53
+ debug_message("Request", env[:request_headers], env[:body])
54
+ end
55
+
56
+ def response_debug(env)
57
+ debug_message("Response", env[:response_headers], env[:body])
58
+ end
59
+
60
+ def debug_message(name, headers, body)
61
+ <<-MESSAGE.gsub(/^ +([^ ])/m, '\\1')
62
+ #{name} Headers:
63
+ ----------------
64
+ #{format_headers(headers)}
65
+
66
+ #{name} Body:
67
+ -------------
68
+ #{filter(body)}
69
+ MESSAGE
70
+ end
71
+
72
+ def format_headers(headers)
73
+ length = headers.map {|k,v| k.to_s.size }.max
74
+ headers.map { |name, value| "#{name.to_s.ljust(length)} : #{filter(value)}" }.join("\n")
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,45 @@
1
+ require 'faraday'
2
+ #if using typhoeus as the adapter uncomment these two requires to avoid seeing "Ethon::Errors::InvalidOption: The option: disable_ssl_peer_verification is invalid." (https://github.com/typhoeus/typhoeus/issues/270)
3
+ #require 'typhoeus'
4
+ #require 'typhoeus/adapters/faraday'
5
+
6
+ # @private
7
+ module FaradayMiddleware
8
+ # @private
9
+ class InstagramOAuth2 < Faraday::Middleware
10
+ def call(env)
11
+
12
+ if env[:method] == :get or env[:method] == :delete
13
+ if env[:url].query.nil?
14
+ query = {}
15
+ else
16
+ query = Faraday::Utils.parse_query(env[:url].query)
17
+ end
18
+
19
+ if @access_token and not query["client_secret"]
20
+ env[:url].query = Faraday::Utils.build_query(query.merge(:access_token => @access_token))
21
+ env[:request_headers] = env[:request_headers].merge('Authorization' => "Token token=\"#{@access_token}\"")
22
+ elsif @client_id
23
+ env[:url].query = Faraday::Utils.build_query(query.merge(:client_id => @client_id))
24
+ end
25
+ else
26
+ if @access_token and not env[:body] && env[:body][:client_secret]
27
+ env[:body] = {} if env[:body].nil?
28
+ env[:body] = env[:body].merge(:access_token => @access_token)
29
+ env[:request_headers] = env[:request_headers].merge('Authorization' => "Token token=\"#{@access_token}\"")
30
+ elsif @client_id
31
+ env[:body] = env[:body].merge(:client_id => @client_id)
32
+ end
33
+ end
34
+
35
+
36
+ @app.call env
37
+ end
38
+
39
+ def initialize(app, client_id, access_token=nil)
40
+ @app = app
41
+ @client_id = client_id
42
+ @access_token = access_token
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,73 @@
1
+ require 'faraday'
2
+ #if using typhoeus as the adapter uncomment these two requires to avoid seeing "Ethon::Errors::InvalidOption: The option: disable_ssl_peer_verification is invalid." (https://github.com/typhoeus/typhoeus/issues/270)
3
+ #require 'typhoeus'
4
+ #require 'typhoeus/adapters/faraday'
5
+
6
+ # @private
7
+ module FaradayMiddleware
8
+ # @private
9
+ class RaiseHttpException < Faraday::Middleware
10
+ def call(env)
11
+ @app.call(env).on_complete do |response|
12
+ case response[:status].to_i
13
+ when 400
14
+ raise Instagram::BadRequest, error_message_400(response)
15
+ when 403
16
+ raise Instagram::Forbidden, error_message_400(response)
17
+ when 404
18
+ raise Instagram::NotFound, error_message_400(response)
19
+ when 429
20
+ raise Instagram::TooManyRequests, error_message_400(response)
21
+ when 500
22
+ raise Instagram::InternalServerError, error_message_500(response, "Something is technically wrong.")
23
+ when 502
24
+ raise Instagram::BadGateway, error_message_500(response, "The server returned an invalid or incomplete response.")
25
+ when 503
26
+ raise Instagram::ServiceUnavailable, error_message_500(response, "Instagram is rate limiting your requests.")
27
+ when 504
28
+ raise Instagram::GatewayTimeout, error_message_500(response, "504 Gateway Time-out")
29
+ end
30
+ end
31
+ end
32
+
33
+ def initialize(app)
34
+ super app
35
+ @parser = nil
36
+ end
37
+
38
+ private
39
+
40
+ def error_message_400(response)
41
+ "#{response[:method].to_s.upcase} #{response[:url].to_s}: #{response[:status]}#{error_body(response[:body])}"
42
+ end
43
+
44
+ def error_body(body)
45
+ # body gets passed as a string, not sure if it is passed as something else from other spots?
46
+ if not body.nil? and not body.empty? and body.kind_of?(String)
47
+ # removed multi_json thanks to wesnolte's commit
48
+ body = begin
49
+ ::JSON.parse(body)
50
+ rescue JSON::ParserError => e
51
+ # handle HTML response here as empty JSON
52
+ if e.message.match /unexpected token/
53
+ nil
54
+ else
55
+ raise e
56
+ end
57
+ end
58
+ end
59
+
60
+ if body.nil?
61
+ nil
62
+ elsif body['meta'] and body['meta']['error_message'] and not body['meta']['error_message'].empty?
63
+ ": #{body['meta']['error_message']}"
64
+ elsif body['error_message'] and not body['error_message'].empty?
65
+ ": #{body['error_type']}: #{body['error_message']}"
66
+ end
67
+ end
68
+
69
+ def error_message_500(response, body=nil)
70
+ "#{response[:method].to_s.upcase} #{response[:url].to_s}: #{[response[:status].to_s + ':', body].compact.join(' ')}"
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,31 @@
1
+ require File.expand_path('../connection', __FILE__)
2
+ require File.expand_path('../request', __FILE__)
3
+ require File.expand_path('../oauth', __FILE__)
4
+
5
+ module Instagram
6
+ # @private
7
+ class API
8
+ # @private
9
+ attr_accessor *Configuration::VALID_OPTIONS_KEYS
10
+
11
+ # Creates a new API
12
+ def initialize(options={})
13
+ options = Instagram.options.merge(options)
14
+ Configuration::VALID_OPTIONS_KEYS.each do |key|
15
+ send("#{key}=", options[key])
16
+ end
17
+ end
18
+
19
+ def config
20
+ conf = {}
21
+ Configuration::VALID_OPTIONS_KEYS.each do |key|
22
+ conf[key] = send key
23
+ end
24
+ conf
25
+ end
26
+
27
+ include Connection
28
+ include Request
29
+ include OAuth
30
+ end
31
+ end
@@ -0,0 +1,62 @@
1
+ module Instagram
2
+ class Client
3
+ # Defines methods related to comments
4
+ module Comments
5
+ # Returns a list of comments for a given media item ID
6
+ #
7
+ # @overload media_comments(id)
8
+ # @param id [Integer] An Instagram media item ID
9
+ # @return [Hashie::Mash] The requested comments.
10
+ # @example Returns a list of comments for the media item of ID 1234
11
+ # Instagram.media_comments(777)
12
+ # @format :json
13
+ # @authenticated true
14
+ #
15
+ # If getting this data of a protected user, you must be authenticated (and be allowed to see that user).
16
+ # @rate_limited true
17
+ # @see http://instagram.com/developer/endpoints/comments/#get_media_comments
18
+ def media_comments(id, *args)
19
+ response = get("media/#{id}/comments")
20
+ response
21
+ end
22
+
23
+ # Creates a comment for a given media item ID
24
+ #
25
+ # @overload create_media_comment(id, text)
26
+ # @param id [Integer] An Instagram media item ID
27
+ # @param text [String] The text of your comment
28
+ # @return [Hashie::Mash] The comment created.
29
+ # @example Creates a new comment on media item with ID 777
30
+ # Instagram.create_media_comment(777, "Oh noes!")
31
+ # @format :json
32
+ # @authenticated true
33
+ #
34
+ # If getting this data of a protected user, you must be authenticated (and be allowed to see that user).
35
+ # @rate_limited true
36
+ # @see http://instagram.com/developer/endpoints/comments/#post_media_comments
37
+ def create_media_comment(id, text, options={})
38
+ response = post("media/#{id}/comments", options.merge(:text => text), signature=true)
39
+ response
40
+ end
41
+
42
+ # Deletes a comment for a given media item ID
43
+ #
44
+ # @overload delete_media_comment(media_id, comment_id)
45
+ # @param media_id [Integer] An Instagram media item ID.
46
+ # @param comment_id [Integer] Your comment ID of the comment you wish to delete.
47
+ # @return [nil]
48
+ # @example Delete the comment with ID of 1234, on the media item with ID of 777
49
+ # Instagram.delete_media_comment(777, 1234)
50
+ # @format :json
51
+ # @authenticated true
52
+ #
53
+ # In order to remove a comment, you must be the owner of the comment, the media item, or both.
54
+ # @rate_limited true
55
+ # @see http://instagram.com/developer/endpoints/comments/#delete_media_comments
56
+ def delete_media_comment(media_id, comment_id, options={})
57
+ response = delete("media/#{media_id}/comments/#{comment_id}", options, signature=true)
58
+ response
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,28 @@
1
+ module Instagram
2
+ class Client
3
+ # Defines methods related to embedding
4
+ module Embedding
5
+ # Returns information about the media associated with the given short link
6
+ #
7
+ # @overload oembed(url=nil, options={})
8
+ # @param url [String] An instagram short link
9
+ # @param options [Hash] A customizable set of options
10
+ # @option options [Integer] :maxheight Maximum height of returned media
11
+ # @option options [Integer] :maxwidth Maximum width of returned media
12
+ # @option options [Integer] :callback A JSON callback to be invoked
13
+ # @return [Hashie::Mash] Information about the media associated with given short link
14
+ # @example Return information about the media associated with http://instagr.am/p/BUG/
15
+ # Instagram.oembed(http://instagr.am/p/BUG/)
16
+ #
17
+ # @see http://instagram.com/developer/embedding/#oembed
18
+ # @format :json
19
+ # @authenticated false
20
+ # @rate_limited true
21
+ def oembed(*args)
22
+ url = args.first
23
+ return nil unless url
24
+ get("oembed?url=#{url}", {}, false, false, true)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ module Instagram
2
+ class Client
3
+ # Defines methods related to real-time geographies
4
+ module Geographies
5
+ # Returns a list of recent media items for a given real-time geography
6
+ #
7
+ # @overload geography_recent_media(id, options={})
8
+ # @param user [Integer] A geography ID from a real-time subscription.
9
+ # @param options [Hash] A customizable set of options.
10
+ # @option options [Integer] :count (nil) Limit the number of results returned
11
+ # @option options [Integer] :min_id (nil) Return media before this min_id
12
+ # @option options [Integer] :max_id (nil) Return media after this max_id
13
+ # @option options [Integer] :min_timestamp (nil) Return media after this UNIX timestamp
14
+ # @option options [Integer] :max_timestamp (nil) Return media before this UNIX timestamp
15
+ # @return [Hashie::Mash]
16
+ # @example Return a list of the most recent media items taken within a specific geography
17
+ # Instagram.geography_recent_media(514276)
18
+ # @see http://instagram.com/developer/endpoints/geographies/
19
+ # @format :json
20
+ # @authenticated false
21
+ # @rate_limited true
22
+ def geography_recent_media(id, *args)
23
+ options = args.last.is_a?(Hash) ? args.pop : {}
24
+ response = get("geographies/#{id}/media/recent", options)
25
+ response
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,58 @@
1
+ module Instagram
2
+ class Client
3
+ # Defines methods related to likes
4
+ module Likes
5
+ # Returns a list of users who like a given media item ID
6
+ #
7
+ # @overload media_likes(id)
8
+ # @param media [Integer] An Instagram media item ID
9
+ # @return [Hashie::Mash] A list of users.
10
+ # @example Returns a list of users who like the media item of ID 1234
11
+ # Instagram.media_likes(777)
12
+ # @format :json
13
+ # @authenticated true
14
+ #
15
+ # If getting this data of a protected user, you must be authenticated (and be allowed to see that user).
16
+ # @rate_limited true
17
+ # @see http://instagram.com/developer/endpoints/likes/#get_media_likes
18
+ def media_likes(id, *args)
19
+ response = get("media/#{id}/likes")
20
+ response
21
+ end
22
+
23
+ # Issues a like by the currently authenticated user, for a given media item ID
24
+ #
25
+ # @overload like_media(id, text)
26
+ # @param id [Integer] An Instagram media item ID
27
+ # @return [Hashie::Mash] Metadata
28
+ # @example Like media item with ID 777
29
+ # Instagram.like_media(777)
30
+ # @format :json
31
+ # @authenticated true
32
+ #
33
+ # If getting this data of a protected user, you must be authenticated (and be allowed to see that user).
34
+ # @rate_limited true
35
+ # @see http://instagram.com/developer/endpoints/likes/#post_likes
36
+ def like_media(id, options={})
37
+ response = post("media/#{id}/likes", options, signature=true)
38
+ response
39
+ end
40
+
41
+ # Removes the like on a givem media item ID for the currently authenticated user
42
+ #
43
+ # @overload unlike_media(id)
44
+ # @param media_id [Integer] An Instagram media item ID.
45
+ # @return [Hashie::Mash] Metadata
46
+ # @example Remove the like for the currently authenticated user on the media item with the ID of 777
47
+ # Instagram.unlike_media(777)
48
+ # @format :json
49
+ # @authenticated true
50
+ # @rate_limited true
51
+ # @see http://instagram.com/developer/endpoints/likes/#delete_likes
52
+ def unlike_media(id, options={})
53
+ response = delete("media/#{id}/likes", options, signature=true)
54
+ response
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,75 @@
1
+ module Instagram
2
+ class Client
3
+ # Defines methods related to media items
4
+ module Locations
5
+ # Returns extended information of a given Instagram location
6
+ #
7
+ # @overload location(id)
8
+ # @param location [Integer] An Instagram location ID
9
+ # @return [Hashie::Mash] The requested location.
10
+ # @example Return extended information for the Instagram office
11
+ # Instagram.location(514276)
12
+ # @format :json
13
+ # @authenticated false
14
+ # @rate_limited true
15
+ # @see http://instagram.com/developer/endpoints/locations/#get_locations
16
+ def location(id, *args)
17
+ response = get("locations/#{id}")
18
+ response
19
+ end
20
+
21
+ # Returns a list of recent media items for a given Instagram location
22
+ #
23
+ # @overload location_recent_media(id, options={})
24
+ # @param user [Integer] An Instagram location ID.
25
+ # @param options [Hash] A customizable set of options.
26
+ # @option options [Integer] :max_timestamp (nil) Return media before this UNIX timestamp
27
+ # @option options [Integer] :max_id (nil) Returns results with an ID less than (that is, older than) or equal to the specified ID.
28
+ # @option options [Integer] :count (nil) Limits the number of results returned per page.
29
+ # @return [Hashie::Mash]
30
+ # @example Return a list of the most recent media items taken at the Instagram office
31
+ # Instagram.location_recent_media(514276)
32
+ # @see http://instagram.com/developer/endpoints/locations/#get_locations_media_recent
33
+ # @format :json
34
+ # @authenticated false
35
+ # @rate_limited true
36
+ def location_recent_media(id, *args)
37
+ options = args.last.is_a?(Hash) ? args.pop : {}
38
+ response = get("locations/#{id}/media/recent", options)
39
+ response
40
+ end
41
+
42
+ # Returns Instagram locations within proximity of given lat,lng or Facebook Places ID
43
+ #
44
+ # @overload location_search(options={})
45
+ # @param facebook_places_id [String] A valid Facebook Places ID
46
+ # @param lat [String] A given latitude in decimal format
47
+ # @param lng [String] A given longitude in decimal format
48
+ # @option options [Integer] :count The number of media items to retrieve.
49
+ # @return [Hashie::Mash] location resultm object, #data is an Array.
50
+ # @example 1: Return a location with the Facebook Places ID = ()
51
+ # Instagram.location_search("3fd66200f964a520c5f11ee3") (Schiller's Liquor Bar, 131 Rivington St., NY, NY 10002)
52
+ # @example 2: Return locations around 37.7808851, -122.3948632 (164 S Park, SF, CA USA)
53
+ # Instagram.location_search("37.7808851", "-122.3948632")
54
+ # @see http://instagram.com/developer/endpoints/locations/#get_locations_search
55
+ # @format :json
56
+ # @authenticated false
57
+ # @rate_limited true
58
+ def location_search(*args)
59
+ options = args.last.is_a?(Hash) ? args.pop : {}
60
+ case args.size
61
+ when 1
62
+ facebook_places_id = args.first
63
+ response = get('locations/search', options.merge(:facebook_places_id => facebook_places_id))
64
+ when 2
65
+ lat, lng = args
66
+ response = get('locations/search', options.merge(:lat => lat, :lng => lng))
67
+ when 3
68
+ lat, lng, distance = args
69
+ response = get('locations/search', options.merge(:lat => lat, :lng => lng, :distance => distance))
70
+ end
71
+ response
72
+ end
73
+ end
74
+ end
75
+ end