bigbluebutton-api-ruby 1.3.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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