birdgrinder 0.1.0.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -123,7 +123,7 @@ module BirdGrinder
123
123
  @user = options.user.screen_name if options.user? && options.user.screen_name?
124
124
  @user ||= options.sender_screen_name if options.sender_screen_name?
125
125
  @last_message_direct = (message == :incoming_direct_message)
126
- @last_message_id = options.id
126
+ @last_message_id = options.id? ? options.id : -1
127
127
  end
128
128
 
129
129
  def halt_handlers!
@@ -38,9 +38,9 @@ module BirdGrinder
38
38
  # Forwards a given message type (with options) to each handler,
39
39
  # storing the current id if changed.
40
40
  def receive_message(type, options = BirdGrinder::Nash.new)
41
- logger.debug "receiving message: #{type.inspect} - #{options.id}"
41
+ logger.debug "receiving message: #{type.inspect} - #{options.id? ? options.id : 'unknown id'}"
42
42
  dispatch(type.to_sym, options)
43
- update_stored_id_for(type, options.id)
43
+ update_stored_id_for(type, options.id) if options.id?
44
44
  end
45
45
 
46
46
  # Fetches all direct messages and mentions and also schedules
@@ -128,7 +128,7 @@ module BirdGrinder
128
128
  def fetch_latest(name, type)
129
129
  options = {}
130
130
  id = stored_id_for(type)
131
- options[:since_id] = id unless id.blank?
131
+ options[:since_id] = id unless id.blank? || id.to_i == 0
132
132
  @tweeter.send(name, options)
133
133
  end
134
134
 
@@ -0,0 +1,44 @@
1
+ module BirdGrinder
2
+ # A simple BirdGrinder::Base subclass which has a
3
+ # focus on processing tweets from a stream.
4
+ class StreamHandler < Base
5
+
6
+ class << self
7
+
8
+ # Do something on tweet's from a given stream
9
+ # @param [Symbol] the stream name, e.g. :filter / :sample
10
+ def tweet_from_stream(name, &blk)
11
+ on_event(:incoming_stream) do
12
+ instance_eval(&blk) if options.streaming_source == name && options.stream_type == :tweet
13
+ end
14
+ end
15
+
16
+ # Do something on delete's from a given stream
17
+ # @param [Symbol] the stream name, e.g. :filter / :sample
18
+ def delete_from_stream(name, &blk)
19
+ on_event(:incoming_stream) do
20
+ instance_eval(&blk) if options.streaming_source == name && options.stream_type == :delete
21
+ end
22
+ end
23
+
24
+ # Do something on rate limit's from a given stream
25
+ # @param [Symbol] the stream name, e.g. :filter / :sample
26
+ def rate_limit_from_stream(name, &blk)
27
+ on_event(:incoming_stream) do
28
+ instance_eval(&blk) if options.streaming_source == name && options.stream_type == :limit
29
+ end
30
+ end
31
+
32
+ %w(sample filter follow track).each do |type|
33
+ define_method(type.to_sym) do |*args|
34
+ BirdGrinder::Loader.once_running do
35
+ streaming = BirdGrinder::Client.current.tweeter.streaming
36
+ streaming.send(type.to_sym, *args)
37
+ end
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
@@ -18,9 +18,15 @@ module BirdGrinder
18
18
  def process_stream_item(json)
19
19
  return if !json.is_a?(Hash)
20
20
  processed = json.to_nash.normalized
21
- processed.type = lookup_type_for_steam_response(processed)
21
+ stream_type = lookup_type_for_steam_response(processed)
22
+ case stream_type
23
+ when :delete
24
+ processed = processed[:delete].status
25
+ when :limit
26
+ processed = processed.limit
27
+ end
28
+ processed.stream_type = stream_type
22
29
  processed.streaming_source = @stream_name
23
- logger.info "Processing Stream Tweet #{processed.id}: #{processed.text}"
24
30
  @parent.delegate.receive_message(:incoming_stream, processed)
25
31
  end
26
32
 
@@ -61,12 +61,12 @@ module BirdGrinder
61
61
  path = opts.delete(:path)
62
62
  processor = StreamProcessor.new(@parent, name)
63
63
  http_opts = {
64
- :on_response => processor.method(:receive_chunk),
65
- :head => {'Authorization' => @parent.auth_credentials}
64
+ :head => {'Authorization' => @parent.auth_credentials}
66
65
  }
67
66
  http_opts[:query] = opts if opts.present?
68
67
  url = streaming_base_url / api_version.to_s / "statuses" / "#{path || name}.json"
69
68
  http = EventMachine::HttpRequest.new(url).get(http_opts)
69
+ http.stream(&processor.method(:receive_chunk))
70
70
  end
71
71
 
72
72
  end
@@ -159,9 +159,55 @@ module BirdGrinder
159
159
  end
160
160
  end
161
161
  end
162
+
163
+ # Gets a list ids who are following a given user id / screenname
164
+ #
165
+ # @param [String,Integer] id the user id or screen name to get followers for.
166
+ # @param [Hash] opts extra options to pass in the query string.
167
+ # @option opts [Integer] :cursor the cursor offset in the results
168
+ def follower_ids(id, opts = {})
169
+ cursor_list = []
170
+ if opts[:cursor].present?
171
+ logger.info "Getting page w/ cursor #{opts[:cursor]} for #{id}"
172
+ get_followers_page(id, opts) do |res|
173
+ results = BirdGrinder::Nash.new
174
+ results.cursor = opts[:cursor]
175
+ results.user_id = id
176
+ results.ids = res.ids? ? res.ids : []
177
+ results.next_cursor = res.next_cursor || 0
178
+ results.previous_cursor = res.previous_cursor || 0
179
+ results.all = (res.previous_cursor == 0 && res.next_cursor == 0)
180
+ delegate.receive_message(:incoming_follower_ids, results)
181
+ end
182
+ else
183
+ logger.info "Getting all followers for #{id}"
184
+ get_followers(id, opts.merge(:cursor => -1), {
185
+ :user_id => id,
186
+ :all => true,
187
+ :ids => []
188
+ }.to_nash)
189
+ end
190
+ end
162
191
 
163
192
  protected
164
193
 
194
+ def get_followers(id, opts, nash)
195
+ get_followers_page(id, opts) do |res|
196
+ nash.ids += res.ids if res.ids?
197
+ if res.next_cursor == 0
198
+ delegate.receive_message(:incoming_follower_ids, nash)
199
+ else
200
+ get_followers(id, opts.merge(:cursor => res.next_cursor), nash)
201
+ end
202
+ end
203
+ end
204
+
205
+ def get_followers_page(id, opts, &blk)
206
+ get("followers/ids/#{id}.json", opts) do |res|
207
+ blk.call(res)
208
+ end
209
+ end
210
+
165
211
  def request(path = "/")
166
212
  EventMachine::HttpRequest.new(api_base_url / path)
167
213
  end
@@ -191,13 +237,17 @@ module BirdGrinder
191
237
 
192
238
  def add_response_callback(http, blk)
193
239
  http.callback do
194
- res = parse_response(http)
195
- if res.nil?
196
- logger.warn "Got back a blank / errored response."
197
- elsif successful?(res)
198
- blk.call(res) unless blk.blank?
240
+ if http.response_header.status == 200
241
+ res = parse_response(http)
242
+ if res.nil?
243
+ logger.warn "Got back a blank / errored response."
244
+ elsif successful?(res)
245
+ blk.call(res) unless blk.blank?
246
+ else
247
+ logger.error "Error: #{res.error} (on #{res.request})"
248
+ end
199
249
  else
200
- logger.eror "Error: #{res.error} (on #{res.request})"
250
+ logger.info "Request returned a non-200 status code, had #{http.response_header.status} instead."
201
251
  end
202
252
  end
203
253
  end
data/lib/bird_grinder.rb CHANGED
@@ -8,7 +8,7 @@ require 'em-http'
8
8
  module BirdGrinder
9
9
  include Perennial
10
10
 
11
- VERSION = [0, 1, 0, 0]
11
+ VERSION = [0, 1, 1, 0]
12
12
 
13
13
  def self.version(include_minor = false)
14
14
  VERSION[0, (include_minor ? 4 : 3)].join(".")
@@ -22,7 +22,7 @@ module BirdGrinder
22
22
  end
23
23
 
24
24
  has_library :cacheable, :tweeter, :client, :base, :command_handler,
25
- :console, :queue_processor
25
+ :console, :queue_processor, :stream_handler
26
26
 
27
27
  extends_library :loader
28
28
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: birdgrinder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Darcy Laycock
@@ -90,6 +90,7 @@ files:
90
90
  - lib/bird_grinder/exceptions.rb
91
91
  - lib/bird_grinder/loader.rb
92
92
  - lib/bird_grinder/queue_processor.rb
93
+ - lib/bird_grinder/stream_handler.rb
93
94
  - lib/bird_grinder/tweeter/search.rb
94
95
  - lib/bird_grinder/tweeter/stream_processor.rb
95
96
  - lib/bird_grinder/tweeter/streaming.rb