twitter 8.2.0 → 8.3.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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.md +1 -1
  3. data/README.md +172 -10
  4. data/lib/twitter/arguments.rb +14 -1
  5. data/lib/twitter/base.rb +72 -11
  6. data/lib/twitter/basic_user.rb +7 -1
  7. data/lib/twitter/client.rb +94 -3
  8. data/lib/twitter/creatable.rb +11 -2
  9. data/lib/twitter/cursor.rb +58 -11
  10. data/lib/twitter/direct_message.rb +32 -4
  11. data/lib/twitter/direct_message_event.rb +34 -10
  12. data/lib/twitter/direct_messages/welcome_message.rb +22 -1
  13. data/lib/twitter/direct_messages/welcome_message_rule.rb +7 -0
  14. data/lib/twitter/direct_messages/welcome_message_rule_wrapper.rb +26 -3
  15. data/lib/twitter/direct_messages/welcome_message_wrapper.rb +36 -11
  16. data/lib/twitter/entities.rb +84 -8
  17. data/lib/twitter/entity/hashtag.rb +7 -1
  18. data/lib/twitter/entity/symbol.rb +7 -1
  19. data/lib/twitter/entity/uri.rb +2 -1
  20. data/lib/twitter/entity/user_mention.rb +20 -1
  21. data/lib/twitter/entity.rb +7 -1
  22. data/lib/twitter/enumerable.rb +20 -3
  23. data/lib/twitter/error.rb +137 -61
  24. data/lib/twitter/factory.rb +9 -5
  25. data/lib/twitter/geo/point.rb +37 -5
  26. data/lib/twitter/geo/polygon.rb +1 -0
  27. data/lib/twitter/geo.rb +16 -2
  28. data/lib/twitter/geo_factory.rb +7 -3
  29. data/lib/twitter/geo_results.rb +39 -8
  30. data/lib/twitter/headers.rb +44 -7
  31. data/lib/twitter/identity.rb +13 -3
  32. data/lib/twitter/language.rb +21 -1
  33. data/lib/twitter/list.rb +101 -11
  34. data/lib/twitter/media/animated_gif.rb +1 -0
  35. data/lib/twitter/media/photo.rb +19 -3
  36. data/lib/twitter/media/video.rb +21 -3
  37. data/lib/twitter/media/video_info.rb +15 -1
  38. data/lib/twitter/media_factory.rb +7 -3
  39. data/lib/twitter/metadata.rb +14 -1
  40. data/lib/twitter/null_object.rb +16 -14
  41. data/lib/twitter/oembed.rb +56 -2
  42. data/lib/twitter/place.rb +74 -6
  43. data/lib/twitter/premium_search_results.rb +87 -18
  44. data/lib/twitter/profile.rb +100 -44
  45. data/lib/twitter/profile_banner.rb +9 -4
  46. data/lib/twitter/rate_limit.rb +32 -3
  47. data/lib/twitter/relationship.rb +8 -5
  48. data/lib/twitter/rest/account_activity.rb +55 -26
  49. data/lib/twitter/rest/api.rb +2 -0
  50. data/lib/twitter/rest/client.rb +18 -0
  51. data/lib/twitter/rest/direct_messages/welcome_messages.rb +89 -18
  52. data/lib/twitter/rest/direct_messages.rb +158 -94
  53. data/lib/twitter/rest/favorites.rb +57 -21
  54. data/lib/twitter/rest/form_encoder.rb +57 -17
  55. data/lib/twitter/rest/friends_and_followers.rb +101 -35
  56. data/lib/twitter/rest/help.rb +13 -3
  57. data/lib/twitter/rest/lists.rb +133 -45
  58. data/lib/twitter/rest/oauth.rb +23 -17
  59. data/lib/twitter/rest/places_and_geo.rb +44 -28
  60. data/lib/twitter/rest/premium_search.rb +18 -13
  61. data/lib/twitter/rest/request.rb +171 -53
  62. data/lib/twitter/rest/saved_searches.rb +22 -7
  63. data/lib/twitter/rest/search.rb +20 -16
  64. data/lib/twitter/rest/spam_reporting.rb +5 -1
  65. data/lib/twitter/rest/suggested_users.rb +14 -5
  66. data/lib/twitter/rest/timelines.rb +92 -52
  67. data/lib/twitter/rest/trends.rb +31 -12
  68. data/lib/twitter/rest/tweets.rb +145 -88
  69. data/lib/twitter/rest/undocumented.rb +11 -2
  70. data/lib/twitter/rest/upload_utils.rb +42 -26
  71. data/lib/twitter/rest/users.rb +150 -71
  72. data/lib/twitter/rest/utils.rb +135 -39
  73. data/lib/twitter/saved_search.rb +23 -2
  74. data/lib/twitter/search_results.rb +62 -17
  75. data/lib/twitter/settings.rb +37 -11
  76. data/lib/twitter/size.rb +37 -3
  77. data/lib/twitter/source_user.rb +4 -3
  78. data/lib/twitter/streaming/client.rb +60 -8
  79. data/lib/twitter/streaming/connection.rb +55 -8
  80. data/lib/twitter/streaming/deleted_tweet.rb +8 -0
  81. data/lib/twitter/streaming/event.rb +43 -1
  82. data/lib/twitter/streaming/friend_list.rb +1 -0
  83. data/lib/twitter/streaming/message_parser.rb +20 -10
  84. data/lib/twitter/streaming/response.rb +31 -5
  85. data/lib/twitter/streaming/stall_warning.rb +23 -0
  86. data/lib/twitter/suggestion.rb +25 -1
  87. data/lib/twitter/target_user.rb +2 -1
  88. data/lib/twitter/trend.rb +29 -1
  89. data/lib/twitter/trend_results.rb +50 -7
  90. data/lib/twitter/tweet.rb +180 -21
  91. data/lib/twitter/user.rb +289 -53
  92. data/lib/twitter/utils.rb +12 -13
  93. data/lib/twitter/variant.rb +12 -1
  94. data/lib/twitter/version.rb +66 -29
  95. data/lib/twitter.rb +6 -1
  96. metadata +23 -57
  97. data/.yardopts +0 -16
  98. data/CHANGELOG.md +0 -1040
  99. data/CONTRIBUTING.md +0 -49
  100. data/twitter.gemspec +0 -40
@@ -9,66 +9,81 @@ require "twitter/utils"
9
9
 
10
10
  module Twitter
11
11
  module REST
12
+ # Methods for working with tweets
12
13
  module Tweets
13
14
  include Twitter::REST::UploadUtils
14
15
  include Twitter::REST::Utils
15
16
  include Twitter::Utils
17
+
18
+ # Maximum tweets per request
16
19
  MAX_TWEETS_PER_REQUEST = 100
17
20
 
18
21
  # Returns up to 100 of the first retweets of a given tweet
19
22
  #
23
+ # @api public
20
24
  # @see https://dev.twitter.com/rest/reference/get/statuses/retweets/:id
21
25
  # @rate_limited Yes
22
26
  # @authentication Requires user context
23
27
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
28
+ # @example
29
+ # client.retweets(25938088801)
24
30
  # @return [Array<Twitter::Tweet>]
25
31
  # @param tweet [Integer, String, URI, Twitter::Tweet] A Tweet ID, URI, or object.
26
32
  # @param options [Hash] A customizable set of options.
27
- # @option options [Integer] :count Specifies the number of records to retrieve. Must be less than or equal to 100.
28
- # @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.
33
+ # @option options [Integer] :count Specifies the number of records to retrieve.
34
+ # @option options [Boolean, String, Integer] :trim_user Include only the author's ID.
29
35
  def retweets(tweet, options = {})
30
- perform_get_with_objects("/1.1/statuses/retweets/#{extract_id(tweet)}.json", options, Twitter::Tweet)
36
+ perform_get_with_objects("/1.1/statuses/retweets/#{extract_id(tweet)}.json", options, Tweet)
31
37
  end
32
38
 
33
- # Show up to 100 users who retweeted the Tweet
39
+ # Shows up to 100 users who retweeted the Tweet
34
40
  #
41
+ # @api public
35
42
  # @see https://dev.twitter.com/rest/reference/get/statuses/retweets/:id
36
43
  # @rate_limited Yes
37
44
  # @authentication Requires user context
38
45
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
46
+ # @example
47
+ # client.retweeters_of(25938088801)
39
48
  # @return [Array]
40
49
  # @param tweet [Integer, String, URI, Twitter::Tweet] A Tweet ID, URI, or object.
41
50
  # @param options [Hash] A customizable set of options.
42
- # @option options [Integer] :count Specifies the number of records to retrieve. Must be less than or equal to 100.
43
- # @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.
44
- # @option options [Boolean] :ids_only ('false') Only return user IDs instead of full user objects.
51
+ # @option options [Integer] :count Specifies the number of records to retrieve.
52
+ # @option options [Boolean, String, Integer] :trim_user Include only the author's ID.
53
+ # @option options [Boolean] :ids_only Only return user IDs.
45
54
  def retweeters_of(tweet, options = {})
46
55
  options = options.dup
47
- ids_only = !!options.delete(:ids_only)
56
+ ids_only = options.delete(:ids_only)
48
57
  retweeters = retweets(tweet, options).collect(&:user)
49
58
  ids_only ? retweeters.collect(&:id) : retweeters
50
59
  end
51
60
 
52
61
  # Returns a Tweet
53
62
  #
63
+ # @api public
54
64
  # @see https://dev.twitter.com/rest/reference/get/statuses/show/:id
55
65
  # @rate_limited Yes
56
66
  # @authentication Requires user context
57
67
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
58
68
  # @raise [Twitter::Error::Forbidden] Error raised when supplied status is over 280 characters.
69
+ # @example
70
+ # client.status(25938088801)
59
71
  # @return [Twitter::Tweet] The requested Tweet.
60
72
  # @param tweet [Integer, String, URI, Twitter::Tweet] A Tweet ID, URI, or object.
61
73
  # @param options [Hash] A customizable set of options.
62
- # @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.
74
+ # @option options [Boolean, String, Integer] :trim_user Include only the author's ID.
63
75
  def status(tweet, options = {})
64
- perform_get_with_object("/1.1/statuses/show/#{extract_id(tweet)}.json", options, Twitter::Tweet)
76
+ perform_get_with_object("/1.1/statuses/show/#{extract_id(tweet)}.json", options, Tweet)
65
77
  end
66
78
 
67
79
  # Returns Tweets
68
80
  #
81
+ # @api public
69
82
  # @see https://dev.twitter.com/rest/reference/get/statuses/lookup
70
83
  # @rate_limited Yes
71
84
  # @authentication Required
85
+ # @example
86
+ # client.statuses(25938088801, 25938088802)
72
87
  # @return [Array<Twitter::Tweet>] The requested Tweets.
73
88
  # @overload statuses(*tweets)
74
89
  # @param tweets [Enumerable<Integer, String, URI, Twitter::Tweet>] A collection of Tweet IDs, URIs, or objects.
@@ -77,19 +92,22 @@ module Twitter
77
92
  # @param options [Hash] A customizable set of options.
78
93
  # @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.
79
94
  def statuses(*args)
80
- arguments = Twitter::Arguments.new(args)
95
+ arguments = Arguments.new(args)
81
96
  flat_pmap(arguments.each_slice(MAX_TWEETS_PER_REQUEST)) do |tweets|
82
- perform_post_with_objects("/1.1/statuses/lookup.json", arguments.options.merge(id: tweets.collect { |u| extract_id(u) }.join(",")), Twitter::Tweet)
97
+ perform_post_with_objects("/1.1/statuses/lookup.json", arguments.options.merge(id: tweets.collect { |u| extract_id(u) }.join(",")), Tweet)
83
98
  end
84
99
  end
85
100
 
86
101
  # Destroys the specified Tweets
87
102
  #
103
+ # @api public
88
104
  # @see https://dev.twitter.com/rest/reference/post/statuses/destroy/:id
89
105
  # @note The authenticating user must be the author of the specified Tweets.
90
106
  # @rate_limited No
91
107
  # @authentication Requires user context
92
108
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
109
+ # @example
110
+ # client.destroy_status(25938088801)
93
111
  # @return [Array<Twitter::Tweet>] The deleted Tweets.
94
112
  # @overload destroy_status(*tweets)
95
113
  # @param tweets [Enumerable<Integer, String, URI, Twitter::Tweet>] A collection of Tweet IDs, URIs, or objects.
@@ -98,71 +116,82 @@ module Twitter
98
116
  # @param options [Hash] A customizable set of options.
99
117
  # @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.
100
118
  def destroy_status(*args)
101
- arguments = Twitter::Arguments.new(args)
119
+ arguments = Arguments.new(args)
102
120
  pmap(arguments) do |tweet|
103
- perform_post_with_object("/1.1/statuses/destroy/#{extract_id(tweet)}.json", arguments.options, Twitter::Tweet)
121
+ perform_post_with_object("/1.1/statuses/destroy/#{extract_id(tweet)}.json", arguments.options, Tweet)
104
122
  end
105
123
  end
106
- alias destroy_tweet destroy_status
124
+ # @!method destroy_tweet
125
+ # @api public
126
+ # @see #destroy_status
127
+ alias_method :destroy_tweet, :destroy_status
107
128
 
108
129
  # Updates the authenticating user's status
109
130
  #
131
+ # @api public
110
132
  # @see https://dev.twitter.com/rest/reference/post/statuses/update
111
- # @note A status update with text identical to the authenticating user's current status will be ignored to prevent duplicates.
133
+ # @note Duplicate statuses are ignored to prevent duplicates.
112
134
  # @rate_limited No
113
135
  # @authentication Requires user context
114
136
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
115
- # @return [Twitter::Tweet] The created Tweet. When the tweet is deemed a duplicate by Twitter, returns the last Tweet from the user's timeline.
137
+ # @example
138
+ # client.update('I just posted a status update!')
139
+ # @return [Twitter::Tweet] The created Tweet.
116
140
  # @param status [String] The text of your status update, up to 280 characters.
117
141
  # @param options [Hash] A customizable set of options.
118
- # @option options [Boolean, String, Integer] :possibly_sensitive Set to true for content which may not be suitable for every audience.
119
- # @option options [Twitter::Tweet] :in_reply_to_status An existing status that the update is in reply to. If the status being replied to was not originally posted by the authenticated user, the text of the status must begin with an @-mention, or twitter will reject the update.
120
- # @option options [Integer] :in_reply_to_status_id The ID of an existing status that the update is in reply to.
121
- # @option options [Float] :lat The latitude of the location this tweet refers to. This option will be ignored unless it is inside the range -90.0 to +90.0 (North is positive) inclusive. It will also be ignored if there isn't a corresponding :long option.
122
- # @option options [Float] :long The longitude of the location this tweet refers to. The valid ranges for longitude is -180.0 to +180.0 (East is positive) inclusive. This option will be ignored if outside that range, if it is not a number, if geo_enabled is disabled, or if there not a corresponding :lat option.
123
- # @option options [Twitter::Place] :place A place in the world. These can be retrieved from {Twitter::REST::PlacesAndGeo#reverse_geocode}.
124
- # @option options [String] :place_id A place in the world. These IDs can be retrieved from {Twitter::REST::PlacesAndGeo#reverse_geocode}.
125
- # @option options [String] :display_coordinates Whether or not to put a pin on the exact coordinates a tweet has been sent from.
126
- # @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.
142
+ # @option options [Boolean, String, Integer] :possibly_sensitive Sensitive content flag.
143
+ # @option options [Twitter::Tweet] :in_reply_to_status An existing status to reply to.
144
+ # @option options [Integer] :in_reply_to_status_id The ID of a status to reply to.
145
+ # @option options [Float] :lat The latitude of the location.
146
+ # @option options [Float] :long The longitude of the location.
147
+ # @option options [Twitter::Place] :place A place in the world.
148
+ # @option options [String] :place_id A place ID.
149
+ # @option options [String] :display_coordinates Pin on exact coordinates.
150
+ # @option options [Boolean, String, Integer] :trim_user Include only the author's ID.
127
151
  def update(status, options = {})
128
152
  update!(status, options)
129
- rescue Twitter::Error::DuplicateStatus
130
- user_timeline(count: 1).first
153
+ rescue Error::DuplicateStatus
154
+ user_timeline(count: 1).first # steep:ignore NoMethod
131
155
  end
132
156
 
133
- # Updates the authenticating user's status
157
+ # Updates the authenticating user's status, raising an error on duplicate
134
158
  #
159
+ # @api public
135
160
  # @see https://dev.twitter.com/rest/reference/post/statuses/update
136
- # @note A status update with text identical to the authenticating user's current status will be ignored to prevent duplicates.
137
161
  # @rate_limited No
138
162
  # @authentication Requires user context
139
163
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
140
164
  # @raise [Twitter::Error::DuplicateStatus] Error raised when a duplicate status is posted.
165
+ # @example
166
+ # client.update!('I just posted a status update!')
141
167
  # @return [Twitter::Tweet] The created Tweet.
142
168
  # @param status [String] The text of your status update, up to 280 characters.
143
169
  # @param options [Hash] A customizable set of options.
144
- # @option options [Boolean, String, Integer] :possibly_sensitive Set to true for content which may not be suitable for every audience.
145
- # @option options [Twitter::Tweet] :in_reply_to_status An existing status that the update is in reply to. If the status being replied to was not originally posted by the authenticated user, the text of the status must begin with an @-mention, or twitter will reject the update.
146
- # @option options [Integer] :in_reply_to_status_id The ID of an existing status that the update is in reply to.
147
- # @option options [Float] :lat The latitude of the location this tweet refers to. This option will be ignored unless it is inside the range -90.0 to +90.0 (North is positive) inclusive. It will also be ignored if there isn't a corresponding :long option.
148
- # @option options [Float] :long The longitude of the location this tweet refers to. The valid ranges for longitude is -180.0 to +180.0 (East is positive) inclusive. This option will be ignored if outside that range, if it is not a number, if geo_enabled is disabled, or if there not a corresponding :lat option.
149
- # @option options [Twitter::Place] :place A place in the world. These can be retrieved from {Twitter::REST::PlacesAndGeo#reverse_geocode}.
150
- # @option options [String] :place_id A place in the world. These IDs can be retrieved from {Twitter::REST::PlacesAndGeo#reverse_geocode}.
151
- # @option options [String] :display_coordinates Whether or not to put a pin on the exact coordinates a tweet has been sent from.
152
- # @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.
170
+ # @option options [Boolean, String, Integer] :possibly_sensitive Sensitive content flag.
171
+ # @option options [Twitter::Tweet] :in_reply_to_status An existing status to reply to.
172
+ # @option options [Integer] :in_reply_to_status_id The ID of a status to reply to.
173
+ # @option options [Float] :lat The latitude of the location.
174
+ # @option options [Float] :long The longitude of the location.
175
+ # @option options [Twitter::Place] :place A place in the world.
176
+ # @option options [String] :place_id A place ID.
177
+ # @option options [String] :display_coordinates Pin on exact coordinates.
178
+ # @option options [Boolean, String, Integer] :trim_user Include only the author's ID.
153
179
  def update!(status, options = {})
154
180
  hash = options.dup
155
181
  hash[:in_reply_to_status_id] = hash.delete(:in_reply_to_status).id unless hash[:in_reply_to_status].nil?
156
182
  hash[:place_id] = hash.delete(:place).woeid unless hash[:place].nil?
157
- perform_post_with_object("/1.1/statuses/update.json", hash.merge(status:), Twitter::Tweet)
183
+ perform_post_with_object("/1.1/statuses/update.json", hash.merge(status:), Tweet)
158
184
  end
159
185
 
160
186
  # Retweets the specified Tweets as the authenticating user
161
187
  #
188
+ # @api public
162
189
  # @see https://dev.twitter.com/rest/reference/post/statuses/retweet/:id
163
190
  # @rate_limited Yes
164
191
  # @authentication Requires user context
165
192
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
193
+ # @example
194
+ # client.retweet(25938088801)
166
195
  # @return [Array<Twitter::Tweet>] The original tweets with retweet details embedded.
167
196
  # @overload retweet(*tweets)
168
197
  # @param tweets [Enumerable<Integer, String, URI, Twitter::Tweet>] A collection of Tweet IDs, URIs, or objects.
@@ -171,22 +200,25 @@ module Twitter
171
200
  # @param options [Hash] A customizable set of options.
172
201
  # @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
202
  def retweet(*args)
174
- arguments = Twitter::Arguments.new(args)
203
+ arguments = Arguments.new(args)
175
204
  pmap(arguments) do |tweet|
176
205
  post_retweet(extract_id(tweet), arguments.options)
177
- rescue Twitter::Error::AlreadyRetweeted, Twitter::Error::NotFound
178
- next
206
+ rescue Error::AlreadyRetweeted, Error::NotFound
207
+ nil
179
208
  end.compact
180
209
  end
181
210
 
182
- # Retweets the specified Tweets as the authenticating user and raises an error if one has already been retweeted
211
+ # Retweets the specified Tweets and raises an error if already retweeted
183
212
  #
213
+ # @api public
184
214
  # @see https://dev.twitter.com/rest/reference/post/statuses/retweet/:id
185
215
  # @rate_limited Yes
186
216
  # @authentication Requires user context
187
217
  # @raise [Twitter::Error::AlreadyRetweeted] Error raised when tweet has already been retweeted.
188
218
  # @raise [Twitter::Error::NotFound] Error raised when tweet does not exist or has been deleted.
189
219
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
220
+ # @example
221
+ # client.retweet!(25938088801)
190
222
  # @return [Array<Twitter::Tweet>] The original tweets with retweet details embedded.
191
223
  # @overload retweet!(*tweets)
192
224
  # @param tweets [Enumerable<Integer, String, URI, Twitter::Tweet>] A collection of Tweet IDs, URIs, or objects.
@@ -195,70 +227,77 @@ module Twitter
195
227
  # @param options [Hash] A customizable set of options.
196
228
  # @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.
197
229
  def retweet!(*args)
198
- arguments = Twitter::Arguments.new(args)
230
+ arguments = Arguments.new(args)
199
231
  pmap(arguments) do |tweet|
200
232
  post_retweet(extract_id(tweet), arguments.options)
201
- end.compact
233
+ end
202
234
  end
203
235
 
204
236
  # Updates the authenticating user's status with media
205
237
  #
238
+ # @api public
206
239
  # @see https://dev.twitter.com/rest/reference/post/statuses/update_with_media
207
- # @note A status update with text/media identical to the authenticating user's current status will NOT be ignored
208
240
  # @rate_limited No
209
241
  # @authentication Requires user context
210
242
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
243
+ # @example
244
+ # client.update_with_media('I just posted a photo!', File.new('/path/to/image.png'))
211
245
  # @return [Twitter::Tweet] The created Tweet.
212
246
  # @param status [String] The text of your status update, up to 280 characters.
213
- # @param media [File, Array<File>] An image file or array of image files (PNG, JPEG or GIF).
247
+ # @param media [File, Array<File>] An image file or array of image files.
214
248
  # @param options [Hash] A customizable set of options.
215
- # @option options [Boolean, String, Integer] :possibly_sensitive Set to true for content which may not be suitable for every audience.
216
- # @option options [Twitter::Tweet] :in_reply_to_status An existing status that the update is in reply to.
217
- # @option options [Integer] :in_reply_to_status_id The ID of an existing Tweet that the update is in reply to.
218
- # @option options [Float] :lat The latitude of the location this tweet refers to. This option will be ignored unless it is inside the range -90.0 to +90.0 (North is positive) inclusive. It will also be ignored if there isn't a corresponding :long option.
219
- # @option options [Float] :long The longitude of the location this tweet refers to. The valid ranges for longitude is -180.0 to +180.0 (East is positive) inclusive. This option will be ignored if outside that range, if it is not a number, if geo_enabled is disabled, or if there not a corresponding :lat option.
220
- # @option options [Twitter::Place] :place A place in the world. These can be retrieved from {Twitter::REST::PlacesAndGeo#reverse_geocode}.
221
- # @option options [String] :place_id A place in the world. These IDs can be retrieved from {Twitter::REST::PlacesAndGeo#reverse_geocode}.
222
- # @option options [String] :display_coordinates Whether or not to put a pin on the exact coordinates a tweet has been sent from.
223
- # @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.
249
+ # @option options [Boolean, String, Integer] :possibly_sensitive Sensitive content flag.
250
+ # @option options [Twitter::Tweet] :in_reply_to_status An existing status to reply to.
251
+ # @option options [Integer] :in_reply_to_status_id The ID of a status to reply to.
252
+ # @option options [Float] :lat The latitude of the location.
253
+ # @option options [Float] :long The longitude of the location.
254
+ # @option options [Twitter::Place] :place A place in the world.
255
+ # @option options [String] :place_id A place ID.
256
+ # @option options [String] :display_coordinates Pin on exact coordinates.
257
+ # @option options [Boolean, String, Integer] :trim_user Include only the author's ID.
224
258
  def update_with_media(status, media, options = {})
225
- options = options.dup
226
259
  media_ids = pmap(array_wrap(media)) do |medium|
227
- upload(medium)[:media_id]
260
+ upload(medium).fetch(:media_id)
228
261
  end
229
262
  update!(status, options.merge(media_ids: media_ids.join(",")))
230
263
  end
231
264
 
232
265
  # Returns oEmbed for a Tweet
233
266
  #
267
+ # @api public
234
268
  # @see https://dev.twitter.com/rest/reference/get/statuses/oembed
235
269
  # @rate_limited Yes
236
270
  # @authentication Requires user context
237
271
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
272
+ # @example
273
+ # client.oembed(25938088801)
238
274
  # @return [Twitter::OEmbed] OEmbed for the requested Tweet.
239
275
  # @param tweet [Integer, String, URI, Twitter::Tweet] A Tweet ID, URI, or object.
240
276
  # @param options [Hash] A customizable set of options.
241
- # @option options [Integer] :maxwidth The maximum width in pixels that the embed should be rendered at. This value is constrained to be between 250 and 550 pixels.
242
- # @option options [Boolean, String, Integer] :hide_media Specifies whether the embedded Tweet should automatically expand images which were uploaded via {https://dev.twitter.com/rest/reference/post/statuses/update_with_media POST statuses/update_with_media}. When set to either true, t or 1 images will not be expanded. Defaults to false.
243
- # @option options [Boolean, String, Integer] :hide_thread Specifies whether the embedded Tweet should automatically show the original message in the case that the embedded Tweet is a reply. When set to either true, t or 1 the original Tweet will not be shown. Defaults to false.
244
- # @option options [Boolean, String, Integer] :omit_script Specifies whether the embedded Tweet HTML should include a `<script>` element pointing to widgets.js. In cases where a page already includes widgets.js, setting this value to true will prevent a redundant script element from being included. When set to either true, t or 1 the `<script>` element will not be included in the embed HTML, meaning that pages must include a reference to widgets.js manually. Defaults to false.
245
- # @option options [String] :align Specifies whether the embedded Tweet should be left aligned, right aligned, or centered in the page. Valid values are left, right, center, and none. Defaults to none, meaning no alignment styles are specified for the Tweet.
246
- # @option options [String] :related A value for the TWT related parameter, as described in {https://dev.twitter.com/web/intents Web Intents}. This value will be forwarded to all Web Intents calls.
247
- # @option options [String] :lang Language code for the rendered embed. This will affect the text and localization of the rendered HTML.
248
- # @option options [String] :widget_type Set to video to return a Twitter Video embed for the given Tweet.
249
- # @option options [Boolean, String] :hide_tweet Applies to video type only. Set to 1 or true to link directly to the Tweet URL instead of displaying a Tweet overlay when a viewer clicks on the Twitter bird logo.
277
+ # @option options [Integer] :maxwidth The maximum width in pixels.
278
+ # @option options [Boolean, String, Integer] :hide_media Hide uploaded images.
279
+ # @option options [Boolean, String, Integer] :hide_thread Hide original message for replies.
280
+ # @option options [Boolean, String, Integer] :omit_script Omit widgets.js script element.
281
+ # @option options [String] :align Alignment: left, right, center, or none.
282
+ # @option options [String] :related Related users for Web Intents.
283
+ # @option options [String] :lang Language code for the rendered embed.
284
+ # @option options [String] :widget_type Set to video for video embed.
285
+ # @option options [Boolean, String] :hide_tweet Link directly to Tweet URL.
250
286
  def oembed(tweet, options = {})
251
287
  options = options.dup
252
288
  options[:id] = extract_id(tweet)
253
- perform_get_with_object("/1.1/statuses/oembed.json", options, Twitter::OEmbed)
289
+ perform_get_with_object("/1.1/statuses/oembed.json", options, OEmbed)
254
290
  end
255
291
 
256
292
  # Returns oEmbeds for Tweets
257
293
  #
294
+ # @api public
258
295
  # @see https://dev.twitter.com/rest/reference/get/statuses/oembed
259
296
  # @rate_limited Yes
260
297
  # @authentication Requires user context
261
298
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
299
+ # @example
300
+ # client.oembeds(25938088801, 25938088802)
262
301
  # @return [Array<Twitter::OEmbed>] OEmbeds for the requested Tweets.
263
302
  # @overload oembed(*tweets)
264
303
  # @param tweets [Enumerable<Integer, String, URI, Twitter::Tweet>] A collection of Tweet IDs, URIs, or objects.
@@ -273,18 +312,21 @@ module Twitter
273
312
  # @option options [String] :related A value for the TWT related parameter, as described in {https://dev.twitter.com/web/intents Web Intents}. This value will be forwarded to all Web Intents calls.
274
313
  # @option options [String] :lang Language code for the rendered embed. This will affect the text and localization of the rendered HTML.
275
314
  def oembeds(*args)
276
- arguments = Twitter::Arguments.new(args)
315
+ arguments = Arguments.new(args)
277
316
  pmap(arguments) do |tweet|
278
- oembed(extract_id(tweet), arguments.options)
317
+ oembed(tweet, arguments.options)
279
318
  end
280
319
  end
281
320
 
282
- # Returns a collection of up to 100 user IDs belonging to users who have retweeted the tweet specified by the id parameter.
321
+ # Returns up to 100 user IDs who retweeted the tweet
283
322
  #
323
+ # @api public
284
324
  # @see https://dev.twitter.com/rest/reference/get/statuses/retweeters/ids
285
325
  # @rate_limited Yes
286
326
  # @authentication Required
287
327
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
328
+ # @example
329
+ # client.retweeters_ids(25938088801)
288
330
  # @return [Twitter::Cursor]
289
331
  # @overload retweeters_ids(options)
290
332
  # @param options [Hash] A customizable set of options.
@@ -292,17 +334,20 @@ module Twitter
292
334
  # @param tweet [Integer, String, URI, Twitter::Tweet] A Tweet ID, URI, or object.
293
335
  # @param options [Hash] A customizable set of options.
294
336
  def retweeters_ids(*args)
295
- arguments = Twitter::Arguments.new(args)
337
+ arguments = Arguments.new(args)
296
338
  arguments.options[:id] ||= extract_id(arguments.first)
297
339
  perform_get_with_cursor("/1.1/statuses/retweeters/ids.json", arguments.options, :ids)
298
340
  end
299
341
 
300
342
  # Untweets a retweeted status as the authenticating user
301
343
  #
344
+ # @api public
302
345
  # @see https://dev.twitter.com/rest/reference/post/statuses/unretweet/:id
303
346
  # @rate_limited Yes
304
347
  # @authentication Requires user context
305
348
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
349
+ # @example
350
+ # client.unretweet(25938088801)
306
351
  # @return [Array<Twitter::Tweet>] The original tweets with retweet details embedded.
307
352
  # @overload unretweet(*tweets)
308
353
  # @param tweets [Enumerable<Integer, String, URI, Twitter::Tweet>] A collection of Tweet IDs, URIs, or objects.
@@ -311,32 +356,44 @@ module Twitter
311
356
  # @param options [Hash] A customizable set of options.
312
357
  # @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.
313
358
  def unretweet(*args)
314
- arguments = Twitter::Arguments.new(args)
359
+ arguments = Arguments.new(args)
315
360
  pmap(arguments) do |tweet|
316
361
  post_unretweet(extract_id(tweet), arguments.options)
317
- rescue Twitter::Error::NotFound
318
- next
362
+ rescue Error::NotFound
363
+ nil
319
364
  end.compact
320
365
  end
321
366
 
322
- private
367
+ private
323
368
 
369
+ # Wraps object in an array if not already an array
370
+ #
371
+ # @api private
372
+ # @return [Array]
324
373
  def array_wrap(object)
325
- if object.respond_to?(:to_ary)
326
- object.to_ary || [object]
327
- else
328
- [object]
329
- end
374
+ object.instance_of?(Array) ? object : [object]
330
375
  end
331
376
 
332
- def post_retweet(tweet, options)
333
- response = perform_post("/1.1/statuses/retweet/#{extract_id(tweet)}.json", options)
334
- Twitter::Tweet.new(response)
377
+ # Posts a retweet
378
+ #
379
+ # @api private
380
+ # @param tweet_id [Integer] The tweet ID to retweet
381
+ # @param options [Hash] Request options
382
+ # @return [Twitter::Tweet]
383
+ def post_retweet(tweet_id, options)
384
+ response = perform_post("/1.1/statuses/retweet/#{tweet_id}.json", options)
385
+ Tweet.new(response)
335
386
  end
336
387
 
337
- def post_unretweet(tweet, options)
338
- response = perform_post("/1.1/statuses/unretweet/#{extract_id(tweet)}.json", options)
339
- Twitter::Tweet.new(response)
388
+ # Posts an unretweet
389
+ #
390
+ # @api private
391
+ # @param tweet_id [Integer] The tweet ID to unretweet
392
+ # @param options [Hash] Request options
393
+ # @return [Twitter::Tweet]
394
+ def post_unretweet(tweet_id, options)
395
+ response = perform_post("/1.1/statuses/unretweet/#{tweet_id}.json", options)
396
+ Tweet.new(response)
340
397
  end
341
398
  end
342
399
  end
@@ -6,13 +6,19 @@ require "twitter/user"
6
6
 
7
7
  module Twitter
8
8
  module REST
9
+ # Undocumented Twitter API endpoints
9
10
  module Undocumented
10
11
  include Twitter::REST::Utils
11
12
 
13
+ # Returns users following followers of the specified user
14
+ #
15
+ # @api public
12
16
  # @note Undocumented
13
17
  # @rate_limited Yes
14
18
  # @authentication Requires user context
15
19
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
20
+ # @example
21
+ # client.following_followers_of('sferik')
16
22
  # @return [Twitter::Cursor]
17
23
  # @overload following_followers_of(options = {})
18
24
  # Returns users following followers of the specified user
@@ -24,19 +30,22 @@ module Twitter
24
30
  # @param user [Integer, String, Twitter::User] A Twitter user ID, screen name, URI, or object.
25
31
  # @param options [Hash] A customizable set of options.
26
32
  def following_followers_of(*args)
27
- cursor_from_response_with_user(:users, Twitter::User, "/users/following_followers_of.json", args)
33
+ cursor_from_response_with_user(:users, User, "/users/following_followers_of.json", args)
28
34
  end
29
35
 
30
36
  # Returns Tweets count for a URI
31
37
  #
38
+ # @api public
32
39
  # @note Undocumented
33
40
  # @rate_limited No
34
41
  # @authentication Not required
42
+ # @example
43
+ # client.tweet_count('https://twitter.com')
35
44
  # @return [Integer]
36
45
  # @param url [String, URI] A URL.
37
46
  # @param options [Hash] A customizable set of options.
38
47
  def tweet_count(url, options = {})
39
- HTTP.get("https://cdn.api.twitter.com/1/urls/count.json", params: options.merge(url: url.to_s)).parse["count"]
48
+ HTTP.get("https://cdn.api.twitter.com/1/urls/count.json", params: options.merge(url: url.to_s)).parse["count"] # steep:ignore NoMethod
40
49
  end
41
50
  end
42
51
  end
@@ -2,70 +2,86 @@ require "twitter/rest/request"
2
2
 
3
3
  module Twitter
4
4
  module REST
5
+ # Utilities for uploading media to Twitter
6
+ #
7
+ # @api private
5
8
  module UploadUtils
6
- private
9
+ private
7
10
 
8
- # Uploads images and videos. Videos require multiple requests and uploads in chunks of 5 Megabytes.
9
- # The only supported video format is mp4.
11
+ # Uploads images and videos to Twitter
10
12
  #
13
+ # @api private
11
14
  # @see https://developer.twitter.com/en/docs/media/upload-media/uploading-media/media-best-practices
15
+ # @return [Hash]
12
16
  def upload(media, media_category_prefix: "tweet")
13
17
  ext = File.extname(media)
14
18
  return chunk_upload(media, "video/mp4", "#{media_category_prefix}_video") if ext == ".mp4"
15
19
  return chunk_upload(media, "video/quicktime", "#{media_category_prefix}_video") if ext == ".mov"
16
20
  return chunk_upload(media, "image/gif", "#{media_category_prefix}_gif") if ext == ".gif" && File.size(media) > 5_000_000
17
21
 
18
- Twitter::REST::Request.new(self, :multipart_post, "https://upload.twitter.com/1.1/media/upload.json", key: :media, file: media).perform
22
+ Request.new(self, :multipart_post, "https://upload.twitter.com/1.1/media/upload.json", key: :media, file: media).perform
19
23
  end
20
24
 
25
+ # Uploads large media in chunks
26
+ #
27
+ # @api private
21
28
  # @raise [Twitter::Error::TimeoutError] Error raised when the upload is longer than the value specified in Twitter::Client#timeouts[:upload].
22
29
  # @raise [Twitter::Error::MediaError] Error raised when Twitter return an error about a media which is not mapped by the gem.
23
30
  # @raise [Twitter::Error::MediaInternalError] Error raised when Twitter returns an InternalError error.
24
31
  # @raise [Twitter::Error::InvalidMedia] Error raised when Twitter returns an InvalidMedia error.
25
32
  # @raise [Twitter::Error::UnsupportedMedia] Error raised when Twitter returns an UnsupportedMedia error.
26
33
  # @see https://developer.twitter.com/en/docs/media/upload-media/uploading-media/chunked-media-upload
34
+ # @return [Hash]
27
35
  def chunk_upload(media, media_type, media_category)
28
- Timeout.timeout(timeouts&.fetch(:upload, nil), Twitter::Error::TimeoutError) do
29
- init = Twitter::REST::Request.new(self, :post, "https://upload.twitter.com/1.1/media/upload.json",
30
- command: "INIT",
31
- media_type:,
32
- media_category:,
33
- total_bytes: media.size).perform
34
- append_media(media, init[:media_id])
36
+ Timeout.timeout(timeouts&.fetch(:upload, nil), Error::TimeoutError) do # steep:ignore UnknownConstant,NoMethod
37
+ init = Request.new(self, :post, "https://upload.twitter.com/1.1/media/upload.json",
38
+ command: "INIT",
39
+ media_type:,
40
+ media_category:,
41
+ total_bytes: media.size).perform
42
+ append_media(media, init.fetch(:media_id))
35
43
  media.close
36
- finalize_media(init[:media_id])
44
+ finalize_media(init.fetch(:media_id))
37
45
  end
38
46
  end
39
47
 
48
+ # Appends media chunks
49
+ #
50
+ # @api private
40
51
  # @see https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-append
52
+ # @return [void]
41
53
  def append_media(media, media_id)
42
54
  until media.eof?
43
55
  chunk = media.read(5_000_000)
44
56
  seg ||= -1
45
- Twitter::REST::Request.new(self, :multipart_post, "https://upload.twitter.com/1.1/media/upload.json",
46
- command: "APPEND",
47
- media_id:,
48
- segment_index: seg += 1,
49
- key: :media,
50
- file: StringIO.new(chunk)).perform
57
+ Request.new(self, :multipart_post, "https://upload.twitter.com/1.1/media/upload.json",
58
+ command: "APPEND",
59
+ media_id:,
60
+ segment_index: seg += 1,
61
+ key: :media,
62
+ file: StringIO.new(chunk)).perform
51
63
  end
52
64
  end
53
65
 
66
+ # Finalizes a media upload
67
+ #
68
+ # @api private
54
69
  # @see https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-finalize
55
70
  # @see https://developer.twitter.com/en/docs/media/upload-media/api-reference/get-media-upload-status
71
+ # @return [Hash]
56
72
  def finalize_media(media_id)
57
- response = Twitter::REST::Request.new(self, :post, "https://upload.twitter.com/1.1/media/upload.json",
58
- command: "FINALIZE", media_id:).perform
59
- failed_or_succeeded = %w[failed succeeded]
73
+ response = Request.new(self, :post, "https://upload.twitter.com/1.1/media/upload.json",
74
+ command: "FINALIZE", media_id:).perform
75
+ terminal_states = %w[failed succeeded]
60
76
 
61
77
  loop do
62
- return response if !response[:processing_info] || failed_or_succeeded.include?(response[:processing_info][:state])
78
+ processing_info = response[:processing_info]
79
+ return response if !processing_info || terminal_states.include?(processing_info[:state])
63
80
 
64
- sleep(response[:processing_info][:check_after_secs])
65
- response = Twitter::REST::Request.new(self, :get, "https://upload.twitter.com/1.1/media/upload.json",
66
- command: "STATUS", media_id:).perform
81
+ sleep(processing_info.fetch(:check_after_secs))
82
+ response = Request.new(self, :get, "https://upload.twitter.com/1.1/media/upload.json",
83
+ command: "STATUS", media_id:).perform
67
84
  end
68
- response
69
85
  end
70
86
  end
71
87
  end