trubl 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. data/.yardopts +8 -0
  2. data/LICENSE.md +20 -0
  3. data/README.md +72 -0
  4. data/Rakefile +11 -0
  5. data/lib/trubl.rb +30 -0
  6. data/lib/trubl/api/category.rb +33 -0
  7. data/lib/trubl/api/channel.rb +32 -0
  8. data/lib/trubl/api/conversation.rb +31 -0
  9. data/lib/trubl/api/hashtags.rb +45 -0
  10. data/lib/trubl/api/me.rb +68 -0
  11. data/lib/trubl/api/search.rb +35 -0
  12. data/lib/trubl/api/streams.rb +14 -0
  13. data/lib/trubl/api/suggested_users.rb +16 -0
  14. data/lib/trubl/api/touts.rb +143 -0
  15. data/lib/trubl/api/users.rb +79 -0
  16. data/lib/trubl/authorization.rb +7 -0
  17. data/lib/trubl/authorizations.rb +8 -0
  18. data/lib/trubl/base.rb +37 -0
  19. data/lib/trubl/category.rb +7 -0
  20. data/lib/trubl/channel.rb +7 -0
  21. data/lib/trubl/client.rb +252 -0
  22. data/lib/trubl/collection.rb +43 -0
  23. data/lib/trubl/conversation.rb +7 -0
  24. data/lib/trubl/hashtag.rb +7 -0
  25. data/lib/trubl/hashtags.rb +8 -0
  26. data/lib/trubl/oauth.rb +56 -0
  27. data/lib/trubl/pagination.rb +17 -0
  28. data/lib/trubl/tout.rb +29 -0
  29. data/lib/trubl/touts.rb +8 -0
  30. data/lib/trubl/user.rb +29 -0
  31. data/lib/trubl/users.rb +8 -0
  32. data/lib/trubl/version.rb +16 -0
  33. data/lib/trubl/widget.rb +15 -0
  34. data/lib/trubl/widgets.rb +8 -0
  35. data/spec/fixtures/category_response.json +1 -0
  36. data/spec/fixtures/category_touts_response.json +689 -0
  37. data/spec/fixtures/category_users_response.json +1709 -0
  38. data/spec/fixtures/channel_response.json +1 -0
  39. data/spec/fixtures/channel_touts_response.json +1 -0
  40. data/spec/fixtures/channel_users_response.json +1 -0
  41. data/spec/fixtures/client1_auth_resp.json +1 -0
  42. data/spec/fixtures/client2_auth_resp.json +1 -0
  43. data/spec/fixtures/conversation_authors_response.json +37 -0
  44. data/spec/fixtures/conversation_response.json +41 -0
  45. data/spec/fixtures/conversation_touts_response.json +107 -0
  46. data/spec/fixtures/featured_touts_response.json +4249 -0
  47. data/spec/fixtures/hashtag_response.json +1 -0
  48. data/spec/fixtures/hashtags_touts_response.json +304 -0
  49. data/spec/fixtures/latest_touts_response.json +3600 -0
  50. data/spec/fixtures/like_tout_response.json +109 -0
  51. data/spec/fixtures/me_authorizations_response.json +14 -0
  52. data/spec/fixtures/me_fb_sharing_response.json +21 -0
  53. data/spec/fixtures/me_friends_response.json +121 -0
  54. data/spec/fixtures/me_retrieve_user_friends_response.json +174 -0
  55. data/spec/fixtures/me_retrieve_user_liked_touts_response.json +1255 -0
  56. data/spec/fixtures/me_retrieve_user_touts_response.json +479 -0
  57. data/spec/fixtures/retout_tout_response.json +99 -0
  58. data/spec/fixtures/retrieve_me_response.json +35 -0
  59. data/spec/fixtures/retrieve_tout.json +98 -0
  60. data/spec/fixtures/retrieve_tout_response.json +98 -0
  61. data/spec/fixtures/search_hashtags_response.json +100 -0
  62. data/spec/fixtures/search_touts_response.json +304 -0
  63. data/spec/fixtures/search_users_response.json +37 -0
  64. data/spec/fixtures/suggested_hashtags_response.json +137 -0
  65. data/spec/fixtures/suggested_users_response.json +1 -0
  66. data/spec/fixtures/test.mp4 +0 -0
  67. data/spec/fixtures/tout.json +98 -0
  68. data/spec/fixtures/tout_conversation_response.json +41 -0
  69. data/spec/fixtures/touts_liked_by_response.json +65 -0
  70. data/spec/fixtures/touts_liked_by_user_response.json +106 -0
  71. data/spec/fixtures/touts_me_updates_response.json +357 -0
  72. data/spec/fixtures/touts_search_results.json +304 -0
  73. data/spec/fixtures/trending_hashtags_response.json +149 -0
  74. data/spec/fixtures/unlike_tout_response.json +109 -0
  75. data/spec/fixtures/update_me_response.json +35 -0
  76. data/spec/fixtures/user.json +28 -0
  77. data/spec/fixtures/user_followers.json +1409 -0
  78. data/spec/fixtures/user_touts_response.json +479 -0
  79. data/spec/fixtures/user_with_utf8.json +29 -0
  80. data/spec/fixtures/users.json +58 -0
  81. data/spec/fixtures/users_search_results.json +37 -0
  82. data/spec/fixtures/widgets.json +142 -0
  83. data/spec/spec_helper.rb +61 -0
  84. data/spec/trubl/api/category_spec.rb +39 -0
  85. data/spec/trubl/api/channel_spec.rb +39 -0
  86. data/spec/trubl/api/conversation_spec.rb +38 -0
  87. data/spec/trubl/api/hashtags_spec.rb +60 -0
  88. data/spec/trubl/api/me_spec.rb +98 -0
  89. data/spec/trubl/api/search_spec.rb +40 -0
  90. data/spec/trubl/api/streams_spec.rb +18 -0
  91. data/spec/trubl/api/suggested_users_spec.rb +17 -0
  92. data/spec/trubl/api/touts_spec.rb +215 -0
  93. data/spec/trubl/api/users_spec.rb +122 -0
  94. data/spec/trubl/base_spec.rb +88 -0
  95. data/spec/trubl/category_spec.rb +15 -0
  96. data/spec/trubl/channel_spec.rb +16 -0
  97. data/spec/trubl/client_spec.rb +141 -0
  98. data/spec/trubl/conversation_spec.rb +13 -0
  99. data/spec/trubl/hashtag_spec.rb +11 -0
  100. data/spec/trubl/oauth_spec.rb +27 -0
  101. data/spec/trubl/tout_spec.rb +41 -0
  102. data/spec/trubl/user_spec.rb +65 -0
  103. data/spec/trubl_spec.rb +23 -0
  104. data/trubl.gemspec +41 -0
  105. metadata +494 -0
@@ -0,0 +1,79 @@
1
+ require_relative '../touts'
2
+ require_relative '../user'
3
+ require_relative '../users'
4
+
5
+ module Trubl
6
+ module API
7
+ module Users
8
+ # implements http://developer.tout.com/api-overview/users-api
9
+ # mixed in to a Client instance, the self passed to response objects is that instance
10
+
11
+ # implements http://developer.tout.com/api/users-api/apimethod/retrieve-user
12
+ # @param uid [String] a user uid
13
+ # @return [Trubl::User] or nil
14
+ def retrieve_user(uid=nil)
15
+ return nil if uid.blank?
16
+
17
+ response = get("/api/v1/users/#{uid}")
18
+ Trubl::User.new.from_response(response)
19
+ end
20
+
21
+ # implements http://developer.tout.com/api/users-api/apimethod/retrieve-users
22
+ # @param uids [Array<String>] of user uids
23
+ # @return [Array<Trubl::User>]
24
+ def retrieve_users(uids=[])
25
+ uids = (uids.is_a?(Array) ? uids : [uids]).compact.uniq.sort
26
+ return [] if uids.blank?
27
+
28
+ requests = uids.in_groups_of(100, false).collect do |uid_group|
29
+ {path: "users", query: {uids: uid_group.join(',')} }
30
+ end
31
+
32
+ multi_request(:get, requests).
33
+ collect { |response| Trubl::Users.new.from_response(response) }.
34
+ flatten.
35
+ compact
36
+ end
37
+
38
+ # implements http://developer.tout.com/api/users-api/apimethod/retrieve-list-touts-liked-user
39
+ # returns Array of Trubl::Tout instances or nil
40
+ def retrieve_user_likes(uid, order=nil, per_page=nil, page=nil)
41
+ response = get("/api/v1/users/#{uid}/likes", query: {order: order, per_page: per_page, page: page})
42
+ Trubl::Touts.new.from_response(response)
43
+ end
44
+
45
+ # implements http://developer.tout.com/api/users-api/apimethod/retrieve-users-touts
46
+ # return Array of Trubl::Tout instances or nil
47
+ def retrieve_user_touts(uid, order=nil, per_page=nil, page=nil)
48
+ response = get("/api/v1/users/#{uid}/touts", query: {order: order, per_page: per_page, page: page})
49
+ Trubl::Touts.new.from_response(response)
50
+ end
51
+
52
+ # implements http://developer.tout.com/api/users-api/apimethod/retrieve-list-users-follow-user
53
+ # returns Array of Trubl::User instances or nil
54
+ def retrieve_user_followers(uid, order=nil, per_page=nil, page=nil)
55
+ response = get("/api/v1/users/#{uid}/followers", query: {order: order, per_page: per_page, page: page})
56
+ Trubl::Users.new.from_response(response)
57
+ end
58
+
59
+ # order, per_page, page arent supported at the moment
60
+ def retrieve_user_widgets(uid, order=nil, per_page=nil, page=nil)
61
+ response = get("/api/v1/users/#{uid}/widgets")
62
+ Trubl::Widgets.new.from_response(response)
63
+ end
64
+
65
+ # implements http://developer.tout.com/api/users-api/apimethod/follow-user
66
+ # returns response object
67
+ def follow_user(uid)
68
+ post("/api/v1/users/#{uid}/follows")
69
+ end
70
+
71
+ # implements http://developer.tout.com/api/users-api/apimethod/unfollow-user
72
+ # returns response object
73
+ def unfollow_user(uid)
74
+ delete("/api/v1/users/#{uid}/follows")
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,7 @@
1
+ require 'trubl/base'
2
+
3
+ module Trubl
4
+ class Authorization < Trubl::Base
5
+
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ require_relative './collection'
2
+ require_relative './authorization'
3
+
4
+ module Trubl
5
+ class Authorizations < Collection
6
+
7
+ end
8
+ end
@@ -0,0 +1,37 @@
1
+ require 'hashie'
2
+
3
+ module Trubl
4
+ class Base < Hashie::Mash
5
+
6
+ def from_response(response)
7
+ return nil if missing_or_exception?(response)
8
+ initialize(parse(response))
9
+ end
10
+
11
+ def parse(response)
12
+ JSON.parse(response.body)[klass_name]
13
+ end
14
+
15
+ def klass_name
16
+ self.class.name.downcase.gsub('trubl::', '')
17
+ end
18
+
19
+
20
+ private
21
+
22
+ def missing_or_exception?(response)
23
+ code = if response.respond_to?(:code)
24
+ response.code
25
+ elsif response.respond_to?(:status)
26
+ response.status
27
+ else
28
+ nil
29
+ end
30
+
31
+ code && (400..600).include?(code)
32
+ end
33
+
34
+ end
35
+ end
36
+
37
+
@@ -0,0 +1,7 @@
1
+ require 'trubl/base'
2
+
3
+ module Trubl
4
+ class Category < Trubl::Base
5
+
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require 'trubl/base'
2
+
3
+ module Trubl
4
+ class Channel < Trubl::Base
5
+
6
+ end
7
+ end
@@ -0,0 +1,252 @@
1
+ require_relative './api/category'
2
+ require_relative './api/channel'
3
+ require_relative './api/conversation'
4
+ require_relative './api/hashtags'
5
+ require_relative './api/me'
6
+ require_relative './api/search'
7
+ require_relative './api/streams'
8
+ require_relative './api/suggested_users'
9
+ require_relative './api/touts'
10
+ require_relative './api/users'
11
+ require_relative './widgets'
12
+ require_relative './oauth'
13
+
14
+ require 'httparty'
15
+ require 'uri'
16
+ require 'faraday'
17
+ require 'active_support/core_ext'
18
+
19
+ if RUBY_ENGINE == 'ruby'
20
+ require 'typhoeus'
21
+ require 'typhoeus/adapters/faraday'
22
+ end
23
+
24
+ # instantiate a Tout client instance
25
+ module Trubl
26
+ # Wrapper for the Tout REST API
27
+ #
28
+ # @note All methods have been separated into modules and follow the grouping used in http://developer.tout.com/apis the Tout API Documentation.
29
+ class Client
30
+ include Trubl::API::Category
31
+ include Trubl::API::Channel
32
+ include Trubl::API::Conversation
33
+ include Trubl::API::Hashtags
34
+ include Trubl::API::Me
35
+ include Trubl::API::Search
36
+ include Trubl::API::Streams
37
+ include Trubl::API::Suggested_Users
38
+ include Trubl::API::Touts
39
+ include Trubl::API::Users
40
+ include Trubl::OAuth
41
+
42
+ attr_reader :client_id, :client_secret, :access_token, :callback_url
43
+
44
+ # Initialize a new Tout client with creds and callback url
45
+ def initialize(client_id=nil, client_secret=nil, callback_url=nil, *args)
46
+ opts = (args.last.is_a?(Hash) ? args.last : {}).with_indifferent_access
47
+
48
+ opts.delete_if { |k, v| v.nil? }.reverse_merge!(default_tout_configuration)
49
+
50
+ @client_id = client_id
51
+ @client_secret = client_secret
52
+ @access_token = opts[:access_token]
53
+ @callback_url = callback_url
54
+ @uri_scheme = opts[:uri_scheme]
55
+ @uri_host = opts[:uri_host]
56
+ @uri_port = opts[:uri_port]
57
+ @uri_base_path = opts[:uri_base_path]
58
+ @uri_version = opts[:uri_version]
59
+ @auth_site = opts[:auth_site]
60
+ @authorize_url = opts[:authorize_url]
61
+ @token_url = opts[:token_url]
62
+ @email = opts[:email]
63
+ @password = opts[:password]
64
+ end
65
+
66
+ def default_tout_configuration
67
+ {
68
+ uri_scheme: 'https',
69
+ uri_host: 'api.tout.com',
70
+ uri_base_path: '/api',
71
+ uri_version: 'v1',
72
+ auth_site: 'https://www.tout.com/',
73
+ authorize_url: '/oauth/authorize',
74
+ token_url: '/oauth/token',
75
+ email: nil,
76
+ password: nil
77
+ }.with_indifferent_access
78
+ end
79
+
80
+ def credentials()
81
+ {
82
+ client_id: @client_id,
83
+ client_secret: @client_secret,
84
+ access_token: @access_token
85
+ }
86
+ end
87
+
88
+ def api_uri_root()
89
+ # Changed this from URI.join because scheme became pointless. It could not
90
+ # override the scheme set in the host and the scheme was required to be set
91
+ # in @uri_host or else URI.join throws an error
92
+ URI.parse('').tap do |uri|
93
+ uri.scheme = @uri_scheme
94
+ uri.host = @uri_host.gsub(/https?:\/\//, '') # strip the scheme if it is part of the hostname
95
+ uri.path = [@uri_base_path, @uri_version, nil].join('/')
96
+ uri.port = @uri_port unless @uri_port.blank?
97
+ end.to_s
98
+ end
99
+
100
+ # Perform an HTTP DELETE request
101
+ def delete(path, params={})
102
+ request(:delete, path, params)
103
+ end
104
+
105
+ # Perform an HTTP GET request
106
+ def get(path, params={})
107
+ request(:get, path, params)
108
+ end
109
+
110
+ # Perform an HTTP POST request
111
+ def post(path, params={})
112
+ request(:post, path, params)
113
+ end
114
+
115
+ # Perform an HTTP Multipart Form Request
116
+ def multipart_post(path, params={})
117
+ raise ArgumentError.new("Must specify a valid file to include\nYou specified #{params[:data]}") unless File.exists?(params[:data])
118
+ uri = full_uri(path)
119
+ payload = { 'tout[data]' => Faraday::UploadIO.new(params[:data], 'video/mp4')}.merge(params)
120
+
121
+ Trubl.logger.info("Trubl::Client multipart post-ing #{uri.to_s} (content omitted)")
122
+
123
+ Faraday.new(url: uri.host) do |faraday|
124
+ faraday.headers = options
125
+ faraday.request :multipart
126
+ faraday.response :logger
127
+ faraday.adapter Faraday.default_adapter
128
+ end.post(uri.to_s, payload).tap do |response|
129
+ if !response.status =~ /20[0-9]/
130
+ Trubl.logger.fatal("Trubl::Client multipart post-ing #{uri.to_s} #{response.code} #{response.parsed_response}")
131
+ end
132
+ end
133
+ end
134
+
135
+ # Perform an HTTP PUT request
136
+ def put(path, params={})
137
+ request(:put, path, params)
138
+ end
139
+
140
+ # ToDo: model response handling off of oauth2.client.request
141
+ # in fact, perhaps we swap this out for the oauth2 request method...
142
+ def request(method, path, params)
143
+ params ||= {}
144
+ uri = full_url(path)
145
+
146
+ Trubl.logger.info("Trubl::Client #{method}-ing #{uri} with params #{params.merge(headers: headers)}")
147
+ response = HTTParty.send(method, uri, params.merge(headers: headers))
148
+
149
+ if !response.code =~ /20[0-9]/
150
+ Trubl.logger.fatal("Trubl::Client #{response.code} #{method}-ing #{uri.to_s} #{response.parsed_response}")
151
+ else
152
+ Trubl.logger.debug("Trubl::Client #{uri} response: #{response.body}")
153
+ end
154
+ response.body.force_encoding("utf-8") if response.body and response.body.respond_to?(:force_encoding)
155
+ response
156
+ end
157
+
158
+ def multi_request(method, requests=[], opts={})
159
+ return [] if requests.blank? or [:get].exclude?(method.to_sym)
160
+
161
+ if requests.size == 1
162
+ request = requests.first
163
+ path = [request[:path], request[:query].try(:to_query)].compact.join('?')
164
+ return [request(method, path, request[:params])]
165
+ end
166
+
167
+ opts.reverse_merge! max_concurrency: 10
168
+
169
+ Trubl.logger.info("Trubl::Client multi-#{method}-ing #{requests.join(', ')} with headers #{headers}")
170
+
171
+ action = RUBY_ENGINE == 'ruby' ? :multi_request_typhoeus : :multi_request_threaded
172
+
173
+ self.send(action, method, requests, opts).collect do |response|
174
+ response.body.force_encoding("utf-8") if response.body and response.body.respond_to?(:force_encoding)
175
+ response
176
+ end
177
+ end
178
+
179
+ def multi_request_threaded(method, requests=[], opts={})
180
+ responses = []
181
+ mutex = Mutex.new
182
+ requests = requests.clone
183
+
184
+ opts[:max_concurrency].times.map do
185
+ Thread.new(requests, responses) do |requests, responses|
186
+ while request = mutex.synchronize { requests.pop }
187
+ response = HTTParty.send(method, full_url(request[:path]), {headers: headers}.merge(request[:params] || {} ))
188
+ mutex.synchronize { responses << response }
189
+ end
190
+ end
191
+ end.each(&:join)
192
+
193
+ responses
194
+ end
195
+
196
+ def multi_request_typhoeus(method, requests=[], opts={})
197
+ # https://github.com/lostisland/faraday/wiki/Parallel-requests
198
+ # https://github.com/typhoeus/typhoeus/issues/226
199
+ hydra = Typhoeus::Hydra.new(max_concurrency: opts[:max_concurrency])
200
+
201
+ conn = Faraday.new(url: api_uri_root, parallel_manager: hydra) do |builder|
202
+ builder.request :url_encoded
203
+ builder.adapter :typhoeus
204
+ end
205
+
206
+ requests = requests.collect do |request|
207
+ if request.is_a?(String)
208
+ {path: request, params: {}}
209
+ else
210
+ request.reverse_merge params: {}
211
+ end
212
+ end
213
+
214
+ [].tap do |responses|
215
+ conn.in_parallel do
216
+ requests.each do |request|
217
+ path = [request[:path], request[:query].try(:to_query)].compact.join('?')
218
+ responses << conn.send(method, path, request[:params], headers)
219
+ end
220
+ end
221
+ end
222
+ end
223
+
224
+ def set_logger(level)
225
+ Trubl.logger(level)
226
+ end
227
+
228
+ private
229
+ # Fully qualified uri
230
+ def full_uri(path)
231
+ URI.parse("#{api_uri_root}#{path}")
232
+ end
233
+
234
+ # Fully qualified url
235
+ def full_url(path)
236
+ URI.join(api_uri_root, path).to_s
237
+ end
238
+
239
+ def headers
240
+ {
241
+ "Authorization" => "Bearer #{@access_token}",
242
+ "Connection" => 'keep-alive',
243
+ "Accept" => 'application/json'
244
+ }
245
+ end
246
+
247
+ def options(params={})
248
+ headers.merge(params)
249
+ end
250
+
251
+ end
252
+ end
@@ -0,0 +1,43 @@
1
+ require_relative './pagination'
2
+
3
+ module Trubl
4
+ class Collection < Array
5
+
6
+ def from_response(response, options = {})
7
+ return nil if missing_or_exception?(response)
8
+ json = JSON.parse(response.body)
9
+ self.concat (json[container_name] || []).map{|m| klass.new(m[member_name]) }
10
+ end
11
+
12
+ def klass
13
+ "Trubl::#{member_name.classify}".constantize
14
+ end
15
+
16
+ def container_name
17
+ klass_name
18
+ end
19
+
20
+ def member_name
21
+ klass_name.singularize
22
+ end
23
+
24
+ def klass_name
25
+ self.class.name.downcase.gsub('trubl::', '')
26
+ end
27
+
28
+ private
29
+
30
+ def missing_or_exception?(response)
31
+ code = if response.respond_to?(:code)
32
+ response.code
33
+ elsif response.respond_to?(:status)
34
+ response.status
35
+ else
36
+ nil
37
+ end
38
+
39
+ code && (400..600).include?(code)
40
+ end
41
+
42
+ end
43
+ end