From 666f2361c827b64182aa3d160da3d1a91dca8ab1 Mon Sep 17 00:00:00 2001 From: Sharon Kennedy Date: Tue, 1 Dec 2020 13:20:13 -0500 Subject: [PATCH] pull in user settings, fix race conditions on facilitator invitation, add more logging and notifications. --- README.md | 26 ++++++-------- sample.config.json | 19 ++++++++++ src/bot.js | 86 ++++++++++++++++++++++++++-------------------- src/setup.js | 2 +- 4 files changed, 80 insertions(+), 53 deletions(-) create mode 100644 sample.config.json diff --git a/README.md b/README.md index 55684bd..c54a618 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,10 @@ # Safe Support Chat Bot -A simple Matrix bot that handles inviting, uninviting, and notifying Riot users on the recieving end of the [Safe Support chatbox](https://github.com/nomadic-labs/safesupport-chatbox). +A simple Matrix bot that handles inviting, uninviting, and notifying Riot users on the recieving end of the [Safe Support chatbox](https://github.com/nomadic-labs/safesupport-chatbox). + +The bot configuration file is `config.json`. It can also pull in user-set configurations from the Safe Support Chat Admin app. To do so, run the command `yarn setup` before starting the bot. -The bot can be configured with an `.env` file with the following variables: -``` -MATRIX_SERVER_URL= -BOT_DISPLAY_NAME= -BOT_USERNAME= -BOT_PASSWORD= -BOT_USERID= -FACILITATOR_ROOM_ID= -CHAT_OFFLINE_MESSAGE= -CAPTURE_TRANSCRIPTS= -``` ## What does the bot do? * The bot receives an invitation to every chatroom created by the embedded chatbox, and automatically accepts * Upon joining a new room, the bot invites all of the members of the Facilitators community @@ -29,7 +20,7 @@ CAPTURE_TRANSCRIPTS= ### Bot commands |Command|Response| ---- | --- +--- | --- |`!bot hi`|Bot responds with a greeting| |`!bot transcript`|Bot sends the chat transcript as a .txt file| |`!bot transcript please`|Bot happily sends the transcript :)| @@ -48,9 +39,14 @@ cd safesupport-bot yarn ``` -Copy the sample `.env` file and add in your own variables +Copy the sample config file and add in the missing values. ``` -cp .env.sample .env +cp sample.config.json config.json +``` + +Pull in the user-defined settings (if there are any). +``` +yarn setup ``` Start the local server diff --git a/sample.config.json b/sample.config.json new file mode 100644 index 0000000..0daad6e --- /dev/null +++ b/sample.config.json @@ -0,0 +1,19 @@ +{ + "matrixServerUrl": "", + "settingsEndpoint": "", + "facilitatorRoomId": "", + "encryptionConfig": { + "algorithm": "m.megolm.v1.aes-sha2" + }, + "kickReason": "A facilitator has already joined this chat.", + "botErrorMessage": "Something went wrong on our end, please restart the chat and try again.", + "botUserId": "", + "botUsername": "", + "botPassword": "", + "botDisplayName": "Help Bot", + "captureTranscripts": true, + "chatNotAvailableMessage": "The support chat is not available right now.", + "chatInactiveMessage": "This chat has been closed due to inactivity.", + "maxWaitTime": 180, + "maxInactiveTime": 3600 +} \ No newline at end of file diff --git a/src/bot.js b/src/bot.js index 40a41b9..f8851f7 100644 --- a/src/bot.js +++ b/src/bot.js @@ -90,14 +90,6 @@ class OcrccBot { } } - inviteUserToRoom(roomId, member) { - try { - this.client.invite(roomId, member) - } catch(err) { - this.handleBotCrash(roomId, err); - } - } - kickUserFromRoom(roomId, member) { try { this.client.kick(roomId, member, this.config.kickReason) @@ -114,9 +106,14 @@ class OcrccBot { (user.presence !== "offline") && memberId !== this.config.botUserId ) { - invitations.push(memberId) - this.inviteUserToRoom(roomId, memberId); - return memberId + try { + this.client.invite(roomId, memberId) + logger.log("info", `CHAT INVITATION SENT TO ${memberId} FOR ROOM ${roomId}`) + return memberId + } catch(err) { + this.handleBotCrash(roomId, err); + return null + } } else { return null } @@ -137,8 +134,15 @@ class OcrccBot { if (invitations.filter(i => i).length > 0) { this.localStorage.setItem(`${roomId}-invitations`, invitations) } else { - logger.log('info', "NO FACILITATORS ONLINE") this.sendBotSignal(roomId, BOT_SIGNAL_CHAT_OFFLINE) + + // send notification to Support Chat Notifications room + const currentDate = new Date() + const closedTime = currentDate.toLocaleTimeString() + const roomRef = roomId.split(':')[0] + const notification = `No facilitators were online, chat closed at ${closedTime} (room ID: ${roomRef})` + logger.log('info', `NO FACILITATORS ONLINE, CHAT CLOSED AT ${closedTime} (room ID: ${roomRef})`) + this.sendTextMessage(this.config.facilitatorRoomId, notification); } } catch(err) { @@ -174,6 +178,7 @@ class OcrccBot { handleBotCrash(roomId, error) { if (roomId) { this.sendTextMessage(roomId, this.config.botErrorMessage); + this.sendBotSignal(roomId, BOT_SIGNAL_END_CHAT) } this.sendTextMessage( @@ -539,32 +544,11 @@ class OcrccBot { member.membership === "leave" && member.userId !== this.config.botUserId ) { - const room = this.client.getRoom(member.roomId) - if (!room) return; - - const roomMembers = await room.getJoinedMembers() // array - const facilitatorRoomMembers = await this.client.getJoinedRoomMembers(this.config.facilitatorRoomId) // object - const isBotInRoom = Boolean(roomMembers.find(member => member.userId === this.config.botUserId)) - - // leave if there is nobody in the room - try { - const memberCount = roomMembers.length - if (memberCount === 1 && isBotInRoom) { // just the bot left - logger.log("info", `LEAVING EMPTY ROOM: ${member.roomId}`); - this.deleteTranscript(member.userId, member.roomId); - this.localStorage.removeItem(`${member.roomId}-facilitator`) - this.localStorage.removeItem(`${member.roomId}-transcript`) - this.localStorage.removeItem(`${member.roomId}-waiting`) - return await this.client.leave(member.roomId) - } - } catch(err) { - return logger.log("error", `ERROR LEAVING EMPTY ROOM ==> ${err}`); - } // notify room if the facilitator has left try { const facilitatorId = this.localStorage.getItem(`${member.roomId}-facilitator`) - if (isBotInRoom && member.userId === facilitatorId) { + if (member.userId === facilitatorId) { this.sendTextMessage( member.roomId, `${member.name} has left the chat.` @@ -580,8 +564,29 @@ class OcrccBot { logger.log("error", `ERROR NOTIFYING THAT FACLITATOR HAS LEFT THE ROOM ==> ${err}`); } + const room = this.client.getRoom(member.roomId) + if (!room) return; + const roomMembers = await room.getJoinedMembers() // array + + // leave if there is nobody in the room + try { + const memberCount = roomMembers.length + const isBotInRoom = Boolean(roomMembers.find(member => member.userId === this.config.botUserId)) + if (memberCount === 1 && isBotInRoom) { // just the bot left + logger.log("info", `LEAVING EMPTY ROOM: ${member.roomId}`); + this.deleteTranscript(member.userId, member.roomId); + this.localStorage.removeItem(`${member.roomId}-facilitator`) + this.localStorage.removeItem(`${member.roomId}-transcript`) + this.localStorage.removeItem(`${member.roomId}-waiting`) + return await this.client.leave(member.roomId) + } + } catch(err) { + return logger.log("error", `ERROR LEAVING EMPTY ROOM ==> ${err}`); + } + // send signal to close the chat if there are no facilitators in the room try { + const facilitatorRoomMembers = await this.client.getJoinedRoomMembers(this.config.facilitatorRoomId) // object const facilitators = facilitatorRoomMembers['joined'] let facilitatorInRoom = false; @@ -607,14 +612,21 @@ class OcrccBot { setTimeout(async() => { const stillWaiting = this.localStorage.getItem(`${roomId}-waiting`) if (stillWaiting) { - logger.log("info", `FACILITATOR DID NOT JOIN CHAT WITHIN TIME LIMIT, SENDING SIGNAL TO END CHAT`); await this.sendTextMessage( roomId, this.config.chatNotAvailableMessage ); this.sendBotSignal(roomId, BOT_SIGNAL_END_CHAT) + + // send notification to Support Chat Notifications room + const currentDate = new Date() + const closedTime = currentDate.toLocaleTimeString() + const roomRef = roomId.split(':')[0] + const notification = `No facilitators joined the chat within the maximum wait time, chat closed at ${closedTime} (room ID: ${roomRef})`; + this.sendTextMessage(this.config.facilitatorRoomId, notification); + logger.log("info", `NO FACILITATORS JOINED THE CHAT WITHIN THE MAXIMUM WAIT TIME, CHAT CLOSED AT ${closedTime} (room ID: ${roomRef})`); } - }, this.config.maxWaitTime) + }, this.config.maxWaitTime * 1000) // convert seconds to milliseconds } setInactivityTimeout(roomId) { @@ -631,7 +643,7 @@ class OcrccBot { this.config.chatInactiveMessage ); this.sendBotSignal(roomId, BOT_SIGNAL_END_CHAT) - }, this.config.maxInactiveTime) + }, this.config.maxInactiveTime * 1000) // convert seconds to milliseconds this.inactivityTimers[roomId] = newTimeout; } diff --git a/src/setup.js b/src/setup.js index 1bac54a..e65d490 100644 --- a/src/setup.js +++ b/src/setup.js @@ -20,7 +20,7 @@ const getSettings = async () => { return Object.entries(fields).reduce(((settingsObj, [k,v]) => { const [scope, key] = k.split('_'); - if (scope === 'platfrom') { + if (scope === 'platform') { settingsObj[key] = v; }