Browse Source

First commit

Penar Musaraj 4 years ago
commit
e66d8a13e8

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+.DS_Store

+ 3 - 0
README.md

@@ -0,0 +1,3 @@
+# discourse-bbb
+
+Discourse integration with BigBlueButton.

+ 51 - 0
app/controllers/bbb_client_controller.rb

@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+require 'digest/sha1'
+
+module BigBlue
+  class BbbClientController < ApplicationController
+    before_action :ensure_logged_in
+
+    def create
+      render json: {
+        url: build_url(params)
+      }
+    end
+
+    private
+
+    def build_url(args)
+      return false unless SiteSetting.bbb_endpoint && SiteSetting.bbb_secret
+
+      meeting_id = args['meetingID']
+      url = SiteSetting.bbb_endpoint
+      secret = SiteSetting.bbb_secret
+      attendee_pw = args['attendeePW']
+      moderator_pw = args['moderatorPW']
+
+      query = {
+        meetingID: meeting_id,
+        attendeePW: attendee_pw,
+        moderatorPW: moderator_pw
+      }.to_query
+
+      checksum = Digest::SHA1.hexdigest ("create" + query + secret)
+
+      create_url = "#{url}create?#{query}&checksum=#{checksum}"
+      response = Excon.get(create_url)
+
+      if response.status != 200
+        Rails.logger.warn("Could not create meeting: #{response.inspect}")
+        return false
+      end
+
+      join_params = {
+        fullName: current_user.name || current_user.username,
+        meetingID: meeting_id,
+        password: attendee_pw # TODO: pass moderator username or staff as moderator?
+      }.to_query
+
+      join_checksum = Digest::SHA1.hexdigest ("join" + join_params + secret)
+      "#{url}join?#{join_params}&checksum=#{join_checksum}"
+    end
+  end
+end

+ 34 - 0
assets/javascripts/discourse-bbb/controllers/insert-bbb.js.es6

@@ -0,0 +1,34 @@
+import Controller from "@ember/controller";
+import ModalFunctionality from "discourse/mixins/modal-functionality";
+
+export default Controller.extend(ModalFunctionality, {
+  keyDown(e) {
+    if (e.keyCode === 13) {
+      e.preventDefault();
+      e.stopPropagation();
+      return false;
+    }
+  },
+
+  onShow() {
+    this.setProperties({
+      meetingID: "",
+      attendeePW: "",
+      moderatorPW: "",
+      buttonText: ""
+    });
+  },
+
+  actions: {
+    insert() {
+      const btnTxt = this.buttonText ? ` label="${this.buttonText}"` : "";
+      this.toolbarEvent.addText(
+        `[wrap=discourse-bbb meetingID="${this.meetingID}"${btnTxt} attendeePW="${this.attendeePW}" moderatorPW="${this.moderatorPW}"][/wrap]`
+      );
+      this.send("closeModal");
+    },
+    cancel() {
+      this.send("closeModal");
+    }
+  }
+});

+ 80 - 0
assets/javascripts/discourse/initializers/bbb.js.es6

@@ -0,0 +1,80 @@
+import { withPluginApi } from "discourse/lib/plugin-api";
+import showModal from "discourse/lib/show-modal";
+import { iconHTML } from "discourse-common/lib/icon-library";
+import { ajax } from "discourse/lib/ajax";
+import { popupAjaxError } from "discourse/lib/ajax-error";
+
+function launchBBB($elem, fullWindow) {
+  const data = $elem.data();
+
+  ajax("/bbb/create.json", {
+    type: "POST",
+    data: data
+  })
+    .then(res => {
+      if (res.url) {
+        console.log(fullWindow);
+        if (fullWindow) {
+          window.location.href = res.url;
+        } else {
+          $elem.children().hide();
+          $elem.append(
+            `<iframe src="${res.url}" allow="camera;microphone;fullscreen;speaker" width="690" height="500" style="border:none"></iframe>`
+          );
+        }
+      }
+    })
+    .catch(function(error) {
+      popupAjaxError(error);
+    });
+}
+
+function attachButton($elem, fullWindow) {
+  const buttonLabel = $elem.data("label") || I18n.t("bbb.launch");
+
+  $elem.html(
+    `<button class='launch-bbb btn'>${iconHTML(
+      "video"
+    )} ${buttonLabel}</button>`
+  );
+  $elem.find("button").on("click", () => launchBBB($elem, fullWindow));
+}
+
+function attachBBB($elem, helper) {
+  if (helper) {
+    const siteSettings = Discourse.__container__.lookup("site-settings:main");
+    const fullWindow = siteSettings.bbb_full_window;
+
+    $elem.find("[data-wrap=discourse-bbb]").each((idx, val) => {
+      attachButton($(val), fullWindow);
+    });
+  }
+}
+
+export default {
+  name: "insert-bbb",
+
+  initialize() {
+    withPluginApi("0.8.31", api => {
+      const currentUser = api.getCurrentUser();
+      const siteSettings = api.container.lookup("site-settings:main");
+
+      api.onToolbarCreate(toolbar => {
+        if (siteSettings.bbb_staff_only && !currentUser.staff) {
+          return;
+        }
+
+        toolbar.addButton({
+          title: "bbb.composer_title",
+          id: "insertBBB",
+          group: "insertions",
+          icon: "fab-bootstrap",
+          perform: e =>
+            showModal("insert-bbb").setProperties({ toolbarEvent: e })
+        });
+      });
+
+      api.decorateCooked(attachBBB, { id: "discourse-bbb" });
+    });
+  }
+};

+ 28 - 0
assets/javascripts/discourse/templates/modal/insert-bbb.hbs

@@ -0,0 +1,28 @@
+{{#d-modal-body title="bbb.modal.title" class="insert-bbb"}}
+<div class="insert-bbb-form">
+  <div class="insert-bbb-input">
+    <label>{{i18n "bbb.meetingID"}}</label>
+    {{text-field value=meetingID}}
+  </div>
+
+  <div class="insert-bbb-input">
+    <label>{{i18n "bbb.button_text"}}</label>
+    {{text-field value=buttonText placeholderKey="bbb.launch"}}
+  </div>
+
+  <div class="insert-bbb-input">
+    <label>{{i18n "bbb.attendeePW"}}</label>
+    {{text-field value=attendeePW}}
+  </div>
+
+  <div class="insert-bbb-input">
+    <label>{{i18n "bbb.moderatorPW"}}</label>
+    {{text-field value=moderatorPW}}
+  </div>
+</div>
+{{/d-modal-body}}
+
+<div class="modal-footer">
+  {{d-button class="btn-primary" disabled=insertDisabled label="bbb.modal.insert" action=(action "insert")}}
+  {{d-button class="btn-danger" label="bbb.modal.cancel" action=(action "cancel")}}
+</div>

+ 13 - 0
config/locales/client.en.yml

@@ -0,0 +1,13 @@
+en:
+  js:
+    bbb:
+      composer_title: BigBlueButton Integration
+      meetingID: Meeting ID
+      button_text: Button label (optional)
+      attendeePW: Attendee password
+      moderatorPW: Moderator password
+      launch: Start Video Conference
+      modal:
+        insert: Insert
+        cancel: Cancel
+        title: Add BBB Integration

+ 7 - 0
config/locales/server.en.yml

@@ -0,0 +1,7 @@
+en:
+  site_settings:
+    bbb_enabled: "Enable BigBlueButton integration"
+    bbb_endpoint: "BigBlueButton Server URL (must include `api/`, as in: mysite.com/bigbluebutton/api/)."
+    bbb_secret: "BigBlueButton Shared Secret"
+    bbb_full_window: "If unchecked, video conference will load in an iframe."
+    bbb_staff_only: "Only show toolbar button to staff members."

+ 17 - 0
config/settings.yml

@@ -0,0 +1,17 @@
+plugins:
+  bbb_enabled:
+    default: false
+    client: true
+  bbb_endpoint:
+    default: ""
+    client: false
+  bbb_secret:
+    default: ""
+    client: false
+    secret: true
+  bbb_full_window:
+    default: true
+    client: true
+  bbb_staff_only:
+    default: true
+    client: true

+ 35 - 0
plugin.rb

@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+# name: discourse-bbb
+# about: Integrate BigBlueButton in Discourse.
+# version: 1.0.0
+# authors: Penar Musaraj
+# url: https://github.com/pmusaraj/discourse-bbb
+
+enabled_site_setting :bbb_enabled
+register_svg_icon "fab-bootstrap"
+register_svg_icon "video"
+
+after_initialize do
+  [
+    "../app/controllers/bbb_client_controller",
+  ].each { |path| require File.expand_path(path, __FILE__) }
+
+  module ::BigBlue
+    PLUGIN_NAME ||= "discourse-bbb".freeze
+
+    class Engine < ::Rails::Engine
+      engine_name BigBlue::PLUGIN_NAME
+      isolate_namespace BigBlue
+    end
+  end
+
+  BigBlue::Engine.routes.draw do
+    post '/create' => 'bbb_client#create', constraints: { format: :json }
+  end
+
+  Discourse::Application.routes.append do
+    mount ::BigBlue::Engine, at: "/bbb"
+  end
+
+end