weeter 0.15.0 → 0.17.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.
- checksums.yaml +7 -0
- data/.ruby-version +1 -0
- data/.travis.yml +1 -1
- data/lib/weeter/configuration/client_app_config.rb +5 -2
- data/lib/weeter/configuration/limiter_config.rb +1 -1
- data/lib/weeter/plugins/lib/oauth_http.rb +5 -5
- data/lib/weeter/plugins/notification/resque.rb +9 -6
- data/lib/weeter/plugins/subscription/http.rb +4 -3
- data/lib/weeter/plugins/subscription/redis.rb +3 -3
- data/lib/weeter/twitter/tweet_consumer.rb +51 -15
- data/lib/weeter/version.rb +1 -1
- data/spec/spec_helper.rb +2 -0
- data/spec/weeter/configuration/client_app_config_spec.rb +2 -6
- data/spec/weeter/configuration/twitter_config_spec.rb +15 -15
- data/spec/weeter/configuration_spec.rb +5 -5
- data/spec/weeter/limitator_spec.rb +27 -27
- data/spec/weeter/plugins/notification_plugin_spec.rb +6 -6
- data/spec/weeter/plugins/subscription/update_server_spec.rb +11 -11
- data/spec/weeter/plugins/subscription_plugin_spec.rb +7 -7
- data/spec/weeter/twitter/tweet_consumer_spec.rb +60 -52
- data/spec/weeter/twitter/tweet_item_spec.rb +27 -27
- data/weeter.gemspec +12 -11
- metadata +80 -91
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 13c5cd768b0f83d96e1a67b87a505e3e99ae4b18
|
4
|
+
data.tar.gz: 47f1550576846acc61ad07c1323702e4b1ad60cd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cf47012d1be61abcfc9a8800d8b46229f6c62a967515dfa1477891e86243cf31329c1e05e554612441c25320d2e18070d4e4d39536ca8e17c85a5db7e7324761
|
7
|
+
data.tar.gz: 959ae16e71443ff6b561240f2b8432ec3f291b472a272f7373fffc2f663dd15e635671972efedf1899dcfb4e03dbf8a7cb7fa32c96001351cbd43c530c1a7ba8
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.2.6
|
data/.travis.yml
CHANGED
@@ -4,8 +4,11 @@ require 'hashie'
|
|
4
4
|
module Weeter
|
5
5
|
class Configuration
|
6
6
|
class ClientAppConfig < Hashie::Mash
|
7
|
-
|
8
|
-
|
7
|
+
DEFAULT_SUBSCRIPTIONS_UPDATE_PORT = 7337
|
8
|
+
InvalidConfiguration = Class.new(StandardError)
|
9
|
+
|
10
|
+
def verify_redis_namespace_config
|
11
|
+
!!self.redis_namespace || raise(InvalidConfiguration, 'missing `redis-namespace` config')
|
9
12
|
end
|
10
13
|
end
|
11
14
|
end
|
@@ -8,7 +8,7 @@ module Weeter
|
|
8
8
|
def self.get(config, url, params = {})
|
9
9
|
request(config, :get, url, params)
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
def self.put(config, url, params = {})
|
13
13
|
request(config, :put, url, params)
|
14
14
|
end
|
@@ -16,11 +16,11 @@ module Weeter
|
|
16
16
|
def self.post(config, url, params = {})
|
17
17
|
request(config, :post, url, params)
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
def self.delete(config, url, params = {})
|
21
21
|
request(config, :delete, url, params)
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
def self.request(config, method, url, params = {})
|
25
25
|
if method == :post
|
26
26
|
request_options = {:body => params}
|
@@ -32,9 +32,9 @@ module Weeter
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def self.oauth_header(config, uri, params, http_method)
|
35
|
-
::
|
35
|
+
::SimpleOAuth::Header.new(http_method, uri, params, config.oauth).to_s
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
40
|
-
end
|
40
|
+
end
|
@@ -9,19 +9,19 @@ module Weeter
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def publish_tweet(tweet_item)
|
12
|
-
resque_job = %Q|{"class":"WeeterPublishTweetJob","args":[#{tweet_item.to_json}]}|
|
12
|
+
resque_job = %Q|{"class":"WeeterPublishTweetJob","args":[#{tweet_item.to_json}],"jid": "#{SecureRandom.hex(12)}"}|
|
13
13
|
Weeter.logger.info("Publishing tweet #{tweet_item['id']} from user #{tweet_item['user']['id_str']}: #{tweet_item['text']}")
|
14
14
|
enqueue(resque_job)
|
15
15
|
end
|
16
16
|
|
17
17
|
def delete_tweet(tweet_item)
|
18
|
-
resque_job = %Q|{"class":"WeeterDeleteTweetJob","args":[#{tweet_item.to_json}]}|
|
18
|
+
resque_job = %Q|{"class":"WeeterDeleteTweetJob","args":[#{tweet_item.to_json}],"jid": "#{SecureRandom.hex(12)}"}|
|
19
19
|
Weeter.logger.info("Deleting tweet #{tweet_item['id']} for user #{tweet_item['user']['id_str']}")
|
20
20
|
enqueue(resque_job)
|
21
21
|
end
|
22
22
|
|
23
23
|
def notify_missed_tweets(tweet_item)
|
24
|
-
resque_job = %Q|{"class":"WeeterMissedTweetsJob","args":[#{tweet_item.to_json}]}|
|
24
|
+
resque_job = %Q|{"class":"WeeterMissedTweetsJob","args":[#{tweet_item.to_json}],"jid": "#{SecureRandom.hex(12)}"}|
|
25
25
|
Weeter.logger.info("Notifying of missed tweets (#{tweet_item.missed_tweets_count}).")
|
26
26
|
enqueue(resque_job)
|
27
27
|
end
|
@@ -29,7 +29,7 @@ module Weeter
|
|
29
29
|
def notify_rate_limiting_initiated(tweet_item, limited_keys)
|
30
30
|
payload = tweet_item.to_hash.merge(:limited_keys => limited_keys)
|
31
31
|
payload_json = MultiJson.encode(payload)
|
32
|
-
resque_job = %Q|{"class":"WeeterRateLimitingInitiatedJob","args":[#{payload_json}]}|
|
32
|
+
resque_job = %Q|{"class":"WeeterRateLimitingInitiatedJob","args":[#{payload_json}],"jid": "#{SecureRandom.hex(12)}"}|
|
33
33
|
Weeter.logger.info("Initiated rate limiting with tweet: #{payload_json}")
|
34
34
|
enqueue(resque_job)
|
35
35
|
end
|
@@ -37,7 +37,10 @@ module Weeter
|
|
37
37
|
protected
|
38
38
|
|
39
39
|
def redis
|
40
|
-
@redis ||=
|
40
|
+
@redis ||= begin
|
41
|
+
@config.verify_redis_namespace_config
|
42
|
+
create_redis_client
|
43
|
+
end
|
41
44
|
end
|
42
45
|
|
43
46
|
def enqueue(job)
|
@@ -45,7 +48,7 @@ module Weeter
|
|
45
48
|
end
|
46
49
|
|
47
50
|
def queue_key
|
48
|
-
"
|
51
|
+
"#{@config.redis_namespace}:#{@config.queue}"
|
49
52
|
end
|
50
53
|
end
|
51
54
|
end
|
@@ -23,11 +23,12 @@ module Weeter
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def listen_for_filter_update(tweet_consumer)
|
26
|
-
|
26
|
+
port = @config.subscription_updates_port || Weeter::Configuration::ClientAppConfig::DEFAULT_SUBSCRIPTIONS_UPDATE_PORT
|
27
|
+
EM.start_server('localhost', port, UpdateServer) do |conn|
|
27
28
|
conn.tweet_consumer = tweet_consumer
|
28
29
|
end
|
29
30
|
end
|
30
|
-
|
31
|
+
|
31
32
|
class UpdateServer < EM::Connection
|
32
33
|
include EM::HttpServer
|
33
34
|
attr_accessor :tweet_consumer
|
@@ -42,4 +43,4 @@ module Weeter
|
|
42
43
|
end
|
43
44
|
end
|
44
45
|
end
|
45
|
-
end
|
46
|
+
end
|
@@ -23,8 +23,8 @@ module Weeter
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def listen_for_filter_update(tweet_consumer)
|
26
|
-
|
27
|
-
pub_sub_redis.
|
26
|
+
channel = @config.subscriptions_changed_channel
|
27
|
+
pub_sub_redis.subscribe(channel) do |message|
|
28
28
|
Weeter.logger.info [:message, channel, message]
|
29
29
|
Weeter.logger.info("Retrieving updated filters from redis")
|
30
30
|
get_initial_filters do |filter_params|
|
@@ -41,7 +41,7 @@ module Weeter
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def pub_sub_redis
|
44
|
-
@pub_sub_redis ||= create_redis_client
|
44
|
+
@pub_sub_redis ||= create_redis_client.pubsub
|
45
45
|
end
|
46
46
|
|
47
47
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'twitter
|
1
|
+
require 'em-twitter'
|
2
2
|
require 'multi_json'
|
3
3
|
|
4
4
|
module Weeter
|
@@ -23,18 +23,22 @@ module Weeter
|
|
23
23
|
def connect(filter_params)
|
24
24
|
filter_params = limit_filter_params(filter_params)
|
25
25
|
filter_params = clean_filter_params(filter_params)
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
params
|
31
|
-
|
32
|
-
|
26
|
+
oauth_options = @config.auth_options[:oauth]
|
27
|
+
|
28
|
+
options = {
|
29
|
+
:path => '/1.1/statuses/filter.json',
|
30
|
+
:params => filter_params,
|
31
|
+
:oauth => {
|
32
|
+
:consumer_key => oauth_options[:consumer_key],
|
33
|
+
:consumer_secret => oauth_options[:consumer_secret],
|
34
|
+
:token => oauth_options[:access_key],
|
35
|
+
:token_secret => oauth_options[:access_secret]
|
36
|
+
}
|
37
|
+
}
|
33
38
|
|
34
39
|
Weeter.logger.info("Connecting to Twitter stream...")
|
35
|
-
@
|
36
|
-
|
37
|
-
@stream.each_item do |item|
|
40
|
+
@client = EM::Twitter::Client.connect(options)
|
41
|
+
@client.each do |item|
|
38
42
|
begin
|
39
43
|
tweet_item = TweetItem.new(MultiJson.decode(item))
|
40
44
|
|
@@ -52,18 +56,50 @@ module Weeter
|
|
52
56
|
end
|
53
57
|
end
|
54
58
|
|
55
|
-
@
|
59
|
+
@client.on_unauthorized do |msg|
|
60
|
+
Weeter.logger.debug("on_unauthorized: #{msg}")
|
61
|
+
end
|
62
|
+
|
63
|
+
@client.on_forbidden do |msg|
|
64
|
+
Weeter.logger.debug("on_forbidden: #{msg}")
|
65
|
+
end
|
66
|
+
|
67
|
+
@client.on_not_found do |msg|
|
68
|
+
Weeter.logger.debug("on_not_found: #{msg}")
|
69
|
+
end
|
70
|
+
|
71
|
+
@client.on_not_acceptable do |msg|
|
72
|
+
Weeter.logger.debug("on_not_acceptable: #{msg}")
|
73
|
+
end
|
74
|
+
|
75
|
+
@client.on_too_long do |msg|
|
76
|
+
Weeter.logger.debug("on_too_long: #{msg}")
|
77
|
+
end
|
78
|
+
|
79
|
+
@client.on_range_unacceptable do |msg|
|
80
|
+
Weeter.logger.debug("on_range_unacceptable: #{msg}")
|
81
|
+
end
|
82
|
+
|
83
|
+
@client.on_enhance_your_calm do |msg| # rate-limited
|
84
|
+
Weeter.logger.debug("on_enhance_your_calm: #{msg}")
|
85
|
+
end
|
86
|
+
|
87
|
+
@client.on_error do |msg|
|
56
88
|
Weeter.logger.error("Twitter stream error: #{msg}. Connect options were #{connect_options.inspect}")
|
57
89
|
end
|
58
90
|
|
59
|
-
@
|
91
|
+
@client.on_reconnect do |msg|
|
92
|
+
Weeter.logger.debug("on_reconnect: #{msg}")
|
93
|
+
end
|
94
|
+
|
95
|
+
@client.on_max_reconnects do |timeout, retries|
|
60
96
|
Weeter.logger.error("Twitter stream max-reconnects reached: timeout=#{timeout}, retries=#{retries}")
|
61
97
|
end
|
62
98
|
end
|
63
99
|
|
64
100
|
def reconnect(filter_params)
|
65
|
-
@
|
66
|
-
@
|
101
|
+
@client.stop
|
102
|
+
@client.unbind
|
67
103
|
connect(filter_params)
|
68
104
|
end
|
69
105
|
|
data/lib/weeter/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -2,17 +2,13 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Weeter::Configuration::ClientAppConfig do
|
4
4
|
%w{delete_url subscriptions_url oauth}.each do |setting|
|
5
|
-
it "
|
5
|
+
it "accepts setting for #{setting}" do
|
6
6
|
Weeter.configure do |conf|
|
7
7
|
conf.client_app do |app|
|
8
8
|
app.send("#{setting}=", "testvalue")
|
9
9
|
end
|
10
10
|
end
|
11
|
-
Weeter::Configuration.instance.client_app.send(setting).
|
11
|
+
expect(Weeter::Configuration.instance.client_app.send(setting)).to eq("testvalue")
|
12
12
|
end
|
13
13
|
end
|
14
|
-
|
15
|
-
it "should default subscription_updates_port" do
|
16
|
-
Weeter::Configuration.instance.client_app.subscription_updates_port.should == 7337
|
17
|
-
end
|
18
14
|
end
|
@@ -2,38 +2,38 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Weeter::Configuration::TwitterConfig do
|
4
4
|
%w{basic_auth oauth}.each do |setting|
|
5
|
-
it "
|
5
|
+
it "accepts setting for #{setting}" do
|
6
6
|
Weeter.configure do |conf|
|
7
7
|
conf.twitter do |app|
|
8
8
|
app.send("#{setting}=", "testvalue")
|
9
9
|
end
|
10
10
|
end
|
11
|
-
Weeter::Configuration::TwitterConfig.instance.send(setting).
|
11
|
+
expect(Weeter::Configuration::TwitterConfig.instance.send(setting)).to eq("testvalue")
|
12
12
|
end
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
describe "auth_options" do
|
16
|
-
|
16
|
+
|
17
17
|
before do
|
18
18
|
Weeter::Configuration::TwitterConfig.instance.oauth = nil
|
19
19
|
Weeter::Configuration::TwitterConfig.instance.basic_auth = nil
|
20
20
|
end
|
21
|
-
|
22
|
-
it "
|
21
|
+
|
22
|
+
it "returns the oauth settings with a oauth credentials" do
|
23
23
|
Weeter::Configuration::TwitterConfig.instance.oauth = {:consumer_key => 'consumer_key', :consumer_secret => 'consumer_secret', :access_key => 'acces_key', :access_secret => 'access_secret'}
|
24
|
-
Weeter::Configuration::TwitterConfig.instance.auth_options.
|
24
|
+
expect(Weeter::Configuration::TwitterConfig.instance.auth_options).to eq({:oauth => {:consumer_key => 'consumer_key', :consumer_secret => 'consumer_secret', :access_key => 'acces_key', :access_secret => 'access_secret'}})
|
25
25
|
end
|
26
|
-
|
27
|
-
it "
|
26
|
+
|
27
|
+
it "returns the basic auth settings separated by a colon" do
|
28
28
|
Weeter::Configuration::TwitterConfig.instance.basic_auth = {:username => "bob", :password => "s3cr3t"}
|
29
|
-
Weeter::Configuration::TwitterConfig.instance.auth_options.
|
29
|
+
expect(Weeter::Configuration::TwitterConfig.instance.auth_options).to eq({:auth => "bob:s3cr3t"})
|
30
30
|
end
|
31
|
-
|
32
|
-
it "
|
31
|
+
|
32
|
+
it "prefers oauth over basic auth" do
|
33
33
|
Weeter::Configuration::TwitterConfig.instance.basic_auth = {:username => "bob", :password => "s3cr3t"}
|
34
34
|
Weeter::Configuration::TwitterConfig.instance.oauth = {:consumer_key => 'consumer_key', :consumer_secret => 'consumer_secret', :access_key => 'acces_key', :access_secret => 'access_secret'}
|
35
|
-
Weeter::Configuration::TwitterConfig.instance.auth_options.
|
35
|
+
expect(Weeter::Configuration::TwitterConfig.instance.auth_options).to eq({:oauth => {:consumer_key => 'consumer_key', :consumer_secret => 'consumer_secret', :access_key => 'acces_key', :access_secret => 'access_secret'}})
|
36
36
|
end
|
37
37
|
end
|
38
|
-
|
39
|
-
end
|
38
|
+
|
39
|
+
end
|
@@ -2,14 +2,14 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Weeter::Configuration do
|
4
4
|
describe "#twitter" do
|
5
|
-
it "
|
6
|
-
Weeter::Configuration.instance.twitter.
|
5
|
+
it "returns the instance" do
|
6
|
+
expect(Weeter::Configuration.instance.twitter).to eq(Weeter::Configuration::TwitterConfig.instance)
|
7
7
|
end
|
8
8
|
|
9
|
-
it "
|
9
|
+
it "yields the instance when a block is provided" do
|
10
10
|
Weeter::Configuration.instance.twitter do |twitter_config|
|
11
|
-
twitter_config.
|
11
|
+
expect(twitter_config).to eq(Weeter::Configuration::TwitterConfig.instance)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
15
|
-
end
|
15
|
+
end
|
@@ -14,7 +14,7 @@ describe Weeter::Limitator do
|
|
14
14
|
let(:keys) { ['key'] }
|
15
15
|
|
16
16
|
describe '.new' do
|
17
|
-
it { limitator.
|
17
|
+
it { expect(limitator).to be }
|
18
18
|
end
|
19
19
|
|
20
20
|
describe '#limit_status' do
|
@@ -25,38 +25,38 @@ describe Weeter::Limitator do
|
|
25
25
|
|
26
26
|
context 'max: 0' do
|
27
27
|
let(:max) { 0 }
|
28
|
-
|
29
|
-
|
28
|
+
it { expect(subject.status).to eq(Weeter::Limitator::INITIATE_LIMITING) }
|
29
|
+
it { expect(subject.limited_keys).to eq(keys) }
|
30
30
|
|
31
31
|
context 'no keys' do
|
32
32
|
let(:keys) { [] }
|
33
|
-
|
34
|
-
|
33
|
+
it { expect(subject.status).to eq(Weeter::Limitator::DO_NOT_LIMIT) }
|
34
|
+
it { expect(subject.limited_keys).to be_nil }
|
35
35
|
end
|
36
36
|
|
37
37
|
context 'two keys' do
|
38
38
|
let(:keys) { ['key', 'key2'] }
|
39
|
-
|
40
|
-
|
39
|
+
it { expect(subject.status).to eq(Weeter::Limitator::INITIATE_LIMITING) }
|
40
|
+
it { expect(subject.limited_keys).to eq(keys) }
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
44
|
context 'max: 1' do
|
45
45
|
let(:max) { 1 }
|
46
46
|
|
47
|
-
|
48
|
-
|
47
|
+
it { expect(subject.status).to eq(Weeter::Limitator::DO_NOT_LIMIT) }
|
48
|
+
it { expect(subject.limited_keys).to be_nil }
|
49
49
|
|
50
50
|
context 'two keys within max' do
|
51
51
|
let(:keys) { ['key', 'key2'] }
|
52
52
|
|
53
|
-
|
53
|
+
it { expect(subject.status).to eq(Weeter::Limitator::DO_NOT_LIMIT) }
|
54
54
|
end
|
55
55
|
|
56
56
|
context 'no keys' do
|
57
57
|
let(:keys) { [] }
|
58
|
-
|
59
|
-
|
58
|
+
it { expect(subject.status).to eq(Weeter::Limitator::DO_NOT_LIMIT) }
|
59
|
+
it { expect(subject.limited_keys).to be_nil }
|
60
60
|
end
|
61
61
|
|
62
62
|
context 'one key just outside max' do
|
@@ -66,8 +66,8 @@ describe Weeter::Limitator do
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
-
|
70
|
-
|
69
|
+
it { expect(subject.status).to eq(Weeter::Limitator::INITIATE_LIMITING) }
|
70
|
+
it { expect(subject.limited_keys).to eq(keys) }
|
71
71
|
|
72
72
|
context 'outside duration' do
|
73
73
|
let(:some_time_after_duration) do
|
@@ -75,11 +75,11 @@ describe Weeter::Limitator do
|
|
75
75
|
end
|
76
76
|
|
77
77
|
before do
|
78
|
-
limitator.
|
78
|
+
expect(limitator).to receive(:now).and_return(some_time_after_duration).at_least(:once)
|
79
79
|
end
|
80
80
|
|
81
|
-
|
82
|
-
|
81
|
+
it { expect(subject.status).to eq(Weeter::Limitator::DO_NOT_LIMIT) }
|
82
|
+
it { expect(subject.limited_keys).to be_nil }
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
@@ -90,8 +90,8 @@ describe Weeter::Limitator do
|
|
90
90
|
limitator.process(*keys)
|
91
91
|
end
|
92
92
|
|
93
|
-
|
94
|
-
|
93
|
+
it { expect(subject.status).to eq(Weeter::Limitator::INITIATE_LIMITING) }
|
94
|
+
it { expect(subject.limited_keys).to eq(keys) }
|
95
95
|
end
|
96
96
|
|
97
97
|
context 'two keys past max' do
|
@@ -102,8 +102,8 @@ describe Weeter::Limitator do
|
|
102
102
|
limitator.process(*keys)
|
103
103
|
end
|
104
104
|
|
105
|
-
|
106
|
-
|
105
|
+
it { expect(subject.status).to eq(Weeter::Limitator::CONTINUE_LIMITING) }
|
106
|
+
it { expect(subject.limited_keys).to eq(keys) }
|
107
107
|
end
|
108
108
|
|
109
109
|
context 'one key just past max: 1, one key within max: 1' do
|
@@ -114,8 +114,8 @@ describe Weeter::Limitator do
|
|
114
114
|
limitator.process(keys.first)
|
115
115
|
end
|
116
116
|
|
117
|
-
|
118
|
-
|
117
|
+
it { expect(subject.status).to eq(Weeter::Limitator::INITIATE_LIMITING) }
|
118
|
+
it { expect(subject.limited_keys).to eq([keys.first]) }
|
119
119
|
end
|
120
120
|
|
121
121
|
context 'one key past max: 1, one key within max: 1' do
|
@@ -127,8 +127,8 @@ describe Weeter::Limitator do
|
|
127
127
|
limitator.process(keys.first)
|
128
128
|
end
|
129
129
|
|
130
|
-
|
131
|
-
|
130
|
+
it { expect(subject.status).to eq(Weeter::Limitator::CONTINUE_LIMITING) }
|
131
|
+
it { expect(subject.limited_keys).to eq([keys.first]) }
|
132
132
|
end
|
133
133
|
|
134
134
|
context 'one key past max: 1, one key just past max: 1' do
|
@@ -140,8 +140,8 @@ describe Weeter::Limitator do
|
|
140
140
|
limitator.process(*[keys.first, keys.last])
|
141
141
|
end
|
142
142
|
|
143
|
-
|
144
|
-
|
143
|
+
it { expect(subject.status).to eq(Weeter::Limitator::INITIATE_LIMITING) }
|
144
|
+
it { expect(subject.limited_keys).to eq(keys) }
|
145
145
|
end
|
146
146
|
end
|
147
147
|
end
|
@@ -4,14 +4,14 @@ module Weeter
|
|
4
4
|
module Plugins
|
5
5
|
describe NotificationPlugin do
|
6
6
|
describe "#publish_tweet" do
|
7
|
-
it "
|
7
|
+
it "delegates to the configured plugin" do
|
8
8
|
client_app_config = Hashie::Mash.new(:notification_plugin => :http)
|
9
9
|
tweet_item = TweetItem.new({})
|
10
|
-
|
11
|
-
mock_plugin =
|
12
|
-
Notification::Http.
|
13
|
-
|
14
|
-
mock_plugin.
|
10
|
+
|
11
|
+
mock_plugin = double(Notification::Http)
|
12
|
+
expect(Notification::Http).to receive(:new).and_return(mock_plugin)
|
13
|
+
|
14
|
+
expect(mock_plugin).to receive(:publish_tweet).with(tweet_item)
|
15
15
|
|
16
16
|
plugin = NotificationPlugin.new(client_app_config)
|
17
17
|
plugin.publish_tweet(tweet_item)
|
@@ -5,26 +5,26 @@ module Weeter
|
|
5
5
|
describe Http::UpdateServer do
|
6
6
|
before(:each) do
|
7
7
|
@new_ids = [1,2,3]
|
8
|
-
@tweet_consumer =
|
8
|
+
@tweet_consumer = double('TweetConsumer', :reconnect => nil)
|
9
9
|
@tweet_server = Http::UpdateServer.new(nil)
|
10
10
|
@tweet_server.instance_variable_set('@http_post_content', MultiJson.encode(@new_ids))
|
11
11
|
@tweet_server.tweet_consumer = @tweet_consumer
|
12
|
-
@response =
|
13
|
-
EM::DelegatedHttpResponse.
|
12
|
+
@response = double('DelegatedHttpResponse', :send_response => nil)
|
13
|
+
expect(EM::DelegatedHttpResponse).to receive(:new).and_return(@response)
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
after(:each) do
|
17
17
|
@tweet_server.process_http_request
|
18
18
|
end
|
19
|
-
|
20
|
-
it "
|
21
|
-
@tweet_consumer.
|
19
|
+
|
20
|
+
it "processes http request" do
|
21
|
+
expect(@tweet_consumer).to receive(:reconnect).with(@new_ids)
|
22
22
|
end
|
23
|
-
|
24
|
-
it "
|
25
|
-
@response.
|
23
|
+
|
24
|
+
it "sends the response" do
|
25
|
+
expect(@response). to receive(:send_response)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
30
|
-
end
|
30
|
+
end
|
@@ -4,17 +4,17 @@ module Weeter
|
|
4
4
|
module Plugins
|
5
5
|
describe SubscriptionPlugin do
|
6
6
|
describe "#get_initial_filters" do
|
7
|
-
it "
|
7
|
+
it "delegates to the configured plugin" do
|
8
8
|
client_app_config = Hashie::Mash.new(:subscription_plugin => :http)
|
9
|
-
|
10
|
-
mock_plugin =
|
11
|
-
Subscription::Http.
|
12
|
-
|
13
|
-
mock_plugin.
|
9
|
+
|
10
|
+
mock_plugin = double(Subscription::Http)
|
11
|
+
expect(Subscription::Http).to receive(:new).and_return(mock_plugin)
|
12
|
+
|
13
|
+
expect(mock_plugin).to receive(:get_initial_filters).and_yield([{'foo' => 'bar'}])
|
14
14
|
|
15
15
|
plugin = SubscriptionPlugin.new(client_app_config)
|
16
16
|
plugin.get_initial_filters do |filter_params|
|
17
|
-
filter_params.
|
17
|
+
expect(filter_params).to eq([{'foo' => 'bar'}])
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'em-twitter'
|
2
3
|
|
3
4
|
describe Weeter::Twitter::TweetConsumer do
|
4
5
|
let(:limiter) do
|
@@ -9,20 +10,25 @@ describe Weeter::Twitter::TweetConsumer do
|
|
9
10
|
end
|
10
11
|
|
11
12
|
describe "auth" do
|
12
|
-
it '
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
Twitter::
|
13
|
+
it 'connects to JSON stream with auth options for the configuration' do
|
14
|
+
mock_client = double('EM::Twitter::Client', on_error: nil, on_unauthorized: nil, on_forbidden: nil, on_not_found: nil,
|
15
|
+
on_not_acceptable: nil, on_range_unacceptable: nil, on_too_long: nil, on_enhance_your_calm: nil, on_reconnect: nil,
|
16
|
+
on_max_reconnects: nil,
|
17
|
+
each: nil)
|
18
|
+
expect(Weeter::Configuration::TwitterConfig.instance).to receive(:auth_options).and_return(:oauth => { :foo => :bar }).at_least(:once)
|
19
|
+
expect(EM::Twitter::Client).to receive(:connect).with(hash_including(
|
20
|
+
:path => "/1.1/statuses/filter.json",
|
21
|
+
:params => { 'follow' => [1, 2] }
|
22
|
+
)).and_return(mock_client)
|
23
|
+
|
24
|
+
consumer = Weeter::Twitter::TweetConsumer.new(Weeter::Configuration::TwitterConfig.instance, double('NotificationPlugin'), limiter)
|
19
25
|
consumer.connect({'follow' => ['1','2']})
|
20
26
|
end
|
21
27
|
end
|
22
28
|
|
23
29
|
describe '#limit_filter_params' do
|
24
30
|
|
25
|
-
let(:client_proxy) {
|
31
|
+
let(:client_proxy) { double('NotificationPlugin', :publish_tweet => nil) }
|
26
32
|
let(:consumer) do
|
27
33
|
Weeter::Twitter::TweetConsumer.new(Weeter::Configuration::TwitterConfig.instance, client_proxy, limiter)
|
28
34
|
end
|
@@ -40,8 +46,8 @@ describe Weeter::Twitter::TweetConsumer do
|
|
40
46
|
context 'limit not reached' do
|
41
47
|
it 'leaves the values alone' do
|
42
48
|
result = consumer.send(:limit_filter_params, params)
|
43
|
-
result.fetch('track').length.
|
44
|
-
result.fetch('follow').length.
|
49
|
+
expect(result.fetch('track').length).to eq(0)
|
50
|
+
expect(result.fetch('follow').length).to eq(0)
|
45
51
|
end
|
46
52
|
end
|
47
53
|
|
@@ -50,8 +56,8 @@ describe Weeter::Twitter::TweetConsumer do
|
|
50
56
|
|
51
57
|
it 'it limits follows, but not tracks' do
|
52
58
|
result = consumer.send(:limit_filter_params, params)
|
53
|
-
result.fetch('follow').length.
|
54
|
-
result.fetch('track').length.
|
59
|
+
expect(result.fetch('follow').length).to eq(5000)
|
60
|
+
expect(result.fetch('track').length).to eq(0)
|
55
61
|
end
|
56
62
|
end
|
57
63
|
|
@@ -60,8 +66,8 @@ describe Weeter::Twitter::TweetConsumer do
|
|
60
66
|
|
61
67
|
it 'limits tracks, but not follows' do
|
62
68
|
result = consumer.send(:limit_filter_params, params)
|
63
|
-
result.fetch('track').length.
|
64
|
-
result.fetch('follow').length.
|
69
|
+
expect(result.fetch('track').length).to eq(400)
|
70
|
+
expect(result.fetch('follow').length).to eq(0)
|
65
71
|
end
|
66
72
|
end
|
67
73
|
|
@@ -72,8 +78,8 @@ describe Weeter::Twitter::TweetConsumer do
|
|
72
78
|
it 'limits both' do
|
73
79
|
|
74
80
|
result = consumer.send(:limit_filter_params, params)
|
75
|
-
result.fetch('track').length.
|
76
|
-
result.fetch('follow').length.
|
81
|
+
expect(result.fetch('track').length).to eq(400)
|
82
|
+
expect(result.fetch('follow').length).to eq(5000)
|
77
83
|
end
|
78
84
|
end
|
79
85
|
end
|
@@ -83,20 +89,27 @@ describe Weeter::Twitter::TweetConsumer do
|
|
83
89
|
let(:tweet_values) {
|
84
90
|
[@tweet_hash]
|
85
91
|
}
|
86
|
-
let(:
|
87
|
-
|
88
|
-
|
92
|
+
let(:mock_client) {
|
93
|
+
client = double('EM::Twitter::Client', on_error: nil, on_unauthorized: nil, on_forbidden: nil, on_not_found: nil,
|
94
|
+
on_not_acceptable: nil, on_range_unacceptable: nil, on_too_long: nil, on_enhance_your_calm: nil, on_reconnect: nil,
|
95
|
+
on_max_reconnects: nil,
|
96
|
+
each: nil)
|
97
|
+
client_expectation = expect(client).to receive(:each)
|
89
98
|
tweet_values.each do |t|
|
90
|
-
|
99
|
+
client_expectation.and_yield(MultiJson.encode(t))
|
91
100
|
end
|
92
|
-
|
101
|
+
client
|
93
102
|
}
|
94
103
|
before(:each) do
|
95
|
-
@filter_params = {'follow' => ['1','2','3']}
|
96
|
-
Weeter::Configuration::TwitterConfig.instance.stub!(:auth_options).and_return(:foo => :bar)
|
97
104
|
@tweet_hash = {'text' => "Hey", 'id_str' => "123", 'user' => {'id_str' => "1"}}
|
98
|
-
Twitter::
|
99
|
-
|
105
|
+
expect(EM::Twitter::Client).to receive(:connect).with(hash_including(
|
106
|
+
:path => "/1.1/statuses/filter.json",
|
107
|
+
:params => { 'follow' => [1, 2, 3] }
|
108
|
+
)).and_return(mock_client)
|
109
|
+
|
110
|
+
@filter_params = {'follow' => ['1','2','3']}
|
111
|
+
expect(Weeter::Configuration::TwitterConfig.instance).to receive(:auth_options).and_return(:oauth => { :foo => :bar }).at_least(:once)
|
112
|
+
@client_proxy = double('NotificationPlugin', :publish_tweet => nil)
|
100
113
|
@consumer = Weeter::Twitter::TweetConsumer.new(Weeter::Configuration::TwitterConfig.instance, @client_proxy, limiter)
|
101
114
|
end
|
102
115
|
|
@@ -104,50 +117,45 @@ describe Weeter::Twitter::TweetConsumer do
|
|
104
117
|
@consumer.connect(@filter_params)
|
105
118
|
end
|
106
119
|
|
107
|
-
it "
|
120
|
+
it "instantiates a TweetItem" do
|
108
121
|
tweet_item = Weeter::TweetItem.new(@tweet_hash)
|
109
|
-
Weeter::TweetItem.
|
110
|
-
end
|
111
|
-
|
112
|
-
it "should connect to a Twitter JSON stream" do
|
113
|
-
Twitter::JSONStream.should_receive(:connect).
|
114
|
-
with(:ssl => true, :foo => :bar, :params => {'follow' => [1,2,3]}, :method => 'POST')
|
122
|
+
expect(Weeter::TweetItem).to receive(:new).with({'text' => "Hey", 'id_str' => "123", 'user' => {'id_str' => "1"}}).and_return(tweet_item)
|
115
123
|
end
|
116
124
|
|
117
|
-
it "
|
118
|
-
mock_tweet =
|
119
|
-
Weeter::TweetItem.
|
120
|
-
@client_proxy.
|
125
|
+
it "publishes new tweet if publishable" do
|
126
|
+
mock_tweet = double('tweet', :deletion? => false, :publishable? => true, :limit_notice? => false, :limiting_facets => [])
|
127
|
+
expect(Weeter::TweetItem).to receive(:new).and_return(mock_tweet)
|
128
|
+
expect(@client_proxy).to receive(:publish_tweet).with(mock_tweet)
|
121
129
|
end
|
122
130
|
|
123
|
-
it "
|
124
|
-
mock_tweet =
|
125
|
-
Weeter::TweetItem.
|
126
|
-
@client_proxy.
|
131
|
+
it "does not publish unpublishable tweets" do
|
132
|
+
mock_tweet = double('tweet', :deletion? => false, :publishable? => false, :limit_notice? => false, :[] => '', :limiting_facets => [], :disconnect_notice? => false)
|
133
|
+
expect(Weeter::TweetItem).to receive(:new).and_return mock_tweet
|
134
|
+
expect(@client_proxy).to_not receive(:publish_tweet).with(mock_tweet)
|
127
135
|
end
|
128
136
|
|
129
|
-
it "
|
130
|
-
mock_tweet =
|
131
|
-
Weeter::TweetItem.
|
132
|
-
@client_proxy.
|
137
|
+
it "deletes deletion tweets" do
|
138
|
+
mock_tweet = double('tweet', :deletion? => true, :publishable? => false, :limit_notice? => false, :limiting_facets => [])
|
139
|
+
expect(Weeter::TweetItem).to receive(:new).and_return mock_tweet
|
140
|
+
expect(@client_proxy).to receive(:delete_tweet).with(mock_tweet)
|
133
141
|
end
|
134
142
|
|
135
|
-
it "
|
143
|
+
it "notifies when stream is limited by Twitter" do
|
136
144
|
tweet_item = Weeter::TweetItem.new({'limit' => { 'track' => 65 } })
|
137
|
-
Weeter::TweetItem.
|
138
|
-
@client_proxy.
|
145
|
+
expect(Weeter::TweetItem).to receive(:new).and_return(tweet_item)
|
146
|
+
expect(@client_proxy).to receive(:notify_missed_tweets).with(tweet_item)
|
139
147
|
end
|
140
148
|
|
141
149
|
context "when weeter is initiating rate-limiting on a facet" do
|
142
150
|
let(:tweet_values) {
|
143
151
|
[@tweet_hash, @tweet_hash]
|
144
152
|
}
|
145
|
-
it "
|
146
|
-
tweet_item1 =
|
147
|
-
tweet_item2 =
|
148
|
-
Weeter::TweetItem.
|
153
|
+
it "notifies that rate limiting is being initiated" do
|
154
|
+
tweet_item1 = double('tweet', :deletion? => false, :publishable? => true, :limit_notice? => false, :limiting_facets => ['key'], :[] => '1')
|
155
|
+
tweet_item2 = double('tweet', :deletion? => false, :publishable? => true, :limit_notice? => false, :limiting_facets => ['key'], :[] => '2')
|
156
|
+
expect(Weeter::TweetItem).to receive(:new).and_return(tweet_item1, tweet_item2)
|
149
157
|
|
150
|
-
@client_proxy.
|
158
|
+
expect(@client_proxy).to receive(:notify_rate_limiting_initiated).with(tweet_item2, ['key'])
|
151
159
|
end
|
152
160
|
end
|
153
161
|
end
|
@@ -6,77 +6,77 @@ describe Weeter::TweetItem do
|
|
6
6
|
}
|
7
7
|
|
8
8
|
describe "deletion?" do
|
9
|
-
it "
|
9
|
+
it "is true if it is a deletion request" do
|
10
10
|
item = Weeter::TweetItem.new({"delete"=>{"status"=>{"id"=>234, "user_id"=>34555}}})
|
11
|
-
item.
|
11
|
+
expect(item).to be_deletion
|
12
12
|
end
|
13
13
|
|
14
|
-
it "
|
14
|
+
it "is false if it is not a deletion request" do
|
15
15
|
item = Weeter::TweetItem.new({'text' => "Hey", 'id_str' => "123", 'user' => {'id_str' => "1"}})
|
16
|
-
item.
|
16
|
+
expect(item).to_not be_deletion
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
20
|
describe "publishable" do
|
21
21
|
|
22
22
|
|
23
|
-
it "
|
23
|
+
it "is publishable if not a reply or a retweet" do
|
24
24
|
item = Weeter::TweetItem.new(tweet_json)
|
25
|
-
item.
|
25
|
+
expect(item).to be_publishable
|
26
26
|
end
|
27
27
|
|
28
|
-
it "
|
28
|
+
it "is not publishable if implicitly retweeted" do
|
29
29
|
item = Weeter::TweetItem.new(tweet_json.merge({'text' => 'RT @joe Hey'}))
|
30
|
-
item.
|
30
|
+
expect(item).to_not be_publishable
|
31
31
|
end
|
32
32
|
|
33
|
-
it "
|
33
|
+
it "is not publishable if explicitly retweeted" do
|
34
34
|
item = Weeter::TweetItem.new(tweet_json.merge('retweeted_status' => {'id_str' => '111', 'text' => 'Hey', 'user' => {'id_str' => "1"}}))
|
35
|
-
item.
|
35
|
+
expect(item).to_not be_publishable
|
36
36
|
end
|
37
37
|
|
38
|
-
it "
|
38
|
+
it "is not publishable if implicit reply" do
|
39
39
|
item = Weeter::TweetItem.new(tweet_json.merge('text' => '@joe Hey'))
|
40
|
-
item.
|
40
|
+
expect(item).to_not be_publishable
|
41
41
|
end
|
42
42
|
|
43
|
-
it "
|
43
|
+
it "is not publishable if explicit reply" do
|
44
44
|
item = Weeter::TweetItem.new(tweet_json.merge('text' => '@joe Hey', 'in_reply_to_user_id_str' => '1'))
|
45
|
-
item.
|
45
|
+
expect(item).to_not be_publishable
|
46
46
|
end
|
47
47
|
|
48
|
-
it "
|
48
|
+
it "is not publishable if disconnect message" do
|
49
49
|
item = Weeter::TweetItem.new({"disconnect" => {"code" => 7,"stream_name" => "YappBox-statuses668638","reason" => "admin logout"}})
|
50
|
-
item.
|
50
|
+
expect(item).to_not be_publishable
|
51
51
|
end
|
52
52
|
|
53
53
|
end
|
54
54
|
|
55
55
|
describe "limit_notice?" do
|
56
|
-
it "
|
56
|
+
it "is true if it's a limit notice" do
|
57
57
|
item = Weeter::TweetItem.new({ 'limit' => { 'track' => 65 }})
|
58
|
-
item.
|
59
|
-
item.missed_tweets_count.
|
58
|
+
expect(item).to be_limit_notice
|
59
|
+
expect(item.missed_tweets_count).to eq(65)
|
60
60
|
end
|
61
|
-
it "
|
61
|
+
it "is not be true if it's a limit notice" do
|
62
62
|
item = Weeter::TweetItem.new(tweet_json)
|
63
|
-
item.
|
64
|
-
lambda {
|
63
|
+
expect(item).to_not be_limit_notice
|
64
|
+
expect(lambda {
|
65
65
|
item.missed_tweets_count
|
66
|
-
}.
|
66
|
+
}).to_not raise_error
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
70
|
describe "json attributes" do
|
71
71
|
|
72
|
-
it "
|
72
|
+
it "delegates hash calls to its json" do
|
73
73
|
item = Weeter::TweetItem.new({'text' => "Hey"})
|
74
|
-
item['text'].
|
74
|
+
expect(item['text']).to eq("Hey")
|
75
75
|
end
|
76
76
|
|
77
|
-
it "
|
77
|
+
it "retrieves nested attributes" do
|
78
78
|
item = Weeter::TweetItem.new({'text' => "Hey", 'id_str' => "123", 'user' => {'id_str' => '1'}})
|
79
|
-
item['user']['id_str'].
|
79
|
+
expect(item['user']['id_str']).to eq("1")
|
80
80
|
end
|
81
81
|
|
82
82
|
end
|
data/weeter.gemspec
CHANGED
@@ -19,17 +19,18 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
20
|
s.require_paths = ["lib"]
|
21
21
|
|
22
|
-
s.add_dependency('eventmachine')
|
23
|
-
s.add_dependency('eventmachine_httpserver', '
|
24
|
-
s.add_dependency('em-hiredis', '
|
25
|
-
s.add_dependency('multi_json', '
|
26
|
-
s.add_dependency('hashie', '>=
|
27
|
-
s.add_dependency('em-http-request', '
|
28
|
-
s.add_dependency('i18n', "~> 0.6.
|
29
|
-
s.add_dependency('activesupport', ">= 3.
|
30
|
-
s.add_dependency("simple_oauth", '
|
31
|
-
s.add_dependency('
|
22
|
+
s.add_dependency('eventmachine', '~> 1.2.0')
|
23
|
+
s.add_dependency('eventmachine_httpserver', '~> 0.2.1')
|
24
|
+
s.add_dependency('em-hiredis', '~> 0.3.1')
|
25
|
+
s.add_dependency('multi_json', '~> 1.3.0')
|
26
|
+
s.add_dependency('hashie', '>= 2.0.5')
|
27
|
+
s.add_dependency('em-http-request', '~> 1.1.5')
|
28
|
+
s.add_dependency('i18n', "~> 0.6.11")
|
29
|
+
s.add_dependency('activesupport', ">= 3.2.22")
|
30
|
+
s.add_dependency("simple_oauth", '~> 0.3.1')
|
31
|
+
s.add_dependency('em-twitter', '~> 0.3.5')
|
32
32
|
|
33
|
-
s.add_development_dependency 'rspec', '~>
|
33
|
+
s.add_development_dependency 'rspec', '~> 3.4.0'
|
34
|
+
s.add_development_dependency 'byebug', '~> 2.4.1'
|
34
35
|
s.add_development_dependency 'ZenTest'
|
35
36
|
end
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: weeter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.17.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Luke Melia
|
@@ -11,200 +10,190 @@ authors:
|
|
11
10
|
autorequire:
|
12
11
|
bindir: bin
|
13
12
|
cert_chain: []
|
14
|
-
date:
|
13
|
+
date: 2017-07-21 00:00:00.000000000 Z
|
15
14
|
dependencies:
|
16
15
|
- !ruby/object:Gem::Dependency
|
17
16
|
name: eventmachine
|
18
|
-
prerelease: false
|
19
17
|
requirement: !ruby/object:Gem::Requirement
|
20
18
|
requirements:
|
21
|
-
- -
|
19
|
+
- - "~>"
|
22
20
|
- !ruby/object:Gem::Version
|
23
|
-
version:
|
24
|
-
none: false
|
21
|
+
version: 1.2.0
|
25
22
|
type: :runtime
|
23
|
+
prerelease: false
|
26
24
|
version_requirements: !ruby/object:Gem::Requirement
|
27
25
|
requirements:
|
28
|
-
- -
|
26
|
+
- - "~>"
|
29
27
|
- !ruby/object:Gem::Version
|
30
|
-
version:
|
31
|
-
none: false
|
28
|
+
version: 1.2.0
|
32
29
|
- !ruby/object:Gem::Dependency
|
33
30
|
name: eventmachine_httpserver
|
34
|
-
prerelease: false
|
35
31
|
requirement: !ruby/object:Gem::Requirement
|
36
32
|
requirements:
|
37
|
-
- -
|
33
|
+
- - "~>"
|
38
34
|
- !ruby/object:Gem::Version
|
39
35
|
version: 0.2.1
|
40
|
-
none: false
|
41
36
|
type: :runtime
|
37
|
+
prerelease: false
|
42
38
|
version_requirements: !ruby/object:Gem::Requirement
|
43
39
|
requirements:
|
44
|
-
- -
|
40
|
+
- - "~>"
|
45
41
|
- !ruby/object:Gem::Version
|
46
42
|
version: 0.2.1
|
47
|
-
none: false
|
48
43
|
- !ruby/object:Gem::Dependency
|
49
44
|
name: em-hiredis
|
50
|
-
prerelease: false
|
51
45
|
requirement: !ruby/object:Gem::Requirement
|
52
46
|
requirements:
|
53
|
-
- -
|
47
|
+
- - "~>"
|
54
48
|
- !ruby/object:Gem::Version
|
55
|
-
version: 0.1
|
56
|
-
none: false
|
49
|
+
version: 0.3.1
|
57
50
|
type: :runtime
|
51
|
+
prerelease: false
|
58
52
|
version_requirements: !ruby/object:Gem::Requirement
|
59
53
|
requirements:
|
60
|
-
- -
|
54
|
+
- - "~>"
|
61
55
|
- !ruby/object:Gem::Version
|
62
|
-
version: 0.1
|
63
|
-
none: false
|
56
|
+
version: 0.3.1
|
64
57
|
- !ruby/object:Gem::Dependency
|
65
58
|
name: multi_json
|
66
|
-
prerelease: false
|
67
59
|
requirement: !ruby/object:Gem::Requirement
|
68
60
|
requirements:
|
69
|
-
- -
|
61
|
+
- - "~>"
|
70
62
|
- !ruby/object:Gem::Version
|
71
|
-
version: 1.0
|
72
|
-
none: false
|
63
|
+
version: 1.3.0
|
73
64
|
type: :runtime
|
65
|
+
prerelease: false
|
74
66
|
version_requirements: !ruby/object:Gem::Requirement
|
75
67
|
requirements:
|
76
|
-
- -
|
68
|
+
- - "~>"
|
77
69
|
- !ruby/object:Gem::Version
|
78
|
-
version: 1.0
|
79
|
-
none: false
|
70
|
+
version: 1.3.0
|
80
71
|
- !ruby/object:Gem::Dependency
|
81
72
|
name: hashie
|
82
|
-
prerelease: false
|
83
73
|
requirement: !ruby/object:Gem::Requirement
|
84
74
|
requirements:
|
85
|
-
- -
|
75
|
+
- - ">="
|
86
76
|
- !ruby/object:Gem::Version
|
87
|
-
version:
|
88
|
-
none: false
|
77
|
+
version: 2.0.5
|
89
78
|
type: :runtime
|
79
|
+
prerelease: false
|
90
80
|
version_requirements: !ruby/object:Gem::Requirement
|
91
81
|
requirements:
|
92
|
-
- -
|
82
|
+
- - ">="
|
93
83
|
- !ruby/object:Gem::Version
|
94
|
-
version:
|
95
|
-
none: false
|
84
|
+
version: 2.0.5
|
96
85
|
- !ruby/object:Gem::Dependency
|
97
86
|
name: em-http-request
|
98
|
-
prerelease: false
|
99
87
|
requirement: !ruby/object:Gem::Requirement
|
100
88
|
requirements:
|
101
|
-
- -
|
89
|
+
- - "~>"
|
102
90
|
- !ruby/object:Gem::Version
|
103
|
-
version: 1.
|
104
|
-
none: false
|
91
|
+
version: 1.1.5
|
105
92
|
type: :runtime
|
93
|
+
prerelease: false
|
106
94
|
version_requirements: !ruby/object:Gem::Requirement
|
107
95
|
requirements:
|
108
|
-
- -
|
96
|
+
- - "~>"
|
109
97
|
- !ruby/object:Gem::Version
|
110
|
-
version: 1.
|
111
|
-
none: false
|
98
|
+
version: 1.1.5
|
112
99
|
- !ruby/object:Gem::Dependency
|
113
100
|
name: i18n
|
114
|
-
prerelease: false
|
115
101
|
requirement: !ruby/object:Gem::Requirement
|
116
102
|
requirements:
|
117
|
-
- - ~>
|
103
|
+
- - "~>"
|
118
104
|
- !ruby/object:Gem::Version
|
119
|
-
version: 0.6.
|
120
|
-
none: false
|
105
|
+
version: 0.6.11
|
121
106
|
type: :runtime
|
107
|
+
prerelease: false
|
122
108
|
version_requirements: !ruby/object:Gem::Requirement
|
123
109
|
requirements:
|
124
|
-
- - ~>
|
110
|
+
- - "~>"
|
125
111
|
- !ruby/object:Gem::Version
|
126
|
-
version: 0.6.
|
127
|
-
none: false
|
112
|
+
version: 0.6.11
|
128
113
|
- !ruby/object:Gem::Dependency
|
129
114
|
name: activesupport
|
130
|
-
prerelease: false
|
131
115
|
requirement: !ruby/object:Gem::Requirement
|
132
116
|
requirements:
|
133
|
-
- -
|
117
|
+
- - ">="
|
134
118
|
- !ruby/object:Gem::Version
|
135
|
-
version: 3.
|
136
|
-
none: false
|
119
|
+
version: 3.2.22
|
137
120
|
type: :runtime
|
121
|
+
prerelease: false
|
138
122
|
version_requirements: !ruby/object:Gem::Requirement
|
139
123
|
requirements:
|
140
|
-
- -
|
124
|
+
- - ">="
|
141
125
|
- !ruby/object:Gem::Version
|
142
|
-
version: 3.
|
143
|
-
none: false
|
126
|
+
version: 3.2.22
|
144
127
|
- !ruby/object:Gem::Dependency
|
145
128
|
name: simple_oauth
|
146
|
-
prerelease: false
|
147
129
|
requirement: !ruby/object:Gem::Requirement
|
148
130
|
requirements:
|
149
|
-
- -
|
131
|
+
- - "~>"
|
150
132
|
- !ruby/object:Gem::Version
|
151
|
-
version: 0.1
|
152
|
-
none: false
|
133
|
+
version: 0.3.1
|
153
134
|
type: :runtime
|
135
|
+
prerelease: false
|
154
136
|
version_requirements: !ruby/object:Gem::Requirement
|
155
137
|
requirements:
|
156
|
-
- -
|
138
|
+
- - "~>"
|
157
139
|
- !ruby/object:Gem::Version
|
158
|
-
version: 0.1
|
159
|
-
none: false
|
140
|
+
version: 0.3.1
|
160
141
|
- !ruby/object:Gem::Dependency
|
161
|
-
name:
|
162
|
-
prerelease: false
|
142
|
+
name: em-twitter
|
163
143
|
requirement: !ruby/object:Gem::Requirement
|
164
144
|
requirements:
|
165
|
-
- - ~>
|
145
|
+
- - "~>"
|
166
146
|
- !ruby/object:Gem::Version
|
167
|
-
version: 0.
|
168
|
-
none: false
|
147
|
+
version: 0.3.5
|
169
148
|
type: :runtime
|
149
|
+
prerelease: false
|
170
150
|
version_requirements: !ruby/object:Gem::Requirement
|
171
151
|
requirements:
|
172
|
-
- - ~>
|
152
|
+
- - "~>"
|
173
153
|
- !ruby/object:Gem::Version
|
174
|
-
version: 0.
|
175
|
-
none: false
|
154
|
+
version: 0.3.5
|
176
155
|
- !ruby/object:Gem::Dependency
|
177
156
|
name: rspec
|
157
|
+
requirement: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - "~>"
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: 3.4.0
|
162
|
+
type: :development
|
178
163
|
prerelease: false
|
164
|
+
version_requirements: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - "~>"
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: 3.4.0
|
169
|
+
- !ruby/object:Gem::Dependency
|
170
|
+
name: byebug
|
179
171
|
requirement: !ruby/object:Gem::Requirement
|
180
172
|
requirements:
|
181
|
-
- - ~>
|
173
|
+
- - "~>"
|
182
174
|
- !ruby/object:Gem::Version
|
183
|
-
version: 2.
|
184
|
-
none: false
|
175
|
+
version: 2.4.1
|
185
176
|
type: :development
|
177
|
+
prerelease: false
|
186
178
|
version_requirements: !ruby/object:Gem::Requirement
|
187
179
|
requirements:
|
188
|
-
- - ~>
|
180
|
+
- - "~>"
|
189
181
|
- !ruby/object:Gem::Version
|
190
|
-
version: 2.
|
191
|
-
none: false
|
182
|
+
version: 2.4.1
|
192
183
|
- !ruby/object:Gem::Dependency
|
193
184
|
name: ZenTest
|
194
|
-
prerelease: false
|
195
185
|
requirement: !ruby/object:Gem::Requirement
|
196
186
|
requirements:
|
197
|
-
- -
|
187
|
+
- - ">="
|
198
188
|
- !ruby/object:Gem::Version
|
199
189
|
version: '0'
|
200
|
-
none: false
|
201
190
|
type: :development
|
191
|
+
prerelease: false
|
202
192
|
version_requirements: !ruby/object:Gem::Requirement
|
203
193
|
requirements:
|
204
|
-
- -
|
194
|
+
- - ">="
|
205
195
|
- !ruby/object:Gem::Version
|
206
196
|
version: '0'
|
207
|
-
none: false
|
208
197
|
description: Weeter subscribes to a set of twitter users or search terms using Twitter's
|
209
198
|
streaming API, and notifies your app with each new tweet.
|
210
199
|
email:
|
@@ -215,8 +204,9 @@ executables:
|
|
215
204
|
extensions: []
|
216
205
|
extra_rdoc_files: []
|
217
206
|
files:
|
218
|
-
- .gitignore
|
219
|
-
- .
|
207
|
+
- ".gitignore"
|
208
|
+
- ".ruby-version"
|
209
|
+
- ".travis.yml"
|
220
210
|
- Gemfile
|
221
211
|
- LICENSE
|
222
212
|
- README.md
|
@@ -263,27 +253,26 @@ files:
|
|
263
253
|
- weeter.gemspec
|
264
254
|
homepage: http://github.com/lukemelia/weeter
|
265
255
|
licenses: []
|
256
|
+
metadata: {}
|
266
257
|
post_install_message:
|
267
258
|
rdoc_options: []
|
268
259
|
require_paths:
|
269
260
|
- lib
|
270
261
|
required_ruby_version: !ruby/object:Gem::Requirement
|
271
262
|
requirements:
|
272
|
-
- -
|
263
|
+
- - ">="
|
273
264
|
- !ruby/object:Gem::Version
|
274
265
|
version: '0'
|
275
|
-
none: false
|
276
266
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
277
267
|
requirements:
|
278
|
-
- -
|
268
|
+
- - ">="
|
279
269
|
- !ruby/object:Gem::Version
|
280
270
|
version: '0'
|
281
|
-
none: false
|
282
271
|
requirements: []
|
283
272
|
rubyforge_project: weeter
|
284
|
-
rubygems_version:
|
273
|
+
rubygems_version: 2.4.8
|
285
274
|
signing_key:
|
286
|
-
specification_version:
|
275
|
+
specification_version: 4
|
287
276
|
summary: Consume the Twitter stream and notify your app
|
288
277
|
test_files:
|
289
278
|
- spec/spec_helper.rb
|