twitter 8.2.0 → 8.3.1

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 +160 -96
  53. data/lib/twitter/rest/favorites.rb +58 -22
  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 +136 -47
  58. data/lib/twitter/rest/oauth.rb +21 -15
  59. data/lib/twitter/rest/places_and_geo.rb +44 -28
  60. data/lib/twitter/rest/premium_search.rb +17 -12
  61. data/lib/twitter/rest/request.rb +173 -55
  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 +15 -6
  66. data/lib/twitter/rest/timelines.rb +92 -52
  67. data/lib/twitter/rest/trends.rb +32 -13
  68. data/lib/twitter/rest/tweets.rb +147 -89
  69. data/lib/twitter/rest/undocumented.rb +10 -1
  70. data/lib/twitter/rest/upload_utils.rb +45 -29
  71. data/lib/twitter/rest/users.rb +151 -72
  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 +66 -18
  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 +61 -9
  79. data/lib/twitter/streaming/connection.rb +54 -6
  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
@@ -4,80 +4,96 @@ require "twitter/rest/utils"
4
4
 
5
5
  module Twitter
6
6
  module REST
7
+ # Methods for working with places and geo data
7
8
  module PlacesAndGeo
8
9
  include Twitter::REST::Utils
9
10
 
10
11
  # Returns all the information about a known place
11
12
  #
13
+ # @api public
12
14
  # @see https://dev.twitter.com/rest/reference/get/geo/id/:place_id
13
15
  # @rate_limited Yes
14
16
  # @authentication Requires user context
15
17
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
16
- # @param place_id [String] A place in the world. These IDs can be retrieved from {Twitter::REST::PlacesAndGeo#reverse_geocode}.
18
+ # @example
19
+ # client.place('df51dec6f4ee2b2c')
20
+ # @param place_id [String] A place in the world.
17
21
  # @param options [Hash] A customizable set of options.
18
22
  # @return [Twitter::Place] The requested place.
19
23
  def place(place_id, options = {})
20
- perform_get_with_object("/1.1/geo/id/#{place_id}.json", options, Twitter::Place)
24
+ perform_get_with_object("/1.1/geo/id/#{place_id}.json", options, Place)
21
25
  end
22
26
 
23
27
  # Searches for up to 20 places that can be used as a place_id
24
28
  #
29
+ # @api public
25
30
  # @see https://dev.twitter.com/rest/reference/get/geo/reverse_geocode
26
- # @note This request is an informative call and will deliver generalized results about geography.
31
+ # @note Delivers generalized results about geography.
27
32
  # @rate_limited Yes
28
33
  # @authentication Requires user context
29
34
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
35
+ # @example
36
+ # client.reverse_geocode(lat: 37.7821, long: -122.4093)
30
37
  # @param options [Hash] A customizable set of options.
31
- # @option options [Float] :lat The latitude to search around. 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.
32
- # @option options [Float] :long The longitude to search around. The valid range 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.
33
- # @option options [String] :accuracy ('0m') A hint on the "region" in which to search. If a number, then this is a radius in meters, but it can also take a string that is suffixed with ft to specify feet. If coming from a device, in practice, this value is whatever accuracy the device has measuring its location (whether it be coming from a GPS, WiFi triangulation, etc.).
34
- # @option options [String] :granularity ('neighborhood') This is the minimal granularity of place types to return and must be one of: 'poi', 'neighborhood', 'city', 'admin' or 'country'.
35
- # @option options [Integer] :max_results A hint as to the number of results to return. This does not guarantee that the number of results returned will equal max_results, but instead informs how many "nearby" results to return. Ideally, only pass in the number of places you intend to display to the user here.
38
+ # @option options [Float] :lat The latitude to search around.
39
+ # @option options [Float] :long The longitude to search around.
40
+ # @option options [String] :accuracy ('0m') A hint on the region in which to search.
41
+ # @option options [String] :granularity ('neighborhood') Minimal granularity of place types to return.
42
+ # @option options [Integer] :max_results A hint as to the number of results to return.
36
43
  # @return [Array<Twitter::Place>]
37
44
  def reverse_geocode(options = {})
38
- perform_get_with_object("/1.1/geo/reverse_geocode.json", options, Twitter::GeoResults)
45
+ perform_get_with_object("/1.1/geo/reverse_geocode.json", options, GeoResults)
39
46
  end
40
47
 
41
- # Search for places that can be attached to a {Twitter::REST::Tweets#update}
48
+ # Searches for places that can be attached to a tweet
42
49
  #
50
+ # @api public
43
51
  # @see https://dev.twitter.com/rest/reference/get/geo/search
44
52
  # @rate_limited Yes
45
53
  # @authentication Requires user context
46
54
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
55
+ # @example
56
+ # client.geo_search(query: 'Twitter HQ')
47
57
  # @param options [Hash] A customizable set of options.
48
- # @option options [Float] :lat The latitude to search around. 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.
49
- # @option options [Float] :long The longitude to search around. The valid range 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.
50
- # @option options [String] :query Free-form text to match against while executing a geo-based query, best suited for finding nearby locations by name.
51
- # @option options [String] :ip An IP address. Used when attempting to fix geolocation based off of the user's IP address.
52
- # @option options [String] :granularity ('neighborhood') This is the minimal granularity of place types to return and must be one of: 'poi', 'neighborhood', 'city', 'admin' or 'country'.
53
- # @option options [String] :accuracy ('0m') A hint on the "region" in which to search. If a number, then this is a radius in meters, but it can also take a string that is suffixed with ft to specify feet. If coming from a device, in practice, this value is whatever accuracy the device has measuring its location (whether it be coming from a GPS, WiFi triangulation, etc.).
54
- # @option options [Integer] :max_results A hint as to the number of results to return. This does not guarantee that the number of results returned will equal max_results, but instead informs how many "nearby" results to return. Ideally, only pass in the number of places you intend to display to the user here.
55
- # @option options [String] :contained_within This is the place_id which you would like to restrict the search results to. Setting this value means only places within the given place_id will be found.
56
- # @option options [String] :"attribute:street_address" This option searches for places which have this given street address. There are other well-known and application-specific attributes available. Custom attributes are also permitted.
58
+ # @option options [Float] :lat The latitude to search around.
59
+ # @option options [Float] :long The longitude to search around.
60
+ # @option options [String] :query Free-form text to match against.
61
+ # @option options [String] :ip An IP address for geolocation.
62
+ # @option options [String] :granularity ('neighborhood') Minimal granularity of place types to return.
63
+ # @option options [String] :accuracy ('0m') A hint on the region in which to search.
64
+ # @option options [Integer] :max_results A hint as to the number of results to return.
65
+ # @option options [String] :contained_within The place_id to restrict results to.
57
66
  # @return [Array<Twitter::Place>]
58
67
  def geo_search(options = {})
59
- perform_get_with_object("/1.1/geo/search.json", options, Twitter::GeoResults)
68
+ perform_get_with_object("/1.1/geo/search.json", options, GeoResults)
60
69
  end
61
- alias places_nearby geo_search
70
+ # @!method places_nearby
71
+ # @api public
72
+ # @see #geo_search
73
+ alias_method :places_nearby, :geo_search
62
74
 
63
75
  # Locates places near the given coordinates which are similar in name
64
76
  #
77
+ # @api public
65
78
  # @see https://dev.twitter.com/rest/reference/get/geo/similar_places
66
- # @note Conceptually, you would use this method to get a list of known places to choose from first. Then, if the desired place doesn't exist, make a request to {Twitter::REST::PlacesAndGeo#place} to create a new one. The token contained in the response is the token necessary to create a new place.
67
79
  # @rate_limited Yes
68
80
  # @authentication Requires user context
69
81
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
82
+ # @example
83
+ # client.similar_places(lat: 37.7821, long: -122.4093, name: 'Twitter')
70
84
  # @param options [Hash] A customizable set of options.
71
- # @option options [Float] :lat The latitude to search around. 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.
72
- # @option options [Float] :long The longitude to search around. The valid range 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.
85
+ # @option options [Float] :lat The latitude to search around.
86
+ # @option options [Float] :long The longitude to search around.
73
87
  # @option options [String] :name The name a place is known as.
74
- # @option options [String] :contained_within This is the place_id which you would like to restrict the search results to. Setting this value means only places within the given place_id will be found.
75
- # @option options [String] :"attribute:street_address" This option searches for places which have this given street address. There are other well-known and application-specific attributes available. Custom attributes are also permitted.
88
+ # @option options [String] :contained_within The place_id to restrict results to.
76
89
  # @return [Array<Twitter::Place>]
77
90
  def similar_places(options = {})
78
- perform_get_with_object("/1.1/geo/similar_places.json", options, Twitter::GeoResults)
91
+ perform_get_with_object("/1.1/geo/similar_places.json", options, GeoResults)
79
92
  end
80
- alias places_similar similar_places
93
+ # @!method places_similar
94
+ # @api public
95
+ # @see #similar_places
96
+ alias_method :places_similar, :similar_places
81
97
  end
82
98
  end
83
99
  end
@@ -3,31 +3,36 @@ require "twitter/premium_search_results"
3
3
 
4
4
  module Twitter
5
5
  module REST
6
+ # Methods for Premium Search API
6
7
  module PremiumSearch
8
+ # Maximum tweets per request
7
9
  MAX_TWEETS_PER_REQUEST = 100
8
10
 
9
- # Returns tweets from the 30-Day API that match a specified query.
11
+ # Returns tweets from the Premium API that match a specified query
10
12
  #
13
+ # @api public
11
14
  # @see https://developer.twitter.com/en/docs/tweets/search/overview/premium
12
- # @see https://developer.twitter.com/en/docs/tweets/search/api-reference/premium-search.html#DataEndpoint
13
15
  # @rate_limited Yes
14
16
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
17
+ # @example
18
+ # client.premium_search('twitter')
15
19
  # @param query [String] A search term.
16
20
  # @param options [Hash] A customizable set of options.
17
- # @option options [String] :tag Tags can be used to segregate rules and their matching data into different logical groups.
18
- # @option options [Integer] :maxResults The maximum number of search results to be returned by a request. A number between 10 and the system limit (currently 500, 100 for Sandbox environments). By default, a request response will return 100 results
19
- # @option options [String] :fromDate The oldest UTC timestamp (from most recent 30 days) from which the Tweets will be provided. Date should be formatted as yyyymmddhhmm.
20
- # @option options [String] :toDate The latest, most recent UTC timestamp to which the activities will be provided. Date should be formatted as yyyymmddhhmm.
21
- # @option request_config [String] :product Indicates the search endpoint you are making requests to, either 30day or fullarchive. Default 30day
22
- # @return [Twitter::PremiumSearchResults] Return tweets that match a specified query with search metadata
21
+ # @option options [String] :tag Tags for segregating rules into logical groups.
22
+ # @option options [Integer] :maxResults The maximum number of search results.
23
+ # @option options [String] :fromDate The oldest UTC timestamp.
24
+ # @option options [String] :toDate The latest UTC timestamp.
25
+ # @param request_config [Hash] Request configuration options.
26
+ # @option request_config [String] :product The search endpoint (30day or fullarchive).
27
+ # @return [Twitter::PremiumSearchResults] Tweets matching the query.
23
28
  def premium_search(query, options = {}, request_config = {})
24
29
  options = options.clone
25
30
  options[:maxResults] ||= MAX_TWEETS_PER_REQUEST
26
- request_config[:request_method] = :json_post if request_config[:request_method].nil? || request_config[:request_method] == :post
31
+ request_config[:request_method] = :json_post if request_config[:request_method].nil? || request_config.fetch(:request_method).eql?(:post)
27
32
  request_config[:product] ||= "30day"
28
- path = "/1.1/tweets/search/#{request_config[:product]}/#{dev_environment}.json"
29
- request = Twitter::REST::Request.new(self, request_config[:request_method], path, options.merge(query:))
30
- Twitter::PremiumSearchResults.new(request, request_config)
33
+ path = "/1.1/tweets/search/#{request_config.fetch(:product)}/#{dev_environment}.json" # steep:ignore NoMethod
34
+ request = Request.new(self, request_config.fetch(:request_method), path, options.merge(query:))
35
+ PremiumSearchResults.new(request, request_config)
31
36
  end
32
37
  end
33
38
  end
@@ -1,6 +1,5 @@
1
- require "addressable/uri"
1
+ require "uri"
2
2
  require "http"
3
- require "http/form_data"
4
3
  require "json"
5
4
  require "openssl"
6
5
  require "twitter/error"
@@ -11,128 +10,242 @@ require "twitter/rest/form_encoder"
11
10
 
12
11
  module Twitter
13
12
  module REST
13
+ # Handles HTTP requests to the Twitter API
14
14
  class Request # rubocop:disable Metrics/ClassLength
15
15
  include Twitter::Utils
16
+
17
+ # The base URL for Twitter API requests
16
18
  BASE_URL = "https://api.twitter.com".freeze
17
- attr_accessor :client, :headers, :options, :path, :rate_limit,
18
- :request_method, :uri
19
- alias verb request_method
20
19
 
20
+ # The client making the request
21
+ #
22
+ # @api public
23
+ # @example
24
+ # request.client # => #<Twitter::REST::Client>
25
+ # @return [Twitter::Client]
26
+ attr_accessor :client
27
+
28
+ # The request headers
29
+ #
30
+ # @api public
31
+ # @example
32
+ # request.headers # => {user_agent: "...", authorization: "..."}
33
+ # @return [Hash]
34
+ attr_accessor :headers
35
+
36
+ # The request options
37
+ #
38
+ # @api public
39
+ # @example
40
+ # request.options # => {count: 10}
41
+ # @return [Hash]
42
+ attr_accessor :options
43
+
44
+ # The request path
45
+ #
46
+ # @api public
47
+ # @example
48
+ # request.path # => "/1.1/statuses/home_timeline.json"
49
+ # @return [String]
50
+ attr_accessor :path
51
+
52
+ # The rate limit information from the response
53
+ #
54
+ # @api public
55
+ # @example
56
+ # request.rate_limit # => #<Twitter::RateLimit>
57
+ # @return [Twitter::RateLimit]
58
+ attr_accessor :rate_limit
59
+
60
+ # The HTTP request method
61
+ #
62
+ # @api public
63
+ # @example
64
+ # request.request_method # => :get
65
+ # @return [Symbol]
66
+ attr_accessor :request_method
67
+
68
+ # The request URI
69
+ #
70
+ # @api public
71
+ # @example
72
+ # request.uri # => #<URI::HTTPS>
73
+ # @return [URI::Generic]
74
+ attr_accessor :uri
75
+
76
+ # Returns the HTTP verb
77
+ #
78
+ # @api public
79
+ # @example
80
+ # request.verb # => :get
81
+ # @return [Symbol]
82
+ def verb
83
+ request_method
84
+ end
85
+
86
+ # Initializes a new Request
87
+ #
88
+ # @api public
89
+ # @example
90
+ # Twitter::REST::Request.new(client, :get, "/1.1/statuses/home_timeline.json")
21
91
  # @param client [Twitter::Client]
22
92
  # @param request_method [String, Symbol]
23
93
  # @param path [String]
24
94
  # @param options [Hash]
95
+ # @param params [Hash]
25
96
  # @return [Twitter::REST::Request]
26
97
  def initialize(client, request_method, path, options = {}, params = nil)
27
98
  @client = client
28
- @uri = Addressable::URI.parse(path.start_with?("http") ? path : BASE_URL + path)
99
+ @uri = URI.parse(path.start_with?("http") ? path : BASE_URL + path)
29
100
  multipart_options = params || options
30
101
  set_multipart_options!(request_method, multipart_options)
31
- @path = uri.path
102
+ @path = uri.path # steep:ignore NoMethod,IncompatibleAssignment
32
103
  @options = options
33
104
  @options_key = {get: :params, json_post: :json, json_put: :json, delete: :params}[request_method] || :form
34
105
  @params = params
35
106
  end
36
107
 
108
+ # Performs the HTTP request and returns the response
109
+ #
110
+ # @api public
111
+ # @example
112
+ # request.perform # => [{id: 123, text: "Hello"}]
37
113
  # @return [Array, Hash]
38
114
  def perform
39
- response = http_client.headers(@headers).public_send(@request_method, @uri.to_s, request_options)
115
+ response = http_client.headers(@headers).public_send(@request_method, @uri.to_str, **request_options)
40
116
  response_body = response.body.empty? ? "" : symbolize_keys!(response.parse)
41
- response_headers = response.headers
42
- fail_or_return_response_body(response.code, response_body, response_headers)
117
+ fail_or_return_response_body(response.code, response_body, response)
43
118
  end
44
119
 
45
- private
120
+ private
46
121
 
122
+ # Build the request options hash
123
+ #
124
+ # @api private
125
+ # @return [Hash]
47
126
  def request_options
48
- options = if @options_key == :form
49
- {form: HTTP::FormData.create(@options, encoder: Twitter::REST::FormEncoder.method(:encode))}
50
- else
51
- {@options_key => @options}
52
- end
53
-
54
- if @params
55
- if options[:params]
56
- options[:params].merge(@params)
57
- else
58
- options[:params] = @params
59
- end
127
+ options = if @options_key.eql?(:form)
128
+ {form: HTTP::FormData.create(@options, encoder: FormEncoder.public_method(:encode))}
129
+ else
130
+ {@options_key => @options}
60
131
  end
132
+
133
+ options[:params] = @params if @params # steep:ignore ArgumentTypeMismatch
61
134
  options
62
135
  end
63
136
 
137
+ # Merge multipart file data into options
138
+ #
139
+ # @api private
140
+ # @param options [Hash]
141
+ # @return [void]
64
142
  def merge_multipart_file!(options)
65
143
  key = options.delete(:key)
66
144
  file = options.delete(:file)
67
145
 
68
- options[key] = if file.is_a?(StringIO)
69
- HTTP::FormData::File.new(file, content_type: "video/mp4")
70
- else
71
- HTTP::FormData::File.new(file, filename: File.basename(file), content_type: content_type(File.basename(file)))
72
- end
146
+ options[key] = if file.instance_of?(StringIO)
147
+ HTTP::FormData::File.new(file, content_type: "video/mp4")
148
+ else
149
+ HTTP::FormData::File.new(file, filename: File.basename(file), content_type: content_type(File.basename(file)))
150
+ end
73
151
  end
74
152
 
153
+ # Set multipart and header options based on request method
154
+ #
155
+ # @api private
156
+ # @param request_method [Symbol]
157
+ # @param options [Hash]
158
+ # @return [void]
75
159
  def set_multipart_options!(request_method, options)
76
160
  if %i[multipart_post json_post].include?(request_method)
77
- merge_multipart_file!(options) if request_method == :multipart_post
78
- options = {}
161
+ merge_multipart_file!(options) if request_method.eql?(:multipart_post)
162
+ options = {} # : Hash[Symbol, untyped]
79
163
  @request_method = :post
80
- elsif request_method == :json_put
164
+ elsif request_method.eql?(:json_put)
81
165
  @request_method = :put
82
166
  else
83
167
  @request_method = request_method
84
168
  end
85
- @headers = Twitter::Headers.new(@client, @request_method, @uri, options).request_headers
169
+ @headers = Headers.new(@client, @request_method, @uri, options).request_headers # steep:ignore ArgumentTypeMismatch
86
170
  end
87
171
 
172
+ # Determine content type based on file extension
173
+ #
174
+ # @api private
175
+ # @param basename [String]
176
+ # @return [String]
88
177
  def content_type(basename)
89
178
  case basename
90
- when /\.gif$/i
179
+ when /\.gif\z/i
91
180
  "image/gif"
92
- when /\.jpe?g/i
181
+ when /\.jpe?g\z/i
93
182
  "image/jpeg"
94
- when /\.png$/i
183
+ when /\.png\z/i
95
184
  "image/png"
96
185
  else
97
186
  "application/octet-stream"
98
187
  end
99
188
  end
100
189
 
101
- def fail_or_return_response_body(code, body, headers)
190
+ # Check response and return body or raise error
191
+ #
192
+ # @api private
193
+ # @param code [Integer]
194
+ # @param body [Hash, Array, String]
195
+ # @param response [HTTP::Response]
196
+ # @return [Hash, Array, String]
197
+ def fail_or_return_response_body(code, body, response)
198
+ headers = response.headers
102
199
  error = error(code, body, headers)
103
200
  raise(error) if error
104
201
 
105
- @rate_limit = Twitter::RateLimit.new(headers)
202
+ @rate_limit = RateLimit.new(headers)
106
203
  body
107
204
  end
108
205
 
109
- def error(code, body, headers)
110
- klass = Twitter::Error::ERRORS[code]
111
- if klass == Twitter::Error::Forbidden
112
- forbidden_error(body, headers)
206
+ # Build error object from response
207
+ #
208
+ # @api private
209
+ # @param code [Integer]
210
+ # @param body [Hash, Array, String]
211
+ # @param response [HTTP::Response]
212
+ # @return [Twitter::Error, nil]
213
+ def error(code, body, response)
214
+ klass = Error::ERRORS[code]
215
+ if klass.eql?(Error::Forbidden)
216
+ forbidden_error(body, response)
113
217
  elsif !klass.nil?
114
- klass.from_response(body, headers)
115
- elsif body.is_a?(Hash) && (err = body.dig(:processing_info, :error))
116
- Twitter::Error::MediaError.from_processing_response(err, headers)
218
+ klass.from_response(body, response)
219
+ elsif body.instance_of?(Hash) && (err = body.dig(:processing_info, :error))
220
+ Error::MediaError.from_processing_response(err, response)
117
221
  end
118
222
  end
119
223
 
120
- def forbidden_error(body, headers)
121
- error = Twitter::Error::Forbidden.from_response(body, headers)
122
- klass = Twitter::Error::FORBIDDEN_MESSAGES[error.message]
224
+ # Build forbidden error with specific message handling
225
+ #
226
+ # @api private
227
+ # @param body [Hash, Array, String]
228
+ # @param response [HTTP::Response]
229
+ # @return [Twitter::Error]
230
+ def forbidden_error(body, response)
231
+ error = Error::Forbidden.from_response(body, response)
232
+ klass = Error::FORBIDDEN_MESSAGES[error.message]
123
233
  if klass
124
- klass.from_response(body, headers)
234
+ klass.from_response(body, response)
125
235
  else
126
236
  error
127
237
  end
128
238
  end
129
239
 
240
+ # Recursively symbolize hash keys
241
+ #
242
+ # @api private
243
+ # @param object [Hash, Array, Object]
244
+ # @return [Hash, Array, Object]
130
245
  def symbolize_keys!(object)
131
246
  case object
132
247
  when Array
133
- object.each_with_index do |val, index|
134
- object[index] = symbolize_keys!(val)
135
- end
248
+ object.each { |val| symbolize_keys!(val) }
136
249
  when Hash
137
250
  object.dup.each_key do |key|
138
251
  object[key.to_sym] = symbolize_keys!(object.delete(key))
@@ -141,22 +254,27 @@ module Twitter
141
254
  object
142
255
  end
143
256
 
144
- # Returns boolean indicating if all the keys required by HTTP::Client are present in Twitter::Client#timeouts
257
+ # Check if all timeout keys are defined
145
258
  #
259
+ # @api private
146
260
  # @return [Boolean]
147
- def timeout_keys_defined
261
+ def timeout_keys_defined?
148
262
  (%i[write connect read] - (@client.timeouts&.keys || [])).empty?
149
263
  end
150
264
 
151
- # @return [HTTP::Client, HTTP]
265
+ # Build HTTP client with proxy and timeout settings
266
+ #
267
+ # @api private
268
+ # @return [HTTP::Session, HTTP]
152
269
  def http_client
153
270
  client = @client.proxy ? HTTP.via(*proxy) : HTTP
154
- client = client.timeout(connect: @client.timeouts[:connect], read: @client.timeouts[:read], write: @client.timeouts[:write]) if timeout_keys_defined
271
+ client = client.timeout(connect: @client.timeouts.fetch(:connect), read: @client.timeouts.fetch(:read), write: @client.timeouts.fetch(:write)) if timeout_keys_defined?
155
272
  client
156
273
  end
157
274
 
158
275
  # Return proxy values as a compacted array
159
276
  #
277
+ # @api private
160
278
  # @return [Array]
161
279
  def proxy
162
280
  @client.proxy.values_at(:host, :port, :username, :password).compact
@@ -5,13 +5,19 @@ require "twitter/utils"
5
5
 
6
6
  module Twitter
7
7
  module REST
8
+ # Methods for working with saved searches
8
9
  module SavedSearches
9
10
  include Twitter::REST::Utils
10
11
  include Twitter::Utils
11
12
 
13
+ # Returns the authenticated user's saved search queries
14
+ #
15
+ # @api public
12
16
  # @rate_limited Yes
13
17
  # @authentication Requires user context
14
18
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
19
+ # @example
20
+ # client.saved_searches
15
21
  # @return [Array<Twitter::SavedSearch>] The saved searches.
16
22
  # @overload saved_search(options = {})
17
23
  # Returns the authenticated user's saved search queries
@@ -30,9 +36,9 @@ module Twitter
30
36
  # @param ids [Enumerable<Integer>] A collection of saved search IDs.
31
37
  # @param options [Hash] A customizable set of options.
32
38
  def saved_searches(*args)
33
- arguments = Twitter::Arguments.new(args)
39
+ arguments = Arguments.new(args)
34
40
  if arguments.empty?
35
- perform_get_with_objects("/1.1/saved_searches/list.json", arguments.options, Twitter::SavedSearch)
41
+ perform_get_with_objects("/1.1/saved_searches/list.json", arguments.options, SavedSearch)
36
42
  else
37
43
  pmap(arguments) do |id|
38
44
  saved_search(id, arguments.options)
@@ -40,39 +46,48 @@ module Twitter
40
46
  end
41
47
  end
42
48
 
43
- # Retrieve the data for saved searches owned by the authenticating user
49
+ # Retrieves a saved search by ID
44
50
  #
51
+ # @api public
45
52
  # @see https://dev.twitter.com/rest/reference/get/saved_searches/show/:id
46
53
  # @rate_limited Yes
47
54
  # @authentication Requires user context
48
55
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
56
+ # @example
57
+ # client.saved_search(16129012)
49
58
  # @return [Twitter::SavedSearch] The saved searches.
50
59
  # @param id [Integer] The ID of the saved search.
51
60
  # @param options [Hash] A customizable set of options.
52
61
  def saved_search(id, options = {})
53
- perform_get_with_object("/1.1/saved_searches/show/#{id}.json", options, Twitter::SavedSearch)
62
+ perform_get_with_object("/1.1/saved_searches/show/#{id}.json", options, SavedSearch)
54
63
  end
55
64
 
56
65
  # Creates a saved search for the authenticated user
57
66
  #
67
+ # @api public
58
68
  # @see https://dev.twitter.com/rest/reference/post/saved_searches/create
59
69
  # @rate_limited No
60
70
  # @authentication Requires user context
61
71
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
72
+ # @example
73
+ # client.create_saved_search('twitter')
62
74
  # @return [Twitter::SavedSearch] The created saved search.
63
75
  # @param query [String] The query of the search the user would like to save.
64
76
  # @param options [Hash] A customizable set of options.
65
77
  def create_saved_search(query, options = {})
66
- perform_post_with_object("/1.1/saved_searches/create.json", options.merge(query:), Twitter::SavedSearch)
78
+ perform_post_with_object("/1.1/saved_searches/create.json", options.merge(query:), SavedSearch)
67
79
  end
68
80
 
69
81
  # Destroys saved searches for the authenticated user
70
82
  #
83
+ # @api public
71
84
  # @see https://dev.twitter.com/rest/reference/post/saved_searches/destroy/:id
72
85
  # @note The search specified by ID must be owned by the authenticating user.
73
86
  # @rate_limited No
74
87
  # @authentication Requires user context
75
88
  # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
89
+ # @example
90
+ # client.destroy_saved_search(16129012)
76
91
  # @return [Array<Twitter::SavedSearch>] The deleted saved searches.
77
92
  # @overload destroy_saved_search(*ids)
78
93
  # @param ids [Enumerable<Integer>] A collection of saved search IDs.
@@ -80,9 +95,9 @@ module Twitter
80
95
  # @param ids [Enumerable<Integer>] A collection of saved search IDs.
81
96
  # @param options [Hash] A customizable set of options.
82
97
  def destroy_saved_search(*args)
83
- arguments = Twitter::Arguments.new(args)
98
+ arguments = Arguments.new(args)
84
99
  pmap(arguments) do |id|
85
- perform_post_with_object("/1.1/saved_searches/destroy/#{id}.json", arguments.options, Twitter::SavedSearch)
100
+ perform_post_with_object("/1.1/saved_searches/destroy/#{id}.json", arguments.options, SavedSearch)
86
101
  end
87
102
  end
88
103
  end