forked from External/greenlight
Merge pull request #74 from daronco/summit-9
Several improvements made during the Summit IX
This commit is contained in:
commit
520aacf302
1
Gemfile
1
Gemfile
|
@ -61,5 +61,6 @@ gem 'bootstrap-social-rails', '~> 4.12'
|
|||
gem 'font-awesome-rails'
|
||||
gem 'jquery-ui-rails'
|
||||
gem 'jquery-datatables-rails', '~> 3.4.0'
|
||||
gem 'rails-timeago', '~> 2.0'
|
||||
|
||||
gem 'http_accept_language'
|
||||
|
|
|
@ -152,6 +152,9 @@ GEM
|
|||
nokogiri (~> 1.6.0)
|
||||
rails-html-sanitizer (1.0.3)
|
||||
loofah (~> 2.0)
|
||||
rails-timeago (2.15.0)
|
||||
actionpack (>= 3.1)
|
||||
activesupport (>= 3.1)
|
||||
railties (5.0.0.1)
|
||||
actionpack (= 5.0.0.1)
|
||||
activesupport (= 5.0.0.1)
|
||||
|
@ -224,6 +227,7 @@ DEPENDENCIES
|
|||
omniauth-twitter (= 1.2.1)
|
||||
puma (~> 3.0)
|
||||
rails (~> 5.0.0, >= 5.0.0.1)
|
||||
rails-timeago (~> 2.0)
|
||||
sass-rails (~> 5.0)
|
||||
spring
|
||||
spring-watcher-listen (~> 2.0.0)
|
||||
|
@ -237,4 +241,4 @@ RUBY VERSION
|
|||
ruby 2.3.1p112
|
||||
|
||||
BUNDLED WITH
|
||||
1.13.5
|
||||
1.13.6
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 853 KiB |
|
@ -18,6 +18,7 @@
|
|||
//= require jquery-ui
|
||||
//= require dataTables/jquery.dataTables
|
||||
//= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
|
||||
//= require rails-timeago-all
|
||||
//= require bootstrap-sprockets
|
||||
//= require turbolinks
|
||||
//= require_self
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
$(".center-panel-wrapper").html(html);
|
||||
displayRoomURL();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var initRooms = function() {
|
||||
App.messages = App.cable.subscriptions.create({
|
||||
|
@ -43,6 +43,13 @@
|
|||
} else if (data.action === 'meeting_ended') {
|
||||
sessionStatusRefresh($('.meeting-url').val());
|
||||
showAlert(I18n.meeting_ended, 4000);
|
||||
} else if (data.action === 'user_waiting') {
|
||||
// show a browser notification only to the owner
|
||||
if (GreenLight.user.roomOwner) {
|
||||
showNotification(I18n.user_waiting_title, {
|
||||
body: I18n.user_waiting_body.replace(/%{user}/, data.user)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -23,22 +23,35 @@
|
|||
},
|
||||
{
|
||||
received: function(data) {
|
||||
|
||||
var recordings = Recordings.getInstance();
|
||||
var table = recordings.table.api()
|
||||
var row = table.row("#"+data.record_id);
|
||||
var table = recordings.table.api();
|
||||
var row = table.row("#"+data.id);
|
||||
|
||||
if (data.action === 'update') {
|
||||
var rowData = row.data();
|
||||
rowData.published = data.published
|
||||
table.row("#"+data.record_id).data(rowData);
|
||||
|
||||
rowData.published = data.published;
|
||||
rowData.listed = data.listed;
|
||||
table.row("#"+data.id).data(rowData);
|
||||
recordings.draw();
|
||||
|
||||
var published = (data.published) ? 'published' : 'unpublished';
|
||||
showAlert(I18n['recording_'+published], 4000);
|
||||
var status = data.published ? (data.listed ? 'published' : 'unlisted') : 'unpublished';
|
||||
showAlert(I18n['recording_'+status], 4000);
|
||||
|
||||
} else if (data.action === 'delete') {
|
||||
row.remove();
|
||||
recordings.draw();
|
||||
|
||||
showAlert(I18n.recording_deleted, 4000);
|
||||
|
||||
} else if (data.action === 'create') {
|
||||
if (row.length == 0) {
|
||||
data.duration = data.length;
|
||||
table.rows.add([data]);
|
||||
recordings.draw();
|
||||
showAlert(I18n.recording_created, 4000);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -100,6 +100,15 @@
|
|||
.tooltip('fixTitle');
|
||||
});
|
||||
|
||||
// button used to send invitations to the meeting (i.e. "mailto:" link)
|
||||
$('.center-panel-wrapper').on('click', '.meeting-invite', function (event) {
|
||||
var meetingURL = Meeting.getInstance().getURL();
|
||||
var subject = $(this).data("invite-subject");
|
||||
var body = $(this).data("invite-body").replace("&&URL&&", meetingURL);
|
||||
var mailto = "mailto:?subject=" + encodeURIComponent(subject) + "&body=" + encodeURIComponent(body);
|
||||
window.open(mailto);
|
||||
});
|
||||
|
||||
$('.center-panel-wrapper').on('mouseleave', '.meeting-url-copy', function (event, msg) {
|
||||
$(this).blur();
|
||||
});
|
||||
|
@ -120,7 +129,7 @@
|
|||
selector: '.has-tooltip',
|
||||
container: 'body'
|
||||
};
|
||||
$(document).tooltip(options)
|
||||
$(document).tooltip(options);
|
||||
var options = {
|
||||
selector: '.bottom-tooltip',
|
||||
container: 'body',
|
||||
|
@ -151,7 +160,7 @@
|
|||
var joinedMeetings = localStorage.getItem('joinedMeetings');
|
||||
if (joinedMeetings && joinedMeetings.length > 0) {
|
||||
joinedMeetings = joinedMeetings.split(',');
|
||||
$('.center-panel-wrapper .panel-footer').removeClass('hidden')
|
||||
$('.center-panel-wrapper .panel-footer').removeClass('hidden');
|
||||
|
||||
for (var i = joinedMeetings.length - 1; i >= 0; i--) {
|
||||
$('ul.previously-joined').append('<li><a href="/meetings/'+joinedMeetings[i]+'">'+joinedMeetings[i]+'</a></li>');
|
||||
|
|
|
@ -37,7 +37,7 @@ class @Recordings
|
|||
{ data: "previews", orderable: false },
|
||||
{ data: "duration", orderable: false },
|
||||
{ data: "playbacks", orderable: false },
|
||||
{ data: "published", visible: false },
|
||||
{ data: "listed", visible: false },
|
||||
{ data: "id", orderable: false }
|
||||
],
|
||||
columnDefs: [
|
||||
|
@ -45,10 +45,13 @@ class @Recordings
|
|||
targets: 0,
|
||||
render: (data, type, row) ->
|
||||
if type == 'display'
|
||||
return new Date(data)
|
||||
date = new Date(data)
|
||||
title = date
|
||||
.toLocaleString($('html').attr('lang'),
|
||||
{month: 'long', day: 'numeric', year: 'numeric',
|
||||
hour12: 'true', hour: '2-digit', minute: '2-digit'})
|
||||
timeago = '<time datetime="'+date.toISOString()+'" data-time-ago="'+date.toISOString()+'">'+date.toISOString()+'</time>'
|
||||
return title+'<span class="timeago">('+timeago+')</span>'
|
||||
return data
|
||||
},
|
||||
{
|
||||
|
@ -78,18 +81,18 @@ class @Recordings
|
|||
render: (data, type, row) ->
|
||||
if type == 'display'
|
||||
roomName = Meeting.getInstance().getId()
|
||||
published = row.published
|
||||
publishText = if published then 'unpublish' else 'publish'
|
||||
recordingActions = $('.hidden-elements').find('.recording-actions')
|
||||
recordingActions.find('.recording-update > i.default')
|
||||
.removeClass(PUBLISHED_CLASSES.join(' '))
|
||||
.addClass(getPublishClass(published))
|
||||
recordingActions.find('.recording-update > i.hover')
|
||||
.removeClass(PUBLISHED_CLASSES.join(' '))
|
||||
.addClass(getPublishClass(!published))
|
||||
recordingActions.find('.recording-update')
|
||||
.attr('data-published', published)
|
||||
.attr('title', I18n[publishText+'_recording'])
|
||||
classes = ['recording-unpublished', 'recording-unlisted', 'recording-published']
|
||||
if row.published
|
||||
if row.listed
|
||||
cls = classes[2]
|
||||
else
|
||||
cls = classes[1]
|
||||
else
|
||||
cls = classes[0]
|
||||
trigger = recordingActions.find('.recording-update-trigger')
|
||||
trigger.removeClass(classes.join(' '))
|
||||
trigger.addClass(cls)
|
||||
return recordingActions.html()
|
||||
return data
|
||||
}
|
||||
|
@ -106,17 +109,33 @@ class @Recordings
|
|||
@getTable().api().clear().draw().destroy()
|
||||
|
||||
# enable popovers
|
||||
# can't use trigger:'focus' because it doesn't work will with buttons inside
|
||||
# the popover
|
||||
options = {
|
||||
selector: '.has-popover',
|
||||
html: true,
|
||||
trigger: 'focus',
|
||||
trigger: 'click',
|
||||
title: ->
|
||||
return I18n.are_you_sure;
|
||||
return $(this).data("popover-title");
|
||||
content: ->
|
||||
return $(".delete-popover-body").html()
|
||||
bodySelector = $(this).data("popover-body")
|
||||
return $(bodySelector).html()
|
||||
}
|
||||
$('#recordings').popover(options)
|
||||
|
||||
# close popovers manually when clicking outside of them or in buttons
|
||||
# with [data-dismiss="popover"]
|
||||
# careful to hide only the open popover and not all of them, otherwise they won't reopen
|
||||
$('body').on 'click', (e) ->
|
||||
$('.has-popover').each ->
|
||||
if !$(this).is(e.target) and $(this).has(e.target).length == 0 and $('.popover.in').has(e.target).length == 0
|
||||
if $(this).next(".popover.in").length > 0
|
||||
$(this).popover('hide')
|
||||
$(document).on 'click', '[data-dismiss="popover"]', (e) ->
|
||||
$('.has-popover').each ->
|
||||
if $(this).next(".popover.in").length > 0
|
||||
$(this).popover('hide')
|
||||
|
||||
# Gets the current instance or creates a new one
|
||||
@getInstance: ->
|
||||
if _recordingsInstance && Recordings.initialized()
|
||||
|
@ -153,24 +172,32 @@ class @Recordings
|
|||
# setup click handlers for the action buttons
|
||||
setupActionHandlers: ->
|
||||
table_api = this.table.api()
|
||||
|
||||
@getTable().on 'click', '.recording-update', (event) ->
|
||||
btn = $(this)
|
||||
row = table_api.row($(this).closest('tr')).data()
|
||||
url = $('.meeting-url').val()
|
||||
id = row.id
|
||||
published = btn.data('published')
|
||||
|
||||
published = btn.data('visibility') == "unlisted" ||
|
||||
btn.data('visibility') == "published"
|
||||
listed = btn.data('visibility') == "published"
|
||||
|
||||
btn.prop('disabled', true)
|
||||
|
||||
data = { published: published.toString() }
|
||||
data["meta_" + GreenLight.META_LISTED] = listed.toString();
|
||||
$.ajax({
|
||||
method: 'PATCH',
|
||||
url: url+'/recordings/'+id,
|
||||
data: {published: (!published).toString()}
|
||||
data: data
|
||||
}).done((data) ->
|
||||
|
||||
).fail((data) ->
|
||||
btn.prop('disabled', false)
|
||||
)
|
||||
|
||||
this.table.on 'click', '.recording-delete', (event) ->
|
||||
@getTable().on 'click', '.recording-delete', (event) ->
|
||||
btn = $(this)
|
||||
row = table_api.row($(this).closest('tr')).data()
|
||||
url = $('.meeting-url').val()
|
||||
|
@ -185,6 +212,9 @@ class @Recordings
|
|||
btn.prop('disabled', false)
|
||||
)
|
||||
|
||||
@getTable().on 'draw.dt', (event) ->
|
||||
$('time[data-time-ago]').timeago();
|
||||
|
||||
getTable: ->
|
||||
@table
|
||||
|
||||
|
|
|
@ -20,12 +20,6 @@ $.ajaxSetup({
|
|||
}
|
||||
});
|
||||
|
||||
var PUBLISHED_CLASSES = ['fa-eye-slash', 'fa-eye']
|
||||
|
||||
var getPublishClass = function(published) {
|
||||
return PUBLISHED_CLASSES[+published];
|
||||
}
|
||||
|
||||
var loopJoin = function() {
|
||||
var jqxhr = Meeting.getInstance().getJoinMeetingResponse();
|
||||
jqxhr.done(function(data) {
|
||||
|
@ -38,8 +32,9 @@ var loopJoin = function() {
|
|||
jqxhr.fail(function(xhr, status, error) {
|
||||
console.info("meeting join failed");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var alertTimeout = null;
|
||||
var showAlert = function(html, timeout_delay) {
|
||||
if (!html) {
|
||||
return;
|
||||
|
@ -49,12 +44,34 @@ var showAlert = function(html, timeout_delay) {
|
|||
$('#alerts').html($('.alert-template').html());
|
||||
|
||||
if (timeout_delay) {
|
||||
setTimeout(function() {
|
||||
clearTimeout(alertTimeout);
|
||||
alertTimeout = setTimeout(function() {
|
||||
$('#alerts > .alert').alert('close');
|
||||
}, timeout_delay);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var displayRoomURL = function() {
|
||||
$('.meeting-url').val(Meeting.getInstance().getURL());
|
||||
}
|
||||
};
|
||||
|
||||
var showNotification = function(title, options) {
|
||||
if (Notification.permission === "granted") {
|
||||
var icon = '<%= asset_path("bbb-logo.png") %>';
|
||||
options = $.extend(options, {
|
||||
icon: icon
|
||||
});
|
||||
var notification = new Notification(title, options);
|
||||
notification.onclick = function() {
|
||||
window.focus();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// For now there are notifications only for users signed in and when they
|
||||
// are in their room's page
|
||||
$(document).on("turbolinks:load", function() {
|
||||
if (GreenLight.user.roomOwner) {
|
||||
Notification.requestPermission();
|
||||
}
|
||||
});
|
|
@ -37,16 +37,11 @@
|
|||
.dataTables_empty {
|
||||
text-align: center;
|
||||
}
|
||||
.recording-update:hover > {
|
||||
.default {
|
||||
display: none;
|
||||
}
|
||||
.hover {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.fa.hover {
|
||||
display: none;
|
||||
.timeago {
|
||||
margin-left: 5px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -69,3 +64,34 @@
|
|||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.meeting-url-group {
|
||||
position: relative;
|
||||
|
||||
.generate-link {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 10px;
|
||||
z-index: 9;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.recording-update-trigger {
|
||||
&.recording-unpublished {
|
||||
color: red;
|
||||
}
|
||||
|
||||
&.recording-unlisted {
|
||||
color: #e3a91e;
|
||||
}
|
||||
|
||||
&.recording-published {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,10 @@ html, body {
|
|||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
body[data-controller=landing][data-action=rooms].app-background {
|
||||
background-image: asset-url('bg_personal_room');
|
||||
}
|
||||
|
||||
#alerts {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
|
@ -106,3 +110,7 @@ html, body {
|
|||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.popover {
|
||||
max-width: none;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,9 @@ class BbbController < ApplicationController
|
|||
before_action :authorize_recording_owner!, only: [:update_recordings, :delete_recordings]
|
||||
before_action :load_and_authorize_room_owner!, only: [:end]
|
||||
|
||||
skip_before_action :verify_authenticity_token, only: :callback
|
||||
before_action :validate_checksum, only: :callback
|
||||
|
||||
# GET /:resource/:id/join
|
||||
def join
|
||||
if params[:name].blank?
|
||||
|
@ -39,7 +42,9 @@ class BbbController < ApplicationController
|
|||
user_is_moderator: true
|
||||
}
|
||||
end
|
||||
options[:meeting_logout_url] = "#{request.base_url}/#{params[:resource]}/#{params[:id]}"
|
||||
base_url = "#{request.base_url}/#{params[:resource]}/#{params[:id]}"
|
||||
options[:meeting_logout_url] = base_url
|
||||
options[:hook_url] = "#{base_url}/callback"
|
||||
|
||||
bbb_res = bbb_join_url(
|
||||
params[:id],
|
||||
|
@ -47,14 +52,33 @@ class BbbController < ApplicationController
|
|||
options
|
||||
)
|
||||
|
||||
# the user can join the meeting
|
||||
if bbb_res[:returncode] && current_user && current_user == user
|
||||
JoinMeetingJob.perform_later(user.encrypted_id)
|
||||
|
||||
# user will be waiting for a moderator
|
||||
else
|
||||
NotifyUserWaitingJob.perform_later(user.encrypted_id, params[:name])
|
||||
end
|
||||
|
||||
render_bbb_response bbb_res, bbb_res[:response]
|
||||
end
|
||||
end
|
||||
|
||||
# POST /:resource/:id/callback
|
||||
# Endpoint for webhook calls from BigBlueButton
|
||||
def callback
|
||||
begin
|
||||
data = JSON.parse(read_body(request))
|
||||
treat_callback_event(data["event"])
|
||||
rescue Exception => e
|
||||
logger.error "Error parsing webhook data. Data: #{data}, exception: #{e.inspect}"
|
||||
|
||||
# respond with 200 anyway so BigBlueButton knows the hook call was ok
|
||||
render head(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /rooms/:id/end
|
||||
def end
|
||||
load_and_authorize_room_owner!
|
||||
|
@ -76,9 +100,11 @@ class BbbController < ApplicationController
|
|||
|
||||
# PATCH /rooms/:id/recordings/:record_id
|
||||
def update_recordings
|
||||
bbb_res = bbb_update_recordings(params[:record_id], params[:published] == 'true')
|
||||
published = params[:published] == 'true'
|
||||
metadata = params.select{ |k, v| k.match(/^meta_/) }
|
||||
bbb_res = bbb_update_recordings(params[:record_id], published, metadata)
|
||||
if bbb_res[:returncode]
|
||||
RecordingUpdatesJob.perform_later(@user.encrypted_id, params[:record_id], bbb_res[:published])
|
||||
RecordingUpdatesJob.perform_later(@user.encrypted_id, params[:record_id])
|
||||
end
|
||||
render_bbb_response bbb_res
|
||||
end
|
||||
|
@ -128,4 +154,80 @@ class BbbController < ApplicationController
|
|||
@response = response
|
||||
render status: @status
|
||||
end
|
||||
|
||||
def read_body(request)
|
||||
request.body.read.force_encoding("UTF-8")
|
||||
end
|
||||
|
||||
def treat_callback_event(event)
|
||||
eventName = (event.present? && event['header'].present?) ? event['header']['name'] : nil
|
||||
|
||||
# a recording is ready
|
||||
if eventName == "publish_ended"
|
||||
if event['payload'] && event['payload']['metadata'] && event['payload']['meeting_id']
|
||||
token = event['payload']['metadata'][META_TOKEN]
|
||||
record_id = event['payload']['meeting_id']
|
||||
|
||||
# the webhook event doesn't have all the data we need, so we need
|
||||
# to send a getRecordings anyway
|
||||
# TODO: if the webhooks included all data in the event we wouldn't need this
|
||||
rec_info = bbb_get_recordings(token, record_id)
|
||||
rec_info = rec_info[:recordings].first
|
||||
RecordingCreatedJob.perform_later(token, parse_recording_for_view(rec_info))
|
||||
|
||||
# send an email to the owner of this recording, if defined
|
||||
if Rails.configuration.mail_notifications
|
||||
owner = User.find_by(encrypted_id: token)
|
||||
RecordingReadyEmailJob.perform_later(owner) if owner.present?
|
||||
end
|
||||
|
||||
# TODO: remove the webhook now that the meeting and recording are done
|
||||
# remove only if the meeting is not running, otherwise the hook is needed
|
||||
# if Rails.configuration.use_webhooks
|
||||
# webhook_remove("#{base_url}/callback")
|
||||
# end
|
||||
else
|
||||
logger.error "Bad format for event #{event}, won't process"
|
||||
end
|
||||
else
|
||||
logger.info "Callback event will not be treated. Event name: #{eventName}"
|
||||
end
|
||||
|
||||
render head(:ok) && return
|
||||
end
|
||||
|
||||
# Validates the checksum received in a callback call.
|
||||
# If the checksum doesn't match, renders an ok and aborts execution.
|
||||
def validate_checksum
|
||||
secret = ENV['BIGBLUEBUTTON_SECRET']
|
||||
checksum = params["checksum"]
|
||||
data = read_body(request)
|
||||
callback_url = uri_remove_param(request.url, "checksum")
|
||||
|
||||
checksum_str = "#{callback_url}#{data}#{secret}"
|
||||
calculated_checksum = Digest::SHA1.hexdigest(checksum_str)
|
||||
|
||||
if calculated_checksum != checksum
|
||||
logger.error "Checksum did not match. Calculated: #{calculated_checksum}, received: #{checksum}"
|
||||
|
||||
# respond with 200 anyway so BigBlueButton knows the hook call was ok
|
||||
# but abort execution
|
||||
render head(:ok) && return
|
||||
end
|
||||
end
|
||||
|
||||
# Removes parameters from an URI
|
||||
def uri_remove_param(uri, params = nil)
|
||||
return uri unless params
|
||||
params = Array(params)
|
||||
uri_parsed = URI.parse(uri)
|
||||
return uri unless uri_parsed.query
|
||||
new_params = uri_parsed.query.gsub(/&/, '&').split('&').reject { |q| params.include?(q.split('=').first) }
|
||||
uri = uri.split('?').first
|
||||
if new_params.count > 0
|
||||
"#{uri}?#{new_params.join('&')}"
|
||||
else
|
||||
uri
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,4 +15,8 @@
|
|||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
module UsersHelper
|
||||
def is_room_owner
|
||||
token = current_user ? current_user.encrypted_id : nil
|
||||
token.present? && params[:id].present? && token == params[:id]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
#
|
||||
# Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free Software
|
||||
# Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License along
|
||||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
class NotifyUserWaitingJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(room, user)
|
||||
ActionCable.server.broadcast "#{room}_meeting_updates_channel",
|
||||
{ action: 'user_waiting', user: user }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
#
|
||||
# Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free Software
|
||||
# Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License along
|
||||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
class RecordingCreatedJob < ApplicationJob
|
||||
include BbbApi
|
||||
|
||||
queue_as :default
|
||||
|
||||
def perform(room, recording)
|
||||
ActionCable.server.broadcast "#{room}_recording_updates_channel",
|
||||
{ action: 'create' }.merge(recording)
|
||||
end
|
||||
end
|
|
@ -28,7 +28,7 @@ class RecordingDeletesJob < ApplicationJob
|
|||
if !bbb_res[:recordings] || bbb_res[:messageKey] == 'noRecordings'
|
||||
ActionCable.server.broadcast "#{room}_recording_updates_channel",
|
||||
action: 'delete',
|
||||
record_id: record_id
|
||||
id: record_id
|
||||
break
|
||||
end
|
||||
sleep sleep_time
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
#
|
||||
# Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free Software
|
||||
# Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License along
|
||||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
class RecordingReadyEmailJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(user)
|
||||
if user.email.present?
|
||||
NotificationMailer.recording_ready_email(user).deliver
|
||||
end
|
||||
end
|
||||
end
|
|
@ -19,22 +19,13 @@ class RecordingUpdatesJob < ApplicationJob
|
|||
|
||||
queue_as :default
|
||||
|
||||
def perform(room, record_id, published)
|
||||
tries = 0
|
||||
sleep_time = 2
|
||||
|
||||
while tries < 4
|
||||
bbb_res = bbb_get_recordings(nil, record_id)
|
||||
if bbb_res[:recordings].first[:published].to_s == published
|
||||
ActionCable.server.broadcast "#{room}_recording_updates_channel",
|
||||
action: 'update',
|
||||
record_id: record_id,
|
||||
published: bbb_res[:recordings].first[:published]
|
||||
break
|
||||
end
|
||||
sleep sleep_time
|
||||
sleep_time = sleep_time * 2
|
||||
tries += 1
|
||||
end
|
||||
def perform(room, record_id)
|
||||
bbb_res = bbb_get_recordings(nil, record_id)
|
||||
recording = bbb_res[:recordings].first
|
||||
ActionCable.server.broadcast "#{room}_recording_updates_channel",
|
||||
action: 'update',
|
||||
id: record_id,
|
||||
published: recording[:published],
|
||||
listed: bbb_is_recording_listed(recording)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
module BbbApi
|
||||
META_LISTED = "gl-listed"
|
||||
META_TOKEN = "gl-token"
|
||||
META_HOOK_URL = "gl-webhooks-callback-url"
|
||||
|
||||
def bbb_endpoint
|
||||
Rails.configuration.bigbluebutton_endpoint || ''
|
||||
end
|
||||
|
@ -51,7 +55,7 @@ module BbbApi
|
|||
|
||||
# See if the meeting is running
|
||||
begin
|
||||
bbb_meeting_info = bbb.get_meeting_info( meeting_id, nil )
|
||||
bbb_meeting_info = bbb.get_meeting_info(meeting_id, nil)
|
||||
rescue BigBlueButton::BigBlueButtonException => exc
|
||||
# This means that is not created
|
||||
|
||||
|
@ -65,7 +69,22 @@ module BbbApi
|
|||
logout_url = options[:meeting_logout_url] || "#{request.base_url}"
|
||||
moderator_password = random_password(12)
|
||||
viewer_password = random_password(12)
|
||||
meeting_options = {record: options[:meeting_recorded].to_s, logoutURL: logout_url, moderatorPW: moderator_password, attendeePW: viewer_password}
|
||||
meeting_options = {
|
||||
record: options[:meeting_recorded].to_s,
|
||||
logoutURL: logout_url,
|
||||
moderatorPW: moderator_password,
|
||||
attendeePW: viewer_password,
|
||||
"meta_#{BbbApi::META_LISTED}": false,
|
||||
"meta_#{BbbApi::META_TOKEN}": meeting_token
|
||||
}
|
||||
meeting_options.merge!(
|
||||
{ "meta_#{BbbApi::META_HOOK_URL}": options[:hook_url] }
|
||||
) if options[:hook_url]
|
||||
|
||||
if Rails.configuration.use_webhooks
|
||||
webhook_register(options[:hook_url], meeting_id)
|
||||
end
|
||||
|
||||
# Create the meeting
|
||||
bbb.create_meeting(options[:meeting_name], meeting_id, meeting_options)
|
||||
|
||||
|
@ -101,7 +120,7 @@ module BbbApi
|
|||
options[:recordID] = record_id
|
||||
end
|
||||
if meeting_id
|
||||
options[:meetingID] = (Digest::SHA1.hexdigest(Rails.application.secrets[:secret_key_base]+meeting_id)).to_s
|
||||
options[:meetingID] = bbb_meeting_id(meeting_id)
|
||||
end
|
||||
res = bbb_safe_execute :get_recordings, options
|
||||
|
||||
|
@ -148,6 +167,8 @@ module BbbApi
|
|||
else
|
||||
[]
|
||||
end
|
||||
|
||||
recording[:listed] = bbb_is_recording_listed(recording)
|
||||
end
|
||||
res
|
||||
end
|
||||
|
@ -168,8 +189,11 @@ module BbbApi
|
|||
response_data = bbb_exception_res exc
|
||||
end
|
||||
|
||||
def bbb_update_recordings(id, published)
|
||||
def bbb_update_recordings(id, published, metadata)
|
||||
bbb_safe_execute :publish_recordings, id, published
|
||||
|
||||
params = { recordID: id }.merge(metadata)
|
||||
bbb_safe_execute :send_api_request, "updateRecordings", params
|
||||
end
|
||||
|
||||
def bbb_delete_recordings(id)
|
||||
|
@ -191,6 +215,67 @@ module BbbApi
|
|||
response_data
|
||||
end
|
||||
|
||||
def bbb_is_recording_listed(recording)
|
||||
!recording[:metadata].blank? &&
|
||||
recording[:metadata][BbbApi::META_LISTED.to_sym] == "true"
|
||||
end
|
||||
|
||||
# Parses a recording as returned by getRecordings and returns it
|
||||
# as an object as expected by the views.
|
||||
# TODO: this is almost the same done by jbuilder templates (bbb/recordings),
|
||||
# how to reuse them?
|
||||
def parse_recording_for_view(recording)
|
||||
recording[:previews] ||= []
|
||||
previews = recording[:previews].map do |preview|
|
||||
{
|
||||
url: preview[:content],
|
||||
width: preview[:width],
|
||||
height: preview[:height],
|
||||
alt: preview[:alt]
|
||||
}
|
||||
end
|
||||
recording[:playbacks] ||= []
|
||||
playbacks = recording[:playbacks].map do |playback|
|
||||
{
|
||||
type: playback[:type],
|
||||
type_i18n: t(playback[:type]),
|
||||
url: playback[:url],
|
||||
previews: previews
|
||||
}
|
||||
end
|
||||
{
|
||||
id: recording[:recordID],
|
||||
name: recording[:name],
|
||||
published: recording[:published],
|
||||
end_time: recording[:endTime].to_s,
|
||||
start_time: recording[:startTime].to_s,
|
||||
length: recording[:length],
|
||||
listed: recording[:listed],
|
||||
playbacks: playbacks,
|
||||
previews: previews
|
||||
}
|
||||
end
|
||||
|
||||
def webhook_register(url, meeting_id=nil)
|
||||
params = { callbackURL: url }
|
||||
params.merge!({ meetingID: meeting_id }) if meeting_id.present?
|
||||
bbb_safe_execute :send_api_request, "hooks/create", params
|
||||
end
|
||||
|
||||
def webhook_remove(url)
|
||||
res = bbb_safe_execute :send_api_request, "hooks/list"
|
||||
if res && res[:hooks] && res[:hooks][:hook]
|
||||
res[:hooks][:hook] = [res[:hooks][:hook]] unless res[:hooks][:hook].is_a?(Array)
|
||||
hook = res[:hooks][:hook].select{ |h|
|
||||
h[:callbackURL] == url
|
||||
}.first
|
||||
if hook.present?
|
||||
params = { hookID: hook[:hookID] }
|
||||
bbb_safe_execute :send_api_request, "hooks/destroy", params
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def success_join_res(join_url)
|
||||
{
|
||||
returncode: true,
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
#
|
||||
# Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free Software
|
||||
# Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License along
|
||||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
class NotificationMailer < ActionMailer::Base
|
||||
default from: Rails.configuration.smtp_from
|
||||
|
||||
def recording_ready_email(user)
|
||||
@user = user
|
||||
@room_url = resource_url(resource: "rooms", id: user.encrypted_id)
|
||||
mail(to: user.email, subject: t('.subject'))
|
||||
end
|
||||
end
|
|
@ -21,6 +21,7 @@ class User < ApplicationRecord
|
|||
def self.from_omniauth(auth_hash)
|
||||
user = find_or_initialize_by(uid: auth_hash['uid'], provider: auth_hash['provider'])
|
||||
user.username = self.send("#{auth_hash['provider']}_username", auth_hash) rescue nil
|
||||
user.email = self.send("#{auth_hash['provider']}_email", auth_hash) rescue nil
|
||||
user.name = auth_hash['info']['name']
|
||||
user.save!
|
||||
user
|
||||
|
@ -30,10 +31,18 @@ class User < ApplicationRecord
|
|||
auth_hash['info']['nickname']
|
||||
end
|
||||
|
||||
def self.twitter_email(auth_hash)
|
||||
auth_hash['info']['email']
|
||||
end
|
||||
|
||||
def self.google_username(auth_hash)
|
||||
auth_hash['info']['email'].split('@').first
|
||||
end
|
||||
|
||||
def self.google_email(auth_hash)
|
||||
auth_hash['info']['email']
|
||||
end
|
||||
|
||||
def room_url
|
||||
"/rooms/#{encrypted_id}"
|
||||
end
|
||||
|
|
|
@ -24,6 +24,7 @@ json.recordings do
|
|||
json.end_time recording[:endTime]
|
||||
json.published recording[:published]
|
||||
json.length recording[:length]
|
||||
json.listed recording[:listed]
|
||||
json.previews do
|
||||
json.array!(recording[:previews]) do |preview|
|
||||
json.partial! 'preview', preview: preview
|
||||
|
|
|
@ -43,13 +43,24 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||
|
||||
<div hidden class="hidden-elements">
|
||||
<div class="delete-popover-body">
|
||||
<button type="button" class="btn btn-danger recording-delete">
|
||||
<button type="button" class="btn btn-danger recording-delete" data-dismiss="popover">
|
||||
<%= t('yes') %>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default">
|
||||
<button type="button" class="btn btn-default" data-dismiss="popover">
|
||||
<%= t('no') %>
|
||||
</button>
|
||||
</div>
|
||||
<div class="recording-visibility-popover">
|
||||
<button type="button" class="btn btn-default btn-danger recording-update" data-visibility="unpublished">
|
||||
<%= t('unpublished') %>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-warning recording-update" data-visibility="unlisted">
|
||||
<%= t('unlisted') %>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-success recording-update" data-visibility="published">
|
||||
<%= t('published') %>
|
||||
</button>
|
||||
</div>
|
||||
<div class="alert-template">
|
||||
<div class="alert alert-success alert-dismissible fade in" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
|
@ -59,12 +70,14 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||
</div>
|
||||
</div>
|
||||
<div class="recording-actions">
|
||||
<button type="button" class="btn btn-default recording-update bottom-tooltip" data-published="">
|
||||
<i class="fa default" aria-hidden="true"></i>
|
||||
<i class="fa hover" aria-hidden="true"></i>
|
||||
<button type="button" class="btn btn-default has-popover recording-update-trigger"
|
||||
data-placement="left" data-popover-body=".recording-visibility-popover"
|
||||
data-popover-title="<%= t('change_recording_visibility') %>">
|
||||
<i class="fa fa-eye" aria-hidden="true"></i>
|
||||
</button>
|
||||
<a tabindex="0" role="button" class="btn btn-default has-popover delete-tooltip"
|
||||
data-placement="top">
|
||||
data-placement="top" data-popover-body=".delete-popover-body"
|
||||
data-popover-title="<%= t('are_you_sure') %>">
|
||||
<i class="fa fa-trash-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -77,6 +77,13 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||
</body>
|
||||
</html>
|
||||
|
||||
<!-- Global javascript variables and helpers -->
|
||||
<script type="text/javascript">
|
||||
window.I18n = <%= client_translations.to_json.html_safe %>
|
||||
window.GreenLight = {};
|
||||
window.GreenLight.META_LISTED = "<%= BbbApi::META_LISTED %>";
|
||||
window.GreenLight.META_TOKEN = "<%= BbbApi::META_TOKEN %>";
|
||||
window.GreenLight.META_HOOK_URL = "<%= BbbApi::META_HOOK_URL %>";
|
||||
window.GreenLight.user = {};
|
||||
window.GreenLight.user.roomOwner = <%= is_room_owner %>;
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
|
||||
</head>
|
||||
<body>
|
||||
<p><%= t('.hi', name: @user.name) %></p>
|
||||
<p><%= t('.phrase1') %></p>
|
||||
<p><%= t('.phrase2', url: @room_url) %></p>
|
||||
</body>
|
||||
</html>
|
|
@ -15,15 +15,17 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||
|
||||
<div <%= "hidden" if hidden %> class="meeting-url-wrapper">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control meeting-url"/>
|
||||
<span class="input-group-btn">
|
||||
<div class="meeting-url-group">
|
||||
<input type="text" class="form-control meeting-url"/>
|
||||
<% if params[:action] == 'index' %>
|
||||
<button type="button" class="btn btn-default generate-link has-tooltip"
|
||||
title="<%= t('url_refresh_hint') %>"
|
||||
>
|
||||
<i class="fa fa-refresh" aria-hidden="true"></i>
|
||||
</button>
|
||||
<i class="fa fa-refresh generate-link has-tooltip" aria-hidden="true"
|
||||
title="<%= t('url_refresh_hint') %>"
|
||||
></i>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<span class="input-group-btn">
|
||||
|
||||
<button type="button" class="btn btn-default meeting-url-copy has-tooltip"
|
||||
title="<%= t('url_copy_explanation') %>"
|
||||
data-copied-hint="<%= t('copied') %>"
|
||||
|
@ -32,6 +34,22 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||
>
|
||||
<i class="fa fa-paperclip" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
<% if current_user %>
|
||||
<% body = t('meeting_invite.signed_in.body', user: current_user.name) %>
|
||||
<% subject = t('meeting_invite.signed_in.subject', user: current_user.name) %>
|
||||
<% else %>
|
||||
<% body = t('meeting_invite.not_signed_in.body') %>
|
||||
<% subject = t('meeting_invite.not_signed_in.subject') %>
|
||||
<% end %>
|
||||
<button type="button" class="btn btn-default meeting-invite has-tooltip"
|
||||
title="<%= t('meeting_invite.explanation') %>"
|
||||
data-invite-body="<%= body %>"
|
||||
data-invite-subject="<%= subject %>"
|
||||
>
|
||||
<i class="fa fa-envelope-o" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -37,5 +37,39 @@ module Greenlight
|
|||
# BigBlueButton
|
||||
config.bigbluebutton_endpoint = ENV['BIGBLUEBUTTON_ENDPOINT']
|
||||
config.bigbluebutton_secret = ENV['BIGBLUEBUTTON_SECRET']
|
||||
|
||||
config.use_webhooks = ENV['GREENLIGHT_USE_WEBHOOKS'] == "true"
|
||||
config.mail_notifications = ENV['GREENLIGHT_MAIL_NOTIFICATIONS'] == "true"
|
||||
|
||||
# SMTP and action mailer
|
||||
if config.mail_notifications
|
||||
config.smtp_from = ENV['SMTP_FROM']
|
||||
config.smtp_server = ENV['SMTP_SERVER']
|
||||
config.smtp_domain = ENV['SMTP_DOMAIN']
|
||||
config.smtp_port = ENV['SMTP_PORT'] || 587
|
||||
config.smtp_username = ENV['SMTP_USERNAME']
|
||||
config.smtp_password = ENV['SMTP_PASSWORD']
|
||||
config.smtp_auth = ENV['SMTP_AUTH'] || "login"
|
||||
config.smtp_starttls_auto = ENV['SMTP_STARTTLS_AUTO'].nil? ? true : ENV['SMTP_STARTTLS_AUTO']
|
||||
config.smtp_tls = ENV['SMTP_TLS'].nil? ? false : ENV['SMTP_TLS']
|
||||
|
||||
config.action_mailer.default_url_options = { host: ENV['GREENLIGHT_DOMAIN'] }
|
||||
config.action_mailer.delivery_method = :smtp
|
||||
config.action_mailer.perform_deliveries = true
|
||||
config.action_mailer.raise_delivery_errors = true
|
||||
config.action_mailer.smtp_settings = {
|
||||
address: config.smtp_server,
|
||||
domain: config.smtp_domain,
|
||||
port: config.smtp_port,
|
||||
user_name: config.smtp_username,
|
||||
password: config.smtp_password,
|
||||
authentication: config.smtp_auth,
|
||||
enable_starttls_auto: config.smtp_starttls_auto,
|
||||
tls: config.smtp_tls
|
||||
}
|
||||
config.action_mailer.default_options = {
|
||||
from: config.smtp_from
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
en-US:
|
||||
actions: Actions
|
||||
are_you: Are you %{name}?
|
||||
are_you_sure: Are you sure?
|
||||
change_recording_visibility: "Change visibility to:"
|
||||
client:
|
||||
are_you_sure: Are you sure?
|
||||
delete_recording: Delete recording
|
||||
|
@ -46,10 +48,14 @@ en-US:
|
|||
no_recordings: No Recordings
|
||||
no_recordings_yet: No Recordings (Yet!)
|
||||
publish_recording: Publish recording
|
||||
recording_created: A recording was created
|
||||
recording_deleted: Recording was deleted
|
||||
recording_published: Recording was published
|
||||
recording_unlisted: Recording was unlisted
|
||||
recording_unpublished: Recording was unpublished
|
||||
unpublish_recording: Hide recording
|
||||
user_waiting_body: "%{user} is waiting to join your room!"
|
||||
user_waiting_title: A user is waiting
|
||||
copied: Copied
|
||||
copy_error: Use Ctrl-c to copy
|
||||
create_session: Create a Session
|
||||
|
@ -67,18 +73,35 @@ en-US:
|
|||
join_session_user: Join %{name} session
|
||||
login: login
|
||||
logout: logout
|
||||
meeting_invite:
|
||||
explanation: Send an email with an invitation to this meeting
|
||||
not_signed_in:
|
||||
body: "You have been invited to a meeting.\n\nPlease open the following page in your web browser: &&URL&&"
|
||||
subject: "Invitation to a meeting"
|
||||
signed_in:
|
||||
body: "You have been invited by %{user} to a meeting.\n\nPlease open the following page in your web browser: &&URL&&"
|
||||
subject: "%{user} invited you to a meeting"
|
||||
my_room: my room
|
||||
no: No
|
||||
notification_mailer:
|
||||
recording_ready_email:
|
||||
hi: "Hi, %{name}"
|
||||
phrase1: "One of your recordings has just been made available."
|
||||
phrase2: "Access the following website to view it and publish to other users: %{url}"
|
||||
subject: "Your recording is ready!"
|
||||
oauth_signup: Signup for customized sessions
|
||||
past_recordings: Past Recordings
|
||||
footer_html: "%{greenlight_link} build %{version}, Powered by %{bbb_link}"
|
||||
presentation: Presentation
|
||||
previously_joined_meetings: Previously Joined Sessions
|
||||
published: Published
|
||||
return_to_room: Return to your personal room
|
||||
session_url_explanation: The session will be taking place using the following URL
|
||||
start: Start
|
||||
your_personal_room: Your Personal Room
|
||||
thumbnails: Thumbnails
|
||||
unlisted: Unlisted
|
||||
unpublished: Unpublished
|
||||
url_copy_explanation: Copy this URL to invite others to the meeting
|
||||
url_refresh_hint: Generate a new meeting URL
|
||||
video: Video
|
||||
|
|
|
@ -31,6 +31,7 @@ Rails.application.routes.draw do
|
|||
get '/:resource/:id/join', to: 'bbb#join', as: :bbb_join, defaults: {format: 'json'}
|
||||
get '/:resource/:id/wait', to: 'landing#wait_for_moderator'
|
||||
get '/:resource/:id/session_status_refresh', to: 'landing#session_status_refresh'
|
||||
post '/:resource/:id/callback', to: 'bbb#callback' #, defaults: {format: 'json'}
|
||||
delete '/rooms/:id/end', to: 'bbb#end', defaults: {format: 'json'}
|
||||
get '/rooms/:id/recordings', to: 'bbb#recordings', defaults: {format: 'json'}
|
||||
patch '/rooms/:id/recordings/:record_id', to: 'bbb#update_recordings', defaults: {format: 'json'}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
||||
#
|
||||
# Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free Software
|
||||
# Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License along
|
||||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
class AddEmailToUser < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
add_column :users, :email, :string
|
||||
add_index :users, :email, unique: true
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20161108224701) do
|
||||
ActiveRecord::Schema.define(version: 20161208185458) do
|
||||
|
||||
create_table "users", force: :cascade do |t|
|
||||
t.string "provider", null: false
|
||||
|
@ -20,6 +20,8 @@ ActiveRecord::Schema.define(version: 20161108224701) do
|
|||
t.datetime "updated_at", null: false
|
||||
t.string "username"
|
||||
t.string "encrypted_id", null: false
|
||||
t.string "email"
|
||||
t.index ["email"], name: "index_users_on_email", unique: true
|
||||
t.index ["encrypted_id"], name: "index_users_on_encrypted_id", unique: true
|
||||
t.index ["provider", "uid"], name: "index_users_on_provider_and_uid", unique: true
|
||||
t.index ["provider"], name: "index_users_on_provider"
|
||||
|
|
27
sample.env
27
sample.env
|
@ -7,6 +7,33 @@ RAILS_ENV=development
|
|||
BIGBLUEBUTTON_ENDPOINT=http://test-install.blindsidenetworks.com/bigbluebutton/
|
||||
BIGBLUEBUTTON_SECRET=8cd8ef52e8e101574e400365b55e11a6
|
||||
|
||||
# If "true", GreenLight will register a webhook callback for each meeting
|
||||
# created. This callback is called for all events that happen in the meeting,
|
||||
# including the processing of its recording. These events are used to update
|
||||
# the web page dynamically as things happen in the server.
|
||||
# If not "true", the application will add a metadata to the meetings with this same
|
||||
# callback URL. Scripts can then be added to BigBlueButton to call this callback
|
||||
# URL and send specific events to GreenLight (e.g. a post publish script to warn
|
||||
# the application when recordings are done).
|
||||
GREENLIGHT_USE_WEBHOOKS=false
|
||||
|
||||
# The web site domain, needed for emails mostly
|
||||
GREENLIGHT_DOMAIN=localhost
|
||||
|
||||
# Enable email notifications
|
||||
GREENLIGHT_MAIL_NOTIFICATIONS=true
|
||||
|
||||
# Email configurations
|
||||
SMTP_FROM=email@gmail.com
|
||||
SMTP_SERVER=smtp.gmail.com
|
||||
SMTP_DOMAIN=gmail.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USERNAME=email@gmail.com
|
||||
SMTP_PASSWORD=my-secret-password
|
||||
# SMTP_TLS=false
|
||||
# SMTP_AUTH=login
|
||||
# SMTP_STARTTLS_AUTO=true
|
||||
|
||||
# OmniAuth
|
||||
TWITTER_ID=
|
||||
TWITTER_SECRET=
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
#!/usr/bin/ruby
|
||||
|
||||
##################################################################
|
||||
# Make sure the dependencies of gems are met
|
||||
#
|
||||
# gem install jwt
|
||||
# gem install java_properties
|
||||
##################################################################
|
||||
|
||||
#
|
||||
# Example of a post publish script to send an event to GreenLight
|
||||
# whenever a recording is published in the BigBlueButton server.
|
||||
#
|
||||
# Uses the same data format and checksum calculation method used by
|
||||
# the webhooks module.
|
||||
#
|
||||
|
||||
require "trollop"
|
||||
require "net/http"
|
||||
require "jwt"
|
||||
require "java_properties"
|
||||
require "json"
|
||||
require "digest/sha1"
|
||||
require "uri"
|
||||
require File.expand_path('../../../lib/recordandplayback', __FILE__)
|
||||
|
||||
logger = Logger.new("/var/log/bigbluebutton/post_process.log", 'weekly')
|
||||
logger.level = Logger::INFO
|
||||
BigBlueButton.logger = logger
|
||||
|
||||
opts = Trollop::options do
|
||||
opt :meeting_id, "Meeting id to archive", :type => String
|
||||
end
|
||||
meeting_id = opts[:meeting_id]
|
||||
|
||||
meeting_metadata = BigBlueButton::Events.get_meeting_metadata("/var/bigbluebutton/recording/raw/#{meeting_id}/events.xml")
|
||||
|
||||
BigBlueButton.logger.info("Post Process: Recording Notify for [#{meeting_id}] starts")
|
||||
|
||||
begin
|
||||
callback_url = meeting_metadata["gl-webhooks-callback-url"]
|
||||
|
||||
unless callback_url.nil?
|
||||
BigBlueButton.logger.info("Making callback for recording ready notification")
|
||||
|
||||
props = JavaProperties::Properties.new("/var/lib/tomcat7/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties")
|
||||
secret = props[:securitySalt]
|
||||
|
||||
timestamp = Time.now.to_i
|
||||
event = {
|
||||
"header": {
|
||||
"name": "publish_ended"
|
||||
},
|
||||
"payload":{
|
||||
"metadata": meeting_metadata,
|
||||
"meeting_id": meeting_id
|
||||
}
|
||||
}
|
||||
payload = {
|
||||
event: event,
|
||||
timestamp: timestamp
|
||||
}
|
||||
|
||||
checksum_str = "#{callback_url}#{payload.to_json}#{secret}"
|
||||
checksum = Digest::SHA1.hexdigest(checksum_str)
|
||||
BigBlueButton.logger.info("Got checksum #{checksum} for #{checksum_str}")
|
||||
|
||||
separator = URI.parse(callback_url).query ? "&" : "?"
|
||||
uri = URI.parse("#{callback_url}#{separator}checksum=#{checksum}")
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
http.use_ssl = (uri.scheme == 'https')
|
||||
|
||||
request = Net::HTTP::Post.new(uri.request_uri, 'Content-Type' => 'application/json')
|
||||
request.body = payload.to_json
|
||||
BigBlueButton.logger.info("Posted event to #{callback_url}")
|
||||
|
||||
response = http.request(request)
|
||||
code = response.code.to_i
|
||||
|
||||
if code == 410
|
||||
BigBlueButton.logger.info("Notified for deleted meeting: #{meeting_id}")
|
||||
# TODO: should we automatically delete the recording here?
|
||||
elsif code == 404
|
||||
BigBlueButton.logger.warn("404 error when notifying for recording: #{meeting_id}, ignoring")
|
||||
elsif code < 200 || code >= 300
|
||||
BigBlueButton.logger.debug("Callback HTTP request failed: #{response.code} #{response.message} (code #{code})")
|
||||
else
|
||||
BigBlueButton.logger.debug("Recording notifier successful: #{meeting_id} (code #{code})")
|
||||
end
|
||||
|
||||
else
|
||||
BigBlueButton.logger.info("Blank callback URL, aborting.")
|
||||
end
|
||||
|
||||
rescue => e
|
||||
BigBlueButton.logger.info("Rescued")
|
||||
BigBlueButton.logger.info(e.to_s)
|
||||
end
|
||||
|
||||
BigBlueButton.logger.info("Post Process: Recording Notify ends")
|
||||
|
||||
exit 0
|
|
@ -0,0 +1,6 @@
|
|||
# Preview all emails at http://localhost:3000/rails/mailers/example_mailer
|
||||
class NotificationMailerPreview < ActionMailer::Preview
|
||||
def recording_ready_email_preview
|
||||
NotificationMailer.recording_ready_email(User.first)
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue