bigbluebutton-api-ruby 1.3.0 → 1.8.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.
@@ -9,10 +9,11 @@ require 'bigbluebutton_formatter'
9
9
  require 'bigbluebutton_modules'
10
10
  require 'bigbluebutton_config_xml'
11
11
  require 'bigbluebutton_config_layout'
12
+ require 'logger'
12
13
 
13
14
  module BigBlueButton
14
15
 
15
- # This class provides access to the BigBlueButton API. For more details see README.rdoc.
16
+ # This class provides access to the BigBlueButton API. For more details see README.
16
17
  #
17
18
  # Sample usage of the API is as follows:
18
19
  # 1. Create a meeting with create_meeting;
@@ -44,14 +45,14 @@ module BigBlueButton
44
45
  # URL to a BigBlueButton server (e.g. http://demo.bigbluebutton.org/bigbluebutton/api)
45
46
  attr_accessor :url
46
47
 
47
- # Secret salt for this server
48
- attr_accessor :salt
48
+ # Shared secret for this server
49
+ attr_accessor :secret
49
50
 
50
51
  # API version e.g. 0.81
51
52
  attr_accessor :version
52
53
 
53
- # Flag to turn on/off debug mode
54
- attr_accessor :debug
54
+ # logger to log reponses and infos
55
+ attr_accessor :logger
55
56
 
56
57
  # Maximum wait time for HTTP requests (secs)
57
58
  attr_accessor :timeout
@@ -65,22 +66,27 @@ module BigBlueButton
65
66
 
66
67
  # Initializes an instance
67
68
  # url:: URL to a BigBlueButton server (e.g. http://demo.bigbluebutton.org/bigbluebutton/api)
68
- # salt:: Secret salt for this server
69
+ # secret:: Shared secret for this server
69
70
  # version:: API version e.g. 0.81
70
- def initialize(url, salt, version='0.81', debug=false)
71
- @supported_versions = ['0.8', '0.81']
71
+ def initialize(url, secret, version=nil, logger=nil)
72
+ @supported_versions = ['0.8', '0.81', '0.9', '1.0']
72
73
  @url = url
73
- @salt = salt
74
- @debug = debug
74
+ @secret = secret
75
75
  @timeout = 10 # default timeout for api requests
76
76
  @request_headers = {} # http headers sent in all requests
77
-
78
- @version = version || get_api_version
77
+ @logger = logger
78
+ if logger.nil?
79
+ @logger = Logger.new(STDOUT)
80
+ @logger.level = Logger::INFO
81
+ end
82
+
83
+ version = nil if version && version.strip.empty?
84
+ @version = nearest_version(version || get_api_version)
79
85
  unless @supported_versions.include?(@version)
80
- raise BigBlueButtonException.new("BigBlueButton error: Invalid API version #{version}. Supported versions: #{@supported_versions.join(', ')}")
86
+ @logger.warn("BigBlueButtonAPI: detected unsupported version, using the closest one that is supported (#{@version})")
81
87
  end
82
88
 
83
- puts "BigBlueButtonAPI: Using version #{@version}" if @debug
89
+ @logger.debug("BigBlueButtonAPI: Using version #{@version}")
84
90
  end
85
91
 
86
92
  # Creates a new meeting. Returns the hash with the response or throws BigBlueButtonException
@@ -225,7 +231,8 @@ module BigBlueButton
225
231
  # in this hash and they will be added to the API call.
226
232
  def join_meeting_url(meeting_id, user_name, password, options={})
227
233
  params = { :meetingID => meeting_id, :password => password, :fullName => user_name }.merge(options)
228
- get_url(:join, params)
234
+ url, data = get_url(:join, params)
235
+ url
229
236
  end
230
237
 
231
238
  # Returns a hash object containing the information of a meeting.
@@ -459,6 +466,10 @@ module BigBlueButton
459
466
  options[:meetingID] = options[:meetingID].join(",") if options[:meetingID].instance_of?(Array)
460
467
  end
461
468
 
469
+ if options.has_key?(:state)
470
+ options[:state] = options[:state].join(",") if options[:state].instance_of?(Array)
471
+ end
472
+
462
473
  response = send_api_request(:getRecordings, options)
463
474
 
464
475
  formatter = BigBlueButtonFormatter.new(response)
@@ -467,6 +478,30 @@ module BigBlueButton
467
478
  response
468
479
  end
469
480
 
481
+ # Available since BBB v1.1
482
+ # Update metadata (or other attributes depending on the API implementation) for a given recordID (or set of record IDs).
483
+ # recordIDs (string, Array):: ID or IDs of the target recordings.
484
+ # Any of the following values are accepted:
485
+ # "id1"
486
+ # "id1,id2,id3"
487
+ # ["id1"]
488
+ # ["id1", "id2", "id3"]
489
+ # meta (String):: Pass one or more metadata values to be update (format is the same as in create call)
490
+ # options (Hash):: Hash with additional parameters. This method doesn't accept additional
491
+ # parameters, but if you have a custom API with more parameters, you
492
+ # can simply pass them in this hash and they will be added to the API call.
493
+ #
494
+ # === Example responses
495
+ #
496
+ # { :returncode => success, :updated => true }
497
+ #
498
+ def update_recordings(recordIDs, meta=nil, options={})
499
+ recordIDs = recordIDs.join(",") if recordIDs.instance_of?(Array) # ["id1", "id2"] becomes "id1,id2"
500
+ params = { :recordID => recordIDs, :meta => meta }.merge(options)
501
+ send_api_request(:updateRecordings, params)
502
+ end
503
+
504
+
470
505
  # Publish and unpublish recordings for a given recordID (or set of record IDs).
471
506
  # recordIDs (string, Array):: ID or IDs of the target recordings.
472
507
  # Any of the following values are accepted:
@@ -598,10 +633,15 @@ module BigBlueButton
598
633
  response[:returncode]
599
634
  end
600
635
 
636
+ def check_url
637
+ url, data = get_url(:check)
638
+ url
639
+ end
640
+
601
641
  # API's are equal if all the following attributes are equal.
602
642
  def ==(other)
603
643
  r = true
604
- [:url, :supported_versions, :salt, :version, :debug].each do |param|
644
+ [:url, :supported_versions, :secret, :version, :logger].each do |param|
605
645
  r = r && self.send(param) == other.send(param)
606
646
  end
607
647
  r
@@ -622,11 +662,12 @@ module BigBlueButton
622
662
  # params (Hash):: The parameters to be passed in the URL
623
663
  def get_url(method, params={})
624
664
  if method == :index
625
- return @url
665
+ return @url, nil
666
+ elsif method == :check
667
+ baseurl = URI.join(@url, "/").to_s
668
+ return "#{baseurl}check", nil
626
669
  end
627
670
 
628
- url = "#{@url}/#{method}?"
629
-
630
671
  # stringify and escape all params
631
672
  params.delete_if { |k, v| v.nil? } unless params.nil?
632
673
  # some API calls require the params to be sorted
@@ -634,16 +675,22 @@ module BigBlueButton
634
675
  params = params.inject({}){ |memo,(k,v)| memo[k.to_sym] = v; memo }
635
676
  params = Hash[params.sort]
636
677
  params_string = ""
637
- params_string = params.map{ |k,v| "#{k}=" + CGI::escape(v.to_s) unless k.nil? || v.nil? }.join("&")
678
+ params_string = params.map{ |k,v| "#{k}=" + URI.encode_www_form_component(v.to_s) unless k.nil? || v.nil? }.join("&")
638
679
 
639
680
  # checksum calc
640
- checksum_param = params_string + @salt
681
+ checksum_param = params_string + @secret
641
682
  checksum_param = method.to_s + checksum_param
642
683
  checksum = Digest::SHA1.hexdigest(checksum_param)
643
684
 
644
- # final url
645
- url += "#{params_string}&" unless params_string.empty?
646
- url += "checksum=#{checksum}"
685
+ if method == :setConfigXML
686
+ params_string = "checksum=#{checksum}&#{params_string}"
687
+ return "#{@url}/#{method}", params_string
688
+ else
689
+ url = "#{@url}/#{method}?"
690
+ url += "#{params_string}&" unless params_string.empty?
691
+ url += "checksum=#{checksum}"
692
+ return url, nil
693
+ end
647
694
  end
648
695
 
649
696
  # Performs an API call.
@@ -660,7 +707,9 @@ module BigBlueButton
660
707
  # raw (boolean):: If true, returns the data as it was received. Will not parse it into a Hash,
661
708
  # check for errors or throw exceptions.
662
709
  def send_api_request(method, params={}, data=nil, raw=false)
663
- url = get_url(method, params)
710
+ # if the method returns a body, use it as the data in the post request
711
+ url, body = get_url(method, params)
712
+ data = body if body
664
713
 
665
714
  @http_response = send_request(url, data)
666
715
  return {} if @http_response.body.empty?
@@ -699,29 +748,59 @@ module BigBlueButton
699
748
  # Otherwise uses GET
700
749
  def send_request(url, data=nil)
701
750
  begin
702
- puts "BigBlueButtonAPI: URL request = #{url}" if @debug
751
+ @logger.debug("BigBlueButtonAPI: URL request = #{url}")
703
752
  url_parsed = URI.parse(url)
704
753
  http = Net::HTTP.new(url_parsed.host, url_parsed.port)
705
754
  http.open_timeout = @timeout
706
755
  http.read_timeout = @timeout
756
+ http.use_ssl = true if url_parsed.scheme.downcase == 'https'
757
+
707
758
  if data.nil?
708
759
  response = http.get(url_parsed.request_uri, @request_headers)
709
760
  else
710
- puts "BigBlueButtonAPI: Sending as a POST request with data.size = #{data.size}" if @debug
711
- opts = { 'Content-Type' => 'text/xml' }.merge @request_headers
761
+ @logger.debug("BigBlueButtonAPI: Sending as a POST request with data.size = #{data.size}")
762
+ opts = { 'Content-Type' => 'application/xml' }.merge @request_headers
712
763
  response = http.post(url_parsed.request_uri, data, opts)
713
764
  end
714
- puts "BigBlueButtonAPI: URL response = #{response.body}" if @debug
765
+ @logger.info("BigBlueButtonAPI: request=#{url} response_status=#{response.class.name} response_code=#{response.code} message_key=#{response.message}")
766
+ @logger.debug("BigBlueButtonAPI: URL response = #{response.body}")
715
767
 
716
- rescue TimeoutError => error
717
- raise BigBlueButtonException.new("Timeout error. Your server is probably down: \"#{@url}\". Error: #{error}")
768
+ rescue Timeout::Error => error
769
+ exception = BigBlueButtonException.new("Timeout error. Your server is probably down: \"#{@url}\". Error: #{error}")
770
+ exception.key = 'TimeoutError'
771
+ raise exception
718
772
 
719
773
  rescue Exception => error
720
- raise BigBlueButtonException.new("Connection error. Your URL is probably incorrect: \"#{@url}\". Error: #{error}")
774
+ exception = BigBlueButtonException.new("Connection error. Your URL is probably incorrect: \"#{@url}\". Error: #{error}")
775
+ exception.key = 'IncorrectUrlError'
776
+ raise exception
721
777
  end
722
778
 
723
779
  response
724
780
  end
725
781
 
782
+ def nearest_version(target)
783
+ version = target
784
+
785
+ # 0.81 in BBB is actually < than 0.9, but not when comparing here
786
+ # so normalize x.xx versions to x.x.x
787
+ match = version.match(/(\d)\.(\d)(\d)/)
788
+ version = "#{match[1]}.#{match[2]}.#{match[3]}" if match
789
+
790
+ # we don't allow older versions than the one supported, use an old version of the gem for that
791
+ if Gem::Version.new(version) < Gem::Version.new(@supported_versions[0])
792
+ exception = BigBlueButtonException.new("BigBlueButton error: Invalid API version #{version}. Supported versions: #{@supported_versions.join(', ')}")
793
+ exception.key = 'APIVersionError'
794
+ raise exception
795
+
796
+ # allow newer versions by using the newest one we support
797
+ elsif Gem::Version.new(version) > Gem::Version.new(@supported_versions.last)
798
+ @supported_versions.last
799
+
800
+ else
801
+ target
802
+ end
803
+ end
804
+
726
805
  end
727
806
  end
@@ -29,7 +29,9 @@ module BigBlueButton
29
29
  begin
30
30
  @xml = XmlSimple.xml_in(xml, opts)
31
31
  rescue Exception => e
32
- raise BigBlueButton::BigBlueButtonException.new("Error parsing the layouts XML. Error: #{e.message}")
32
+ exception = BigBlueButton::BigBlueButtonException.new("Error parsing the layouts XML. Error: #{e.message}")
33
+ exception.key = 'XMLParsingError'
34
+ raise exception
33
35
  end
34
36
  end
35
37
 
@@ -25,15 +25,17 @@ module BigBlueButton
25
25
  attr_accessor :xml
26
26
 
27
27
  def initialize(xml)
28
- @original_xml = nil
28
+ @original_string = nil
29
29
  @xml = nil
30
30
  unless xml.nil?
31
31
  opts = { 'ForceArray' => false, 'KeepRoot' => true }
32
32
  begin
33
33
  @xml = XmlSimple.xml_in(xml, opts)
34
- @original_xml = Marshal.load(Marshal.dump(@xml))
34
+ @original_string = self.as_string.clone
35
35
  rescue Exception => e
36
- raise BigBlueButton::BigBlueButtonException.new("Error parsing the config XML. Error: #{e.message}")
36
+ exception = BigBlueButton::BigBlueButtonException.new("Error parsing the config XML. Error: #{e.message}")
37
+ exception.key = 'XMLParsingError'
38
+ raise exception
37
39
  end
38
40
  end
39
41
  end
@@ -60,7 +62,8 @@ module BigBlueButton
60
62
  if tag
61
63
  attr = find_attribute(tag, attr_name)
62
64
  if attr
63
- tag[attr_name] = value
65
+ # saves always as string
66
+ tag[attr_name] = value.is_a?(String) ? value : value.to_s
64
67
  else
65
68
  nil
66
69
  end
@@ -75,7 +78,7 @@ module BigBlueButton
75
78
 
76
79
  def is_modified?
77
80
  @xml and
78
- @xml != @original_xml
81
+ self.as_string != @original_string
79
82
  end
80
83
 
81
84
  protected
@@ -1,6 +1,6 @@
1
1
  module BigBlueButton
2
2
 
3
- class BigBlueButtonException < Exception
3
+ class BigBlueButtonException < StandardError
4
4
  attr_accessor :key
5
5
 
6
6
  def to_s
@@ -22,7 +22,7 @@ module BigBlueButton
22
22
  unless @hash.has_key?(key)
23
23
  0
24
24
  else
25
- @hash[key] = @hash[key].to_i
25
+ @hash[key] = @hash[key].to_i rescue 0
26
26
  end
27
27
  end
28
28
 
@@ -49,7 +49,9 @@ module BigBlueButton
49
49
  if value.is_a?(Numeric)
50
50
  result = value == 0 ? nil : DateTime.parse(Time.at(value/1000.0).to_s)
51
51
  else
52
- if value.downcase == "null"
52
+ if (value.is_a?(Hash) || value.is_a?(Array)) && value.empty?
53
+ result = nil
54
+ elsif value.is_a?(String) && (value.empty? || value.downcase == 'null')
53
55
  result = nil
54
56
  else
55
57
  # note: just in case the value comes as a string in the format: "Thu Sep 01 17:51:42 UTC 2011"
@@ -10,7 +10,9 @@ module BigBlueButton
10
10
  hash = XmlSimple.xml_in(xml_io, opts)
11
11
  return symbolize_keys(hash)
12
12
  rescue Exception => e
13
- raise BigBlueButtonException.new("Impossible to convert XML to hash. Error: #{e.message}")
13
+ exception = BigBlueButtonException.new("Impossible to convert XML to hash. Error: #{e.message}")
14
+ exception.key = 'XMLConversionError'
15
+ raise exception
14
16
  end
15
17
  end
16
18
 
@@ -5,10 +5,9 @@ describe BigBlueButton::BigBlueButtonApi do
5
5
 
6
6
  # default variables and API object for all tests
7
7
  let(:url) { "http://server.com" }
8
- let(:salt) { "1234567890abcdefghijkl" }
8
+ let(:secret) { "1234567890abcdefghijkl" }
9
9
  let(:version) { "0.81" }
10
- let(:debug) { false }
11
- let(:api) { BigBlueButton::BigBlueButtonApi.new(url, salt, version, debug) }
10
+ let(:api) { BigBlueButton::BigBlueButtonApi.new(url, secret, version) }
12
11
 
13
12
  describe "#get_default_config_xml" do
14
13
  let(:response) { "<response><returncode>1</returncode></response>" }
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ # Tests for BBB API version 0.81
4
+ describe BigBlueButton::BigBlueButtonApi do
5
+
6
+ # default variables and API object for all tests
7
+ let(:url) { "http://server.com" }
8
+ let(:secret) { "1234567890abcdefghijkl" }
9
+ let(:version) { "0.9" }
10
+ let(:api) { BigBlueButton::BigBlueButtonApi.new(url, secret, version) }
11
+
12
+ describe "#create_meeting" do
13
+ context "accepts the new parameters" do
14
+ let(:req_params) {
15
+ { :name => "name", :meetingID => "meeting-id",
16
+ :moderatorOnlyMessage => "my-msg", :autoStartRecording => "false",
17
+ :allowStartStopRecording => "true"
18
+ }
19
+ }
20
+
21
+ before { api.should_receive(:send_api_request).with(:create, req_params) }
22
+ it {
23
+ options = {
24
+ :moderatorOnlyMessage => "my-msg",
25
+ :autoStartRecording => "false",
26
+ :allowStartStopRecording => "true"
27
+ }
28
+ api.create_meeting("name", "meeting-id", options)
29
+ }
30
+ end
31
+ end
32
+ end