birdgrinder 0.1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/birdgrinder +34 -0
- data/examples/bird_grinder_client.rb +45 -0
- data/lib/bird_grinder/base.rb +134 -0
- data/lib/bird_grinder/cacheable.rb +66 -0
- data/lib/bird_grinder/client.rb +136 -0
- data/lib/bird_grinder/command_handler.rb +84 -0
- data/lib/bird_grinder/console.rb +42 -0
- data/lib/bird_grinder/exceptions.rb +6 -0
- data/lib/bird_grinder/loader.rb +5 -0
- data/lib/bird_grinder/queue_processor.rb +86 -0
- data/lib/bird_grinder/tweeter/search.rb +71 -0
- data/lib/bird_grinder/tweeter/stream_processor.rb +46 -0
- data/lib/bird_grinder/tweeter/streaming.rb +74 -0
- data/lib/bird_grinder/tweeter.rb +234 -0
- data/lib/bird_grinder.rb +29 -0
- data/lib/birdgrinder.rb +1 -0
- data/lib/moneta/basic_file.rb +113 -0
- data/lib/moneta/redis.rb +49 -0
- data/templates/boot.erb +3 -0
- data/templates/debug_handler.erb +13 -0
- data/templates/hello_world_handler.erb +10 -0
- data/templates/rakefile.erb +15 -0
- data/templates/settings.yml.erb +4 -0
- data/templates/setup.erb +40 -0
- data/templates/test_helper.erb +17 -0
- data/test/bird_grinder_test.rb +9 -0
- data/test/test_helper.rb +35 -0
- metadata +140 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'readline'
|
2
|
+
require 'irb'
|
3
|
+
require 'irb/completion'
|
4
|
+
|
5
|
+
module BirdGrinder
|
6
|
+
# A simple controller for bringing up an IRB instance with the birdgrinder
|
7
|
+
# environment pre-loaded.
|
8
|
+
class Console
|
9
|
+
|
10
|
+
# Define code here that you want available at the IRB
|
11
|
+
# prompt automatically.
|
12
|
+
module BaseExtensions
|
13
|
+
include BirdGrinder::Loggable
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
setup_irb
|
18
|
+
end
|
19
|
+
|
20
|
+
# Include the base extensions in our top level binding
|
21
|
+
# so they can be accessed at the prompt.
|
22
|
+
def setup_irb
|
23
|
+
# This is a bit hacky, surely there is a better way?
|
24
|
+
# e.g. some way to specify which scope irb runs in.
|
25
|
+
eval("include BirdGrinder::Console::BaseExtensions", TOPLEVEL_BINDING)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Actually starts IRB
|
29
|
+
def run
|
30
|
+
puts "Loading BirdGrinder Console..."
|
31
|
+
# Trick IRB into thinking it has no arguments.
|
32
|
+
ARGV.replace []
|
33
|
+
IRB.start
|
34
|
+
end
|
35
|
+
|
36
|
+
# Starts up a new IRB instance with access to birdgrinder features.
|
37
|
+
def self.run
|
38
|
+
self.new.run
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'em-redis'
|
2
|
+
|
3
|
+
module BirdGrinder
|
4
|
+
# When running, the queue processor makes it possible to
|
5
|
+
# use a redis queue queue up and dispatch tweets and direct
|
6
|
+
# messages from external processes. This is useful since
|
7
|
+
# it makes it easy to have 1 outgoing source of tweets,
|
8
|
+
# triggered from any external application. Included in
|
9
|
+
# examples/bird_grinder_client.rb is a simple example
|
10
|
+
# client which uses redis to queue tweets and dms.
|
11
|
+
class QueueProcessor
|
12
|
+
|
13
|
+
cattr_accessor :polling_delay, :namespace, :action_whitelist
|
14
|
+
# 10 seconds if queue is empty.
|
15
|
+
self.polling_delay = 10
|
16
|
+
self.namespace = 'bg:messages'
|
17
|
+
self.action_whitelist = ["tweet", "dm"]
|
18
|
+
|
19
|
+
is :loggable
|
20
|
+
|
21
|
+
attr_accessor :tweeter
|
22
|
+
|
23
|
+
# Initializes redis and our tweeter.
|
24
|
+
def initialize
|
25
|
+
@tweeter = Tweeter.new(self)
|
26
|
+
@redis = EM::P::Redis.connect
|
27
|
+
end
|
28
|
+
|
29
|
+
# Attempts to pop and process an item from the front of the queue.
|
30
|
+
# Also, it will queue up the next check - if current item was empty,
|
31
|
+
# it will happen after a specified delay otherwise it will check now.
|
32
|
+
def check_queue
|
33
|
+
logger.debug "Checking Redis for outgoing messages"
|
34
|
+
@redis.lpop(@@namespace) do |res|
|
35
|
+
if res.blank?
|
36
|
+
logger.debug "Empty queue, scheduling check in #{@@polling_delay} seconds"
|
37
|
+
schedule_check(@@polling_delay)
|
38
|
+
else
|
39
|
+
logger.debug "Got item, processing and scheduling next check"
|
40
|
+
begin
|
41
|
+
handle_action Yajl::Parser.parse(res)
|
42
|
+
rescue Yajl::ParseError => e
|
43
|
+
logger.error "Couldn't parse json: #{e.message}"
|
44
|
+
end
|
45
|
+
schedule_check
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Check the queue.
|
51
|
+
#
|
52
|
+
# @param [Integer, nil] time the specified delay. If nil, it will be done now.
|
53
|
+
def schedule_check(time = nil)
|
54
|
+
if time == nil
|
55
|
+
check_queue
|
56
|
+
else
|
57
|
+
EventMachine.add_timer(@@polling_delay) { check_queue }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Processes a given action action - calling handle action
|
62
|
+
# if present.
|
63
|
+
def process_action(res)
|
64
|
+
if res.is_a?(Hash) && res["action"].present?
|
65
|
+
handle_action(res["action"], res["arguments"])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Calls the correct method on the tweeter if present
|
70
|
+
# and in the whitelist. logs and caught argument errors.
|
71
|
+
def handle_action(action, args)
|
72
|
+
args ||= []
|
73
|
+
@tweeter.send(action, *[*args]) if @@action_whitelist.include?(action)
|
74
|
+
rescue ArgumentError
|
75
|
+
logger.warn "Incorrect call for #{action} with arguuments #{args}"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Starts the queue processor with an initial check.
|
79
|
+
# raises an exception if the reactor isn't running.
|
80
|
+
def self.start
|
81
|
+
raise "EventMachine must be running" unless EM.reactor_running?
|
82
|
+
new.check_queue
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module BirdGrinder
|
2
|
+
class Tweeter
|
3
|
+
class Search
|
4
|
+
is :loggable
|
5
|
+
|
6
|
+
# 30 seconds between searches
|
7
|
+
DELAY_SEARCH = 30
|
8
|
+
|
9
|
+
cattr_accessor :search_base_url
|
10
|
+
@@search_base_url = "http://search.twitter.com/"
|
11
|
+
|
12
|
+
def initialize(parent)
|
13
|
+
logger.debug "Initializing Search"
|
14
|
+
@parent = parent
|
15
|
+
end
|
16
|
+
|
17
|
+
# Uses the twitter search api to look up a
|
18
|
+
# given query. If :repeat is given, it will
|
19
|
+
# repeat indefinitely, getting only new messages each
|
20
|
+
# iteration.
|
21
|
+
#
|
22
|
+
# @param [String] query what you wish to search for
|
23
|
+
# @param [Hash] opts options for the query string (except for :repeat)
|
24
|
+
# @option opts [Boolean] :repeat if present, will repeat indefinitely.
|
25
|
+
def search_for(query, opts = {})
|
26
|
+
logger.info "Searching for #{query.inspect}"
|
27
|
+
opts = opts.dup
|
28
|
+
repeat = opts.delete(:repeat)
|
29
|
+
perform_search(query, opts) do |response|
|
30
|
+
if repeat && response.max_id?
|
31
|
+
logger.info "Scheduling next search iteration for #{query.inspect}"
|
32
|
+
EM.add_timer(DELAY_SEARCH) do
|
33
|
+
opts[:repeat] = true
|
34
|
+
opts[:since_id] = response.max_id
|
35
|
+
search_for(query, opts)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def perform_search(query, opts = {}, &blk)
|
44
|
+
url = search_base_url / "search.json"
|
45
|
+
query_opts = opts.stringify_keys
|
46
|
+
query_opts["q"] = query.to_s.strip
|
47
|
+
query_opts["rpp"] ||= 100
|
48
|
+
http = EventMachine::HttpRequest.new(url).get(:query => query_opts)
|
49
|
+
http.callback do
|
50
|
+
response = parse_response(http)
|
51
|
+
blk.call(response) if blk.present?
|
52
|
+
if response.results?
|
53
|
+
response.results.each do |result|
|
54
|
+
result.type = :search
|
55
|
+
@parent.delegate.receive_message(:incoming_search, result)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse_response(http)
|
62
|
+
response = Yajl::Parser.parse(http.response)
|
63
|
+
response.to_nash.normalized
|
64
|
+
rescue Yajl::ParseError => e
|
65
|
+
logger.error "Couldn't parse search response, error:\n#{e.message}"
|
66
|
+
BirdGrinder::Nash.new
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module BirdGrinder
|
2
|
+
class Tweeter
|
3
|
+
class StreamProcessor
|
4
|
+
is :loggable
|
5
|
+
|
6
|
+
def initialize(parent, stream_name)
|
7
|
+
@parent = parent
|
8
|
+
@stream_name = stream_name.to_sym
|
9
|
+
setup_parser
|
10
|
+
end
|
11
|
+
|
12
|
+
def receive_chunk(chunk)
|
13
|
+
@parser << chunk
|
14
|
+
rescue Yajl::ParseError => e
|
15
|
+
logger.error "Couldn't parse json: #{e.message}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def process_stream_item(json)
|
19
|
+
return if !json.is_a?(Hash)
|
20
|
+
processed = json.to_nash.normalized
|
21
|
+
processed.type = lookup_type_for_steam_response(processed)
|
22
|
+
processed.streaming_source = @stream_name
|
23
|
+
logger.info "Processing Stream Tweet #{processed.id}: #{processed.text}"
|
24
|
+
@parent.delegate.receive_message(:incoming_stream, processed)
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def lookup_type_for_steam_response(response)
|
30
|
+
if response.delete?
|
31
|
+
:delete
|
32
|
+
elsif response.limit?
|
33
|
+
:limit
|
34
|
+
else
|
35
|
+
:tweet
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def setup_parser
|
40
|
+
@parser = Yajl::Parser.new
|
41
|
+
@parser.on_parse_complete = method(:process_stream_item)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'bird_grinder/tweeter/stream_processor'
|
2
|
+
|
3
|
+
module BirdGrinder
|
4
|
+
class Tweeter
|
5
|
+
# Basic support for the twitter streaming api. Provides
|
6
|
+
# access to sample, filter, follow and track. Note that
|
7
|
+
# it will dispatch messages as :incoming_stream, with
|
8
|
+
# options.streaming_source set to the stream origin.
|
9
|
+
class Streaming
|
10
|
+
is :loggable
|
11
|
+
|
12
|
+
cattr_accessor :streaming_base_url, :api_version
|
13
|
+
self.streaming_base_url = "http://stream.twitter.com/"
|
14
|
+
self.api_version = 1
|
15
|
+
|
16
|
+
attr_accessor :parent
|
17
|
+
|
18
|
+
def initialize(parent)
|
19
|
+
@parent = parent
|
20
|
+
logger.debug "Initializing Streaming Support"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Start processing the sample stream
|
24
|
+
#
|
25
|
+
# @param [Hash] opts extra options for the query
|
26
|
+
def sample(opts = {})
|
27
|
+
get(:sample, opts)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Start processing the filter stream
|
31
|
+
#
|
32
|
+
# @param [Hash] opts extra options for the query
|
33
|
+
def filter(opts = {})
|
34
|
+
get(:filter, opts)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Start processing the filter stream with a given follow
|
38
|
+
# argument.
|
39
|
+
#
|
40
|
+
# @param [Array] args what to follow, joined with ","
|
41
|
+
def follow(*args)
|
42
|
+
opts = args.extract_options!
|
43
|
+
opts[:follow] = args.join(",")
|
44
|
+
opts[:path] = :filter
|
45
|
+
get(:follow, opts)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Starts tracking a specific query.
|
49
|
+
#
|
50
|
+
# @param [Hash] opts extra options for the query
|
51
|
+
def track(query, opts = {})
|
52
|
+
opts[:track] = query
|
53
|
+
opts[:path] = :filter
|
54
|
+
get(:track, opts)
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
def get(name, opts = {}, attempts = 0)
|
60
|
+
logger.debug "Getting stream #{name} w/ options: #{opts.inspect}"
|
61
|
+
path = opts.delete(:path)
|
62
|
+
processor = StreamProcessor.new(@parent, name)
|
63
|
+
http_opts = {
|
64
|
+
:on_response => processor.method(:receive_chunk),
|
65
|
+
:head => {'Authorization' => @parent.auth_credentials}
|
66
|
+
}
|
67
|
+
http_opts[:query] = opts if opts.present?
|
68
|
+
url = streaming_base_url / api_version.to_s / "statuses" / "#{path || name}.json"
|
69
|
+
http = EventMachine::HttpRequest.new(url).get(http_opts)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module BirdGrinder
|
4
|
+
# An asynchronous, delegate-based twitter client that uses
|
5
|
+
# em-http-request and yajl on the backend. It's built to be fast,
|
6
|
+
# minimal and easy to use.
|
7
|
+
#
|
8
|
+
# The delegate is simply any class - the tweeter will attempt to
|
9
|
+
# call receive_message([Symbol], [BirdGrinder::Nash]) every time
|
10
|
+
# it processes a message / item of some kind. This in turn makes
|
11
|
+
# it easy to process items. Also, it will dispatch both
|
12
|
+
# incoming (e.g. :incoming_mention, :incoming_direct_message) and
|
13
|
+
# outgoing (e.g. :outgoing_tweet) events.
|
14
|
+
#
|
15
|
+
# It has support the twitter search api (via #search) and the currently-
|
16
|
+
# alpha twitter streaming api (using #streaming) built right in.
|
17
|
+
class Tweeter
|
18
|
+
is :loggable, :delegateable
|
19
|
+
|
20
|
+
require 'bird_grinder/tweeter/streaming'
|
21
|
+
require 'bird_grinder/tweeter/search'
|
22
|
+
|
23
|
+
VALID_FETCHES = [:direct_messages, :mentions]
|
24
|
+
|
25
|
+
cattr_accessor :api_base_url
|
26
|
+
self.api_base_url = "http://twitter.com/"
|
27
|
+
|
28
|
+
attr_reader :auth_credentials
|
29
|
+
|
30
|
+
# Initializes the tweeter with a given delegate. It will use
|
31
|
+
# username and password from your settings file for authorization
|
32
|
+
# with twitter.
|
33
|
+
#
|
34
|
+
# @param [Delegate] delegate the delegate class
|
35
|
+
def initialize(delegate)
|
36
|
+
check_auth!
|
37
|
+
@auth_credentials = [BirdGrinder::Settings.username, BirdGrinder::Settings.password]
|
38
|
+
delegate_to delegate
|
39
|
+
end
|
40
|
+
|
41
|
+
# Automates fetching mentions / direct messages at the same time.
|
42
|
+
#
|
43
|
+
# @param [Array<Symbol>] fetches what to load - :all for all fetches, or names of the fetches otherwise
|
44
|
+
def fetch(*fetches)
|
45
|
+
options = fetches.extract_options!
|
46
|
+
fetches = VALID_FETCHES if fetches == [:all]
|
47
|
+
(fetches & VALID_FETCHES).each do |fetch_type|
|
48
|
+
send(fetch_type, options.dup)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Tells the twitter api to follow a specific user
|
53
|
+
#
|
54
|
+
# @param [String] user the screen_name of the user to follow
|
55
|
+
# @param [Hash] opts extra options to pass in the query string
|
56
|
+
def follow(user, opts = {})
|
57
|
+
user = user.to_s.strip
|
58
|
+
logger.info "Following '#{user}'"
|
59
|
+
post("friendships/create.json", opts.merge(:screen_name => user)) do
|
60
|
+
delegate.receive_message(:outgoing_follow, :user => user)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Tells the twitter api to unfollow a specific user
|
65
|
+
#
|
66
|
+
# @param [String] user the screen_name of the user to unfollow
|
67
|
+
# @param [Hash] opts extra options to pass in the query string
|
68
|
+
def unfollow(user, opts = {})
|
69
|
+
user = user.to_s.strip
|
70
|
+
logger.info "Unfollowing '#{user}'"
|
71
|
+
post("friendships/destroy.json", opts.merge(:screen_name => user)) do
|
72
|
+
delegate.receive_message(:outgoing_unfollow, :user => user)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Updates your current status on twitter with a specific message
|
77
|
+
#
|
78
|
+
# @param [String] message the contents of your tweet
|
79
|
+
# @param [Hash] opts extra options to pass in the query string
|
80
|
+
def tweet(message, opts = {})
|
81
|
+
message = message.to_s.strip
|
82
|
+
logger.debug "Tweeting #{message}"
|
83
|
+
post("statuses/update.json", opts.merge(:status => message)) do |json|
|
84
|
+
delegate.receive_message(:outgoing_tweet, status_to_args(json))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Sends a direct message to a given user
|
89
|
+
#
|
90
|
+
# @param [String] user the screen_name of the user you wish to dm
|
91
|
+
# @param [String] text the text to send to the user
|
92
|
+
# @param [Hash] opts extra options to pass in the query string
|
93
|
+
def dm(user, text, opts = {})
|
94
|
+
text = text.to_s.strip
|
95
|
+
user = user.to_s.strip
|
96
|
+
logger.debug "DM'ing #{user}: #{text}"
|
97
|
+
post("direct_messages/new.json", opts.merge(:user => user, :text => text)) do
|
98
|
+
delegate.receive_message(:outgoing_direct_message, :user => user, :text => text)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns an instance of BirdGrinder::Tweeter::Streaming,
|
103
|
+
# used for accessing the alpha streaming api for twitter.
|
104
|
+
#
|
105
|
+
# @see BirdGrinder::Tweeter::Streaming
|
106
|
+
def streaming
|
107
|
+
@streaming ||= Streaming.new(self)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Uses the twitter search api to look up a given
|
111
|
+
# query, with a set of possible options.
|
112
|
+
#
|
113
|
+
# @param [String] query the query you wish to search for
|
114
|
+
# @param [Hash] opts the opts to query, all except :repeat are sent to twitter.
|
115
|
+
# @option opts [Boolean] :repeat repeat the query indefinitely, fetching new messages each time
|
116
|
+
def search(query, opts = {})
|
117
|
+
@search ||= Search.new(self)
|
118
|
+
@search.search_for(query, opts)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Sends a correctly-formatted at reply to a given user.
|
122
|
+
# If the users screen_name isn't at the start of the tweet,
|
123
|
+
# it will be appended accordingly.
|
124
|
+
#
|
125
|
+
# @param [String] user the user to reply to's screen name
|
126
|
+
# @param [String] test the text to reply with
|
127
|
+
# @param [Hash] opts the options to pass in the query string
|
128
|
+
def reply(user, text, opts = {})
|
129
|
+
user = user.to_s.strip
|
130
|
+
text = text.to_s.strip
|
131
|
+
text = "@#{user} #{text}".strip unless text =~ /^\@#{user}\b/i
|
132
|
+
tweet(text, opts)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Asynchronously fetches the current users (as specified by your settings)
|
136
|
+
# direct messages from the twitter api
|
137
|
+
#
|
138
|
+
# @param [Hash] opts options to pass in the query string
|
139
|
+
def direct_messages(opts = {})
|
140
|
+
logger.debug "Fetching direct messages..."
|
141
|
+
get("direct_messages.json", opts) do |dms|
|
142
|
+
logger.debug "Fetched a total of #{dms.size} direct message(s)"
|
143
|
+
dms.each do |dm|
|
144
|
+
delegate.receive_message(:incoming_direct_message, status_to_args(dm, :direct_message))
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Asynchronously fetches the current users (as specified by your settings)
|
150
|
+
# mentions from the twitter api
|
151
|
+
#
|
152
|
+
# @param [Hash] opts options to pass in the query string
|
153
|
+
def mentions(opts = {})
|
154
|
+
logger.debug "Fetching mentions..."
|
155
|
+
get("statuses/mentions.json", opts) do |mentions|
|
156
|
+
logger.debug "Fetched a total of #{mentions.size} mention(s)"
|
157
|
+
mentions.each do |status|
|
158
|
+
delegate.receive_message(:incoming_mention, status_to_args(status, :mention))
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
protected
|
164
|
+
|
165
|
+
def request(path = "/")
|
166
|
+
EventMachine::HttpRequest.new(api_base_url / path)
|
167
|
+
end
|
168
|
+
|
169
|
+
def get(path, params = {}, &blk)
|
170
|
+
http = request(path).get({
|
171
|
+
:head => {'Authorization' => @auth_credentials},
|
172
|
+
:query => params.stringify_keys
|
173
|
+
})
|
174
|
+
add_response_callback(http, blk)
|
175
|
+
http
|
176
|
+
end
|
177
|
+
|
178
|
+
def post(path, params = {}, &blk)
|
179
|
+
real_params = {}
|
180
|
+
params.each_pair { |k,v| real_params[URI.encode(k.to_s)] = URI.encode(v) }
|
181
|
+
http = request(path).post({
|
182
|
+
:head => {
|
183
|
+
'Authorization' => @auth_credentials,
|
184
|
+
'Content-Type' => 'application/x-www-form-urlencoded'
|
185
|
+
},
|
186
|
+
:body => real_params
|
187
|
+
})
|
188
|
+
add_response_callback(http, blk)
|
189
|
+
http
|
190
|
+
end
|
191
|
+
|
192
|
+
def add_response_callback(http, blk)
|
193
|
+
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?
|
199
|
+
else
|
200
|
+
logger.eror "Error: #{res.error} (on #{res.request})"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def parse_response(http)
|
206
|
+
response = Yajl::Parser.parse(http.response)
|
207
|
+
if response.respond_to?(:to_ary)
|
208
|
+
response.map { |i| i.to_nash }
|
209
|
+
else
|
210
|
+
response.to_nash
|
211
|
+
end
|
212
|
+
rescue Yajl::ParseError => e
|
213
|
+
logger.error "Invalid Response: #{http.response} (#{e.message})"
|
214
|
+
nil
|
215
|
+
end
|
216
|
+
|
217
|
+
def successful?(response)
|
218
|
+
response.respond_to?(:to_nash) ? !response.to_nash.error? : true
|
219
|
+
end
|
220
|
+
|
221
|
+
def status_to_args(status_items, type = :tweet)
|
222
|
+
results = status_items.to_nash.normalized
|
223
|
+
results.type = type
|
224
|
+
results
|
225
|
+
end
|
226
|
+
|
227
|
+
def check_auth!
|
228
|
+
if BirdGrinder::Settings["username"].blank? || BirdGrinder::Settings["username"].blank?
|
229
|
+
raise BirdGrinder::MissingAuthDetails, "Missing twitter username or password."
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
end
|