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 'font-awesome-rails'
|
||||||
gem 'jquery-ui-rails'
|
gem 'jquery-ui-rails'
|
||||||
gem 'jquery-datatables-rails', '~> 3.4.0'
|
gem 'jquery-datatables-rails', '~> 3.4.0'
|
||||||
|
gem 'rails-timeago', '~> 2.0'
|
||||||
|
|
||||||
gem 'http_accept_language'
|
gem 'http_accept_language'
|
||||||
|
|
|
@ -152,6 +152,9 @@ GEM
|
||||||
nokogiri (~> 1.6.0)
|
nokogiri (~> 1.6.0)
|
||||||
rails-html-sanitizer (1.0.3)
|
rails-html-sanitizer (1.0.3)
|
||||||
loofah (~> 2.0)
|
loofah (~> 2.0)
|
||||||
|
rails-timeago (2.15.0)
|
||||||
|
actionpack (>= 3.1)
|
||||||
|
activesupport (>= 3.1)
|
||||||
railties (5.0.0.1)
|
railties (5.0.0.1)
|
||||||
actionpack (= 5.0.0.1)
|
actionpack (= 5.0.0.1)
|
||||||
activesupport (= 5.0.0.1)
|
activesupport (= 5.0.0.1)
|
||||||
|
@ -224,6 +227,7 @@ DEPENDENCIES
|
||||||
omniauth-twitter (= 1.2.1)
|
omniauth-twitter (= 1.2.1)
|
||||||
puma (~> 3.0)
|
puma (~> 3.0)
|
||||||
rails (~> 5.0.0, >= 5.0.0.1)
|
rails (~> 5.0.0, >= 5.0.0.1)
|
||||||
|
rails-timeago (~> 2.0)
|
||||||
sass-rails (~> 5.0)
|
sass-rails (~> 5.0)
|
||||||
spring
|
spring
|
||||||
spring-watcher-listen (~> 2.0.0)
|
spring-watcher-listen (~> 2.0.0)
|
||||||
|
@ -237,4 +241,4 @@ RUBY VERSION
|
||||||
ruby 2.3.1p112
|
ruby 2.3.1p112
|
||||||
|
|
||||||
BUNDLED WITH
|
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 jquery-ui
|
||||||
//= require dataTables/jquery.dataTables
|
//= require dataTables/jquery.dataTables
|
||||||
//= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
|
//= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
|
||||||
|
//= require rails-timeago-all
|
||||||
//= require bootstrap-sprockets
|
//= require bootstrap-sprockets
|
||||||
//= require turbolinks
|
//= require turbolinks
|
||||||
//= require_self
|
//= require_self
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
$(".center-panel-wrapper").html(html);
|
$(".center-panel-wrapper").html(html);
|
||||||
displayRoomURL();
|
displayRoomURL();
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
var initRooms = function() {
|
var initRooms = function() {
|
||||||
App.messages = App.cable.subscriptions.create({
|
App.messages = App.cable.subscriptions.create({
|
||||||
|
@ -43,6 +43,13 @@
|
||||||
} else if (data.action === 'meeting_ended') {
|
} else if (data.action === 'meeting_ended') {
|
||||||
sessionStatusRefresh($('.meeting-url').val());
|
sessionStatusRefresh($('.meeting-url').val());
|
||||||
showAlert(I18n.meeting_ended, 4000);
|
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) {
|
received: function(data) {
|
||||||
|
|
||||||
var recordings = Recordings.getInstance();
|
var recordings = Recordings.getInstance();
|
||||||
var table = recordings.table.api()
|
var table = recordings.table.api();
|
||||||
var row = table.row("#"+data.record_id);
|
var row = table.row("#"+data.id);
|
||||||
|
|
||||||
if (data.action === 'update') {
|
if (data.action === 'update') {
|
||||||
var rowData = row.data();
|
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();
|
recordings.draw();
|
||||||
|
|
||||||
var published = (data.published) ? 'published' : 'unpublished';
|
var status = data.published ? (data.listed ? 'published' : 'unlisted') : 'unpublished';
|
||||||
showAlert(I18n['recording_'+published], 4000);
|
showAlert(I18n['recording_'+status], 4000);
|
||||||
|
|
||||||
} else if (data.action === 'delete') {
|
} else if (data.action === 'delete') {
|
||||||
row.remove();
|
row.remove();
|
||||||
recordings.draw();
|
recordings.draw();
|
||||||
|
|
||||||
showAlert(I18n.recording_deleted, 4000);
|
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');
|
.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) {
|
$('.center-panel-wrapper').on('mouseleave', '.meeting-url-copy', function (event, msg) {
|
||||||
$(this).blur();
|
$(this).blur();
|
||||||
});
|
});
|
||||||
|
@ -120,7 +129,7 @@
|
||||||
selector: '.has-tooltip',
|
selector: '.has-tooltip',
|
||||||
container: 'body'
|
container: 'body'
|
||||||
};
|
};
|
||||||
$(document).tooltip(options)
|
$(document).tooltip(options);
|
||||||
var options = {
|
var options = {
|
||||||
selector: '.bottom-tooltip',
|
selector: '.bottom-tooltip',
|
||||||
container: 'body',
|
container: 'body',
|
||||||
|
@ -151,7 +160,7 @@
|
||||||
var joinedMeetings = localStorage.getItem('joinedMeetings');
|
var joinedMeetings = localStorage.getItem('joinedMeetings');
|
||||||
if (joinedMeetings && joinedMeetings.length > 0) {
|
if (joinedMeetings && joinedMeetings.length > 0) {
|
||||||
joinedMeetings = joinedMeetings.split(',');
|
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--) {
|
for (var i = joinedMeetings.length - 1; i >= 0; i--) {
|
||||||
$('ul.previously-joined').append('<li><a href="/meetings/'+joinedMeetings[i]+'">'+joinedMeetings[i]+'</a></li>');
|
$('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: "previews", orderable: false },
|
||||||
{ data: "duration", orderable: false },
|
{ data: "duration", orderable: false },
|
||||||
{ data: "playbacks", orderable: false },
|
{ data: "playbacks", orderable: false },
|
||||||
{ data: "published", visible: false },
|
{ data: "listed", visible: false },
|
||||||
{ data: "id", orderable: false }
|
{ data: "id", orderable: false }
|
||||||
],
|
],
|
||||||
columnDefs: [
|
columnDefs: [
|
||||||
|
@ -45,10 +45,13 @@ class @Recordings
|
||||||
targets: 0,
|
targets: 0,
|
||||||
render: (data, type, row) ->
|
render: (data, type, row) ->
|
||||||
if type == 'display'
|
if type == 'display'
|
||||||
return new Date(data)
|
date = new Date(data)
|
||||||
|
title = date
|
||||||
.toLocaleString($('html').attr('lang'),
|
.toLocaleString($('html').attr('lang'),
|
||||||
{month: 'long', day: 'numeric', year: 'numeric',
|
{month: 'long', day: 'numeric', year: 'numeric',
|
||||||
hour12: 'true', hour: '2-digit', minute: '2-digit'})
|
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
|
return data
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -78,18 +81,18 @@ class @Recordings
|
||||||
render: (data, type, row) ->
|
render: (data, type, row) ->
|
||||||
if type == 'display'
|
if type == 'display'
|
||||||
roomName = Meeting.getInstance().getId()
|
roomName = Meeting.getInstance().getId()
|
||||||
published = row.published
|
|
||||||
publishText = if published then 'unpublish' else 'publish'
|
|
||||||
recordingActions = $('.hidden-elements').find('.recording-actions')
|
recordingActions = $('.hidden-elements').find('.recording-actions')
|
||||||
recordingActions.find('.recording-update > i.default')
|
classes = ['recording-unpublished', 'recording-unlisted', 'recording-published']
|
||||||
.removeClass(PUBLISHED_CLASSES.join(' '))
|
if row.published
|
||||||
.addClass(getPublishClass(published))
|
if row.listed
|
||||||
recordingActions.find('.recording-update > i.hover')
|
cls = classes[2]
|
||||||
.removeClass(PUBLISHED_CLASSES.join(' '))
|
else
|
||||||
.addClass(getPublishClass(!published))
|
cls = classes[1]
|
||||||
recordingActions.find('.recording-update')
|
else
|
||||||
.attr('data-published', published)
|
cls = classes[0]
|
||||||
.attr('title', I18n[publishText+'_recording'])
|
trigger = recordingActions.find('.recording-update-trigger')
|
||||||
|
trigger.removeClass(classes.join(' '))
|
||||||
|
trigger.addClass(cls)
|
||||||
return recordingActions.html()
|
return recordingActions.html()
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
@ -106,17 +109,33 @@ class @Recordings
|
||||||
@getTable().api().clear().draw().destroy()
|
@getTable().api().clear().draw().destroy()
|
||||||
|
|
||||||
# enable popovers
|
# enable popovers
|
||||||
|
# can't use trigger:'focus' because it doesn't work will with buttons inside
|
||||||
|
# the popover
|
||||||
options = {
|
options = {
|
||||||
selector: '.has-popover',
|
selector: '.has-popover',
|
||||||
html: true,
|
html: true,
|
||||||
trigger: 'focus',
|
trigger: 'click',
|
||||||
title: ->
|
title: ->
|
||||||
return I18n.are_you_sure;
|
return $(this).data("popover-title");
|
||||||
content: ->
|
content: ->
|
||||||
return $(".delete-popover-body").html()
|
bodySelector = $(this).data("popover-body")
|
||||||
|
return $(bodySelector).html()
|
||||||
}
|
}
|
||||||
$('#recordings').popover(options)
|
$('#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
|
# Gets the current instance or creates a new one
|
||||||
@getInstance: ->
|
@getInstance: ->
|
||||||
if _recordingsInstance && Recordings.initialized()
|
if _recordingsInstance && Recordings.initialized()
|
||||||
|
@ -153,24 +172,32 @@ class @Recordings
|
||||||
# setup click handlers for the action buttons
|
# setup click handlers for the action buttons
|
||||||
setupActionHandlers: ->
|
setupActionHandlers: ->
|
||||||
table_api = this.table.api()
|
table_api = this.table.api()
|
||||||
|
|
||||||
@getTable().on 'click', '.recording-update', (event) ->
|
@getTable().on 'click', '.recording-update', (event) ->
|
||||||
btn = $(this)
|
btn = $(this)
|
||||||
row = table_api.row($(this).closest('tr')).data()
|
row = table_api.row($(this).closest('tr')).data()
|
||||||
url = $('.meeting-url').val()
|
url = $('.meeting-url').val()
|
||||||
id = row.id
|
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)
|
btn.prop('disabled', true)
|
||||||
|
|
||||||
|
data = { published: published.toString() }
|
||||||
|
data["meta_" + GreenLight.META_LISTED] = listed.toString();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: url+'/recordings/'+id,
|
url: url+'/recordings/'+id,
|
||||||
data: {published: (!published).toString()}
|
data: data
|
||||||
}).done((data) ->
|
}).done((data) ->
|
||||||
|
|
||||||
).fail((data) ->
|
).fail((data) ->
|
||||||
btn.prop('disabled', false)
|
btn.prop('disabled', false)
|
||||||
)
|
)
|
||||||
|
|
||||||
this.table.on 'click', '.recording-delete', (event) ->
|
@getTable().on 'click', '.recording-delete', (event) ->
|
||||||
btn = $(this)
|
btn = $(this)
|
||||||
row = table_api.row($(this).closest('tr')).data()
|
row = table_api.row($(this).closest('tr')).data()
|
||||||
url = $('.meeting-url').val()
|
url = $('.meeting-url').val()
|
||||||
|
@ -185,6 +212,9 @@ class @Recordings
|
||||||
btn.prop('disabled', false)
|
btn.prop('disabled', false)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@getTable().on 'draw.dt', (event) ->
|
||||||
|
$('time[data-time-ago]').timeago();
|
||||||
|
|
||||||
getTable: ->
|
getTable: ->
|
||||||
@table
|
@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 loopJoin = function() {
|
||||||
var jqxhr = Meeting.getInstance().getJoinMeetingResponse();
|
var jqxhr = Meeting.getInstance().getJoinMeetingResponse();
|
||||||
jqxhr.done(function(data) {
|
jqxhr.done(function(data) {
|
||||||
|
@ -38,8 +32,9 @@ var loopJoin = function() {
|
||||||
jqxhr.fail(function(xhr, status, error) {
|
jqxhr.fail(function(xhr, status, error) {
|
||||||
console.info("meeting join failed");
|
console.info("meeting join failed");
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
|
var alertTimeout = null;
|
||||||
var showAlert = function(html, timeout_delay) {
|
var showAlert = function(html, timeout_delay) {
|
||||||
if (!html) {
|
if (!html) {
|
||||||
return;
|
return;
|
||||||
|
@ -49,12 +44,34 @@ var showAlert = function(html, timeout_delay) {
|
||||||
$('#alerts').html($('.alert-template').html());
|
$('#alerts').html($('.alert-template').html());
|
||||||
|
|
||||||
if (timeout_delay) {
|
if (timeout_delay) {
|
||||||
setTimeout(function() {
|
clearTimeout(alertTimeout);
|
||||||
|
alertTimeout = setTimeout(function() {
|
||||||
$('#alerts > .alert').alert('close');
|
$('#alerts > .alert').alert('close');
|
||||||
}, timeout_delay);
|
}, timeout_delay);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
var displayRoomURL = function() {
|
var displayRoomURL = function() {
|
||||||
$('.meeting-url').val(Meeting.getInstance().getURL());
|
$('.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 {
|
.dataTables_empty {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.recording-update:hover > {
|
.timeago {
|
||||||
.default {
|
margin-left: 5px;
|
||||||
display: none;
|
font-size: 13px;
|
||||||
}
|
cursor: pointer;
|
||||||
.hover {
|
color: #999;
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.fa.hover {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,3 +64,34 @@
|
||||||
z-index: 1;
|
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;
|
background-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body[data-controller=landing][data-action=rooms].app-background {
|
||||||
|
background-image: asset-url('bg_personal_room');
|
||||||
|
}
|
||||||
|
|
||||||
#alerts {
|
#alerts {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
|
@ -106,3 +110,7 @@ html, body {
|
||||||
text-align: center;
|
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 :authorize_recording_owner!, only: [:update_recordings, :delete_recordings]
|
||||||
before_action :load_and_authorize_room_owner!, only: [:end]
|
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
|
# GET /:resource/:id/join
|
||||||
def join
|
def join
|
||||||
if params[:name].blank?
|
if params[:name].blank?
|
||||||
|
@ -39,7 +42,9 @@ class BbbController < ApplicationController
|
||||||
user_is_moderator: true
|
user_is_moderator: true
|
||||||
}
|
}
|
||||||
end
|
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(
|
bbb_res = bbb_join_url(
|
||||||
params[:id],
|
params[:id],
|
||||||
|
@ -47,14 +52,33 @@ class BbbController < ApplicationController
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# the user can join the meeting
|
||||||
if bbb_res[:returncode] && current_user && current_user == user
|
if bbb_res[:returncode] && current_user && current_user == user
|
||||||
JoinMeetingJob.perform_later(user.encrypted_id)
|
JoinMeetingJob.perform_later(user.encrypted_id)
|
||||||
|
|
||||||
|
# user will be waiting for a moderator
|
||||||
|
else
|
||||||
|
NotifyUserWaitingJob.perform_later(user.encrypted_id, params[:name])
|
||||||
end
|
end
|
||||||
|
|
||||||
render_bbb_response bbb_res, bbb_res[:response]
|
render_bbb_response bbb_res, bbb_res[:response]
|
||||||
end
|
end
|
||||||
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
|
# DELETE /rooms/:id/end
|
||||||
def end
|
def end
|
||||||
load_and_authorize_room_owner!
|
load_and_authorize_room_owner!
|
||||||
|
@ -76,9 +100,11 @@ class BbbController < ApplicationController
|
||||||
|
|
||||||
# PATCH /rooms/:id/recordings/:record_id
|
# PATCH /rooms/:id/recordings/:record_id
|
||||||
def update_recordings
|
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]
|
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
|
end
|
||||||
render_bbb_response bbb_res
|
render_bbb_response bbb_res
|
||||||
end
|
end
|
||||||
|
@ -128,4 +154,80 @@ class BbbController < ApplicationController
|
||||||
@response = response
|
@response = response
|
||||||
render status: @status
|
render status: @status
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -15,4 +15,8 @@
|
||||||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
module UsersHelper
|
module UsersHelper
|
||||||
|
def is_room_owner
|
||||||
|
token = current_user ? current_user.encrypted_id : nil
|
||||||
|
token.present? && params[:id].present? && token == params[:id]
|
||||||
|
end
|
||||||
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'
|
if !bbb_res[:recordings] || bbb_res[:messageKey] == 'noRecordings'
|
||||||
ActionCable.server.broadcast "#{room}_recording_updates_channel",
|
ActionCable.server.broadcast "#{room}_recording_updates_channel",
|
||||||
action: 'delete',
|
action: 'delete',
|
||||||
record_id: record_id
|
id: record_id
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
sleep sleep_time
|
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
|
queue_as :default
|
||||||
|
|
||||||
def perform(room, record_id, published)
|
def perform(room, record_id)
|
||||||
tries = 0
|
bbb_res = bbb_get_recordings(nil, record_id)
|
||||||
sleep_time = 2
|
recording = bbb_res[:recordings].first
|
||||||
|
ActionCable.server.broadcast "#{room}_recording_updates_channel",
|
||||||
while tries < 4
|
action: 'update',
|
||||||
bbb_res = bbb_get_recordings(nil, record_id)
|
id: record_id,
|
||||||
if bbb_res[:recordings].first[:published].to_s == published
|
published: recording[:published],
|
||||||
ActionCable.server.broadcast "#{room}_recording_updates_channel",
|
listed: bbb_is_recording_listed(recording)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,6 +15,10 @@
|
||||||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
module BbbApi
|
module BbbApi
|
||||||
|
META_LISTED = "gl-listed"
|
||||||
|
META_TOKEN = "gl-token"
|
||||||
|
META_HOOK_URL = "gl-webhooks-callback-url"
|
||||||
|
|
||||||
def bbb_endpoint
|
def bbb_endpoint
|
||||||
Rails.configuration.bigbluebutton_endpoint || ''
|
Rails.configuration.bigbluebutton_endpoint || ''
|
||||||
end
|
end
|
||||||
|
@ -51,7 +55,7 @@ module BbbApi
|
||||||
|
|
||||||
# See if the meeting is running
|
# See if the meeting is running
|
||||||
begin
|
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
|
rescue BigBlueButton::BigBlueButtonException => exc
|
||||||
# This means that is not created
|
# This means that is not created
|
||||||
|
|
||||||
|
@ -65,7 +69,22 @@ module BbbApi
|
||||||
logout_url = options[:meeting_logout_url] || "#{request.base_url}"
|
logout_url = options[:meeting_logout_url] || "#{request.base_url}"
|
||||||
moderator_password = random_password(12)
|
moderator_password = random_password(12)
|
||||||
viewer_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
|
# Create the meeting
|
||||||
bbb.create_meeting(options[:meeting_name], meeting_id, meeting_options)
|
bbb.create_meeting(options[:meeting_name], meeting_id, meeting_options)
|
||||||
|
|
||||||
|
@ -101,7 +120,7 @@ module BbbApi
|
||||||
options[:recordID] = record_id
|
options[:recordID] = record_id
|
||||||
end
|
end
|
||||||
if meeting_id
|
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
|
end
|
||||||
res = bbb_safe_execute :get_recordings, options
|
res = bbb_safe_execute :get_recordings, options
|
||||||
|
|
||||||
|
@ -148,6 +167,8 @@ module BbbApi
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
recording[:listed] = bbb_is_recording_listed(recording)
|
||||||
end
|
end
|
||||||
res
|
res
|
||||||
end
|
end
|
||||||
|
@ -168,8 +189,11 @@ module BbbApi
|
||||||
response_data = bbb_exception_res exc
|
response_data = bbb_exception_res exc
|
||||||
end
|
end
|
||||||
|
|
||||||
def bbb_update_recordings(id, published)
|
def bbb_update_recordings(id, published, metadata)
|
||||||
bbb_safe_execute :publish_recordings, id, published
|
bbb_safe_execute :publish_recordings, id, published
|
||||||
|
|
||||||
|
params = { recordID: id }.merge(metadata)
|
||||||
|
bbb_safe_execute :send_api_request, "updateRecordings", params
|
||||||
end
|
end
|
||||||
|
|
||||||
def bbb_delete_recordings(id)
|
def bbb_delete_recordings(id)
|
||||||
|
@ -191,6 +215,67 @@ module BbbApi
|
||||||
response_data
|
response_data
|
||||||
end
|
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)
|
def success_join_res(join_url)
|
||||||
{
|
{
|
||||||
returncode: true,
|
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)
|
def self.from_omniauth(auth_hash)
|
||||||
user = find_or_initialize_by(uid: auth_hash['uid'], provider: auth_hash['provider'])
|
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.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.name = auth_hash['info']['name']
|
||||||
user.save!
|
user.save!
|
||||||
user
|
user
|
||||||
|
@ -30,10 +31,18 @@ class User < ApplicationRecord
|
||||||
auth_hash['info']['nickname']
|
auth_hash['info']['nickname']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.twitter_email(auth_hash)
|
||||||
|
auth_hash['info']['email']
|
||||||
|
end
|
||||||
|
|
||||||
def self.google_username(auth_hash)
|
def self.google_username(auth_hash)
|
||||||
auth_hash['info']['email'].split('@').first
|
auth_hash['info']['email'].split('@').first
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.google_email(auth_hash)
|
||||||
|
auth_hash['info']['email']
|
||||||
|
end
|
||||||
|
|
||||||
def room_url
|
def room_url
|
||||||
"/rooms/#{encrypted_id}"
|
"/rooms/#{encrypted_id}"
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,6 +24,7 @@ json.recordings do
|
||||||
json.end_time recording[:endTime]
|
json.end_time recording[:endTime]
|
||||||
json.published recording[:published]
|
json.published recording[:published]
|
||||||
json.length recording[:length]
|
json.length recording[:length]
|
||||||
|
json.listed recording[:listed]
|
||||||
json.previews do
|
json.previews do
|
||||||
json.array!(recording[:previews]) do |preview|
|
json.array!(recording[:previews]) do |preview|
|
||||||
json.partial! 'preview', preview: 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 hidden class="hidden-elements">
|
||||||
<div class="delete-popover-body">
|
<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') %>
|
<%= t('yes') %>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-default">
|
<button type="button" class="btn btn-default" data-dismiss="popover">
|
||||||
<%= t('no') %>
|
<%= t('no') %>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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-template">
|
||||||
<div class="alert alert-success alert-dismissible fade in" role="alert">
|
<div class="alert alert-success alert-dismissible fade in" role="alert">
|
||||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
<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>
|
</div>
|
||||||
<div class="recording-actions">
|
<div class="recording-actions">
|
||||||
<button type="button" class="btn btn-default recording-update bottom-tooltip" data-published="">
|
<button type="button" class="btn btn-default has-popover recording-update-trigger"
|
||||||
<i class="fa default" aria-hidden="true"></i>
|
data-placement="left" data-popover-body=".recording-visibility-popover"
|
||||||
<i class="fa hover" aria-hidden="true"></i>
|
data-popover-title="<%= t('change_recording_visibility') %>">
|
||||||
|
<i class="fa fa-eye" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<a tabindex="0" role="button" class="btn btn-default has-popover delete-tooltip"
|
<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>
|
<i class="fa fa-trash-o" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -77,6 +77,13 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
<!-- Global javascript variables and helpers -->
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.I18n = <%= client_translations.to_json.html_safe %>
|
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>
|
</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 <%= "hidden" if hidden %> class="meeting-url-wrapper">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control meeting-url"/>
|
<div class="meeting-url-group">
|
||||||
<span class="input-group-btn">
|
<input type="text" class="form-control meeting-url"/>
|
||||||
<% if params[:action] == 'index' %>
|
<% if params[:action] == 'index' %>
|
||||||
<button type="button" class="btn btn-default generate-link has-tooltip"
|
<i class="fa fa-refresh generate-link has-tooltip" aria-hidden="true"
|
||||||
title="<%= t('url_refresh_hint') %>"
|
title="<%= t('url_refresh_hint') %>"
|
||||||
>
|
></i>
|
||||||
<i class="fa fa-refresh" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="input-group-btn">
|
||||||
|
|
||||||
<button type="button" class="btn btn-default meeting-url-copy has-tooltip"
|
<button type="button" class="btn btn-default meeting-url-copy has-tooltip"
|
||||||
title="<%= t('url_copy_explanation') %>"
|
title="<%= t('url_copy_explanation') %>"
|
||||||
data-copied-hint="<%= t('copied') %>"
|
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>
|
<i class="fa fa-paperclip" aria-hidden="true"></i>
|
||||||
</button>
|
</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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -37,5 +37,39 @@ module Greenlight
|
||||||
# BigBlueButton
|
# BigBlueButton
|
||||||
config.bigbluebutton_endpoint = ENV['BIGBLUEBUTTON_ENDPOINT']
|
config.bigbluebutton_endpoint = ENV['BIGBLUEBUTTON_ENDPOINT']
|
||||||
config.bigbluebutton_secret = ENV['BIGBLUEBUTTON_SECRET']
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,6 +38,8 @@
|
||||||
en-US:
|
en-US:
|
||||||
actions: Actions
|
actions: Actions
|
||||||
are_you: Are you %{name}?
|
are_you: Are you %{name}?
|
||||||
|
are_you_sure: Are you sure?
|
||||||
|
change_recording_visibility: "Change visibility to:"
|
||||||
client:
|
client:
|
||||||
are_you_sure: Are you sure?
|
are_you_sure: Are you sure?
|
||||||
delete_recording: Delete recording
|
delete_recording: Delete recording
|
||||||
|
@ -46,10 +48,14 @@ en-US:
|
||||||
no_recordings: No Recordings
|
no_recordings: No Recordings
|
||||||
no_recordings_yet: No Recordings (Yet!)
|
no_recordings_yet: No Recordings (Yet!)
|
||||||
publish_recording: Publish recording
|
publish_recording: Publish recording
|
||||||
|
recording_created: A recording was created
|
||||||
recording_deleted: Recording was deleted
|
recording_deleted: Recording was deleted
|
||||||
recording_published: Recording was published
|
recording_published: Recording was published
|
||||||
|
recording_unlisted: Recording was unlisted
|
||||||
recording_unpublished: Recording was unpublished
|
recording_unpublished: Recording was unpublished
|
||||||
unpublish_recording: Hide recording
|
unpublish_recording: Hide recording
|
||||||
|
user_waiting_body: "%{user} is waiting to join your room!"
|
||||||
|
user_waiting_title: A user is waiting
|
||||||
copied: Copied
|
copied: Copied
|
||||||
copy_error: Use Ctrl-c to copy
|
copy_error: Use Ctrl-c to copy
|
||||||
create_session: Create a Session
|
create_session: Create a Session
|
||||||
|
@ -67,18 +73,35 @@ en-US:
|
||||||
join_session_user: Join %{name} session
|
join_session_user: Join %{name} session
|
||||||
login: login
|
login: login
|
||||||
logout: logout
|
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
|
my_room: my room
|
||||||
no: No
|
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
|
oauth_signup: Signup for customized sessions
|
||||||
past_recordings: Past Recordings
|
past_recordings: Past Recordings
|
||||||
footer_html: "%{greenlight_link} build %{version}, Powered by %{bbb_link}"
|
footer_html: "%{greenlight_link} build %{version}, Powered by %{bbb_link}"
|
||||||
presentation: Presentation
|
presentation: Presentation
|
||||||
previously_joined_meetings: Previously Joined Sessions
|
previously_joined_meetings: Previously Joined Sessions
|
||||||
|
published: Published
|
||||||
return_to_room: Return to your personal room
|
return_to_room: Return to your personal room
|
||||||
session_url_explanation: The session will be taking place using the following URL
|
session_url_explanation: The session will be taking place using the following URL
|
||||||
start: Start
|
start: Start
|
||||||
your_personal_room: Your Personal Room
|
your_personal_room: Your Personal Room
|
||||||
thumbnails: Thumbnails
|
thumbnails: Thumbnails
|
||||||
|
unlisted: Unlisted
|
||||||
|
unpublished: Unpublished
|
||||||
url_copy_explanation: Copy this URL to invite others to the meeting
|
url_copy_explanation: Copy this URL to invite others to the meeting
|
||||||
url_refresh_hint: Generate a new meeting URL
|
url_refresh_hint: Generate a new meeting URL
|
||||||
video: Video
|
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/join', to: 'bbb#join', as: :bbb_join, defaults: {format: 'json'}
|
||||||
get '/:resource/:id/wait', to: 'landing#wait_for_moderator'
|
get '/:resource/:id/wait', to: 'landing#wait_for_moderator'
|
||||||
get '/:resource/:id/session_status_refresh', to: 'landing#session_status_refresh'
|
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'}
|
delete '/rooms/:id/end', to: 'bbb#end', defaults: {format: 'json'}
|
||||||
get '/rooms/:id/recordings', to: 'bbb#recordings', 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'}
|
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.
|
# 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|
|
create_table "users", force: :cascade do |t|
|
||||||
t.string "provider", null: false
|
t.string "provider", null: false
|
||||||
|
@ -20,6 +20,8 @@ ActiveRecord::Schema.define(version: 20161108224701) do
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.string "username"
|
t.string "username"
|
||||||
t.string "encrypted_id", null: false
|
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 ["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", "uid"], name: "index_users_on_provider_and_uid", unique: true
|
||||||
t.index ["provider"], name: "index_users_on_provider"
|
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_ENDPOINT=http://test-install.blindsidenetworks.com/bigbluebutton/
|
||||||
BIGBLUEBUTTON_SECRET=8cd8ef52e8e101574e400365b55e11a6
|
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
|
# OmniAuth
|
||||||
TWITTER_ID=
|
TWITTER_ID=
|
||||||
TWITTER_SECRET=
|
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