twitter 4.3.0 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/.yardopts +10 -0
  2. data/CHANGELOG.md +123 -110
  3. data/CONTRIBUTING.md +51 -0
  4. data/README.md +17 -2
  5. data/lib/twitter/api/direct_messages.rb +3 -6
  6. data/lib/twitter/api/favorites.rb +6 -13
  7. data/lib/twitter/api/friends_and_followers.rb +76 -8
  8. data/lib/twitter/api/lists.rb +65 -36
  9. data/lib/twitter/api/places_and_geo.rb +3 -3
  10. data/lib/twitter/api/saved_searches.rb +2 -2
  11. data/lib/twitter/api/spam_reporting.rb +2 -2
  12. data/lib/twitter/api/suggested_users.rb +1 -1
  13. data/lib/twitter/api/timelines.rb +12 -8
  14. data/lib/twitter/api/tweets.rb +8 -12
  15. data/lib/twitter/api/undocumented.rb +3 -3
  16. data/lib/twitter/api/users.rb +16 -10
  17. data/lib/twitter/api/utils.rb +109 -30
  18. data/lib/twitter/base.rb +15 -5
  19. data/lib/twitter/basic_user.rb +0 -1
  20. data/lib/twitter/client.rb +16 -37
  21. data/lib/twitter/core_ext/enumerable.rb +2 -2
  22. data/lib/twitter/default.rb +5 -8
  23. data/lib/twitter/exceptable.rb +36 -0
  24. data/lib/twitter/factory.rb +4 -4
  25. data/lib/twitter/list.rb +0 -1
  26. data/lib/twitter/request/multipart_with_file.rb +5 -7
  27. data/lib/twitter/search_results.rb +10 -6
  28. data/lib/twitter/settings.rb +0 -1
  29. data/lib/twitter/source_user.rb +0 -7
  30. data/lib/twitter/target_user.rb +0 -1
  31. data/lib/twitter/tweet.rb +15 -17
  32. data/lib/twitter/user.rb +4 -14
  33. data/lib/twitter/version.rb +4 -4
  34. data/spec/fixtures/followers_list.json +1 -0
  35. data/spec/fixtures/friends_list.json +1 -0
  36. data/spec/fixtures/ids_list.json +1 -1
  37. data/spec/fixtures/ids_list2.json +1 -1
  38. data/spec/helper.rb +9 -8
  39. data/spec/twitter/action_factory_spec.rb +1 -1
  40. data/spec/twitter/api/favorites_spec.rb +2 -2
  41. data/spec/twitter/api/friends_and_followers_spec.rb +102 -2
  42. data/spec/twitter/api/spam_reporting_spec.rb +2 -2
  43. data/spec/twitter/api/tweets_spec.rb +2 -2
  44. data/spec/twitter/api/users_spec.rb +107 -49
  45. data/spec/twitter/base_spec.rb +1 -1
  46. data/spec/twitter/client_spec.rb +4 -4
  47. data/spec/twitter/cursor_spec.rb +2 -2
  48. data/spec/twitter/error/client_error_spec.rb +16 -5
  49. data/spec/twitter/error/server_error_spec.rb +1 -1
  50. data/spec/twitter/error_spec.rb +2 -2
  51. data/spec/twitter/geo_factory_spec.rb +1 -1
  52. data/spec/twitter/identifiable_spec.rb +2 -2
  53. data/spec/twitter/media_factory_spec.rb +1 -1
  54. data/spec/twitter/search_results_spec.rb +11 -0
  55. data/spec/twitter/tweet_spec.rb +11 -0
  56. data/twitter.gemspec +3 -2
  57. metadata +190 -173
  58. data/lib/twitter/core_ext/array.rb +0 -7
  59. data/lib/twitter/core_ext/hash.rb +0 -100
  60. data/lib/twitter/core_ext/string.rb +0 -10
@@ -31,7 +31,7 @@ module Twitter
31
31
  # @param ids [Array<Integer>, Set<Integer>] An array of Tweet IDs.
32
32
  # @param options [Hash] A customizable set of options.
33
33
  def saved_searches(*args)
34
- options = args.extract_options!
34
+ options = extract_options!(args)
35
35
  if args.empty?
36
36
  collection_from_response(Twitter::SavedSearch, :get, "/1.1/saved_searches/list.json", options)
37
37
  else
@@ -87,7 +87,7 @@ module Twitter
87
87
  # @param ids [Array<Integer>, Set<Integer>] An array of Tweet IDs.
88
88
  # @param options [Hash] A customizable set of options.
89
89
  def saved_search_destroy(*args)
90
- options = args.extract_options!
90
+ options = extract_options!(args)
91
91
  args.flatten.threaded_map do |id|
92
92
  object_from_response(Twitter::SavedSearch, :post, "/1.1/saved_searches/destroy/#{id}.json", options)
93
93
  end
@@ -8,7 +8,7 @@ module Twitter
8
8
 
9
9
  # The users specified are blocked by the authenticated user and reported as spammers
10
10
  #
11
- # @see https://dev.twitter.com/docs/api/1.1/post/report_spam
11
+ # @see https://dev.twitter.com/docs/api/1.1/post/users/report_spam
12
12
  # @rate_limited Yes
13
13
  # @authentication_required Requires user context
14
14
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
@@ -22,7 +22,7 @@ module Twitter
22
22
  # @param users [Array<Integer, String, Twitter::User>, Set<Integer, String, Twitter::User>] An array of Twitter user IDs, screen names, or objects.
23
23
  # @param options [Hash] A customizable set of options.
24
24
  def report_spam(*args)
25
- threaded_users_from_response(:post, "/1.1/report_spam.json", args)
25
+ threaded_users_from_response(:post, "/1.1/users/report_spam.json", args)
26
26
  end
27
27
 
28
28
  end
@@ -27,7 +27,7 @@ module Twitter
27
27
  # @example Return the users in the Art & Design category
28
28
  # Twitter.suggestions("art-design")
29
29
  def suggestions(*args)
30
- options = args.extract_options!
30
+ options = extract_options!(args)
31
31
  if slug = args.pop
32
32
  object_from_response(Twitter::Suggestion, :get, "/1.1/users/suggestions/#{slug}.json", options)
33
33
  else
@@ -99,10 +99,8 @@ module Twitter
99
99
  # @example Return the 20 most recent retweets posted by the authenticating user
100
100
  # Twitter.retweeted_by_me
101
101
  def retweeted_by_me(options={})
102
- options[:include_rts] = true
103
- count = options[:count] || DEFAULT_TWEETS_PER_REQUEST
104
- collect_with_count(count) do |count_options|
105
- select_retweets(user_timeline(options.merge(count_options)))
102
+ retweets_from_timeline(options) do |options|
103
+ user_timeline(options)
106
104
  end
107
105
  end
108
106
 
@@ -148,10 +146,8 @@ module Twitter
148
146
  # @example Return the 20 most recent retweets posted by users followed by the authenticating user
149
147
  # Twitter.retweeted_to_me
150
148
  def retweeted_to_me(options={})
151
- options[:include_rts] = true
152
- count = options[:count] || DEFAULT_TWEETS_PER_REQUEST
153
- collect_with_count(count) do |count_options|
154
- select_retweets(home_timeline(options.merge(count_options)))
149
+ retweets_from_timeline(options) do |options|
150
+ home_timeline(options)
155
151
  end
156
152
  end
157
153
 
@@ -202,6 +198,14 @@ module Twitter
202
198
  end.flatten.compact[0...count]
203
199
  end
204
200
 
201
+ def retweets_from_timeline(options)
202
+ options[:include_rts] = true
203
+ count = options[:count] || DEFAULT_TWEETS_PER_REQUEST
204
+ collect_with_count(count) do |count_options|
205
+ select_retweets(yield(options.merge(count_options)))
206
+ end
207
+ end
208
+
205
209
  # @param tweets [Array]
206
210
  # @return [Array]
207
211
  def select_retweets(tweets)
@@ -144,7 +144,7 @@ module Twitter
144
144
  # @param options [Hash] A customizable set of options.
145
145
  # @option options [Boolean, String, Integer] :trim_user Each tweet returned in a timeline will include a user object with only the author's numerical ID when set to true, 't' or 1.
146
146
  def retweet(*args)
147
- options = args.extract_options!
147
+ options = extract_options!(args)
148
148
  args.flatten.threaded_map do |id|
149
149
  begin
150
150
  post_retweet(id, options)
@@ -171,16 +171,12 @@ module Twitter
171
171
  # @param options [Hash] A customizable set of options.
172
172
  # @option options [Boolean, String, Integer] :trim_user Each tweet returned in a timeline will include a user object with only the author's numerical ID when set to true, 't' or 1.
173
173
  def retweet!(*args)
174
- options = args.extract_options!
174
+ options = extract_options!(args)
175
175
  args.flatten.threaded_map do |id|
176
176
  begin
177
177
  post_retweet(id, options)
178
178
  rescue Twitter::Error::Forbidden => error
179
- if error.message == "sharing is not permissible for this status (Share validations failed)"
180
- raise Twitter::Error::AlreadyRetweeted.new("Tweet with the ID #{id} has already been retweeted by the authenticated user.")
181
- else
182
- raise
183
- end
179
+ handle_forbidden_error(Twitter::Error::AlreadyRetweeted, error)
184
180
  end
185
181
  end.compact
186
182
  end
@@ -252,7 +248,7 @@ module Twitter
252
248
  # @option options [String] :related A value for the TWT related parameter, as described in {https://dev.twitter.com/docs/intents Web Intents}. This value will be forwarded to all Web Intents calls.
253
249
  # @option options [String] :lang Language code for the rendered embed. This will affect the text and localization of the rendered HTML.
254
250
  def oembeds(*args)
255
- options = args.extract_options!
251
+ options = extract_options!(args)
256
252
  args.flatten.threaded_map do |id|
257
253
  object_from_response(Twitter::OEmbed, :get, "/1.1/statuses/oembed.json?id=#{id}", options)
258
254
  end
@@ -261,13 +257,13 @@ module Twitter
261
257
  private
262
258
 
263
259
  # @param request_method [Symbol]
264
- # @param url [String]
260
+ # @param path [String]
265
261
  # @param args [Array]
266
262
  # @return [Array<Twitter::Tweet>]
267
- def threaded_tweets_from_response(request_method, url, args)
268
- options = args.extract_options!
263
+ def threaded_tweets_from_response(request_method, path, args)
264
+ options = extract_options!(args)
269
265
  args.flatten.threaded_map do |id|
270
- object_from_response(Twitter::Tweet, request_method, url + "/#{id}.json", options)
266
+ object_from_response(Twitter::Tweet, request_method, path + "/#{id}.json", options)
271
267
  end
272
268
  end
273
269
 
@@ -66,9 +66,9 @@ module Twitter
66
66
  # Twitter.following_followers_of('sferik')
67
67
  # Twitter.following_followers_of(7505382) # Same as above
68
68
  def following_followers_of(*args)
69
- options = args.extract_options!
69
+ options = extract_options!(args)
70
70
  merge_default_cursor!(options)
71
- options.merge_user!(args.pop || screen_name)
71
+ merge_user!(options, args.pop || screen_name)
72
72
  cursor_from_response(:users, Twitter::User, :get, "/users/following_followers_of.json", options)
73
73
  end
74
74
 
@@ -105,7 +105,7 @@ module Twitter
105
105
  # @param ids [Array<Integer>, Set<Integer>] An array of Tweet IDs.
106
106
  # @param options [Hash] A customizable set of options.
107
107
  def statuses_activity(*args)
108
- options = args.extract_options!
108
+ options = extract_options!(args)
109
109
  args.flatten.threaded_map do |id|
110
110
  status_activity(id, options)
111
111
  end
@@ -239,15 +239,22 @@ module Twitter
239
239
  # @overload users(*users)
240
240
  # @param users [Array<Integer, String, Twitter::User>, Set<Integer, String, Twitter::User>] An array of Twitter user IDs, screen names, or objects.
241
241
  # @example Return extended information for @sferik and @pengwynn
242
- # Twitter.users('sferik', 'pengwynn')
243
- # Twitter.users(7505382, 14100886) # Same as above
242
+ # Twitter.users('sferik', 'pengwynn') # Retrieve users with a POST request using screen_names
243
+ # Twitter.users(7505382, 14100886) # Same as above using twitter_ids
244
244
  # @overload users(*users, options)
245
245
  # @param users [Array<Integer, String, Twitter::User>, Set<Integer, String, Twitter::User>] An array of Twitter user IDs, screen names, or objects.
246
246
  # @param options [Hash] A customizable set of options.
247
+ # @option options [Symbol, String] :method Requests users via a GET request instead of the standard POST request if set to ':get'.
248
+ # @option options [Boolean] :include_entities The tweet entities node will be disincluded when set to false.
249
+ # @example Return extended information for @sferik and @pengwynn
250
+ # Twitter.users('sferik', 'pengwynn', :method => :get) # Retrieve users with a GET request
251
+ # Twitter.users(7505382, 14100886, {:method => :get}) # Same as above
252
+ # Twitter.users(7505382, 14100886, {:method => :get, :include_entities => false}) # See Twitter API documentation
247
253
  def users(*args)
248
- options = args.extract_options!
254
+ options = extract_options!(args)
255
+ method = options.delete(:method) || :post
249
256
  args.flatten.each_slice(MAX_USERS_PER_REQUEST).threaded_map do |users|
250
- collection_from_response(Twitter::User, :post, "/1.1/users/lookup.json", options.merge_users(users))
257
+ collection_from_response(Twitter::User, method, "/1.1/users/lookup.json", merge_users(options, users))
251
258
  end.flatten
252
259
  end
253
260
 
@@ -272,9 +279,9 @@ module Twitter
272
279
  # Twitter.user('sferik')
273
280
  # Twitter.user(7505382) # Same as above
274
281
  def user(*args)
275
- options = args.extract_options!
282
+ options = extract_options!(args)
276
283
  if user = args.pop
277
- options.merge_user!(user)
284
+ merge_user!(options, user)
278
285
  object_from_response(Twitter::User, :get, "/1.1/users/show.json", options)
279
286
  else
280
287
  verify_credentials(options)
@@ -292,7 +299,7 @@ module Twitter
292
299
  # Twitter.user?('sferik')
293
300
  # Twitter.user?(7505382) # Same as above
294
301
  def user?(user, options={})
295
- options.merge_user!(user)
302
+ merge_user!(options, user)
296
303
  get("/1.1/users/show.json", options)
297
304
  true
298
305
  rescue Twitter::Error::NotFound
@@ -416,12 +423,11 @@ module Twitter
416
423
  # Twitter.profile_banner('sferik')
417
424
  # Twitter.profile_banner(7505382) # Same as above
418
425
  def profile_banner(*args)
419
- options = args.extract_options!
420
- options.merge_user!(args.pop || screen_name)
426
+ options = extract_options!(args)
427
+ merge_user!(options, args.pop || screen_name)
421
428
  object_from_response(Twitter::ProfileBanner, :get, "/1.1/users/profile_banner.json", options)
422
429
  end
423
430
 
424
-
425
431
  end
426
432
  end
427
433
  end
@@ -1,6 +1,4 @@
1
- require 'twitter/core_ext/array'
2
1
  require 'twitter/core_ext/enumerable'
3
- require 'twitter/core_ext/hash'
4
2
  require 'twitter/core_ext/kernel'
5
3
  require 'twitter/cursor'
6
4
  require 'twitter/user'
@@ -24,77 +22,97 @@ module Twitter
24
22
 
25
23
  # @param klass [Class]
26
24
  # @param request_method [Symbol]
27
- # @param url [String]
25
+ # @param path [String]
28
26
  # @param params [Hash]
29
27
  # @param options [Hash]
30
28
  # @return [Array]
31
- def collection_from_response(klass, request_method, url, params={})
32
- collection_from_array(klass, send(request_method.to_sym, url, params)[:body])
29
+ def collection_from_response(klass, request_method, path, params={})
30
+ collection_from_array(klass, send(request_method.to_sym, path, params)[:body])
33
31
  end
34
32
 
35
33
  # @param klass [Class]
36
34
  # @param request_method [Symbol]
37
- # @param url [String]
35
+ # @param path [String]
38
36
  # @param params [Hash]
39
37
  # @param options [Hash]
40
38
  # @return [Object]
41
- def object_from_response(klass, request_method, url, params={})
42
- response = send(request_method.to_sym, url, params)
39
+ def object_from_response(klass, request_method, path, params={})
40
+ response = send(request_method.to_sym, path, params)
43
41
  klass.from_response(response)
44
42
  end
45
43
 
46
44
  # @param klass [Class]
47
45
  # @param request_method [Symbol]
48
- # @param url [String]
46
+ # @param path [String]
49
47
  # @param args [Array]
50
48
  # @return [Array]
51
- def objects_from_response(klass, request_method, url, args)
52
- options = args.extract_options!
53
- options.merge_user!(args.pop)
54
- collection_from_response(klass, request_method, url, options)
49
+ def objects_from_response(klass, request_method, path, args)
50
+ options = extract_options!(args)
51
+ merge_user!(options, args.pop)
52
+ collection_from_response(klass, request_method, path, options)
55
53
  end
56
54
 
57
55
  # @param request_method [Symbol]
58
- # @param url [String]
56
+ # @param path [String]
59
57
  # @param args [Array]
60
58
  # @return [Array<Integer>]
61
- def ids_from_response(request_method, url, args)
62
- options = args.extract_options!
59
+ def ids_from_response(request_method, path, args)
60
+ options = extract_options!(args)
63
61
  merge_default_cursor!(options)
64
- options.merge_user!(args.pop)
65
- cursor_from_response(:ids, nil, request_method, url, options, calling_method)
62
+ merge_user!(options, args.pop)
63
+ cursor_from_response(:ids, nil, request_method, path, options, calling_method)
66
64
  end
67
65
 
68
66
  # @param collection_name [Symbol]
69
67
  # @param klass [Class]
70
68
  # @param request_method [Symbol]
71
- # @param url [String]
69
+ # @param path [String]
72
70
  # @param params [Hash]
73
71
  # @param options [Hash]
74
72
  # @return [Twitter::Cursor]
75
- def cursor_from_response(collection_name, klass, request_method, url, params={}, method_name=calling_method)
76
- response = send(request_method.to_sym, url, params)
73
+ def cursor_from_response(collection_name, klass, request_method, path, params={}, method_name=calling_method)
74
+ response = send(request_method.to_sym, path, params)
77
75
  Twitter::Cursor.from_response(response, collection_name.to_sym, klass, self, method_name, params)
78
76
  end
79
77
 
80
78
  # @param request_method [Symbol]
81
- # @param url [String]
79
+ # @param path [String]
82
80
  # @param args [Array]
83
81
  # @return [Array<Twitter::User>]
84
- def users_from_response(request_method, url, args)
85
- options = args.extract_options!
86
- options.merge_user!(args.pop || screen_name)
87
- collection_from_response(Twitter::User, request_method, url, options)
82
+ def users_from_response(request_method, path, args)
83
+ options = extract_options!(args)
84
+ merge_user!(options, args.pop || screen_name)
85
+ collection_from_response(Twitter::User, request_method, path, options)
88
86
  end
89
87
 
90
88
  # @param request_method [Symbol]
91
- # @param url [String]
89
+ # @param path [String]
92
90
  # @param args [Array]
93
91
  # @return [Array<Twitter::User>]
94
- def threaded_users_from_response(request_method, url, args)
95
- options = args.extract_options!
92
+ def threaded_users_from_response(request_method, path, args)
93
+ options = extract_options!(args)
96
94
  args.flatten.threaded_map do |user|
97
- object_from_response(Twitter::User, request_method, url, options.merge_user(user))
95
+ object_from_response(Twitter::User, request_method, path, merge_user(options, user))
96
+ end
97
+ end
98
+
99
+ # @param klass [Class]
100
+ # @param request_method [Symbol]
101
+ # @param path [String]
102
+ # @param args [Array]
103
+ # @return [Array]
104
+ def threaded_object_from_response(klass, request_method, path, args)
105
+ options = extract_options!(args)
106
+ args.flatten.threaded_map do |id|
107
+ object_from_response(klass, request_method, path, options.merge(:id => id))
108
+ end
109
+ end
110
+
111
+ def handle_forbidden_error(klass, error)
112
+ if error.message == klass::MESSAGE
113
+ raise klass.new
114
+ else
115
+ raise error
98
116
  end
99
117
  end
100
118
 
@@ -106,6 +124,67 @@ module Twitter
106
124
  @screen_name ||= verify_credentials.screen_name
107
125
  end
108
126
 
127
+ def extract_options!(array)
128
+ array.last.is_a?(::Hash) ? array.pop : {}
129
+ end
130
+
131
+ # Take a user and merge it into the hash with the correct key
132
+ #
133
+ # @param hash [Hash]
134
+ # @param user [Integer, String, Twitter::User] A Twitter user ID, screen_name, or object.
135
+ # @return [Hash]
136
+ def merge_user(hash, user, prefix=nil)
137
+ merge_user!(hash.dup, user, prefix)
138
+ end
139
+
140
+ # Take a user and merge it into the hash with the correct key
141
+ #
142
+ # @param hash [Hash]
143
+ # @param user [Integer, String, Twitter::User] A Twitter user ID, screen_name, or object.
144
+ # @return [Hash]
145
+ def merge_user!(hash, user, prefix=nil)
146
+ case user
147
+ when Integer
148
+ hash[[prefix, "user_id"].compact.join("_").to_sym] = user
149
+ when String
150
+ hash[[prefix, "screen_name"].compact.join("_").to_sym] = user
151
+ when Twitter::User
152
+ hash[[prefix, "user_id"].compact.join("_").to_sym] = user.id
153
+ end
154
+ hash
155
+ end
156
+
157
+ # Take a multiple users and merge them into the hash with the correct keys
158
+ #
159
+ # @param hash [Hash]
160
+ # @param users [Array<Integer, String, Twitter::User>, Set<Integer, String, Twitter::User>] An array of Twitter user IDs, screen_names, or objects.
161
+ # @return [Hash]
162
+ def merge_users(hash, users)
163
+ merge_users!(hash.dup, users)
164
+ end
165
+
166
+ # Take a multiple users and merge them into the hash with the correct keys
167
+ #
168
+ # @param hash [Hash]
169
+ # @param users [Array<Integer, String, Twitter::User>, Set<Integer, String, Twitter::User>] An array of Twitter user IDs, screen_names, or objects.
170
+ # @return [Hash]
171
+ def merge_users!(hash, users)
172
+ user_ids, screen_names = [], []
173
+ users.flatten.each do |user|
174
+ case user
175
+ when Integer
176
+ user_ids << user
177
+ when String
178
+ screen_names << user
179
+ when Twitter::User
180
+ user_ids << user.id
181
+ end
182
+ end
183
+ hash[:user_id] = user_ids.join(',') unless user_ids.empty?
184
+ hash[:screen_name] = screen_names.join(',') unless screen_names.empty?
185
+ hash
186
+ end
187
+
109
188
  end
110
189
  end
111
190
  end
@@ -3,20 +3,22 @@ require 'twitter/error/identity_map_key_error'
3
3
 
4
4
  module Twitter
5
5
  class Base
6
- attr_reader :attrs
7
- alias to_hash attrs
8
-
9
6
  # Define methods that retrieve the value from an initialized instance variable Hash, using the attribute as a key
10
7
  #
11
8
  # @param attrs [Array, Set, Symbol]
12
9
  def self.attr_reader(*attrs)
13
- attrs.each do |attribute|
14
- class_eval do
10
+ mod = Module.new do
11
+ attrs.each do |attribute|
15
12
  define_method attribute do
16
13
  @attrs[attribute.to_sym]
17
14
  end
15
+ define_method "#{attribute}?" do
16
+ !!@attrs[attribute.to_sym]
17
+ end
18
18
  end
19
19
  end
20
+ const_set(:Attributes, mod)
21
+ include mod
20
22
  end
21
23
 
22
24
  # return [Twitter::IdentityMap]
@@ -88,6 +90,14 @@ module Twitter
88
90
  nil
89
91
  end
90
92
 
93
+ # Retrieve the attributes of an object
94
+ #
95
+ # @return [Hash]
96
+ def attrs
97
+ @attrs
98
+ end
99
+ alias to_hash attrs
100
+
91
101
  # Update the attributes of an object
92
102
  #
93
103
  # @param attrs [Hash]