akane 0.0.1
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/.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
|