twitter_with_auto_pagination 0.6.2 → 0.7.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/README.md +8 -18
- data/lib/twitter_with_auto_pagination.rb +42 -1
- data/lib/twitter_with_auto_pagination/log_subscriber.rb +2 -2
- data/lib/twitter_with_auto_pagination/rest/api.rb +31 -0
- data/lib/twitter_with_auto_pagination/rest/extension/clusters.rb +43 -0
- data/lib/twitter_with_auto_pagination/rest/extension/favoriting.rb +106 -0
- data/lib/twitter_with_auto_pagination/rest/extension/friends_and_followers.rb +131 -0
- data/lib/twitter_with_auto_pagination/rest/extension/replying.rb +90 -0
- data/lib/twitter_with_auto_pagination/rest/extension/unfollowing.rb +29 -0
- data/lib/twitter_with_auto_pagination/rest/favorites.rb +20 -0
- data/lib/twitter_with_auto_pagination/rest/friends_and_followers.rb +94 -0
- data/lib/twitter_with_auto_pagination/rest/search.rb +19 -0
- data/lib/twitter_with_auto_pagination/rest/timelines.rb +37 -0
- data/lib/twitter_with_auto_pagination/rest/uncategorized.rb +83 -0
- data/lib/twitter_with_auto_pagination/rest/users.rb +62 -0
- data/lib/twitter_with_auto_pagination/rest/utils.rb +303 -0
- data/spec/helper.rb +60 -1
- data/spec/twitter_with_auto_pagination/client_spec.rb +150 -0
- data/twitter_with_auto_pagination.gemspec +1 -1
- metadata +17 -8
- data/lib/twitter_with_auto_pagination/client.rb +0 -139
- data/lib/twitter_with_auto_pagination/existing_api.rb +0 -127
- data/lib/twitter_with_auto_pagination/new_api.rb +0 -337
- data/lib/twitter_with_auto_pagination/utils.rb +0 -303
- data/spec/twitter_with_auto_pagination_spec.rb +0 -131
@@ -1,337 +0,0 @@
|
|
1
|
-
module TwitterWithAutoPagination
|
2
|
-
module NewApi
|
3
|
-
def friends_parallelly(*args)
|
4
|
-
options = {super_operation: __method__}.merge(args.extract_options!)
|
5
|
-
_friend_ids = friend_ids(*(args + [options]))
|
6
|
-
users(_friend_ids.map { |id| id.to_i }, options)
|
7
|
-
end
|
8
|
-
|
9
|
-
def followers_parallelly(*args)
|
10
|
-
options = {super_operation: __method__}.merge(args.extract_options!)
|
11
|
-
_follower_ids = follower_ids(*(args + [options]))
|
12
|
-
users(_follower_ids.map { |id| id.to_i }, options)
|
13
|
-
end
|
14
|
-
|
15
|
-
def _fetch_parallelly(signatures) # [{method: :friends, args: ['ts_3156', ...], {...}]
|
16
|
-
result = Array.new(signatures.size)
|
17
|
-
|
18
|
-
Parallel.each_with_index(signatures, in_threads: result.size) do |signature, i|
|
19
|
-
result[i] = send(signature[:method], *signature[:args])
|
20
|
-
end
|
21
|
-
|
22
|
-
result
|
23
|
-
end
|
24
|
-
|
25
|
-
def friends_and_followers(*args)
|
26
|
-
_fetch_parallelly(
|
27
|
-
[
|
28
|
-
{method: :friends_parallelly, args: args},
|
29
|
-
{method: :followers_parallelly, args: args}])
|
30
|
-
end
|
31
|
-
|
32
|
-
def friends_followers_and_statuses(*args)
|
33
|
-
_fetch_parallelly(
|
34
|
-
[
|
35
|
-
{method: :friends_parallelly, args: args},
|
36
|
-
{method: :followers_parallelly, args: args},
|
37
|
-
{method: :user_timeline, args: args}])
|
38
|
-
end
|
39
|
-
|
40
|
-
def one_sided_friends(me = nil)
|
41
|
-
if me.nil?
|
42
|
-
friends_parallelly.to_a - followers_parallelly.to_a
|
43
|
-
elsif uid_or_screen_name?(me)
|
44
|
-
# TODO use friends_and_followers
|
45
|
-
friends_parallelly(me).to_a - followers_parallelly(me).to_a
|
46
|
-
elsif me.respond_to?(:friends) && me.respond_to?(:followers)
|
47
|
-
me.friends.to_a - me.followers.to_a
|
48
|
-
else
|
49
|
-
raise
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def one_sided_followers(me = nil)
|
54
|
-
if me.nil?
|
55
|
-
followers_parallelly.to_a - friends_parallelly.to_a
|
56
|
-
elsif uid_or_screen_name?(me)
|
57
|
-
# TODO use friends_and_followers
|
58
|
-
followers_parallelly(me).to_a - friends_parallelly(me).to_a
|
59
|
-
elsif me.respond_to?(:friends) && me.respond_to?(:followers)
|
60
|
-
me.followers.to_a - me.friends.to_a
|
61
|
-
else
|
62
|
-
raise
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def mutual_friends(me = nil)
|
67
|
-
if me.nil?
|
68
|
-
friends_parallelly.to_a & followers_parallelly.to_a
|
69
|
-
elsif uid_or_screen_name?(me)
|
70
|
-
# TODO use friends_and_followers
|
71
|
-
friends_parallelly(me).to_a & followers_parallelly(me).to_a
|
72
|
-
elsif me.respond_to?(:friends) && me.respond_to?(:followers)
|
73
|
-
me.friends.to_a & me.followers.to_a
|
74
|
-
else
|
75
|
-
raise
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def common_friends(me, you)
|
80
|
-
if uid_or_screen_name?(me) && uid_or_screen_name?(you)
|
81
|
-
friends_parallelly(me).to_a & friends_parallelly(you).to_a
|
82
|
-
elsif me.respond_to?(:friends) && you.respond_to?(:friends)
|
83
|
-
me.friends.to_a & you.friends.to_a
|
84
|
-
else
|
85
|
-
raise
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def common_followers(me, you)
|
90
|
-
if uid_or_screen_name?(me) && uid_or_screen_name?(you)
|
91
|
-
followers_parallelly(me).to_a & followers_parallelly(you).to_a
|
92
|
-
elsif me.respond_to?(:followers) && you.respond_to?(:followers)
|
93
|
-
me.followers.to_a & you.followers.to_a
|
94
|
-
else
|
95
|
-
raise
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def users_which_you_removed(pre_me, cur_me)
|
100
|
-
if uid_or_screen_name?(pre_me) && uid_or_screen_name?(cur_me)
|
101
|
-
friends_parallelly(pre_me).to_a - friends_parallelly(cur_me).to_a
|
102
|
-
elsif pre_me.respond_to?(:friends) && cur_me.respond_to?(:friends)
|
103
|
-
pre_me.friends.to_a - cur_me.friends.to_a
|
104
|
-
else
|
105
|
-
raise
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def users_who_removed_you(pre_me, cur_me)
|
110
|
-
if uid_or_screen_name?(pre_me) && uid_or_screen_name?(cur_me)
|
111
|
-
followers_parallelly(pre_me).to_a - followers_parallelly(cur_me).to_a
|
112
|
-
elsif pre_me.respond_to?(:followers) && cur_me.respond_to?(:followers)
|
113
|
-
pre_me.followers.to_a - cur_me.followers.to_a
|
114
|
-
else
|
115
|
-
raise
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def _extract_screen_names(tweets)
|
120
|
-
tweets.map do |t|
|
121
|
-
$1 if t.text =~ /^(?:\.)?@(\w+)( |\W)/ # include statuses starts with .
|
122
|
-
end.compact
|
123
|
-
end
|
124
|
-
|
125
|
-
# users which specified user is replying
|
126
|
-
# in_reply_to_user_id and in_reply_to_status_id is not used because of distinguishing mentions from replies
|
127
|
-
def users_which_you_replied_to(*args)
|
128
|
-
options = args.extract_options!
|
129
|
-
tweets =
|
130
|
-
if args.empty?
|
131
|
-
user_timeline(options)
|
132
|
-
elsif uid_or_screen_name?(args[0])
|
133
|
-
user_timeline(args[0], options)
|
134
|
-
elsif args[0].kind_of?(Array) && args[0].all? { |t| t.respond_to?(:text) }
|
135
|
-
args[0]
|
136
|
-
else
|
137
|
-
raise
|
138
|
-
end
|
139
|
-
|
140
|
-
screen_names = _extract_screen_names(tweets)
|
141
|
-
result = users(screen_names, {super_operation: __method__}.merge(options))
|
142
|
-
if options.has_key?(:uniq) && !options[:uniq]
|
143
|
-
screen_names.map { |sn| result.find { |r| r.screen_name == sn } }.compact
|
144
|
-
else
|
145
|
-
result.uniq { |r| r.id }
|
146
|
-
end
|
147
|
-
rescue Twitter::Error::NotFound => e
|
148
|
-
e.message == 'No user matches for specified terms.' ? [] : (raise e)
|
149
|
-
rescue => e
|
150
|
-
logger.warn "#{__method__} #{args.inspect} #{e.class} #{e.message}"
|
151
|
-
raise e
|
152
|
-
end
|
153
|
-
|
154
|
-
def _extract_uids(tweets)
|
155
|
-
tweets.map do |t|
|
156
|
-
t.user.id.to_i if t.text =~ /^(?:\.)?@(\w+)( |\W)/ # include statuses starts with .
|
157
|
-
end.compact
|
158
|
-
end
|
159
|
-
|
160
|
-
def _extract_users(tweets, uids)
|
161
|
-
uids.map { |uid| tweets.find { |t| t.user.id.to_i == uid.to_i } }.map { |t| t.user }.compact
|
162
|
-
end
|
163
|
-
|
164
|
-
# users which specified user is replied
|
165
|
-
# when user is login you had better to call mentions_timeline
|
166
|
-
def users_who_replied_to_you(*args)
|
167
|
-
options = args.extract_options!
|
168
|
-
|
169
|
-
result =
|
170
|
-
if args.empty? || (uid_or_screen_name?(args[0]) && authenticating_user?(args[0]))
|
171
|
-
mentions_timeline.map { |m| m.user }
|
172
|
-
else
|
173
|
-
searched_result = search('@' + user(args[0]).screen_name, options)
|
174
|
-
uids = _extract_uids(searched_result)
|
175
|
-
_extract_users(searched_result, uids)
|
176
|
-
end
|
177
|
-
|
178
|
-
if options.has_key?(:uniq) && !options[:uniq]
|
179
|
-
result
|
180
|
-
else
|
181
|
-
result.uniq { |r| r.id }
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
def _count_users_with_two_sided_threshold(users, options)
|
186
|
-
min = options.has_key?(:min) ? options[:min] : 0
|
187
|
-
max = options.has_key?(:max) ? options[:max] : 1000
|
188
|
-
users.each_with_object(Hash.new(0)) { |u, memo| memo[u.id] += 1 }.
|
189
|
-
select { |_k, v| min <= v && v <= max }.
|
190
|
-
sort_by { |_, v| -v }.to_h
|
191
|
-
end
|
192
|
-
|
193
|
-
def _extract_favorite_users(favs, options = {})
|
194
|
-
counted_value = _count_users_with_two_sided_threshold(favs.map { |t| t.user }, options)
|
195
|
-
counted_value.map do |uid, cnt|
|
196
|
-
fav = favs.find { |f| f.user.id.to_i == uid.to_i }
|
197
|
-
Array.new(cnt, fav.user)
|
198
|
-
end.flatten
|
199
|
-
end
|
200
|
-
|
201
|
-
def users_which_you_faved(*args)
|
202
|
-
options = args.extract_options!
|
203
|
-
|
204
|
-
favs =
|
205
|
-
if args.empty?
|
206
|
-
favorites(options)
|
207
|
-
elsif uid_or_screen_name?(args[0])
|
208
|
-
favorites(args[0], options)
|
209
|
-
elsif args[0].kind_of?(Array) && args[0].all? { |t| t.respond_to?(:text) }
|
210
|
-
args[0]
|
211
|
-
else
|
212
|
-
raise
|
213
|
-
end
|
214
|
-
|
215
|
-
result = _extract_favorite_users(favs, options)
|
216
|
-
if options.has_key?(:uniq) && !options[:uniq]
|
217
|
-
result
|
218
|
-
else
|
219
|
-
result.uniq { |r| r.id }
|
220
|
-
end
|
221
|
-
rescue => e
|
222
|
-
logger.warn "#{__method__} #{user.inspect} #{e.class} #{e.message}"
|
223
|
-
raise e
|
224
|
-
end
|
225
|
-
|
226
|
-
def users_who_faved_you(*args)
|
227
|
-
end
|
228
|
-
|
229
|
-
def _extract_inactive_users(users, options = {})
|
230
|
-
authorized = options.delete(:authorized)
|
231
|
-
two_weeks_ago = 2.weeks.ago.to_i
|
232
|
-
users.select do |u|
|
233
|
-
if authorized
|
234
|
-
(Time.parse(u.status.created_at).to_i < two_weeks_ago) rescue false
|
235
|
-
else
|
236
|
-
false
|
237
|
-
end
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
def close_friends(*args)
|
242
|
-
options = {uniq: false}.merge(args.extract_options!)
|
243
|
-
min_max = {
|
244
|
-
min: options.has_key?(:min) ? options.delete(:min) : 0,
|
245
|
-
max: options.has_key?(:max) ? options.delete(:max) : 1000
|
246
|
-
}
|
247
|
-
|
248
|
-
_users_which_you_replied_to, _users_who_replied_to_you, _users_which_you_faved =
|
249
|
-
if args.empty?
|
250
|
-
[users_which_you_replied_to(options), users_who_replied_to_you(options), users_which_you_faved(options)]
|
251
|
-
elsif uid_or_screen_name?(args[0])
|
252
|
-
[users_which_you_replied_to(args[0], options), users_who_replied_to_you(args[0], options), users_which_you_faved(args[0], options)]
|
253
|
-
elsif (m_names = %i(users_which_you_replied_to users_who_replied_to_you users_which_you_faved)).all? { |m_name| args[0].respond_to?(m_name) }
|
254
|
-
m_names.map { |mn| args[0].send(mn) }
|
255
|
-
else
|
256
|
-
raise
|
257
|
-
end
|
258
|
-
|
259
|
-
_users = _users_which_you_replied_to + _users_who_replied_to_you + _users_which_you_faved
|
260
|
-
return [] if _users.empty?
|
261
|
-
|
262
|
-
scores = _count_users_with_two_sided_threshold(_users, min_max)
|
263
|
-
users_which_you_replied_to_scores = _count_users_with_two_sided_threshold(_users_which_you_replied_to, min_max)
|
264
|
-
users_who_replied_to_you_scores = _count_users_with_two_sided_threshold(_users_who_replied_to_you, min_max)
|
265
|
-
users_which_you_faved_scores = _count_users_with_two_sided_threshold(_users_which_you_faved, min_max)
|
266
|
-
|
267
|
-
scores.keys.map { |uid| _users.find { |u| u.id.to_i == uid.to_i } }.
|
268
|
-
map do |u|
|
269
|
-
u[:score] = scores[u.id]
|
270
|
-
u[:users_which_you_replied_to_score] = users_which_you_replied_to_scores[u.id]
|
271
|
-
u[:users_who_replied_to_you_score] = users_who_replied_to_you_scores[u.id]
|
272
|
-
u[:users_which_you_faved_score] = users_which_you_faved_scores[u.id]
|
273
|
-
u
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
def inactive_friends(user = nil)
|
278
|
-
if user.blank?
|
279
|
-
_extract_inactive_users(friends_parallelly, authorized: true)
|
280
|
-
elsif uid_or_screen_name?(user)
|
281
|
-
authorized = authenticating_user?(user) || authorized_user?(user)
|
282
|
-
_extract_inactive_users(friends_parallelly(user), authorized: authorized)
|
283
|
-
elsif user.respond_to?(:friends)
|
284
|
-
authorized = authenticating_user?(user.uid.to_i) || authorized_user?(user.uid.to_i)
|
285
|
-
_extract_inactive_users(user.friends, authorized: authorized)
|
286
|
-
else
|
287
|
-
raise
|
288
|
-
end
|
289
|
-
end
|
290
|
-
|
291
|
-
def inactive_followers(user = nil)
|
292
|
-
if user.blank?
|
293
|
-
_extract_inactive_users(followers_parallelly, authorized: true)
|
294
|
-
elsif uid_or_screen_name?(user)
|
295
|
-
authorized = authenticating_user?(user) || authorized_user?(user)
|
296
|
-
_extract_inactive_users(followers_parallelly(user), authorized: authorized)
|
297
|
-
elsif user.respond_to?(:followers)
|
298
|
-
authorized = authenticating_user?(user.uid.to_i) || authorized_user?(user.uid.to_i)
|
299
|
-
_extract_inactive_users(user.followers, authorized: authorized)
|
300
|
-
else
|
301
|
-
raise
|
302
|
-
end
|
303
|
-
end
|
304
|
-
|
305
|
-
def clusters_belong_to(text)
|
306
|
-
return [] if text.blank?
|
307
|
-
|
308
|
-
exclude_words = JSON.parse(File.read(Rails.configuration.x.constants['cluster_bad_words_path']))
|
309
|
-
special_words = JSON.parse(File.read(Rails.configuration.x.constants['cluster_good_words_path']))
|
310
|
-
|
311
|
-
# クラスタ用の単語の出現回数を記録
|
312
|
-
cluster_word_counter =
|
313
|
-
special_words.map { |sw| [sw, text.scan(sw)] }
|
314
|
-
.delete_if { |item| item[1].empty? }
|
315
|
-
.each_with_object(Hash.new(1)) { |item, memo| memo[item[0]] = item[1].size }
|
316
|
-
|
317
|
-
# 同一文字種の繰り返しを見付ける。漢字の繰り返し、ひらがなの繰り返し、カタカナの繰り返し、など
|
318
|
-
text.scan(/[一-龠〆ヵヶ々]+|[ぁ-んー~]+|[ァ-ヴー~]+|[a-zA-Z0-9]+|[、。!!??]+/).
|
319
|
-
|
320
|
-
# 複数回繰り返される文字を除去
|
321
|
-
map { |w| w.remove /[?!?!。、w]|(ー{2,})/ }.
|
322
|
-
|
323
|
-
# 文字数の少なすぎる単語、ひらがなだけの単語、除外単語を除去する
|
324
|
-
delete_if { |w| w.length <= 1 || (w.length <= 2 && w =~ /^[ぁ-んー~]+$/) || exclude_words.include?(w) }.
|
325
|
-
|
326
|
-
# 出現回数を記録
|
327
|
-
each { |w| cluster_word_counter[w] += 1 }
|
328
|
-
|
329
|
-
# 複数個以上見付かった単語のみを残し、出現頻度順にソート
|
330
|
-
cluster_word_counter.select { |_, v| v > 3 }.sort_by { |_, v| -v }.to_h
|
331
|
-
end
|
332
|
-
|
333
|
-
def clusters_assigned_to
|
334
|
-
raise NotImplementedError.new
|
335
|
-
end
|
336
|
-
end
|
337
|
-
end
|
@@ -1,303 +0,0 @@
|
|
1
|
-
module TwitterWithAutoPagination
|
2
|
-
module Utils
|
3
|
-
# for backward compatibility
|
4
|
-
def uid
|
5
|
-
@uid || user.id.to_i
|
6
|
-
end
|
7
|
-
|
8
|
-
def __uid
|
9
|
-
ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
|
10
|
-
`TwitterWithAutoPagination::Utils##{__method__}` is deprecated.
|
11
|
-
MESSAGE
|
12
|
-
uid
|
13
|
-
end
|
14
|
-
|
15
|
-
def __uid_i
|
16
|
-
ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
|
17
|
-
`TwitterWithAutoPagination::Utils##{__method__}` is deprecated.
|
18
|
-
MESSAGE
|
19
|
-
uid
|
20
|
-
end
|
21
|
-
|
22
|
-
# for backward compatibility
|
23
|
-
def screen_name
|
24
|
-
@screen_name || user.screen_name
|
25
|
-
end
|
26
|
-
|
27
|
-
def __screen_name
|
28
|
-
ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
|
29
|
-
`TwitterWithAutoPagination::Utils##{__method__}` is deprecated.
|
30
|
-
MESSAGE
|
31
|
-
screen_name
|
32
|
-
end
|
33
|
-
|
34
|
-
def uid_or_screen_name?(object)
|
35
|
-
object.kind_of?(String) || object.kind_of?(Integer)
|
36
|
-
end
|
37
|
-
|
38
|
-
def authenticating_user?(target)
|
39
|
-
user.id.to_i == user(target).id.to_i
|
40
|
-
end
|
41
|
-
|
42
|
-
def authorized_user?(target)
|
43
|
-
target_user = user(target)
|
44
|
-
!target_user.protected? || friendship?(user.id.to_i, target_user.id.to_i)
|
45
|
-
end
|
46
|
-
|
47
|
-
def instrument(operation, key, options = nil)
|
48
|
-
payload = {operation: operation, key: key}
|
49
|
-
payload.merge!(options) if options.is_a?(Hash)
|
50
|
-
ActiveSupport::Notifications.instrument('call.twitter_with_auto_pagination', payload) { yield(payload) }
|
51
|
-
end
|
52
|
-
|
53
|
-
def call_old_method(method_name, *args)
|
54
|
-
options = args.extract_options!
|
55
|
-
begin
|
56
|
-
self.call_count += 1
|
57
|
-
_options = {method_name: method_name, call_count: self.call_count, args: args}.merge(options)
|
58
|
-
instrument('api call', args[0], _options) { send(method_name, *args, options) }
|
59
|
-
rescue Twitter::Error::TooManyRequests => e
|
60
|
-
logger.warn "#{__method__}: call=#{method_name} #{args.inspect} #{e.class} Retry after #{e.rate_limit.reset_in} seconds."
|
61
|
-
raise e
|
62
|
-
rescue Twitter::Error::ServiceUnavailable => e
|
63
|
-
logger.warn "#{__method__}: call=#{method_name} #{args.inspect} #{e.class} #{e.message}"
|
64
|
-
raise e
|
65
|
-
rescue Twitter::Error::InternalServerError => e
|
66
|
-
logger.warn "#{__method__}: call=#{method_name} #{args.inspect} #{e.class} #{e.message}"
|
67
|
-
raise e
|
68
|
-
rescue Twitter::Error::Forbidden => e
|
69
|
-
logger.warn "#{__method__}: call=#{method_name} #{args.inspect} #{e.class} #{e.message}"
|
70
|
-
raise e
|
71
|
-
rescue Twitter::Error::NotFound => e
|
72
|
-
logger.warn "#{__method__}: call=#{method_name} #{args.inspect} #{e.class} #{e.message}"
|
73
|
-
raise e
|
74
|
-
rescue => e
|
75
|
-
logger.warn "#{__method__}: call=#{method_name} #{args.inspect} #{e.class} #{e.message}"
|
76
|
-
raise e
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
# user_timeline, search
|
81
|
-
def collect_with_max_id(method_name, *args)
|
82
|
-
options = args.extract_options!
|
83
|
-
call_limit = options.delete(:call_limit) || 3
|
84
|
-
last_response = call_old_method(method_name, *args, options)
|
85
|
-
last_response = yield(last_response) if block_given?
|
86
|
-
return_data = last_response
|
87
|
-
call_count = 1
|
88
|
-
|
89
|
-
while last_response.any? && call_count < call_limit
|
90
|
-
options[:max_id] = last_response.last.kind_of?(Hash) ? last_response.last[:id] : last_response.last.id
|
91
|
-
last_response = call_old_method(method_name, *args, options)
|
92
|
-
last_response = yield(last_response) if block_given?
|
93
|
-
return_data += last_response
|
94
|
-
call_count += 1
|
95
|
-
end
|
96
|
-
|
97
|
-
return_data.flatten
|
98
|
-
end
|
99
|
-
|
100
|
-
# friends, followers
|
101
|
-
def collect_with_cursor(method_name, *args)
|
102
|
-
options = args.extract_options!
|
103
|
-
last_response = call_old_method(method_name, *args, options).attrs
|
104
|
-
return_data = (last_response[:users] || last_response[:ids])
|
105
|
-
|
106
|
-
while (next_cursor = last_response[:next_cursor]) && next_cursor != 0
|
107
|
-
options[:cursor] = next_cursor
|
108
|
-
last_response = call_old_method(method_name, *args, options).attrs
|
109
|
-
return_data += (last_response[:users] || last_response[:ids])
|
110
|
-
end
|
111
|
-
|
112
|
-
return_data
|
113
|
-
end
|
114
|
-
|
115
|
-
require 'digest/md5'
|
116
|
-
|
117
|
-
def file_cache_key(method_name, user, options = {})
|
118
|
-
delim = ':'
|
119
|
-
identifier =
|
120
|
-
case
|
121
|
-
when method_name == :verify_credentials
|
122
|
-
"object-id#{delim}#{object_id}"
|
123
|
-
when method_name == :search
|
124
|
-
"str#{delim}#{user.to_s}"
|
125
|
-
when method_name == :mentions_timeline
|
126
|
-
"#{user.kind_of?(Integer) ? 'id' : 'sn'}#{delim}#{user.to_s}"
|
127
|
-
when method_name == :home_timeline
|
128
|
-
"#{user.kind_of?(Integer) ? 'id' : 'sn'}#{delim}#{user.to_s}"
|
129
|
-
when method_name.in?([:users, :replying]) && options[:super_operation].present?
|
130
|
-
case
|
131
|
-
when user.kind_of?(Array) && user.first.kind_of?(Integer)
|
132
|
-
"#{options[:super_operation]}-ids#{delim}#{Digest::MD5.hexdigest(user.join(','))}"
|
133
|
-
when user.kind_of?(Array) && user.first.kind_of?(String)
|
134
|
-
"#{options[:super_operation]}-sns#{delim}#{Digest::MD5.hexdigest(user.join(','))}"
|
135
|
-
else raise "#{method_name.inspect} #{user.inspect}"
|
136
|
-
end
|
137
|
-
when user.kind_of?(Integer)
|
138
|
-
"id#{delim}#{user.to_s}"
|
139
|
-
when user.kind_of?(Array) && user.first.kind_of?(Integer)
|
140
|
-
"ids#{delim}#{Digest::MD5.hexdigest(user.join(','))}"
|
141
|
-
when user.kind_of?(Array) && user.first.kind_of?(String)
|
142
|
-
"sns#{delim}#{Digest::MD5.hexdigest(user.join(','))}"
|
143
|
-
when user.kind_of?(String)
|
144
|
-
"sn#{delim}#{user}"
|
145
|
-
when user.kind_of?(Twitter::User)
|
146
|
-
"user#{delim}#{user.id.to_s}"
|
147
|
-
else raise "#{method_name.inspect} #{user.inspect}"
|
148
|
-
end
|
149
|
-
|
150
|
-
"#{method_name}#{delim}#{identifier}"
|
151
|
-
end
|
152
|
-
|
153
|
-
def namespaced_key(method_name, user, options = {})
|
154
|
-
file_cache_key(method_name, user, options)
|
155
|
-
end
|
156
|
-
|
157
|
-
PROFILE_SAVE_KEYS = %i(
|
158
|
-
id
|
159
|
-
name
|
160
|
-
screen_name
|
161
|
-
location
|
162
|
-
description
|
163
|
-
url
|
164
|
-
protected
|
165
|
-
followers_count
|
166
|
-
friends_count
|
167
|
-
listed_count
|
168
|
-
favourites_count
|
169
|
-
utc_offset
|
170
|
-
time_zone
|
171
|
-
geo_enabled
|
172
|
-
verified
|
173
|
-
statuses_count
|
174
|
-
lang
|
175
|
-
status
|
176
|
-
profile_image_url_https
|
177
|
-
profile_banner_url
|
178
|
-
profile_link_color
|
179
|
-
suspended
|
180
|
-
verified
|
181
|
-
entities
|
182
|
-
created_at
|
183
|
-
)
|
184
|
-
|
185
|
-
STATUS_SAVE_KEYS = %i(
|
186
|
-
created_at
|
187
|
-
id
|
188
|
-
text
|
189
|
-
source
|
190
|
-
truncated
|
191
|
-
coordinates
|
192
|
-
place
|
193
|
-
entities
|
194
|
-
user
|
195
|
-
contributors
|
196
|
-
is_quote_status
|
197
|
-
retweet_count
|
198
|
-
favorite_count
|
199
|
-
favorited
|
200
|
-
retweeted
|
201
|
-
possibly_sensitive
|
202
|
-
lang
|
203
|
-
)
|
204
|
-
|
205
|
-
# encode
|
206
|
-
def encode_json(obj, caller_name, options = {})
|
207
|
-
options[:reduce] = true unless options.has_key?(:reduce)
|
208
|
-
case caller_name
|
209
|
-
when :user_timeline, :home_timeline, :mentions_timeline, :favorites # Twitter::Tweet
|
210
|
-
JSON.pretty_generate(obj.map { |o| o.attrs })
|
211
|
-
|
212
|
-
when :search # Hash
|
213
|
-
data =
|
214
|
-
if options[:reduce]
|
215
|
-
obj.map { |o| o.to_hash.slice(*STATUS_SAVE_KEYS) }
|
216
|
-
else
|
217
|
-
obj.map { |o| o.to_hash }
|
218
|
-
end
|
219
|
-
JSON.pretty_generate(data)
|
220
|
-
|
221
|
-
when :friends, :followers # Hash
|
222
|
-
data =
|
223
|
-
if options[:reduce]
|
224
|
-
obj.map { |o| o.to_hash.slice(*PROFILE_SAVE_KEYS) }
|
225
|
-
else
|
226
|
-
obj.map { |o| o.to_hash }
|
227
|
-
end
|
228
|
-
JSON.pretty_generate(data)
|
229
|
-
|
230
|
-
when :friend_ids, :follower_ids # Integer
|
231
|
-
JSON.pretty_generate(obj)
|
232
|
-
|
233
|
-
when :verify_credentials # Twitter::User
|
234
|
-
JSON.pretty_generate(obj.to_hash.slice(*PROFILE_SAVE_KEYS))
|
235
|
-
|
236
|
-
when :user # Twitter::User
|
237
|
-
JSON.pretty_generate(obj.to_hash.slice(*PROFILE_SAVE_KEYS))
|
238
|
-
|
239
|
-
when :users, :friends_parallelly, :followers_parallelly # Twitter::User
|
240
|
-
data =
|
241
|
-
if options[:reduce]
|
242
|
-
obj.map { |o| o.to_hash.slice(*PROFILE_SAVE_KEYS) }
|
243
|
-
else
|
244
|
-
obj.map { |o| o.to_hash }
|
245
|
-
end
|
246
|
-
JSON.pretty_generate(data)
|
247
|
-
|
248
|
-
when :user? # true or false
|
249
|
-
obj
|
250
|
-
|
251
|
-
when :friendship? # true or false
|
252
|
-
obj
|
253
|
-
|
254
|
-
else
|
255
|
-
raise "#{__method__}: caller=#{caller_name} key=#{options[:key]} obj=#{obj.inspect}"
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
# decode
|
260
|
-
def decode_json(json_str, caller_name, options = {})
|
261
|
-
obj = json_str.kind_of?(String) ? JSON.parse(json_str) : json_str
|
262
|
-
case
|
263
|
-
when obj.nil?
|
264
|
-
obj
|
265
|
-
|
266
|
-
when obj.kind_of?(Array) && obj.first.kind_of?(Hash)
|
267
|
-
obj.map { |o| Hashie::Mash.new(o) }
|
268
|
-
|
269
|
-
when obj.kind_of?(Array) && obj.first.kind_of?(Integer)
|
270
|
-
obj
|
271
|
-
|
272
|
-
when obj.kind_of?(Hash)
|
273
|
-
Hashie::Mash.new(obj)
|
274
|
-
|
275
|
-
when obj === true || obj === false
|
276
|
-
obj
|
277
|
-
|
278
|
-
when obj.kind_of?(Array) && obj.empty?
|
279
|
-
obj
|
280
|
-
|
281
|
-
else
|
282
|
-
raise "#{__method__}: caller=#{caller_name} key=#{options[:key]} obj=#{obj.inspect}"
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
def fetch_cache_or_call_api(method_name, user, options = {})
|
287
|
-
key = namespaced_key(method_name, user, options)
|
288
|
-
options.update(key: key)
|
289
|
-
|
290
|
-
data =
|
291
|
-
if options[:cache] == :read
|
292
|
-
instrument('Cache Read(Force)', key, caller: method_name) { cache.read(key) }
|
293
|
-
else
|
294
|
-
cache.fetch(key, expires_in: 1.hour, race_condition_ttl: 5.minutes) do
|
295
|
-
_d = yield
|
296
|
-
instrument('serialize', key, caller: method_name) { encode_json(_d, method_name, options) }
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
|
-
instrument('deserialize', key, caller: method_name) { decode_json(data, method_name, options) }
|
301
|
-
end
|
302
|
-
end
|
303
|
-
end
|