ex_twitter 0.1.1 → 0.2.0

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