akane 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +27 -0
- data/Rakefile +6 -0
- data/akane.gemspec +28 -0
- data/bin/akane +6 -0
- data/lib/akane.rb +5 -0
- data/lib/akane/cli.rb +83 -0
- data/lib/akane/config.rb +25 -0
- data/lib/akane/manager.rb +97 -0
- data/lib/akane/receivers/abstract_receiver.rb +47 -0
- data/lib/akane/receivers/stream.rb +63 -0
- data/lib/akane/recorder.rb +104 -0
- data/lib/akane/storages/abstract_storage.rb +26 -0
- data/lib/akane/storages/elasticsearch.rb +242 -0
- data/lib/akane/storages/file.rb +142 -0
- data/lib/akane/storages/mock.rb +51 -0
- data/lib/akane/storages/stdout.rb +23 -0
- data/lib/akane/version.rb +3 -0
- data/spec/config_spec.rb +19 -0
- data/spec/manager_spec.rb +107 -0
- data/spec/receivers/abstract_receiver_spec.rb +48 -0
- data/spec/receivers/stream_spec.rb +105 -0
- data/spec/recorder_spec.rb +86 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/storages/abstract_storage_spec.rb +20 -0
- data/spec/storages/mock_spec.rb +59 -0
- data/spec/support/mock_tweetstream.rb +86 -0
- metadata +169 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'akane/storages/abstract_storage'
|
2
|
+
|
3
|
+
module Akane
|
4
|
+
module Storages
|
5
|
+
class Mock < AbstractStorage
|
6
|
+
class << self
|
7
|
+
def recorded_tweets
|
8
|
+
@recorded_tweets ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def deletion_marks
|
12
|
+
@deletion_marks ||= []
|
13
|
+
end
|
14
|
+
|
15
|
+
def recorded_events
|
16
|
+
@recorded_events ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
def recorded_messages
|
20
|
+
@recorded_messages ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def reset!
|
24
|
+
[recorded_tweets, deletion_marks,
|
25
|
+
recorded_events, recorded_messages].each(&:clear)
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def record_tweet(account, tweet)
|
31
|
+
self.class.recorded_tweets << [account, tweet]
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def mark_as_deleted(account, user_id, tweet_id)
|
36
|
+
self.class.deletion_marks << [account, user_id, tweet_id]
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def record_event(account, event)
|
41
|
+
self.class.recorded_events << [account, event]
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def record_message(account, message)
|
46
|
+
self.class.recorded_messages << [account, message]
|
47
|
+
self
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'akane/storages/abstract_storage'
|
2
|
+
|
3
|
+
module Akane
|
4
|
+
module Storages
|
5
|
+
class Stdout < AbstractStorage
|
6
|
+
def record_tweet(account, tweet)
|
7
|
+
$stdout.puts "[#{account}] #{tweet["user"]["screen_name"]}: #{tweet["text"]}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def mark_as_deleted(account, user_id, tweet_id)
|
11
|
+
$stdout.puts "[#{account}](DELETION) #{user_id}/#{tweet_id}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def record_event(account, event)
|
15
|
+
$stdout.puts "[#{account}](EVENT) #{event["event"]}: #{event["source"]["screen_name"]}-> #{event["target"]["screen_name"]}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def record_message(account, message)
|
19
|
+
$stdout.puts "[#{account}](DM) #{message["user"]["screen_name"]}: #{message["text"]}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'logger'
|
3
|
+
require 'akane/config'
|
4
|
+
|
5
|
+
describe Akane::Config do
|
6
|
+
subject { described_class.new("log" => File::NULL, "foo" => "bar") }
|
7
|
+
|
8
|
+
describe "#logger" do
|
9
|
+
it "returns logger" do
|
10
|
+
expect(subject.logger).to be_a_kind_of(Logger)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#[]" do
|
15
|
+
it "returns from config hash" do
|
16
|
+
expect(subject["foo"]).to eq "bar"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'akane/manager'
|
4
|
+
require 'akane/receivers/stream'
|
5
|
+
require 'akane/storages/mock'
|
6
|
+
require 'akane/config'
|
7
|
+
|
8
|
+
describe Akane::Manager do
|
9
|
+
let(:conf_accounts) do
|
10
|
+
{
|
11
|
+
"a" => {"token" => "a-access-token", "secret" => "a-access-secret"},
|
12
|
+
}
|
13
|
+
end
|
14
|
+
let(:conf_storages) do
|
15
|
+
[
|
16
|
+
{ "mock" => {"a" => "b"} }
|
17
|
+
]
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:config) do
|
21
|
+
Akane::Config.new(
|
22
|
+
"consumer" => {
|
23
|
+
"token" => "consumer-token", "secret" => "consumer-secret"
|
24
|
+
},
|
25
|
+
"accounts" => conf_accounts,
|
26
|
+
"storages" => conf_storages
|
27
|
+
).tap { |_| _.stub(logger: Logger.new(nil)) }
|
28
|
+
end
|
29
|
+
|
30
|
+
subject { Akane::Manager.new(config) }
|
31
|
+
|
32
|
+
before do
|
33
|
+
EM.stub(:run) { |&block| block.call }
|
34
|
+
end
|
35
|
+
|
36
|
+
# receivers -> manager -> recorder -> storage
|
37
|
+
|
38
|
+
describe "#prepare" do
|
39
|
+
it "creates receivers" do
|
40
|
+
Akane::Receivers::Stream.should_receive(:new) \
|
41
|
+
.with(consumer: {token: 'consumer-token', secret: 'consumer-secret'},
|
42
|
+
account: {token: 'a-access-token', secret: 'a-access-secret'},
|
43
|
+
logger: config.logger) \
|
44
|
+
.and_return(double("a").as_null_object)
|
45
|
+
|
46
|
+
subject.prepare
|
47
|
+
end
|
48
|
+
|
49
|
+
it "instantiates storages" do
|
50
|
+
Akane::Storages::Mock.should_receive(:new).with(config: {"a" => "b"}, logger: config.logger)
|
51
|
+
subject.prepare
|
52
|
+
end
|
53
|
+
|
54
|
+
it "creates recorder with storages" do
|
55
|
+
storage = double("storage")
|
56
|
+
Akane::Storages::Mock.stub(new: storage)
|
57
|
+
Akane::Recorder.should_receive(:new).with([storage], logger: config.logger).and_call_original
|
58
|
+
|
59
|
+
subject.prepare
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#start" do
|
64
|
+
let(:receiver) { double("a").as_null_object }
|
65
|
+
let(:recorder) { double("recorder").as_null_object }
|
66
|
+
before do
|
67
|
+
Akane::Receivers::Stream.stub(new: receiver)
|
68
|
+
Akane::Recorder.stub(new: recorder)
|
69
|
+
|
70
|
+
@on_event, @on_tweet, @on_delete, @on_message = nil
|
71
|
+
receiver.stub(:on_event) { |&block| @on_event = block }
|
72
|
+
receiver.stub(:on_tweet) { |&block| @on_tweet = block }
|
73
|
+
receiver.stub(:on_delete) { |&block| @on_delete = block }
|
74
|
+
receiver.stub(:on_message) { |&block| @on_message = block }
|
75
|
+
|
76
|
+
subject.prepare
|
77
|
+
end
|
78
|
+
|
79
|
+
it "starts all receivers" do
|
80
|
+
receiver.should_receive(:start)
|
81
|
+
subject.start
|
82
|
+
end
|
83
|
+
|
84
|
+
it "starts all receivers" do
|
85
|
+
recorder.should_receive(:run)
|
86
|
+
subject.start
|
87
|
+
end
|
88
|
+
|
89
|
+
it "sends all to recorder" do
|
90
|
+
subject.start
|
91
|
+
|
92
|
+
recorder.should_receive(:record_tweet).with('a', "id" => 42)
|
93
|
+
recorder.should_receive(:record_event).with('a', "event" => 'favorite')
|
94
|
+
recorder.should_receive(:mark_as_deleted).with('a', 5, 420)
|
95
|
+
recorder.should_receive(:record_message).with('a', 'mes' => 'sage')
|
96
|
+
|
97
|
+
@on_tweet.call("id" => 42)
|
98
|
+
@on_event.call("event" => 'favorite')
|
99
|
+
@on_delete.call(5, 420)
|
100
|
+
@on_message.call('mes' => 'sage')
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "#run" do
|
105
|
+
it "calls setup then start"
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'akane/receivers/abstract_receiver'
|
3
|
+
|
4
|
+
describe Akane::Receivers::AbstractReceiver do
|
5
|
+
=begin
|
6
|
+
subject { described_class.new(consumer: {token: 'consumer-token', secret: 'consumer-secret'}, account: {token: '42-access-token', secret: 'access-secret'}) }
|
7
|
+
describe "#start" do
|
8
|
+
it "starts listening" do
|
9
|
+
subject.start
|
10
|
+
end
|
11
|
+
|
12
|
+
it "starts returning running? true" do
|
13
|
+
expect { subject.start } \
|
14
|
+
.to change { subject.running? } \
|
15
|
+
.from(false).to(true)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#stop" do
|
20
|
+
it "stops listening" do
|
21
|
+
subject.stop
|
22
|
+
end
|
23
|
+
|
24
|
+
it "stops returning running? true" do
|
25
|
+
subject.start
|
26
|
+
|
27
|
+
expect { subject.stop } \
|
28
|
+
.to change { subject.running? } \
|
29
|
+
.from(true).to(false)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "when received tweet" do
|
34
|
+
it "calls on_tweet hook" do
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "when received event" do
|
39
|
+
it "calls on_event hook" do
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "when error occured" do
|
44
|
+
it "calls on_error hook" do
|
45
|
+
end
|
46
|
+
end
|
47
|
+
=end
|
48
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'logger'
|
3
|
+
require 'akane/receivers/stream'
|
4
|
+
|
5
|
+
describe Akane::Receivers::Stream do
|
6
|
+
let(:config) { {} }
|
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
|
+
|
9
|
+
after(:each) do
|
10
|
+
TweetStream::MockClient.clients.clear
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#start" do
|
14
|
+
it "starts listening" do
|
15
|
+
expect { subject.start } \
|
16
|
+
.to change { TweetStream::MockClient.clients.size } \
|
17
|
+
.by(1)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "starts returning running? true" do
|
21
|
+
expect { subject.start } \
|
22
|
+
.to change { subject.running? } \
|
23
|
+
.from(false).to(true)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#stop" do
|
28
|
+
before do
|
29
|
+
subject.start
|
30
|
+
end
|
31
|
+
|
32
|
+
it "stops listening" do
|
33
|
+
expect { subject.stop } \
|
34
|
+
.to change { TweetStream::MockClient.clients.size } \
|
35
|
+
.by(-1)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "stops returning running? true" do
|
39
|
+
expect { subject.stop } \
|
40
|
+
.to change { subject.running? } \
|
41
|
+
.from(true).to(false)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "when received tweet" do
|
46
|
+
before do
|
47
|
+
subject.start
|
48
|
+
end
|
49
|
+
|
50
|
+
it "calls on_tweet hook" do
|
51
|
+
called = nil
|
52
|
+
subject.on_tweet do |tweet|
|
53
|
+
called = tweet
|
54
|
+
end
|
55
|
+
TweetStream::MockClient.invoke('timeline_status', foo: :bar)
|
56
|
+
expect(called).to eq(foo: :bar)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "when received message" do
|
61
|
+
before do
|
62
|
+
subject.start
|
63
|
+
end
|
64
|
+
|
65
|
+
it "calls on_message hook" do
|
66
|
+
called = nil
|
67
|
+
subject.on_message do |message|
|
68
|
+
called = message
|
69
|
+
end
|
70
|
+
TweetStream::MockClient.invoke('direct_message', foo: :bar)
|
71
|
+
expect(called).to eq(foo: :bar)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "when received deletion" do
|
76
|
+
before do
|
77
|
+
subject.start
|
78
|
+
end
|
79
|
+
|
80
|
+
it "calls on_delete hook" do
|
81
|
+
called = nil
|
82
|
+
subject.on_delete do |u,t|
|
83
|
+
called = [u,t]
|
84
|
+
end
|
85
|
+
TweetStream::MockClient.invoke('delete', 424242, 42)
|
86
|
+
expect(called).to eq([42,424242])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "when received event" do
|
91
|
+
before do
|
92
|
+
subject.start
|
93
|
+
end
|
94
|
+
|
95
|
+
it "calls on_event hook" do
|
96
|
+
called = nil
|
97
|
+
subject.on_event do |h|
|
98
|
+
called = h
|
99
|
+
end
|
100
|
+
TweetStream::MockClient.invoke('anything', "event" => "favorite")
|
101
|
+
TweetStream::MockClient.invoke('anything', "something" => "else")
|
102
|
+
expect(called).to eq("event" => "favorite")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'akane/recorder'
|
3
|
+
|
4
|
+
describe Akane::Recorder do
|
5
|
+
let(:storages) do
|
6
|
+
[
|
7
|
+
double("storage0")
|
8
|
+
]
|
9
|
+
end
|
10
|
+
subject { described_class.new(storages) }
|
11
|
+
|
12
|
+
describe "recording tweets" do
|
13
|
+
it "records tweet" do
|
14
|
+
storages[0].should_receive(:record_tweet).with('a', {"id" => 42})
|
15
|
+
subject.record_tweet('a', "id" => 42)
|
16
|
+
subject.dequeue(true)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "doesn't record tweets which already recorded recently" do
|
20
|
+
storages[0].should_receive(:record_tweet).with('a', {"id" => 40})
|
21
|
+
storages[0].should_receive(:record_tweet).with('a', {"id" => 42})
|
22
|
+
|
23
|
+
subject.record_tweet('a', "id" => 40)
|
24
|
+
subject.record_tweet('a', "id" => 42)
|
25
|
+
subject.dequeue(true)
|
26
|
+
subject.dequeue(true)
|
27
|
+
subject.record_tweet('a', "id" => 42)
|
28
|
+
subject.dequeue(true)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "records retweeted tweet" do
|
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
|
+
storages[0].should_receive(:record_tweet).with('a', retweet)
|
35
|
+
storages[0].should_receive(:record_tweet).with('a', tweet)
|
36
|
+
subject.record_tweet('a', retweet)
|
37
|
+
subject.dequeue(true)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "marking deletion" do
|
42
|
+
it "marks as deleted" do
|
43
|
+
subject.mark_as_deleted('foo', 1, 42)
|
44
|
+
|
45
|
+
storages[0].should_receive(:mark_as_deleted).with('foo', 1, 42)
|
46
|
+
subject.dequeue(true)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "recording messages" do
|
51
|
+
it "records message" do
|
52
|
+
subject.record_message('foo', {"id" => 42})
|
53
|
+
|
54
|
+
storages[0].should_receive(:record_message).with('foo', "id" => 42)
|
55
|
+
subject.dequeue(true)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "recording event" do
|
60
|
+
it "records event" do
|
61
|
+
subject.record_event('foo', {"event" => "favorite"})
|
62
|
+
|
63
|
+
storages[0].should_receive(:record_event).with('foo', "event" => "favorite")
|
64
|
+
subject.dequeue(true)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#run" do
|
69
|
+
before do
|
70
|
+
@th = Thread.new { subject.run(true) }
|
71
|
+
@th.abort_on_exception = true
|
72
|
+
end
|
73
|
+
|
74
|
+
it "continues dequeuing the queue" do
|
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
|
+
10.times { break if subject.queue_length.zero?; sleep 0.1 }
|
80
|
+
end
|
81
|
+
|
82
|
+
after do
|
83
|
+
@th.kill if @th && @th.alive?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|