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.
@@ -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