Permalink
Browse files

Added real-time subscriptions endpoints and tests

  • Loading branch information...
1 parent 266c2aa commit 804a93134a66b34c34a1f355954f9434f7275947 @shayne shayne committed Feb 17, 2011
View
@@ -14,6 +14,6 @@ class Client < API
include Instagram::Client::Tags
include Instagram::Client::Comments
include Instagram::Client::Likes
- include Instagram::Client::RealTime
+ include Instagram::Client::Subscriptions
end
end
@@ -20,7 +20,7 @@ def media_comments(id, *args)
response["data"]
end
- # Create's a comment for a given media item ID
+ # Creates a comment for a given media item ID
#
# @overload create_media_comment(id, text)
# @param id [Integer] An Instagram media item ID
@@ -39,7 +39,7 @@ def create_media_comment(id, text, options={})
response["data"]
end
- # Delete's a comment for a given media item ID
+ # 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.
@@ -0,0 +1,141 @@
+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["data"]
+ 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 (max 5000 meters)
+ # @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 (max 5000 meters)
+ #
+ # 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))
+ response["data"]
+ 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 [nil]
+ # @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))
+ response["data"]
+ 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
+ # @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, &block)
+ raise ArgumentError, "callbacks block expected" unless block_given?
+ 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
@@ -0,0 +1,12 @@
+{
+ "meta": {
+ "code": 200
+ },
+ "data": {
+ "object": "user",
+ "type": "subscription",
+ "id": "1",
+ "aspect": "media",
+ "callback_url": "http://your-callback.com/url/"
+ }
+}
@@ -0,0 +1 @@
+{"meta": {"code": 200}, "data": null}
@@ -0,0 +1,14 @@
+[{
+ "subscription_id": "1",
+ "object": "user",
+ "object_id": "1234",
+ "changed_aspect": "media",
+ "time": 1297286541
+ },
+ {
+ "subscription_id": "2",
+ "object": "tag",
+ "object_id": "nofilter",
+ "changed_aspect": "media",
+ "time": 1297286541
+ }]
@@ -0,0 +1,22 @@
+{
+ "meta": {
+ "code": 200
+ },
+ "data": [
+ {
+ "id": "1",
+ "type": "subscription",
+ "object": "user",
+ "aspect": "media",
+ "callback_url": "http://your-callback.com/url/"
+ },
+ {
+ "id": "2",
+ "type": "subscription",
+ "object": "location",
+ "object_id": "2345",
+ "aspect": "media",
+ "callback_url": "http://your-callback.com/url/"
+ }
+ ]
+}
@@ -0,0 +1,118 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+
+describe Instagram::Client do
+ Instagram::Configuration::VALID_FORMATS.each do |format|
+ context ".new(:format => '#{format}')" do
+
+ before do
+ @client = Instagram::Client.new(:format => format, :client_id => 'CID', :client_secret => 'CS', :access_token => 'AT')
+ end
+
+ describe ".subscriptions" do
+
+ before do
+ stub_get("subscriptions.#{format}").
+ with(:query => {:client_id => @client.client_id, :client_secret => @client.client_secret}).
+ to_return(:body => fixture("subscriptions.#{format}"), :headers => {:content_type => "application/#{format}; charset=utf-8"})
+ end
+
+ it "should get the correct resource" do
+ @client.subscriptions
+ a_get("subscriptions.#{format}").
+ with(:query => {:client_id => @client.client_id, :client_secret => @client.client_secret}).
+ should have_been_made
+ end
+
+ it "should return an array of subscriptions" do
+ subscriptions = @client.subscriptions
+ subscriptions.should be_a Array
+ subscriptions.first.object.should == "user"
+ end
+ end
+
+ describe ".create_subscription" do
+
+ before do
+ stub_post("subscriptions.#{format}").
+ with(:body => {:object => "user", :callback_url => "http://example.com/instagram/callback", :aspect => "media", :client_id => @client.client_id, :client_secret => @client.client_secret}).
+ to_return(:body => fixture("subscription.#{format}"), :headers => {:content_type => "application/#{format}; charset=utf-8"})
+ end
+
+ it "should get the correct resource" do
+ @client.create_subscription("user", :callback_url => "http://example.com/instagram/callback")
+ a_post("subscriptions.#{format}").
+ with(:body => {:object => "user", :callback_url => "http://example.com/instagram/callback", :aspect => "media", :client_id => @client.client_id, :client_secret => @client.client_secret}).
+ should have_been_made
+ end
+
+ it "should return the new subscription when successful" do
+ subscription = @client.create_subscription("user", :callback_url => "http://example.com/instagram/callback")
+ subscription.object.should == "user"
+ end
+ end
+
+ describe ".delete_media_comment" do
+
+ before do
+ stub_delete("subscriptions.#{format}").
+ with(:query => {:object => "user", :client_id => @client.client_id, :client_secret => @client.client_secret}).
+ to_return(:body => fixture("subscription_deleted.#{format}"), :headers => {:content_type => "application/#{format}; charset=utf-8"})
+ end
+
+ it "should get the correct resource" do
+ @client.delete_subscription(:object => "user")
+ a_delete("subscriptions.#{format}").
+ with(:query => {:object => "user", :client_id => @client.client_id, :client_secret => @client.client_secret}).
+ should have_been_made
+ end
+ end
+
+ describe ".process_subscriptions" do
+
+ context "without a callbacks block" do
+ it "should raise an ArgumentError" do
+ lambda do
+ @client.process_subscription(nil)
+ end.should raise_error(ArgumentError)
+ end
+ end
+
+ context "with a callbacks block and valid JSON" do
+
+ before do
+ @json = fixture("subscription_payload.json").read
+ end
+
+ it "should issue a callback to on_user_changed" do
+ @client.process_subscription(@json) do |handler|
+ handler.on_user_changed do |user_id, payload|
+ user_id.should == "1234"
+ end
+ end
+ end
+
+ it "should issue a callback to on_tag_changed" do
+ @client.process_subscription(@json) do |handler|
+ handler.on_tag_changed do |tag_name, payload|
+ tag_name.should == "nofilter"
+ end
+ end
+ end
+
+ it "should issue both callbacks in one block" do
+ @client.process_subscription(@json) do |handler|
+
+ handler.on_user_changed do |user_id, payload|
+ user_id.should == "1234"
+ end
+
+ handler.on_tag_changed do |tag_name, payload|
+ tag_name.should == "nofilter"
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end

0 comments on commit 804a931

Please sign in to comment.