akane 0.3.1 → 0.4.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 -2
- data/lib/akane/receivers/stream.rb +78 -1
- data/lib/akane/recorder.rb +5 -5
- data/lib/akane/storages/file.rb +7 -7
- data/lib/akane/storages/stdout.rb +3 -3
- data/lib/akane/version.rb +1 -1
- data/spec/recorder_spec.rb +24 -16
- data/spec/support/mock_twitter_streaming.rb +4 -0
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9eb63675c2d34bca45cf1a084e2cb1d2383dd57f
|
4
|
+
data.tar.gz: 0aaa3ad0e12340ebffd567ac2b805722fc7f57e4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 557db9f3bd65cf55a32f9fced460f584f89af5ce9499be9a36f87965f1f54c97c106d395d6518d5227a9c721322386599ace35c05f9e2f0684f3387bff8dec5d
|
7
|
+
data.tar.gz: 49633ba74f99864a73308fd63d4cb9aae0e6ec5a57e0e8ff36f4afebfc84ff82a5c76edc321b67c017e531e87cc7ac3800c44ea81fbdf405074e6bc5686878f6
|
data/akane.gemspec
CHANGED
@@ -18,8 +18,8 @@ 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 "elasticsearch", "
|
22
|
-
spec.add_dependency "twitter", "
|
21
|
+
spec.add_dependency "elasticsearch", ">= 0.4.1"
|
22
|
+
spec.add_dependency "twitter", ">= 5.9.0"
|
23
23
|
spec.add_dependency "oauth", ">= 0.4.7"
|
24
24
|
spec.add_dependency "sigdump"
|
25
25
|
|
@@ -1,9 +1,12 @@
|
|
1
1
|
require 'akane/receivers/abstract_receiver'
|
2
|
+
require 'socket'
|
2
3
|
require 'twitter'
|
3
4
|
|
4
5
|
module Akane
|
5
6
|
module Receivers
|
6
7
|
class Stream < AbstractReceiver
|
8
|
+
class TimeoutError < StandardError; end
|
9
|
+
|
7
10
|
def initialize(*)
|
8
11
|
super
|
9
12
|
@thread = nil
|
@@ -36,18 +39,24 @@ module Akane
|
|
36
39
|
consumer_key: @consumer[:token],
|
37
40
|
consumer_secret: @consumer[:secret],
|
38
41
|
access_token: @account[:token],
|
39
|
-
access_token_secret: @account[:secret]
|
42
|
+
access_token_secret: @account[:secret],
|
43
|
+
ssl_socket_class: CustomSSLSocketFactory.new(self),
|
40
44
|
)
|
41
45
|
end
|
42
46
|
|
43
47
|
attr_reader :thread
|
48
|
+
attr_accessor :last
|
44
49
|
|
45
50
|
def start
|
46
51
|
@logger.info "Stream : Starting"
|
47
52
|
|
53
|
+
@last = Time.now
|
54
|
+
@retry_count = 0
|
48
55
|
@thread = Thread.new do
|
49
56
|
begin
|
50
57
|
stream.send(@stream_method, @stream_options) do |obj|
|
58
|
+
@retry_count = 0
|
59
|
+
|
51
60
|
case obj
|
52
61
|
when Twitter::Tweet
|
53
62
|
invoke(:tweet, obj)
|
@@ -59,6 +68,8 @@ module Akane
|
|
59
68
|
invoke(:event,
|
60
69
|
'event' => obj.name, 'source' => obj.source,
|
61
70
|
'target' => obj.target, 'target_object' => obj.target_object)
|
71
|
+
else
|
72
|
+
next
|
62
73
|
end
|
63
74
|
end
|
64
75
|
rescue Exception => e
|
@@ -66,9 +77,53 @@ module Akane
|
|
66
77
|
@logger.error 'Error on stream'
|
67
78
|
@logger.error e.inspect
|
68
79
|
@logger.error e.backtrace
|
80
|
+
|
81
|
+
@retry_count += 1
|
82
|
+
|
83
|
+
# reconnecting https://dev.twitter.com/streaming/overview/connecting
|
84
|
+
case e
|
85
|
+
when Twitter::Error::EnhanceYourCalm # 420
|
86
|
+
interval = 5 ** @retry_count
|
87
|
+
when Twitter::Error
|
88
|
+
interval = [320, 5 ** @retry_count].min
|
89
|
+
else
|
90
|
+
interval = [16, 0.25 * @retry_count].min
|
91
|
+
end
|
92
|
+
|
93
|
+
@logger.info "stream will reconnect after #{interval} sec (retry_count=#{@retry_count})"
|
94
|
+
sleep interval
|
95
|
+
@logger.info 'stream reconnecting'
|
96
|
+
retry
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
@watchdog = Thread.new do
|
101
|
+
th = @thread
|
102
|
+
begin
|
103
|
+
loop do
|
104
|
+
break unless @thread
|
105
|
+
# @logger.debug "watchdog last #{@last} #{Time.now - @last}"
|
106
|
+
if (Time.now - @last) > 90
|
107
|
+
@last = Time.now
|
108
|
+
@logger.error 'watchdog timeout'
|
109
|
+
th.raise(TimeoutError)
|
110
|
+
end
|
111
|
+
sleep 1
|
112
|
+
end
|
113
|
+
@logger.info 'watchdog stop'
|
114
|
+
rescue Exception => e
|
115
|
+
@logger.error 'Error on watchdog'
|
116
|
+
@logger.error e.inspect
|
117
|
+
@logger.error e.backtrace
|
118
|
+
|
119
|
+
sleep 5
|
120
|
+
@logger.info 'watchdog restarting'
|
121
|
+
retry
|
69
122
|
end
|
70
123
|
end
|
71
124
|
|
125
|
+
@thread.abort_on_exception = true
|
126
|
+
|
72
127
|
self
|
73
128
|
end
|
74
129
|
|
@@ -77,6 +132,28 @@ module Akane
|
|
77
132
|
@thread = nil
|
78
133
|
self
|
79
134
|
end
|
135
|
+
|
136
|
+
class CustomSSLSocketFactory
|
137
|
+
def initialize(target)
|
138
|
+
@target = target
|
139
|
+
end
|
140
|
+
|
141
|
+
def new(*args)
|
142
|
+
OpenSSL::SSL::SSLSocket.new(*args).tap do |sock|
|
143
|
+
class << sock
|
144
|
+
def last_target=(x)
|
145
|
+
@akane_ext_last_target = x
|
146
|
+
end
|
147
|
+
def readpartial(*)
|
148
|
+
super.tap do |x|
|
149
|
+
@akane_ext_last_target.last = Time.now
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
sock.last_target = @target
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
80
157
|
end
|
81
158
|
end
|
82
159
|
end
|
data/lib/akane/recorder.rb
CHANGED
@@ -48,12 +48,12 @@ module Akane
|
|
48
48
|
|
49
49
|
def perform(action, account, *payload, raise_errors: false)
|
50
50
|
if action == :record_tweet
|
51
|
-
|
52
|
-
@recently_performed.
|
51
|
+
tweet = payload.last
|
52
|
+
return if @recently_performed[tweet.id]
|
53
|
+
@recently_performed.flag!(tweet.id)
|
53
54
|
|
54
|
-
|
55
|
-
|
56
|
-
perform(:record_tweet, account, payload.last[:retweeted_status], raise_errors: raise_errors)
|
55
|
+
if tweet.retweet?
|
56
|
+
perform(:record_tweet, account, tweet.retweeted_status, raise_errors: raise_errors)
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
data/lib/akane/storages/file.rb
CHANGED
@@ -21,10 +21,10 @@ module Akane
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def record_tweet(account, tweet)
|
24
|
-
timeline_io.puts "[#{tweet
|
25
|
-
"#{tweet
|
24
|
+
timeline_io.puts "[#{tweet.created_at.xmlschema}][#{account}] #{tweet.user.screen_name}: " \
|
25
|
+
"#{tweet.text.gsub(/\r?\n/,' ')} (#{tweet.user.id},#{tweet.id})"
|
26
26
|
|
27
|
-
tweets_io_for_user(tweet
|
27
|
+
tweets_io_for_user(tweet.user.id, tweet.user.screen_name) do |io|
|
28
28
|
io.puts tweet.attrs.to_json
|
29
29
|
end
|
30
30
|
end
|
@@ -41,12 +41,12 @@ module Akane
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def record_message(account, message)
|
44
|
-
messages_raw_io_for_user(message
|
44
|
+
messages_raw_io_for_user(message.sender.id, message.sender.screen_name) do |io|
|
45
45
|
io.puts message.attrs.to_json
|
46
46
|
end
|
47
|
-
messages_io_for_user(message
|
48
|
-
io.puts "[#{message
|
49
|
-
|
47
|
+
messages_io_for_user(message.sender.id, message.sender.screen_name) do |io|
|
48
|
+
io.puts "[#{message.created_at.xmlschema}] #{message.sender.screen_name} -> #{message.recipient.screen_name}:" \
|
49
|
+
" #{message.text} (#{message.sender.id} -> #{message.recipient.id},#{message.id})"
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
@@ -4,7 +4,7 @@ module Akane
|
|
4
4
|
module Storages
|
5
5
|
class Stdout < AbstractStorage
|
6
6
|
def record_tweet(account, tweet)
|
7
|
-
$stdout.puts "[#{account}] #{tweet
|
7
|
+
$stdout.puts "[#{account}] #{tweet.user.screen_name}: #{tweet.text}"
|
8
8
|
end
|
9
9
|
|
10
10
|
def mark_as_deleted(account, user_id, tweet_id)
|
@@ -12,11 +12,11 @@ module Akane
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def record_event(account, event)
|
15
|
-
$stdout.puts "[#{account}](EVENT) #{event
|
15
|
+
$stdout.puts "[#{account}](EVENT) #{event.event}: #{event.source.screen_name}-> #{event.target.screen_name}"
|
16
16
|
end
|
17
17
|
|
18
18
|
def record_message(account, message)
|
19
|
-
$stdout.puts "[#{account}](DM) #{message
|
19
|
+
$stdout.puts "[#{account}](DM) #{message.user.screen_name}: #{message.text}"
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
data/lib/akane/version.rb
CHANGED
data/spec/recorder_spec.rb
CHANGED
@@ -11,26 +11,29 @@ describe Akane::Recorder do
|
|
11
11
|
|
12
12
|
describe "recording tweets" do
|
13
13
|
it "records tweet" do
|
14
|
-
|
15
|
-
|
14
|
+
tweet = Twitter::Tweet.new(id: 42)
|
15
|
+
storages[0].should_receive(:record_tweet).with('a', tweet)
|
16
|
+
subject.record_tweet('a', tweet)
|
16
17
|
subject.dequeue(true)
|
17
18
|
end
|
18
19
|
|
19
20
|
it "doesn't record tweets which already recorded recently" do
|
20
|
-
|
21
|
-
|
21
|
+
tweet0 = Twitter::Tweet.new(id: 40)
|
22
|
+
tweet1 = Twitter::Tweet.new(id: 42)
|
23
|
+
storages[0].should_receive(:record_tweet).with('a', tweet0)
|
24
|
+
storages[0].should_receive(:record_tweet).with('a', tweet1)
|
22
25
|
|
23
|
-
subject.record_tweet('a',
|
24
|
-
subject.record_tweet('a',
|
26
|
+
subject.record_tweet('a', tweet0)
|
27
|
+
subject.record_tweet('a', tweet1)
|
25
28
|
subject.dequeue(true)
|
26
29
|
subject.dequeue(true)
|
27
|
-
subject.record_tweet('a',
|
30
|
+
subject.record_tweet('a', tweet1)
|
28
31
|
subject.dequeue(true)
|
29
32
|
end
|
30
33
|
|
31
34
|
it "records retweeted tweet" do
|
32
|
-
tweet =
|
33
|
-
retweet =
|
35
|
+
tweet = Twitter::Tweet.new(id: 42, text: "foo", user: {id: 1, screen_name: "a"})
|
36
|
+
retweet = Twitter::Tweet.new(id: 43, text: "RT @a: foo", user: {id: 2, screen_name: "b"}, retweeted_status: tweet.to_hash)
|
34
37
|
storages[0].should_receive(:record_tweet).with('a', retweet)
|
35
38
|
storages[0].should_receive(:record_tweet).with('a', tweet)
|
36
39
|
subject.record_tweet('a', retweet)
|
@@ -67,16 +70,19 @@ describe Akane::Recorder do
|
|
67
70
|
|
68
71
|
describe "#run" do
|
69
72
|
it "continues dequeuing the queue" do
|
70
|
-
|
71
|
-
|
73
|
+
tweet0 = Twitter::Tweet.new(id: 42)
|
74
|
+
tweet1 = Twitter::Tweet.new(id: 43)
|
75
|
+
|
76
|
+
storages[0].should_receive(:record_tweet).with('a', tweet0)
|
77
|
+
storages[0].should_receive(:record_tweet).with('b', tweet1)
|
72
78
|
|
73
79
|
@th = Thread.new { subject.run(true) }
|
74
80
|
@th.abort_on_exception = true
|
75
81
|
|
76
82
|
15.times { break if @th.status == "sleep"; sleep 0.1 }
|
77
83
|
|
78
|
-
subject.record_tweet('a',
|
79
|
-
subject.record_tweet('b',
|
84
|
+
subject.record_tweet('a', tweet0)
|
85
|
+
subject.record_tweet('b', tweet1)
|
80
86
|
|
81
87
|
15.times { break if subject.queue_length.zero?; sleep 0.1 }
|
82
88
|
@th.kill if @th && @th.alive?
|
@@ -85,16 +91,18 @@ describe Akane::Recorder do
|
|
85
91
|
|
86
92
|
describe "#stop!" do
|
87
93
|
it "stops gracefully" do
|
88
|
-
|
94
|
+
tweet = Twitter::Tweet.new(id: 40)
|
95
|
+
|
96
|
+
storages[0].should_receive(:record_tweet).with('a', tweet)
|
89
97
|
allow(storages[0]).to receive(:exitable?).and_return(true)
|
90
98
|
|
91
99
|
@th = Thread.new { subject.run(true) }
|
92
100
|
@th.abort_on_exception = true
|
93
101
|
15.times { break if @th.status == "sleep"; sleep 0.1 }
|
94
102
|
|
95
|
-
subject.record_tweet('a',
|
103
|
+
subject.record_tweet('a', tweet)
|
96
104
|
subject.stop!
|
97
|
-
subject.record_tweet('b',
|
105
|
+
subject.record_tweet('b', tweet)
|
98
106
|
|
99
107
|
15.times { break unless @th.alive?; sleep 0.1 }
|
100
108
|
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: akane
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.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: 2016-
|
11
|
+
date: 2016-10-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: elasticsearch
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
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
26
|
version: 0.4.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: twitter
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 5.
|
33
|
+
version: 5.9.0
|
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: 5.
|
40
|
+
version: 5.9.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: oauth
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|