twitter-stream 0.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of twitter-stream might be problematic. Click here for more details.

@@ -0,0 +1,30 @@
1
+ # twitter-stream
2
+
3
+ Simple Ruby client library for [twitter streaming API](http://apiwiki.twitter.com/Streaming-API-Documentation).
4
+ Uses [EventMachine](http://rubyeventmachine.com/) for connection handling. Adheres to twitter's [reconnection guidline](http://apiwiki.twitter.com/Streaming-API-Documentation#Connecting).
5
+
6
+ JSON format only.
7
+
8
+ ## Usage
9
+
10
+ EventMachine::run {
11
+ stream = Twitter::JSONStream.connect(
12
+ :path => '/1/statuses/filter.json?track=football',
13
+ :auth => 'LOGIN:PASSWORD',
14
+ )
15
+
16
+ stream.each_item do |item|
17
+ # do someting with unparsed JSON item
18
+ end
19
+
20
+ stream.on_error do |message|
21
+ # log or something
22
+ end
23
+ }
24
+
25
+
26
+ ## Examples
27
+
28
+ Open examples/reader.rb. Replace LOGIN:PASSWORD with your real twitter login and password. And
29
+ ruby examples/reader.rb
30
+
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+ gem 'rspec'
3
+ require 'spec/rake/spectask'
4
+
5
+ desc "Run all specs"
6
+ Spec::Rake::SpecTask.new('spec') do |t|
7
+ t.spec_files = FileList['spec/**/*.rb']
8
+ t.spec_opts = %w(-fs --color)
9
+ end
10
+
11
+ begin
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gemspec|
14
+ gemspec.name = "twitter-stream"
15
+ gemspec.summary = "Twitter realtime API client"
16
+ gemspec.description = "Simple Ruby client library for twitter streaming API. Uses EventMachine for connection handling. Adheres to twitter's reconnection guidline. JSON format only."
17
+ gemspec.email = "voloko@gmail.com"
18
+ gemspec.homepage = "http://github.com/voloko/twitter-stream"
19
+ gemspec.authors = ["Vladimir Kolesnikov"]
20
+ end
21
+ rescue LoadError
22
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
23
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,40 @@
1
+ require 'rubygems'
2
+ lib_path = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ $LOAD_PATH.unshift lib_path unless $LOAD_PATH.include?(lib_path)
4
+
5
+ require 'twitter/json_stream'
6
+
7
+ EventMachine::run {
8
+ stream = Twitter::JSONStream.connect(
9
+ :path => '/1/statuses/filter.json',
10
+ :auth => 'LOGIN:PASSWORD',
11
+ :method => 'POST',
12
+ :content => 'track=basketball,football,baseball,footy,soccer'
13
+ )
14
+
15
+ stream.each_item do |item|
16
+ $stdout.print "item: #{item}\n"
17
+ $stdout.flush
18
+ end
19
+
20
+ stream.on_error do |message|
21
+ $stdout.print "error: #{message}\n"
22
+ $stdout.flush
23
+ end
24
+
25
+ stream.on_reconnect do |timeout, retries|
26
+ $stdout.print "reconnecting in: #{timeout} seconds\n"
27
+ $stdout.flush
28
+ end
29
+
30
+ stream.on_max_reconnects do |timeout, retries|
31
+ $stdout.print "Failed after #{retries} failed reconnects\n"
32
+ $stdout.flush
33
+ end
34
+
35
+ trap('TERM') {
36
+ stream.stop
37
+ EventMachine.stop if EventMachine.reactor_running?
38
+ }
39
+ }
40
+ puts "The event loop has ended"
@@ -0,0 +1,14 @@
1
+ HTTP/1.1 200 OK
2
+ Content-Type: application/json
3
+ Transfer-Encoding: chunked
4
+ Server: Jetty(6.1.17)
5
+
6
+ {"text":"Just wanted the world to know what our theologically deep pastor @bprentice has for a desktop background. http://yfrog.com/1rl4ij","favorited":false,"in_reply_to_user_id":null,"in_reply_to_screen_name":null,"source":"<a href=\"http://www.atebits.com/\" rel=\"nofollow\">Tweetie</a>","truncated":false,"created_at":"Thu Oct 08 19:34:09 +0000 2009","geo":null,"user":{"geo_enabled":false,"profile_text_color":"5b5252","location":"Stillwater, Oklahoma","statuses_count":122,"followers_count":70,"profile_link_color":"220099","description":"Taking an unchanging Savior to a changing world! Eagle Heights in Stillwater, Oklahoma.","following":null,"friends_count":136,"profile_sidebar_fill_color":"f37a20","url":"http://www.eagleheights.com","profile_image_url":"http://a3.twimg.com/profile_images/249941843/online_logo_normal.jpg","verified":false,"notifications":null,"favourites_count":0,"profile_sidebar_border_color":"c5f109","protected":false,"screen_name":"eagleheights","profile_background_tile":false,"profile_background_image_url":"http://a1.twimg.com/profile_background_images/5935314/EHBC_LOGO.jpg","created_at":"Tue Mar 17 14:52:04 +0000 2009","name":"Eagle Heights","time_zone":"Central Time (US & Canada)","id":24892440,"utc_offset":-21600,"profile_background_color":"3d3d3d"},"id":4714982187,"in_reply_to_status_id":null}
7
+
8
+ {"text":"I finally took a good pic of our resident BobCat @ the JakeCruise Ranch: http://yfrog.com/769vij","favorited":false,"in_reply_to_user_id":null,"in_reply_to_screen_name":null,"source":"<a href=\"http://www.atebits.com/\" rel=\"nofollow\">Tweetie</a>","truncated":false,"created_at":"Thu Oct 08 19:34:12 +0000 2009","geo":null,"user":{"geo_enabled":false,"profile_text_color":"141318","location":"West Hollywood, CA","statuses_count":265,"followers_count":80,"profile_link_color":"DA4425","description":"Gay Programmer in SoCal","following":null,"friends_count":37,"profile_sidebar_fill_color":"5DD2F4","url":"http://www.kelvo.com/","profile_image_url":"http://a1.twimg.com/profile_images/197950488/kelvis_green_current_normal.jpeg","verified":false,"notifications":null,"favourites_count":1,"profile_sidebar_border_color":"1F6926","protected":false,"screen_name":"KelvisWeHo","profile_background_tile":false,"profile_background_image_url":"http://a1.twimg.com/profile_background_images/9898116/thaneeya3.jpg","created_at":"Fri Apr 17 18:44:57 +0000 2009","name":"Kelvis Del Rio","time_zone":"Pacific Time (US & Canada)","id":32517583,"utc_offset":-28800,"profile_background_color":"cccccc"},"id":4714983168,"in_reply_to_status_id":null}
9
+
10
+ {"text":"Thursdays are long. But at least we're allowed a cheat sheet for stats. http://yfrog.com/9gjc7j","favorited":false,"in_reply_to_user_id":null,"in_reply_to_screen_name":null,"source":"<a href=\"http://twitterrific.com\" rel=\"nofollow\">Twitterrific</a>","truncated":false,"created_at":"Thu Oct 08 19:34:21 +0000 2009","geo":null,"user":{"geo_enabled":false,"profile_text_color":"663B12","location":"Calgary ","statuses_count":68,"followers_count":10,"profile_link_color":"1F98C7","description":"pure. love. art. music. ","following":null,"friends_count":22,"profile_sidebar_fill_color":"DAECF4","url":null,"profile_image_url":"http://a3.twimg.com/profile_images/391882135/Photo_550_normal.jpg","verified":false,"notifications":null,"favourites_count":5,"profile_sidebar_border_color":"C6E2EE","protected":false,"screen_name":"KarinRudmik","profile_background_tile":false,"profile_background_image_url":"http://a3.twimg.com/profile_background_images/33520483/shimmering__by_AnBystrowska.jpg","created_at":"Wed Jul 29 02:37:13 +0000 2009","name":"Karin Rudmik","time_zone":"Mountain Time (US & Canada)","id":61091098,"utc_offset":-25200,"profile_background_color":"C6E2EE"},"id":4714986148,"in_reply_to_status_id":null}
11
+
12
+
13
+
14
+ {"text":"acabando de almorzar, les comparto mi ultima creacion: http://yfrog.com/5mi94j pollo a la maracuya, con verduras sofritas ^^","favorited":false,"in_reply_to_user_id":null,"in_reply_to_screen_name":null,"source":"<a href=\"http://www.seesmic.com/\" rel=\"nofollow\">Seesmic</a>","truncated":false,"created_at":"Thu Oct 08 19:34:28 +0000 2009","geo":null,"user":{"geo_enabled":false,"profile_text_color":"3C3940","location":"Cartagena de Indias","statuses_count":1016,"followers_count":190,"profile_link_color":"0099B9","description":"Cartagenero extremadamente consentido, flojo y amante del anime, el manga, la cocina y los mmorpgs. pdt: y de los postres por supuesto!!!","following":null,"friends_count":253,"profile_sidebar_fill_color":"95E8EC","url":"http://www.flickr.com/photos/lobitokun/","profile_image_url":"http://a1.twimg.com/profile_images/451679242/shippo_normal.jpg","verified":false,"notifications":null,"favourites_count":9,"profile_sidebar_border_color":"5ED4DC","protected":false,"screen_name":"lobitokun","profile_background_tile":false,"profile_background_image_url":"http://a3.twimg.com/profile_background_images/24664583/hideki1.jpg","created_at":"Fri Jul 17 15:32:14 +0000 2009","name":"emiro gomez beltran","time_zone":"Bogota","id":57672295,"utc_offset":-18000,"profile_background_color":"0099B9"},"id":4714988998,"in_reply_to_status_id":null}
@@ -0,0 +1,201 @@
1
+ require 'eventmachine'
2
+ require 'em/buftok'
3
+
4
+ module Twitter
5
+ class JSONStream < EventMachine::Connection
6
+ MAX_LINE_LENGTH = 16*1024
7
+
8
+ # network failure reconnections
9
+ NF_RECONNECT_START = 0.25
10
+ NF_RECONNECT_ADD = 0.25
11
+ NF_RECONNECT_MAX = 16
12
+
13
+ # app failure reconnections
14
+ AF_RECONNECT_START = 10
15
+ AF_RECONNECT_MUL = 2
16
+
17
+ RECONNECT_MAX = 320
18
+ RETRIES_MAX = 10
19
+
20
+ DEFAULT_OPTIONS = {
21
+ :method => 'GET',
22
+ :path => '/',
23
+ :content_type => "application/x-www-form-urlencoded",
24
+ :content => '',
25
+ :path => '/1/statuses/filter.json',
26
+ :host => 'stream.twitter.com',
27
+ :port => 80,
28
+ :auth => 'test:test',
29
+ :user_agent => 'TwitterStream',
30
+ }
31
+
32
+ attr_accessor :code
33
+ attr_accessor :headers
34
+ attr_accessor :nf_last_reconnect
35
+ attr_accessor :af_last_reconnect
36
+ attr_accessor :reconnect_retries
37
+
38
+ def self.connect options = {}
39
+ options = DEFAULT_OPTIONS.merge(options)
40
+ EventMachine.connect options[:host], options[:port], self, options
41
+ end
42
+
43
+ def initialize options = {}
44
+ @options = DEFAULT_OPTIONS.merge(options) # merge in case initialize called directly
45
+ @gracefully_closed = false
46
+ @nf_last_reconnect = nil
47
+ @af_last_reconnect = nil
48
+ @reconnect_retries = 0
49
+ end
50
+
51
+ def each_item &block
52
+ @each_item_callback = block
53
+ end
54
+
55
+ def on_error &block
56
+ @error_callback = block
57
+ end
58
+
59
+ def on_reconnect &block
60
+ @reconnect_callback = block
61
+ end
62
+
63
+ def on_max_reconnects &block
64
+ @max_reconnects_callback = block
65
+ end
66
+
67
+ def stop
68
+ @gracefully_closed = true
69
+ close_connection
70
+ end
71
+
72
+ def unbind
73
+ receive_line(@buffer.flush) unless @buffer.empty?
74
+ schedule_reconnect unless @gracefully_closed
75
+ end
76
+
77
+ def receive_data data
78
+ begin
79
+ @buffer.extract(data).each do |line|
80
+ receive_line(line)
81
+ end
82
+ rescue Exception => e
83
+ receive_error(e.message)
84
+ close_connection
85
+ return
86
+ end
87
+ end
88
+
89
+ def post_init
90
+ reset_state
91
+ send_request
92
+ end
93
+
94
+ protected
95
+ def schedule_reconnect
96
+ timeout = reconnect_timeout
97
+ @reconnect_retries += 1
98
+ if timeout <= RECONNECT_MAX && @reconnect_retries <= RETRIES_MAX
99
+ reconnect_after(timeout)
100
+ else
101
+ @max_reconnects_callback.call(timeout, @reconnect_retries) if @max_reconnects_callback
102
+ end
103
+ end
104
+
105
+ def reconnect_after timeout
106
+ @reconnect_callback.call(timeout, @reconnect_retries) if @reconnect_callback
107
+ EventMachine.add_timer(timeout) do
108
+ reconnect @options[:host], @options[:port]
109
+ end
110
+ end
111
+
112
+ def reconnect_timeout
113
+ if (@code == 0) # network failure
114
+ if @nf_last_reconnect
115
+ @nf_last_reconnect += NF_RECONNECT_ADD
116
+ else
117
+ @nf_last_reconnect = NF_RECONNECT_START
118
+ end
119
+ [@nf_last_reconnect,NF_RECONNECT_MAX].min
120
+ else
121
+ if @af_last_reconnect
122
+ @af_last_reconnect *= AF_RECONNECT_MUL
123
+ else
124
+ @af_last_reconnect = AF_RECONNECT_START
125
+ end
126
+ @af_last_reconnect
127
+ end
128
+ end
129
+
130
+ def reset_state
131
+ @code = 0
132
+ @headers = []
133
+ @state = :init
134
+ @buffer = BufferedTokenizer.new("\r", MAX_LINE_LENGTH)
135
+ end
136
+
137
+ def send_request
138
+ data = []
139
+ data << "#{@options[:method]} #{@options[:path]} HTTP/1.1"
140
+ data << "Host: #{@options[:host]}"
141
+ data << "User-agent: #{@options[:user_agent]}" if @options[:user_agent]
142
+ data << "Authorization: Basic " + [@options[:auth]].pack('m').delete("\r\n")
143
+ if @options[:method] == 'POST'
144
+ data << "Content-type: #{@options[:content_type]}"
145
+ data << "Content-length: #{@options[:content].length}"
146
+ end
147
+ data << "\r\n"
148
+ send_data data.join("\r\n") + @options[:content]
149
+ end
150
+
151
+ def receive_line ln
152
+ case @state
153
+ when :init
154
+ parse_response_line ln
155
+ when :headers
156
+ parse_header_line ln
157
+ when :stream
158
+ parse_stream_line ln
159
+ end
160
+ end
161
+
162
+ def receive_error e
163
+ @error_callback.call(e) if @error_callback
164
+ end
165
+
166
+ def parse_stream_line ln
167
+ ln.strip!
168
+ unless ln.empty?
169
+ if ln[0,1] == '{'
170
+ @each_item_callback.call(ln) if @each_item_callback
171
+ end
172
+ end
173
+ end
174
+
175
+ def parse_header_line ln
176
+ ln.strip!
177
+ if ln.empty?
178
+ reset_timeouts
179
+ @state = :stream
180
+ else
181
+ headers << ln
182
+ end
183
+ end
184
+
185
+ def parse_response_line ln
186
+ if ln =~ /\AHTTP\/1\.[01] ([\d]{3})/
187
+ @code = $1.to_i
188
+ @state = :headers
189
+ else
190
+ receive_error('invalid response')
191
+ close_connection
192
+ end
193
+ end
194
+
195
+ def reset_timeouts
196
+ @nf_last_reconnect = @af_last_reconnect = nil
197
+ @reconnect_retries = 0
198
+ end
199
+
200
+ end
201
+ end
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ lib_path = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ $LOAD_PATH.unshift lib_path unless $LOAD_PATH.include?(lib_path)
4
+
5
+ gem 'rspec'
6
+ require 'spec'
7
+ require 'spec/mocks'
8
+
9
+ def fixture_path(path)
10
+ File.join(File.dirname(__FILE__), '..', 'fixtures', path)
11
+ end
12
+
13
+ def read_fixture(path)
14
+ File.read(fixture_path(path))
15
+ end
@@ -0,0 +1,189 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+ require 'twitter/json_stream'
3
+
4
+ include Twitter
5
+
6
+ describe JSONStream do
7
+
8
+ context "on create" do
9
+
10
+ it "should return stream" do
11
+ EM.should_receive(:connect).and_return('TEST INSTANCE')
12
+ stream = JSONStream.connect {}
13
+ stream.should == 'TEST INSTANCE'
14
+ end
15
+
16
+ it "should define default properties" do
17
+ EM.should_receive(:connect).with do |host, port, handler, opts|
18
+ host.should == 'stream.twitter.com'
19
+ port.should == 80
20
+ opts[:path].should == '/1/statuses/filter.json'
21
+ opts[:method].should == 'GET'
22
+ end
23
+ stream = JSONStream.connect {}
24
+ end
25
+
26
+ end
27
+
28
+
29
+ Host = "127.0.0.1"
30
+ Port = 9550
31
+
32
+ class JSONServer < EM::Connection
33
+ attr_accessor :data
34
+ def receive_data data
35
+ $recieved_data = data
36
+ send_data $data_to_send
37
+ EventMachine.next_tick {
38
+ close_connection if $close_connection
39
+ }
40
+ end
41
+ end
42
+
43
+ context "on valid stream" do
44
+ before :each do
45
+ $data_to_send = read_fixture('twitter/basic_http.txt')
46
+ $recieved_data = ''
47
+ $close_connection = false
48
+ end
49
+
50
+ it "should parse headers" do
51
+ EM.run {
52
+ EM.start_server Host, Port, JSONServer
53
+ @stream = JSONStream.connect :host => Host, :port => Port
54
+ EM.add_timer(0.5){ EM.stop }
55
+ }
56
+ @stream.code.should == 200
57
+ @stream.headers[0].downcase.should include 'content-type'
58
+ end
59
+
60
+ it "should parse headers even after connection close" do
61
+ EM.run {
62
+ $close_connection = true
63
+ EM.start_server Host, Port, JSONServer
64
+ @stream = JSONStream.connect :host => Host, :port => Port
65
+ EM.add_timer(0.5){ EM.stop }
66
+ }
67
+ @stream.code.should == 200
68
+ @stream.headers[0].downcase.should include 'content-type'
69
+ end
70
+
71
+ it "should extract records" do
72
+ EM.run {
73
+ EM.start_server Host, Port, JSONServer
74
+ @stream = JSONStream.connect :host => Host, :port => Port, :user_agent => 'TEST_USER_AGENT'
75
+ EM.add_timer(0.5){ EM.stop }
76
+ }
77
+ $recieved_data.upcase.should include('USER-AGENT: TEST_USER_AGENT')
78
+ end
79
+
80
+ it "should send correct user agent" do
81
+ EM.run {
82
+ $close_connection = true
83
+ EM.start_server Host, Port, JSONServer
84
+ @stream = JSONStream.connect :host => Host, :port => Port
85
+ EM.add_timer(0.5){ EM.stop }
86
+ }
87
+ end
88
+ end
89
+
90
+ context "on network failure" do
91
+ before :each do
92
+ $data_to_send = ''
93
+ $close_connection = true
94
+ end
95
+
96
+ it "should reconnect on network failure" do
97
+ EM.run {
98
+ EM.start_server Host, Port, JSONServer
99
+ @stream = JSONStream.connect :host => Host, :port => Port
100
+ @stream.should_receive(:reconnect)
101
+ EM.add_timer(0.5){ EM.stop }
102
+ }
103
+ end
104
+
105
+ it "should reconnect with 0.25 at base" do
106
+ EM.run {
107
+ EM.start_server Host, Port, JSONServer
108
+ @stream = JSONStream.connect :host => Host, :port => Port
109
+ @stream.should_receive(:reconnect_after).with(0.25)
110
+ EM.add_timer(0.5){ EM.stop }
111
+ }
112
+ end
113
+
114
+ it "should reconnect with linear timeout" do
115
+ EM.run {
116
+ EM.start_server Host, Port, JSONServer
117
+ @stream = JSONStream.connect :host => Host, :port => Port
118
+ @stream.nf_last_reconnect = 1
119
+ @stream.should_receive(:reconnect_after).with(1.25)
120
+ EM.add_timer(0.5){ EM.stop }
121
+ }
122
+ end
123
+
124
+ it "should stop reconnecting after 100 times" do
125
+ EM.run {
126
+ EM.start_server Host, Port, JSONServer
127
+ @stream = JSONStream.connect :host => Host, :port => Port
128
+ @stream.reconnect_retries = 100
129
+ @stream.should_not_receive(:reconnect_after)
130
+ EM.add_timer(0.5){ EM.stop }
131
+ }
132
+ end
133
+
134
+ it "should notify after reconnect limit is reached" do
135
+ timeout, retries = nil, nil
136
+ EM.run {
137
+ EM.start_server Host, Port, JSONServer
138
+ @stream = JSONStream.connect :host => Host, :port => Port
139
+ @stream.on_max_reconnects do |t, r|
140
+ timeout, retries = t, r
141
+ end
142
+ @stream.reconnect_retries = 100
143
+ EM.add_timer(0.5){ EM.stop }
144
+ }
145
+ timeout.should == 0.25
146
+ retries.should == 101
147
+ end
148
+ end
149
+
150
+ context "on application failure" do
151
+ before :each do
152
+ $data_to_send = 'HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm="Firehose"\r\n\r\n1'
153
+ $close_connection = true
154
+ end
155
+
156
+ it "should reconnect on application failure 10 at base" do
157
+ EM.run {
158
+ EM.start_server Host, Port, JSONServer
159
+ @stream = JSONStream.connect :host => Host, :port => Port
160
+ @stream.should_receive(:reconnect_after).with(10)
161
+ EM.add_timer(0.5){ EM.stop }
162
+ }
163
+ end
164
+
165
+ it "should reconnect with exponential timeout" do
166
+ EM.run {
167
+ EM.start_server Host, Port, JSONServer
168
+ @stream = JSONStream.connect :host => Host, :port => Port
169
+ @stream.af_last_reconnect = 160
170
+ @stream.should_receive(:reconnect_after).with(320)
171
+ EM.add_timer(0.5){ EM.stop }
172
+ }
173
+ end
174
+
175
+ it "should not try to reconnect after limit is reached" do
176
+ EM.run {
177
+ EM.start_server Host, Port, JSONServer
178
+ @stream = JSONStream.connect :host => Host, :port => Port
179
+ @stream.af_last_reconnect = 320
180
+ @stream.should_not_receive(:reconnect_after)
181
+ EM.add_timer(0.5){ EM.stop }
182
+ }
183
+ end
184
+ end
185
+
186
+
187
+
188
+ end
189
+
@@ -0,0 +1,46 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{twitter-stream}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Vladimir Kolesnikov"]
9
+ s.date = %q{2009-10-11}
10
+ s.description = %q{Simple Ruby client library for twitter streaming API. Uses EventMachine for connection handling. Adheres to twitter's reconnection guidline. JSON format only.}
11
+ s.email = %q{voloko@gmail.com}
12
+ s.extra_rdoc_files = [
13
+ "README.markdown"
14
+ ]
15
+ s.files = [
16
+ "README.markdown",
17
+ "Rakefile",
18
+ "VERSION",
19
+ "examples/reader.rb",
20
+ "fixtures/twitter/basic_http.txt",
21
+ "lib/twitter/json_stream.rb",
22
+ "spec/spec_helper.rb",
23
+ "spec/twitter/json_stream.rb",
24
+ "twitter-stream.gemspec"
25
+ ]
26
+ s.homepage = %q{http://github.com/voloko/twitter-stream}
27
+ s.rdoc_options = ["--charset=UTF-8"]
28
+ s.require_paths = ["lib"]
29
+ s.rubygems_version = %q{1.3.4}
30
+ s.summary = %q{Twitter realtime API client}
31
+ s.test_files = [
32
+ "spec/spec_helper.rb",
33
+ "spec/twitter/json_stream.rb",
34
+ "examples/reader.rb"
35
+ ]
36
+
37
+ if s.respond_to? :specification_version then
38
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
39
+ s.specification_version = 3
40
+
41
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
42
+ else
43
+ end
44
+ else
45
+ end
46
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: twitter-stream
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Vladimir Kolesnikov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-11 00:00:00 +04:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Simple Ruby client library for twitter streaming API. Uses EventMachine for connection handling. Adheres to twitter's reconnection guidline. JSON format only.
17
+ email: voloko@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.markdown
24
+ files:
25
+ - README.markdown
26
+ - Rakefile
27
+ - VERSION
28
+ - examples/reader.rb
29
+ - fixtures/twitter/basic_http.txt
30
+ - lib/twitter/json_stream.rb
31
+ - spec/spec_helper.rb
32
+ - spec/twitter/json_stream.rb
33
+ - twitter-stream.gemspec
34
+ has_rdoc: true
35
+ homepage: http://github.com/voloko/twitter-stream
36
+ licenses: []
37
+
38
+ post_install_message:
39
+ rdoc_options:
40
+ - --charset=UTF-8
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ requirements: []
56
+
57
+ rubyforge_project:
58
+ rubygems_version: 1.3.4
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: Twitter realtime API client
62
+ test_files:
63
+ - spec/spec_helper.rb
64
+ - spec/twitter/json_stream.rb
65
+ - examples/reader.rb