trubl 1.4.2

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 (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