diff --git a/.gitignore b/.gitignore index 4c96c08..ab7bb3a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules .env .idea +run.sh diff --git a/README.md b/README.md index e8d216a..84d2d10 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ At the `.env` file you just copied, set: ``` BIGBLUEBOT_HOST=https://your.bigbluebutton.server ``` - - [optional] room name or meetingID + - [optional] room meetingID ``` BIGBLUEBOT_ROOM=yourbigbluebuttonroomidentifier ``` @@ -30,6 +30,17 @@ If you would like the bots to join an existing room, you may fill out the password variables and use the `meetingID` value as `BIGBLUEBOT_ROOM` To find out these data, you may call the `getMeetings` route of your BBB instance as described [here](https://docs.bigbluebutton.org/dev/api.html#getmeetings). + +Attention: this is NOT the room ID of a room made bei Greenlight! If you want to join an existing room +(this is good and impressive for watching the test in headless mode), use the next option. + - [optional] room name +``` +BIGBLUEBOT_NAME=yourbigbluebuttonroomname +``` +If you want to join a preexisting room, then use this option. YOU HAVE TO PROVIDE the serverAPI secret +to work with this. When you give the (hopefully) unique room name and the server secret then all +other credentials are extracted from the `getMeetings` route of the BBB instance. +Here you can join any room only by knowing the `meetingName` like in getMeetings answer. - [optional] the `attendeePW` and `moderatorPW` as shown in getMeetings ``` BIGBLUEBOT_ATTENDEE_PW=yourattendeepassword diff --git a/config/config.json b/config/config.json index 09b536e..66574e4 100644 --- a/config/config.json +++ b/config/config.json @@ -12,7 +12,8 @@ }, "meeting": { "param": "meetingname", - "name": "Demo Meeting" + "room": "Demo Meeting", + "name": "This is Demo Meeting Room" } }, "api": { @@ -71,7 +72,7 @@ "type": 100 }, "timeout": { - "selector": 60000 + "selector": 1800000 }, "logger": { "level": "info" diff --git a/lib/api.js b/lib/api.js index c44d2c6..ed68e0d 100644 --- a/lib/api.js +++ b/lib/api.js @@ -1,5 +1,6 @@ const sha1 = require('crypto-js/sha1'); const conf = require('./conf'); +const logger = require('./logger'); const { config } = conf; @@ -35,6 +36,7 @@ const getURL = (action, params, options) => { const api = config.api.path; const url = `${host}/${api}/${action}?${query}&checksum=${checksum}`; + logger.debug(url); return url; }; @@ -47,7 +49,7 @@ const calculateChecksum = (action, query, options) => { const getCreateURL = (options) => { const params = { - meetingID: options.room || config.url.meeting.name, + meetingID: options.room || config.url.meeting.room, record: true, moderatorPW: getPassword('moderator', options), attendeePW: getPassword('attendee', options), @@ -58,7 +60,7 @@ const getCreateURL = (options) => { const getEndURL = (options) => { const params = { - meetingID: options.room || config.url.meeting.name, + meetingID: options.room || config.url.meeting.room, password: getPassword('moderator', options), }; @@ -72,7 +74,7 @@ const getJoinURL = (username, options) => { } const params = { - meetingID: options.room || config.url.meeting.name, + meetingID: options.room || config.url.meeting.room, fullName: username, password: password, }; @@ -80,8 +82,13 @@ const getJoinURL = (username, options) => { return getURL('join', params, options); }; +const getMeetingsURL = (options) => { + return getURL('getMeetings', {}, options); +}; + module.exports = { getCreateURL, getEndURL, getJoinURL, + getMeetingsURL, }; diff --git a/lib/conf.js b/lib/conf.js index 8cb1f67..b5fde40 100644 --- a/lib/conf.js +++ b/lib/conf.js @@ -9,6 +9,7 @@ const { BIGBLUEBOT_HOST, BIGBLUEBOT_SECRET, BIGBLUEBOT_ROOM, + BIGBLUEBOT_NAME, BIGBLUEBOT_ATTENDEE_PW, BIGBLUEBOT_MODERATOR_PW, BIGBLUEBOT_BOTS, @@ -24,7 +25,8 @@ const { } = process.env; if (BIGBLUEBOT_HOST) custom.url.host = BIGBLUEBOT_HOST; -if (BIGBLUEBOT_ROOM) custom.url.meeting.name = BIGBLUEBOT_ROOM; +if (BIGBLUEBOT_ROOM) custom.url.meeting.room = BIGBLUEBOT_ROOM; +if (BIGBLUEBOT_NAME) custom.url.meeting.name = BIGBLUEBOT_NAME; if (BIGBLUEBOT_SECRET) custom.api.secret = BIGBLUEBOT_SECRET; if (BIGBLUEBOT_BOTS) custom.bot.population = parseInt(BIGBLUEBOT_BOTS); if (BIGBLUEBOT_WAIT) custom.bot.wait = parseInt(BIGBLUEBOT_WAIT); diff --git a/lib/pool.js b/lib/pool.js index dc06469..c7590a8 100644 --- a/lib/pool.js +++ b/lib/pool.js @@ -4,6 +4,8 @@ const conf = require('./conf'); const { browser, bot, data } = conf.config; +require('events').EventEmitter.defaultMaxListeners = 0 + const args = [ `--lang=${browser.lang}`, `--disable-dev-shm-usage`, @@ -23,6 +25,7 @@ const factory = { } else { return await puppeteer.launch({ headless, + ignoreHTTPSErrors: true, executablePath: path, args, }); diff --git a/lib/run.js b/lib/run.js index 175150c..bf328c1 100644 --- a/lib/run.js +++ b/lib/run.js @@ -3,7 +3,7 @@ const pool = require('./pool'); const conf = require('./conf'); const logger = require('./logger'); -const { api, bot, url, misc } = conf.config; +var { api, bot, url, misc } = conf.config; const dependencies = (options) => { if (!url.host && !options.host) { @@ -22,12 +22,28 @@ const run = async (actions, options = {}) => { logger.info(`Life: ${bot.life / 1000} seconds`); // Assume a private room if the server secret was configured - const private = api.secret || options.secret; + const privileged = options.secret || api.secret; - // Create the meeting if not created yet - if (private) { - const success = await util.create(options); - if (!success) return null; + // Create the meeting if not created yet or use a preexisting one + if (privileged) { + let meet; + // let's see if we can dig out missing information when we ask the server via 'getMeeting' + // first we look for a named meeting + if (url.meeting.name) { + meet = await util.findMeetingByName(url.meeting.name, options); + if (meet) { + url.meeting.room = meet.meetingID; + api.password.moderator = meet.moderatorPW; + api.password.attendee = meet.attendeePW; + } + } + + // ok this might be inefficient if a name was provided, but who cares + meet = await util.findMeetingByID(url.meeting.room, options); + if (!meet) { + const success = await util.create(options); + if (!success) return null; + } } // Fetch the UI labels from locale diff --git a/lib/util.js b/lib/util.js index 8ec86d0..f486e03 100644 --- a/lib/util.js +++ b/lib/util.js @@ -3,6 +3,7 @@ const faker = require('faker'); const conf = require('./conf'); const logger = require('./logger'); const api = require('./api'); +const convert = require('xml-js'); const { config } = conf; const { timeout } = config; @@ -14,7 +15,7 @@ const random = collection => collection[Math.floor(Math.random() * collection.le const getDemoJoinURL = (username, options) => { const user = `${config.url.user.param}=${encodeURI(username)}`; const moderator = `${config.url.moderator.param}=${options.moderator !== undefined ? options.moderator : config.url.moderator.value}`; - const meeting = `${config.url.meeting.param}=${encodeURI(options.room || config.url.meeting.name)}`; + const meeting = `${config.url.meeting.param}=${encodeURI(options.room || config.url.meeting.room)}`; return `${options.host || config.url.host}/${config.url.demo}&${user}&${moderator}&${meeting}`; }; @@ -29,6 +30,84 @@ const url = (username, options) => { return getDemoJoinURL(username, options); }; +// https://github.com/nashwaan/xml-js/issues/53 +function nativeType(value) { + var nValue = Number(value); + if (!isNaN(nValue)) { + return nValue; + } + var bValue = value.toLowerCase(); + if (bValue === 'true') { + return true; + } else if (bValue === 'false') { + return false; + } + return value; +} + +const removeJsonTextAttribute = function(value, parentElement) { + try { + const parentOfParent = parentElement._parent; + const pOpKeys = Object.keys(parentElement._parent); + const keyNo = pOpKeys.length; + const keyName = pOpKeys[keyNo - 1]; + const arrOfKey = parentElement._parent[keyName]; + const arrOfKeyLen = arrOfKey.length; + if (arrOfKeyLen > 0) { + const arr = arrOfKey; + const arrIndex = arrOfKey.length - 1; + arr[arrIndex] = nativeType(value); + } else { + parentElement._parent[keyName] = nativeType(value); + } + } catch (e) {} +}; + +const meetings = async (options) => { + if (options.secret || config.api.secret) { + const url = api.getMeetingsURL(options); + + let meets; + await axios.get(url).then(response => { + meets = convert.xml2js(response.data, { compact: true, nativeType: false, textFn: removeJsonTextAttribute }); + }).catch(error => { + logger.error(error); + }); + logger.debug(meets.response.meetings.meeting); + return [].concat(meets.response.meetings.meeting); + } else { + logger.info('Set BBB-secret to get the meetings') + } + + return []; +}; + +const findMeetingByName = async (name, options) => { + let ret; + let all = await meetings(options); + all.forEach(function (item) { + if (item.meetingName && item.meetingName === name) { + logger.info("Found meeting by name: " + name) + ret = item; + } + }); + + return ret; +}; + +const findMeetingByID = async (id, options) => { + let ret; + let all = await meetings(options); + all.forEach(function (item) { + if (item.meetingID && item.meetingID === id) { + logger.info("Found meeting by ID: " + id) + ret = item; + } + }); + + return ret; +}; + const once = async (page, event, callback) => { return new Promise((resolve, reject) => { let fired = false; @@ -137,9 +216,12 @@ module.exports = { random, generateText, localize, + meetings, + findMeetingByName, + findMeetingByID, join: async (page, locale, options) => { const username = generateUsername(); - logger.info(`${username}: join ${options.host || config.url.host} at ${options.room || config.url.meeting.name}`); + logger.info(`${username}: join ${options.host || config.url.host} at ${options.room || config.url.meeting.room}`); const { width, height } = config.browser.window; await page.setViewport({ width, height }); await page.goto(url(username, options)); diff --git a/package-lock.json b/package-lock.json index 8ae7dee..bb4a425 100644 --- a/package-lock.json +++ b/package-lock.json @@ -362,6 +362,11 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -422,6 +427,14 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==" }, + "xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "requires": { + "sax": "^1.2.4" + } + }, "yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", diff --git a/package.json b/package.json index e8086c8..66bc2d8 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "dotenv": "^8.2.0", "faker": "^5.1.0", "generic-pool": "^3.7.1", - "puppeteer": "^5.5.0" + "puppeteer": "^5.5.0", + "xml-js": "^1.6.11" }, "author": "Pedro Beschorner Marin ", "license": "MIT",