instagram-continued 1.2.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 +7 -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 +11 -0
  10. data/Rakefile +27 -0
  11. data/instagram.gemspec +30 -0
  12. data/lib/faraday/loud_logger.rb +75 -0
  13. data/lib/faraday/oauth2.rb +42 -0
  14. data/lib/faraday/raise_http_exception.rb +61 -0
  15. data/lib/instagram.rb +27 -0
  16. data/lib/instagram/api.rb +31 -0
  17. data/lib/instagram/client.rb +21 -0
  18. data/lib/instagram/client/comments.rb +62 -0
  19. data/lib/instagram/client/embedding.rb +28 -0
  20. data/lib/instagram/client/geographies.rb +29 -0
  21. data/lib/instagram/client/likes.rb +58 -0
  22. data/lib/instagram/client/locations.rb +75 -0
  23. data/lib/instagram/client/media.rb +82 -0
  24. data/lib/instagram/client/subscriptions.rb +211 -0
  25. data/lib/instagram/client/tags.rb +59 -0
  26. data/lib/instagram/client/users.rb +310 -0
  27. data/lib/instagram/client/utils.rb +28 -0
  28. data/lib/instagram/configuration.rb +122 -0
  29. data/lib/instagram/connection.rb +31 -0
  30. data/lib/instagram/error.rb +34 -0
  31. data/lib/instagram/oauth.rb +36 -0
  32. data/lib/instagram/request.rb +82 -0
  33. data/lib/instagram/response.rb +22 -0
  34. data/lib/instagram/version.rb +3 -0
  35. data/spec/faraday/response_spec.rb +87 -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 +302 -0
@@ -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
@@ -0,0 +1,82 @@
1
+ module Instagram
2
+ class Client
3
+ # Defines methods related to media items
4
+ module Media
5
+ # Returns extended information of a given media item
6
+ #
7
+ # @overload media_item(id)
8
+ # @param user [Integer] An Instagram media item ID
9
+ # @return [Hashie::Mash] The requested media item.
10
+ # @example Return extended information for media item 1234
11
+ # Instagram.media_item(1324)
12
+ # @format :json
13
+ # @authenticated false unless requesting media from a protected user
14
+ #
15
+ # If getting this data of a protected user, you must authenticate (and be allowed to see that user).
16
+ # @rate_limited true
17
+ # @see http://instagram.com/developer/endpoints/media/#get_media
18
+ def media_item(*args)
19
+ id = args.first || 'self'
20
+ response = get("media/#{id}")
21
+ response
22
+ end
23
+
24
+ # Returns extended information of a given media item
25
+ #
26
+ # @overload media_shortcode(shortcode)
27
+ # @param shortcode [String] An Instagram media item shortcode
28
+ # @return [Hashie::Mash] The requested media item.
29
+ # @example Return extended information for media item with shortcode 'D'
30
+ # Instagram.media_shortcode('D')
31
+ # @format none
32
+ # @authenticated false unless requesting media from a protected user
33
+ #
34
+ # If getting this data of a protected user, you must authenticate (and be allowed to see that user).
35
+ # @rate_limited true
36
+ # @see http://instagram.com/developer/endpoints/media/#get_media_by_shortcode
37
+ def media_shortcode(*args)
38
+ shortcode = args.first
39
+ response = get("media/shortcode/#{shortcode}", {}, false, false, true)
40
+ response
41
+ end
42
+
43
+ # Returns a list of the overall most popular media
44
+ #
45
+ # @overload media_popular(options={})
46
+ # @param options [Hash] A customizable set of options.
47
+ # @return [Hashie::Mash]
48
+ # @example Returns a list of the overall most popular media
49
+ # Instagram.media_popular
50
+ # @see http://instagram.com/developer/endpoints/media/#get_media_popular
51
+ # @format :json
52
+ # @authenticated false unless requesting it from a protected user
53
+ #
54
+ # If getting this data of a protected user, you must authenticate (and be allowed to see that user).
55
+ # @rate_limited true
56
+ def media_popular(*args)
57
+ options = args.last.is_a?(Hash) ? args.pop : {}
58
+ id = args.first || "self"
59
+ response = get("media/popular", options)
60
+ response
61
+ end
62
+
63
+ # Returns media items within proximity of given lat,lng
64
+ #
65
+ # @param lat [String] A given latitude in decimal format
66
+ # @param lng [String] A given longitude in decimal format
67
+ # @param options [Hash] A customizable set of options.
68
+ # @option options [Integer] :count The number of media items to retrieve.
69
+ # @return [Hashie::Mash] A list of matching media
70
+ # @example Return media around 37.7808851, -122.3948632 (164 S Park, SF, CA USA)
71
+ # Instagram.media_search("37.7808851", "-122.3948632")
72
+ # @see http://instagram.com/developer/endpoints/media/#get_media_search
73
+ # @format :json
74
+ # @authenticated false
75
+ # @rate_limited true
76
+ def media_search(lat, lng, options={})
77
+ response = get('media/search', options.merge(:lat => lat, :lng => lng))
78
+ response
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,211 @@
1
+ require 'openssl'
2
+ require 'multi_json'
3
+
4
+ module Instagram
5
+ class Client
6
+ # Defines methods related to real-time
7
+ module Subscriptions
8
+ # Returns a list of active real-time subscriptions
9
+ #
10
+ # @overload subscriptions(options={})
11
+ # @return [Hashie::Mash] The list of subscriptions.
12
+ # @example Returns a list of subscriptions for the authenticated application
13
+ # Instagram.subscriptions
14
+ # @format :json
15
+ # @authenticated true
16
+ #
17
+ # Requires client_secret to be set on the client or passed in options
18
+ # @rate_limited true
19
+ # @see https://api.instagram.com/developer/realtime/
20
+ def subscriptions(options={})
21
+ response = get("subscriptions", options.merge(:client_secret => client_secret))
22
+ response
23
+ end
24
+
25
+ # Creates a real-time subscription
26
+ #
27
+ # @overload create_subscription(options={})
28
+ # @param options [Hash] A set of parameters
29
+ # @option options [String] :object The object you'd like to subscribe to (user, tag, location or geography)
30
+ # @option options [String] :callback_url The subscription callback URL
31
+ # @option options [String] :aspect The aspect of the object you'd like to subscribe to (in this case, "media").
32
+ # @option options [String, Integer] :object_id When specifying a location or tag use the location's ID or tag name respectively
33
+ # @option options [String, Float] :lat The center latitude of an area, used when subscribing to a geography object
34
+ # @option options [String, Float] :lng The center longitude of an area, used when subscribing to a geography object
35
+ # @option options [String, Integer] :radius The distance in meters you'd like to capture around a given point
36
+ # @overload create_subscription(object, callback_url, aspect="media", options={})
37
+ # @param object [String] The object you'd like to subscribe to (user, tag, location or geography)
38
+ # @param callback_url [String] The subscription callback URL
39
+ # @param aspect [String] The aspect of the object you'd like to subscribe to (in this case, "media").
40
+ # @param options [Hash] Addition options and parameters
41
+ # @option options [String, Integer] :object_id When specifying a location or tag use the location's ID or tag name respectively
42
+ # @option options [String, Float] :lat The center latitude of an area, used when subscribing to a geography object
43
+ # @option options [String, Float] :lng The center longitude of an area, used when subscribing to a geography object
44
+ # @option options [String, Integer] :radius The distance in meters you'd like to capture around a given point
45
+ #
46
+ # Note that we only support "media" at this time, but we might support other types of subscriptions in the future.
47
+ # @return [Hashie::Mash] The subscription created.
48
+ # @example Creates a new subscription to receive notifications for user media changes.
49
+ # Instagram.create_subscription("user", "http://example.com/instagram/callback")
50
+ # @format :json
51
+ # @authenticated true
52
+ #
53
+ # Requires client_secret to be set on the client or passed in options
54
+ # @rate_limited true
55
+ # @see https://api.instagram.com/developer/realtime/
56
+ def create_subscription(*args)
57
+ options = args.last.is_a?(Hash) ? args.pop : {}
58
+ object = args.shift
59
+ callback_url = args.shift
60
+ aspect = args.shift
61
+ options.tap {|o|
62
+ o[:object] = object unless object.nil?
63
+ o[:callback_url] = callback_url unless callback_url.nil?
64
+ o[:aspect] = aspect || o[:aspect] || "media"
65
+ }
66
+ response = post("subscriptions", options.merge(:client_secret => client_secret), signature=true)
67
+ response
68
+ end
69
+
70
+ # Deletes a real-time subscription
71
+ #
72
+ # @overload delete_subscription(options={})
73
+ # @param options [Hash] Addition options and parameters
74
+ # @option options [Integer] :subscription_id The subscription's ID
75
+ # @option options [String] :object When specified will remove all subscriptions of this object type, unless an :object_id is also specified (user, tag, location or geography)
76
+ # @option options [String, Integer] :object_id When specifying :object, include an :object_id to only remove subscriptions of that object and object_id
77
+ # @overload delete_subscription(subscription_id, options={})
78
+ # @param subscription_id [Integer] The subscription's ID
79
+ # @param options [Hash] Addition options and parameters
80
+ # @option options [String] :object When specified will remove all subscriptions of this object type, unless an :object_id is also specified (user, tag, location or geography)
81
+ # @option options [String, Integer] :object_id When specifying :object, include an :object_id to only remove subscriptions of that object and object_id
82
+ # @return [Hashie::Mash]
83
+ # @example Deletes an application's user change subscription
84
+ # Instagram.delete_subscription(:object => "user")
85
+ # @format :json
86
+ # @authenticated true
87
+ #
88
+ # Requires client_secret to be set on the client or passed in options
89
+ # @rate_limited true
90
+ # @see https://api.instagram.com/developer/realtime/
91
+ def delete_subscription(*args)
92
+ options = args.last.is_a?(Hash) ? args.pop : {}
93
+ subscription_id = args.first
94
+ options.merge!(:id => subscription_id) if subscription_id
95
+ response = delete("subscriptions", options.merge(:client_secret => client_secret), signature=true)
96
+ response
97
+ end
98
+
99
+ # As a security measure (to prevent DDoS attacks), Instagram sends a verification request to your server
100
+ # after you request a subscription.
101
+ # This method parses the challenge params and makes sure the call is legitimate.
102
+ #
103
+ # @param params the request parameters sent by Instagram. (You can pass in a Rails params hash.)
104
+ # @param verify_token the verify token sent in the {#subscribe subscription request}, if you provided one
105
+ #
106
+ # @yield verify_token if you need to compute the verification token
107
+ # (for instance, if your callback URL includes a record ID, which you look up
108
+ # and use to calculate a hash), you can pass meet_challenge a block, which
109
+ # will receive the verify_token received back from Instagram.
110
+ #
111
+ # @return the challenge string to be sent back to Instagram, or false if the request is invalid.
112
+ def meet_challenge(params, verify_token = nil, &verification_block)
113
+ if params["hub.mode"] == "subscribe" &&
114
+ # you can make sure this is legitimate through two ways
115
+ # if your store the token across the calls, you can pass in the token value
116
+ # and we'll make sure it matches
117
+ ((verify_token && params["hub.verify_token"] == verify_token) ||
118
+ # alternately, if you sent a specially-constructed value (such as a hash of various secret values)
119
+ # you can pass in a block, which we'll call with the verify_token sent by Instagram
120
+ # if it's legit, return anything that evaluates to true; otherwise, return nil or false
121
+ (verification_block && yield(params["hub.verify_token"])))
122
+ params["hub.challenge"]
123
+ else
124
+ false
125
+ end
126
+ end
127
+
128
+ # Public: As a security measure, all updates from Instagram are signed using
129
+ # X-Hub-Signature: XXXX where XXX is the sha1 of the json payload
130
+ # using your application secret as the key.
131
+ #
132
+ # Example:
133
+ # # in Rails controller
134
+ # def receive_update
135
+ # if Instagram.validate_update(request.body, headers)
136
+ # ...
137
+ # else
138
+ # render text: "not authorized", status: 401
139
+ # end
140
+ # end
141
+ def validate_update(body, headers)
142
+ unless client_secret
143
+ raise ArgumentError, "client_secret must be set during configure"
144
+ end
145
+
146
+ if request_signature = headers['X-Hub-Signature'] || headers['HTTP_X_HUB_SIGNATURE']
147
+ calculated_signature = OpenSSL::HMAC.hexdigest('sha1', client_secret, body)
148
+ calculated_signature == request_signature
149
+ end
150
+ end
151
+
152
+ # Process a subscription notification JSON payload
153
+ #
154
+ # @overload process_subscription(json, &block)
155
+ # @param json [String] The JSON response received by the Instagram real-time server
156
+ # @param block [Proc] A callable in which callbacks are defined
157
+ # @option options [String] :signature Pass in an X-Hub-Signature to use for payload validation
158
+ # @return [nil]
159
+ # @example Process and handle a notification for a user media change
160
+ # Instagram.process_subscription(params[:body]) do |handler|
161
+ #
162
+ # handler.on_user_changed do |user_id, data|
163
+ #
164
+ # user = User.by_instagram_id(user_id)
165
+ # @client = Instagram.client(:access_token => _access_token_for_user(user))
166
+ # latest_media = @client.user_recent_media[0]
167
+ # user.media.create_with_hash(latest_media)
168
+ # end
169
+ #
170
+ # end
171
+ # @format :json
172
+ # @authenticated true
173
+ #
174
+ # Requires client_secret to be set on the client or passed in options
175
+ # @rate_limited true
176
+ # @see https://api.instagram.com/developer/realtime/
177
+ def process_subscription(json, options={}, &block)
178
+ raise ArgumentError, "callbacks block expected" unless block_given?
179
+
180
+ if options.has_key?(:signature)
181
+ if !client_secret
182
+ raise ArgumentError, "client_secret must be set during configure"
183
+ end
184
+ digest = OpenSSL::Digest.new('sha1')
185
+ verify_signature = OpenSSL::HMAC.hexdigest(digest, client_secret, json)
186
+
187
+ if options[:signature] != verify_signature
188
+ raise Instagram::InvalidSignature, "invalid X-Hub-Signature does not match verify signature against client_secret"
189
+ end
190
+ end
191
+
192
+ payload = MultiJson.decode(json)
193
+ @changes = Hash.new { |h,k| h[k] = [] }
194
+ for change in payload
195
+ @changes[change['object']] << change
196
+ end
197
+ block.call(self)
198
+ end
199
+
200
+ [:user, :tag, :location, :geography].each do |object|
201
+ class_eval <<-RUBY_EVAL, __FILE__, __LINE__ +1
202
+ def on_#{object}_changed(&block)
203
+ for change in @changes['#{object}']
204
+ yield change.delete('object_id'), change
205
+ end
206
+ end
207
+ RUBY_EVAL
208
+ end
209
+ end
210
+ end
211
+ end