twitter 4.8.1 → 5.0.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.
- data/CHANGELOG.md +36 -0
- data/CONTRIBUTING.md +13 -15
- data/LICENSE.md +1 -1
- data/README.md +401 -261
- data/Rakefile +6 -0
- data/lib/twitter/arguments.rb +14 -0
- data/lib/twitter/base.rb +109 -89
- data/lib/twitter/client.rb +38 -115
- data/lib/twitter/configuration.rb +5 -2
- data/lib/twitter/core_ext/kernel.rb +5 -1
- data/lib/twitter/creatable.rb +7 -4
- data/lib/twitter/cursor.rb +57 -45
- data/lib/twitter/direct_message.rb +2 -11
- data/lib/twitter/entity/uri.rb +14 -0
- data/lib/twitter/enumerable.rb +15 -0
- data/lib/twitter/error/already_favorited.rb +1 -1
- data/lib/twitter/error/already_posted.rb +10 -0
- data/lib/twitter/error/already_retweeted.rb +1 -1
- data/lib/twitter/error/bad_gateway.rb +2 -3
- data/lib/twitter/error/bad_request.rb +2 -2
- data/lib/twitter/error/forbidden.rb +2 -2
- data/lib/twitter/error/gateway_timeout.rb +2 -3
- data/lib/twitter/error/internal_server_error.rb +2 -3
- data/lib/twitter/error/not_acceptable.rb +2 -2
- data/lib/twitter/error/not_found.rb +2 -2
- data/lib/twitter/error/service_unavailable.rb +2 -3
- data/lib/twitter/error/too_many_requests.rb +2 -2
- data/lib/twitter/error/unauthorized.rb +2 -2
- data/lib/twitter/error/unprocessable_entity.rb +2 -2
- data/lib/twitter/error.rb +65 -14
- data/lib/twitter/factory.rb +13 -12
- data/lib/twitter/geo.rb +2 -7
- data/lib/twitter/geo_factory.rb +11 -7
- data/lib/twitter/geo_results.rb +40 -0
- data/lib/twitter/identity.rb +4 -34
- data/lib/twitter/list.rb +21 -4
- data/lib/twitter/media/photo.rb +6 -4
- data/lib/twitter/media_factory.rb +11 -7
- data/lib/twitter/null_object.rb +25 -0
- data/lib/twitter/oembed.rb +3 -2
- data/lib/twitter/place.rb +15 -15
- data/lib/twitter/profile_banner.rb +3 -2
- data/lib/twitter/rate_limit.rb +4 -17
- data/lib/twitter/relationship.rb +2 -19
- data/lib/twitter/rest/api/direct_messages.rb +138 -0
- data/lib/twitter/rest/api/favorites.rb +115 -0
- data/lib/twitter/rest/api/friends_and_followers.rb +287 -0
- data/lib/twitter/rest/api/help.rb +58 -0
- data/lib/twitter/rest/api/lists.rb +498 -0
- data/lib/twitter/rest/api/oauth.rb +62 -0
- data/lib/twitter/rest/api/places_and_geo.rb +86 -0
- data/lib/twitter/rest/api/saved_searches.rb +93 -0
- data/lib/twitter/rest/api/search.rb +37 -0
- data/lib/twitter/rest/api/spam_reporting.rb +29 -0
- data/lib/twitter/rest/api/suggested_users.rb +51 -0
- data/lib/twitter/rest/api/timelines.rb +202 -0
- data/lib/twitter/rest/api/trends.rb +58 -0
- data/lib/twitter/rest/api/tweets.rb +291 -0
- data/lib/twitter/rest/api/undocumented.rb +52 -0
- data/lib/twitter/rest/api/users.rb +383 -0
- data/lib/twitter/rest/api/utils.rb +246 -0
- data/lib/twitter/rest/client.rb +175 -0
- data/lib/twitter/rest/request/multipart_with_file.rb +36 -0
- data/lib/twitter/rest/response/parse_error_json.rb +15 -0
- data/lib/twitter/rest/response/parse_json.rb +31 -0
- data/lib/twitter/{response → rest/response}/raise_error.rb +8 -11
- data/lib/twitter/search_results.rb +37 -21
- data/lib/twitter/settings.rb +1 -6
- data/lib/twitter/size.rb +2 -15
- data/lib/twitter/streaming/client.rb +89 -0
- data/lib/twitter/streaming/connection.rb +22 -0
- data/lib/twitter/streaming/event.rb +35 -0
- data/lib/twitter/streaming/friend_list.rb +13 -0
- data/lib/twitter/streaming/message_parser.rb +18 -0
- data/lib/twitter/streaming/response.rb +34 -0
- data/lib/twitter/suggestion.rb +5 -8
- data/lib/twitter/token.rb +11 -1
- data/lib/twitter/trend.rb +4 -8
- data/lib/twitter/trend_results.rb +65 -0
- data/lib/twitter/tweet.rb +43 -92
- data/lib/twitter/user.rb +72 -47
- data/lib/twitter/version.rb +4 -4
- data/lib/twitter.rb +4 -39
- data/spec/fixtures/already_posted.json +1 -0
- data/spec/fixtures/ids_list.json +1 -1
- data/spec/fixtures/ids_list2.json +1 -1
- data/spec/fixtures/request_token.txt +6 -0
- data/spec/fixtures/search.json +1 -1
- data/spec/fixtures/search_malformed.json +1 -1
- data/spec/fixtures/track_streaming.json +3 -0
- data/spec/fixtures/track_streaming_user.json +5 -0
- data/spec/helper.rb +8 -13
- data/spec/twitter/base_spec.rb +13 -103
- data/spec/twitter/basic_user_spec.rb +3 -3
- data/spec/twitter/configuration_spec.rb +1 -1
- data/spec/twitter/cursor_spec.rb +17 -35
- data/spec/twitter/direct_message_spec.rb +44 -11
- data/spec/twitter/entity/uri_spec.rb +75 -0
- data/spec/twitter/error_spec.rb +59 -11
- data/spec/twitter/geo/point_spec.rb +6 -6
- data/spec/twitter/geo/polygon_spec.rb +5 -5
- data/spec/twitter/geo_factory_spec.rb +4 -4
- data/spec/twitter/geo_results_spec.rb +35 -0
- data/spec/twitter/geo_spec.rb +6 -6
- data/spec/twitter/identifiable_spec.rb +5 -26
- data/spec/twitter/list_spec.rb +54 -11
- data/spec/twitter/media/photo_spec.rb +122 -6
- data/spec/twitter/media_factory_spec.rb +3 -3
- data/spec/twitter/null_object_spec.rb +27 -0
- data/spec/twitter/oembed_spec.rb +69 -45
- data/spec/twitter/place_spec.rb +84 -28
- data/spec/twitter/profile_banner_spec.rb +1 -1
- data/spec/twitter/rate_limit_spec.rb +8 -25
- data/spec/twitter/relationship_spec.rb +26 -12
- data/spec/twitter/{api → rest/api}/direct_messages_spec.rb +28 -15
- data/spec/twitter/{api → rest/api}/favorites_spec.rb +80 -7
- data/spec/twitter/{api → rest/api}/friends_and_followers_spec.rb +121 -152
- data/spec/twitter/{api → rest/api}/geo_spec.rb +9 -23
- data/spec/twitter/{api → rest/api}/help_spec.rb +6 -6
- data/spec/twitter/{api → rest/api}/lists_spec.rb +116 -95
- data/spec/twitter/{api → rest/api}/oauth_spec.rb +21 -10
- data/spec/twitter/{api → rest/api}/saved_searches_spec.rb +13 -13
- data/spec/twitter/{api → rest/api}/search_spec.rb +8 -9
- data/spec/twitter/{api → rest/api}/spam_reporting_spec.rb +3 -3
- data/spec/twitter/{api → rest/api}/suggested_users_spec.rb +5 -5
- data/spec/twitter/{api → rest/api}/timelines_spec.rb +9 -9
- data/spec/twitter/{api → rest/api}/trends_spec.rb +6 -6
- data/spec/twitter/rest/api/tweets_spec.rb +503 -0
- data/spec/twitter/{api → rest/api}/undocumented_spec.rb +19 -45
- data/spec/twitter/{api → rest/api}/users_spec.rb +64 -39
- data/spec/twitter/rest/client_spec.rb +193 -0
- data/spec/twitter/saved_search_spec.rb +14 -3
- data/spec/twitter/search_results_spec.rb +32 -45
- data/spec/twitter/settings_spec.rb +17 -6
- data/spec/twitter/size_spec.rb +5 -15
- data/spec/twitter/source_user_spec.rb +3 -3
- data/spec/twitter/streaming/client_spec.rb +92 -0
- data/spec/twitter/streaming/event_spec.rb +45 -0
- data/spec/twitter/suggestion_spec.rb +5 -15
- data/spec/twitter/target_user_spec.rb +3 -3
- data/spec/twitter/token_spec.rb +16 -0
- data/spec/twitter/trend_results_spec.rb +89 -0
- data/spec/twitter/trend_spec.rb +26 -13
- data/spec/twitter/tweet_spec.rb +137 -124
- data/spec/twitter/user_spec.rb +139 -80
- data/spec/twitter_spec.rb +0 -119
- data/twitter.gemspec +12 -6
- data.tar.gz.sig +0 -0
- metadata +197 -138
- metadata.gz.sig +0 -0
- data/lib/twitter/action/favorite.rb +0 -19
- data/lib/twitter/action/follow.rb +0 -30
- data/lib/twitter/action/list_member_added.rb +0 -39
- data/lib/twitter/action/mention.rb +0 -46
- data/lib/twitter/action/reply.rb +0 -27
- data/lib/twitter/action/retweet.rb +0 -27
- data/lib/twitter/action/tweet.rb +0 -20
- data/lib/twitter/action_factory.rb +0 -22
- data/lib/twitter/api/arguments.rb +0 -13
- data/lib/twitter/api/direct_messages.rb +0 -148
- data/lib/twitter/api/favorites.rb +0 -126
- data/lib/twitter/api/friends_and_followers.rb +0 -334
- data/lib/twitter/api/help.rb +0 -64
- data/lib/twitter/api/lists.rb +0 -618
- data/lib/twitter/api/oauth.rb +0 -44
- data/lib/twitter/api/places_and_geo.rb +0 -121
- data/lib/twitter/api/saved_searches.rb +0 -99
- data/lib/twitter/api/search.rb +0 -37
- data/lib/twitter/api/spam_reporting.rb +0 -30
- data/lib/twitter/api/suggested_users.rb +0 -55
- data/lib/twitter/api/timelines.rb +0 -214
- data/lib/twitter/api/trends.rb +0 -63
- data/lib/twitter/api/tweets.rb +0 -304
- data/lib/twitter/api/undocumented.rb +0 -97
- data/lib/twitter/api/users.rb +0 -439
- data/lib/twitter/api/utils.rb +0 -187
- data/lib/twitter/configurable.rb +0 -96
- data/lib/twitter/default.rb +0 -102
- data/lib/twitter/entity/url.rb +0 -9
- data/lib/twitter/error/client_error.rb +0 -35
- data/lib/twitter/error/decode_error.rb +0 -9
- data/lib/twitter/error/identity_map_key_error.rb +0 -9
- data/lib/twitter/error/server_error.rb +0 -28
- data/lib/twitter/exceptable.rb +0 -36
- data/lib/twitter/identity_map.rb +0 -22
- data/lib/twitter/request/multipart_with_file.rb +0 -34
- data/lib/twitter/response/parse_json.rb +0 -25
- data/spec/fixtures/about_me.json +0 -1
- data/spec/fixtures/activity_summary.json +0 -1
- data/spec/fixtures/bad_gateway.json +0 -1
- data/spec/fixtures/bad_request.json +0 -1
- data/spec/fixtures/by_friends.json +0 -1
- data/spec/fixtures/end_session.json +0 -1
- data/spec/fixtures/forbidden.json +0 -1
- data/spec/fixtures/internal_server_error.json +0 -1
- data/spec/fixtures/not_acceptable.json +0 -1
- data/spec/fixtures/phoenix_search.phoenix +0 -1
- data/spec/fixtures/resolve.json +0 -1
- data/spec/fixtures/service_unavailable.json +0 -1
- data/spec/fixtures/totals.json +0 -1
- data/spec/fixtures/trends.json +0 -1
- data/spec/fixtures/unauthorized.json +0 -1
- data/spec/fixtures/video_facets.json +0 -1
- data/spec/twitter/action/favorite_spec.rb +0 -29
- data/spec/twitter/action/follow_spec.rb +0 -29
- data/spec/twitter/action/list_member_added_spec.rb +0 -41
- data/spec/twitter/action/mention_spec.rb +0 -52
- data/spec/twitter/action/reply_spec.rb +0 -41
- data/spec/twitter/action/retweet_spec.rb +0 -41
- data/spec/twitter/action_factory_spec.rb +0 -35
- data/spec/twitter/action_spec.rb +0 -16
- data/spec/twitter/api/tweets_spec.rb +0 -285
- data/spec/twitter/client_spec.rb +0 -223
- data/spec/twitter/error/client_error_spec.rb +0 -23
- data/spec/twitter/error/server_error_spec.rb +0 -20
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
require 'base64'
|
|
2
|
+
require 'faraday'
|
|
3
|
+
require 'faraday/request/multipart'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'twitter/client'
|
|
6
|
+
require 'twitter/error'
|
|
7
|
+
require 'twitter/error/configuration_error'
|
|
8
|
+
require 'twitter/rest/api/direct_messages'
|
|
9
|
+
require 'twitter/rest/api/favorites'
|
|
10
|
+
require 'twitter/rest/api/friends_and_followers'
|
|
11
|
+
require 'twitter/rest/api/help'
|
|
12
|
+
require 'twitter/rest/api/lists'
|
|
13
|
+
require 'twitter/rest/api/oauth'
|
|
14
|
+
require 'twitter/rest/api/places_and_geo'
|
|
15
|
+
require 'twitter/rest/api/saved_searches'
|
|
16
|
+
require 'twitter/rest/api/search'
|
|
17
|
+
require 'twitter/rest/api/spam_reporting'
|
|
18
|
+
require 'twitter/rest/api/suggested_users'
|
|
19
|
+
require 'twitter/rest/api/timelines'
|
|
20
|
+
require 'twitter/rest/api/trends'
|
|
21
|
+
require 'twitter/rest/api/tweets'
|
|
22
|
+
require 'twitter/rest/api/undocumented'
|
|
23
|
+
require 'twitter/rest/api/users'
|
|
24
|
+
require 'twitter/rest/request/multipart_with_file'
|
|
25
|
+
require 'twitter/rest/response/parse_json'
|
|
26
|
+
require 'twitter/rest/response/raise_error'
|
|
27
|
+
|
|
28
|
+
module Twitter
|
|
29
|
+
module REST
|
|
30
|
+
# Wrapper for the Twitter REST API
|
|
31
|
+
#
|
|
32
|
+
# @note All methods have been separated into modules and follow the same grouping used in {http://dev.twitter.com/doc the Twitter API Documentation}.
|
|
33
|
+
# @see http://dev.twitter.com/pages/every_developer
|
|
34
|
+
class Client < Twitter::Client
|
|
35
|
+
include Twitter::REST::API::DirectMessages
|
|
36
|
+
include Twitter::REST::API::Favorites
|
|
37
|
+
include Twitter::REST::API::FriendsAndFollowers
|
|
38
|
+
include Twitter::REST::API::Help
|
|
39
|
+
include Twitter::REST::API::Lists
|
|
40
|
+
include Twitter::REST::API::OAuth
|
|
41
|
+
include Twitter::REST::API::PlacesAndGeo
|
|
42
|
+
include Twitter::REST::API::SavedSearches
|
|
43
|
+
include Twitter::REST::API::Search
|
|
44
|
+
include Twitter::REST::API::SpamReporting
|
|
45
|
+
include Twitter::REST::API::SuggestedUsers
|
|
46
|
+
include Twitter::REST::API::Timelines
|
|
47
|
+
include Twitter::REST::API::Trends
|
|
48
|
+
include Twitter::REST::API::Tweets
|
|
49
|
+
include Twitter::REST::API::Undocumented
|
|
50
|
+
include Twitter::REST::API::Users
|
|
51
|
+
attr_accessor :bearer_token
|
|
52
|
+
attr_writer :connection_options, :middleware
|
|
53
|
+
ENDPOINT = 'https://api.twitter.com'
|
|
54
|
+
|
|
55
|
+
def connection_options
|
|
56
|
+
@connection_options ||= {
|
|
57
|
+
:builder => middleware,
|
|
58
|
+
:headers => {
|
|
59
|
+
:accept => 'application/json',
|
|
60
|
+
:user_agent => user_agent,
|
|
61
|
+
},
|
|
62
|
+
:request => {
|
|
63
|
+
:open_timeout => 5,
|
|
64
|
+
:timeout => 10,
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @note Faraday's middleware stack implementation is comparable to that of Rack middleware. The order of middleware is important: the first middleware on the list wraps all others, while the last middleware is the innermost one.
|
|
70
|
+
# @see https://github.com/technoweenie/faraday#advanced-middleware-usage
|
|
71
|
+
# @see http://mislav.uniqpath.com/2011/07/faraday-advanced-http/
|
|
72
|
+
# @return [Faraday::Builder]
|
|
73
|
+
def middleware
|
|
74
|
+
@middleware ||= Faraday::Builder.new do |builder|
|
|
75
|
+
# Convert file uploads to Faraday::UploadIO objects
|
|
76
|
+
builder.use Twitter::REST::Request::MultipartWithFile
|
|
77
|
+
# Checks for files in the payload
|
|
78
|
+
builder.use Faraday::Request::Multipart
|
|
79
|
+
# Convert request params to "www-form-urlencoded"
|
|
80
|
+
builder.use Faraday::Request::UrlEncoded
|
|
81
|
+
# Handle error responses
|
|
82
|
+
builder.use Twitter::REST::Response::RaiseError
|
|
83
|
+
# Parse JSON response bodies
|
|
84
|
+
builder.use Twitter::REST::Response::ParseJson
|
|
85
|
+
# Set Faraday's HTTP adapter
|
|
86
|
+
builder.adapter Faraday.default_adapter
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Perform an HTTP DELETE request
|
|
91
|
+
def delete(path, params={})
|
|
92
|
+
request(:delete, path, params)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Perform an HTTP GET request
|
|
96
|
+
def get(path, params={})
|
|
97
|
+
request(:get, path, params)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Perform an HTTP POST request
|
|
101
|
+
def post(path, params={})
|
|
102
|
+
signature_params = params.values.any?{|value| value.respond_to?(:to_io)} ? {} : params
|
|
103
|
+
request(:post, path, params, signature_params)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Perform an HTTP PUT request
|
|
107
|
+
def put(path, params={})
|
|
108
|
+
request(:put, path, params)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @return [Boolean]
|
|
112
|
+
def bearer_token?
|
|
113
|
+
!!bearer_token
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @return [Boolean]
|
|
117
|
+
def credentials?
|
|
118
|
+
super || bearer_token?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
# Returns a Faraday::Connection object
|
|
124
|
+
#
|
|
125
|
+
# @return [Faraday::Connection]
|
|
126
|
+
def connection
|
|
127
|
+
@connection ||= Faraday.new(ENDPOINT, connection_options)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def request(method, path, params={}, signature_params=params)
|
|
131
|
+
response = connection.send(method.to_sym, path, params) do |request|
|
|
132
|
+
bearer_token_request = params.delete(:bearer_token_request)
|
|
133
|
+
if bearer_token_request
|
|
134
|
+
request.headers[:accept] = '*/*' # It is important we set this, otherwise we get an error.
|
|
135
|
+
request.headers[:authorization] = bearer_token_credentials_auth_header
|
|
136
|
+
request.headers[:content_type] = 'application/x-www-form-urlencoded; charset=UTF-8'
|
|
137
|
+
else
|
|
138
|
+
request.headers[:authorization] = auth_token(method, path, params, signature_params)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
response.env
|
|
142
|
+
rescue Faraday::Error::ClientError, JSON::ParserError
|
|
143
|
+
raise Twitter::Error
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def auth_token(method, path, params={}, signature_params=params)
|
|
147
|
+
if !user_token?
|
|
148
|
+
@bearer_token = token unless bearer_token?
|
|
149
|
+
bearer_auth_header
|
|
150
|
+
else
|
|
151
|
+
oauth_auth_header(method, ENDPOINT + path, signature_params).to_s
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Generates authentication header for a bearer token request
|
|
156
|
+
#
|
|
157
|
+
# @return [String]
|
|
158
|
+
def bearer_token_credentials_auth_header
|
|
159
|
+
basic_auth_token = strict_encode64("#{@consumer_key}:#{@consumer_secret}")
|
|
160
|
+
"Basic #{basic_auth_token}"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def bearer_auth_header
|
|
164
|
+
token = bearer_token.is_a?(Twitter::Token) && bearer_token.bearer? ? bearer_token.access_token : bearer_token
|
|
165
|
+
"Bearer #{token}"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Base64.strict_encode64 is not available on Ruby 1.8.7
|
|
169
|
+
def strict_encode64(str)
|
|
170
|
+
Base64.encode64(str).gsub("\n", "")
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'faraday'
|
|
2
|
+
|
|
3
|
+
module Twitter
|
|
4
|
+
module REST
|
|
5
|
+
module Request
|
|
6
|
+
class MultipartWithFile < Faraday::Middleware
|
|
7
|
+
CONTENT_TYPE = 'Content-Type'
|
|
8
|
+
|
|
9
|
+
def call(env)
|
|
10
|
+
env[:body].each do |key, value|
|
|
11
|
+
if value.respond_to?(:to_io)
|
|
12
|
+
env[:body][key] = Faraday::UploadIO.new(value, mime_type(value.path), value.path)
|
|
13
|
+
end
|
|
14
|
+
end if env[:body].is_a?(::Hash)
|
|
15
|
+
@app.call(env)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def mime_type(path)
|
|
21
|
+
case path
|
|
22
|
+
when /\.jpe?g/i
|
|
23
|
+
'image/jpeg'
|
|
24
|
+
when /\.gif$/i
|
|
25
|
+
'image/gif'
|
|
26
|
+
when /\.png$/i
|
|
27
|
+
'image/png'
|
|
28
|
+
else
|
|
29
|
+
'application/octet-stream'
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'faraday'
|
|
2
|
+
require 'json'
|
|
3
|
+
|
|
4
|
+
module Twitter
|
|
5
|
+
module REST
|
|
6
|
+
module Response
|
|
7
|
+
class ParseJson < Faraday::Response::Middleware
|
|
8
|
+
|
|
9
|
+
def parse(body)
|
|
10
|
+
case body
|
|
11
|
+
when /\A^\s*$\z/, nil
|
|
12
|
+
nil
|
|
13
|
+
else
|
|
14
|
+
JSON.parse(body, :symbolize_names => true)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def on_complete(env)
|
|
19
|
+
if respond_to?(:parse)
|
|
20
|
+
env[:body] = parse(env[:body]) unless unparsable_status_codes.include?(env[:status])
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def unparsable_status_codes
|
|
25
|
+
[204, 301, 302, 304]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -12,20 +12,17 @@ require 'twitter/error/unauthorized'
|
|
|
12
12
|
require 'twitter/error/unprocessable_entity'
|
|
13
13
|
|
|
14
14
|
module Twitter
|
|
15
|
-
module
|
|
16
|
-
|
|
15
|
+
module REST
|
|
16
|
+
module Response
|
|
17
|
+
class RaiseError < Faraday::Response::Middleware
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
def on_complete(env)
|
|
20
|
+
status_code = env[:status].to_i
|
|
21
|
+
error_class = Twitter::Error.errors[status_code]
|
|
22
|
+
raise error_class.from_response(env) if error_class
|
|
23
|
+
end
|
|
23
24
|
|
|
24
|
-
def initialize(app, klass)
|
|
25
|
-
@klass = klass
|
|
26
|
-
super(app)
|
|
27
25
|
end
|
|
28
|
-
|
|
29
26
|
end
|
|
30
27
|
end
|
|
31
28
|
end
|
|
@@ -1,55 +1,71 @@
|
|
|
1
|
-
require 'twitter/
|
|
1
|
+
require 'twitter/enumerable'
|
|
2
2
|
|
|
3
3
|
module Twitter
|
|
4
|
-
class SearchResults
|
|
4
|
+
class SearchResults
|
|
5
|
+
include Twitter::Enumerable
|
|
6
|
+
attr_reader :attrs
|
|
7
|
+
alias to_h attrs
|
|
8
|
+
alias to_hash attrs
|
|
9
|
+
alias to_hsh attrs
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
|
|
13
|
+
# Construct a new SearchResults object from a response hash
|
|
14
|
+
#
|
|
15
|
+
# @param response [Hash]
|
|
16
|
+
# @return [Twitter::Base]
|
|
17
|
+
def from_response(response={})
|
|
18
|
+
new(response[:body])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
5
22
|
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
23
|
+
# Initializes a new SearchResults object
|
|
24
|
+
#
|
|
25
|
+
# @param attrs [Hash]
|
|
26
|
+
# @return [Twitter::SearchResults]
|
|
27
|
+
def initialize(attrs={})
|
|
28
|
+
@attrs = attrs
|
|
29
|
+
@collection = Array(@attrs[:statuses]).map do |tweet|
|
|
30
|
+
Tweet.new(tweet)
|
|
10
31
|
end
|
|
11
32
|
end
|
|
12
|
-
alias collection statuses
|
|
13
|
-
alias results statuses
|
|
14
33
|
|
|
15
34
|
# @return [Float]
|
|
16
35
|
def completed_in
|
|
17
|
-
@attrs[:search_metadata][:completed_in] if search_metadata
|
|
36
|
+
@attrs[:search_metadata][:completed_in] if @attrs[:search_metadata]
|
|
18
37
|
end
|
|
19
38
|
|
|
20
39
|
# @return [Integer]
|
|
21
40
|
def max_id
|
|
22
|
-
@attrs[:search_metadata][:max_id] if search_metadata
|
|
41
|
+
@attrs[:search_metadata][:max_id] if @attrs[:search_metadata]
|
|
23
42
|
end
|
|
24
43
|
|
|
25
44
|
# @return [Integer]
|
|
26
45
|
def page
|
|
27
|
-
@attrs[:search_metadata][:page] if search_metadata
|
|
46
|
+
@attrs[:search_metadata][:page] if @attrs[:search_metadata]
|
|
28
47
|
end
|
|
29
48
|
|
|
30
49
|
# @return [String]
|
|
31
50
|
def query
|
|
32
|
-
@attrs[:search_metadata][:query] if search_metadata
|
|
51
|
+
@attrs[:search_metadata][:query] if @attrs[:search_metadata]
|
|
33
52
|
end
|
|
34
53
|
|
|
35
54
|
# @return [Integer]
|
|
36
55
|
def results_per_page
|
|
37
|
-
@attrs[:search_metadata][:
|
|
56
|
+
@attrs[:search_metadata][:count] if @attrs[:search_metadata]
|
|
38
57
|
end
|
|
39
58
|
alias rpp results_per_page
|
|
40
|
-
|
|
41
|
-
def search_metadata?
|
|
42
|
-
!@attrs[:search_metadata].nil?
|
|
43
|
-
end
|
|
59
|
+
alias count results_per_page
|
|
44
60
|
|
|
45
61
|
# @return [Integer]
|
|
46
62
|
def since_id
|
|
47
|
-
@attrs[:search_metadata][:since_id] if search_metadata
|
|
63
|
+
@attrs[:search_metadata][:since_id] if @attrs[:search_metadata]
|
|
48
64
|
end
|
|
49
65
|
|
|
50
66
|
# @return [Boolean]
|
|
51
67
|
def next_results?
|
|
52
|
-
|
|
68
|
+
!!(@attrs[:search_metadata] && @attrs[:search_metadata][:next_results])
|
|
53
69
|
end
|
|
54
70
|
alias next_page? next_results?
|
|
55
71
|
|
|
@@ -69,11 +85,11 @@ module Twitter
|
|
|
69
85
|
#
|
|
70
86
|
# @note Returned Hash can be merged into the previous search options list to easily access the refresh page.
|
|
71
87
|
# @return [Hash] The parameters needed to refresh the page.
|
|
72
|
-
def
|
|
88
|
+
def refresh_results
|
|
73
89
|
query_string = strip_first_character(@attrs[:search_metadata][:refresh_url])
|
|
74
90
|
query_string_to_hash(query_string)
|
|
75
91
|
end
|
|
76
|
-
alias refresh_page
|
|
92
|
+
alias refresh_page refresh_results
|
|
77
93
|
|
|
78
94
|
private
|
|
79
95
|
|
data/lib/twitter/settings.rb
CHANGED
|
@@ -5,11 +5,6 @@ module Twitter
|
|
|
5
5
|
attr_reader :always_use_https, :discoverable_by_email, :geo_enabled,
|
|
6
6
|
:language, :protected, :screen_name, :show_all_inline_media, :sleep_time,
|
|
7
7
|
:time_zone
|
|
8
|
-
|
|
9
|
-
# @return [Twitter::Place]
|
|
10
|
-
def trend_location
|
|
11
|
-
@trend_location ||= Twitter::Place.fetch_or_new(Array(@attrs[:trend_location]).first)
|
|
12
|
-
end
|
|
13
|
-
|
|
8
|
+
object_attr_reader :Place, :trend_location
|
|
14
9
|
end
|
|
15
10
|
end
|
data/lib/twitter/size.rb
CHANGED
|
@@ -1,24 +1,11 @@
|
|
|
1
|
+
require 'equalizer'
|
|
1
2
|
require 'twitter/base'
|
|
2
3
|
|
|
3
4
|
module Twitter
|
|
4
5
|
class Size < Twitter::Base
|
|
6
|
+
include Equalizer.new(:h, :w)
|
|
5
7
|
attr_reader :h, :resize, :w
|
|
6
8
|
alias height h
|
|
7
9
|
alias width w
|
|
8
|
-
|
|
9
|
-
# @param other [Twitter::Size]
|
|
10
|
-
# @return [Boolean]
|
|
11
|
-
def ==(other)
|
|
12
|
-
super || size_equal(other) || attrs_equal(other)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
protected
|
|
16
|
-
|
|
17
|
-
# @param other [Twitter::Size]
|
|
18
|
-
# @return [Boolean]
|
|
19
|
-
def size_equal(other)
|
|
20
|
-
self.class == other.class && !other.h.nil? && h == other.h && !other.w.nil? && w == other.w
|
|
21
|
-
end
|
|
22
|
-
|
|
23
10
|
end
|
|
24
11
|
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require 'http/request'
|
|
2
|
+
require 'twitter/arguments'
|
|
3
|
+
require 'twitter/client'
|
|
4
|
+
require 'twitter/streaming/connection'
|
|
5
|
+
require 'twitter/streaming/response'
|
|
6
|
+
require 'twitter/streaming/message_parser'
|
|
7
|
+
require 'twitter/streaming/event'
|
|
8
|
+
require 'twitter/streaming/friend_list'
|
|
9
|
+
|
|
10
|
+
module Twitter
|
|
11
|
+
module Streaming
|
|
12
|
+
class Client < Twitter::Client
|
|
13
|
+
attr_writer :connection
|
|
14
|
+
|
|
15
|
+
# Initializes a new Client object
|
|
16
|
+
#
|
|
17
|
+
# @return [Twitter::Streaming::Client]
|
|
18
|
+
def initialize(options={}, &block)
|
|
19
|
+
super
|
|
20
|
+
@connection = Streaming::Connection.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @yield [Twitter::Tweet] A stream of tweets.
|
|
24
|
+
def filter(options={}, &block)
|
|
25
|
+
request(:post, 'https://stream.twitter.com:443/1.1/statuses/filter.json', options, &block)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @yield [Twitter::Tweet] A stream of tweets.
|
|
29
|
+
def firehose(options={}, &block)
|
|
30
|
+
request(:get, 'https://stream.twitter.com:443/1.1/statuses/firehose.json', options, &block)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @yield [Twitter::Tweet] A stream of tweets.
|
|
34
|
+
def sample(options={}, &block)
|
|
35
|
+
request(:get, 'https://stream.twitter.com:443/1.1/statuses/sample.json', options, &block)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @yield [Twitter::Tweet] A stream of tweets.
|
|
39
|
+
def site(*args, &block)
|
|
40
|
+
arguments = Arguments.new(args)
|
|
41
|
+
request(:get, 'https://sitestream.twitter.com:443/1.1/site.json', arguments.options.merge(:follow => arguments.join(',')), &block)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @yield [Twitter::Tweet] A stream of tweets.
|
|
45
|
+
def user(options={}, &block)
|
|
46
|
+
request(:get, 'https://userstream.twitter.com:443/1.1/user.json', options, &block)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Set a Proc to be run when connection established.
|
|
50
|
+
def before_request(&block)
|
|
51
|
+
if block_given?
|
|
52
|
+
@before_request = block
|
|
53
|
+
self
|
|
54
|
+
elsif instance_variable_defined?(:@before_request)
|
|
55
|
+
@before_request
|
|
56
|
+
else
|
|
57
|
+
Proc.new {}
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def request(method, uri, params, &block)
|
|
64
|
+
before_request.call
|
|
65
|
+
headers = default_headers.merge(:authorization => oauth_auth_header(method, uri, params).to_s)
|
|
66
|
+
request = HTTP::Request.new(method, uri + '?' + to_url_params(params), headers)
|
|
67
|
+
response = Streaming::Response.new do |data|
|
|
68
|
+
if item = Streaming::MessageParser.parse(data)
|
|
69
|
+
yield item
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
@connection.stream(request, response)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def to_url_params(params)
|
|
76
|
+
params.map do |param, value|
|
|
77
|
+
[param, URI.encode(value)].join("=")
|
|
78
|
+
end.sort.join('&')
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def default_headers
|
|
82
|
+
@default_headers ||= {
|
|
83
|
+
:accept => '*/*',
|
|
84
|
+
:user_agent => user_agent,
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'http/parser'
|
|
2
|
+
require 'openssl'
|
|
3
|
+
require 'resolv'
|
|
4
|
+
|
|
5
|
+
module Twitter
|
|
6
|
+
module Streaming
|
|
7
|
+
class Connection
|
|
8
|
+
|
|
9
|
+
def stream(request, response)
|
|
10
|
+
client_context = OpenSSL::SSL::SSLContext.new
|
|
11
|
+
client = TCPSocket.new(Resolv.getaddress(request.uri.host), request.uri.port)
|
|
12
|
+
ssl_client = OpenSSL::SSL::SSLSocket.new(client, client_context)
|
|
13
|
+
ssl_client.connect
|
|
14
|
+
request.stream(ssl_client)
|
|
15
|
+
while body = ssl_client.readpartial(1024)
|
|
16
|
+
response << body
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Twitter
|
|
2
|
+
module Streaming
|
|
3
|
+
class Event
|
|
4
|
+
|
|
5
|
+
LIST_EVENTS = [
|
|
6
|
+
:list_created, :list_destroyed, :list_updated, :list_member_added,
|
|
7
|
+
:list_member_added, :list_member_removed, :list_user_subscribed,
|
|
8
|
+
:list_user_subscribed, :list_user_unsubscribed, :list_user_unsubscribed
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
TWEET_EVENTS = [
|
|
12
|
+
:favorite, :unfavorite
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
attr_reader :name, :source, :target, :target_object
|
|
16
|
+
|
|
17
|
+
# @param data [Hash]
|
|
18
|
+
def initialize(data)
|
|
19
|
+
@name = data[:event].intern
|
|
20
|
+
@source = Twitter::User.new(data[:source])
|
|
21
|
+
@target = Twitter::User.new(data[:target])
|
|
22
|
+
@target_object = target_object_factory(@name, data[:target_object])
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
def target_object_factory(event_name, data)
|
|
27
|
+
if LIST_EVENTS.include?(event_name)
|
|
28
|
+
Twitter::List.new(data)
|
|
29
|
+
elsif TWEET_EVENTS.include?(event_name)
|
|
30
|
+
Twitter::Tweet.new(data)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Twitter
|
|
2
|
+
module Streaming
|
|
3
|
+
class MessageParser
|
|
4
|
+
|
|
5
|
+
def self.parse(data)
|
|
6
|
+
if data[:id]
|
|
7
|
+
Tweet.new(data)
|
|
8
|
+
elsif data[:event]
|
|
9
|
+
Event.new(data)
|
|
10
|
+
elsif data[:direct_message]
|
|
11
|
+
DirectMessage.new(data[:direct_message])
|
|
12
|
+
elsif data[:friends]
|
|
13
|
+
FriendList.new(data[:friends])
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'buftok'
|
|
2
|
+
|
|
3
|
+
module Twitter
|
|
4
|
+
module Streaming
|
|
5
|
+
class Response
|
|
6
|
+
|
|
7
|
+
# Initializes a new Response object
|
|
8
|
+
#
|
|
9
|
+
# @return [Twitter::Streaming::Response]
|
|
10
|
+
def initialize(&block)
|
|
11
|
+
@block = block
|
|
12
|
+
@parser = Http::Parser.new(self)
|
|
13
|
+
@tokenizer = BufferedTokenizer.new("\r\n")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def <<(data)
|
|
17
|
+
@parser << data
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def on_headers_complete(headers)
|
|
21
|
+
# TODO: handle response codes
|
|
22
|
+
p(:status_code => @parser.status_code, :header => headers) unless @parser.status_code == 200
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def on_body(data)
|
|
26
|
+
@tokenizer.extract(data).each do |line|
|
|
27
|
+
next if line.empty?
|
|
28
|
+
@block.call(JSON.parse(line, :symbolize_names => true))
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/twitter/suggestion.rb
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
|
+
require 'equalizer'
|
|
1
2
|
require 'twitter/base'
|
|
2
3
|
|
|
3
4
|
module Twitter
|
|
4
5
|
class Suggestion < Twitter::Base
|
|
6
|
+
include Equalizer.new(:slug)
|
|
5
7
|
attr_reader :name, :size, :slug
|
|
6
8
|
|
|
7
|
-
# @param other [Twitter::Suggestion]
|
|
8
|
-
# @return [Boolean]
|
|
9
|
-
def ==(other)
|
|
10
|
-
super || attr_equal(:slug, other) || attrs_equal(other)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
9
|
# @return [Array<Twitter::User>]
|
|
14
10
|
def users
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
Array(@attrs[:users]).map do |user|
|
|
12
|
+
User.new(user)
|
|
17
13
|
end
|
|
18
14
|
end
|
|
15
|
+
memoize :users
|
|
19
16
|
|
|
20
17
|
end
|
|
21
18
|
end
|