twitter 4.3.0 → 4.4.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.
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]