From da4ffe92c51ae0e79ce11686260669238d43ac37 Mon Sep 17 00:00:00 2001 From: John Manuel Derecho Date: Thu, 19 Mar 2020 18:51:30 +0800 Subject: [PATCH 01/11] Support new Instagram Basic Display API --- lib/instagram/client.rb | 16 +- lib/instagram/client/comments.rb | 62 -------- lib/instagram/client/embedding.rb | 28 ---- lib/instagram/client/geographies.rb | 29 ---- lib/instagram/client/likes.rb | 58 ------- lib/instagram/client/locations.rb | 75 --------- lib/instagram/client/media.rb | 4 +- lib/instagram/client/subscriptions.rb | 211 -------------------------- lib/instagram/client/tags.rb | 59 ------- lib/instagram/client/users.rb | 134 ++++++++-------- lib/instagram/configuration.rb | 8 + lib/instagram/connection.rb | 6 +- lib/instagram/oauth.rb | 19 +++ lib/instagram/request.rb | 20 +-- lib/instagram/version.rb | 2 +- 15 files changed, 120 insertions(+), 611 deletions(-) delete mode 100644 lib/instagram/client/comments.rb delete mode 100644 lib/instagram/client/embedding.rb delete mode 100644 lib/instagram/client/geographies.rb delete mode 100644 lib/instagram/client/likes.rb delete mode 100644 lib/instagram/client/locations.rb delete mode 100644 lib/instagram/client/subscriptions.rb delete mode 100644 lib/instagram/client/tags.rb diff --git a/lib/instagram/client.rb b/lib/instagram/client.rb index 1586477..c5b9265 100644 --- a/lib/instagram/client.rb +++ b/lib/instagram/client.rb @@ -10,12 +10,14 @@ class Client < API include Instagram::Client::Users include Instagram::Client::Media - include Instagram::Client::Locations - include Instagram::Client::Geographies - include Instagram::Client::Tags - include Instagram::Client::Comments - include Instagram::Client::Likes - include Instagram::Client::Subscriptions - include Instagram::Client::Embedding + + # @deprecated + # include Instagram::Client::Locations + # include Instagram::Client::Geographies + # include Instagram::Client::Tags + # include Instagram::Client::Comments + # include Instagram::Client::Likes + # include Instagram::Client::Subscriptions + # include Instagram::Client::Embedding end end diff --git a/lib/instagram/client/comments.rb b/lib/instagram/client/comments.rb deleted file mode 100644 index 320ccdb..0000000 --- a/lib/instagram/client/comments.rb +++ /dev/null @@ -1,62 +0,0 @@ -module Instagram - class Client - # Defines methods related to comments - module Comments - # Returns a list of comments for a given media item ID - # - # @overload media_comments(id) - # @param id [Integer] An Instagram media item ID - # @return [Hashie::Mash] The requested comments. - # @example Returns a list of comments for the media item of ID 1234 - # Instagram.media_comments(777) - # @format :json - # @authenticated true - # - # If getting this data of a protected user, you must be authenticated (and be allowed to see that user). - # @rate_limited true - # @see http://instagram.com/developer/endpoints/comments/#get_media_comments - def media_comments(id, *args) - response = get("media/#{id}/comments") - response - end - - # Creates a comment for a given media item ID - # - # @overload create_media_comment(id, text) - # @param id [Integer] An Instagram media item ID - # @param text [String] The text of your comment - # @return [Hashie::Mash] The comment created. - # @example Creates a new comment on media item with ID 777 - # Instagram.create_media_comment(777, "Oh noes!") - # @format :json - # @authenticated true - # - # If getting this data of a protected user, you must be authenticated (and be allowed to see that user). - # @rate_limited true - # @see http://instagram.com/developer/endpoints/comments/#post_media_comments - def create_media_comment(id, text, options={}) - response = post("media/#{id}/comments", options.merge(:text => text), signature=true) - response - end - - # Deletes a comment for a given media item ID - # - # @overload delete_media_comment(media_id, comment_id) - # @param media_id [Integer] An Instagram media item ID. - # @param comment_id [Integer] Your comment ID of the comment you wish to delete. - # @return [nil] - # @example Delete the comment with ID of 1234, on the media item with ID of 777 - # Instagram.delete_media_comment(777, 1234) - # @format :json - # @authenticated true - # - # In order to remove a comment, you must be the owner of the comment, the media item, or both. - # @rate_limited true - # @see http://instagram.com/developer/endpoints/comments/#delete_media_comments - def delete_media_comment(media_id, comment_id, options={}) - response = delete("media/#{media_id}/comments/#{comment_id}", options, signature=true) - response - end - end - end -end diff --git a/lib/instagram/client/embedding.rb b/lib/instagram/client/embedding.rb deleted file mode 100644 index 2f86517..0000000 --- a/lib/instagram/client/embedding.rb +++ /dev/null @@ -1,28 +0,0 @@ -module Instagram - class Client - # Defines methods related to embedding - module Embedding - # Returns information about the media associated with the given short link - # - # @overload oembed(url=nil, options={}) - # @param url [String] An instagram short link - # @param options [Hash] A customizable set of options - # @option options [Integer] :maxheight Maximum height of returned media - # @option options [Integer] :maxwidth Maximum width of returned media - # @option options [Integer] :callback A JSON callback to be invoked - # @return [Hashie::Mash] Information about the media associated with given short link - # @example Return information about the media associated with http://instagr.am/p/BUG/ - # Instagram.oembed(http://instagr.am/p/BUG/) - # - # @see http://instagram.com/developer/embedding/#oembed - # @format :json - # @authenticated false - # @rate_limited true - def oembed(*args) - url = args.first - return nil unless url - get("oembed?url=#{url}", {}, false, false, true) - end - end - end -end diff --git a/lib/instagram/client/geographies.rb b/lib/instagram/client/geographies.rb deleted file mode 100644 index cc0ad88..0000000 --- a/lib/instagram/client/geographies.rb +++ /dev/null @@ -1,29 +0,0 @@ -module Instagram - class Client - # Defines methods related to real-time geographies - module Geographies - # Returns a list of recent media items for a given real-time geography - # - # @overload geography_recent_media(id, options={}) - # @param user [Integer] A geography ID from a real-time subscription. - # @param options [Hash] A customizable set of options. - # @option options [Integer] :count (nil) Limit the number of results returned - # @option options [Integer] :min_id (nil) Return media before this min_id - # @option options [Integer] :max_id (nil) Return media after this max_id - # @option options [Integer] :min_timestamp (nil) Return media after this UNIX timestamp - # @option options [Integer] :max_timestamp (nil) Return media before this UNIX timestamp - # @return [Hashie::Mash] - # @example Return a list of the most recent media items taken within a specific geography - # Instagram.geography_recent_media(514276) - # @see http://instagram.com/developer/endpoints/geographies/ - # @format :json - # @authenticated false - # @rate_limited true - def geography_recent_media(id, *args) - options = args.last.is_a?(Hash) ? args.pop : {} - response = get("geographies/#{id}/media/recent", options) - response - end - end - end -end diff --git a/lib/instagram/client/likes.rb b/lib/instagram/client/likes.rb deleted file mode 100644 index a35b374..0000000 --- a/lib/instagram/client/likes.rb +++ /dev/null @@ -1,58 +0,0 @@ -module Instagram - class Client - # Defines methods related to likes - module Likes - # Returns a list of users who like a given media item ID - # - # @overload media_likes(id) - # @param media [Integer] An Instagram media item ID - # @return [Hashie::Mash] A list of users. - # @example Returns a list of users who like the media item of ID 1234 - # Instagram.media_likes(777) - # @format :json - # @authenticated true - # - # If getting this data of a protected user, you must be authenticated (and be allowed to see that user). - # @rate_limited true - # @see http://instagram.com/developer/endpoints/likes/#get_media_likes - def media_likes(id, *args) - response = get("media/#{id}/likes") - response - end - - # Issues a like by the currently authenticated user, for a given media item ID - # - # @overload like_media(id, text) - # @param id [Integer] An Instagram media item ID - # @return [Hashie::Mash] Metadata - # @example Like media item with ID 777 - # Instagram.like_media(777) - # @format :json - # @authenticated true - # - # If getting this data of a protected user, you must be authenticated (and be allowed to see that user). - # @rate_limited true - # @see http://instagram.com/developer/endpoints/likes/#post_likes - def like_media(id, options={}) - response = post("media/#{id}/likes", options, signature=true) - response - end - - # Removes the like on a givem media item ID for the currently authenticated user - # - # @overload unlike_media(id) - # @param media_id [Integer] An Instagram media item ID. - # @return [Hashie::Mash] Metadata - # @example Remove the like for the currently authenticated user on the media item with the ID of 777 - # Instagram.unlike_media(777) - # @format :json - # @authenticated true - # @rate_limited true - # @see http://instagram.com/developer/endpoints/likes/#delete_likes - def unlike_media(id, options={}) - response = delete("media/#{id}/likes", options, signature=true) - response - end - end - end -end diff --git a/lib/instagram/client/locations.rb b/lib/instagram/client/locations.rb deleted file mode 100644 index 0de44db..0000000 --- a/lib/instagram/client/locations.rb +++ /dev/null @@ -1,75 +0,0 @@ -module Instagram - class Client - # Defines methods related to media items - module Locations - # Returns extended information of a given Instagram location - # - # @overload location(id) - # @param location [Integer] An Instagram location ID - # @return [Hashie::Mash] The requested location. - # @example Return extended information for the Instagram office - # Instagram.location(514276) - # @format :json - # @authenticated false - # @rate_limited true - # @see http://instagram.com/developer/endpoints/locations/#get_locations - def location(id, *args) - response = get("locations/#{id}") - response - end - - # Returns a list of recent media items for a given Instagram location - # - # @overload location_recent_media(id, options={}) - # @param user [Integer] An Instagram location ID. - # @param options [Hash] A customizable set of options. - # @option options [Integer] :max_timestamp (nil) Return media before this UNIX timestamp - # @option options [Integer] :max_id (nil) Returns results with an ID less than (that is, older than) or equal to the specified ID. - # @option options [Integer] :count (nil) Limits the number of results returned per page. - # @return [Hashie::Mash] - # @example Return a list of the most recent media items taken at the Instagram office - # Instagram.location_recent_media(514276) - # @see http://instagram.com/developer/endpoints/locations/#get_locations_media_recent - # @format :json - # @authenticated false - # @rate_limited true - def location_recent_media(id, *args) - options = args.last.is_a?(Hash) ? args.pop : {} - response = get("locations/#{id}/media/recent", options) - response - end - - # Returns Instagram locations within proximity of given lat,lng or foursquare venue id - # - # @overload location_search(options={}) - # @param foursquare_v2_id [String] A valid Foursquare Venue ID (v2) - # @param lat [String] A given latitude in decimal format - # @param lng [String] A given longitude in decimal format - # @option options [Integer] :count The number of media items to retrieve. - # @return [Hashie::Mash] location resultm object, #data is an Array. - # @example 1: Return a location with the Foursquare Venue ID = () - # Instagram.location_search("3fd66200f964a520c5f11ee3") (Schiller's Liquor Bar, 131 Rivington St., NY, NY 10002) - # @example 2: Return locations around 37.7808851, -122.3948632 (164 S Park, SF, CA USA) - # Instagram.location_search("37.7808851", "-122.3948632") - # @see http://instagram.com/developer/endpoints/locations/#get_locations_search - # @format :json - # @authenticated false - # @rate_limited true - def location_search(*args) - options = args.last.is_a?(Hash) ? args.pop : {} - case args.size - when 1 - foursquare_v2_id = args.first - response = get('locations/search', options.merge(:foursquare_v2_id => foursquare_v2_id)) - when 2 - lat, lng = args - response = get('locations/search', options.merge(:lat => lat, :lng => lng)) - when 3 - lat, lng, distance = args - response = get('locations/search', options.merge(:lat => lat, :lng => lng, :distance => distance)) - end - response - end - end - end -end diff --git a/lib/instagram/client/media.rb b/lib/instagram/client/media.rb index 5df2c62..5858128 100644 --- a/lib/instagram/client/media.rb +++ b/lib/instagram/client/media.rb @@ -17,7 +17,7 @@ module Media # @see http://instagram.com/developer/endpoints/media/#get_media def media_item(*args) id = args.first || 'self' - response = get("media/#{id}") + response = get("media/#{id}", args) response end @@ -36,7 +36,7 @@ def media_item(*args) # @see http://instagram.com/developer/endpoints/media/#get_media_by_shortcode def media_shortcode(*args) shortcode = args.first - response = get("media/shortcode/#{shortcode}", {}, false, false, true) + response = get("media/shortcode/#{shortcode}", args, false, false, true) response end diff --git a/lib/instagram/client/subscriptions.rb b/lib/instagram/client/subscriptions.rb deleted file mode 100644 index 8766689..0000000 --- a/lib/instagram/client/subscriptions.rb +++ /dev/null @@ -1,211 +0,0 @@ -require 'openssl' -require 'multi_json' - -module Instagram - class Client - # Defines methods related to real-time - module Subscriptions - # Returns a list of active real-time subscriptions - # - # @overload subscriptions(options={}) - # @return [Hashie::Mash] The list of subscriptions. - # @example Returns a list of subscriptions for the authenticated application - # Instagram.subscriptions - # @format :json - # @authenticated true - # - # Requires client_secret to be set on the client or passed in options - # @rate_limited true - # @see https://api.instagram.com/developer/realtime/ - def subscriptions(options={}) - response = get("subscriptions", options.merge(:client_secret => client_secret)) - response - end - - # Creates a real-time subscription - # - # @overload create_subscription(options={}) - # @param options [Hash] A set of parameters - # @option options [String] :object The object you'd like to subscribe to (user, tag, location or geography) - # @option options [String] :callback_url The subscription callback URL - # @option options [String] :aspect The aspect of the object you'd like to subscribe to (in this case, "media"). - # @option options [String, Integer] :object_id When specifying a location or tag use the location's ID or tag name respectively - # @option options [String, Float] :lat The center latitude of an area, used when subscribing to a geography object - # @option options [String, Float] :lng The center longitude of an area, used when subscribing to a geography object - # @option options [String, Integer] :radius The distance in meters you'd like to capture around a given point - # @overload create_subscription(object, callback_url, aspect="media", options={}) - # @param object [String] The object you'd like to subscribe to (user, tag, location or geography) - # @param callback_url [String] The subscription callback URL - # @param aspect [String] he aspect of the object you'd like to subscribe to (in this case, "media"). - # @param options [Hash] Addition options and parameters - # @option options [String, Integer] :object_id When specifying a location or tag use the location's ID or tag name respectively - # @option options [String, Float] :lat The center latitude of an area, used when subscribing to a geography object - # @option options [String, Float] :lng The center longitude of an area, used when subscribing to a geography object - # @option options [String, Integer] :radius The distance in meters you'd like to capture around a given point - # - # Note that we only support "media" at this time, but we might support other types of subscriptions in the future. - # @return [Hashie::Mash] The subscription created. - # @example Creates a new subscription to receive notifications for user media changes. - # Instagram.create_subscription("user", "http://example.com/instagram/callback") - # @format :json - # @authenticated true - # - # Requires client_secret to be set on the client or passed in options - # @rate_limited true - # @see https://api.instagram.com/developer/realtime/ - def create_subscription(*args) - options = args.last.is_a?(Hash) ? args.pop : {} - object = args.shift - callback_url = args.shift - aspect = args.shift - options.tap {|o| - o[:object] = object unless object.nil? - o[:callback_url] = callback_url unless callback_url.nil? - o[:aspect] = aspect || o[:aspect] || "media" - } - response = post("subscriptions", options.merge(:client_secret => client_secret), signature=true) - response - end - - # Deletes a real-time subscription - # - # @overload delete_subscription(options={}) - # @param options [Hash] Addition options and parameters - # @option options [Integer] :subscription_id The subscription's ID - # @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) - # @option options [String, Integer] :object_id When specifying :object, inlcude an :object_id to only remove subscriptions of that object and object_id - # @overload delete_subscription(subscription_id, options={}) - # @param subscription_id [Integer] The subscription's ID - # @param options [Hash] Addition options and parameters - # @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) - # @option options [String, Integer] :object_id When specifying :object, inlcude an :object_id to only remove subscriptions of that object and object_id - # @return [Hashie::Mash] - # @example Deletes an application's user change subscription - # Instagram.delete_subscription(:object => "user") - # @format :json - # @authenticated true - # - # Requires client_secret to be set on the client or passed in options - # @rate_limited true - # @see https://api.instagram.com/developer/realtime/ - def delete_subscription(*args) - options = args.last.is_a?(Hash) ? args.pop : {} - subscription_id = args.first - options.merge!(:id => subscription_id) if subscription_id - response = delete("subscriptions", options.merge(:client_secret => client_secret), signature=true) - response - end - - # As a security measure (to prevent DDoS attacks), Instagram sends a verification request to your server - # after you request a subscription. - # This method parses the challenge params and makes sure the call is legitimate. - # - # @param params the request parameters sent by Instagram. (You can pass in a Rails params hash.) - # @param verify_token the verify token sent in the {#subscribe subscription request}, if you provided one - # - # @yield verify_token if you need to compute the verification token - # (for instance, if your callback URL includes a record ID, which you look up - # and use to calculate a hash), you can pass meet_challenge a block, which - # will receive the verify_token received back from Instagram. - # - # @return the challenge string to be sent back to Instagram, or false if the request is invalid. - def meet_challenge(params, verify_token = nil, &verification_block) - if params["hub.mode"] == "subscribe" && - # you can make sure this is legitimate through two ways - # if your store the token across the calls, you can pass in the token value - # and we'll make sure it matches - ((verify_token && params["hub.verify_token"] == verify_token) || - # alternately, if you sent a specially-constructed value (such as a hash of various secret values) - # you can pass in a block, which we'll call with the verify_token sent by Instagram - # if it's legit, return anything that evaluates to true; otherwise, return nil or false - (verification_block && yield(params["hub.verify_token"]))) - params["hub.challenge"] - else - false - end - end - - # Public: As a security measure, all updates from Instagram are signed using - # X-Hub-Signature: XXXX where XXX is the sha1 of the json payload - # using your application secret as the key. - # - # Example: - # # in Rails controller - # def receive_update - # if Instagram.validate_update(request.body, headers) - # ... - # else - # render text: "not authorized", status: 401 - # end - # end - def validate_update(body, headers) - unless client_secret - raise ArgumentError, "client_secret must be set during configure" - end - - if request_signature = headers['X-Hub-Signature'] || headers['HTTP_X_HUB_SIGNATURE'] - calculated_signature = OpenSSL::HMAC.hexdigest('sha1', client_secret, body) - calculated_signature == request_signature - end - end - - # Process a subscription notification JSON payload - # - # @overload process_subscription(json, &block) - # @param json [String] The JSON response received by the Instagram real-time server - # @param block [Proc] A callable in which callbacks are defined - # @option options [String] :signature Pass in an X-Hub-Signature to use for payload validation - # @return [nil] - # @example Process and handle a notification for a user media change - # Instagram.process_subscription(params[:body]) do |handler| - # - # handler.on_user_changed do |user_id, data| - # - # user = User.by_instagram_id(user_id) - # @client = Instagram.client(:access_token => _access_token_for_user(user)) - # latest_media = @client.user_recent_media[0] - # user.media.create_with_hash(latest_media) - # end - # - # end - # @format :json - # @authenticated true - # - # Requires client_secret to be set on the client or passed in options - # @rate_limited true - # @see https://api.instagram.com/developer/realtime/ - def process_subscription(json, options={}, &block) - raise ArgumentError, "callbacks block expected" unless block_given? - - if options.has_key?(:signature) - if !client_secret - raise ArgumentError, "client_secret must be set during configure" - end - digest = OpenSSL::Digest.new('sha1') - verify_signature = OpenSSL::HMAC.hexdigest(digest, client_secret, json) - - if options[:signature] != verify_signature - raise Instagram::InvalidSignature, "invalid X-Hub-Signature does not match verify signature against client_secret" - end - end - - payload = MultiJson.decode(json) - @changes = Hash.new { |h,k| h[k] = [] } - for change in payload - @changes[change['object']] << change - end - block.call(self) - end - - [:user, :tag, :location, :geography].each do |object| - class_eval <<-RUBY_EVAL, __FILE__, __LINE__ +1 - def on_#{object}_changed(&block) - for change in @changes['#{object}'] - yield change.delete('object_id'), change - end - end - RUBY_EVAL - end - end - end -end diff --git a/lib/instagram/client/tags.rb b/lib/instagram/client/tags.rb deleted file mode 100644 index 4e3b8fa..0000000 --- a/lib/instagram/client/tags.rb +++ /dev/null @@ -1,59 +0,0 @@ -module Instagram - class Client - # Defines methods related to tags - module Tags - # Returns extended information of a given Instagram tag - # - # @overload tag(tag) - # @param tag [String] An Instagram tag name - # @return [Hashie::Mash] The requested tag. - # @example Return extended information for the tag "cat" - # Instagram.tag('cat') - # @format :json - # @authenticated false - # @rate_limited true - # @see http://instagram.com/developer/endpoints/tags/#get_tags - def tag(tag, *args) - response = get("tags/#{tag}") - response - end - - # Returns a list of recent media items for a given Instagram tag - # - # @overload tag_recent_media(tag, options={}) - # @param tag-name [String] An Instagram tag name. - # @param options [Hash] A customizable set of options. - # @option options [Integer] :max_tag_id (nil) Returns results with an ID less than (that is, older than) or equal to the specified ID. The value can be retrieved from the returned response via pagination.max_tag_id. - # @option options [Integer] :min_tag_id (nil) Returns results with an ID greater than (that is, newer than) or equal to the specified ID. The value can be retrieved from the returned response via pagination.min_tag_id. - # @return [Hashie::Mash] - # @example Return a list of the most recent media items tagged "cat" - # Instagram.tag_recent_media('cat') - # @see https://instagram.com/developer/endpoints/tags/#get_tags_media_recent - # @format :json - # @authenticated false - # @rate_limited true - def tag_recent_media(id, *args) - options = args.last.is_a?(Hash) ? args.pop : {} - response = get("tags/#{id}/media/recent", options, false, false, false) - response - end - - # Returns a list of tags starting with the given search query - # - # @format :json - # @authenticated false - # @rate_limited true - # @param query [String] The beginning or complete tag name to search for - # @param options [Hash] A customizable set of options. - # @option options [Integer] :count The number of media items to retrieve. - # @return [Hashie::Mash] - # @see http://instagram.com/developer/endpoints/tags/#get_tags_search - # @example Return tags that start with "cat" - # Instagram.tag_search("cat") - def tag_search(query, options={}) - response = get('tags/search', options.merge(:q => query)) - response - end - end - end -end diff --git a/lib/instagram/client/users.rb b/lib/instagram/client/users.rb index 2c6488e..dcf3713 100644 --- a/lib/instagram/client/users.rb +++ b/lib/instagram/client/users.rb @@ -17,8 +17,8 @@ module Users # @see http://instagram.com/developer/endpoints/users/#get_users def user(*args) options = args.last.is_a?(Hash) ? args.pop : {} - id = args.first || 'self' - response = get("users/#{id}", options) + id = args.first || 'me' + response = get("#{id}", options, signature=false, raw=false, unformatted=true, no_response_wrapper=true, graph_request: true) response end @@ -34,10 +34,10 @@ def user(*args) # @see http://instagram.com/developer/endpoints/users/#get_users_search # @example Return users that match "Shayne Sweeney" # Instagram.user_search("Shayne Sweeney") - def user_search(query, options={}) - response = get('users/search', options.merge(:q => query)) - response - end + # def user_search(query, options={}) + # response = get('users/search', options.merge(:q => query)) + # response + # end # Returns a list of users whom a given user follows # @@ -60,12 +60,12 @@ def user_search(query, options={}) # # If getting this data of a protected user, you must authenticate (and be allowed to see that user). # @rate_limited true - def user_follows(*args) - options = args.last.is_a?(Hash) ? args.pop : {} - id = args.first || "self" - response = get("users/#{id}/follows", options) - response - end + # def user_follows(*args) + # options = args.last.is_a?(Hash) ? args.pop : {} + # id = args.first || "self" + # response = get("users/#{id}/follows", options) + # response + # end end # Returns a list of users whom a given user is followed by @@ -89,12 +89,12 @@ def user_follows(*args) # # If getting this data of a protected user, you must authenticate (and be allowed to see that user). # @rate_limited true - def user_followed_by(*args) - options = args.last.is_a?(Hash) ? args.pop : {} - id = args.first || "self" - response = get("users/#{id}/followed-by", options) - response - end + # def user_followed_by(*args) + # options = args.last.is_a?(Hash) ? args.pop : {} + # id = args.first || "self" + # response = get("users/#{id}/followed-by", options) + # response + # end # Returns a list of users who have requested the currently authorized user's permission to follow # @@ -111,10 +111,10 @@ def user_followed_by(*args) # @format :json # @authenticated true # @rate_limited true - def user_requested_by() - response = get("users/self/requested-by") - response - end + # def user_requested_by() + # response = get("users/self/requested-by") + # response + # end # Returns most recent media items from the currently authorized user's feed # @@ -130,11 +130,11 @@ def user_requested_by() # @authenticated true # @rate_limited true # @see http://instagram.com/developer/endpoints/users/#get_users_feed - def user_media_feed(*args) - options = args.first.is_a?(Hash) ? args.pop : {} - response = get('users/self/feed', options) - response - end + # def user_media_feed(*args) + # options = args.first.is_a?(Hash) ? args.pop : {} + # response = get('users/self/feed', options) + # response + # end # Returns a list of recent media items for a given user # @@ -159,8 +159,8 @@ def user_media_feed(*args) # @rate_limited true def user_recent_media(*args) options = args.last.is_a?(Hash) ? args.pop : {} - id = args.first || "self" - response = get("users/#{id}/media/recent", options) + id = args.first || "me" + response = get("#{id}/media", options, signature=false, raw=false, unformatted=true, no_response_wrapper=true, graph_request: true) response end @@ -177,10 +177,10 @@ def user_recent_media(*args) # @format :json # @authenticated true # @rate_limited true - def user_liked_media(options={}) - response = get("users/self/media/liked", options) - response - end + # def user_liked_media(options={}) + # response = get("users/self/media/liked", options) + # response + # end # Returns information about the current user's relationship (follow/following/etc) to another user # @@ -194,10 +194,10 @@ def user_liked_media(options={}) # @format :json # @authenticated true # @rate_limited true - def user_relationship(id, options={}) - response = get("users/#{id}/relationship", options) - response - end + # def user_relationship(id, options={}) + # response = get("users/#{id}/relationship", options) + # response + # end # Create a follows relationship between the current user and the target user # @@ -211,11 +211,11 @@ def user_relationship(id, options={}) # @format :json # @authenticated true # @rate_limited true - def follow_user(id, options={}) - options["action"] = "follow" - response = post("users/#{id}/relationship", options, signature=true) - response - end + # def follow_user(id, options={}) + # options["action"] = "follow" + # response = post("users/#{id}/relationship", options, signature=true) + # response + # end # Destroy a follows relationship between the current user and the target user # @@ -229,11 +229,11 @@ def follow_user(id, options={}) # @format :json # @authenticated true # @rate_limited true - def unfollow_user(id, options={}) - options["action"] = "unfollow" - response = post("users/#{id}/relationship", options, signature=true) - response - end + # def unfollow_user(id, options={}) + # options["action"] = "unfollow" + # response = post("users/#{id}/relationship", options, signature=true) + # response + # end # Block a relationship between the current user and the target user # @@ -247,11 +247,11 @@ def unfollow_user(id, options={}) # @format :json # @authenticated true # @rate_limited true - def block_user(id, options={}) - options["action"] = "block" - response = post("users/#{id}/relationship", options, signature=true) - response - end + # def block_user(id, options={}) + # options["action"] = "block" + # response = post("users/#{id}/relationship", options, signature=true) + # response + # end # Remove a relationship block between the current user and the target user # @@ -265,11 +265,11 @@ def block_user(id, options={}) # @format :json # @authenticated true # @rate_limited true - def unblock_user(id, options={}) - options["action"] = "unblock" - response = post("users/#{id}/relationship", options, signature=true) - response - end + # def unblock_user(id, options={}) + # options["action"] = "unblock" + # response = post("users/#{id}/relationship", options, signature=true) + # response + # end # Approve a relationship request between the current user and the target user # @@ -283,11 +283,11 @@ def unblock_user(id, options={}) # @format :json # @authenticated true # @rate_limited true - def approve_user(id, options={}) - options["action"] = "approve" - response = post("users/#{id}/relationship", options, signature=true) - response - end + # def approve_user(id, options={}) + # options["action"] = "approve" + # response = post("users/#{id}/relationship", options, signature=true) + # response + # end # Deny a relationship request between the current user and the target user # @@ -301,10 +301,10 @@ def approve_user(id, options={}) # @format :json # @authenticated true # @rate_limited true - def deny_user(id, options={}) - options["action"] = "deny" - response = post("users/#{id}/relationship", options, signature=true) - response - end + # def deny_user(id, options={}) + # options["action"] = "deny" + # response = post("users/#{id}/relationship", options, signature=true) + # response + # end end end diff --git a/lib/instagram/configuration.rb b/lib/instagram/configuration.rb index 72e03a3..8273f2c 100644 --- a/lib/instagram/configuration.rb +++ b/lib/instagram/configuration.rb @@ -15,6 +15,8 @@ module Configuration :scope, :redirect_uri, :endpoint, + :token_endpoint, + :graph_api_endpoint, :format, :proxy, :user_agent, @@ -48,6 +50,10 @@ module Configuration # @note There is no reason to use any other endpoint at this time DEFAULT_ENDPOINT = 'https://api.instagram.com/v1/'.freeze + # Use graph end point for the new instagram basic display api + TOKEN_ENDPOINT = 'https://api.instagram.com/'.freeze + GRAPH_API_ENDPOINT = 'https://graph.instagram.com/'.freeze + # The response format appended to the path and sent in the 'Accept' header if none is set # # @note JSON is the only available format at this time @@ -111,6 +117,8 @@ def reset self.scope = DEFAULT_SCOPE self.redirect_uri = DEFAULT_REDIRECT_URI self.endpoint = DEFAULT_ENDPOINT + self.token_endpoint = TOKEN_ENDPOINT + self.graph_api_endpoint = GRAPH_API_ENDPOINT self.format = DEFAULT_FORMAT self.proxy = DEFAULT_PROXY self.user_agent = DEFAULT_USER_AGENT diff --git a/lib/instagram/connection.rb b/lib/instagram/connection.rb index 668718e..ee56e55 100644 --- a/lib/instagram/connection.rb +++ b/lib/instagram/connection.rb @@ -6,11 +6,13 @@ module Instagram module Connection private - def connection(raw=false) + def connection(raw=false, graph_request=false) + end_point = graph_request ? graph_api_endpoint : token_endpoint + options = { :headers => {'Accept' => "application/#{format}; charset=utf-8", 'User-Agent' => user_agent}, :proxy => proxy, - :url => endpoint, + :url => end_point, }.merge(connection_options) Faraday::Connection.new(options) do |connection| diff --git a/lib/instagram/oauth.rb b/lib/instagram/oauth.rb index 9a86f59..4785800 100644 --- a/lib/instagram/oauth.rb +++ b/lib/instagram/oauth.rb @@ -12,12 +12,31 @@ def authorize_url(options={}) # Return an access token from authorization def get_access_token(code, options={}) + short_lived_auth = get_short_lived_access_token(code, options) + res = get_long_lived_access_token(short_lived_auth.access_token) + res[:user_id] = short_lived_auth[:user_id] + + temp_instagram = Instagram.client(:access_token => res.access_token) + res[:user] = temp_instagram.user(fields: 'id,username') + res + end + + def get_short_lived_access_token(code, options={}) options[:grant_type] ||= "authorization_code" options[:redirect_uri] ||= self.redirect_uri params = access_token_params.merge(options) + post("/oauth/access_token/", params.merge(:code => code), signature=false, raw=false, unformatted=true, no_response_wrapper=true) end + # Returns an access token that will expire for about 60 days + def get_long_lived_access_token(short_live_access_token, options={}) + options[:grant_type] = 'ig_exchange_token' + options[:client_secret] = self.client_secret + options[:access_token] = short_live_access_token + get("/access_token/", options, signature=false, raw=false, unformatted=true, no_response_wrapper=true, graph_request: true) + end + private def authorization_params diff --git a/lib/instagram/request.rb b/lib/instagram/request.rb index 9c5f061..d792640 100644 --- a/lib/instagram/request.rb +++ b/lib/instagram/request.rb @@ -5,30 +5,30 @@ module Instagram # Defines HTTP request methods module Request # Perform an HTTP GET request - def get(path, options={}, signature=false, raw=false, unformatted=false, no_response_wrapper=no_response_wrapper(), signed=sign_requests) - request(:get, path, options, signature, raw, unformatted, no_response_wrapper, signed) + def get(path, options={}, signature=false, raw=false, unformatted=false, no_response_wrapper=no_response_wrapper(), signed=sign_requests, graph_request: false) + request(:get, path, options, signature, raw, unformatted, no_response_wrapper, signed, graph_request) end # Perform an HTTP POST request - def post(path, options={}, signature=false, raw=false, unformatted=false, no_response_wrapper=no_response_wrapper(), signed=sign_requests) - request(:post, path, options, signature, raw, unformatted, no_response_wrapper, signed) + def post(path, options={}, signature=false, raw=false, unformatted=false, no_response_wrapper=no_response_wrapper(), signed=sign_requests, graph_request: false) + request(:post, path, options, signature, raw, unformatted, no_response_wrapper, signed, graph_request) end # Perform an HTTP PUT request - def put(path, options={}, signature=false, raw=false, unformatted=false, no_response_wrapper=no_response_wrapper(), signed=sign_requests) - request(:put, path, options, signature, raw, unformatted, no_response_wrapper, signed) + def put(path, options={}, signature=false, raw=false, unformatted=false, no_response_wrapper=no_response_wrapper(), signed=sign_requests, graph_request: false) + request(:put, path, options, signature, raw, unformatted, no_response_wrapper, signed, graph_request) end # Perform an HTTP DELETE request - def delete(path, options={}, signature=false, raw=false, unformatted=false, no_response_wrapper=no_response_wrapper(), signed=sign_requests) - request(:delete, path, options, signature, raw, unformatted, no_response_wrapper, signed) + def delete(path, options={}, signature=false, raw=false, unformatted=false, no_response_wrapper=no_response_wrapper(), signed=sign_requests, graph_request: false) + request(:delete, path, options, signature, raw, unformatted, no_response_wrapper, signed, graph_request) end private # Perform an HTTP request - def request(method, path, options, signature=false, raw=false, unformatted=false, no_response_wrapper=false, signed=sign_requests) - response = connection(raw).send(method) do |request| + def request(method, path, options, signature=false, raw=false, unformatted=false, no_response_wrapper=false, signed=sign_requests, graph_request=false) + response = connection(raw, graph_request).send(method) do |request| path = formatted_path(path) unless unformatted if signed == true diff --git a/lib/instagram/version.rb b/lib/instagram/version.rb index 6f4a234..b25bbe5 100644 --- a/lib/instagram/version.rb +++ b/lib/instagram/version.rb @@ -1,3 +1,3 @@ module Instagram - VERSION = '1.1.6'.freeze unless defined?(::Instagram::VERSION) + VERSION = '2.0.0'.freeze unless defined?(::Instagram::VERSION) end From 7e0b140c8d6c882d167e9cba7ed07793e180aa79 Mon Sep 17 00:00:00 2001 From: John Manuel Derecho Date: Fri, 20 Mar 2020 01:21:08 +0800 Subject: [PATCH 02/11] update --- lib/instagram/client/users.rb | 3 ++- lib/instagram/configuration.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/instagram/client/users.rb b/lib/instagram/client/users.rb index dcf3713..c6fa231 100644 --- a/lib/instagram/client/users.rb +++ b/lib/instagram/client/users.rb @@ -158,7 +158,8 @@ def user(*args) # If getting this data of a protected user, you must authenticate (and be allowed to see that user). # @rate_limited true def user_recent_media(*args) - options = args.last.is_a?(Hash) ? args.pop : {} + options = args.last.is_a?(Hash) ? args.pop.select{|k,v| v.present?} : {} + options[:fields] = 'id,caption,media_type,media_url,permalink,thumbnail_url,timestamp,username' id = args.first || "me" response = get("#{id}/media", options, signature=false, raw=false, unformatted=true, no_response_wrapper=true, graph_request: true) response diff --git a/lib/instagram/configuration.rb b/lib/instagram/configuration.rb index 8273f2c..5025ee3 100644 --- a/lib/instagram/configuration.rb +++ b/lib/instagram/configuration.rb @@ -117,7 +117,7 @@ def reset self.scope = DEFAULT_SCOPE self.redirect_uri = DEFAULT_REDIRECT_URI self.endpoint = DEFAULT_ENDPOINT - self.token_endpoint = TOKEN_ENDPOINT + self.token_endpoint = TOKEN_ENDPOINT self.graph_api_endpoint = GRAPH_API_ENDPOINT self.format = DEFAULT_FORMAT self.proxy = DEFAULT_PROXY From 93a1f14a7d11ba962eda0eb85bded26bcffd3d4f Mon Sep 17 00:00:00 2001 From: John Manuel Derecho Date: Mon, 23 Mar 2020 15:44:53 +0800 Subject: [PATCH 03/11] clean up --- lib/instagram/client.rb | 9 -- lib/instagram/client/media.rb | 75 +-------- lib/instagram/client/users.rb | 282 +--------------------------------- lib/instagram/oauth.rb | 4 +- lib/instagram/request.rb | 8 +- 5 files changed, 14 insertions(+), 364 deletions(-) diff --git a/lib/instagram/client.rb b/lib/instagram/client.rb index c5b9265..ecbca33 100644 --- a/lib/instagram/client.rb +++ b/lib/instagram/client.rb @@ -10,14 +10,5 @@ class Client < API include Instagram::Client::Users include Instagram::Client::Media - - # @deprecated - # include Instagram::Client::Locations - # include Instagram::Client::Geographies - # include Instagram::Client::Tags - # include Instagram::Client::Comments - # include Instagram::Client::Likes - # include Instagram::Client::Subscriptions - # include Instagram::Client::Embedding end end diff --git a/lib/instagram/client/media.rb b/lib/instagram/client/media.rb index 5858128..b1bebd4 100644 --- a/lib/instagram/client/media.rb +++ b/lib/instagram/client/media.rb @@ -3,80 +3,7 @@ class Client # Defines methods related to media items module Media # Returns extended information of a given media item - # - # @overload media_item(id) - # @param user [Integer] An Instagram media item ID - # @return [Hashie::Mash] The requested media item. - # @example Return extended information for media item 1234 - # Instagram.media_item(1324) - # @format :json - # @authenticated false unless requesting media from a protected user - # - # If getting this data of a protected user, you must authenticate (and be allowed to see that user). - # @rate_limited true - # @see http://instagram.com/developer/endpoints/media/#get_media - def media_item(*args) - id = args.first || 'self' - response = get("media/#{id}", args) - response - end - - # Returns extended information of a given media item - # - # @overload media_shortcode(shortcode) - # @param shortcode [String] An Instagram media item shortcode - # @return [Hashie::Mash] The requested media item. - # @example Return extended information for media item with shortcode 'D' - # Instagram.media_shortcode('D') - # @format none - # @authenticated false unless requesting media from a protected user - # - # If getting this data of a protected user, you must authenticate (and be allowed to see that user). - # @rate_limited true - # @see http://instagram.com/developer/endpoints/media/#get_media_by_shortcode - def media_shortcode(*args) - shortcode = args.first - response = get("media/shortcode/#{shortcode}", args, false, false, true) - response - end - - # Returns a list of the overall most popular media - # - # @overload media_popular(options={}) - # @param options [Hash] A customizable set of options. - # @return [Hashie::Mash] - # @example Returns a list of the overall most popular media - # Instagram.media_popular - # @see http://instagram.com/developer/endpoints/media/#get_media_popular - # @format :json - # @authenticated false unless requesting it from a protected user - # - # If getting this data of a protected user, you must authenticate (and be allowed to see that user). - # @rate_limited true - def media_popular(*args) - options = args.last.is_a?(Hash) ? args.pop : {} - id = args.first || "self" - response = get("media/popular", options) - response - end - - # Returns media items within proximity of given lat,lng - # - # @param lat [String] A given latitude in decimal format - # @param lng [String] A given longitude in decimal format - # @param options [Hash] A customizable set of options. - # @option options [Integer] :count The number of media items to retrieve. - # @return [Hashie::Mash] A list of matching media - # @example Return media around 37.7808851, -122.3948632 (164 S Park, SF, CA USA) - # Instagram.media_search("37.7808851", "-122.3948632") - # @see http://instagram.com/developer/endpoints/media/#get_media_search - # @format :json - # @authenticated false - # @rate_limited true - def media_search(lat, lng, options={}) - response = get('media/search', options.merge(:lat => lat, :lng => lng)) - response - end + # For implementation please refer to: https://developers.facebook.com/docs/instagram-basic-display-api/reference/media end end end diff --git a/lib/instagram/client/users.rb b/lib/instagram/client/users.rb index c6fa231..a9088fb 100644 --- a/lib/instagram/client/users.rb +++ b/lib/instagram/client/users.rb @@ -14,298 +14,30 @@ module Users # # If getting this data of a protected user, you must authenticate (and be allowed to see that user). # @rate_limited true - # @see http://instagram.com/developer/endpoints/users/#get_users + # @see https://developers.facebook.com/docs/instagram-basic-display-api/reference/user def user(*args) options = args.last.is_a?(Hash) ? args.pop : {} id = args.first || 'me' - response = get("#{id}", options, signature=false, raw=false, unformatted=true, no_response_wrapper=true, graph_request: true) + response = get("#{id}", options, unformatted: true, no_response_wrapper: true, graph_request: true) response end - - # Returns users that match the given query - # - # @format :json - # @authenticated false - # @rate_limited true - # @param query [String] The search query to run against user search. - # @param options [Hash] A customizable set of options. - # @option options [Integer] :count The number of users to retrieve. - # @return [Hashie::Mash] - # @see http://instagram.com/developer/endpoints/users/#get_users_search - # @example Return users that match "Shayne Sweeney" - # Instagram.user_search("Shayne Sweeney") - # def user_search(query, options={}) - # response = get('users/search', options.merge(:q => query)) - # response - # end - - # Returns a list of users whom a given user follows - # - # @overload user_follows(id=nil, options={}) - # @param options [Hash] A customizable set of options. - # @return [Hashie::Mash] - # @example Returns a list of users the authenticated user follows - # Instagram.user_follows - # @overload user_follows(id=nil, options={}) - # @param user [Integer] An Instagram user ID. - # @param options [Hash] A customizable set of options. - # @option options [Integer] :cursor (nil) Breaks the results into pages. Provide values as returned in the response objects's next_cursor attribute to page forward in the list. - # @option options [Integer] :count (nil) Limits the number of results returned per page. - # @return [Hashie::Mash] - # @example Return a list of users @mikeyk follows - # Instagram.user_follows(4) # @mikeyk user ID being 4 - # @see http://instagram.com/developer/endpoints/relationships/#get_users_follows - # @format :json - # @authenticated false unless requesting it from a protected user - # - # If getting this data of a protected user, you must authenticate (and be allowed to see that user). - # @rate_limited true - # def user_follows(*args) - # options = args.last.is_a?(Hash) ? args.pop : {} - # id = args.first || "self" - # response = get("users/#{id}/follows", options) - # response - # end end - # Returns a list of users whom a given user is followed by - # - # @overload user_followed_by(id=nil, options={}) - # @param options [Hash] A customizable set of options. - # @return [Hashie::Mash] - # @example Returns a list of users the authenticated user is followed by - # Instagram.user_followed_by - # @overload user_followed_by(id=nil, options={}) - # @param user [Integer] An Instagram user ID. - # @param options [Hash] A customizable set of options. - # @option options [Integer] :cursor (nil) Breaks the results into pages. Provide values as returned in the response objects's next_cursor attribute to page forward in the list. - # @option options [Integer] :count (nil) Limits the number of results returned per page. - # @return [Hashie::Mash] - # @example Return a list of users @mikeyk is followed by - # Instagram.user_followed_by(4) # @mikeyk user ID being 4 - # @see http://instagram.com/developer/endpoints/relationships/#get_users_followed_by - # @format :json - # @authenticated false unless requesting it from a protected user - # - # If getting this data of a protected user, you must authenticate (and be allowed to see that user). - # @rate_limited true - # def user_followed_by(*args) - # options = args.last.is_a?(Hash) ? args.pop : {} - # id = args.first || "self" - # response = get("users/#{id}/followed-by", options) - # response - # end - - # Returns a list of users who have requested the currently authorized user's permission to follow - # - # @overload user_requested_by() - # @param options [Hash] A customizable set of options. - # @return [Hashie::Mash] - # @example Returns a list of users awaiting approval of a ollow request, for the authenticated user - # Instagram.user_requested_by - # @overload user_requested_by() - # @return [Hashie::Mash] - # @example Return a list of users who have requested to follow the authenticated user - # Instagram.user_requested_by() - # @see http://instagram.com/developer/endpoints/relationships/#get_incoming_requests - # @format :json - # @authenticated true - # @rate_limited true - # def user_requested_by() - # response = get("users/self/requested-by") - # response - # end - - # Returns most recent media items from the currently authorized user's feed - # - # @overload user_media_feed(options={}) - # @param options [Hash] A customizable set of options. - # @option options [Integer] :max_id Returns results with an ID less than (that is, older than) or equal to the specified ID. - # @option options [Integer] :min_id Return media later than this min_id - # @option options [Integer] :count Specifies the number of records to retrieve, per page. - # @return [Hashie::Mash] - # @example Return most recent media images that would appear on @shayne's feed - # Instagram.user_media_feed() # assuming @shayne is the authorized user - # @format :json - # @authenticated true - # @rate_limited true - # @see http://instagram.com/developer/endpoints/users/#get_users_feed - # def user_media_feed(*args) - # options = args.first.is_a?(Hash) ? args.pop : {} - # response = get('users/self/feed', options) - # response - # end # Returns a list of recent media items for a given user # - # @overload user_recent_media(options={}) - # @param options [Hash] A customizable set of options. - # @return [Hashie::Mash] - # @example Returns a list of recent media items for the currently authenticated user - # Instagram.user_recent_media - # @overload user_recent_media(id=nil, options={}) - # @param user [Integer] An Instagram user ID. - # @param options [Hash] A customizable set of options. - # @option options [Integer] :max_id (nil) Returns results with an ID less than (that is, older than) or equal to the specified ID. - # @option options [Integer] :count (nil) Limits the number of results returned per page. - # @return [Hashie::Mash] - # @example Return a list of media items taken by @mikeyk - # Instagram.user_recent_media(4) # @mikeyk user ID being 4 - # @see http://instagram.com/developer/endpoints/users/#get_users_media_recent # @format :json # @authenticated false unless requesting it from a protected user # - # If getting this data of a protected user, you must authenticate (and be allowed to see that user). - # @rate_limited true + # If getting this data of a protected user, you must authenticate (and be allowed to see that user). + # @ true + # @see https://developers.facebook.com/docs/instagram-basic-display-api/reference/user/media def user_recent_media(*args) options = args.last.is_a?(Hash) ? args.pop.select{|k,v| v.present?} : {} - options[:fields] = 'id,caption,media_type,media_url,permalink,thumbnail_url,timestamp,username' + options[:fields] = [:id, :username, :caption, :media_type, :media_url, :permalink, :thumbnail_url, :timestamp].join(',') id = args.first || "me" - response = get("#{id}/media", options, signature=false, raw=false, unformatted=true, no_response_wrapper=true, graph_request: true) + response = get("#{id}/media", options, unformatted: true, no_response_wrapper: true, graph_request: true) response end - - # Returns a list of media items liked by the current user - # - # @overload user_liked_media(options={}) - # @param options [Hash] A customizable set of options. - # @option options [Integer] :max_like_id (nil) Returns results with an ID less than (that is, older than) or equal to the specified ID. - # @option options [Integer] :count (nil) Limits the number of results returned per page. - # @return [Hashie::Mash] - # @example Returns a list of media items liked by the currently authenticated user - # Instagram.user_liked_media - # @see http://instagram.com/developer/endpoints/users/#get_users_liked_feed - # @format :json - # @authenticated true - # @rate_limited true - # def user_liked_media(options={}) - # response = get("users/self/media/liked", options) - # response - # end - - # Returns information about the current user's relationship (follow/following/etc) to another user - # - # @overload user_relationship(id, options={}) - # @param user [Integer] An Instagram user ID. - # @param options [Hash] An optional options hash - # @return [Hashie::Mash] - # @example Return the relationship status between the currently authenticated user and @mikeyk - # Instagram.user_relationship(4) # @mikeyk user ID being 4 - # @see http://instagram.com/developer/endpoints/relationships/#get_relationship - # @format :json - # @authenticated true - # @rate_limited true - # def user_relationship(id, options={}) - # response = get("users/#{id}/relationship", options) - # response - # end - - # Create a follows relationship between the current user and the target user - # - # @overload follow_user(id, options={}) - # @param user [Integer] An Instagram user ID. - # @param options [Hash] An optional options hash - # @return [Hashie::Mash] - # @example Request the current user to follow the target user - # Instagram.follow_user(4) - # @see http://instagram.com/developer/endpoints/relationships/#post_relationship - # @format :json - # @authenticated true - # @rate_limited true - # def follow_user(id, options={}) - # options["action"] = "follow" - # response = post("users/#{id}/relationship", options, signature=true) - # response - # end - - # Destroy a follows relationship between the current user and the target user - # - # @overload unfollow_user(id, options={}) - # @param user [Integer] An Instagram user ID. - # @param options [Hash] An optional options hash - # @return [Hashie::Mash] - # @example Remove a follows relationship between the current user and the target user - # Instagram.unfollow_user(4) - # @see http://instagram.com/developer/endpoints/relationships/#post_relationship - # @format :json - # @authenticated true - # @rate_limited true - # def unfollow_user(id, options={}) - # options["action"] = "unfollow" - # response = post("users/#{id}/relationship", options, signature=true) - # response - # end - - # Block a relationship between the current user and the target user - # - # @overload unfollow_user(id, options={}) - # @param user [Integer] An Instagram user ID. - # @param options [Hash] An optional options hash - # @return [Hashie::Mash] - # @example Block a relationship between the current user and the target user - # Instagram.block_user(4) - # @see http://instagram.com/developer/endpoints/relationships/#post_relationship - # @format :json - # @authenticated true - # @rate_limited true - # def block_user(id, options={}) - # options["action"] = "block" - # response = post("users/#{id}/relationship", options, signature=true) - # response - # end - - # Remove a relationship block between the current user and the target user - # - # @overload unblock_user(id, options={}) - # @param user [Integer] An Instagram user ID. - # @param options [Hash] An optional options hash - # @return [Hashie::Mash] - # @example Remove a relationship block between the current user and the target user - # Instagram.unblock_user(4) - # @see http://instagram.com/developer/endpoints/relationships/#post_relationship - # @format :json - # @authenticated true - # @rate_limited true - # def unblock_user(id, options={}) - # options["action"] = "unblock" - # response = post("users/#{id}/relationship", options, signature=true) - # response - # end - - # Approve a relationship request between the current user and the target user - # - # @overload approve_user(id, options={}) - # @param user [Integer] An Instagram user ID. - # @param options [Hash] An optional options hash - # @return [Hashie::Mash] - # @example Approve a relationship request between the current user and the target user - # Instagram.approve_user(4) - # @see http://instagram.com/developer/endpoints/relationships/#post_relationship - # @format :json - # @authenticated true - # @rate_limited true - # def approve_user(id, options={}) - # options["action"] = "approve" - # response = post("users/#{id}/relationship", options, signature=true) - # response - # end - - # Deny a relationship request between the current user and the target user - # - # @overload deny_user(id, options={}) - # @param user [Integer] An Instagram user ID. - # @param options [Hash] An optional options hash - # @return [Hashie::Mash] - # @example Deny a relationship request between the current user and the target user - # Instagram.deny_user(4) - # @see http://instagram.com/developer/endpoints/relationships/#post_relationship - # @format :json - # @authenticated true - # @rate_limited true - # def deny_user(id, options={}) - # options["action"] = "deny" - # response = post("users/#{id}/relationship", options, signature=true) - # response - # end end end diff --git a/lib/instagram/oauth.rb b/lib/instagram/oauth.rb index 4785800..173770a 100644 --- a/lib/instagram/oauth.rb +++ b/lib/instagram/oauth.rb @@ -26,7 +26,7 @@ def get_short_lived_access_token(code, options={}) options[:redirect_uri] ||= self.redirect_uri params = access_token_params.merge(options) - post("/oauth/access_token/", params.merge(:code => code), signature=false, raw=false, unformatted=true, no_response_wrapper=true) + post("/oauth/access_token/", params.merge(:code => code), unformatted: true, no_response_wrapper: true) end # Returns an access token that will expire for about 60 days @@ -34,7 +34,7 @@ def get_long_lived_access_token(short_live_access_token, options={}) options[:grant_type] = 'ig_exchange_token' options[:client_secret] = self.client_secret options[:access_token] = short_live_access_token - get("/access_token/", options, signature=false, raw=false, unformatted=true, no_response_wrapper=true, graph_request: true) + get("/access_token/", options, unformatted: true, no_response_wrapper: true, graph_request: true) end private diff --git a/lib/instagram/request.rb b/lib/instagram/request.rb index d792640..9832c23 100644 --- a/lib/instagram/request.rb +++ b/lib/instagram/request.rb @@ -5,22 +5,22 @@ module Instagram # Defines HTTP request methods module Request # Perform an HTTP GET request - def get(path, options={}, signature=false, raw=false, unformatted=false, no_response_wrapper=no_response_wrapper(), signed=sign_requests, graph_request: false) + def get(path, options={}, signature: false, raw: false, unformatted: false, no_response_wrapper: no_response_wrapper(), signed: sign_requests, graph_request: false) request(:get, path, options, signature, raw, unformatted, no_response_wrapper, signed, graph_request) end # Perform an HTTP POST request - def post(path, options={}, signature=false, raw=false, unformatted=false, no_response_wrapper=no_response_wrapper(), signed=sign_requests, graph_request: false) + def post(path, options={}, signature: false, raw: false, unformatted: false, no_response_wrapper: no_response_wrapper(), signed: sign_requests, graph_request: false) request(:post, path, options, signature, raw, unformatted, no_response_wrapper, signed, graph_request) end # Perform an HTTP PUT request - def put(path, options={}, signature=false, raw=false, unformatted=false, no_response_wrapper=no_response_wrapper(), signed=sign_requests, graph_request: false) + def put(path, options={}, signature: false, raw: false, unformatted: false, no_response_wrapper: no_response_wrapper(), signed: sign_requests, graph_request: false) request(:put, path, options, signature, raw, unformatted, no_response_wrapper, signed, graph_request) end # Perform an HTTP DELETE request - def delete(path, options={}, signature=false, raw=false, unformatted=false, no_response_wrapper=no_response_wrapper(), signed=sign_requests, graph_request: false) + def delete(path, options={}, signature: false, raw: false, unformatted: false, no_response_wrapper: no_response_wrapper(), signed: sign_requests, graph_request: false) request(:delete, path, options, signature, raw, unformatted, no_response_wrapper, signed, graph_request) end From 43d219a23b16e0013e5239ef1cac2a3b59eb991a Mon Sep 17 00:00:00 2001 From: John Manuel Derecho Date: Wed, 25 Mar 2020 11:41:04 +0800 Subject: [PATCH 04/11] update token invalid message --- lib/faraday/raise_http_exception.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/faraday/raise_http_exception.rb b/lib/faraday/raise_http_exception.rb index ba06a81..e84c66e 100644 --- a/lib/faraday/raise_http_exception.rb +++ b/lib/faraday/raise_http_exception.rb @@ -8,7 +8,7 @@ def call(env) @app.call(env).on_complete do |response| case response[:status].to_i when 400 - raise Instagram::BadRequest, error_message_400(response) + raise Instagram::BadRequest, error_message_400(response, 'Invalid OAuth access token') when 404 raise Instagram::NotFound, error_message_400(response) when 429 @@ -32,8 +32,8 @@ def initialize(app) private - def error_message_400(response) - "#{response[:method].to_s.upcase} #{response[:url].to_s}: #{response[:status]}#{error_body(response[:body])}" + def error_message_400(response, body=nil) + "#{response[:method].to_s.upcase} #{response[:url].to_s}: #{response[:status]} #{error_body(response[:body]) || body}" end def error_body(body) From 729162f94583b00de5c0f9e4ae521f78be1a902b Mon Sep 17 00:00:00 2001 From: John Manuel Derecho Date: Thu, 26 Mar 2020 15:06:47 +0800 Subject: [PATCH 05/11] update version to 2.0.1 --- lib/instagram/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/instagram/version.rb b/lib/instagram/version.rb index b25bbe5..15f676d 100644 --- a/lib/instagram/version.rb +++ b/lib/instagram/version.rb @@ -1,3 +1,3 @@ module Instagram - VERSION = '2.0.0'.freeze unless defined?(::Instagram::VERSION) + VERSION = '2.0.1'.freeze unless defined?(::Instagram::VERSION) end From 1310cb8d61d7b0875d2f78cca88260fbea1acda0 Mon Sep 17 00:00:00 2001 From: John Manuel Derecho Date: Tue, 31 Mar 2020 19:46:09 +0800 Subject: [PATCH 06/11] version 2.0.3 --- lib/faraday/raise_http_exception.rb | 10 +++++----- lib/instagram/client/users.rb | 23 +++++++---------------- lib/instagram/request.rb | 4 ++++ lib/instagram/version.rb | 2 +- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/lib/faraday/raise_http_exception.rb b/lib/faraday/raise_http_exception.rb index e84c66e..b05a46d 100644 --- a/lib/faraday/raise_http_exception.rb +++ b/lib/faraday/raise_http_exception.rb @@ -45,15 +45,15 @@ def error_body(body) if body.nil? nil - elsif body['meta'] and body['meta']['error_message'] and not body['meta']['error_message'].empty? - ": #{body['meta']['error_message']}" - elsif body['error_message'] and not body['error_message'].empty? - ": #{body['error_type']}: #{body['error_message']}" + elsif body['error'] and body['error']['message'] and not body['error']['message'].empty? + ": #{body['error']['message']}" + elsif body['error'] and not body['message'].empty? + ": #{body.to_s}" end end def error_message_500(response, body=nil) - "#{response[:method].to_s.upcase} #{response[:url].to_s}: #{[response[:status].to_s + ':', body].compact.join(' ')}" + "#{response[:method].to_s.upcase} #{response[:url].to_s}: #{[response[:status].to_s + ':', error_body(response[:body]) ,body].compact.join(' ')}" end end end diff --git a/lib/instagram/client/users.rb b/lib/instagram/client/users.rb index a9088fb..f25451a 100644 --- a/lib/instagram/client/users.rb +++ b/lib/instagram/client/users.rb @@ -3,41 +3,32 @@ class Client # Defines methods related to users module Users # Returns extended information of a given user - # - # @overload user(id=nil, options={}) - # @param user [Integer] An Instagram user ID - # @return [Hashie::Mash] The requested user. - # @example Return extended information for @shayne - # Instagram.user(20) # @format :json - # @authenticated false unless requesting it from a protected user # - # If getting this data of a protected user, you must authenticate (and be allowed to see that user). - # @rate_limited true # @see https://developers.facebook.com/docs/instagram-basic-display-api/reference/user def user(*args) options = args.last.is_a?(Hash) ? args.pop : {} id = args.first || 'me' - response = get("#{id}", options, unformatted: true, no_response_wrapper: true, graph_request: true) + response = graph_get("#{id}", options) response end end # Returns a list of recent media items for a given user - # # @format :json - # @authenticated false unless requesting it from a protected user # - # If getting this data of a protected user, you must authenticate (and be allowed to see that user). - # @ true # @see https://developers.facebook.com/docs/instagram-basic-display-api/reference/user/media def user_recent_media(*args) options = args.last.is_a?(Hash) ? args.pop.select{|k,v| v.present?} : {} - options[:fields] = [:id, :username, :caption, :media_type, :media_url, :permalink, :thumbnail_url, :timestamp].join(',') + options[:fields] = media_query_fields id = args.first || "me" - response = get("#{id}/media", options, unformatted: true, no_response_wrapper: true, graph_request: true) + response = graph_get("#{id}/media", options) response end + + def media_query_fields + 'id,username,caption,media_type,media_url,permalink,thumbnail_url,timestamp' + end end end diff --git a/lib/instagram/request.rb b/lib/instagram/request.rb index 9832c23..1296cf7 100644 --- a/lib/instagram/request.rb +++ b/lib/instagram/request.rb @@ -24,6 +24,10 @@ def delete(path, options={}, signature: false, raw: false, unformatted: false, n request(:delete, path, options, signature, raw, unformatted, no_response_wrapper, signed, graph_request) end + def graph_get(path, options={}) + get(path, options, unformatted: true, no_response_wrapper: true, graph_request: true) + end + private # Perform an HTTP request diff --git a/lib/instagram/version.rb b/lib/instagram/version.rb index 15f676d..fdf5c09 100644 --- a/lib/instagram/version.rb +++ b/lib/instagram/version.rb @@ -1,3 +1,3 @@ module Instagram - VERSION = '2.0.1'.freeze unless defined?(::Instagram::VERSION) + VERSION = '2.0.3'.freeze unless defined?(::Instagram::VERSION) end From 29d771fdafc8f2ea96034845ca11e6420148db5f Mon Sep 17 00:00:00 2001 From: John Manuel Derecho Date: Thu, 4 Jun 2020 09:53:38 +0800 Subject: [PATCH 07/11] add refresh token --- lib/instagram/oauth.rb | 6 ++++++ lib/instagram/version.rb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/instagram/oauth.rb b/lib/instagram/oauth.rb index 173770a..fdf4ed2 100644 --- a/lib/instagram/oauth.rb +++ b/lib/instagram/oauth.rb @@ -37,6 +37,12 @@ def get_long_lived_access_token(short_live_access_token, options={}) get("/access_token/", options, unformatted: true, no_response_wrapper: true, graph_request: true) end + def refresh_instagram_token(long_live_access_token, options={}) + options[:grant_type] = 'ig_refresh_token' + options[:access_token] = long_live_access_token + get("/refresh_access_token/", options, unformatted: true, no_response_wrapper: true, graph_request: true) + end + private def authorization_params diff --git a/lib/instagram/version.rb b/lib/instagram/version.rb index fdf5c09..359a55e 100644 --- a/lib/instagram/version.rb +++ b/lib/instagram/version.rb @@ -1,3 +1,3 @@ module Instagram - VERSION = '2.0.3'.freeze unless defined?(::Instagram::VERSION) + VERSION = '2.0.4'.freeze unless defined?(::Instagram::VERSION) end From 78a257453f1f509f04c9e8d8c7c19896d5dda562 Mon Sep 17 00:00:00 2001 From: John Manuel Derecho Date: Wed, 7 Oct 2020 14:53:02 +0800 Subject: [PATCH 08/11] improve error handling --- lib/faraday/raise_http_exception.rb | 52 +++++++++++++++++++++++++---- lib/instagram/error.rb | 2 ++ lib/instagram/version.rb | 2 +- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/lib/faraday/raise_http_exception.rb b/lib/faraday/raise_http_exception.rb index b05a46d..58a05f5 100644 --- a/lib/faraday/raise_http_exception.rb +++ b/lib/faraday/raise_http_exception.rb @@ -4,11 +4,23 @@ module FaradayMiddleware # @private class RaiseHttpException < Faraday::Middleware + TOKEN_EXPIRED_ERROR_CODES = { + '190' => 'Access token has expired', + '460' => 'Password Changed', + '463' => 'Login status or access token has expired, been revoked, or is otherwise invalid', + '467' => 'Access token has expired, been revoked, or is otherwise invalid', + '492' => 'Invalid Session' + } + def call(env) @app.call(env).on_complete do |response| case response[:status].to_i when 400 - raise Instagram::BadRequest, error_message_400(response, 'Invalid OAuth access token') + if access_token_expired?(response) + raise Instagram::AccessTokenExpired, error_message_400(response, token_expired_message(response)) + else + raise Instagram::BadRequest, error_message_400(response, 'Invalid OAuth access token') + end when 404 raise Instagram::NotFound, error_message_400(response) when 429 @@ -32,8 +44,29 @@ def initialize(app) private + def token_expired_message(response) + TOKEN_EXPIRED_ERROR_CODES[error_code(response)] + end + + def access_token_expired?(response) + body = error_body(response[:body]) + + if body.present? + code = error_code(response) + if code.present? + return TOKEN_EXPIRED_ERROR_CODES[code.to_s].present? + end + end + + false + end + def error_message_400(response, body=nil) - "#{response[:method].to_s.upcase} #{response[:url].to_s}: #{response[:status]} #{error_body(response[:body]) || body}" + "#{response[:method].to_s.upcase} #{response[:url].to_s}: #{[response[:status].to_s + ':', error_body_message(response), body].compact.join(' ')}" + end + + def error_message_500(response, body=nil) + "#{response[:method].to_s.upcase} #{response[:url].to_s}: #{[response[:status].to_s + ':', error_body_message(response), body].compact.join(' ')}" end def error_body(body) @@ -43,6 +76,17 @@ def error_body(body) body = ::JSON.parse(body) end + body + end + + def error_code(response) + body = error_body(response[:body]) + body.dig('error').dig('code') + end + + def error_body_message(response) + body = error_body(response['body']) + if body.nil? nil elsif body['error'] and body['error']['message'] and not body['error']['message'].empty? @@ -51,9 +95,5 @@ def error_body(body) ": #{body.to_s}" end end - - def error_message_500(response, body=nil) - "#{response[:method].to_s.upcase} #{response[:url].to_s}: #{[response[:status].to_s + ':', error_body(response[:body]) ,body].compact.join(' ')}" - end end end diff --git a/lib/instagram/error.rb b/lib/instagram/error.rb index a2a277e..0009979 100644 --- a/lib/instagram/error.rb +++ b/lib/instagram/error.rb @@ -5,6 +5,8 @@ class Error < StandardError; end # Raised when Instagram returns the HTTP status code 400 class BadRequest < Error; end + class AccessTokenExpired < Error; end + # Raised when Instagram returns the HTTP status code 404 class NotFound < Error; end diff --git a/lib/instagram/version.rb b/lib/instagram/version.rb index 359a55e..72463a4 100644 --- a/lib/instagram/version.rb +++ b/lib/instagram/version.rb @@ -1,3 +1,3 @@ module Instagram - VERSION = '2.0.4'.freeze unless defined?(::Instagram::VERSION) + VERSION = '2.0.7'.freeze unless defined?(::Instagram::VERSION) end From 08483d3e8d68ac7da760fd26c8716c41f4f30548 Mon Sep 17 00:00:00 2001 From: John Manuel Derecho Date: Tue, 24 Jan 2023 11:53:42 +0800 Subject: [PATCH 09/11] fix post request for access token --- lib/instagram/oauth.rb | 2 +- lib/instagram/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/instagram/oauth.rb b/lib/instagram/oauth.rb index fdf4ed2..19b13a9 100644 --- a/lib/instagram/oauth.rb +++ b/lib/instagram/oauth.rb @@ -34,7 +34,7 @@ def get_long_lived_access_token(short_live_access_token, options={}) options[:grant_type] = 'ig_exchange_token' options[:client_secret] = self.client_secret options[:access_token] = short_live_access_token - get("/access_token/", options, unformatted: true, no_response_wrapper: true, graph_request: true) + post("/access_token/", options, unformatted: true, no_response_wrapper: true, graph_request: true) end def refresh_instagram_token(long_live_access_token, options={}) diff --git a/lib/instagram/version.rb b/lib/instagram/version.rb index 72463a4..fa336d9 100644 --- a/lib/instagram/version.rb +++ b/lib/instagram/version.rb @@ -1,3 +1,3 @@ module Instagram - VERSION = '2.0.7'.freeze unless defined?(::Instagram::VERSION) + VERSION = '2.0.8'.freeze unless defined?(::Instagram::VERSION) end From 6b4a398c684627669b5889f11ff95ad2fa4c2863 Mon Sep 17 00:00:00 2001 From: John Manuel Derecho Date: Mon, 3 Jul 2023 18:57:38 +0800 Subject: [PATCH 10/11] revert post to get longlive token --- lib/instagram/oauth.rb | 2 +- lib/instagram/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/instagram/oauth.rb b/lib/instagram/oauth.rb index 19b13a9..fdf4ed2 100644 --- a/lib/instagram/oauth.rb +++ b/lib/instagram/oauth.rb @@ -34,7 +34,7 @@ def get_long_lived_access_token(short_live_access_token, options={}) options[:grant_type] = 'ig_exchange_token' options[:client_secret] = self.client_secret options[:access_token] = short_live_access_token - post("/access_token/", options, unformatted: true, no_response_wrapper: true, graph_request: true) + get("/access_token/", options, unformatted: true, no_response_wrapper: true, graph_request: true) end def refresh_instagram_token(long_live_access_token, options={}) diff --git a/lib/instagram/version.rb b/lib/instagram/version.rb index fa336d9..dd0c4df 100644 --- a/lib/instagram/version.rb +++ b/lib/instagram/version.rb @@ -1,3 +1,3 @@ module Instagram - VERSION = '2.0.8'.freeze unless defined?(::Instagram::VERSION) + VERSION = '2.0.9'.freeze unless defined?(::Instagram::VERSION) end From 42da95b572e85232920573e85d0e761696646b43 Mon Sep 17 00:00:00 2001 From: John Manuel Derecho Date: Thu, 6 Jul 2023 16:23:57 +0800 Subject: [PATCH 11/11] error handling --- lib/faraday/raise_http_exception.rb | 14 ++++++++------ lib/instagram/version.rb | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/faraday/raise_http_exception.rb b/lib/faraday/raise_http_exception.rb index 58a05f5..2399f2e 100644 --- a/lib/faraday/raise_http_exception.rb +++ b/lib/faraday/raise_http_exception.rb @@ -73,15 +73,17 @@ def error_body(body) # body gets passed as a string, not sure if it is passed as something else from other spots? if not body.nil? and not body.empty? and body.kind_of?(String) # removed multi_json thanks to wesnolte's commit - body = ::JSON.parse(body) + body = ::JSON.parse(body) rescue body end body end def error_code(response) - body = error_body(response[:body]) - body.dig('error').dig('code') + body = error_body(response[:body]) + body.dig('error', 'code') + rescue + response.dig(:status) end def error_body_message(response) @@ -89,9 +91,9 @@ def error_body_message(response) if body.nil? nil - elsif body['error'] and body['error']['message'] and not body['error']['message'].empty? - ": #{body['error']['message']}" - elsif body['error'] and not body['message'].empty? + elsif body.is_a?(Hash) && (msg = body.dig('error', 'message')).present? + ": #{msg}" + else ": #{body.to_s}" end end diff --git a/lib/instagram/version.rb b/lib/instagram/version.rb index dd0c4df..0748a0e 100644 --- a/lib/instagram/version.rb +++ b/lib/instagram/version.rb @@ -1,3 +1,3 @@ module Instagram - VERSION = '2.0.9'.freeze unless defined?(::Instagram::VERSION) + VERSION = '2.0.10'.freeze unless defined?(::Instagram::VERSION) end