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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b6e75ec5065f36b1c0c716011de7d5a27cc1dc74
4
- data.tar.gz: 592bf1058bd1b7c14adb72a9a63d155ae37ccfc6
3
+ metadata.gz: 9eb63675c2d34bca45cf1a084e2cb1d2383dd57f
4
+ data.tar.gz: 0aaa3ad0e12340ebffd567ac2b805722fc7f57e4
5
5
  SHA512:
6
- metadata.gz: 9cd8b857e19b6923561a41e5ac45823c5d18580ae30cfe918919b6793743f117159aa760326a850612973770e750ff1e2fadae21659aed954e10fbd4b15299e9
7
- data.tar.gz: b950d9ab866c3934656a9f7ce69e63c1f08872e89b2d579d5aca1d499baf67b2d8f53f78d44ed121afe3ca2312db1811232a2ff9c3fc3095d1dd376dc2a15a25
6
+ metadata.gz: 557db9f3bd65cf55a32f9fced460f584f89af5ce9499be9a36f87965f1f54c97c106d395d6518d5227a9c721322386599ace35c05f9e2f0684f3387bff8dec5d
7
+ data.tar.gz: 49633ba74f99864a73308fd63d4cb9aae0e6ec5a57e0e8ff36f4afebfc84ff82a5c76edc321b67c017e531e87cc7ac3800c44ea81fbdf405074e6bc5686878f6
@@ -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", "~> 0.4.1"
22
- spec.add_dependency "twitter", "~> 5.5.1"
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
@@ -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
- return if @recently_performed[payload.last[:id]]
52
- @recently_performed.flag!(payload.last[:id])
51
+ tweet = payload.last
52
+ return if @recently_performed[tweet.id]
53
+ @recently_performed.flag!(tweet.id)
53
54
 
54
- # WTF: Twitter::NullObject
55
- unless payload.last[:retweeted_status].nil?
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
 
@@ -21,10 +21,10 @@ module Akane
21
21
  end
22
22
 
23
23
  def record_tweet(account, 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]})"
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[:user][:id], tweet[:user][:screen_name]) do |io|
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[:sender][:id], message[:sender][:screen_name]) do |io|
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[: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]})"
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["user"]["screen_name"]}: #{tweet["text"]}"
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["event"]}: #{event["source"]["screen_name"]}-> #{event["target"]["screen_name"]}"
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["user"]["screen_name"]}: #{message["text"]}"
19
+ $stdout.puts "[#{account}](DM) #{message.user.screen_name}: #{message.text}"
20
20
  end
21
21
  end
22
22
  end
@@ -1,3 +1,3 @@
1
1
  module Akane
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -11,26 +11,29 @@ 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', {id: 42})
15
- subject.record_tweet('a', id: 42)
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
- storages[0].should_receive(:record_tweet).with('a', {id: 40})
21
- storages[0].should_receive(:record_tweet).with('a', {id: 42})
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', id: 40)
24
- subject.record_tweet('a', id: 42)
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', id: 42)
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 = {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}
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
- storages[0].should_receive(:record_tweet).with('a', {:id => 42})
71
- storages[0].should_receive(:record_tweet).with('b', {:id => 43})
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', :id => 42)
79
- subject.record_tweet('b', :id => 43)
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
- storages[0].should_receive(:record_tweet).with('a', {:id => 42})
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', :id => 42)
103
+ subject.record_tweet('a', tweet)
96
104
  subject.stop!
97
- subject.record_tweet('b', :id => 42)
105
+ subject.record_tweet('b', tweet)
98
106
 
99
107
  15.times { break unless @th.alive?; sleep 0.1 }
100
108
 
@@ -56,6 +56,10 @@ class Twitter::Streaming::MockClient
56
56
  @mutex = Mutex.new
57
57
  end
58
58
 
59
+ def ssl_socket_class=(x)
60
+ return x
61
+ end
62
+
59
63
  attr_accessor :options, :hooks
60
64
 
61
65
  def invoke(*args)
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.3.1
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-09-28 00:00:00.000000000 Z
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.5.1
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.5.1
40
+ version: 5.9.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: oauth
43
43
  requirement: !ruby/object:Gem::Requirement