bigbluebutton-api-ruby 0.0.11 → 0.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/.gitignore +5 -2
  2. data/.rvmrc +1 -1
  3. data/.travis.yml +6 -0
  4. data/CHANGELOG.rdoc +19 -8
  5. data/Gemfile +9 -1
  6. data/Gemfile.lock +62 -9
  7. data/LICENSE +5 -7
  8. data/LICENSE_003 +20 -0
  9. data/README.rdoc +42 -19
  10. data/Rakefile +31 -19
  11. data/bigbluebutton-api-ruby.gemspec +5 -5
  12. data/examples/get_version_example.rb +18 -0
  13. data/examples/join_example.rb +59 -0
  14. data/examples/overall_0.7_example.rb +92 -0
  15. data/examples/prepare.rb +38 -0
  16. data/extras/bigbluebutton_bot.rb +64 -0
  17. data/extras/download_bot_from.txt +1 -0
  18. data/extras/test-presentation.pdf +0 -0
  19. data/features/check_status.feature +45 -0
  20. data/features/config.yml.example +21 -0
  21. data/features/create_meetings.feature +29 -0
  22. data/features/end_meetings.feature +27 -0
  23. data/features/join_meetings.feature +29 -0
  24. data/features/pre_upload_slides.feature +14 -0
  25. data/features/recordings.feature +34 -0
  26. data/features/step_definitions/check_status_steps.rb +119 -0
  27. data/features/step_definitions/common_steps.rb +122 -0
  28. data/features/step_definitions/create_meetings_steps.rb +54 -0
  29. data/features/step_definitions/end_meetings_steps.rb +49 -0
  30. data/features/step_definitions/join_meetings_steps.rb +39 -0
  31. data/features/step_definitions/pre_upload_slides_steps.rb +13 -0
  32. data/features/step_definitions/recordings_steps.rb +38 -0
  33. data/features/support/api_tests/configs.rb +51 -0
  34. data/features/support/env.rb +7 -0
  35. data/features/support/hooks.rb +11 -0
  36. data/lib/bigbluebutton_api.rb +301 -97
  37. data/lib/bigbluebutton_formatter.rb +105 -19
  38. data/lib/bigbluebutton_modules.rb +92 -0
  39. data/lib/hash_to_xml.rb +22 -51
  40. data/spec/bigbluebutton_api_0.8_spec.rb +273 -0
  41. data/spec/bigbluebutton_api_spec.rb +211 -117
  42. data/spec/bigbluebutton_formatter_spec.rb +178 -29
  43. data/spec/bigbluebutton_modules_spec.rb +95 -0
  44. data/spec/data/hash_to_xml_complex.xml +45 -0
  45. data/spec/hash_to_xml_spec.rb +143 -0
  46. data/spec/spec_helper.rb +4 -2
  47. data/spec/support/forgery/forgeries/random_name.rb +7 -0
  48. data/spec/support/forgery/forgeries/url.rb +5 -0
  49. metadata +47 -12
  50. data/test/config.yml.example +0 -9
  51. data/test/test.rb +0 -154
@@ -0,0 +1,122 @@
1
+ # Common steps, used in several features
2
+
3
+ When /^the default BigBlueButton server$/ do
4
+ @api = BigBlueButton::BigBlueButtonApi.new(@config_server['url'],
5
+ @config_server['salt'],
6
+ @config_server['version'].to_s,
7
+ @config['debug'])
8
+ @api.timeout = @config['timeout_req']
9
+ end
10
+
11
+ # default create call, with no optional parameters (only the mod pass)
12
+ When /^that a meeting was created$/ do
13
+ steps %Q{ When the default BigBlueButton server }
14
+
15
+ @req.id = Forgery(:basic).random_name("test")
16
+ @req.name = @req.id
17
+ @req.mod_pass = Forgery(:basic).password
18
+ @req.opts = { :moderatorPW => @req.mod_pass }
19
+ @req.method = :create
20
+ @req.response = @api.create_meeting(@req.id, @req.name, @req.opts)
21
+ end
22
+
23
+ When /^that a meeting was created with all the optional arguments$/i do
24
+ steps %Q{ When the default BigBlueButton server }
25
+
26
+ @req.id = Forgery(:basic).random_name("test-create")
27
+ @req.name = @req.id
28
+ @req.mod_pass = Forgery(:basic).password
29
+ @req.opts = { :moderatorPW => @req.mod_pass,
30
+ :attendeePW => Forgery(:basic).password,
31
+ :welcome => Forgery(:lorem_ipsum).words(10),
32
+ :dialNumber => Forgery(:basic).number(:at_most => 999999999).to_s,
33
+ :logoutURL => Forgery(:internet).url,
34
+ :voiceBridge => Forgery(:basic).number(:at_least => 70000, :at_most => 79999),
35
+ :webVoice => Forgery(:basic).text,
36
+ :maxParticipants => Forgery(:basic).number }
37
+ if @api.version >= "0.8"
38
+ @req.opts.merge!( { :record => false,
39
+ :duration => Forgery(:basic).number(:at_least => 10, :at_most => 60),
40
+ :meta_one => "one", :meta_TWO => "TWO" } )
41
+ end
42
+ @req.method = :create
43
+ @req.response = @api.create_meeting(@req.id, @req.name, @req.opts)
44
+ end
45
+
46
+ When /^the meeting is running$/ do
47
+ steps %Q{ When the meeting is running with 1 attendees }
48
+ end
49
+
50
+ When /^the meeting is running with (\d+) attendees$/ do |count|
51
+ mobile_salt = @config_server.has_key?('mobile_salt') ? @config_server['mobile_salt'] : ""
52
+ BigBlueButtonBot.new(@api, @req.id, mobile_salt, count.to_i, @config['timeout_bot_start'])
53
+ end
54
+
55
+ When /^the response is an error with the key "(.*)"$/ do |key|
56
+ @req.exception.should_not be_nil
57
+ @req.exception.key.should == key
58
+ end
59
+
60
+ When /^the response is successful$/ do
61
+ @req.response[:returncode].should be_true
62
+ end
63
+
64
+ When /^the response is successful with no messages$/ do
65
+ @req.response[:returncode].should be_true
66
+ @req.response[:messageKey].should == ""
67
+ @req.response[:message].should == ""
68
+ end
69
+
70
+ When /^the response has the messageKey "(.*)"$/ do |key|
71
+ @req.response[:messageKey].should == key
72
+ @req.response[:message].should_not be_empty
73
+ end
74
+
75
+ When /^the response is successful and well formatted$/ do
76
+ case @req.method
77
+ when :create
78
+ steps %Q{ When the response to the create method is successful and well formatted }
79
+ when :end
80
+ steps %Q{ When the response to the end method is successful and well formatted }
81
+ when :get_recordings
82
+ steps %Q{ When the response to the get_recordings method is successful and well formatted }
83
+ end
84
+ end
85
+
86
+ When /^the response to the create method is successful and well formatted$/ do
87
+ @req.response[:returncode].should be_true
88
+ @req.response[:meetingID].should == @req.id
89
+ @req.response[:hasBeenForciblyEnded].should be_false
90
+ @req.response[:messageKey].should == ""
91
+ @req.response[:message].should == ""
92
+
93
+ if @req.opts.has_key?(:attendeePW)
94
+ @req.response[:attendeePW].should == @req.opts[:attendeePW]
95
+ else # auto generated password
96
+ @req.response[:attendeePW].should be_a(String)
97
+ @req.response[:attendeePW].should_not be_empty
98
+ @req.opts[:attendeePW] = @req.response[:attendeePW]
99
+ end
100
+ if @req.opts.has_key?(:moderatorPW)
101
+ @req.response[:moderatorPW].should == @req.opts[:moderatorPW]
102
+ else # auto generated password
103
+ @req.response[:moderatorPW].should be_a(String)
104
+ @req.response[:moderatorPW].should_not be_empty
105
+ @req.opts[:moderatorPW] = @req.response[:moderatorPW]
106
+ end
107
+
108
+ if @api.version >= "0.8"
109
+ @req.response[:createTime].should be_a(Numeric)
110
+ end
111
+ end
112
+
113
+ When /^the response to the end method is successful and well formatted$/ do
114
+ @req.response[:returncode].should be_true
115
+ @req.response[:messageKey].should == "sentEndMeetingRequest"
116
+ @req.response[:message].should_not be_empty
117
+ end
118
+
119
+ When /^the response to the get_recordings method is successful and well formatted$/ do
120
+ @req.response[:returncode].should be_true
121
+ @req.response[:recordings].should == []
122
+ end
@@ -0,0 +1,54 @@
1
+ When /^the create method is called with all the optional arguments$/i do
2
+ steps %Q{ When that a meeting was created with all the optional arguments }
3
+ end
4
+
5
+ When /^the create method is called with no optional arguments$/i do
6
+ steps %Q{ When the default BigBlueButton server }
7
+ steps %Q{ When that a meeting was created }
8
+ end
9
+
10
+ When /^the create method is called with a duplicated meeting id$/ do
11
+ steps %Q{ When the default BigBlueButton server }
12
+
13
+ @req.id = Forgery(:basic).random_name("test-create")
14
+ @req.name = @req.id
15
+
16
+ # first meeting
17
+ @req.method = :create
18
+ @api.create_meeting(@req.id, @req.name)
19
+
20
+ begin
21
+ # duplicated meeting to be tested
22
+ @req.method = :create
23
+ @req.response = @api.create_meeting(@req.id, @req.name)
24
+ rescue Exception => @req.exception
25
+ end
26
+ end
27
+
28
+ When /^the create method is called$/ do
29
+ steps %Q{ When the default BigBlueButton server }
30
+
31
+ @req.id = Forgery(:basic).random_name("test-create")
32
+ @req.name = @req.id
33
+ @req.method = :create
34
+ @req.response = @api.create_meeting(@req.id, @req.name)
35
+ @req.mod_pass = @req.response[:moderatorPW]
36
+ end
37
+
38
+ When /^the meeting is forcibly ended$/ do
39
+ @req.response = @api.end_meeting(@req.id, @req.mod_pass)
40
+ end
41
+
42
+ When /^the create method is called again with the same meeting id$/ do
43
+ begin
44
+ @req.method = :create
45
+ @req.response = @api.create_meeting(@req.id, @req.name)
46
+ rescue Exception => @req.exception
47
+ end
48
+ end
49
+
50
+ When /^the meeting exists in the server$/ do
51
+ @req.response = @api.get_meetings
52
+ @req.response[:meetings].reject!{ |m| m[:meetingID] != @req.id }
53
+ @req.response[:meetings].count.should == 1
54
+ end
@@ -0,0 +1,49 @@
1
+ When /^the method to end the meeting is called$/ do
2
+ begin
3
+ @req.method = :end
4
+ @req.response = @api.end_meeting(@req.id, @req.mod_pass)
5
+ rescue Exception => @req.exception
6
+ end
7
+ end
8
+
9
+ When /^the meeting should be ended$/ do
10
+ # the meeting only ends when everybody closes the session
11
+ BigBlueButtonBot.finalize
12
+
13
+ # wait for the meeting to end
14
+ Timeout::timeout(@config['timeout_ending']) do
15
+ running = true
16
+ while running
17
+ sleep 1
18
+ response = @api.get_meetings
19
+ selected = response[:meetings].reject!{ |m| m[:meetingID] != @req.id }
20
+ running = selected[0][:running] unless selected.nil?
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ When /^the flag hasBeenForciblyEnded should be set$/ do
27
+ @req.response[:hasBeenForciblyEnded].should be_true
28
+ end
29
+
30
+ When /^the information returned by get_meeting_info is correct$/ do
31
+ # check only what is different in a meeting that WAS ENDED
32
+ # the rest is checked in other scenarios
33
+
34
+ @req.response = @api.get_meeting_info(@req.id, @req.mod_pass)
35
+ @req.response[:running].should be_false
36
+ @req.response[:hasBeenForciblyEnded].should be_true
37
+ @req.response[:participantCount].should == 0
38
+ @req.response[:moderatorCount].should == 0
39
+ @req.response[:attendees].should == []
40
+
41
+ # start and end times that should be within 2 hours from now
42
+ @req.response[:startTime].should be_a(DateTime)
43
+ @req.response[:startTime].should < DateTime.now
44
+ @req.response[:startTime].should >= DateTime.now - (2/24.0)
45
+ @req.response[:endTime].should be_a(DateTime)
46
+ @req.response[:endTime].should < DateTime.now
47
+ @req.response[:endTime].should >= DateTime.now - (2/24.0)
48
+ @req.response[:endTime].should > @req.response[:startTime]
49
+ end
@@ -0,0 +1,39 @@
1
+ When /^the user tries to access the link to join the meeting as (.*)$/ do |role|
2
+ case role.downcase.to_sym
3
+ when :moderator
4
+ @req.response = @api.join_meeting_url(@req.id, "any-mod", @req.mod_pass)
5
+ when :attendee
6
+ @req.response = @api.join_meeting_url(@req.id, "any-attendee", @req.response[:attendeePW])
7
+ end
8
+ end
9
+
10
+ When /^he is redirected to the BigBlueButton client$/ do
11
+ # requests the join url and expects a redirect
12
+ uri = URI(@req.response)
13
+ response = Net::HTTP.get_response(uri)
14
+ response.should be_a(Net::HTTPFound)
15
+ response.code.should == "302"
16
+
17
+ # check redirect to the correct bbb client page
18
+ bbb_client_url = @api.url.gsub(URI(@api.url).path, "") + "/client/BigBlueButton.html"
19
+ response["location"].should match(/#{bbb_client_url}/)
20
+ end
21
+
22
+ When /^the user tries to access the link to join a meeting that was not created$/ do
23
+ @req.response = @api.join_meeting_url("should-not-exist-in-server", "any", "any")
24
+ end
25
+
26
+ When /^the response is an xml with the error "(.*)"$/ do |error|
27
+ # requests the join url and expects an ok with an xml in the response body
28
+ uri = URI(@req.response)
29
+ response = Net::HTTP.get_response(uri)
30
+ response.should be_a(Net::HTTPOK)
31
+ response.code.should == "200"
32
+ response["content-type"].should match(/text\/xml/)
33
+ response.body.should match(/#{error}/)
34
+ end
35
+
36
+ When /^the user tries to access the link to join the meeting using a wrong password$/ do
37
+ @req.response = @api.join_meeting_url(@req.id, "any-attendee", @req.mod_pass + "is wrong")
38
+ end
39
+
@@ -0,0 +1,13 @@
1
+ When /^the user creates a meeting pre\-uploading the following presentations:$/ do |table|
2
+ modules = BigBlueButton::BigBlueButtonModules.new
3
+ table.hashes.each do |pres|
4
+ modules.add_presentation(pres["type"].to_sym, pres["presentation"])
5
+ end
6
+
7
+ @req.id = Forgery(:basic).random_name("test-pre-upload")
8
+ @req.name = @req.id
9
+ @req.method = :create
10
+ @req.opts = {}
11
+ @req.response = @api.create_meeting(@req.id, @req.name, @req.opts, modules)
12
+ @req.mod_pass = @req.response[:moderatorPW]
13
+ end
@@ -0,0 +1,38 @@
1
+ When /^the user creates a meeting with the record flag$/ do
2
+ steps %Q{ When the default BigBlueButton server }
3
+
4
+ @req.id = Forgery(:basic).random_name("test-recordings")
5
+ @req.name = @req.id
6
+ @req.method = :create
7
+ @req.opts = { :record => true }
8
+ @req.response = @api.create_meeting(@req.id, @req.name, @req.opts)
9
+ @req.mod_pass = @req.response[:moderatorPW]
10
+ end
11
+
12
+ When /^the meeting is set to be recorded$/ do
13
+ @req.response = @api.get_meeting_info(@req.id, @req.mod_pass)
14
+ @req.response[:returncode].should be_true
15
+ @req.response[:recording].should be_true
16
+ end
17
+
18
+ When /^the user creates a meeting without the record flag$/ do
19
+ steps %Q{ When the default BigBlueButton server }
20
+
21
+ @req.id = Forgery(:basic).random_name("test-recordings")
22
+ @req.name = @req.id
23
+ @req.method = :create
24
+ @req.opts = {}
25
+ @req.response = @api.create_meeting(@req.id, @req.name)
26
+ @req.mod_pass = @req.response[:moderatorPW]
27
+ end
28
+
29
+ When /^the meeting is not set to be recorded$/i do
30
+ @req.response = @api.get_meeting_info(@req.id, @req.mod_pass)
31
+ @req.response[:returncode].should be_true
32
+ @req.response[:recording].should be_false
33
+ end
34
+
35
+ When /^the user calls the get_recordings method$/ do
36
+ @req.method = :get_recordings
37
+ @req.response = @api.get_recordings
38
+ end
@@ -0,0 +1,51 @@
1
+ module BigBlueButtonAPITests
2
+
3
+ # Test object that stores information about an API request
4
+ class APIRequest
5
+ attr_accessor :opts # options hash
6
+ attr_accessor :id # meetind id
7
+ attr_accessor :mod_pass # moderator password
8
+ attr_accessor :name # meeting name
9
+ attr_accessor :method # last api method called
10
+ attr_accessor :response # last api response
11
+ attr_accessor :exception # last exception
12
+ end
13
+
14
+ # Global configurations
15
+ module Configs
16
+ class << self
17
+ attr_accessor :cfg # configuration file
18
+ attr_accessor :cfg_server # shortcut to the choosen server configs
19
+ attr_accessor :req # api request
20
+
21
+ def initialize_cfg
22
+ config_file = File.join(File.dirname(__FILE__), '..', '..', 'config.yml')
23
+ unless File.exist? config_file
24
+ throw Exception.new(config_file + " does not exists. Copy the example and configure your server.")
25
+ end
26
+ config = YAML.load_file(config_file)
27
+ config
28
+ end
29
+
30
+ def initialize_cfg_server
31
+ if ENV['SERVER']
32
+ unless self.cfg['servers'].has_key?(ENV['SERVER'])
33
+ throw Exception.new("Server #{ENV['SERVER']} does not exists in your configuration file.")
34
+ end
35
+ srv = self.cfg['servers'][ENV['SERVER']]
36
+ else
37
+ srv = self.cfg['servers'][self.cfg['servers'].keys.first]
38
+ end
39
+ srv['bbb_version'] = '0.7' unless srv.has_key?('bbb_version')
40
+ srv
41
+ end
42
+
43
+ def load
44
+ self.cfg = initialize_cfg
45
+ self.cfg_server = initialize_cfg_server
46
+ self.req = BigBlueButtonAPITests::APIRequest.new
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,7 @@
1
+ $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '..', '..', 'lib')
2
+
3
+ require 'bigbluebutton_api'
4
+ require 'forgery'
5
+ Dir["#{File.dirname(__FILE__)}/../../spec/support/forgery/**/*.rb"].each { |f| require f }
6
+
7
+ Dir["#{File.dirname(__FILE__)}/../../extras/**/*.rb"].each { |f| require f }
@@ -0,0 +1,11 @@
1
+ Before do
2
+ # stores the global configurations in variables that are easier to access
3
+ BigBlueButtonAPITests::Configs.load
4
+ @config = BigBlueButtonAPITests::Configs.cfg
5
+ @config_server = BigBlueButtonAPITests::Configs.cfg_server
6
+ @req = BigBlueButtonAPITests::Configs.req
7
+ end
8
+
9
+ After do |scenario|
10
+ BigBlueButtonBot.finalize
11
+ end
@@ -3,37 +3,27 @@ require 'cgi'
3
3
  require 'rexml/document'
4
4
  require 'digest/sha1'
5
5
  require 'rubygems'
6
- require 'nokogiri'
7
6
  require 'hash_to_xml'
8
7
  require 'bigbluebutton_exception'
9
8
  require 'bigbluebutton_formatter'
9
+ require 'bigbluebutton_modules'
10
10
 
11
11
  module BigBlueButton
12
12
 
13
- # This class provides access to the BigBlueButton API. BigBlueButton
14
- # is an open source project that provides web conferencing for distance
15
- # education (http://code.google.com/p/bigbluebutton/wiki/API). This API
16
- # was developed to support the following version of BBB: 0.7, 0.8 (soon)
13
+ # This class provides access to the BigBlueButton API. For more details see README.rdoc.
17
14
  #
18
15
  # Sample usage of the API is as follows:
19
- # 1) Create a meeting with the create_meeting call
20
- # 2) Direct a user to either join_meeting_url
21
- # 3) To force meeting to end, call end_meeting
16
+ # 1. Create a meeting with create_meeting;
17
+ # 2. Redirect a user to the URL returned by join_meeting_url;
18
+ # 3. Get information about the meetings with get_meetings and get_meeting_info;
19
+ # 4. To force meeting to end, call end_meeting .
22
20
  #
23
- # 0.0.4+:
24
- # Author:: Leonardo Crauss Daronco (mailto:leonardodaronco@gmail.com)
25
- # Copyright:: Copyright (c) 2011 Leonardo Crauss Daronco
26
- # Project:: GT-Mconf: Multiconference system for interoperable web and mobile @ PRAV Labs - UFRGS
27
- # License:: Distributes under same terms as Ruby
28
- #
29
- # 0.0.3 and below:
30
- # Author:: Joe Kinsella (mailto:joe.kinsella@gmail.com)
31
- # Copyright:: Copyright (c) 2010 Joe Kinsella
32
- # License:: Distributes under same terms as Ruby
33
- #
34
- # Considerations about the returning hash:
35
- # * The XML returned by BBB is converted to a Hash. See the desired method's documentation for examples.
36
- # * Three values will *always* exist in the hash: :returncode (boolean), :messageKey (string) and :message (string)
21
+ # Important info about the data returned by the methods:
22
+ # * The XML returned by BBB is converted to a Hash. See individual method's documentation for examples.
23
+ # * Three values will *always* exist in the hash:
24
+ # * :returncode (boolean)
25
+ # * :messageKey (string)
26
+ # * :message (string)
37
27
  # * Some of the values returned by BBB are converted to better represent the data. Some of these are listed
38
28
  # bellow. They will *always* have the type informed:
39
29
  # * :meetingID (string)
@@ -52,11 +42,11 @@ module BigBlueButton
52
42
  # salt:: Secret salt for this server
53
43
  # version:: API version: 0.7 (valid for 0.7, 0.71 and 0.71a)
54
44
  def initialize(url, salt, version='0.7', debug=false)
55
- @supported_versions = ['0.7']
45
+ @supported_versions = ['0.7', '0.8']
56
46
  @url = url
57
47
  @salt = salt
58
48
  @debug = debug
59
- @timeout = 2 # 2 seconds timeout for get requests
49
+ @timeout = 10 # default timeout for api requests
60
50
 
61
51
  @version = version || get_api_version
62
52
  unless @supported_versions.include?(@version)
@@ -66,74 +56,107 @@ module BigBlueButton
66
56
  puts "BigBlueButtonAPI: Using version #{@version}" if @debug
67
57
  end
68
58
 
69
- # Returns the url used to join the meeting
70
- # meeting_id:: Unique identifier for the meeting
71
- # user_name:: Name of the user
72
- # password:: Password for this meeting - used to set the user as moderator or attendee
73
- # user_id:: Unique identifier for this user
74
- # web_voice_conf:: Custom voice-extension for users using VoIP
75
- def join_meeting_url(meeting_id, user_name, password,
76
- user_id = nil, web_voice_conf = nil)
77
-
78
- params = { :meetingID => meeting_id, :password => password, :fullName => user_name,
79
- :userID => user_id, :webVoiceConf => web_voice_conf }
80
- get_url(:join, params)
81
- end
59
+
60
+ #
61
+ # API calls since 0.7
62
+ #
63
+
82
64
 
83
65
  # Creates a new meeting. Returns the hash with the response or
84
66
  # throws BigBlueButtonException on failure.
85
- # meeting_name:: Name for the meeting
86
- # meeting_id:: Unique identifier for the meeting
87
- # moderator_password:: Moderator password
88
- # attendee_password:: Attendee password
89
- # welcome_message:: Welcome message to display in chat window
90
- # dialin_number:: Dial in number for conference using a regular phone
91
- # logout_url:: URL to return user to after exiting meeting
92
- # voice_bridge:: Voice conference number
67
+ # meeting_name (string):: Name for the meeting
68
+ # meeting_id (string, integer):: Unique identifier for the meeting
69
+ # options (Hash):: Hash with optional parameters. The accepted parameters are:
70
+ # moderatorPW (string, int), attendeePW (string, int), welcome (string),
71
+ # dialNumber (int), logoutURL (string), maxParticipants (int),
72
+ # voiceBridge (int), record (boolean), duration (int) and "meta" parameters
73
+ # (usually strings). If a parameter passed in the hash is not supported it will
74
+ # simply be discarded. For details about each see BBB API docs.
75
+ # modules (BigBlueButtonModules):: Configuration for the modules. The modules are sent as an xml and the
76
+ # request will use an HTTP POST instead of GET. Currently only the
77
+ # "presentation" module is available. Only used for version > 0.8.
78
+ # See usage examples below.
93
79
  #
94
- # === Return examples (for 0.7)
80
+ # === Example
81
+ #
82
+ # options = { :moderatorPW => "123", :attendeePW => "321", :welcome => "Welcome here!",
83
+ # :dialNumber => 5190909090, :logoutURL => "http://mconf.org", :maxParticipants => 25,
84
+ # :voiceBridge => 76543, :record => "true", :duration => 0, :meta_category => "Remote Class" }
85
+ # create_meeting("My Meeting", "my-meeting", options)
86
+ #
87
+ # === Example with modules (see BigBlueButtonModules docs for more)
88
+ #
89
+ # modules = BigBlueButton::BigBlueButtonModules.new
90
+ # modules.add_presentation(:url, "http://www.samplepdf.com/sample.pdf")
91
+ # modules.add_presentation(:url, "http://www.samplepdf.com/sample2.pdf")
92
+ # modules.add_presentation(:file, "presentations/class01.ppt")
93
+ # modules.add_presentation(:base64, "JVBERi0xLjQKJ....[clipped here]....0CiUlRU9GCg==", "first-class.pdf")
94
+ # create_meeting("My Meeting", "my-meeting", nil, modules)
95
+ #
96
+ # === Example response for 0.7
95
97
  #
96
98
  # On successful creation:
97
99
  #
98
100
  # {
99
- # :returncode=>true, :meetingID=>"bigbluebutton-api-ruby-test",
100
- # :attendeePW=>"1234", :moderatorPW=>"4321", :hasBeenForciblyEnded=>false,
101
- # :messageKey=>"", :message=>""
101
+ # :returncode => true, :meetingID => "test",
102
+ # :attendeePW => "1234", :moderatorPW => "4321", :hasBeenForciblyEnded => false,
103
+ # :messageKey => "", :message => ""
102
104
  # }
103
105
  #
104
106
  # Meeting that was forcibly ended:
105
107
  #
106
108
  # {
107
- # :returncode=>true, :meetingID=>"bigbluebutton-api-ruby-test",
108
- # :attendeePW=>"1234", :moderatorPW=>"4321", :hasBeenForciblyEnded=>true,
109
- # :messageKey=>"duplicateWarning",
110
- # :message=>"This conference was already in existence and may currently be in progress."
109
+ # :returncode => true, :meetingID => "test",
110
+ # :attendeePW => "1234", :moderatorPW => "4321", :hasBeenForciblyEnded => true,
111
+ # :messageKey => "duplicateWarning",
112
+ # :message => "This conference was already in existence and may currently be in progress."
111
113
  # }
112
114
  #
113
- def create_meeting(meeting_name, meeting_id, moderator_password = nil, attendee_password = nil,
114
- welcome_message = nil, dial_number = nil, logout_url = nil,
115
- max_participants = nil, voice_bridge = nil)
116
-
117
- params = { :name => meeting_name, :meetingID => meeting_id,
118
- :moderatorPW => moderator_password, :attendeePW => attendee_password,
119
- :welcome => welcome_message, :dialNumber => dial_number,
120
- :logoutURL => logout_url, :maxParticpants => max_participants,
121
- :voiceBridge => voice_bridge }
115
+ # === Example response for 0.8
116
+ #
117
+ # {
118
+ # :returncode => true, :meetingID => "Test", :createTime => 1308591802,
119
+ # :attendeePW => "1234", :moderatorPW => "4321", :hasBeenForciblyEnded => false,
120
+ # :messageKey => "", :message => ""
121
+ # }
122
+ #
123
+ def create_meeting(meeting_name, meeting_id, options={}, modules=nil)
124
+ valid_options = [:moderatorPW, :attendeePW, :welcome, :maxParticipants,
125
+ :dialNumber, :voiceBridge, :webVoice, :logoutURL]
126
+
127
+ selected_opt = options.clone
128
+ if @version >= "0.8"
129
+ # v0.8 added "record", "duration" and "meta_" parameters
130
+ valid_options += [:record, :duration]
131
+ selected_opt.reject!{ |k,v| !valid_options.include?(k) and !(k.to_s =~ /^meta_.*$/) }
132
+ selected_opt[:record] = selected_opt[:record].to_s if selected_opt.has_key?(:record)
133
+ else
134
+ selected_opt.reject!{ |k,v| !valid_options.include?(k) }
135
+ end
136
+ params = { :name => meeting_name, :meetingID => meeting_id }.merge(selected_opt)
122
137
 
123
- response = send_api_request(:create, params)
138
+ # with modules we send a post request (only for >= 0.8)
139
+ if modules and @version >= "0.8"
140
+ response = send_api_request(:create, params, modules.to_xml)
141
+ else
142
+ response = send_api_request(:create, params)
143
+ end
124
144
 
125
145
  formatter = BigBlueButtonFormatter.new(response)
126
146
  formatter.to_string(:meetingID)
127
147
  formatter.to_string(:moderatorPW)
128
148
  formatter.to_string(:attendeePW)
129
149
  formatter.to_boolean(:hasBeenForciblyEnded)
150
+ if @version >= "0.8"
151
+ formatter.to_int(:createTime)
152
+ end
130
153
 
131
154
  response
132
155
  end
133
156
 
134
157
  # Ends an existing meeting. Throws BigBlueButtonException on failure.
135
- # meeting_id:: Unique identifier for the meeting
136
- # moderator_password:: Moderator password
158
+ # meeting_id (string, int):: Unique identifier for the meeting
159
+ # moderator_password (string, int):: Moderator password
137
160
  #
138
161
  # === Return examples (for 0.7)
139
162
  #
@@ -151,12 +174,29 @@ module BigBlueButton
151
174
 
152
175
  # Returns true or false as to whether meeting is open. A meeting is
153
176
  # only open after at least one participant has joined.
154
- # meeting_id:: Unique identifier for the meeting
177
+ # meeting_id (string, int):: Unique identifier for the meeting
155
178
  def is_meeting_running?(meeting_id)
156
179
  hash = send_api_request(:isMeetingRunning, { :meetingID => meeting_id } )
157
180
  BigBlueButtonFormatter.new(hash).to_boolean(:running)
158
181
  end
159
182
 
183
+ # Returns the url used to join the meeting
184
+ # meeting_id (string, int):: Unique identifier for the meeting
185
+ # user_name (string):: Name of the user
186
+ # password (string):: Password for this meeting - used to set the user as moderator or attendee
187
+ # options (Hash):: Hash with optional parameters. The accepted parameters are:
188
+ # userID (string, int), webVoiceConf (string, int) and createTime (int).
189
+ # For details about each see BBB API docs.
190
+ def join_meeting_url(meeting_id, user_name, password, options={})
191
+ valid_options = [:userID, :webVoiceConf]
192
+ valid_options += [:createTime] if @version >= "0.8"
193
+ options.reject!{ |k,v| !valid_options.include?(k) }
194
+
195
+ params = { :meetingID => meeting_id, :password => password, :fullName => user_name }.merge(options)
196
+
197
+ get_url(:join, params)
198
+ end
199
+
160
200
  # Warning: As of this version of the gem, this call does not work
161
201
  # (instead of returning XML response, it should join the meeting).
162
202
  #
@@ -164,14 +204,19 @@ module BigBlueButton
164
204
  # directing the user's browser to moderator_url or attendee_url
165
205
  # (note: this will still be required however to actually use bbb).
166
206
  # Returns the URL a user can use to enter this meeting.
167
- # meeting_id:: Unique identifier for the meeting
168
- # user_name:: Name of the user
169
- # password:: Moderator or attendee password for this meeting
170
- # user_id:: Unique identifier for this user
171
- # web_voice_conf:: Custom voice-extension for users using VoIP
172
- def join_meeting(meeting_id, user_name, password, user_id = nil, web_voice_conf = nil)
173
- params = { :meetingID => meeting_id, :password => password, :fullName => user_name,
174
- :userID => user_id, :webVoiceConf => web_voice_conf }
207
+ # meeting_id (string, int):: Unique identifier for the meeting
208
+ # user_name (string):: Name of the user
209
+ # password (string, int):: Moderator or attendee password for this meeting
210
+ # options (Hash):: Hash with optional parameters. The accepted parameters are:
211
+ # userID (string, int), webVoiceConf (string, int) and createTime (int).
212
+ # For details about each see BBB API docs.
213
+ def join_meeting(meeting_id, user_name, password, options={})
214
+ valid_options = [:userID, :webVoiceConf]
215
+ valid_options += [:createTime] if @version >= "0.8"
216
+ options.reject!{ |k,v| !valid_options.include?(k) }
217
+
218
+ params = { :meetingID => meeting_id, :password => password, :fullName => user_name }.merge(options)
219
+
175
220
  send_api_request(:join, params)
176
221
  end
177
222
 
@@ -179,10 +224,10 @@ module BigBlueButton
179
224
  # See the API documentation for details on the return XML
180
225
  # (http://code.google.com/p/bigbluebutton/wiki/API).
181
226
  #
182
- # meeting_id:: Unique identifier for the meeting
183
- # password:: Moderator password for this meeting
227
+ # meeting_id (string, int):: Unique identifier for the meeting
228
+ # password (string, int):: Moderator password for this meeting
184
229
  #
185
- # === Return examples (for 0.7)
230
+ # === Example responses for 0.7
186
231
  #
187
232
  # With attendees:
188
233
  #
@@ -205,12 +250,23 @@ module BigBlueButton
205
250
  # :attendees=>[], :messageKey=>"", :message=>""
206
251
  # }
207
252
  #
253
+ # === Example responses for 0.8
254
+ #
255
+ # {
256
+ # :returncode => true, :meetingName => "test", :meetingID => "test", :createTime => 1321906390524,
257
+ # :voiceBridge => 72194, :attendeePW => "1234", :moderatorPW => "4321", :running => false, :recording => false,
258
+ # :hasBeenForciblyEnded => false, :startTime => nil, :endTime => nil, :participantCount => 0, :maxUsers => 9,
259
+ # :moderatorCount => 0, :attendees => [],
260
+ # :metadata => { :two => "TWO", :one => "one" },
261
+ # :messageKey => "", :message => ""
262
+ # }
263
+ #
208
264
  def get_meeting_info(meeting_id, password)
209
265
  response = send_api_request(:getMeetingInfo, { :meetingID => meeting_id, :password => password } )
210
266
 
211
267
  formatter = BigBlueButtonFormatter.new(response)
212
268
  formatter.flatten_objects(:attendees, :attendee)
213
- response[:attendees].each { |a| formatter.format_attendee(a) }
269
+ response[:attendees].each { |a| BigBlueButtonFormatter.format_attendee(a) }
214
270
 
215
271
  formatter.to_string(:meetingID)
216
272
  formatter.to_string(:moderatorPW)
@@ -219,6 +275,15 @@ module BigBlueButton
219
275
  formatter.to_boolean(:running)
220
276
  formatter.to_datetime(:startTime)
221
277
  formatter.to_datetime(:endTime)
278
+ formatter.to_int(:participantCount)
279
+ formatter.to_int(:moderatorCount)
280
+ if @version >= "0.8"
281
+ formatter.to_string(:meetingName)
282
+ formatter.to_int(:maxUsers)
283
+ formatter.to_int(:voiceBridge)
284
+ formatter.to_int(:createTime)
285
+ formatter.to_boolean(:recording)
286
+ end
222
287
 
223
288
  response
224
289
  end
@@ -226,7 +291,7 @@ module BigBlueButton
226
291
  # Returns a hash object containing information about the meetings currently existent in the BBB
227
292
  # server, either they are running or not.
228
293
  #
229
- # === Return examples (for 0.7)
294
+ # === Example responses for 0.7
230
295
  #
231
296
  # Server with one or more meetings:
232
297
  #
@@ -247,7 +312,7 @@ module BigBlueButton
247
312
 
248
313
  formatter = BigBlueButtonFormatter.new(response)
249
314
  formatter.flatten_objects(:meetings, :meeting)
250
- response[:meetings].each { |m| formatter.format_meeting(m) }
315
+ response[:meetings].each { |m| BigBlueButtonFormatter.format_meeting(m) }
251
316
  response
252
317
  end
253
318
 
@@ -259,13 +324,119 @@ module BigBlueButton
259
324
  response[:returncode] ? response[:version].to_s : ""
260
325
  end
261
326
 
262
- # Make a simple request to the server to test the connection
327
+
328
+
329
+ #
330
+ # API calls since 0.8
331
+ #
332
+
333
+ # Retrieves the recordings that are available for playback for a given meetingID (or set of meeting IDs).
334
+ # options (Hash):: Hash with optional parameters. The accepted parameters are:
335
+ # :meetingID (string, Array). For details about each see BBB API docs.
336
+ # Any of the following values are accepted for :meetingID :
337
+ # :meetingID => "id1"
338
+ # :meetingID => "id1,id2,id3"
339
+ # :meetingID => ["id1"]
340
+ # :meetingID => ["id1", "id2", "id3"]
341
+ #
342
+ # === Example responses
343
+ # TODO: this example is not accurate yet
344
+ #
345
+ # { :returncode => true,
346
+ # :recordings => [
347
+ # {
348
+ # :recordID => "7f5745a08b24fa27551e7a065849dda3ce65dd32-1321618219268", :meetingID=>"bd1811beecd20f24314819a52ec202bf446ab94b",
349
+ # :name => "Evening Class1", :published => true,
350
+ # :startTime => #<DateTime: 2011-11-18T12:10:23+00:00 (212188378223/86400,0/1,2299161)>,
351
+ # :endTime => #<DateTime: 2011-11-18T12:12:25+00:00 (42437675669/17280,0/1,2299161)>,
352
+ # :metadata => { :course => "Fundamentals Of JAVA",
353
+ # :description => "List of recordings",
354
+ # :activity => "Evening Class1" },
355
+ # :playback => {
356
+ # :format => {
357
+ # :type => "slides",
358
+ # :url => "http://test-install.blindsidenetworks.com/playback/slides/playback.html?meetingId=7f5745a08b24fa27551e7a065849dda3ce65dd32-1321618219268",
359
+ # :length=>3
360
+ # }
361
+ # }
362
+ # },
363
+ # { :recordID => "183f0bf3a0982a127bdb8161-13085974450", :meetingID => "CS102",
364
+ # ...
365
+ # ...
366
+ # }
367
+ # ]
368
+ # }
369
+ #
370
+ def get_recordings(options={})
371
+ raise BigBlueButtonException.new("Method only supported for versions >= 0.8") if @version < "0.8"
372
+
373
+ valid_options = [:meetingID]
374
+ options.reject!{ |k,v| !valid_options.include?(k) }
375
+
376
+ # ["id1", "id2", "id3"] becomes "id1,id2,id3"
377
+ if options.has_key?(:meetingID)
378
+ options[:meetingID] = options[:meetingID].join(",") if options[:meetingID].instance_of?(Array)
379
+ end
380
+
381
+ response = send_api_request(:getRecordings, options)
382
+
383
+ formatter = BigBlueButtonFormatter.new(response)
384
+ formatter.flatten_objects(:recordings, :recording)
385
+ response[:recordings].each { |r| BigBlueButtonFormatter.format_recording(r) }
386
+ response
387
+ end
388
+
389
+ # Publish and unpublish recordings for a given recordID (or set of record IDs).
390
+ # recordIDs (string, Array):: ID or IDs of the target recordings.
391
+ # Any of the following values are accepted:
392
+ # "id1"
393
+ # "id1,id2,id3"
394
+ # ["id1"]
395
+ # ["id1", "id2", "id3"]
396
+ # publish (boolean):: Publish or unpublish the recordings?
397
+ #
398
+ # === Example responses
399
+ #
400
+ # { :returncode => true, :published => true }
401
+ #
402
+ def publish_recordings(recordIDs, publish)
403
+ raise BigBlueButtonException.new("Method only supported for versions >= 0.8") if @version < "0.8"
404
+
405
+ recordIDs = recordIDs.join(",") if recordIDs.instance_of?(Array) # ["id1", "id2"] becomes "id1,id2"
406
+ response = send_api_request(:publishRecordings, { :recordID => recordIDs, :publish => publish.to_s })
407
+ end
408
+
409
+ # Delete one or more recordings for a given recordID (or set of record IDs).
410
+ # recordIDs (string, Array):: ID or IDs of the target recordings.
411
+ # Any of the following values are accepted:
412
+ # "id1"
413
+ # "id1,id2,id3"
414
+ # ["id1"]
415
+ # ["id1", "id2", "id3"]
416
+ #
417
+ # === Example responses
418
+ #
419
+ # { :returncode => true, :deleted => true }
420
+ #
421
+ def delete_recordings(recordIDs)
422
+ raise BigBlueButtonException.new("Method only supported for versions >= 0.8") if @version < "0.8"
423
+
424
+ recordIDs = recordIDs.join(",") if recordIDs.instance_of?(Array) # ["id1", "id2"] becomes "id1,id2"
425
+ response = send_api_request(:deleteRecordings, { :recordID => recordIDs })
426
+ end
427
+
428
+
429
+ #
430
+ # Helper functions
431
+ #
432
+
433
+ # Make a simple request to the server to test the connection.
263
434
  def test_connection
264
435
  response = send_api_request(:index)
265
436
  response[:returncode]
266
437
  end
267
438
 
268
- # API's are equal if all the following attributes are equal
439
+ # API's are equal if all the following attributes are equal.
269
440
  def ==(other)
270
441
  r = true
271
442
  [:url, :supported_versions, :salt, :version, :debug].each do |param|
@@ -274,11 +445,20 @@ module BigBlueButton
274
445
  r
275
446
  end
276
447
 
448
+ # Returns the HTTP response object returned in the last API call.
277
449
  def last_http_response
278
450
  @http_response
279
451
  end
280
452
 
281
- def get_url(method, data={})
453
+ # Returns the XML returned in the last API call.
454
+ def last_xml_response
455
+ @xml_response
456
+ end
457
+
458
+ # Formats an API call URL for the method 'method' using the parameters in 'params'.
459
+ # method (symbol):: The API method to be called (:create, :index, :join, and others)
460
+ # params (Hash):: The parameters to be passed in the URL
461
+ def get_url(method, params={})
282
462
  if method == :index
283
463
  return @url
284
464
  end
@@ -286,38 +466,50 @@ module BigBlueButton
286
466
  url = "#{@url}/#{method}?"
287
467
 
288
468
  # stringify and escape all params
289
- data.delete_if { |k, v| v.nil? } unless data.nil?
290
- params = ""
291
- params = data.map{ |k,v| "#{k}=" + CGI::escape(v.to_s) unless k.nil? || v.nil? }.join("&")
469
+ params.delete_if { |k, v| v.nil? } unless params.nil?
470
+ params_string = ""
471
+ params_string = params.map{ |k,v| "#{k}=" + CGI::escape(v.to_s) unless k.nil? || v.nil? }.join("&")
292
472
 
293
473
  # checksum calc
294
- checksum_param = params + @salt
474
+ checksum_param = params_string + @salt
295
475
  checksum_param = method.to_s + checksum_param
296
476
  checksum = Digest::SHA1.hexdigest(checksum_param)
297
477
 
298
478
  # final url
299
- url += "#{params}&" unless params.empty?
479
+ url += "#{params_string}&" unless params_string.empty?
300
480
  url += "checksum=#{checksum}"
301
481
  end
302
482
 
303
- def send_api_request(method, data = {})
304
- url = get_url(method, data)
305
-
306
- @http_response = send_request(url)
483
+ # Performs an API call.
484
+ #
485
+ # Throws a BigBlueButtonException if something goes wrong (e.g. server offline).
486
+ # Also throws an exception of the request was not successful (i.e. returncode == FAILED).
487
+ #
488
+ # Only formats the standard values in the response (the ones that exist in all responses).
489
+ #
490
+ # method (symbol):: The API method to be called (:create, :index, :join, and others)
491
+ # params (Hash):: The parameters to be passed in the URL
492
+ # data (string):: Data to be sent with the request. If set, the request will use an HTTP
493
+ # POST instead of a GET and the data will be sent in the request body.
494
+ def send_api_request(method, params={}, data=nil)
495
+ url = get_url(method, params)
496
+
497
+ @http_response = send_request(url, data)
307
498
  return { } if @http_response.body.empty?
308
499
 
309
500
  # 'Hashify' the XML
310
- hash = Hash.from_xml(@http_response.body)
501
+ @xml_response = @http_response.body
502
+ hash = Hash.from_xml(@xml_response)
311
503
 
312
504
  # simple validation of the xml body
313
- unless hash.has_key?(:response) and hash[:response].has_key?(:returncode)
505
+ unless hash.has_key?(:returncode)
314
506
  raise BigBlueButtonException.new("Invalid response body. Is the API URL correct? \"#{@url}\", version #{@version}")
315
507
  end
316
508
 
317
509
  # default cleanup in the response
318
510
  hash = BigBlueButtonFormatter.new(hash).default_formatting
319
511
 
320
- # all responses should have a returncode
512
+ # if the return code is an error generates an exception
321
513
  unless hash[:returncode]
322
514
  exception = BigBlueButtonException.new(hash[:message])
323
515
  exception.key = hash.has_key?(:messageKey) ? hash[:messageKey] : ""
@@ -329,20 +521,32 @@ module BigBlueButton
329
521
 
330
522
  protected
331
523
 
332
- def send_request(url)
524
+ # :nodoc:
525
+ # If data is set, uses a POST with data in the request body
526
+ # Otherwise uses GET
527
+ def send_request(url, data=nil)
333
528
  begin
334
529
  puts "BigBlueButtonAPI: URL request = #{url}" if @debug
335
530
  url_parsed = URI.parse(url)
336
531
  http = Net::HTTP.new(url_parsed.host, url_parsed.port)
337
532
  http.open_timeout = @timeout
338
533
  http.read_timeout = @timeout
339
- response = http.get(url_parsed.request_uri)
534
+ if data.nil?
535
+ response = http.get(url_parsed.request_uri)
536
+ else
537
+ puts "BigBlueButtonAPI: Sending as a POST request with data.size = #{data.size}" if @debug
538
+ opts = { 'Content-Type' => 'text/xml' }
539
+ response = http.post(url_parsed.request_uri, data, opts)
540
+ end
340
541
  puts "BigBlueButtonAPI: URL response = #{response.body}" if @debug
542
+
341
543
  rescue TimeoutError => error
342
544
  raise BigBlueButtonException.new("Timeout error. Your server is probably down: \"#{@url}\"")
545
+
343
546
  rescue Exception => error
344
547
  raise BigBlueButtonException.new("Connection error. Your URL is probably incorrect: \"#{@url}\"")
345
548
  end
549
+
346
550
  response
347
551
  end
348
552