botr 0.1.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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.document +3 -0
  3. data/.gitignore +31 -0
  4. data/.rspec +1 -0
  5. data/.yardopts +1 -0
  6. data/ChangeLog.md +4 -0
  7. data/LICENSE.txt +20 -0
  8. data/README.md +278 -0
  9. data/Rakefile +43 -0
  10. data/botr.gemspec +24 -0
  11. data/botr.watchr +3 -0
  12. data/certs/cacert.pem +3554 -0
  13. data/lib/botr.rb +34 -0
  14. data/lib/botr/api/api.rb +83 -0
  15. data/lib/botr/api/authentication.rb +24 -0
  16. data/lib/botr/channels/channel.rb +137 -0
  17. data/lib/botr/channels/channel_thumbnail.rb +88 -0
  18. data/lib/botr/channels/channel_video.rb +130 -0
  19. data/lib/botr/channels/channel_view.rb +88 -0
  20. data/lib/botr/common/logger.rb +31 -0
  21. data/lib/botr/configuration.rb +18 -0
  22. data/lib/botr/http/http.rb +88 -0
  23. data/lib/botr/http/http_backend.rb +66 -0
  24. data/lib/botr/http/http_response.rb +48 -0
  25. data/lib/botr/http/multipart.rb +84 -0
  26. data/lib/botr/http/uri_ext.rb +28 -0
  27. data/lib/botr/object.rb +20 -0
  28. data/lib/botr/players/player.rb +149 -0
  29. data/lib/botr/players/player_view.rb +88 -0
  30. data/lib/botr/version.rb +3 -0
  31. data/lib/botr/videos/video.rb +187 -0
  32. data/lib/botr/videos/video_caption.rb +154 -0
  33. data/lib/botr/videos/video_conversion.rb +114 -0
  34. data/lib/botr/videos/video_engagement.rb +51 -0
  35. data/lib/botr/videos/video_tag.rb +52 -0
  36. data/lib/botr/videos/video_thumbnail.rb +87 -0
  37. data/lib/botr/videos/video_view.rb +89 -0
  38. data/spec/authentication_spec.rb +31 -0
  39. data/spec/botr_spec.rb +8 -0
  40. data/spec/http_backend_spec.rb +93 -0
  41. data/spec/http_response_spec.rb +48 -0
  42. data/spec/multipart_spec.rb +35 -0
  43. data/spec/spec_helper.rb +5 -0
  44. data/spec/test.txt +1 -0
  45. metadata +150 -0
@@ -0,0 +1,88 @@
1
+ module BOTR
2
+
3
+ class ChannelView < BOTR::Object
4
+
5
+ class << self
6
+
7
+ attr_reader :last_status
8
+
9
+ def show(key, **options)
10
+ json = get_request(options.merge(:method => 'show',
11
+ :channel_key => key))
12
+ res = JSON.parse(json.body)
13
+
14
+ if json.status == 200
15
+ params = process_show_response(res)
16
+ else
17
+ raise "HTTP Error #{json.status}: #{json.body}"
18
+ end
19
+
20
+ return new(params)
21
+ end
22
+
23
+ alias :find :show
24
+
25
+ def list(**options)
26
+ json = get_request(options.merge(:method => 'list'))
27
+ res = JSON.parse(json.body)
28
+
29
+ if json.status == 200
30
+ results = process_list_response(res)
31
+ else
32
+ raise "HTTP Error #{json.status}: #{json.body}"
33
+ end
34
+
35
+ return results
36
+ end
37
+
38
+ def all
39
+ list({})
40
+ end
41
+
42
+ private
43
+
44
+ def process_show_response(body)
45
+ @last_status = body["status"]
46
+
47
+ return body["channel"]
48
+ end
49
+
50
+ def process_list_response(body)
51
+ @last_status = body["status"]
52
+ res = []
53
+
54
+ if body["channels"]
55
+ body["channels"].each do |channel|
56
+ res << new(channel)
57
+ end
58
+ elsif body["days"]
59
+ body["days"].each do |day|
60
+ res << new(day)
61
+ end
62
+ elsif body["years"]
63
+ body["years"].each do |year|
64
+ res << new(year)
65
+ end
66
+ else
67
+ res << new(body)
68
+ end
69
+
70
+ return res
71
+ end
72
+
73
+ end
74
+
75
+ attr_reader :last_status, :key, :days, :years, :views, :title, :date,
76
+ :timestamp, :number, :year, :number, :total
77
+
78
+ def initialize(params = {})
79
+ params.each do |key, val|
80
+ param = "@#{key.to_s}"
81
+ next unless methods.include? key.to_sym
82
+ instance_variable_set(param, val)
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,31 @@
1
+ module BOTR
2
+
3
+ module Logger
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ def logger=(log)
10
+ @logger = log
11
+ end
12
+
13
+ def logger
14
+ @logger ||= self.class.logger
15
+ end
16
+
17
+ module ClassMethods
18
+
19
+ def logger=(log)
20
+ @@logger = log
21
+ end
22
+
23
+ def logger
24
+ @@logger ||= nil
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,18 @@
1
+ module BOTR
2
+
3
+ class << self
4
+ attr_accessor :configuration
5
+ end
6
+
7
+ def self.configure
8
+ self.configuration ||= Configuration.new
9
+ yield(configuration)
10
+ end
11
+
12
+ class Configuration
13
+
14
+ attr_accessor :protocol, :server, :api_key, :secret_key
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,88 @@
1
+ module BOTR
2
+
3
+ module HTTP
4
+
5
+ def client
6
+ @client = BOTR::HTTPBackend.new unless @client
7
+ @client
8
+ end
9
+
10
+ def get_request(options = {})
11
+ params = options.dup
12
+
13
+ http = params.delete(:client) || client
14
+ method = params.delete(:method)
15
+ url = params.delete(:api_url) || api_url(method)
16
+ format = params.delete(:api_format) || api_format
17
+ key = params.delete(:api_key) || api_key
18
+ timestamp = params.delete(:api_timestamp) || api_timestamp
19
+ nonce = params.delete(:api_nonce) || api_nonce
20
+ secret = params.delete(:api_secret_key)|| api_secret_key
21
+
22
+ params = params.merge(:api_format => format,
23
+ :api_key => key,
24
+ :api_timestamp => timestamp,
25
+ :api_nonce => nonce)
26
+
27
+ http.get(url, params.merge(:api_signature => self.signature(params)))
28
+ end
29
+
30
+ def post_request(options = {}, data_path = nil)
31
+ params = options.dup
32
+
33
+ http = params.delete(:client) || client
34
+ url = params.delete(:api_url) || upload_url
35
+ format = params.delete(:api_format) || api_format
36
+ key = params.delete(:upload_key) || upload_key
37
+ token = params.delete(:upload_token) || upload_token
38
+
39
+ params = params.merge(:api_format => format,
40
+ :key => key,
41
+ :token => token)
42
+
43
+ http.post(url, params, data_path)
44
+ end
45
+
46
+ def put_request(options = {})
47
+ params = options.dup
48
+
49
+ http = params.delete(:client) || client
50
+ method = 'update'
51
+ url = params.delete(:api_url) || api_url(method)
52
+ format = params.delete(:api_format) || api_format
53
+ key = params.delete(:api_key) || api_key
54
+ timestamp = params.delete(:api_timestamp) || api_timestamp
55
+ nonce = params.delete(:api_nonce) || api_nonce
56
+ secret = params.delete(:api_secret_key)|| api_secret_key
57
+
58
+ params = params.merge(:api_format => format,
59
+ :api_key => key,
60
+ :api_timestamp => timestamp,
61
+ :api_nonce => nonce)
62
+
63
+ http.post(url, params.merge(:api_signature => self.signature(params)))
64
+ end
65
+
66
+ def delete_request(options = {})
67
+ params = options.dup
68
+
69
+ http = params.delete(:client) || client
70
+ method = 'delete'
71
+ url = params.delete(:api_url) || api_url(method)
72
+ format = params.delete(:api_format) || api_format
73
+ key = params.delete(:api_key) || api_key
74
+ timestamp = params.delete(:api_timestamp) || api_timestamp
75
+ nonce = params.delete(:api_nonce) || api_nonce
76
+ secret = params.delete(:api_secret_key)|| api_secret_key
77
+
78
+ params = params.merge(:api_format => format,
79
+ :api_key => key,
80
+ :api_timestamp => timestamp,
81
+ :api_nonce => nonce)
82
+
83
+ http.post(url, params.merge(:api_signature => self.signature(params)))
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,66 @@
1
+ require 'net/http'
2
+
3
+ module BOTR
4
+
5
+ class HTTPBackend
6
+
7
+ # GET request.
8
+ def get(path, params = {})
9
+ uri = URI(path)
10
+ uri.query = URI.encode_www_form(params)
11
+
12
+ res = Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
13
+ http.request_get uri
14
+ end
15
+
16
+ respond(res)
17
+ end
18
+
19
+ # POST request with optional multipart/form-data.
20
+ def post(path, params = {}, data_path = "")
21
+ uri = URI(path)
22
+ uri.query = URI.encode_www_form(params)
23
+
24
+ res = Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
25
+ req = Net::HTTP::Post.new(uri.request_uri)
26
+
27
+ if data_path.empty?
28
+ req.set_form_data(params)
29
+ else
30
+ boundary = rand(1000000).to_s
31
+
32
+ req.body_stream = BOTR::Multipart.new(data_path, boundary)
33
+ req["Content-Type"] = "multipart/form-data; boundary=#{boundary}"
34
+ req["Content-Length"] = req.body_stream.size.to_s
35
+ end
36
+
37
+ http.request req
38
+ end
39
+
40
+ respond(res)
41
+ end
42
+
43
+ private
44
+
45
+ # Map Net::HTTPResponses to internal ones.
46
+ def respond(resp)
47
+ case resp
48
+ when Net::HTTPOK
49
+ BOTR::OKResponse.new(resp.body)
50
+ when Net::HTTPBadRequest
51
+ BOTR::BadRequestResponse.new(resp.body)
52
+ when Net::HTTPUnauthorized
53
+ BOTR::UnauthorizedResponse.new(resp.body)
54
+ when Net::HTTPForbidden
55
+ BOTR::ForbiddenResponse.new(resp.body)
56
+ when Net::HTTPNotFound
57
+ BOTR::NotFoundResponse.new(resp.body)
58
+ when Net::HTTPMethodNotAllowed
59
+ BOTR::NotAllowedResponse.new(resp.body)
60
+ else
61
+ BOTR::HTTPResponse.new(resp.code, resp.body)
62
+ end
63
+ end
64
+ end
65
+
66
+ end
@@ -0,0 +1,48 @@
1
+ module BOTR
2
+
3
+ class HTTPResponse
4
+ attr_accessor :status, :body
5
+
6
+ def initialize(status, body = "")
7
+ @status = status
8
+ @body = body
9
+ end
10
+ end
11
+
12
+ class OKResponse < HTTPResponse
13
+ def initialize(body = "")
14
+ super(200, body)
15
+ end
16
+ end
17
+
18
+ class BadRequestResponse < HTTPResponse
19
+ def initialize(body = "")
20
+ super(400, body)
21
+ end
22
+ end
23
+
24
+ class UnauthorizedResponse < HTTPResponse
25
+ def initialize(body = "")
26
+ super(401, body)
27
+ end
28
+ end
29
+
30
+ class ForbiddenResponse < HTTPResponse
31
+ def initialize(body = "")
32
+ super(403, body)
33
+ end
34
+ end
35
+
36
+ class NotFoundResponse < HTTPResponse
37
+ def initialize(body = "")
38
+ super(404, body)
39
+ end
40
+ end
41
+
42
+ class NotAllowedResponse < HTTPResponse
43
+ def initialize(body = "")
44
+ super(405, body)
45
+ end
46
+ end
47
+
48
+ end
@@ -0,0 +1,84 @@
1
+ require 'mime/types'
2
+ require 'tempfile'
3
+
4
+ module BOTR
5
+
6
+ class Multipart
7
+
8
+ def initialize(data_path = "", boundary = nil)
9
+ @data_path = data_path
10
+ @boundary = boundary
11
+
12
+ build_stream
13
+ end
14
+
15
+ def close
16
+ @stream.close! unless @stream.closed?
17
+ end
18
+
19
+ def method_missing(*args)
20
+ @stream.send(*args)
21
+ end
22
+
23
+ def respond_to?(meth)
24
+ @stream.respond_to?(meth) || super(meth)
25
+ end
26
+
27
+
28
+ private
29
+
30
+ def boundary
31
+ @boundary ||= rand(1000000).to_s
32
+ end
33
+
34
+ def mime_type_for(path)
35
+ mime = MIME::Types.type_for(path).first
36
+ mime ? mime.content_type : 'text/plain'
37
+ end
38
+
39
+ # Build a multipart/form-data body as per RFC 2388.
40
+ def build_stream
41
+ # Form input field name for the file path must be set to file.
42
+ # Any other field name will not be picked up by the upload server.
43
+ name = 'file'
44
+ content_length = File.size(@data_path)
45
+ content_type = mime_type_for(@data_path)
46
+
47
+ @stream = Tempfile.new("BitsOnTheRun.Upload.")
48
+ @stream.binmode
49
+
50
+ write_stream_head(name, content_type, content_length)
51
+ write_stream_body
52
+ write_stream_tail
53
+
54
+ @stream.seek(0)
55
+ end
56
+
57
+ def write_stream_head(name, content_type, content_length)
58
+ transfer_encoding = "binary"
59
+ content_disposition = "form-data"
60
+
61
+ @stream.write "--#{boundary}\r\n"
62
+ @stream.write "Content-Disposition: #{content_disposition}; name=\"#{name}\"; filename=\"#{File.basename(@data_path)}\"\r\n"
63
+ @stream.write "Content-Length: #{content_length}\r\n"
64
+ @stream.write "Content-Type: #{content_type}\r\n"
65
+ @stream.write "Content-Transfer-Encoding: #{transfer_encoding}\r\n"
66
+ @stream.write "\r\n"
67
+ end
68
+
69
+ def write_stream_body
70
+ f = File.new(@data_path)
71
+ while data = f.read(8124)
72
+ @stream.write(data)
73
+ end
74
+ ensure
75
+ f.close unless f.closed?
76
+ end
77
+
78
+ def write_stream_tail
79
+ @stream.write "\r\n"
80
+ @stream.write "--#{boundary}--\r\n"
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,28 @@
1
+ module URI
2
+
3
+ # FIX: For some reason, the Bits on the Run API doesn't support the encoding
4
+ # of spaces to "+". As such, (ASCII space) will encode to "%20".
5
+ TBLENCWWWCOMP__ = TBLENCWWWCOMP_.dup
6
+ TBLENCWWWCOMP__[' '] = '%20'
7
+ TBLENCWWWCOMP__.freeze
8
+
9
+ def self.encode_www_form_component(str, enc=nil)
10
+ str = str.to_s.dup
11
+
12
+ if str.encoding != Encoding::ASCII_8BIT
13
+ if enc && enc != Encoding::ASCII_8BIT
14
+ str.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace)
15
+ str.encode!(enc, fallback: ->(x){"&#{x.ord};"})
16
+ end
17
+ str.force_encoding(Encoding::ASCII_8BIT)
18
+ end
19
+ str.gsub!(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP__)
20
+ str.force_encoding(Encoding::US_ASCII)
21
+ end
22
+
23
+ def self.decode_www_form_component(str, enc=Encoding::UTF_8)
24
+ raise ArgumentError, "invalid %-encoding (#{str})" unless /\A[^%]*(?:%\h\h[^%]*)*\z/ =~ str
25
+ str.b.gsub(/\+|%\h\h/, TBLENCWWWCOMP__).force_encoding(enc)
26
+ end
27
+
28
+ end