twitter-userstream 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+