ex_twitter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.md ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2014 Shinohara Teruki
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ ex-twitter
2
+ ==========
3
+
4
+ A Ruby wrapper to the Twitter gem.
5
+
6
+ ## Installation
7
+
8
+ ### Gem
9
+
10
+ ```
11
+ gem install ex_twitter
12
+ ```
13
+
14
+ ### Rails
15
+
16
+ Add ex_twitter to your Gemfile, and bundle.
17
+
18
+ ## Features
19
+
20
+ This gem is a thin wrapper of Twitter gem.
21
+ Twitter gem has twitter API like methods.
22
+ This gem has high functionality methods and don't raise exceptions.
23
+
24
+ ## Examples
25
+
26
+ ```
27
+ require 'ex_twitter'
28
+ client = ExTwitter.new(config)
29
+
30
+ # get all tweets
31
+ client.get_all_tweets
32
+
33
+ # get all friend ids
34
+ client.get_all_friends_ids
35
+
36
+ # get all friends in parallel
37
+ client.get_users(friend_ids)
38
+ ```
39
+
data/Rakefile ADDED
File without changes
@@ -0,0 +1,22 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.add_dependency 'twitter'
6
+ spec.add_dependency 'activesupport'
7
+ spec.add_dependency 'parallel'
8
+ spec.add_development_dependency 'bundler'
9
+ spec.authors = ['Shinohara Teruki']
10
+ spec.description = %q(A wrapper to the Twitter gem.)
11
+ spec.email = %w[ts_3156@yahoo.co.jp]
12
+ spec.files = %w[LICENSE.md README.md Rakefile ex_twitter.gemspec]
13
+ spec.files += Dir.glob('lib/**/*.rb')
14
+ spec.files += Dir.glob('spec/**/*')
15
+ spec.homepage = 'http://github.com/ts-3156/ex-twitter/'
16
+ spec.licenses = %w[MIT]
17
+ spec.name = 'ex_twitter'
18
+ spec.require_paths = %w[lib]
19
+ spec.summary = spec.description
20
+ spec.test_files = Dir.glob('spec/**/*')
21
+ spec.version = '0.0.1'
22
+ end
data/lib/ex_stream.rb ADDED
@@ -0,0 +1,68 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'twitter'
3
+ require 'yaml'
4
+ require 'active_support'
5
+ require 'parallel'
6
+
7
+ # extended twitter
8
+ class ExStream < Twitter::Streaming::Client
9
+ attr_accessor :cache, :cache_expires_in
10
+
11
+ MAX_ATTEMPTS = 1
12
+ WAIT = false
13
+ CACHE_EXPIRES_IN = 300
14
+
15
+ def initialize(config={})
16
+ self.cache_expires_in = (config[:cache_expires_in] || CACHE_EXPIRES_IN)
17
+ self.cache = ActiveSupport::Cache::FileStore.new(File.join(Dir::pwd, 'ex_twitter_cache'),
18
+ {expires_in: self.cache_expires_in, race_condition_ttl: self.cache_expires_in})
19
+ super
20
+ end
21
+
22
+ def print_filter_stream(topics)
23
+ filter(track: topics.join(",")) do |object|
24
+ puts object.text if object.is_a?(Twitter::Tweet)
25
+ end
26
+ end
27
+
28
+ def print_sample_stream
29
+ sample do |object|
30
+ puts object.text if object.is_a?(Twitter::Tweet)
31
+ end
32
+ end
33
+
34
+ # An object may be one of the following:
35
+ # Twitter::DirectMessage
36
+ # Twitter::Streaming::DeletedTweet
37
+ # Twitter::Streaming::Event
38
+ # Twitter::Streaming::FriendList
39
+ # Twitter::Streaming::StallWarning
40
+ # Twitter::Tweet
41
+ def print_user_stream
42
+ user do |object|
43
+ case object
44
+ when Twitter::Tweet
45
+ puts "It's a tweet!"
46
+ when Twitter::DirectMessage
47
+ puts "It's a direct message!"
48
+ when Twitter::Streaming::StallWarning
49
+ warn "Falling behind!"
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ if __FILE__ == $0
56
+ puts '--start--'
57
+ yml_config = YAML.load_file('config.yml')
58
+ config = {
59
+ consumer_key: yml_config['consumer_key'],
60
+ consumer_secret: yml_config['consumer_secret'],
61
+ access_token: yml_config['access_token'],
62
+ access_token_secret: yml_config['access_token_secret']
63
+ }
64
+ client = ExStream.new(config)
65
+ client.print_sample_stream
66
+ end
67
+
68
+
data/lib/ex_twitter.rb ADDED
@@ -0,0 +1,378 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'twitter'
3
+ require 'yaml'
4
+ require 'active_support'
5
+ require 'parallel'
6
+
7
+ # extended twitter
8
+ class ExTwitter < Twitter::REST::Client
9
+ attr_accessor :cache, :cache_expires_in
10
+
11
+ MAX_ATTEMPTS = 1
12
+ WAIT = false
13
+ CACHE_EXPIRES_IN = 300
14
+
15
+ def initialize(config={})
16
+ self.cache_expires_in = (config[:cache_expires_in] || CACHE_EXPIRES_IN)
17
+ self.cache = ActiveSupport::Cache::FileStore.new(File.join(Dir::pwd, 'ex_twitter_cache'),
18
+ {expires_in: self.cache_expires_in, race_condition_ttl: self.cache_expires_in})
19
+ super
20
+ end
21
+
22
+ def read(key)
23
+ self.cache.read(key)
24
+ rescue => e
25
+ puts "in read #{key} #{e.inspect}"
26
+ nil
27
+ end
28
+
29
+ def write(key, value)
30
+ self.cache.write(key, value)
31
+ rescue => e
32
+ puts "in write #{key} #{value} #{e.inspect}"
33
+ false
34
+ end
35
+
36
+ def collect_with_max_id(collection=[], max_id=nil, &block)
37
+ response = yield(max_id)
38
+ return response unless response[1].nil?
39
+
40
+ collection += response[0]
41
+ response[0].empty? ? [collection.flatten, nil] : collect_with_max_id(collection, response[0].last.id - 1, &block)
42
+ end
43
+
44
+ def collect_with_cursor(collection=[], cursor=-1, &block)
45
+ response = yield(cursor)
46
+ return response unless response[1].nil?
47
+
48
+ collection += (response[0][:users] || response[0][:ids])
49
+ next_cursor = response[0][:next_cursor]
50
+ next_cursor == 0 ? [collection.flatten, nil] : collect_with_cursor(collection, next_cursor, &block)
51
+ end
52
+
53
+ def get_latest_200_tweets(user=nil)
54
+ num_attempts = 0
55
+ options = {count: 200, include_rts: true}
56
+ begin
57
+ num_attempts += 1
58
+ [user_timeline(user, options), nil]
59
+ rescue Twitter::Error::TooManyRequests => e
60
+ if num_attempts <= MAX_ATTEMPTS
61
+ if WAIT
62
+ sleep e.rate_limit.reset_in
63
+ retry
64
+ else
65
+ puts "retry #{e.rate_limit.reset_in} seconds later"
66
+ [[], e]
67
+ end
68
+ else
69
+ puts "fail. num_attempts > MAX_ATTEMPTS(=#{MAX_ATTEMPTS})"
70
+ [[], e]
71
+ end
72
+ rescue => e
73
+ if num_attempts <= MAX_ATTEMPTS
74
+ retry
75
+ else
76
+ puts "fail. num_attempts > MAX_ATTEMPTS(=#{MAX_ATTEMPTS}), something error #{e.inspect}"
77
+ [[], e]
78
+ end
79
+ end
80
+ end
81
+
82
+ def get_all_tweets(user=nil)
83
+ num_attempts = 0
84
+ collect_with_max_id do |max_id|
85
+ options = {count: 200, include_rts: true}
86
+ options[:max_id] = max_id unless max_id.nil?
87
+ begin
88
+ num_attempts += 1
89
+ [user_timeline(user, options), nil]
90
+ rescue Twitter::Error::TooManyRequests => e
91
+ if num_attempts <= MAX_ATTEMPTS
92
+ if WAIT
93
+ sleep e.rate_limit.reset_in
94
+ retry
95
+ else
96
+ puts "retry #{e.rate_limit.reset_in} seconds later"
97
+ [[], e]
98
+ end
99
+ else
100
+ puts "fail. num_attempts > MAX_ATTEMPTS(=#{MAX_ATTEMPTS})"
101
+ [[], e]
102
+ end
103
+ rescue => e
104
+ if num_attempts <= MAX_ATTEMPTS
105
+ retry
106
+ else
107
+ puts "fail. num_attempts > MAX_ATTEMPTS(=#{MAX_ATTEMPTS}), something error #{e.inspect}"
108
+ [[], e]
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ def get_all_friends(user=nil)
115
+ num_attempts = 0
116
+ collect_with_cursor do |cursor|
117
+ options = {count: 200, include_user_entities: true}
118
+ options[:cursor] = cursor unless cursor.nil?
119
+ cache_key = "#{self.class.name}:#{__callee__}:#{user}:#{options}"
120
+
121
+ cache = self.read(cache_key)
122
+ next [cache, nil] unless cache.nil?
123
+ begin
124
+ num_attempts += 1
125
+ object = friends(user, options)
126
+ self.write(cache_key, object.attrs)
127
+ [object.attrs, nil]
128
+ rescue Twitter::Error::TooManyRequests => e
129
+ if num_attempts <= MAX_ATTEMPTS
130
+ if WAIT
131
+ sleep e.rate_limit.reset_in
132
+ retry
133
+ else
134
+ puts "retry #{e.rate_limit.reset_in} seconds later"
135
+ [{}, e]
136
+ end
137
+ else
138
+ puts "fail. num_attempts > MAX_ATTEMPTS(=#{MAX_ATTEMPTS})"
139
+ [{}, e]
140
+ end
141
+ rescue => e
142
+ if num_attempts <= MAX_ATTEMPTS
143
+ retry
144
+ else
145
+ puts "fail. num_attempts > MAX_ATTEMPTS(=#{MAX_ATTEMPTS}), something error #{e.inspect}"
146
+ [{}, e]
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ def get_all_followers(user=nil)
153
+ num_attempts = 0
154
+ collect_with_cursor do |cursor|
155
+ options = {count: 200, include_user_entities: true}
156
+ options[:cursor] = cursor unless cursor.nil?
157
+ cache_key = "#{self.class.name}:#{__callee__}:#{user}:#{options}"
158
+
159
+ cache = self.read(cache_key)
160
+ next [cache, nil] unless cache.nil?
161
+ begin
162
+ num_attempts += 1
163
+ object = followers(user, options)
164
+ self.write(cache_key, object.attrs)
165
+ [object.attrs, nil]
166
+ rescue Twitter::Error::TooManyRequests => e
167
+ if num_attempts <= MAX_ATTEMPTS
168
+ if WAIT
169
+ sleep e.rate_limit.reset_in
170
+ retry
171
+ else
172
+ puts "retry #{e.rate_limit.reset_in} seconds later"
173
+ [{}, e]
174
+ end
175
+ else
176
+ puts "fail. num_attempts > MAX_ATTEMPTS(=#{MAX_ATTEMPTS})"
177
+ [{}, e]
178
+ end
179
+ rescue => e
180
+ if num_attempts <= MAX_ATTEMPTS
181
+ retry
182
+ else
183
+ puts "fail. num_attempts > MAX_ATTEMPTS(=#{MAX_ATTEMPTS}), something error #{e.inspect}"
184
+ [{}, e]
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ def get_all_friend_ids(user=nil)
191
+ num_attempts = 0
192
+ collect_with_cursor do |cursor|
193
+ options = {count: 5000}
194
+ options[:cursor] = cursor unless cursor.nil?
195
+ cache_key = "#{self.class.name}:#{__callee__}:#{user}:#{options}"
196
+
197
+ cache = self.read(cache_key)
198
+ next [cache, nil] unless cache.nil?
199
+ begin
200
+ num_attempts += 1
201
+ object = friend_ids(user, options)
202
+ self.write(cache_key, object.attrs)
203
+ [object.attrs, nil]
204
+ rescue Twitter::Error::TooManyRequests => e
205
+ if num_attempts <= MAX_ATTEMPTS
206
+ if WAIT
207
+ sleep e.rate_limit.reset_in
208
+ retry
209
+ else
210
+ puts "retry #{e.rate_limit.reset_in} seconds later"
211
+ [{}, e]
212
+ end
213
+ else
214
+ puts "fail. num_attempts > MAX_ATTEMPTS(=#{MAX_ATTEMPTS})"
215
+ [{}, e]
216
+ end
217
+ rescue => e
218
+ if num_attempts <= MAX_ATTEMPTS
219
+ retry
220
+ else
221
+ puts "fail. num_attempts > MAX_ATTEMPTS(=#{MAX_ATTEMPTS}), something error #{e.inspect}"
222
+ [{}, e]
223
+ end
224
+ end
225
+ end
226
+ end
227
+
228
+ def get_all_follower_ids(user=nil)
229
+ num_attempts = 0
230
+ collect_with_cursor do |cursor|
231
+ options = {count: 5000}
232
+ options[:cursor] = cursor unless cursor.nil?
233
+ cache_key = "#{self.class.name}:#{__callee__}:#{user}:#{options}"
234
+
235
+ cache = self.read(cache_key)
236
+ next [cache, nil] unless cache.nil?
237
+ begin
238
+ num_attempts += 1
239
+ object = follower_ids(user, options)
240
+ self.write(cache_key, object.attrs)
241
+ [object.attrs, nil]
242
+ rescue Twitter::Error::TooManyRequests => e
243
+ if num_attempts <= MAX_ATTEMPTS
244
+ if WAIT
245
+ sleep e.rate_limit.reset_in
246
+ retry
247
+ else
248
+ puts "retry #{e.rate_limit.reset_in} seconds later"
249
+ [{}, e]
250
+ end
251
+ else
252
+ puts "fail. num_attempts > MAX_ATTEMPTS(=#{MAX_ATTEMPTS})"
253
+ [{}, e]
254
+ end
255
+ rescue => e
256
+ if num_attempts <= MAX_ATTEMPTS
257
+ retry
258
+ else
259
+ puts "fail. num_attempts > MAX_ATTEMPTS(=#{MAX_ATTEMPTS}), something error #{e.inspect}"
260
+ [{}, e]
261
+ end
262
+ end
263
+ end
264
+ end
265
+
266
+ def get_users(ids)
267
+ ids_per_worker = []
268
+ while(ids.size > 0)
269
+ ids_per_worker << ids.slice!(0, [100, ids.size].min)
270
+ end
271
+
272
+ num_attempts = 0
273
+ processed_users = []
274
+ Parallel.each_with_index(ids_per_worker, in_threads: ids_per_worker.size) do |ids, i|
275
+ cache_key = "#{self.class.name}:#{__callee__}:#{i}:#{ids}"
276
+ cache = self.read(cache_key)
277
+ if cache.nil?
278
+ begin
279
+ num_attempts += 1
280
+ object = {i: i, users: users(ids)}
281
+ self.write(cache_key, object)
282
+ processed_users << object
283
+ rescue Twitter::Error::TooManyRequests => e
284
+ if num_attempts <= MAX_ATTEMPTS
285
+ if WAIT
286
+ sleep e.rate_limit.reset_in
287
+ retry
288
+ else
289
+ puts "retry #{e.rate_limit.reset_in} seconds later"
290
+ {i: i, users: [], e: e}
291
+ end
292
+ else
293
+ puts "fail. num_attempts > MAX_ATTEMPTS(=#{MAX_ATTEMPTS})"
294
+ {i: i, users: [], e: e}
295
+ end
296
+ rescue => e
297
+ if num_attempts <= MAX_ATTEMPTS
298
+ retry
299
+ else
300
+ puts "fail. num_attempts > MAX_ATTEMPTS(=#{MAX_ATTEMPTS}), something error #{e.inspect}"
301
+ {i: i, users: [], e: e}
302
+ end
303
+ end
304
+ else
305
+ processed_users << cache
306
+ end
307
+ end
308
+ # TODO remove if users have error, or raise
309
+ processed_users.sort_by{|p|p[:i]}.map{|p|p[:users]}.flatten
310
+ end
311
+
312
+ # mentions_timeline is to fetch the timeline of Tweets mentioning the authenticated user
313
+ # get_mentions is to fetch the Tweets mentioning the screen_name's user
314
+ def get_mentions(screen_name)
315
+ search_tweets("to:#{screen_name}", {result_type: 'recent', count: 100})
316
+ end
317
+
318
+ def search_japanese_tweets(str)
319
+ search_tweets(str, {result_type: 'recent', count: 100, lang: 'ja'})
320
+ end
321
+
322
+ def search_tweets_except_rt(str)
323
+ search_tweets("#{str} -rt", {result_type: 'recent', count: 100})
324
+ end
325
+
326
+ def search_tweets(str, options)
327
+ num_attempts = 0
328
+ begin
329
+ num_attempts += 1
330
+ result = search(str, options)
331
+ [result.take(100), nil]
332
+ rescue Twitter::Error::TooManyRequests => e
333
+ if num_attempts <= MAX_ATTEMPTS
334
+ if WAIT
335
+ sleep e.rate_limit.reset_in
336
+ retry
337
+ else
338
+ puts "retry #{e.rate_limit.reset_in} seconds later"
339
+ [[], e]
340
+ end
341
+ else
342
+ puts "fail. num_attempts > MAX_ATTEMPTS(=#{MAX_ATTEMPTS})"
343
+ [[], e]
344
+ end
345
+ rescue => e
346
+ if num_attempts <= MAX_ATTEMPTS
347
+ retry
348
+ else
349
+ puts "fail. num_attempts > MAX_ATTEMPTS(=#{MAX_ATTEMPTS}), something error #{e.inspect}"
350
+ [[], e]
351
+ end
352
+ end
353
+ end
354
+ end
355
+
356
+ if __FILE__ == $0
357
+ puts '--start--'
358
+ yml_config = YAML.load_file('config.yml')
359
+ config = {
360
+ consumer_key: yml_config['consumer_key'],
361
+ consumer_secret: yml_config['consumer_secret'],
362
+ access_token: yml_config['access_token'],
363
+ access_token_secret: yml_config['access_token_secret']
364
+ }
365
+ client = ExTwitter.new(config)
366
+ #followers, error = client.get_all_followers
367
+ #puts "#{followers.size} #{error.inspect}"
368
+ tweets, error = client.search_tweets_except_rt('#グラドル自画撮り部')
369
+ puts tweets.size
370
+ tweets.each do |t|
371
+ next if !t.media?
372
+ puts t.text
373
+ puts t.media[0].attrs
374
+ puts '----'
375
+ end
376
+ end
377
+
378
+
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ex_twitter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Shinohara Teruki
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-03-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: twitter
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: activesupport
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: parallel
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: bundler
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: A wrapper to the Twitter gem.
79
+ email:
80
+ - ts_3156@yahoo.co.jp
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - LICENSE.md
86
+ - README.md
87
+ - Rakefile
88
+ - ex_twitter.gemspec
89
+ - lib/ex_twitter.rb
90
+ - lib/ex_stream.rb
91
+ homepage: http://github.com/ts-3156/ex-twitter/
92
+ licenses:
93
+ - MIT
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 1.8.24
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: A wrapper to the Twitter gem.
116
+ test_files: []