ex_twitter 0.1.1 → 0.2.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 +0 -8
- data/ex_twitter.gemspec +1 -1
- data/lib/ex_twitter/client.rb +194 -0
- data/lib/ex_twitter/existing_api.rb +122 -0
- data/lib/ex_twitter/log_subscriber.rb +79 -0
- data/lib/ex_twitter/new_api.rb +238 -0
- data/lib/ex_twitter/utils.rb +293 -0
- data/lib/ex_twitter.rb +1 -805
- metadata +7 -4
- data/lib/ex_stream.rb +0 -68
- data/lib/ex_twitter_subscriber.rb +0 -8
@@ -0,0 +1,293 @@
|
|
1
|
+
module ExTwitter
|
2
|
+
module Utils
|
3
|
+
# for backward compatibility
|
4
|
+
def uid
|
5
|
+
user.id
|
6
|
+
end
|
7
|
+
|
8
|
+
def __uid
|
9
|
+
uid
|
10
|
+
end
|
11
|
+
|
12
|
+
def __uid_i
|
13
|
+
uid.to_i
|
14
|
+
end
|
15
|
+
|
16
|
+
# for backward compatibility
|
17
|
+
def screen_name
|
18
|
+
user.screen_name
|
19
|
+
end
|
20
|
+
|
21
|
+
def __screen_name
|
22
|
+
screen_name
|
23
|
+
end
|
24
|
+
|
25
|
+
def uid_or_screen_name?(object)
|
26
|
+
object.kind_of?(String) || object.kind_of?(Integer)
|
27
|
+
end
|
28
|
+
|
29
|
+
def authenticating_user?(target)
|
30
|
+
user.id.to_i == user(target).id.to_i
|
31
|
+
end
|
32
|
+
|
33
|
+
def authorized_user?(target)
|
34
|
+
target_user = user(target)
|
35
|
+
!target_user.protected? || friendship?(user.id.to_i, target_user.id.to_i)
|
36
|
+
end
|
37
|
+
|
38
|
+
def instrument(operation, key, options = nil)
|
39
|
+
payload = {operation: operation, key: key}
|
40
|
+
payload.merge!(options) if options.is_a?(Hash)
|
41
|
+
ActiveSupport::Notifications.instrument('call.ex_twitter', payload) { yield(payload) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def call_old_method(method_name, *args)
|
45
|
+
options = args.extract_options!
|
46
|
+
begin
|
47
|
+
self.call_count += 1
|
48
|
+
_options = {method_name: method_name, call_count: self.call_count, args: args}.merge(options)
|
49
|
+
instrument('api call', args[0], _options) { send(method_name, *args, options) }
|
50
|
+
rescue Twitter::Error::TooManyRequests => e
|
51
|
+
logger.warn "#{__method__}: call=#{method_name} #{args.inspect} #{e.class} Retry after #{e.rate_limit.reset_in} seconds."
|
52
|
+
raise e
|
53
|
+
rescue Twitter::Error::ServiceUnavailable => e
|
54
|
+
logger.warn "#{__method__}: call=#{method_name} #{args.inspect} #{e.class} #{e.message}"
|
55
|
+
raise e
|
56
|
+
rescue Twitter::Error::InternalServerError => e
|
57
|
+
logger.warn "#{__method__}: call=#{method_name} #{args.inspect} #{e.class} #{e.message}"
|
58
|
+
raise e
|
59
|
+
rescue Twitter::Error::Forbidden => e
|
60
|
+
logger.warn "#{__method__}: call=#{method_name} #{args.inspect} #{e.class} #{e.message}"
|
61
|
+
raise e
|
62
|
+
rescue Twitter::Error::NotFound => e
|
63
|
+
logger.warn "#{__method__}: call=#{method_name} #{args.inspect} #{e.class} #{e.message}"
|
64
|
+
raise e
|
65
|
+
rescue => e
|
66
|
+
logger.warn "#{__method__}: call=#{method_name} #{args.inspect} #{e.class} #{e.message}"
|
67
|
+
raise e
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# user_timeline, search
|
72
|
+
def collect_with_max_id(method_name, *args)
|
73
|
+
options = args.extract_options!
|
74
|
+
call_limit = options.delete(:call_limit) || 3
|
75
|
+
last_response = call_old_method(method_name, *args, options)
|
76
|
+
last_response = yield(last_response) if block_given?
|
77
|
+
return_data = last_response
|
78
|
+
call_count = 1
|
79
|
+
|
80
|
+
while last_response.any? && call_count < call_limit
|
81
|
+
options[:max_id] = last_response.last.kind_of?(Hash) ? last_response.last[:id] : last_response.last.id
|
82
|
+
last_response = call_old_method(method_name, *args, options)
|
83
|
+
last_response = yield(last_response) if block_given?
|
84
|
+
return_data += last_response
|
85
|
+
call_count += 1
|
86
|
+
end
|
87
|
+
|
88
|
+
return_data.flatten
|
89
|
+
end
|
90
|
+
|
91
|
+
# friends, followers
|
92
|
+
def collect_with_cursor(method_name, *args)
|
93
|
+
options = args.extract_options!
|
94
|
+
last_response = call_old_method(method_name, *args, options).attrs
|
95
|
+
return_data = (last_response[:users] || last_response[:ids])
|
96
|
+
|
97
|
+
while (next_cursor = last_response[:next_cursor]) && next_cursor != 0
|
98
|
+
options[:cursor] = next_cursor
|
99
|
+
last_response = call_old_method(method_name, *args, options).attrs
|
100
|
+
return_data += (last_response[:users] || last_response[:ids])
|
101
|
+
end
|
102
|
+
|
103
|
+
return_data
|
104
|
+
end
|
105
|
+
|
106
|
+
require 'digest/md5'
|
107
|
+
|
108
|
+
def file_cache_key(method_name, user, options = {})
|
109
|
+
delim = ':'
|
110
|
+
identifier =
|
111
|
+
case
|
112
|
+
when method_name == :search
|
113
|
+
"str#{delim}#{user.to_s}"
|
114
|
+
when method_name == :mentions_timeline
|
115
|
+
"#{user.kind_of?(Integer) ? 'id' : 'sn'}#{delim}#{user.to_s}"
|
116
|
+
when method_name == :home_timeline
|
117
|
+
"#{user.kind_of?(Integer) ? 'id' : 'sn'}#{delim}#{user.to_s}"
|
118
|
+
when method_name.in?([:users, :replying]) && options[:super_operation].present?
|
119
|
+
case
|
120
|
+
when user.kind_of?(Array) && user.first.kind_of?(Integer)
|
121
|
+
"#{options[:super_operation]}-ids#{delim}#{Digest::MD5.hexdigest(user.join(','))}"
|
122
|
+
when user.kind_of?(Array) && user.first.kind_of?(String)
|
123
|
+
"#{options[:super_operation]}-sns#{delim}#{Digest::MD5.hexdigest(user.join(','))}"
|
124
|
+
else raise "#{method_name.inspect} #{user.inspect}"
|
125
|
+
end
|
126
|
+
when user.kind_of?(Integer)
|
127
|
+
"id#{delim}#{user.to_s}"
|
128
|
+
when user.kind_of?(Array) && user.first.kind_of?(Integer)
|
129
|
+
"ids#{delim}#{Digest::MD5.hexdigest(user.join(','))}"
|
130
|
+
when user.kind_of?(Array) && user.first.kind_of?(String)
|
131
|
+
"sns#{delim}#{Digest::MD5.hexdigest(user.join(','))}"
|
132
|
+
when user.kind_of?(String)
|
133
|
+
"sn#{delim}#{user}"
|
134
|
+
when user.kind_of?(Twitter::User)
|
135
|
+
"user#{delim}#{user.id.to_s}"
|
136
|
+
else raise "#{method_name.inspect} #{user.inspect}"
|
137
|
+
end
|
138
|
+
|
139
|
+
"#{method_name}#{delim}#{identifier}"
|
140
|
+
end
|
141
|
+
|
142
|
+
def namespaced_key(method_name, user, options = {})
|
143
|
+
file_cache_key(method_name, user, options)
|
144
|
+
end
|
145
|
+
|
146
|
+
PROFILE_SAVE_KEYS = %i(
|
147
|
+
id
|
148
|
+
name
|
149
|
+
screen_name
|
150
|
+
location
|
151
|
+
description
|
152
|
+
url
|
153
|
+
protected
|
154
|
+
followers_count
|
155
|
+
friends_count
|
156
|
+
listed_count
|
157
|
+
favourites_count
|
158
|
+
utc_offset
|
159
|
+
time_zone
|
160
|
+
geo_enabled
|
161
|
+
verified
|
162
|
+
statuses_count
|
163
|
+
lang
|
164
|
+
status
|
165
|
+
profile_image_url_https
|
166
|
+
profile_banner_url
|
167
|
+
profile_link_color
|
168
|
+
suspended
|
169
|
+
verified
|
170
|
+
entities
|
171
|
+
created_at
|
172
|
+
)
|
173
|
+
|
174
|
+
STATUS_SAVE_KEYS = %i(
|
175
|
+
created_at
|
176
|
+
id
|
177
|
+
text
|
178
|
+
source
|
179
|
+
truncated
|
180
|
+
coordinates
|
181
|
+
place
|
182
|
+
entities
|
183
|
+
user
|
184
|
+
contributors
|
185
|
+
is_quote_status
|
186
|
+
retweet_count
|
187
|
+
favorite_count
|
188
|
+
favorited
|
189
|
+
retweeted
|
190
|
+
possibly_sensitive
|
191
|
+
lang
|
192
|
+
)
|
193
|
+
|
194
|
+
# encode
|
195
|
+
def encode_json(obj, caller_name, options = {})
|
196
|
+
options[:reduce] = true unless options.has_key?(:reduce)
|
197
|
+
result =
|
198
|
+
case caller_name
|
199
|
+
when :user_timeline, :home_timeline, :mentions_timeline, :favorites # Twitter::Tweet
|
200
|
+
JSON.pretty_generate(obj.map { |o| o.attrs })
|
201
|
+
|
202
|
+
when :search # Hash
|
203
|
+
data =
|
204
|
+
if options[:reduce]
|
205
|
+
obj.map { |o| o.to_hash.slice(*STATUS_SAVE_KEYS) }
|
206
|
+
else
|
207
|
+
obj.map { |o| o.to_hash }
|
208
|
+
end
|
209
|
+
JSON.pretty_generate(data)
|
210
|
+
|
211
|
+
when :friends, :followers # Hash
|
212
|
+
data =
|
213
|
+
if options[:reduce]
|
214
|
+
obj.map { |o| o.to_hash.slice(*PROFILE_SAVE_KEYS) }
|
215
|
+
else
|
216
|
+
obj.map { |o| o.to_hash }
|
217
|
+
end
|
218
|
+
JSON.pretty_generate(data)
|
219
|
+
|
220
|
+
when :friend_ids, :follower_ids # Integer
|
221
|
+
JSON.pretty_generate(obj)
|
222
|
+
|
223
|
+
when :user # Twitter::User
|
224
|
+
JSON.pretty_generate(obj.to_hash.slice(*PROFILE_SAVE_KEYS))
|
225
|
+
|
226
|
+
when :users, :friends_parallelly, :followers_parallelly # Twitter::User
|
227
|
+
data =
|
228
|
+
if options[:reduce]
|
229
|
+
obj.map { |o| o.to_hash.slice(*PROFILE_SAVE_KEYS) }
|
230
|
+
else
|
231
|
+
obj.map { |o| o.to_hash }
|
232
|
+
end
|
233
|
+
JSON.pretty_generate(data)
|
234
|
+
|
235
|
+
when :user? # true or false
|
236
|
+
obj
|
237
|
+
|
238
|
+
when :friendship? # true or false
|
239
|
+
obj
|
240
|
+
|
241
|
+
else
|
242
|
+
raise "#{__method__}: caller=#{caller_name} key=#{options[:key]} obj=#{obj.inspect}"
|
243
|
+
end
|
244
|
+
result
|
245
|
+
end
|
246
|
+
|
247
|
+
# decode
|
248
|
+
def decode_json(json_str, caller_name, options = {})
|
249
|
+
obj = json_str.kind_of?(String) ? JSON.parse(json_str) : json_str
|
250
|
+
result =
|
251
|
+
case
|
252
|
+
when obj.nil?
|
253
|
+
obj
|
254
|
+
|
255
|
+
when obj.kind_of?(Array) && obj.first.kind_of?(Hash)
|
256
|
+
obj.map { |o| Hashie::Mash.new(o) }
|
257
|
+
|
258
|
+
when obj.kind_of?(Array) && obj.first.kind_of?(Integer)
|
259
|
+
obj
|
260
|
+
|
261
|
+
when obj.kind_of?(Hash)
|
262
|
+
Hashie::Mash.new(obj)
|
263
|
+
|
264
|
+
when obj === true || obj === false
|
265
|
+
obj
|
266
|
+
|
267
|
+
when obj.kind_of?(Array) && obj.empty?
|
268
|
+
obj
|
269
|
+
|
270
|
+
else
|
271
|
+
raise "#{__method__}: caller=#{caller_name} key=#{options[:key]} obj=#{obj.inspect}"
|
272
|
+
end
|
273
|
+
result
|
274
|
+
end
|
275
|
+
|
276
|
+
def fetch_cache_or_call_api(method_name, user, options = {})
|
277
|
+
key = namespaced_key(method_name, user, options)
|
278
|
+
options.update(key: key)
|
279
|
+
|
280
|
+
data =
|
281
|
+
if options[:cache] == :read
|
282
|
+
instrument('Cache Read(Force)', key, caller: method_name) { cache.read(key) }
|
283
|
+
else
|
284
|
+
cache.fetch(key, expires_in: 1.hour, race_condition_ttl: 5.minutes) do
|
285
|
+
_d = yield
|
286
|
+
instrument('serialize', key, caller: method_name) { encode_json(_d, method_name, options) }
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
instrument('deserialize', key, caller: method_name) { decode_json(data, method_name, options) }
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|