akane 0.0.1 → 0.1.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 +4 -4
- data/akane.gemspec +2 -1
- data/lib/akane/cli.rb +30 -0
- data/lib/akane/config.rb +8 -0
- data/lib/akane/manager.rb +2 -16
- data/lib/akane/receivers/stream.rb +28 -36
- data/lib/akane/recorder.rb +6 -5
- data/lib/akane/storages/file.rb +8 -8
- data/lib/akane/util.rb +18 -0
- data/lib/akane/version.rb +1 -1
- data/spec/manager_spec.rb +0 -5
- data/spec/receivers/stream_spec.rb +44 -16
- data/spec/recorder_spec.rb +17 -17
- data/spec/spec_helper.rb +1 -1
- data/spec/support/mock_twitter_streaming.rb +98 -0
- data/spec/util_spec.rb +36 -0
- metadata +27 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0bd862e8bde263f78e295317635f8bb4eb0256cd
|
4
|
+
data.tar.gz: 4b64ada85e74c5018bc7f013c3f37d850cccb7e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2015deae33ec6b5598a17743dc308f01ce8cf452819f1f2ca2334b1a37365897fb79c7f7d8676bb1618ebe5bc9ac98d3e224d39cd34738bad89cecb038029343
|
7
|
+
data.tar.gz: fe3250c3bc69de4bafcad0f68ea3d488efdf0ee75d7718e04eabe27f3ce2a400a200fab02bb1fe124858dab17d430fa2555277278b38cf161ae38696e4f681da
|
data/akane.gemspec
CHANGED
@@ -18,8 +18,9 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "tweetstream", "~> 2.6.0"
|
22
21
|
spec.add_dependency "elasticsearch", "~> 0.4.1"
|
22
|
+
spec.add_dependency "twitter", "~> 5.5.1"
|
23
|
+
spec.add_dependency "oauth", ">= 0.4.7"
|
23
24
|
|
24
25
|
spec.add_development_dependency "bundler"
|
25
26
|
spec.add_development_dependency "rake"
|
data/lib/akane/cli.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'akane/manager'
|
2
2
|
require 'akane/config'
|
3
|
+
require 'optparse'
|
3
4
|
|
4
5
|
module Akane
|
5
6
|
class CLI
|
@@ -20,6 +21,35 @@ module Akane
|
|
20
21
|
manager.run
|
21
22
|
end
|
22
23
|
|
24
|
+
def auth
|
25
|
+
require 'twitter'
|
26
|
+
|
27
|
+
consumer = config.consumer
|
28
|
+
|
29
|
+
request_token = consumer.get_request_token
|
30
|
+
puts "Allow me using this URL: #{request_token.authorize_url}"
|
31
|
+
print "Then type shown PIN: "
|
32
|
+
pin = $stdin.gets.chomp
|
33
|
+
print "Authorizing... "
|
34
|
+
|
35
|
+
access_token = request_token.get_access_token(oauth_verifier: pin)
|
36
|
+
twitter = Twitter::REST::Client.new(
|
37
|
+
consumer_key: config[:consumer]['token'],
|
38
|
+
consumer_secret: config[:consumer]['secret'],
|
39
|
+
access_token: access_token.token,
|
40
|
+
access_token_secret: access_token.secret,
|
41
|
+
)
|
42
|
+
puts "done.\n\n"
|
43
|
+
|
44
|
+
puts <<-EOY
|
45
|
+
---
|
46
|
+
accounts:
|
47
|
+
#{twitter.current_user.screen_name}:
|
48
|
+
token: #{access_token.token}
|
49
|
+
secret: #{access_token.secret}
|
50
|
+
EOY
|
51
|
+
end
|
52
|
+
|
23
53
|
def help
|
24
54
|
puts <<-EOH
|
25
55
|
Usage:
|
data/lib/akane/config.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
require 'logger'
|
3
|
+
require 'oauth'
|
3
4
|
|
4
5
|
module Akane
|
5
6
|
class Config
|
@@ -18,6 +19,13 @@ module Akane
|
|
18
19
|
@hash[k.to_s]
|
19
20
|
end
|
20
21
|
|
22
|
+
def consumer
|
23
|
+
consumer = self[:consumer]
|
24
|
+
return nil unless consumer
|
25
|
+
OAuth::Consumer.new(consumer['token'], consumer['secret'],
|
26
|
+
site: 'https://api.twitter.com/')
|
27
|
+
end
|
28
|
+
|
21
29
|
def logger
|
22
30
|
Logger.new(@hash["log"] || $stdout)
|
23
31
|
end
|
data/lib/akane/manager.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'eventmachine'
|
2
1
|
require 'akane/config'
|
3
2
|
require 'akane/recorder'
|
4
3
|
require 'akane/receivers/stream'
|
@@ -53,27 +52,14 @@ module Akane
|
|
53
52
|
@logger.info "Starting receivers..."
|
54
53
|
@receivers.each(&:start)
|
55
54
|
@logger.info "Starting recorder..."
|
56
|
-
|
57
|
-
EM.defer { @recorder.run }
|
58
|
-
else
|
59
|
-
@recorder.run
|
60
|
-
end
|
55
|
+
@recorder.run
|
61
56
|
end
|
62
57
|
|
63
58
|
def run
|
64
59
|
@logger.info "Running..."
|
65
60
|
self.prepare()
|
66
61
|
|
67
|
-
|
68
|
-
start()
|
69
|
-
else
|
70
|
-
@logger.info "Diving into Eventmachine"
|
71
|
-
EM.epoll
|
72
|
-
EM.kqueue
|
73
|
-
EM.run do
|
74
|
-
start()
|
75
|
-
end
|
76
|
-
end
|
62
|
+
start()
|
77
63
|
end
|
78
64
|
|
79
65
|
private
|
@@ -1,61 +1,53 @@
|
|
1
1
|
require 'akane/receivers/abstract_receiver'
|
2
|
-
require '
|
2
|
+
require 'twitter'
|
3
3
|
|
4
4
|
module Akane
|
5
5
|
module Receivers
|
6
6
|
class Stream < AbstractReceiver
|
7
7
|
def initialize(*)
|
8
8
|
super
|
9
|
-
@
|
9
|
+
@thread = nil
|
10
10
|
end
|
11
11
|
|
12
|
-
def running?() @
|
12
|
+
def running?() !!(@thread && @thread.alive?) end
|
13
13
|
|
14
14
|
def stream
|
15
|
-
@stream ||=
|
16
|
-
auth_method: :oauth,
|
15
|
+
@stream ||= Twitter::Streaming::Client.new(
|
17
16
|
consumer_key: @consumer[:token],
|
18
17
|
consumer_secret: @consumer[:secret],
|
19
|
-
|
20
|
-
|
21
|
-
)
|
22
|
-
|
23
|
-
invoke(:event, hash) if hash["event"]
|
24
|
-
end
|
25
|
-
|
26
|
-
stream.on_timeline_status do |tweet|
|
27
|
-
invoke(:tweet, tweet)
|
28
|
-
end
|
29
|
-
|
30
|
-
stream.on_delete do |tweet_id, user_id|
|
31
|
-
invoke(:delete, user_id, tweet_id)
|
32
|
-
end
|
18
|
+
access_token: @account[:token],
|
19
|
+
access_token_secret: @account[:secret]
|
20
|
+
)
|
21
|
+
end
|
33
22
|
|
34
|
-
|
35
|
-
invoke(:message, message)
|
36
|
-
end
|
23
|
+
attr_reader :thread
|
37
24
|
|
38
|
-
|
39
|
-
|
40
|
-
end
|
25
|
+
def start
|
26
|
+
@logger.info "Stream : Starting"
|
41
27
|
|
42
|
-
|
43
|
-
|
28
|
+
@thread = Thread.new do
|
29
|
+
stream.user do |obj|
|
30
|
+
case obj
|
31
|
+
when Twitter::Tweet
|
32
|
+
invoke(:tweet, obj)
|
33
|
+
when Twitter::DirectMessage
|
34
|
+
invoke(:message, obj)
|
35
|
+
when Twitter::Streaming::DeletedTweet
|
36
|
+
invoke(:delete, obj.user_id, obj.id)
|
37
|
+
when Twitter::Streaming::Event
|
38
|
+
invoke(:event,
|
39
|
+
'event' => obj.name, 'source' => obj.source,
|
40
|
+
'target' => obj.target, 'target_object' => obj.target_object)
|
41
|
+
end
|
44
42
|
end
|
45
|
-
|
46
|
-
end
|
43
|
+
end
|
47
44
|
|
48
|
-
def start
|
49
|
-
@logger.info "Stream : Starting"
|
50
|
-
stream.userstream
|
51
|
-
@running = true
|
52
45
|
self
|
53
46
|
end
|
54
47
|
|
55
48
|
def stop
|
56
|
-
|
57
|
-
@
|
58
|
-
@running = false
|
49
|
+
@thread.tap(&:kill).join
|
50
|
+
@thread = nil
|
59
51
|
self
|
60
52
|
end
|
61
53
|
end
|
data/lib/akane/recorder.rb
CHANGED
@@ -39,11 +39,12 @@ module Akane
|
|
39
39
|
|
40
40
|
def perform(action, account, *payload, raise_errors: false)
|
41
41
|
if action == :record_tweet
|
42
|
-
return if @recently_performed[payload.last[
|
43
|
-
@recently_performed.flag!(payload.last[
|
42
|
+
return if @recently_performed[payload.last[:id]]
|
43
|
+
@recently_performed.flag!(payload.last[:id])
|
44
44
|
|
45
|
-
|
46
|
-
|
45
|
+
# WTF: Twitter::NullObject
|
46
|
+
unless payload.last[:retweeted_status].nil?
|
47
|
+
perform(:record_tweet, account, payload.last[:retweeted_status], raise_errors: raise_errors)
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
@@ -64,7 +65,7 @@ module Akane
|
|
64
65
|
begin
|
65
66
|
self.dequeue(raise_errors)
|
66
67
|
rescue Exception => e
|
67
|
-
raise e if
|
68
|
+
raise e if Interrupt === e
|
68
69
|
raise e if raise_errors
|
69
70
|
@logger.error "Error while recorder dequing: #{e.inspect}"
|
70
71
|
@logger.error e.backtrace
|
data/lib/akane/storages/file.rb
CHANGED
@@ -17,10 +17,10 @@ module Akane
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def record_tweet(account, tweet)
|
20
|
-
timeline_io.puts "[#{tweet[
|
21
|
-
"#{tweet[
|
20
|
+
timeline_io.puts "[#{tweet[:created_at].xmlschema}][#{account}] #{tweet[:user][:screen_name]}: " \
|
21
|
+
"#{tweet[:text].gsub(/\r?\n/,' ')} (#{tweet[:user][:id]},#{tweet[:id]})"
|
22
22
|
|
23
|
-
tweets_io_for_user(tweet[
|
23
|
+
tweets_io_for_user(tweet[:user][:id], tweet[:user][:screen_name]) do |io|
|
24
24
|
io.puts tweet.attrs.to_json
|
25
25
|
end
|
26
26
|
end
|
@@ -33,16 +33,16 @@ module Akane
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def record_event(account, event)
|
36
|
-
event_io.puts event.merge(
|
36
|
+
event_io.puts event.merge(:happened_on => account).to_json
|
37
37
|
end
|
38
38
|
|
39
39
|
def record_message(account, message)
|
40
|
-
messages_raw_io_for_user(message[
|
40
|
+
messages_raw_io_for_user(message[:sender][:id], message[:sender][:screen_name]) do |io|
|
41
41
|
io.puts message.attrs.to_json
|
42
42
|
end
|
43
|
-
messages_io_for_user(message[
|
44
|
-
io.puts "[#{message[
|
45
|
-
" #{message[
|
43
|
+
messages_io_for_user(message[:sender][:id], message[:sender][:screen_name]) do |io|
|
44
|
+
io.puts "[#{message[:created_at].xmlschema}] #{message[:sender][:screen_name]} -> #{message[:recipient][:screen_name]}:" \
|
45
|
+
" #{message[:text]} (#{message[:sender][:id]} -> #{message[:recipient][:id]},#{message[:id]})"
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
data/lib/akane/util.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Akane
|
2
|
+
module Util
|
3
|
+
class << self
|
4
|
+
def symbolish_hash(hash)
|
5
|
+
Hash[hash.map { |k, v|
|
6
|
+
[
|
7
|
+
k.is_a?(Symbol) ? k : k.to_s.to_sym,
|
8
|
+
case v
|
9
|
+
when Hash; symbolish_hash(v)
|
10
|
+
when Array; v.map{ |i| i.kind_of?(Hash) ? symbolish_hash(i) : i }
|
11
|
+
else; v
|
12
|
+
end
|
13
|
+
]
|
14
|
+
}]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/akane/version.rb
CHANGED
data/spec/manager_spec.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'eventmachine'
|
3
2
|
require 'akane/manager'
|
4
3
|
require 'akane/receivers/stream'
|
5
4
|
require 'akane/storages/mock'
|
@@ -29,10 +28,6 @@ describe Akane::Manager do
|
|
29
28
|
|
30
29
|
subject { Akane::Manager.new(config) }
|
31
30
|
|
32
|
-
before do
|
33
|
-
EM.stub(:run) { |&block| block.call }
|
34
|
-
end
|
35
|
-
|
36
31
|
# receivers -> manager -> recorder -> storage
|
37
32
|
|
38
33
|
describe "#prepare" do
|
@@ -7,13 +7,13 @@ describe Akane::Receivers::Stream do
|
|
7
7
|
subject { described_class.new(consumer: {token: 'consumer-token', secret: 'consumer-secret'}, account: {token: '42-access-token', secret: 'access-secret'}, config: config, logger: Logger.new(nil)) }
|
8
8
|
|
9
9
|
after(:each) do
|
10
|
-
|
10
|
+
Twitter::Streaming::MockClient.clients.clear
|
11
11
|
end
|
12
12
|
|
13
13
|
describe "#start" do
|
14
14
|
it "starts listening" do
|
15
|
-
expect { subject.start } \
|
16
|
-
.to change {
|
15
|
+
expect { subject.start; 100.times { break if subject.thread.status == 'sleep'; sleep 0.1 } } \
|
16
|
+
.to change { Twitter::Streaming::MockClient.clients.size } \
|
17
17
|
.by(1)
|
18
18
|
end
|
19
19
|
|
@@ -27,11 +27,12 @@ describe Akane::Receivers::Stream do
|
|
27
27
|
describe "#stop" do
|
28
28
|
before do
|
29
29
|
subject.start
|
30
|
+
100.times { break if subject.thread.status == 'sleep'; sleep 0.1 }
|
30
31
|
end
|
31
32
|
|
32
33
|
it "stops listening" do
|
33
34
|
expect { subject.stop } \
|
34
|
-
.to change {
|
35
|
+
.to change { Twitter::Streaming::MockClient.clients.size } \
|
35
36
|
.by(-1)
|
36
37
|
end
|
37
38
|
|
@@ -45,36 +46,51 @@ describe Akane::Receivers::Stream do
|
|
45
46
|
describe "when received tweet" do
|
46
47
|
before do
|
47
48
|
subject.start
|
49
|
+
100.times { break if subject.thread.status == 'sleep'; sleep 0.1 }
|
48
50
|
end
|
49
51
|
|
50
52
|
it "calls on_tweet hook" do
|
51
53
|
called = nil
|
52
|
-
subject.on_tweet do |
|
53
|
-
called =
|
54
|
+
subject.on_tweet do |t|
|
55
|
+
called = t
|
54
56
|
end
|
55
|
-
|
56
|
-
|
57
|
+
|
58
|
+
tweet = Twitter::Tweet.new(id: 1)
|
59
|
+
Twitter::Streaming::MockClient.invoke(tweet)
|
60
|
+
|
61
|
+
Twitter::Streaming::MockClient.invoke(:disconnect)
|
62
|
+
subject.thread.join
|
63
|
+
|
64
|
+
expect(called).to eq(tweet)
|
57
65
|
end
|
58
66
|
end
|
59
67
|
|
60
68
|
describe "when received message" do
|
61
69
|
before do
|
62
70
|
subject.start
|
71
|
+
100.times { break if subject.thread.status == 'sleep'; sleep 0.1 }
|
63
72
|
end
|
64
73
|
|
65
74
|
it "calls on_message hook" do
|
66
75
|
called = nil
|
67
|
-
subject.on_message do |
|
68
|
-
called =
|
76
|
+
subject.on_message do |m|
|
77
|
+
called = m
|
69
78
|
end
|
70
|
-
|
71
|
-
|
79
|
+
|
80
|
+
message = Twitter::DirectMessage.new(id: 1)
|
81
|
+
Twitter::Streaming::MockClient.invoke(message)
|
82
|
+
|
83
|
+
Twitter::Streaming::MockClient.invoke(:disconnect)
|
84
|
+
subject.thread.join
|
85
|
+
|
86
|
+
expect(called).to eq(message)
|
72
87
|
end
|
73
88
|
end
|
74
89
|
|
75
90
|
describe "when received deletion" do
|
76
91
|
before do
|
77
92
|
subject.start
|
93
|
+
100.times { break if subject.thread.status == 'sleep'; sleep 0.1 }
|
78
94
|
end
|
79
95
|
|
80
96
|
it "calls on_delete hook" do
|
@@ -82,7 +98,13 @@ describe Akane::Receivers::Stream do
|
|
82
98
|
subject.on_delete do |u,t|
|
83
99
|
called = [u,t]
|
84
100
|
end
|
85
|
-
|
101
|
+
|
102
|
+
deletion = Twitter::Streaming::DeletedTweet.new(user_id: 42, id: 424242)
|
103
|
+
Twitter::Streaming::MockClient.invoke(deletion)
|
104
|
+
|
105
|
+
Twitter::Streaming::MockClient.invoke(:disconnect)
|
106
|
+
subject.thread.join
|
107
|
+
|
86
108
|
expect(called).to eq([42,424242])
|
87
109
|
end
|
88
110
|
end
|
@@ -90,6 +112,7 @@ describe Akane::Receivers::Stream do
|
|
90
112
|
describe "when received event" do
|
91
113
|
before do
|
92
114
|
subject.start
|
115
|
+
100.times { break if subject.thread.status == 'sleep'; sleep 0.1 }
|
93
116
|
end
|
94
117
|
|
95
118
|
it "calls on_event hook" do
|
@@ -97,9 +120,14 @@ describe Akane::Receivers::Stream do
|
|
97
120
|
subject.on_event do |h|
|
98
121
|
called = h
|
99
122
|
end
|
100
|
-
|
101
|
-
|
102
|
-
|
123
|
+
event = Twitter::Streaming::Event.new(event: 'favorite', source: {id: 1}, target: {id: 2}, target_object: {id: 3})
|
124
|
+
Twitter::Streaming::MockClient.invoke(event)
|
125
|
+
|
126
|
+
Twitter::Streaming::MockClient.invoke(:disconnect)
|
127
|
+
subject.thread.join
|
128
|
+
|
129
|
+
expect(called).to eq('event' => event.name, 'source' => event.source,
|
130
|
+
'target' => event.target, 'target_object' => event.target_object)
|
103
131
|
end
|
104
132
|
end
|
105
133
|
end
|
data/spec/recorder_spec.rb
CHANGED
@@ -11,26 +11,26 @@ describe Akane::Recorder do
|
|
11
11
|
|
12
12
|
describe "recording tweets" do
|
13
13
|
it "records tweet" do
|
14
|
-
storages[0].should_receive(:record_tweet).with('a', {
|
15
|
-
subject.record_tweet('a',
|
14
|
+
storages[0].should_receive(:record_tweet).with('a', {id: 42})
|
15
|
+
subject.record_tweet('a', id: 42)
|
16
16
|
subject.dequeue(true)
|
17
17
|
end
|
18
18
|
|
19
19
|
it "doesn't record tweets which already recorded recently" do
|
20
|
-
storages[0].should_receive(:record_tweet).with('a', {
|
21
|
-
storages[0].should_receive(:record_tweet).with('a', {
|
20
|
+
storages[0].should_receive(:record_tweet).with('a', {id: 40})
|
21
|
+
storages[0].should_receive(:record_tweet).with('a', {id: 42})
|
22
22
|
|
23
|
-
subject.record_tweet('a',
|
24
|
-
subject.record_tweet('a',
|
23
|
+
subject.record_tweet('a', id: 40)
|
24
|
+
subject.record_tweet('a', id: 42)
|
25
25
|
subject.dequeue(true)
|
26
26
|
subject.dequeue(true)
|
27
|
-
subject.record_tweet('a',
|
27
|
+
subject.record_tweet('a', id: 42)
|
28
28
|
subject.dequeue(true)
|
29
29
|
end
|
30
30
|
|
31
31
|
it "records retweeted tweet" do
|
32
|
-
tweet = {
|
33
|
-
retweet = {
|
32
|
+
tweet = {id: 42, text: "foo", user: {id: 1, screen_name: "a"}}
|
33
|
+
retweet = {id: 43, text: "RT @a: foo", user: {id: 2, screen_name: "b"}, retweeted_status: tweet}
|
34
34
|
storages[0].should_receive(:record_tweet).with('a', retweet)
|
35
35
|
storages[0].should_receive(:record_tweet).with('a', tweet)
|
36
36
|
subject.record_tweet('a', retweet)
|
@@ -49,18 +49,18 @@ describe Akane::Recorder do
|
|
49
49
|
|
50
50
|
describe "recording messages" do
|
51
51
|
it "records message" do
|
52
|
-
subject.record_message('foo', {
|
52
|
+
subject.record_message('foo', {id: 42})
|
53
53
|
|
54
|
-
storages[0].should_receive(:record_message).with('foo',
|
54
|
+
storages[0].should_receive(:record_message).with('foo', id: 42)
|
55
55
|
subject.dequeue(true)
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
59
|
describe "recording event" do
|
60
60
|
it "records event" do
|
61
|
-
subject.record_event('foo', {
|
61
|
+
subject.record_event('foo', {event: "favorite"})
|
62
62
|
|
63
|
-
storages[0].should_receive(:record_event).with('foo',
|
63
|
+
storages[0].should_receive(:record_event).with('foo', event: "favorite")
|
64
64
|
subject.dequeue(true)
|
65
65
|
end
|
66
66
|
end
|
@@ -72,10 +72,10 @@ describe Akane::Recorder do
|
|
72
72
|
end
|
73
73
|
|
74
74
|
it "continues dequeuing the queue" do
|
75
|
-
storages[0].should_receive(:record_tweet).with('a', {
|
76
|
-
storages[0].should_receive(:record_tweet).with('b', {
|
77
|
-
subject.record_tweet('a',
|
78
|
-
subject.record_tweet('b',
|
75
|
+
storages[0].should_receive(:record_tweet).with('a', {:id => 42})
|
76
|
+
storages[0].should_receive(:record_tweet).with('b', {:id => 43})
|
77
|
+
subject.record_tweet('a', :id => 42)
|
78
|
+
subject.record_tweet('b', :id => 43)
|
79
79
|
10.times { break if subject.queue_length.zero?; sleep 0.1 }
|
80
80
|
end
|
81
81
|
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'twitter'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
class Twitter::Streaming::MockClient
|
5
|
+
Original = Twitter::Streaming::Client
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def clients
|
9
|
+
@clients ||= []
|
10
|
+
end
|
11
|
+
|
12
|
+
def clients_mutex
|
13
|
+
@clients_mutex ||= Mutex.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_client(client)
|
17
|
+
clients_mutex.synchronize {
|
18
|
+
clients << client
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def remove_client(client)
|
23
|
+
clients_mutex.synchronize {
|
24
|
+
clients.delete client
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def invoke(*args)
|
29
|
+
clients.each do |client|
|
30
|
+
client.invoke(*args)
|
31
|
+
end
|
32
|
+
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def enable!
|
37
|
+
klass = self
|
38
|
+
Twitter::Streaming.instance_eval {
|
39
|
+
remove_const :Client
|
40
|
+
const_set :Client, klass
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def disable!
|
45
|
+
klass = Original
|
46
|
+
Twitter::Streaming.instance_eval {
|
47
|
+
remove_const :Client
|
48
|
+
const_set :Client, klass
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def initialize(options={})
|
54
|
+
@options = options
|
55
|
+
@queues = []
|
56
|
+
@mutex = Mutex.new
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_accessor :options, :hooks
|
60
|
+
|
61
|
+
def invoke(*args)
|
62
|
+
@queues.each do |queue|
|
63
|
+
queue << args
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def user(options = {}, &block)
|
68
|
+
connect(&block)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def connect
|
74
|
+
queue = Queue.new
|
75
|
+
was_empty = nil
|
76
|
+
@mutex.synchronize {
|
77
|
+
was_empty = @queues.empty?
|
78
|
+
@queues << queue
|
79
|
+
|
80
|
+
if was_empty
|
81
|
+
self.class.add_client self
|
82
|
+
end
|
83
|
+
}
|
84
|
+
while _ = queue.pop
|
85
|
+
break if _ == [:disconnect]
|
86
|
+
yield *_
|
87
|
+
end
|
88
|
+
ensure
|
89
|
+
@mutex.synchronize {
|
90
|
+
@queues.delete queue
|
91
|
+
if @queues.empty?
|
92
|
+
self.class.remove_client self
|
93
|
+
end
|
94
|
+
}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
Twitter::Streaming::MockClient.enable!
|
data/spec/util_spec.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'akane/util'
|
3
|
+
|
4
|
+
describe Akane::Util do
|
5
|
+
describe ".symbolish_hash" do
|
6
|
+
let(:target) { {'a' => 1, 'b' => 2} }
|
7
|
+
subject { described_class.symbolish_hash(target) }
|
8
|
+
|
9
|
+
it { should eq(a: 1, b: 2) }
|
10
|
+
|
11
|
+
context "with Hash" do
|
12
|
+
let(:target) { {'a' => {'b' => 2}} }
|
13
|
+
it { should eq(a: {b: 2}) }
|
14
|
+
|
15
|
+
context "with nested Hash" do
|
16
|
+
let(:target) { {'a' => {'b' => {'c' => 4}}} }
|
17
|
+
it { should eq(a: {b: {c: 4}}) }
|
18
|
+
end
|
19
|
+
|
20
|
+
context "with nested Array" do
|
21
|
+
let(:target) { {'a' => {'b' => [{'c' => 4}]}} }
|
22
|
+
it { should eq(a: {b: [{c: 4}]}) }
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with nested Array" do
|
26
|
+
let(:target) { {'a' => {'b' => [{'c' => {'d' => 5}}]}} }
|
27
|
+
it { should eq(a: {b: [{c: {d: 5}}]}) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "with Hash in Array" do
|
32
|
+
let(:target) { {'a' => [1,2,{'b' => 3}]} }
|
33
|
+
it { should eq(a: [1,2,{b: 3}]) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
metadata
CHANGED
@@ -1,43 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: akane
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shota Fukumori (sora_h)
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-01-
|
11
|
+
date: 2014-01-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: elasticsearch
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.4.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 0.4.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: twitter
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 5.5.1
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 5.5.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: oauth
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.4.7
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.4.7
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: bundler
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +136,7 @@ files:
|
|
122
136
|
- lib/akane/storages/file.rb
|
123
137
|
- lib/akane/storages/mock.rb
|
124
138
|
- lib/akane/storages/stdout.rb
|
139
|
+
- lib/akane/util.rb
|
125
140
|
- lib/akane/version.rb
|
126
141
|
- spec/config_spec.rb
|
127
142
|
- spec/manager_spec.rb
|
@@ -132,6 +147,8 @@ files:
|
|
132
147
|
- spec/storages/abstract_storage_spec.rb
|
133
148
|
- spec/storages/mock_spec.rb
|
134
149
|
- spec/support/mock_tweetstream.rb
|
150
|
+
- spec/support/mock_twitter_streaming.rb
|
151
|
+
- spec/util_spec.rb
|
135
152
|
homepage: https://github.com/sorah/akane
|
136
153
|
licenses:
|
137
154
|
- MIT
|
@@ -166,4 +183,6 @@ test_files:
|
|
166
183
|
- spec/storages/abstract_storage_spec.rb
|
167
184
|
- spec/storages/mock_spec.rb
|
168
185
|
- spec/support/mock_tweetstream.rb
|
186
|
+
- spec/support/mock_twitter_streaming.rb
|
187
|
+
- spec/util_spec.rb
|
169
188
|
has_rdoc:
|