Skip to content

Instantly share code, notes, and snippets.

@andreslucena
Last active March 10, 2021 14:42
Show Gist options
  • Save andreslucena/9c293f4ba916a3e7da164720da0af21b to your computer and use it in GitHub Desktop.
Save andreslucena/9c293f4ba916a3e7da164720da0af21b to your computer and use it in GitHub Desktop.
A Decidim hack for having videoconferences on Meetings

This is a quick hack for having videoconferences support on Decidim. Supported services:

  1. YouTube
  2. Jitsi We've been using this on Barcelona (decidim.barcelona and multitenant organization) and also on Meta (meta.decidim.org)

How it works:

  1. You need to add this as a HTML snippet option (config.enable_html_header_snippets on config/initializers/decidim.rb) or as a javascript file on your application (app/assets/javascripts/videoconference.js for instance).
  2. You can configure a Meeting with two services, through the address detail field (see screenshots). You'll need to configure a real address if having geocoding enabled. On this case then you can put the whole city (ie "Barcelona").

2.1. YouTube: you need to also add the URL of the Streaming on the location field Imgur screenshot

2.2. Jitsi: you don't need to do anything else. It only works for logged in participants (a visitor who has no user account will see a warning message). The Jitsi room is generated with the Decidim Reference. Imgur screenshot

Check the future releases notes when upgrading as this could be a new feature or its own module.

Happy hacking!

TODO

  • Add better sign up / sign in granularity for every space (like "I want sign up on X assembly" and "I want visitors on Y participatory process")
  • Handling of private/transparences spaces
  • Add Localret room supports (or other jitsi servers)
  • Add better i18n handling

CHANGELOG

Update 2020-06-18 - version 0.2

  • Meetings only visible during the meeting or the minutes before that's configured on "var minutes_before" (default 30 minutes)
  • Let's you configure if the meeting should be vissible for everyone or only for the logged in users in "var should_work_for_logged_in_only" (default true, meaning that everyone only logged in users will see the meeting room). Just FYI, this is not secure, as the meeting is created using the Reference and that's public information.
<script>
function isJitsiMeeting() {
return $('.address__details strong').first().html() == "Jitsi";
}
function isYouTubeMeeting() {
return $('.address__details strong').first().html() == "YouTube";
}
function hasUserNotLoggedIn() {
return $('.sign-in-link').length > 0;
}
function isLocationMeetings() {
return window.location.href.indexOf("/meetings") != -1;
}
function addJitsiAlert() {
var msg_ca = "Per accedir a la videoconfèrencia has de iniciar sessió. <a href='/users/sign_up'>Registra't </a> o <a href='/users/sign_in'>Entra </a>";
var msg_es = "Para acceder a la videoconferencia debes iniciar sesión como usuaria. <a href='/users/sign_up'>Regístrate </a> o <a href='/users/sign_in'>Entra</a>";
var msg = $('html').attr('lang') == "ca" ? msg_ca : msg_es;
var html = '<div class="flash callout warning">' + msg + '</div>';
var $main_content = $('.columns.mediumlarge-8.mediumlarge-pull-4');
$main_content.html( html + $main_content.html());
}
function getJitsiURL(){
// get the jitsi URL with the reference and a slug
var ref = $('.tech-info').html().replace('Referència: ','').replace('Referencia: ', '');
var url = "https://meet.jit.si/" + ref;
return url;
}
function getYouTubeURL(){
// get the YouTube URL from Decidim metadata
var url = $('.address__details span:last').html();
var video_id = /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/.exec(url)[5];
return "https://www.youtube.com/embed/" + video_id;
}
function buildVideoconferenceButton(type_class, i18n){
var msg_title = $('html').attr('lang') == "ca" ? i18n["ca"]["title"] : i18n["es"]["title"];
var msg_button = $('html').attr('lang') == "ca" ? i18n["ca"]["button"] : i18n["es"]["button"];
var button = '<section><h4 class="section-heading">' + msg_title + '</h4>';
button += '<p><a href="#" class="button small button--sc ' + type_class + '">' + msg_button + '</a></p></section>';
var html = '<div class="row etherpad"><div class="columns large-12">';
html += button;
html += '</div></div>';
return html;
}
function addVideoconferenceButton(type_class, i18n){
// show the youtube/jitsi button to open the videoconference
if ( isJitsiMeeting() || isYouTubeMeeting() ) {
var button_html = buildVideoconferenceButton(type_class, i18n);
$('main .wrapper:first .row:nth-child(2)').after(button_html);
}
}
function addJitsiIframe(selector){
// add the jitsi iframe
if ( isLocationMeetings() ) {
$(selector).parent().append('<iframe allow="camera; microphone" src = "' + getJitsiURL() + '" frameborder="0" width="100%" height="600px"></iframe>')
}
}
function addYouTubeIframe(selector){
// add the youtube iframe
if ( isLocationMeetings() ) {
var iframe = '<iframe width="100%" height="630" src="' + getYouTubeURL() + '" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe';
$(selector).parent().append(iframe);
}
}
function clickOnJitsiButton(selector){
// check for click on jitsi button. if clicked then open the iframe
$(selector).on('click', function(e){
e.preventDefault();
addJitsiIframe(selector);
$(this).hide();
});
}
function clickOnYouTubeButton(selector){
// check for click on youtube button. if clicked then open the iframe
$(selector).on('click', function(e){
e.preventDefault();
addYouTubeIframe(selector);
$(this).hide();
});
}
function getMeetingMetadata(){
var meet_full_date = $(".extra__date").text().toLowerCase().trim();
var meet_month_str = $(".extra__month").text().toLowerCase();
var meet_full_time = $('.extra__time').text().trim().split(' - ');
var meet_start_time = meet_full_time[0];
var meet_end_time = meet_full_time[1];
var meet_day = meet_full_date.split(' ')[0];
// ugly hack for converting dates - decidim should offer an abbr format or something like that for having the month on raw
var months = {
'gener': 1, 'enero': 1, 'january': 1,
'febrer': 2, 'febrero': 2, 'february': 2,
'març': 3, 'marzo': 3, 'march': 3,
'abril': 4, 'abril': 4, 'april': 4,
'maig': 5, 'mayo': 5, 'may': 5,
'juny': 6, 'junio': 6, 'june': 6,
'juliol': 7, 'julio': 7, 'july': 7,
'agost': 8, 'agosto': 8, 'august': 8,
'setembre': 9, 'septiembre': 9, 'september': 9,
'octubre': 10, 'octubre': 10, 'october': 10,
'novembre': 11, 'noviembre': 11, 'november': 11,
'desembre': 12, 'diciembre': 12, 'december': 12
};
var meet_month = months[meet_month_str];
return {
start_datetime: new Date(2020, meet_month - 1, meet_day, meet_start_time.split(':')[0], meet_start_time.split(':')[1]),
end_datetime: new Date(2020, meet_month - 1, meet_day, meet_end_time.split(':')[0], meet_end_time.split(':')[1])
}
}
function shouldMeetingBeVisible(minutes_before){
var current_datetime = new Date();
var meeting_start_datetime = getMeetingMetadata()["start_datetime"];
var meeting_end_datetime = getMeetingMetadata()["end_datetime"];
if ( current_datetime < meeting_start_datetime ) {
var minutes_diff = (meeting_start_datetime.getTime() - current_datetime.getTime()) / 1000 / 60;
if (minutes_diff < minutes_before) {
return true;
} else {
return false;
}
} else {
if ( current_datetime < meeting_end_datetime ) {
// meeting has already started but has not finished yet
return true;
} else {
return false;
}
}
}
$( document ).ready(function() {
// version 0.2
var minutes_before = 30;
var should_work_for_logged_in_only = true;
//
var i18n = {
ca: { title: "Sala de reunions online", button: "Obrir videoconfèrencia" },
es: { title: "Sala de reuniones online", button: "Abrir videoconferencia" }
}
if ( shouldMeetingBeVisible(minutes_before) ) {
if ( isJitsiMeeting() ) {
if ( should_work_for_logged_in_only && hasUserNotLoggedIn() ) {
addJitsiAlert();
} else {
addVideoconferenceButton("js-embed-open-jitsi", i18n);
clickOnJitsiButton('.js-embed-open-jitsi');
}
}
if ( isYouTubeMeeting() ) {
addVideoconferenceButton("js-embed-open-youtube", i18n);
clickOnYouTubeButton('.js-embed-open-youtube');
}
}
});
</script>
@Leusev
Copy link

Leusev commented Jan 19, 2021

Hi!
in first place thanks for this hack! 😄
As a recent issue in Decidim DiBa customer, I found that there's a little bug into this code. As we have changed year to 2021, this javascript code stops working due to this 2020 hardcoded year into getMeetingMetadata function.
image

I fixed it in our customer code in this way in order to solve it:
image
...
image

leaving the final code as follows:

function getMeetingMetadata(){
    var current_year = new Date().getFullYear();
    var meet_full_date = $(".extra__date").text().toLowerCase().trim();
    var meet_month_str = $(".extra__month").text().toLowerCase();
    var meet_full_time = $('.extra__time').text().trim().split(' - ');
    var meet_start_time = meet_full_time[0];
    var meet_end_time = meet_full_time[1]; 
    var meet_day = meet_full_date.split(' ')[0];
    // ugly hack for converting dates - decidim should offer an abbr format or something like that for having the month on raw 
    var months = {
        'gener': 1, 'enero': 1, 'january': 1,
        'febrer': 2, 'febrero': 2, 'february': 2, 
        'març': 3, 'marzo': 3, 'march': 3, 
        'abril': 4, 'abril': 4, 'april': 4,
        'maig': 5, 'mayo': 5, 'may': 5,
        'juny': 6, 'junio': 6, 'june': 6,
        'juliol': 7, 'julio': 7, 'july': 7,
        'agost': 8, 'agosto': 8, 'august': 8,
        'setembre': 9, 'septiembre': 9, 'september': 9,
        'octubre': 10, 'octubre': 10, 'october': 10, 
        'novembre': 11, 'noviembre': 11, 'november': 11,
        'desembre': 12, 'diciembre': 12, 'december': 12
    };
    var meet_month = months[meet_month_str];
    return {
        start_datetime: new Date(current_year, meet_month - 1, meet_day, meet_start_time.split(':')[0], meet_start_time.split(':')[1]),
        end_datetime: new Date(current_year, meet_month - 1, meet_day,  meet_end_time.split(':')[0], meet_end_time.split(':')[1])
    }
}

Thanks a lot!

@JoelXN
Copy link

JoelXN commented Mar 10, 2021

Hi I found a small bug in the code in version 0.23.

In the 'getMeetingMetadata' function when trying to retrieve the month the variable meet_month returns 'undefined'.

To fix this I made a modification to line 99.

I changed:
var meet_month_str = $(".extra__month").text().toLowerCase();

for:
var meet_month_str = $(".extra__month").text().toLowerCase().trim();

thus correctly recovers the month.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment