Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' of github.com:intridea/tweetstream

Conflicts:
  lib/tweetstream/client.rb
  • Loading branch information...
commit 8e3fe75291d7cd2d79fbcb2741e20ee50503aa42 2 parents 1b2e2b3 + 40ce000
@stve stve authored
View
27 README.md
@@ -51,6 +51,33 @@ user ids:
The methods available to TweetStream::Client will be kept in parity
with the methods available on the Streaming API wiki page.
+Using the Twitter Userstream
+----------------------------
+
+Using the Twitter userstream works similarly to the regular streaming, except you use the userstream method.
+
+ # Use 'userstream' to get message from your stream
+ TweetStream::Client.new.userstream do |status|
+ puts status.text
+ end
+
+
+You also can use method hooks for both regular timeline statuses and direct messages.
+
+ client = TweetStream::Client.new
+
+ client.on_direct_message do |direct_message|
+ puts "direct message"
+ puts direct_message.text
+ end
+
+ client.on_timeline_status do |status|
+ puts "timeline status"
+ puts status.text
+ end
+
+ client.userstream
+
Configuration and Changes in 1.1.0
----------------------------------
View
3  lib/tweetstream.rb
@@ -2,6 +2,7 @@
require 'tweetstream/client'
require 'tweetstream/hash'
require 'tweetstream/status'
+require 'tweetstream/direct_message'
require 'tweetstream/user'
require 'tweetstream/error'
require 'tweetstream/daemon'
@@ -28,4 +29,4 @@ def respond_to?(method, include_private = false)
new.respond_to?(method, include_private) || super(method, include_private)
end
end
-end
+end
View
141 lib/tweetstream/client.rb
@@ -110,6 +110,11 @@ def filter(query_params = {}, &block)
start('statuses/filter', query_params.merge(:method => :post), &block)
end
+ # Make a call to the userstream api for currently authenticated user
+ def userstream(&block)
+ start('', :extra_stream_parameters => {:host => "userstream.twitter.com", :path => "/2/user.json"}, &block)
+ end
+
# Set a Proc to be run when a deletion notice is received
# from the Twitter stream. For example:
#
@@ -174,6 +179,69 @@ def on_error(&block)
end
end
+ # Set a Proc to be run when a direct message is encountered in the
+ # processing of the stream.
+ #
+ # @client = TweetStream::Client.new('user','pass')
+ # @client.on_direct_message do |direct_message|
+ # # do something with the direct message
+ # end
+ #
+ # Block must take one argument: the direct message.
+ # If no block is given, it will return the currently set
+ # direct message proc. When a block is given, the TweetStream::Client
+ # object is returned to allow for chaining.
+ def on_direct_message(&block)
+ if block_given?
+ @on_direct_message = block
+ self
+ else
+ @on_direct_message
+ end
+ end
+
+ # Set a Proc to be run whenever anything is encountered in the
+ # processing of the stream.
+ #
+ # @client = TweetStream::Client.new('user','pass')
+ # @client.on_anything do |status|
+ # # do something with the status
+ # end
+ #
+ # Block can take one or two arguments. |status (, client)|
+ # If no block is given, it will return the currently set
+ # timeline status proc. When a block is given, the TweetStream::Client
+ # object is returned to allow for chaining.
+ def on_anything(&block)
+ if block_given?
+ @on_anything = block
+ self
+ else
+ @on_anything
+ end
+ end
+
+ # Set a Proc to be run when a regular timeline message is encountered in the
+ # processing of the stream.
+ #
+ # @client = TweetStream::Client.new('user','pass')
+ # @client.on_timeline_message do |status|
+ # # do something with the status
+ # end
+ #
+ # Block can take one or two arguments. |status (, client)|
+ # If no block is given, it will return the currently set
+ # timeline status proc. When a block is given, the TweetStream::Client
+ # object is returned to allow for chaining.
+ def on_timeline_status(&block)
+ if block_given?
+ @on_timeline_status = block
+ self
+ else
+ @on_timeline_status
+ end
+ end
+
# Set a Proc to be run when connection established.
# Called in EventMachine::Connection#post_init
#
@@ -207,31 +275,36 @@ def start(path, query_parameters = {}, &block) #:nodoc:
limit_proc = query_parameters.delete(:limit) || self.on_limit
error_proc = query_parameters.delete(:error) || self.on_error
inited_proc = query_parameters.delete(:inited) || self.on_inited
+ direct_message_proc = query_parameters.delete(:direct_message) || self.on_direct_message
+ timeline_status_proc = query_parameters.delete(:timeline_status) || self.on_timeline_status
+ anything_proc = query_parameters.delete(:anything) || self.on_anything
params = normalize_filter_parameters(query_parameters)
+ extra_stream_parameters = query_parameters.delete(:extra_stream_parameters) || {}
+
uri = method == :get ? build_uri(path, params) : build_uri(path)
+ stream_params = {
+ :path => uri,
+ :method => method.to_s.upcase,
+ :user_agent => user_agent,
+ :on_inited => inited_proc,
+ :filters => params.delete(:track),
+ :params => params,
+ :ssl => true
+ }.merge(auth_params).merge(extra_stream_parameters)
+
EventMachine.epoll
EventMachine.kqueue = EM.kqueue?
- EventMachine::run {
+ EventMachine::run {
if @on_interval_proc.is_a?(Proc)
timer = @on_interval_time || Configuration::DEFAULT_TIMER_INTERVAL
proc = @on_interval_proc
EM.add_periodic_timer(timer, &proc)
end
- stream_params = {
- :path => uri,
- :method => method.to_s.upcase,
- :user_agent => user_agent,
- :on_inited => inited_proc,
- :filters => params.delete(:track),
- :params => params,
- :ssl => true
- }.merge(auth_params)
-
@stream = Twitter::JSONStream.connect(stream_params)
@stream.each_item do |item|
begin
@@ -247,23 +320,31 @@ def start(path, query_parameters = {}, &block) #:nodoc:
end
hash = TweetStream::Hash.new(raw_hash)
-
if hash[:delete] && hash[:delete][:status]
delete_proc.call(hash[:delete][:status][:id], hash[:delete][:status][:user_id]) if delete_proc.is_a?(Proc)
elsif hash[:limit] && hash[:limit][:track]
limit_proc.call(hash[:limit][:track]) if limit_proc.is_a?(Proc)
+
+ elsif hash[:direct_message]
+ yield_message_to direct_message_proc, TweetStream::DirectMessage.new(hash[:direct_message])
+
elsif hash[:text] && hash[:user]
@last_status = TweetStream::Status.new(hash)
-
- # Give the block the option to receive either one
- # or two arguments, depending on its arity.
- case block.arity
- when 1
- yield @last_status
- when 2
- yield @last_status, self
+ yield_message_to timeline_status_proc, @last_status
+
+ if block_given?
+ # Give the block the option to receive either one
+ # or two arguments, depending on its arity.
+ case block.arity
+ when 1
+ yield @last_status
+ when 2
+ yield @last_status, self
+ end
end
end
+
+ yield_message_to anything_proc, hash
end
@stream.on_error do |message|
@@ -299,14 +380,11 @@ def build_query_parameters(query)
def build_post_body(query) #:nodoc:
return '' unless query && query.is_a?(::Hash) && query.size > 0
- pairs = []
- query.each_pair do |k,v|
+ query.map do |k, v|
v = v.flatten.collect { |q| q.to_s }.join(',') if v.is_a?(Array)
- pairs << "#{k.to_s}=#{CGI.escape(v.to_s)}"
- end
-
- pairs.join('&')
+ "#{k.to_s}=#{CGI.escape(v.to_s)}"
+ end.join('&')
end
def normalize_filter_parameters(query_parameters = {})
@@ -333,5 +411,16 @@ def auth_params
}
end
end
+
+ def yield_message_to(procedure, message)
+ if procedure.is_a?(Proc)
+ case procedure.arity
+ when 1
+ procedure.call(message)
+ when 2
+ procedure.call(message, self)
+ end
+ end
+ end
end
end
View
6 lib/tweetstream/direct_message.rb
@@ -0,0 +1,6 @@
+class TweetStream::DirectMessage < TweetStream::Hash
+ def initialize(hash)
+ super
+ self[:user] = self[:sender] = TweetStream::User.new(self[:sender])
+ end
+end
View
2  lib/tweetstream/status.rb
@@ -5,7 +5,7 @@ def initialize(hash)
super
self[:user] = TweetStream::User.new(self[:user])
end
-
+
def id
self[:id] || super
end
View
1  spec/data/direct_messages.json
@@ -0,0 +1 @@
+{"direct_message":{"created_at":"Sat Sep 24 18:59:38 +0000 2011", "id_str":"4227325281", "sender_screen_name":"coreyhaines", "sender":{"name":"Corey Haines", "profile_sidebar_fill_color":"DAECF4", "profile_sidebar_border_color":"C6E2EE", "profile_background_tile":false, "profile_image_url":"http://a0.twimg.com/profile_images/1508969901/Photo_on_2011-08-22_at_19.15__3_normal.jpg", "created_at":"Sun Dec 23 18:11:29 +0000 2007", "location":"Chicago, IL", "follow_request_sent":false, "id_str":"11458102", "is_translator":false, "profile_link_color":"1F98C7", "default_profile":false, "favourites_count":122, "contributors_enabled":false, "url":"http://www.coreyhaines.com", "id":11458102, "profile_image_url_https":"https://si0.twimg.com/profile_images/1508969901/Photo_on_2011-08-22_at_19.15__3_normal.jpg", "utc_offset":-21600, "profile_use_background_image":true, "listed_count":593, "lang":"en", "followers_count":5764, "protected":false, "profile_text_color":"663B12", "notifications":false, "description":"Software Journeyman, Coderetreat Facilitator, Cofounder of MercuryApp.com, Awesome....\r\nI make magic!", "verified":false, "profile_background_color":"C6E2EE", "geo_enabled":false, "profile_background_image_url_https":"https://si0.twimg.com/images/themes/theme2/bg.gif", "time_zone":"Central Time (US & Canada)", "profile_background_image_url":"http://a1.twimg.com/images/themes/theme2/bg.gif", "default_profile_image":false, "friends_count":423, "statuses_count":35950, "following":false, "screen_name":"coreyhaines", "show_all_inline_media":false}, "recipient_screen_name":"coreyhainestest", "text":"waddup gain", "id":4227325281, "recipient":{"name":"Corey's Test Account", "profile_sidebar_fill_color":"DDEEF6", "profile_sidebar_border_color":"C0DEED", "profile_background_tile":false, "profile_image_url":"http://a2.twimg.com/sticky/default_profile_images/default_profile_3_normal.png", "created_at":"Sat Sep 24 13:04:56 +0000 2011", "location":null, "follow_request_sent":false, "id_str":"379145826", "is_translator":false, "profile_link_color":"0084B4", "default_profile":true, "favourites_count":0, "contributors_enabled":false, "url":null, "id":379145826, "profile_image_url_https":"https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png", "utc_offset":null, "profile_use_background_image":true, "listed_count":0, "lang":"en", "followers_count":1, "protected":false, "profile_text_color":"333333", "notifications":false, "description":null, "verified":false, "profile_background_color":"C0DEED", "geo_enabled":false, "profile_background_image_url_https":"https://si0.twimg.com/images/themes/theme1/bg.png", "time_zone":null, "profile_background_image_url":"http://a0.twimg.com/images/themes/theme1/bg.png", "default_profile_image":true, "friends_count":1, "statuses_count":21, "following":true, "screen_name":"coreyhainestest", "show_all_inline_media":false}, "recipient_id":379145826, "sender_id":11458102}}
View
10 spec/spec_helper.rb
@@ -24,3 +24,13 @@ def sample_tweets
@tweets
end
end
+
+def sample_direct_messages
+ return @direct_messages if @direct_messages
+
+ @direct_messages = []
+ Yajl::Parser.parse(File.open(File.dirname(__FILE__) + '/data/direct_messages.json', 'r')) do |hash|
+ @direct_messages << hash
+ end
+ @direct_messages
+end
View
89 spec/tweetstream/client_spec.rb
@@ -1,4 +1,4 @@
-require File.dirname(__FILE__) + '/../spec_helper'
+require 'spec_helper'
describe TweetStream::Client do
before(:each) do
@@ -123,6 +123,81 @@
end.track('abc')
end
+ context "using on_anything" do
+ it "yields the raw hash" do
+ hash = {:id => 1234}
+ @stream.should_receive(:each_item).and_yield(hash.to_json)
+ yielded_hash = nil
+ @client.on_anything do |hash|
+ yielded_hash = hash
+ end.track('abc')
+ yielded_hash.should_not be_nil
+ yielded_hash.id.should == 1234
+ end
+ it 'yields itself if block has an arity of 2' do
+ hash = {:id => 1234}
+ @stream.should_receive(:each_item).and_yield(hash.to_json)
+ yielded_client = nil
+ @client.on_anything do |_, client|
+ yielded_client = client
+ end.track('abc')
+ yielded_client.should_not be_nil
+ yielded_client.should == @client
+ end
+ end
+
+ context 'using on_timeline_status' do
+ it 'yields a Status' do
+ tweet = sample_tweets[0]
+ tweet[:id] = 123
+ tweet[:user][:screen_name] = 'monkey'
+ tweet[:text] = "Oo oo aa aa"
+ @stream.should_receive(:each_item).and_yield(tweet.to_json)
+ yielded_status = nil
+ @client.on_timeline_status do |status|
+ yielded_status = status
+ end.track('abc')
+ yielded_status.should_not be_nil
+ yielded_status[:id].should == 123
+ yielded_status.user.screen_name.should == 'monkey'
+ yielded_status.text.should == 'Oo oo aa aa'
+ end
+ it 'yields itself if block has an arity of 2' do
+ @stream.should_receive(:each_item).and_yield(sample_tweets[0].to_json)
+ yielded_client = nil
+ @client.on_timeline_status do |_, client|
+ yielded_client = client
+ end.track('abc')
+ yielded_client.should_not be_nil
+ yielded_client.should == @client
+ end
+ end
+
+ context 'using on_direct_message' do
+ it 'yields a DirectMessage' do
+ direct_message = sample_direct_messages[0]
+ direct_message["direct_message"]["id"] = 1234
+ direct_message["direct_message"]["sender"]["screen_name"] = "coder"
+ @stream.should_receive(:each_item).and_yield(direct_message.to_json)
+ yielded_dm = nil
+ @client.on_direct_message do |dm|
+ yielded_dm = dm
+ end.userstream
+ yielded_dm.should_not be_nil
+ yielded_dm.id.should == 1234
+ yielded_dm.user.screen_name.should == "coder"
+ end
+
+ it 'yields itself if block has an arity of 2' do
+ @stream.should_receive(:each_item).and_yield(sample_direct_messages[0].to_json)
+ yielded_client = nil
+ @client.on_direct_message do |_, client|
+ yielded_client = client
+ end.userstream
+ yielded_client.should == @client
+ end
+ end
+
it 'should call on_error if a non-hash response is received' do
@stream.should_receive(:each_item).and_yield('["favorited"]')
@client.on_error do |message|
@@ -319,6 +394,18 @@
@client.track('monday')
end
+
+ context "when calling #userstream" do
+ it "sends the userstream host" do
+ Twitter::JSONStream.should_receive(:connect).with(hash_including(:host => "userstream.twitter.com")).and_return(@stream)
+ @client.userstream
+ end
+
+ it "uses the userstream uri" do
+ Twitter::JSONStream.should_receive(:connect).with(hash_including(:path => "/2/user.json")).and_return(@stream)
+ @client.userstream
+ end
+ end
end
end
View
21 spec/tweetstream/direct_message_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe TweetStream::DirectMessage do
+ it 'modifies the :sender key into a TweetStream::User object called #user' do
+ @status = TweetStream::DirectMessage.new({:sender => {:screen_name => 'bob'}})
+ @status.user.is_a?(TweetStream::User).should be_true
+ @status.user.screen_name.should == 'bob'
+ end
+
+ it 'transforms the sender into a TweetStream::User object called #sender' do
+ @status = TweetStream::DirectMessage.new({:sender => {:screen_name => 'bob'}})
+ @status.sender.is_a?(TweetStream::User).should be_true
+ @status.sender.screen_name.should == 'bob'
+ end
+
+ it 'overrides the #id method for itself and the user' do
+ @status = TweetStream::DirectMessage.new({:id => 123, :sender => {:id => 345}})
+ @status.id.should == 123
+ @status.user.id.should == 345
+ end
+end
View
8 spec/tweetstream/status_spec.rb
@@ -1,14 +1,14 @@
-require File.dirname(__FILE__) + '/../spec_helper'
+require 'spec_helper'
describe TweetStream::Status do
it 'should modify the :user key into a TweetStream::User object' do
- @status = TweetStream::Status.new(:user => {:screen_name => 'bob'})
+ @status = TweetStream::Status.new({:user => {:screen_name => 'bob'}})
@status.user.is_a?(TweetStream::User).should be_true
@status.user.screen_name.should == 'bob'
end
-
+
it 'should override the #id method for itself and the user' do
- @status = TweetStream::Status.new(:id => 123, :user => {:id => 345})
+ @status = TweetStream::Status.new({:id => 123, :user => {:id => 345}})
@status.id.should == 123
@status.user.id.should == 345
end
Please sign in to comment.
Something went wrong with that request. Please try again.