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.
- checksums.yaml +4 -4
- data/LICENSE.md +1 -1
- data/README.md +172 -10
- data/lib/twitter/arguments.rb +14 -1
- data/lib/twitter/base.rb +72 -11
- data/lib/twitter/basic_user.rb +7 -1
- data/lib/twitter/client.rb +94 -3
- data/lib/twitter/creatable.rb +11 -2
- data/lib/twitter/cursor.rb +58 -11
- data/lib/twitter/direct_message.rb +32 -4
- data/lib/twitter/direct_message_event.rb +34 -10
- data/lib/twitter/direct_messages/welcome_message.rb +22 -1
- data/lib/twitter/direct_messages/welcome_message_rule.rb +7 -0
- data/lib/twitter/direct_messages/welcome_message_rule_wrapper.rb +26 -3
- data/lib/twitter/direct_messages/welcome_message_wrapper.rb +36 -11
- data/lib/twitter/entities.rb +84 -8
- data/lib/twitter/entity/hashtag.rb +7 -1
- data/lib/twitter/entity/symbol.rb +7 -1
- data/lib/twitter/entity/uri.rb +2 -1
- data/lib/twitter/entity/user_mention.rb +20 -1
- data/lib/twitter/entity.rb +7 -1
- data/lib/twitter/enumerable.rb +20 -3
- data/lib/twitter/error.rb +137 -61
- data/lib/twitter/factory.rb +9 -5
- data/lib/twitter/geo/point.rb +37 -5
- data/lib/twitter/geo/polygon.rb +1 -0
- data/lib/twitter/geo.rb +16 -2
- data/lib/twitter/geo_factory.rb +7 -3
- data/lib/twitter/geo_results.rb +39 -8
- data/lib/twitter/headers.rb +44 -7
- data/lib/twitter/identity.rb +13 -3
- data/lib/twitter/language.rb +21 -1
- data/lib/twitter/list.rb +101 -11
- data/lib/twitter/media/animated_gif.rb +1 -0
- data/lib/twitter/media/photo.rb +19 -3
- data/lib/twitter/media/video.rb +21 -3
- data/lib/twitter/media/video_info.rb +15 -1
- data/lib/twitter/media_factory.rb +7 -3
- data/lib/twitter/metadata.rb +14 -1
- data/lib/twitter/null_object.rb +16 -14
- data/lib/twitter/oembed.rb +56 -2
- data/lib/twitter/place.rb +74 -6
- data/lib/twitter/premium_search_results.rb +87 -18
- data/lib/twitter/profile.rb +100 -44
- data/lib/twitter/profile_banner.rb +9 -4
- data/lib/twitter/rate_limit.rb +32 -3
- data/lib/twitter/relationship.rb +8 -5
- data/lib/twitter/rest/account_activity.rb +55 -26
- data/lib/twitter/rest/api.rb +2 -0
- data/lib/twitter/rest/client.rb +18 -0
- data/lib/twitter/rest/direct_messages/welcome_messages.rb +89 -18
- data/lib/twitter/rest/direct_messages.rb +160 -96
- data/lib/twitter/rest/favorites.rb +58 -22
- data/lib/twitter/rest/form_encoder.rb +57 -17
- data/lib/twitter/rest/friends_and_followers.rb +101 -35
- data/lib/twitter/rest/help.rb +13 -3
- data/lib/twitter/rest/lists.rb +136 -47
- data/lib/twitter/rest/oauth.rb +21 -15
- data/lib/twitter/rest/places_and_geo.rb +44 -28
- data/lib/twitter/rest/premium_search.rb +17 -12
- data/lib/twitter/rest/request.rb +173 -55
- data/lib/twitter/rest/saved_searches.rb +22 -7
- data/lib/twitter/rest/search.rb +20 -16
- data/lib/twitter/rest/spam_reporting.rb +5 -1
- data/lib/twitter/rest/suggested_users.rb +15 -6
- data/lib/twitter/rest/timelines.rb +92 -52
- data/lib/twitter/rest/trends.rb +32 -13
- data/lib/twitter/rest/tweets.rb +147 -89
- data/lib/twitter/rest/undocumented.rb +10 -1
- data/lib/twitter/rest/upload_utils.rb +45 -29
- data/lib/twitter/rest/users.rb +151 -72
- data/lib/twitter/rest/utils.rb +135 -39
- data/lib/twitter/saved_search.rb +23 -2
- data/lib/twitter/search_results.rb +66 -18
- data/lib/twitter/settings.rb +37 -11
- data/lib/twitter/size.rb +37 -3
- data/lib/twitter/source_user.rb +4 -3
- data/lib/twitter/streaming/client.rb +61 -9
- data/lib/twitter/streaming/connection.rb +54 -6
- data/lib/twitter/streaming/deleted_tweet.rb +8 -0
- data/lib/twitter/streaming/event.rb +43 -1
- data/lib/twitter/streaming/friend_list.rb +1 -0
- data/lib/twitter/streaming/message_parser.rb +20 -10
- data/lib/twitter/streaming/response.rb +31 -5
- data/lib/twitter/streaming/stall_warning.rb +23 -0
- data/lib/twitter/suggestion.rb +25 -1
- data/lib/twitter/target_user.rb +2 -1
- data/lib/twitter/trend.rb +29 -1
- data/lib/twitter/trend_results.rb +50 -7
- data/lib/twitter/tweet.rb +180 -21
- data/lib/twitter/user.rb +289 -53
- data/lib/twitter/utils.rb +12 -13
- data/lib/twitter/variant.rb +12 -1
- data/lib/twitter/version.rb +66 -29
- data/lib/twitter.rb +6 -1
- metadata +23 -57
- data/.yardopts +0 -16
- data/CHANGELOG.md +0 -1040
- data/CONTRIBUTING.md +0 -49
- 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
|
-
# @
|
|
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,
|
|
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
|
|
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.
|
|
32
|
-
# @option options [Float] :long The longitude to search around.
|
|
33
|
-
# @option options [String] :accuracy ('0m') A hint on the
|
|
34
|
-
# @option options [String] :granularity ('neighborhood')
|
|
35
|
-
# @option options [Integer] :max_results A hint as to the number of results to return.
|
|
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,
|
|
45
|
+
perform_get_with_object("/1.1/geo/reverse_geocode.json", options, GeoResults)
|
|
39
46
|
end
|
|
40
47
|
|
|
41
|
-
#
|
|
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.
|
|
49
|
-
# @option options [Float] :long The longitude to search around.
|
|
50
|
-
# @option options [String] :query Free-form text to match against
|
|
51
|
-
# @option options [String] :ip An IP address
|
|
52
|
-
# @option options [String] :granularity ('neighborhood')
|
|
53
|
-
# @option options [String] :accuracy ('0m') A hint on the
|
|
54
|
-
# @option options [Integer] :max_results A hint as to the number of results to return.
|
|
55
|
-
# @option options [String] :contained_within
|
|
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,
|
|
68
|
+
perform_get_with_object("/1.1/geo/search.json", options, GeoResults)
|
|
60
69
|
end
|
|
61
|
-
|
|
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.
|
|
72
|
-
# @option options [Float] :long The longitude to search around.
|
|
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
|
|
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,
|
|
91
|
+
perform_get_with_object("/1.1/geo/similar_places.json", options, GeoResults)
|
|
79
92
|
end
|
|
80
|
-
|
|
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
|
|
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
|
|
18
|
-
# @option options [Integer] :maxResults The maximum number of search results
|
|
19
|
-
# @option options [String] :fromDate The oldest UTC timestamp
|
|
20
|
-
# @option options [String] :toDate The latest
|
|
21
|
-
# @
|
|
22
|
-
# @
|
|
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
|
|
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
|
|
29
|
-
request =
|
|
30
|
-
|
|
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
|
data/lib/twitter/rest/request.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
require "
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
202
|
+
@rate_limit = RateLimit.new(headers)
|
|
106
203
|
body
|
|
107
204
|
end
|
|
108
205
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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,
|
|
115
|
-
elsif body.
|
|
116
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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,
|
|
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.
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
|
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 =
|
|
39
|
+
arguments = Arguments.new(args)
|
|
34
40
|
if arguments.empty?
|
|
35
|
-
perform_get_with_objects("/1.1/saved_searches/list.json", arguments.options,
|
|
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
|
-
#
|
|
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,
|
|
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:),
|
|
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 =
|
|
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,
|
|
100
|
+
perform_post_with_object("/1.1/saved_searches/destroy/#{id}.json", arguments.options, SavedSearch)
|
|
86
101
|
end
|
|
87
102
|
end
|
|
88
103
|
end
|