twitter-userstream 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # Generated from /Users/joshua/Development/userstream/userstream.gemspec
2
+ source :gemcutter
3
+ gem "eventmachine", ">= 0.12.10"
4
+ gem "em-http-request", ">= 0.2.7"
5
+ gem "yajl-ruby", ">= 0.7.5"
6
+ gem "load_path_find", ">= 0.0.5"
7
+ gem "oauth", ">= 0.4.0"
8
+ gem 'rainbow'
9
+ gem 'growl'
10
+ gem 'json'
@@ -0,0 +1,20 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |s|
4
+ s.name = "twitter-userstream"
5
+ s.description = s.summary = "Eventmachine-based Twitter Userstream client"
6
+ s.email = "noah@involver.com"
7
+ s.homepage = "https://github.com/nhorton/twitter-userstream"
8
+ s.authors = ["Joshua Hull", "Fabien Penso", "Noah Horton"]
9
+ s.files = FileList["[A-Z]*", "{lib}/**/*", "{bin}/**/*", "{image}/**/*"]
10
+ s.add_dependency 'eventmachine', ">= 0.12.10"
11
+ s.add_dependency 'em-http-request', ">= 0.2.7"
12
+ s.add_dependency 'yajl-ruby', ">= 0.7.5"
13
+ s.add_dependency 'load_path_find', ">= 0.0.5"
14
+ s.add_dependency 'oauth', ">= 0.4.0"
15
+ s.executables = ['chirp_growl']
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
20
+ end
@@ -0,0 +1,53 @@
1
+ = Userstream
2
+
3
+ == About
4
+
5
+ This is an experimental EventMachine-based client for http://userstream.twitter.com.
6
+
7
+ == Fun Things You Can Do At Home
8
+
9
+ Fire up the growl client!
10
+
11
+ sudo gem install userstream
12
+ chirp_growl
13
+
14
+ Wow! It's growling my tweets! How meta.
15
+
16
+ == Usage
17
+
18
+ require 'rubygems'
19
+ require 'userstream'
20
+ require 'rainbow'
21
+
22
+ chirp = Userstream.new
23
+ chirp.on_reconnect{ puts "reconnecting..." }
24
+ chirp.on_tweet {|t, u| puts "#{t.text} (from #{t.user.name.foreground(:red)} (#{('@' + t.user.screen_name).foreground(:green)}))" }
25
+ chirp.on_follow {|t, u| puts "#{t.source.screen_name.foreground(:green)} following #{t.target.screen_name.foreground(:green)}" }
26
+ chirp.on_favorite {|t, u| puts "#{t.source.screen_name.foreground(:green)} <3 -> #{t.target_object.text}" }
27
+ chirp.on_retweet {|t, u| puts "#{t.source.screen_name.foreground(:green)} RT -> #{t.target_object.text}" }
28
+ chirp.on_direct_message {|t, u| puts "DM : #{t.text} (from #{t.sender.name.foreground(:red)} (#{('@' + t.sender.screen_name).foreground(:green)}))" }
29
+ chirp.connect(Userstream::Connect::User.basic('joshbuddy', 'xxxxxxxx'))
30
+
31
+ == Oauth Usage
32
+
33
+ chirp = Userstream.oauth(consumer_token, consumer_secret)
34
+ chirp.on_reconnect{ puts "reconnecting..." }
35
+ chirp.on_tweet {|t, u| puts "#{t.text} (from #{t.user.name.foreground(:red)} (#{('@' + t.user.screen_name).foreground(:green)}))" }
36
+ chirp.on_follow {|t, u| puts "#{t.source.screen_name.foreground(:green)} following #{t.target.screen_name.foreground(:green)}" }
37
+ chirp.on_favorite {|t, u| puts "#{t.source.screen_name.foreground(:green)} <3 -> #{t.target_object.text}" }
38
+ chirp.on_retweet {|t, u| puts "#{t.source.screen_name.foreground(:green)} RT -> #{t.target_object.text}" }
39
+ chirp.on_direct_message {|t, u| puts "DM : #{t.text} (from #{t.sender.name.foreground(:red)} (#{('@' + t.sender.screen_name).foreground(:green)}))" }
40
+ chirp.connect(Userstream::Connect::User.oauth(access_token, access_secret))
41
+
42
+ == Multi User Usage
43
+
44
+ chirp = Userstream.new(consumer_token, consumer_secret)
45
+
46
+ chirp.on_reconnect{ puts "reconnecting..." }
47
+ chirp.on_tweet {|t, u| puts "#{t.text} (from #{t.user.name.foreground(:red)} (#{('@' + t.user.screen_name).foreground(:green)}))" }
48
+ chirp.on_follow {|t, u| puts "#{t.source.screen_name.foreground(:green)} following #{t.target.screen_name.foreground(:green)}" }
49
+ chirp.on_favorite {|t, u| puts "#{t.source.screen_name.foreground(:green)} <3 -> #{t.target_object.text}" }
50
+ chirp.on_retweet {|t, u| puts "#{t.source.screen_name.foreground(:green)} RT -> #{t.target_object.text}" }
51
+ chirp.on_direct_message {|t, u| puts "DM : #{t.text} (from #{t.sender.name.foreground(:red)} (#{('@' + t.sender.screen_name).foreground(:green)}))" }
52
+
53
+ chirp.connect(Userstream::Connect::User.oauth(access_token1, access_secret1), Userstream::Connect::User.oauth(access_token2, access_secret2))
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env ruby -rubygems
2
+
3
+ begin
4
+ require 'growl'
5
+ rescue LoadError
6
+ puts "You'll need to install growl\nsudo gem install growl"
7
+ exit(1)
8
+ end
9
+
10
+ begin
11
+ require 'json'
12
+ rescue LoadError
13
+ puts "You'll need to install json\nsudo gem install json"
14
+ exit(1)
15
+ end
16
+
17
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'userstream')
18
+ require 'etc'
19
+ require 'fileutils'
20
+
21
+ FileUtils.mkdir_p(File.join(Etc.getpwuid.dir, '.userstream'))
22
+ FileUtils.mkdir_p(File.join(Etc.getpwuid.dir, '.userstream', 'cache'))
23
+
24
+ json_file = File.join(Etc.getpwuid.dir, '.userstream', 'user.json')
25
+ cache_dir = File.join(Etc.getpwuid.dir, '.userstream', 'cache')
26
+
27
+ if File.exist?(json_file)
28
+ data = JSON.parse(File.read(json_file))
29
+ @username = data['username']
30
+ @password = data['password']
31
+ else
32
+ puts "Enter your username:"
33
+ @username = gets.strip
34
+ puts "Enter your password:"
35
+ @password = gets.strip
36
+ File.open(json_file, 'w') {|f| f << {:username => @username, :password => @password}.to_json}
37
+ end
38
+
39
+ chirp = Userstream.new
40
+
41
+ Growl.notify {
42
+ self.message = 'Starting chirp_growl...'
43
+ self.title = 'ChirpStreeeeem!'
44
+ self.image = File.join(File.dirname(__FILE__), '..', 'image', 'bird.png')
45
+ }
46
+
47
+ chirp.on_tweet { |t, u|
48
+ t.user.with_profile_image(u, cache_dir) do |image_path|
49
+ Growl.notify {
50
+ self.message = t.text
51
+ self.title = "@#{t.user.screen_name}"
52
+ self.image = image_path if image_path
53
+ }
54
+ end
55
+ }
56
+
57
+ chirp.connect(Userstream::Connect::User.basic(@username, @password))
58
+
59
+
Binary file
@@ -0,0 +1,153 @@
1
+ require 'eventmachine'
2
+ require 'em-http'
3
+ require 'yajl'
4
+ require 'pp'
5
+ require 'load_path_find'
6
+
7
+ require 'oauth'
8
+ require 'oauth/client/em_http'
9
+
10
+ $LOAD_PATH.add_current
11
+
12
+ require 'userstream/twitter_object'
13
+ require 'userstream/event'
14
+ require 'userstream/event/follow'
15
+ require 'userstream/event/direct_message'
16
+ require 'userstream/event/retweet'
17
+ require 'userstream/event/favorite'
18
+ require 'userstream/event/unfavorite'
19
+ require 'userstream/event/delete'
20
+ require 'userstream/user'
21
+ require 'userstream/friend'
22
+ require 'userstream/tweet'
23
+ require 'userstream/connect'
24
+
25
+ class Userstream
26
+
27
+ include Connect
28
+
29
+ attr_reader :handlers
30
+
31
+ EventHandlerTypes = [:friend, :tweet, :follow, :unfollow, :favorite, :unfavorite, :retweet, :delete, :direct_message, :block, :unblock, :list_member_removed, :list_member_added, :list_user_subscribed, :list_user_unsubscribed]
32
+ ConnectionHandlerTypes = [:disconnect, :connect]
33
+ HandlerTypes = ConnectionHandlerTypes + EventHandlerTypes
34
+
35
+ Handlers = Struct.new(*HandlerTypes)
36
+
37
+ attr_reader :consumer_token, :consumer_secret, :fill_in
38
+
39
+ def initialize(options = nil)
40
+ @consumer_token = options && options[:consumer_token]
41
+ @consumer_secret = options && options[:consumer_secret]
42
+ @fill_in = options && options[:fill_in]
43
+ @connect_url = "https://userstream.twitter.com/2/user.json"
44
+ @handlers = Handlers.new(*HandlerTypes.map{|h| []})
45
+ @on_connect_called = {}
46
+ @users = []
47
+ end
48
+
49
+ HandlerTypes.each do |h|
50
+ module_eval "
51
+ def on_#{h}(&block)
52
+ @handlers.#{h} << block
53
+ end
54
+ "
55
+ end
56
+
57
+ EventHandlerTypes.each do |h|
58
+ const_name = h.to_s.split("_").map{|p| p.capitalize}.join
59
+ module_eval "
60
+ def dispatch_#{h}(user, data)
61
+ unless @handlers.#{h}.empty?
62
+ obj = #{const_name}.new(self, data)
63
+ @fill_in ? obj.load_all(user) { |nobj|
64
+ @handlers.#{h}.each{|h| h.call(nobj, user)}
65
+ } : @handlers.#{h}.each{|h| h.call(obj, user)}
66
+ end
67
+ end
68
+ "
69
+ end
70
+
71
+ def dispatch_friend(user, data)
72
+ unless @handlers.friend.empty?
73
+ data['friends'].each_slice(100) do |friend_ids|
74
+ parser = Yajl::Parser.new
75
+ parser.on_parse_complete = proc { |friends|
76
+ friends.each do |friend|
77
+ @handlers.friend.each{|h| h.call(friend)}
78
+ end
79
+ }
80
+ friend_http = get_connection(user, "http://api.twitter.com/1/users/lookup.json", :post, :body => {'user_id' => friend_ids.join(',')})
81
+ friend_http.stream { |chunk|
82
+ parser << chunk
83
+ }
84
+ end
85
+ end
86
+ end
87
+
88
+ def dispatch_disconnect(user)
89
+ return if @handlers.disconnect.empty?
90
+ @handlers.disconnect.each{|h| h.call(user)}
91
+ end
92
+
93
+ def dispatch_connect(user)
94
+ return if @on_connect_called[user.name]
95
+ @handlers.connect.each{|h| h.call(user)}
96
+ @on_connect_called[user.name] = true
97
+ end
98
+
99
+ def connect(*users)
100
+ unless EM.reactor_running?
101
+ EM.run { connect(*users) }
102
+ else
103
+ @users.concat(users)
104
+ @user_adder ||= EM.add_periodic_timer(1) {
105
+ users_to_connect = @users.slice!(0, 10)
106
+ if users_to_connect && !users_to_connect.empty?
107
+ users_to_connect.each do |user|
108
+ connect_single(user)
109
+ end
110
+ end
111
+ }
112
+ end
113
+ end
114
+
115
+ def connect_single(user)
116
+ parser = Yajl::Parser.new
117
+ parser.on_parse_complete = Proc.new{|parsed_data|
118
+ if parsed_data['direct_message']
119
+ dispatch_direct_message(user, parsed_data)
120
+ elsif parsed_data['friends']
121
+ dispatch_friend(user, parsed_data)
122
+ elsif parsed_data['text']
123
+ dispatch_tweet(user, parsed_data)
124
+ elsif parsed_data['event']
125
+ method_sym = "dispatch_#{parsed_data['event']}".to_sym
126
+ if respond_to?(method_sym)
127
+ send("dispatch_#{parsed_data['event']}".to_sym, user, parsed_data)
128
+ else
129
+ puts "no handler for #{parsed_data['event']}"
130
+ end
131
+ elsif parsed_data['delete']
132
+ dispatch_delete(user, parsed_data)
133
+ else
134
+ puts "i didn't know what to do with this!"
135
+ pp parsed_data
136
+ end
137
+ }
138
+
139
+ http = get_connection(user, @connect_url, :get)
140
+ http.errback { |e, err|
141
+ dispatch_disconnect(user)
142
+ }
143
+ http.stream { |chunk|
144
+ dispatch_connect(user)
145
+ begin
146
+ parser << chunk
147
+ rescue Yajl::ParseError
148
+ p $!
149
+ puts "bad chunk: #{chunk.inspect}"
150
+ end
151
+ }
152
+ end
153
+ end
@@ -0,0 +1,60 @@
1
+ class Userstream
2
+ module Connect
3
+ class User
4
+
5
+ class Oauth
6
+ attr_reader :name, :access_token, :access_secret
7
+ def initialize(access_token, access_secret, name = nil)
8
+ @name = name
9
+ @access_token = access_token
10
+ @access_secret = access_secret
11
+ end
12
+
13
+ def oauth?
14
+ true
15
+ end
16
+ end
17
+
18
+ class Basic
19
+ attr_reader :name, :password
20
+ def initialize(name, password)
21
+ @name = name
22
+ @password = password
23
+ end
24
+
25
+ def oauth?
26
+ false
27
+ end
28
+ end
29
+
30
+ def self.oauth(access_token, access_secret, name = nil)
31
+ Oauth.new(access_token, access_secret, name)
32
+ end
33
+
34
+ def self.basic(name, password)
35
+ Basic.new(name, password)
36
+ end
37
+ end
38
+
39
+ def get_connection(user, url, method, extras = nil)
40
+ options = extras ? extras.merge(:timeout => 0) : {:timeout => 0}
41
+ if user.oauth?
42
+ request = EM::HttpRequest.new(url)
43
+ request.send(method, options) do |client|
44
+ twitter_oauth_consumer.sign!(client, twitter_oauth_access_token(user.access_token, user.access_secret))
45
+ end
46
+ else
47
+ http = EM::HttpRequest.new(url).send(method, options.merge(:head => {'authorization' => [user.name, user.password]}))
48
+ end
49
+
50
+ end
51
+
52
+ def twitter_oauth_consumer
53
+ OAuth::Consumer.new(consumer_token, consumer_secret, :site => "http://twitter.com")
54
+ end
55
+
56
+ def twitter_oauth_access_token(token=nil,secret=nil)
57
+ OAuth::AccessToken.new(twitter_oauth_consumer, token, secret)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,5 @@
1
+ class Userstream
2
+
3
+ class Event < TwitterObject
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ class Userstream
2
+ class Delete < Event
3
+ ATTRS = [:id, :user_id]
4
+ attr_accessor *ATTRS
5
+ user_writer :user_id
6
+ end
7
+ end
@@ -0,0 +1,24 @@
1
+ class Userstream
2
+ class DirectMessage < TwitterObject
3
+
4
+ ATTRS = [:coordinates, :recipient, :sender, :sender_screen_name, :text, :id, :recipient_id, :sender_id]
5
+
6
+ attr_accessor *ATTRS
7
+ user_writer :sender
8
+ user_writer :recipient
9
+
10
+ def initialize(base, data = nil)
11
+ @base = base
12
+ from_json(data['direct_message']) if data and data['direct_message']
13
+ yield self if block_given?
14
+ end
15
+
16
+ def tweet_loadable_id
17
+ id
18
+ end
19
+
20
+ def loaded?
21
+ !text.nil?
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ class Userstream
2
+ class Favorite < Event
3
+ ATTRS = [ :target, :source, :target_object ]
4
+ attr_accessor *ATTRS
5
+ user_writer :target, :source
6
+ tweet_writer :target_object
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ class Userstream
2
+ class Follow < Event
3
+ ATTRS = [ :target, :source ]
4
+ attr_accessor *ATTRS
5
+ user_writer :target, :source
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ class Userstream
2
+ class Retweet < Event
3
+ ATTRS = [ :target, :source, :target_object ]
4
+ attr_accessor *ATTRS
5
+ user_writer :target, :source
6
+ tweet_writer :target_object
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ class Userstream
2
+ class Unfavorite < Event
3
+ ATTRS = [ :target, :source, :target_object ]
4
+ attr_accessor *ATTRS
5
+ user_writer :target, :source
6
+ tweet_writer :target_object
7
+ end
8
+ end
@@ -0,0 +1,4 @@
1
+ class Userstream
2
+ class Friend < User
3
+ end
4
+ end
@@ -0,0 +1,17 @@
1
+ class Userstream
2
+ class Tweet < TwitterObject
3
+
4
+ ATTRS = [:coordinates, :favorited, :created_at, :truncated, :contributors, :text, :id, :geo, :in_reply_to_user_id, :place, :source, :in_reply_to_screen_name, :in_reply_to_status_id, :user]
5
+
6
+ attr_accessor *ATTRS
7
+ user_writer :user
8
+
9
+ def tweet_loadable_id
10
+ id
11
+ end
12
+
13
+ def loaded?
14
+ !text.nil?
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,135 @@
1
+ class TwitterObject
2
+
3
+ SINGLE_USER_URL = "http://api.twitter.com/1/users/show/%s.json"
4
+ SINGLE_TWEET_URL = "http://api.twitter.com/1/statuses/show/%s.json"
5
+
6
+ attr_reader :base, :data
7
+
8
+ def initialize(base, data = nil)
9
+ @base, @data = base, data
10
+ from_json(data) if data
11
+ yield self if block_given?
12
+ end
13
+
14
+ def self.user_writer(*attrs)
15
+ attrs.each do |attr|
16
+ module_eval "
17
+ def #{attr}=(#{attr})
18
+ @#{attr} = if #{attr}.is_a?(Hash)
19
+ Userstream::User.new(base, #{attr})
20
+ else
21
+ Userstream::User.new(base) {|u| u.id = #{attr}}
22
+ end
23
+ end
24
+ "
25
+ end
26
+ end
27
+
28
+ def self.tweet_writer(*attrs)
29
+ attrs.each do |attr|
30
+ module_eval "
31
+ def #{attr}=(#{attr})
32
+ @#{attr} = if #{attr}.is_a?(Hash)
33
+ Userstream::Tweet.new(base, #{attr})
34
+ else
35
+ Userstream::Tweet.new(base) {|t| t.id = #{attr}}
36
+ end
37
+ end
38
+ "
39
+ end
40
+ end
41
+
42
+ def from_json(data)
43
+ self.class.attrs.each { |a| self.send(:"#{a}=", data[a.to_s]) }
44
+ end
45
+
46
+ def self.attrs
47
+ const_get(:ATTRS)
48
+ end
49
+
50
+ def load_all(loading_user, &block)
51
+ attrs = self.class.attrs
52
+ if respond_to?(:loaded?) && !loaded?
53
+ if respond_to?(:user_loadable_id)
54
+ from_json(get_user_data(loading_user, user_loadable_id)[user_loadable_id])
55
+ load_all(&block)
56
+ elsif respond_to?(:tweet_loadable_id)
57
+ from_json(get_tweet_data(loading_user, tweet_loadable_id)[tweet_loadable_id])
58
+ load_all(&block)
59
+ end
60
+ else
61
+ tweet_ids = {}
62
+ user_ids = {}
63
+ attrs.each do |a|
64
+ obj = send(a)
65
+ if obj.respond_to?(:loaded?) && !obj.loaded?
66
+ if obj.respond_to?(:user_loadable_id)
67
+ user_ids[a] = obj.user_loadable_id
68
+ elsif obj.respond_to?(:tweet_loadable_id)
69
+ tweet_ids[a] = obj.tweet_loadable_id
70
+ end
71
+ end
72
+ end
73
+ get_tweet_data(loading_user, tweet_ids.values) { |tweet_data|
74
+ tweet_ids.each{|k,v| self.send(:"#{k}=", tweet_data[v])}
75
+ user_data = get_user_data(loading_user, user_ids.values) { |user_data|
76
+ user_ids.each{|k,v| self.send(:"#{k}=", user_data[v])}
77
+ yield self
78
+ }
79
+ }
80
+ end
81
+
82
+ end
83
+
84
+ def get_tweet_data(loading_user, ids)
85
+ ids = Array(ids).uniq
86
+ data = {}
87
+ if ids.empty?
88
+ yield data
89
+ else
90
+ load_tweet_data(loading_user, ids, data) { yield data }
91
+ end
92
+ end
93
+
94
+ def load_tweet_data(loading_user, ids, data, &block)
95
+ id = ids.shift
96
+ if (id)
97
+ parser = Yajl::Parser.new
98
+ parser.on_parse_complete = proc { |parsed|
99
+ data[id] = parsed
100
+ load_tweet_data(ids, data, &block)
101
+ }
102
+ http = base.get_connection(loading_user, "http://api.twitter.com/1/statuses/show/%s.json" % id, :get)
103
+ http.stream { |chunk|
104
+ parser << chunk
105
+ }
106
+ else
107
+ yield
108
+ end
109
+ end
110
+
111
+ def get_user_data(loading_user, ids)
112
+ ids = Array(ids).uniq
113
+ data = {}
114
+ if ids.empty?
115
+ yield data
116
+ else
117
+ load_user_data(loading_user, ids, data) { yield data }
118
+ end
119
+ end
120
+
121
+ def load_user_data(loading_user, ids, data)
122
+ parser = Yajl::Parser.new
123
+ parser.on_parse_complete = proc { |parsed|
124
+ parsed.each do |user|
125
+ data[user["id"]] = user
126
+ end
127
+ yield
128
+ }
129
+ http = base.get_connection(loading_user, "http://api.twitter.com/1/users/lookup.json", :post, :body => {'user_id' => ids.join(',')})
130
+ http.stream { |chunk|
131
+ parser << chunk
132
+ }
133
+ end
134
+
135
+ end
@@ -0,0 +1,46 @@
1
+ require 'em-http'
2
+ require 'digest/md5'
3
+
4
+ class Userstream
5
+ class User < TwitterObject
6
+
7
+ ATTRS = [
8
+ :profile_background_tile, :name, :profile_sidebar_border_color, :profile_sidebar_fill_color, :profile_image_url, :location, :created_at,
9
+ :profile_link_color, :url, :contributors_enabled, :favourites_count, :utc_offset, :id, :followers_count, :protected, :lang,
10
+ :profile_text_color, :geo_enabled, :profile_background_color, :time_zone, :notifications, :description, :verified, :profile_background_image_url,
11
+ :statuses_count, :friends_count, :screen_name, :following
12
+ ]
13
+
14
+ attr_accessor *ATTRS
15
+
16
+ def loaded?
17
+ name
18
+ end
19
+
20
+ def user_loadable_id
21
+ id
22
+ end
23
+
24
+ def with_profile_image(loading_user, cache_dir)
25
+ raise unless loaded?
26
+
27
+ cached_file = File.join(cache_dir, "#{Digest::MD5.hexdigest(profile_image_url)}#{File.extname(profile_image_url)}")
28
+
29
+ if File.exist?(cached_file)
30
+ yield cached_file
31
+ else
32
+ http = base.get_connection(loading_user, profile_image_url, :get)
33
+ http.callback do
34
+ if http.response_header.status == 200
35
+ File.open(cached_file, 'w') {|f| f << http.response}
36
+ yield cached_file
37
+ else
38
+ yield nil
39
+ end
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+ end
metadata ADDED
@@ -0,0 +1,286 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: twitter-userstream
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Joshua Hull
14
+ - Fabien Penso
15
+ - Noah Horton
16
+ autorequire:
17
+ bindir: bin
18
+ cert_chain: []
19
+
20
+ date: 2011-05-14 00:00:00 Z
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: eventmachine
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 59
31
+ segments:
32
+ - 0
33
+ - 12
34
+ - 10
35
+ version: 0.12.10
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: em-http-request
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ hash: 25
47
+ segments:
48
+ - 0
49
+ - 2
50
+ - 7
51
+ version: 0.2.7
52
+ type: :runtime
53
+ version_requirements: *id002
54
+ - !ruby/object:Gem::Dependency
55
+ name: yajl-ruby
56
+ prerelease: false
57
+ requirement: &id003 !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 9
63
+ segments:
64
+ - 0
65
+ - 7
66
+ - 5
67
+ version: 0.7.5
68
+ type: :runtime
69
+ version_requirements: *id003
70
+ - !ruby/object:Gem::Dependency
71
+ name: load_path_find
72
+ prerelease: false
73
+ requirement: &id004 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ hash: 21
79
+ segments:
80
+ - 0
81
+ - 0
82
+ - 5
83
+ version: 0.0.5
84
+ type: :runtime
85
+ version_requirements: *id004
86
+ - !ruby/object:Gem::Dependency
87
+ name: oauth
88
+ prerelease: false
89
+ requirement: &id005 !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 15
95
+ segments:
96
+ - 0
97
+ - 4
98
+ - 0
99
+ version: 0.4.0
100
+ type: :runtime
101
+ version_requirements: *id005
102
+ - !ruby/object:Gem::Dependency
103
+ name: rainbow
104
+ prerelease: false
105
+ requirement: &id006 !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ hash: 3
111
+ segments:
112
+ - 0
113
+ version: "0"
114
+ type: :runtime
115
+ version_requirements: *id006
116
+ - !ruby/object:Gem::Dependency
117
+ name: growl
118
+ prerelease: false
119
+ requirement: &id007 !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ hash: 3
125
+ segments:
126
+ - 0
127
+ version: "0"
128
+ type: :runtime
129
+ version_requirements: *id007
130
+ - !ruby/object:Gem::Dependency
131
+ name: json
132
+ prerelease: false
133
+ requirement: &id008 !ruby/object:Gem::Requirement
134
+ none: false
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ hash: 3
139
+ segments:
140
+ - 0
141
+ version: "0"
142
+ type: :runtime
143
+ version_requirements: *id008
144
+ - !ruby/object:Gem::Dependency
145
+ name: eventmachine
146
+ prerelease: false
147
+ requirement: &id009 !ruby/object:Gem::Requirement
148
+ none: false
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ hash: 59
153
+ segments:
154
+ - 0
155
+ - 12
156
+ - 10
157
+ version: 0.12.10
158
+ type: :runtime
159
+ version_requirements: *id009
160
+ - !ruby/object:Gem::Dependency
161
+ name: em-http-request
162
+ prerelease: false
163
+ requirement: &id010 !ruby/object:Gem::Requirement
164
+ none: false
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ hash: 25
169
+ segments:
170
+ - 0
171
+ - 2
172
+ - 7
173
+ version: 0.2.7
174
+ type: :runtime
175
+ version_requirements: *id010
176
+ - !ruby/object:Gem::Dependency
177
+ name: yajl-ruby
178
+ prerelease: false
179
+ requirement: &id011 !ruby/object:Gem::Requirement
180
+ none: false
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ hash: 9
185
+ segments:
186
+ - 0
187
+ - 7
188
+ - 5
189
+ version: 0.7.5
190
+ type: :runtime
191
+ version_requirements: *id011
192
+ - !ruby/object:Gem::Dependency
193
+ name: load_path_find
194
+ prerelease: false
195
+ requirement: &id012 !ruby/object:Gem::Requirement
196
+ none: false
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ hash: 21
201
+ segments:
202
+ - 0
203
+ - 0
204
+ - 5
205
+ version: 0.0.5
206
+ type: :runtime
207
+ version_requirements: *id012
208
+ - !ruby/object:Gem::Dependency
209
+ name: oauth
210
+ prerelease: false
211
+ requirement: &id013 !ruby/object:Gem::Requirement
212
+ none: false
213
+ requirements:
214
+ - - ">="
215
+ - !ruby/object:Gem::Version
216
+ hash: 15
217
+ segments:
218
+ - 0
219
+ - 4
220
+ - 0
221
+ version: 0.4.0
222
+ type: :runtime
223
+ version_requirements: *id013
224
+ description: Eventmachine-based Twitter Userstream client
225
+ email: noah@involver.com
226
+ executables:
227
+ - chirp_growl
228
+ extensions: []
229
+
230
+ extra_rdoc_files: []
231
+
232
+ files:
233
+ - Gemfile
234
+ - Rakefile
235
+ - Readme.rdoc
236
+ - VERSION
237
+ - bin/chirp_growl
238
+ - image/bird.png
239
+ - lib/userstream.rb
240
+ - lib/userstream/connect.rb
241
+ - lib/userstream/event.rb
242
+ - lib/userstream/event/delete.rb
243
+ - lib/userstream/event/direct_message.rb
244
+ - lib/userstream/event/favorite.rb
245
+ - lib/userstream/event/follow.rb
246
+ - lib/userstream/event/retweet.rb
247
+ - lib/userstream/event/unfavorite.rb
248
+ - lib/userstream/friend.rb
249
+ - lib/userstream/tweet.rb
250
+ - lib/userstream/twitter_object.rb
251
+ - lib/userstream/user.rb
252
+ homepage: https://github.com/nhorton/twitter-userstream
253
+ licenses: []
254
+
255
+ post_install_message:
256
+ rdoc_options: []
257
+
258
+ require_paths:
259
+ - lib
260
+ required_ruby_version: !ruby/object:Gem::Requirement
261
+ none: false
262
+ requirements:
263
+ - - ">="
264
+ - !ruby/object:Gem::Version
265
+ hash: 3
266
+ segments:
267
+ - 0
268
+ version: "0"
269
+ required_rubygems_version: !ruby/object:Gem::Requirement
270
+ none: false
271
+ requirements:
272
+ - - ">="
273
+ - !ruby/object:Gem::Version
274
+ hash: 3
275
+ segments:
276
+ - 0
277
+ version: "0"
278
+ requirements: []
279
+
280
+ rubyforge_project:
281
+ rubygems_version: 1.8.1
282
+ signing_key:
283
+ specification_version: 3
284
+ summary: Eventmachine-based Twitter Userstream client
285
+ test_files: []
286
+