instagram-continued 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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