Replace FNV32a pad's id generator with salted SHA1
When managing Etherpad's pads, Meteor makes API calls to initiate the closed captions and shared notes modules. The pad id was being mapped to a shorter id than the meeting id because of a Etherpad lenght limitation. Changed to something less guessable.
This commit is contained in:
parent
705ea9915a
commit
c0a7f9cd92
@ -1,22 +1,20 @@
|
|||||||
import { Meteor } from 'meteor/meteor';
|
import { Meteor } from 'meteor/meteor';
|
||||||
import { hashFNV32a } from '/imports/api/common/server/helpers';
|
import { hashSHA1 } from '/imports/api/common/server/helpers';
|
||||||
import { check } from 'meteor/check';
|
import { check } from 'meteor/check';
|
||||||
|
|
||||||
|
const ETHERPAD = Meteor.settings.private.etherpad;
|
||||||
const CAPTIONS_CONFIG = Meteor.settings.public.captions;
|
const CAPTIONS_CONFIG = Meteor.settings.public.captions;
|
||||||
const BASENAME = Meteor.settings.public.app.basename;
|
const BASENAME = Meteor.settings.public.app.basename;
|
||||||
const APP = Meteor.settings.private.app;
|
const APP = Meteor.settings.private.app;
|
||||||
const LOCALES_URL = `http://${APP.host}:${APP.port}${BASENAME}${APP.localesUrl}`;
|
const LOCALES_URL = `http://${APP.host}:${APP.port}${BASENAME}${APP.localesUrl}`;
|
||||||
const CAPTIONS = '_captions_';
|
const CAPTIONS_TOKEN = '_cc_';
|
||||||
const TOKEN = '$';
|
const TOKEN = '$';
|
||||||
|
|
||||||
// Captions padId should look like: {padId}_captions_{locale}
|
// Captions padId should look like: {prefix}_cc_{locale}
|
||||||
const generatePadId = (meetingId, locale) => {
|
const generatePadId = (meetingId, locale) => `${hashSHA1(meetingId+locale+ETHERPAD.apikey)}${CAPTIONS_TOKEN}${locale}`;
|
||||||
const padId = `${hashFNV32a(meetingId, true)}${CAPTIONS}${locale}`;
|
|
||||||
return padId;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isCaptionsPad = (padId) => {
|
const isCaptionsPad = (padId) => {
|
||||||
const splitPadId = padId.split(CAPTIONS);
|
const splitPadId = padId.split(CAPTIONS_TOKEN);
|
||||||
return splitPadId.length === 2;
|
return splitPadId.length === 2;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -45,6 +43,7 @@ const processForCaptionsPadOnly = fn => (message, ...args) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
CAPTIONS_TOKEN,
|
||||||
generatePadId,
|
generatePadId,
|
||||||
processForCaptionsPadOnly,
|
processForCaptionsPadOnly,
|
||||||
isEnabled,
|
isEnabled,
|
||||||
|
@ -23,8 +23,9 @@ export default function appendText(text, locale) {
|
|||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
const { status } = response;
|
const { status } = response;
|
||||||
if (status === 200) {
|
if (status !== 200) {
|
||||||
Logger.verbose('Captions: appended text', { padId });
|
Logger.error(`Could not append captions for padId=${padId}`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}).catch(error => Logger.error(`Could not append captions for padId=${padId}: ${error}`));
|
}).catch(error => Logger.error(`Could not append captions for padId=${padId}: ${error}`));
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ export default function createCaptions(meetingId) {
|
|||||||
const { status } = response;
|
const { status } = response;
|
||||||
if (status !== 200) {
|
if (status !== 200) {
|
||||||
Logger.error(`Could not get locales info for ${meetingId} ${status}`);
|
Logger.error(`Could not get locales info for ${meetingId} ${status}`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const locales = response.data;
|
const locales = response.data;
|
||||||
locales.forEach((locale) => {
|
locales.forEach((locale) => {
|
||||||
|
@ -21,7 +21,6 @@ export default function editCaptions(padId, data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
meetingId,
|
meetingId,
|
||||||
ownerId,
|
ownerId,
|
||||||
|
@ -11,11 +11,17 @@ export default function fetchReadOnlyPadId(padId) {
|
|||||||
check(padId, String);
|
check(padId, String);
|
||||||
|
|
||||||
const readOnlyURL = getReadOnlyIdURL(padId);
|
const readOnlyURL = getReadOnlyIdURL(padId);
|
||||||
|
|
||||||
axios({
|
axios({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: readOnlyURL,
|
url: readOnlyURL,
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
|
const { status } = response;
|
||||||
|
if (status !== 200) {
|
||||||
|
Logger.error(`Could not get closed captions readOnlyID for ${padId} ${status}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const readOnlyPadId = getDataFromResponse(response.data, 'readOnlyID');
|
const readOnlyPadId = getDataFromResponse(response.data, 'readOnlyID');
|
||||||
if (readOnlyPadId) {
|
if (readOnlyPadId) {
|
||||||
updateReadOnlyPadId(padId, readOnlyPadId);
|
updateReadOnlyPadId(padId, readOnlyPadId);
|
||||||
|
@ -2,13 +2,14 @@ import { check } from 'meteor/check';
|
|||||||
import Captions from '/imports/api/captions';
|
import Captions from '/imports/api/captions';
|
||||||
import updateOwnerId from '/imports/api/captions/server/modifiers/updateOwnerId';
|
import updateOwnerId from '/imports/api/captions/server/modifiers/updateOwnerId';
|
||||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||||
|
import { CAPTIONS_TOKEN } from '/imports/api/captions/server/helpers';
|
||||||
|
|
||||||
export default function takeOwnership(locale) {
|
export default function takeOwnership(locale) {
|
||||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||||
|
|
||||||
check(locale, String);
|
check(locale, String);
|
||||||
|
|
||||||
const pad = Captions.findOne({ meetingId, padId: { $regex: `_captions_${locale}$` } });
|
const pad = Captions.findOne({ meetingId, padId: { $regex: `${CAPTIONS_TOKEN}${locale}$` } });
|
||||||
|
|
||||||
if (pad) {
|
if (pad) {
|
||||||
updateOwnerId(meetingId, requesterUserId, pad.padId);
|
updateOwnerId(meetingId, requesterUserId, pad.padId);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import sha1 from 'crypto-js/sha1';
|
||||||
import Users from '/imports/api/users';
|
import Users from '/imports/api/users';
|
||||||
|
|
||||||
const MSG_DIRECT_TYPE = 'DIRECT';
|
const MSG_DIRECT_TYPE = 'DIRECT';
|
||||||
@ -38,31 +39,7 @@ export const processForHTML5ServerOnly = fn => (message, ...args) => {
|
|||||||
return fn(message, ...args);
|
return fn(message, ...args);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export const hashSHA1 = (str) => sha1(str).toString();
|
||||||
* Calculate a 32 bit FNV-1a hash
|
|
||||||
* Found here: https://gist.github.com/vaiorabbit/5657561
|
|
||||||
* Ref.: http://isthe.com/chongo/tech/comp/fnv/
|
|
||||||
*
|
|
||||||
* @param {string} str the input value
|
|
||||||
* @param {boolean} [asString=false] set to true to return the hash value as
|
|
||||||
* 8-digit hex string instead of an integer
|
|
||||||
* @param {integer} [seed] optionally pass the hash of the previous chunk
|
|
||||||
* @returns {integer | string}
|
|
||||||
*/
|
|
||||||
/* eslint-disable */
|
|
||||||
export const hashFNV32a = (str, asString, seed) => {
|
|
||||||
let hval = (seed === undefined) ? 0x811c9dc5 : seed;
|
|
||||||
|
|
||||||
for (let i = 0, l = str.length; i < l; i++) {
|
|
||||||
hval ^= str.charCodeAt(i);
|
|
||||||
hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
|
|
||||||
}
|
|
||||||
if (asString) {
|
|
||||||
return (`0000000${(hval >>> 0).toString(16)}`).substr(-8);
|
|
||||||
}
|
|
||||||
return hval >>> 0;
|
|
||||||
};
|
|
||||||
/* eslint-enable */
|
|
||||||
|
|
||||||
export const extractCredentials = (credentials) => {
|
export const extractCredentials = (credentials) => {
|
||||||
if (!credentials) return {};
|
if (!credentials) return {};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Meteor } from 'meteor/meteor';
|
import { Meteor } from 'meteor/meteor';
|
||||||
import { hashFNV32a } from '/imports/api/common/server/helpers';
|
import { hashSHA1 } from '/imports/api/common/server/helpers';
|
||||||
|
|
||||||
const ETHERPAD = Meteor.settings.private.etherpad;
|
const ETHERPAD = Meteor.settings.private.etherpad;
|
||||||
const NOTE_CONFIG = Meteor.settings.public.note;
|
const NOTE_CONFIG = Meteor.settings.public.note;
|
||||||
@ -12,10 +12,7 @@ const getReadOnlyIdURL = padId => `${BASE_URL}/getReadOnlyID?apikey=${ETHERPAD.a
|
|||||||
|
|
||||||
const appendTextURL = (padId, text) => `${BASE_URL}/appendText?apikey=${ETHERPAD.apikey}&padID=${padId}&text=${encodeURIComponent(text)}`;
|
const appendTextURL = (padId, text) => `${BASE_URL}/appendText?apikey=${ETHERPAD.apikey}&padID=${padId}&text=${encodeURIComponent(text)}`;
|
||||||
|
|
||||||
const generateNoteId = (meetingId) => {
|
const generateNoteId = (meetingId) => hashSHA1(meetingId+ETHERPAD.apikey);
|
||||||
const noteId = hashFNV32a(meetingId, true);
|
|
||||||
return noteId;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isEnabled = () => NOTE_CONFIG.enabled;
|
const isEnabled = () => NOTE_CONFIG.enabled;
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ export default function createNote(meetingId) {
|
|||||||
const noteId = generateNoteId(meetingId);
|
const noteId = generateNoteId(meetingId);
|
||||||
|
|
||||||
const createURL = createPadURL(noteId);
|
const createURL = createPadURL(noteId);
|
||||||
|
|
||||||
axios({
|
axios({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: createURL,
|
url: createURL,
|
||||||
@ -30,6 +31,7 @@ export default function createNote(meetingId) {
|
|||||||
const { status } = responseOuter;
|
const { status } = responseOuter;
|
||||||
if (status !== 200) {
|
if (status !== 200) {
|
||||||
Logger.error(`Could not get note info for ${meetingId} ${status}`);
|
Logger.error(`Could not get note info for ${meetingId} ${status}`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const readOnlyURL = getReadOnlyIdURL(noteId);
|
const readOnlyURL = getReadOnlyIdURL(noteId);
|
||||||
axios({
|
axios({
|
||||||
|
@ -10,11 +10,9 @@ const getLang = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getPadParams = () => {
|
const getPadParams = () => {
|
||||||
const { config } = NOTE_CONFIG;
|
let config = {};
|
||||||
const User = Users.findOne({ userId: Auth.userID }, { fields: { name: 1, color: 1 } });
|
|
||||||
config.userName = User.name;
|
|
||||||
config.userColor = User.color;
|
|
||||||
config.lang = getLang();
|
config.lang = getLang();
|
||||||
|
config.rtl = document.documentElement.getAttribute('dir') === 'rtl';
|
||||||
|
|
||||||
const params = [];
|
const params = [];
|
||||||
Object.keys(config).forEach((k) => {
|
Object.keys(config).forEach((k) => {
|
||||||
@ -26,12 +24,12 @@ const getPadParams = () => {
|
|||||||
|
|
||||||
const getPadURL = (padId, readOnlyPadId, ownerId) => {
|
const getPadURL = (padId, readOnlyPadId, ownerId) => {
|
||||||
const userId = Auth.userID;
|
const userId = Auth.userID;
|
||||||
|
const params = getPadParams();
|
||||||
let url;
|
let url;
|
||||||
if (!ownerId || (ownerId && userId === ownerId)) {
|
if (!ownerId || (ownerId && userId === ownerId)) {
|
||||||
const params = getPadParams();
|
|
||||||
url = Auth.authenticateURL(`${NOTE_CONFIG.url}/p/${padId}?${params}`);
|
url = Auth.authenticateURL(`${NOTE_CONFIG.url}/p/${padId}?${params}`);
|
||||||
} else {
|
} else {
|
||||||
url = Auth.authenticateURL(`${NOTE_CONFIG.url}/p/${readOnlyPadId}`);
|
url = Auth.authenticateURL(`${NOTE_CONFIG.url}/p/${readOnlyPadId}?${params}`);
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@ import { Meteor } from 'meteor/meteor';
|
|||||||
import { Session } from 'meteor/session';
|
import { Session } from 'meteor/session';
|
||||||
|
|
||||||
const CAPTIONS_CONFIG = Meteor.settings.public.captions;
|
const CAPTIONS_CONFIG = Meteor.settings.public.captions;
|
||||||
const CAPTIONS = '_captions_';
|
const CAPTIONS_TOKEN = '_cc_';
|
||||||
const LINE_BREAK = '\n';
|
const LINE_BREAK = '\n';
|
||||||
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
|
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ const getActiveCaptions = () => {
|
|||||||
|
|
||||||
const getCaptions = locale => Captions.findOne({
|
const getCaptions = locale => Captions.findOne({
|
||||||
meetingId: Auth.meetingID,
|
meetingId: Auth.meetingID,
|
||||||
padId: { $regex: `${CAPTIONS}${locale}$` },
|
padId: { $regex: `${CAPTIONS_TOKEN}${locale}$` },
|
||||||
});
|
});
|
||||||
|
|
||||||
const getCaptionsData = () => {
|
const getCaptionsData = () => {
|
||||||
@ -170,6 +170,7 @@ const initSpeechRecognition = (locale) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
CAPTIONS_TOKEN,
|
||||||
getCaptionsData,
|
getCaptionsData,
|
||||||
getAvailableLocales,
|
getAvailableLocales,
|
||||||
getOwnedLocales,
|
getOwnedLocales,
|
||||||
|
@ -24,18 +24,15 @@ const getLang = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getNoteParams = () => {
|
const getNoteParams = () => {
|
||||||
const { config } = NOTE_CONFIG;
|
let config = {};
|
||||||
const User = Users.findOne({ userId: Auth.userID }, { fields: { name: 1, color: 1 } });
|
|
||||||
config.userName = User.name;
|
|
||||||
config.userColor = User.color;
|
|
||||||
config.lang = getLang();
|
config.lang = getLang();
|
||||||
|
config.rtl = document.documentElement.getAttribute('dir') === 'rtl';
|
||||||
|
|
||||||
const params = [];
|
const params = [];
|
||||||
for (const key in config) {
|
Object.keys(config).forEach((k) => {
|
||||||
if (config.hasOwnProperty(key)) {
|
params.push(`${k}=${encodeURIComponent(config[k])}`);
|
||||||
params.push(`${key}=${encodeURIComponent(config[key])}`);
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
return params.join('&');
|
return params.join('&');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -51,7 +48,8 @@ const isLocked = () => {
|
|||||||
|
|
||||||
const getReadOnlyURL = () => {
|
const getReadOnlyURL = () => {
|
||||||
const readOnlyNoteId = getReadOnlyNoteId();
|
const readOnlyNoteId = getReadOnlyNoteId();
|
||||||
const url = Auth.authenticateURL(`${NOTE_CONFIG.url}/p/${readOnlyNoteId}`);
|
const params = getNoteParams();
|
||||||
|
const url = Auth.authenticateURL(`${NOTE_CONFIG.url}/p/${readOnlyNoteId}?${params}`);
|
||||||
return url;
|
return url;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
5
bigbluebutton-html5/package-lock.json
generated
5
bigbluebutton-html5/package-lock.json
generated
@ -1127,6 +1127,11 @@
|
|||||||
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=",
|
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"crypto-js": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg=="
|
||||||
|
},
|
||||||
"css-selector-tokenizer": {
|
"css-selector-tokenizer": {
|
||||||
"version": "0.7.0",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz",
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
"browser-detect": "^0.2.28",
|
"browser-detect": "^0.2.28",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"clipboard": "^2.0.4",
|
"clipboard": "^2.0.4",
|
||||||
|
"crypto-js": "^4.0.0",
|
||||||
"eventemitter2": "~5.0.1",
|
"eventemitter2": "~5.0.1",
|
||||||
"fastdom": "^1.0.9",
|
"fastdom": "^1.0.9",
|
||||||
"fibers": "^3.1.1",
|
"fibers": "^3.1.1",
|
||||||
|
@ -288,12 +288,6 @@ public:
|
|||||||
note:
|
note:
|
||||||
enabled: false
|
enabled: false
|
||||||
url: ETHERPAD_HOST
|
url: ETHERPAD_HOST
|
||||||
config:
|
|
||||||
showLineNumbers: false
|
|
||||||
showChat: false
|
|
||||||
noColors: true
|
|
||||||
showControls: true
|
|
||||||
rtl: false
|
|
||||||
layout:
|
layout:
|
||||||
autoSwapLayout: false
|
autoSwapLayout: false
|
||||||
hidePresentation: false
|
hidePresentation: false
|
||||||
|
@ -22,7 +22,6 @@ source "https://rubygems.org"
|
|||||||
gem "absolute_time"
|
gem "absolute_time"
|
||||||
gem "builder"
|
gem "builder"
|
||||||
gem "fastimage"
|
gem "fastimage"
|
||||||
gem "fnv"
|
|
||||||
gem "java_properties"
|
gem "java_properties"
|
||||||
gem "journald-logger"
|
gem "journald-logger"
|
||||||
gem "jwt"
|
gem "jwt"
|
||||||
|
@ -7,7 +7,6 @@ GEM
|
|||||||
crass (1.0.5)
|
crass (1.0.5)
|
||||||
fastimage (2.1.5)
|
fastimage (2.1.5)
|
||||||
ffi (1.11.1)
|
ffi (1.11.1)
|
||||||
fnv (0.2.0)
|
|
||||||
jaro_winkler (1.5.2)
|
jaro_winkler (1.5.2)
|
||||||
java_properties (0.0.4)
|
java_properties (0.0.4)
|
||||||
journald-logger (2.0.4)
|
journald-logger (2.0.4)
|
||||||
@ -48,7 +47,6 @@ DEPENDENCIES
|
|||||||
absolute_time
|
absolute_time
|
||||||
builder
|
builder
|
||||||
fastimage
|
fastimage
|
||||||
fnv
|
|
||||||
java_properties
|
java_properties
|
||||||
journald-logger
|
journald-logger
|
||||||
jwt
|
jwt
|
||||||
|
@ -36,7 +36,7 @@ require 'logger'
|
|||||||
require 'find'
|
require 'find'
|
||||||
require 'rubygems'
|
require 'rubygems'
|
||||||
require 'net/http'
|
require 'net/http'
|
||||||
require 'fnv'
|
require 'digest'
|
||||||
require 'shellwords'
|
require 'shellwords'
|
||||||
require 'English'
|
require 'English'
|
||||||
|
|
||||||
@ -226,9 +226,10 @@ module BigBlueButton
|
|||||||
r.split("-")[1].to_i / 1000
|
r.split("-")[1].to_i / 1000
|
||||||
end
|
end
|
||||||
|
|
||||||
# Notes id will be an 8-sized hash string based on the meeting id
|
# Notes id will be a SHA1 hash string based on the meeting id and etherpad's apikey
|
||||||
def self.get_notes_id(meeting_id)
|
def self.get_notes_id(meeting_id, notes_apikey)
|
||||||
FNV.new.fnv1a_32(meeting_id).to_s(16).rjust(8, '0')
|
value = meeting_id + notes_apikey
|
||||||
|
Digest::SHA1.hexdigest value
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.done_to_timestamp(r)
|
def self.done_to_timestamp(r)
|
||||||
|
@ -45,11 +45,11 @@ def archive_events(meeting_id, redis_host, redis_port, redis_password, raw_archi
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def archive_notes(meeting_id, notes_endpoint, notes_formats, raw_archive_dir)
|
def archive_notes(meeting_id, notes_endpoint, notes_formats, notes_apikey, raw_archive_dir)
|
||||||
BigBlueButton.logger.info("Archiving notes for #{meeting_id}")
|
BigBlueButton.logger.info("Archiving notes for #{meeting_id}")
|
||||||
notes_dir = "#{raw_archive_dir}/#{meeting_id}/notes"
|
notes_dir = "#{raw_archive_dir}/#{meeting_id}/notes"
|
||||||
FileUtils.mkdir_p(notes_dir)
|
FileUtils.mkdir_p(notes_dir)
|
||||||
notes_id = BigBlueButton.get_notes_id(meeting_id)
|
notes_id = BigBlueButton.get_notes_id(meeting_id, notes_apikey)
|
||||||
|
|
||||||
tmp_note = "#{notes_dir}/tmp_note.txt"
|
tmp_note = "#{notes_dir}/tmp_note.txt"
|
||||||
BigBlueButton.try_download("#{notes_endpoint}/#{notes_id}/export/txt", tmp_note)
|
BigBlueButton.try_download("#{notes_endpoint}/#{notes_id}/export/txt", tmp_note)
|
||||||
@ -180,6 +180,7 @@ kurento_screenshare_dir = props['kurento_screenshare_src']
|
|||||||
log_dir = props['log_dir']
|
log_dir = props['log_dir']
|
||||||
notes_endpoint = props['notes_endpoint']
|
notes_endpoint = props['notes_endpoint']
|
||||||
notes_formats = props['notes_formats']
|
notes_formats = props['notes_formats']
|
||||||
|
notes_apikey = props['notes_apikey']
|
||||||
|
|
||||||
# Determine the filenames for the done and fail files
|
# Determine the filenames for the done and fail files
|
||||||
if !break_timestamp.nil?
|
if !break_timestamp.nil?
|
||||||
@ -198,7 +199,7 @@ archive_events(meeting_id, redis_host, redis_port, redis_password, raw_archive_d
|
|||||||
# FreeSWITCH Audio files
|
# FreeSWITCH Audio files
|
||||||
archive_audio(meeting_id, audio_dir, raw_archive_dir)
|
archive_audio(meeting_id, audio_dir, raw_archive_dir)
|
||||||
# Etherpad notes
|
# Etherpad notes
|
||||||
archive_notes(meeting_id, notes_endpoint, notes_formats, raw_archive_dir)
|
archive_notes(meeting_id, notes_endpoint, notes_formats, notes_apikey, raw_archive_dir)
|
||||||
# Presentation files
|
# Presentation files
|
||||||
archive_directory("#{presentation_dir}/#{meeting_id}/#{meeting_id}", "#{target_dir}/presentation")
|
archive_directory("#{presentation_dir}/#{meeting_id}/#{meeting_id}", "#{target_dir}/presentation")
|
||||||
# Red5 media
|
# Red5 media
|
||||||
|
@ -8,6 +8,7 @@ raw_webrtc_deskshare_src: /usr/share/red5/webapps/video-broadcast/streams
|
|||||||
raw_deskshare_src: /var/bigbluebutton/deskshare
|
raw_deskshare_src: /var/bigbluebutton/deskshare
|
||||||
raw_presentation_src: /var/bigbluebutton
|
raw_presentation_src: /var/bigbluebutton
|
||||||
notes_endpoint: http://localhost:9001/p
|
notes_endpoint: http://localhost:9001/p
|
||||||
|
notes_apikey: ETHERPAD_APIKEY
|
||||||
# Specify the notes formats we archive
|
# Specify the notes formats we archive
|
||||||
# txt, doc and odt are also supported
|
# txt, doc and odt are also supported
|
||||||
notes_formats:
|
notes_formats:
|
||||||
|
@ -24,9 +24,9 @@ require 'logger'
|
|||||||
require 'trollop'
|
require 'trollop'
|
||||||
require 'yaml'
|
require 'yaml'
|
||||||
|
|
||||||
def keep_etherpad_events(meeting_id, events_etherpad, notes_endpoint)
|
def keep_etherpad_events(meeting_id, events_etherpad, notes_endpoint, notes_apikey)
|
||||||
BigBlueButton.logger.info("Keeping etherpad events for #{meeting_id}")
|
BigBlueButton.logger.info("Keeping etherpad events for #{meeting_id}")
|
||||||
notes_id = BigBlueButton.get_notes_id(meeting_id)
|
notes_id = BigBlueButton.get_notes_id(meeting_id, notes_apikey)
|
||||||
|
|
||||||
# Always fetch for the audit format
|
# Always fetch for the audit format
|
||||||
BigBlueButton.try_download("#{notes_endpoint}/#{notes_id}/export/etherpad", events_etherpad)
|
BigBlueButton.try_download("#{notes_endpoint}/#{notes_id}/export/etherpad", events_etherpad)
|
||||||
@ -61,6 +61,7 @@ redis_port = props['redis_port']
|
|||||||
redis_password = props['redis_password']
|
redis_password = props['redis_password']
|
||||||
log_dir = props['log_dir']
|
log_dir = props['log_dir']
|
||||||
notes_endpoint = props['notes_endpoint']
|
notes_endpoint = props['notes_endpoint']
|
||||||
|
notes_apikey = props['notes_apikey']
|
||||||
|
|
||||||
raw_events_xml = "#{raw_archive_dir}/#{meeting_id}/events.xml"
|
raw_events_xml = "#{raw_archive_dir}/#{meeting_id}/events.xml"
|
||||||
ended_done_file = "#{recording_dir}/status/ended/#{meeting_id}.done"
|
ended_done_file = "#{recording_dir}/status/ended/#{meeting_id}.done"
|
||||||
@ -83,7 +84,7 @@ if not FileTest.directory?(target_dir)
|
|||||||
FileUtils.mkdir_p target_dir
|
FileUtils.mkdir_p target_dir
|
||||||
|
|
||||||
events_etherpad = "#{target_dir}/events.etherpad"
|
events_etherpad = "#{target_dir}/events.etherpad"
|
||||||
keep_etherpad_events(meeting_id, events_etherpad, notes_endpoint)
|
keep_etherpad_events(meeting_id, events_etherpad, notes_endpoint, notes_apikey)
|
||||||
|
|
||||||
events_xml = "#{target_dir}/events.xml"
|
events_xml = "#{target_dir}/events.xml"
|
||||||
if File.exist? raw_events_xml
|
if File.exist? raw_events_xml
|
||||||
|
Loading…
Reference in New Issue
Block a user