akane 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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