From aef215bd7e1816e68d8e9d44a8f7461ffe7842d7 Mon Sep 17 00:00:00 2001 From: Sharon Kennedy Date: Wed, 22 Apr 2020 18:04:48 -0400 Subject: [PATCH] leave rooms when empty, use localStorage --- dist/bot.js | 796 +++++++++--------- dist/bot.test.js | 253 ++++++ dist/index.js | 38 +- dist/logger.js | 20 +- src/bot.js | 43 +- ...21 PM - !jjfNCYPkwvRztgyxNp:rhok.space.txt | 5 + ...18 PM - !NLmOYIXXomGSNLqygi:rhok.space.txt | 5 + ...04 PM - !BzdphjbeKrimzIjFtf:rhok.space.txt | 6 + ...09 PM - !IsZMQlwOgpsrfZdBQo:rhok.space.txt | 12 + ...24 PM - !jPVeDJGfmrioMshdUY:rhok.space.txt | 5 + ...45 PM - !PNGecrkpokGRVOsMaU:rhok.space.txt | 3 + ...37 PM - !hyWgcgjiKgANPxXwnl:rhok.space.txt | 5 + ...09 PM - !fEoMiYgeAOibhYlqXo:rhok.space.txt | 5 + ...23 PM - !OQZSgsxNUWMLzImfBH:rhok.space.txt | 2 + 14 files changed, 758 insertions(+), 440 deletions(-) create mode 100644 dist/bot.test.js create mode 100644 transcripts/Apr 22, 2020 - 1:00:21 PM - !jjfNCYPkwvRztgyxNp:rhok.space.txt create mode 100644 transcripts/Apr 22, 2020 - 1:02:18 PM - !NLmOYIXXomGSNLqygi:rhok.space.txt create mode 100644 transcripts/Apr 22, 2020 - 4:22:04 PM - !BzdphjbeKrimzIjFtf:rhok.space.txt create mode 100644 transcripts/Apr 22, 2020 - 4:23:09 PM - !IsZMQlwOgpsrfZdBQo:rhok.space.txt create mode 100644 transcripts/Apr 22, 2020 - 4:26:24 PM - !jPVeDJGfmrioMshdUY:rhok.space.txt create mode 100644 transcripts/Apr 22, 2020 - 4:33:45 PM - !PNGecrkpokGRVOsMaU:rhok.space.txt create mode 100644 transcripts/Apr 22, 2020 - 4:50:37 PM - !hyWgcgjiKgANPxXwnl:rhok.space.txt create mode 100644 transcripts/Apr 22, 2020 - 4:58:09 PM - !fEoMiYgeAOibhYlqXo:rhok.space.txt create mode 100644 transcripts/Apr 22, 2020 - 5:28:23 PM - !OQZSgsxNUWMLzImfBH:rhok.space.txt diff --git a/dist/bot.js b/dist/bot.js index 3ab0219..ff50669 100644 --- a/dist/bot.js +++ b/dist/bot.js @@ -1,11 +1,13 @@ "use strict"; -function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); Object.defineProperty(exports, "__esModule", { value: true }); -exports["default"] = void 0; +exports.default = void 0; var fs = _interopRequireWildcard(require("fs")); @@ -21,448 +23,436 @@ var matrix = _interopRequireWildcard(require("matrix-js-sdk")); var _logger = _interopRequireDefault(require("./logger")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - -function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; } - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } - -function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } - global.Olm = require("olm"); -var ENCRYPTION_CONFIG = { - algorithm: "m.megolm.v1.aes-sha2" -}; -var KICK_REASON = "A facilitator has already joined this chat."; -var BOT_ERROR_MESSAGE = "Something went wrong on our end, please restart the chat and try again."; -var MAX_RETRIES = 3; - -var OcrccBot = -/*#__PURE__*/ -function () { - function OcrccBot() { - _classCallCheck(this, OcrccBot); +class OcrccBot { + constructor(botConfig) { + this.config = botConfig; this.awaitingFacilitator = {}; - this.client = matrix.createClient(process.env.MATRIX_SERVER_URL); + this.client = matrix.createClient(this.config.MATRIX_SERVER_URL); this.joinedRooms = []; - this.activeChatrooms = {}; } - _createClass(OcrccBot, [{ - key: "createLocalStorage", - value: function createLocalStorage() { - var storageLoc = "matrix-chatbot-".concat(process.env.BOT_USERNAME); - var dir = path.resolve(path.join(os.homedir(), ".local-storage")); + createLocalStorage() { + const storageLoc = `matrix-chatbot-${this.config.BOT_USERNAME}`; + const dir = path.resolve(path.join(os.homedir(), ".local-storage")); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } - - var localStoragePath = path.resolve(path.join(dir, storageLoc)); - return new _nodeLocalstorage.LocalStorage(localStoragePath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); } - }, { - key: "sendMessage", - value: function sendMessage(roomId, msgText) { - var _this = this; - return this.client.sendTextMessage(roomId, msgText)["catch"](function (err) { - switch (err["name"]) { - case "UnknownDeviceError": - Object.keys(err.devices).forEach(function (userId) { - Object.keys(err.devices[userId]).map(function (deviceId) { - _this.client.setDeviceVerified(userId, deviceId, true); - }); + const localStoragePath = path.resolve(path.join(dir, storageLoc)); + return new _nodeLocalstorage.LocalStorage(localStoragePath); + } + + sendTextMessage(roomId, msgText, showToUser = null) { + const content = { + msgtype: "m.text", + body: msgText, + showToUser: showToUser + }; + this.sendMessage(roomId, content); + } + + async sendMessage(roomId, content) { + _logger.default.log("info", `SENDING MESSAGE: ${content.body}`); + + try { + await this.client.sendMessage(roomId, content); + } catch (err) { + switch (err["name"]) { + case "UnknownDeviceError": + Object.keys(err.devices).forEach(userId => { + Object.keys(err.devices[userId]).map(async deviceId => { + try { + await this.client.setDeviceVerified(userId, deviceId, true); + } catch (err) { + _logger.default.log("error", `ERROR VERIFYING DEVICE: ${err}`); + } }); - return _this.sendMessage(roomId, msgText); - break; - - default: - _logger["default"].log("error", "ERROR SENDING MESSAGE: ".concat(err)); - - _this.handleBotCrash(roomId, err); - - break; - } - }); - } - }, { - key: "inviteUserToRoom", - value: function inviteUserToRoom(client, roomId, member) { - var _this2 = this; - - var retries = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; - - _logger["default"].log("info", "INVITING MEMBER: " + member); - - if (retries > MAX_RETRIES) { - this.handleBotCrash(roomId, "Rate limit exceeded for bot account"); - return _logger["default"].log("error", "RATE LIMIT EXCEEDED AND RETRY LIMIT EXCEEDED"); - } - - return client.invite(roomId, member)["catch"](function (err) { - switch (err["name"]) { - case "M_LIMIT_EXCEEDED": - _logger["default"].log("info", "Rate limit exceeded, retrying."); - - var retryCount = retries + 1; - var delay = retryCount * 2 * 1000; - return setTimeout(_this2.inviteUserToRoom, delay, client, roomId, member, retryCount); - break; - - default: - _logger["default"].log("error", "ERROR INVITING MEMBER: ".concat(err)); - - _this2.handleBotCrash(roomId, err); - - break; - } - }); - } - }, { - key: "kickUserFromRoom", - value: function kickUserFromRoom(client, roomId, member) { - var _this3 = this; - - var retries = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; - - _logger["default"].log("info", "KICKING OUT MEMBER: " + member); - - if (retries > MAX_RETRIES) { - this.handleBotCrash(roomId, "Rate limit exceeded for bot account."); - return _logger["default"].log("error", "RATE LIMIT EXCEEDED AND RETRY LIMIT EXCEEDED"); - } - - return client.kick(roomId, member, KICK_REASON)["catch"](function (err) { - switch (err["name"]) { - case "M_LIMIT_EXCEEDED": - _logger["default"].log("info", "Rate limit exceeded, retrying."); - - var retryCount = retries + 1; - var delay = retryCount * 2 * 1000; - return setTimeout(_this3.kickUserFromRoom, delay, client, roomId, member, retryCount); - break; - - default: - _this3.handleBotCrash(roomId, err); - - _logger["default"].log("error", "ERROR KICKING OUT MEMBER: ".concat(err)); - - break; - } - }); - } - }, { - key: "inviteFacilitators", - value: function inviteFacilitators(roomId) { - var _this4 = this; - - this.awaitingFacilitator[roomId] = true; - var chatOffline = true; - this.client.getJoinedRoomMembers(process.env.FACILITATOR_ROOM_ID).then(function (members) { - var onlineMembersCount = 0; - Object.keys(members["joined"]).forEach(function (member) { - var user = _this4.client.getUser(member); - - if (user.presence === "online" && member !== process.env.BOT_USERID) { - chatOffline = false; - - _this4.inviteUserToRoom(_this4.client, roomId, member); - } - }); - }).then(function () { - if (chatOffline) { - _this4.sendMessage(roomId, process.env.CHAT_OFFLINE_MESSAGE); - } - })["catch"](function (err) { - _this4.handleBotCrash(roomId, err); - - _logger["default"].log("error", "ERROR GETTING ROOM MEMBERS: ".concat(err)); - }); - } - }, { - key: "uninviteFacilitators", - value: function uninviteFacilitators(roomId) { - var _this5 = this; - - this.awaitingFacilitator[roomId] = false; - this.client.getJoinedRoomMembers(process.env.FACILITATOR_ROOM_ID).then(function (allFacilitators) { - _this5.client.getJoinedRoomMembers(roomId).then(function (roomMembers) { - var membersIds = Object.keys(roomMembers["joined"]); - var facilitatorsIds = Object.keys(allFacilitators["joined"]); - facilitatorsIds.forEach(function (f) { - if (!membersIds.includes(f)) { - _this5.kickUserFromRoom(_this5.client, roomId, f); - } }); - }); - })["catch"](function (err) { - _this5.handleBotCrash(roomId, err); + await this.sendMessage(roomId, content); - _logger["default"].log("error", err); + default: + _logger.default.log("error", `ERROR SENDING MESSAGE: ${err}`); + + break; + } + } + } + + 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.KICK_REASON); + } catch (err) { + this.handleBotCrash(roomId, err); + + _logger.default.log("error", `ERROR KICKING OUT MEMBER: ${err}`); + } + } + + async inviteFacilitators(roomId) { + this.localStorage.setItem(`${roomId}-waiting`, 'true'); + let chatOffline = true; + + try { + const data = await this.client.getGroupUsers(this.config.FACILITATOR_GROUP_ID); + const members = data.chunk; + members.forEach(member => { + const memberId = member.user_id; + const user = this.client.getUser(memberId); + + if (user && user.presence === "online" && memberId !== this.config.BOT_USERID) { + chatOffline = false; + this.inviteUserToRoom(roomId, memberId); + } }); - } - }, { - key: "handleBotCrash", - value: function handleBotCrash(roomId, error) { - if (roomId) { - this.sendMessage(roomId, BOT_ERROR_MESSAGE); + + if (chatOffline) { + _logger.default.log('info', "NO FACILITATORS ONLINE"); + + this.sendTextMessage(roomId, this.config.CHAT_OFFLINE_MESSAGE); } + } catch (err) { + this.handleBotCrash(roomId, err); - this.sendMessage(process.env.FACILITATOR_ROOM_ID, "The Help Bot ran into an error: ".concat(error, ". Please verify that the chat service is working.")); + _logger.default.log("error", `ERROR GETTING FACILITATORS: ${err}`); } - }, { - key: "writeToTranscript", - value: function writeToTranscript(event) { - try { - var sender = event.getSender(); - var roomId = event.getRoomId(); - var content = event.getContent(); - var date = event.getDate(); - var time = date.toLocaleTimeString("en-GB", { - timeZone: "America/New_York" - }); - var filepath = this.activeChatrooms[roomId].transcriptFile; + } - if (!content) { - return; + async uninviteFacilitators(roomId) { + this.localStorage.removeItem(`${roomId}-waiting`); + + try { + const groupUsers = await this.client.getGroupUsers(this.config.FACILITATOR_GROUP_ID); + const roomMembers = await this.client.getJoinedRoomMembers(roomId); + const membersIds = Object.keys(roomMembers["joined"]); + const facilitatorsIds = groupUsers.chunk.map(f => f.user_id); + facilitatorsIds.forEach(f => { + if (!membersIds.includes(f)) { + this.kickUserFromRoom(roomId, f); } + }); + } catch (err) { + this.handleBotCrash(roomId, err); - var message = "".concat(sender, " [").concat(time, "]: ").concat(content.body, "\n"); - fs.appendFileSync(filepath, message, "utf8"); - } catch (err) { - _logger["default"].log("error", "ERROR APPENDING TO TRANSCRIPT FILE: ".concat(err)); + _logger.default.log("ERROR UNINVITING FACILITATORS", err); + } + } + + handleBotCrash(roomId, error) { + if (roomId) { + this.sendTextMessage(roomId, this.config.BOT_ERROR_MESSAGE); + } + + this.sendTextMessage(this.config.FACILITATOR_ROOM_ID, `The Help Bot ran into an error: ${error}. Please verify that the chat service is working.`); + } + + handleMessageEvent(event) { + const content = event.getContent(); // do nothing if there's no content + + if (!content) { + return; + } // bot commands + + + if (content.body.startsWith("!bot")) { + return this.handleBotCommand(event); + } // write to transcript + + + if (this.config.CAPTURE_TRANSCRIPTS) { + return this.writeToTranscript(event); + } + } + + writeToTranscript(event) { + try { + const sender = event.getSender(); + const roomId = event.getRoomId(); + const content = event.getContent(); + const date = event.getDate(); + const time = date.toLocaleTimeString("en-GB", { + timeZone: "America/New_York" + }); + const filepath = this.localStorage.getItem(`${roomId}-transcript`); + const message = `${sender} [${time}]: ${content.body}\n`; + fs.appendFileSync(filepath, message, "utf8"); + } catch (err) { + _logger.default.log("error", `ERROR APPENDING TO TRANSCRIPT FILE: ${err}`); + } + } + + handleBotCommand(event) { + try { + const senderId = event.getSender(); + const roomId = event.getRoomId(); + const content = event.getContent(); + const command = content.body.substring("!bot".length).trim(); + + switch (command) { + case "transcript": + this.sendTranscript(senderId, roomId); + break; + + case "transcript please": + this.sendTranscript(senderId, roomId); + break; + + case "hi": + const responses = ["Hi!", "Hello", "Hey :)", "Hi there", "Bleep bloop"]; + const message = responses[Math.floor(Math.random() * responses.length)]; + this.sendTextMessage(roomId, message, senderId); + break; + + default: + this.sendTextMessage(roomId, `Sorry, I don't know that command. I'm not a very smart bot.`, senderId); + break; } + } catch (err) { + _logger.default.log("error", `ERROR EXECUTING BOT COMMAND: ${err}`); } - }, { - key: "deleteOldDevices", - value: function deleteOldDevices() { - var _this6 = this; + } - var doDelete = function doDelete(oldDevices) { - var auth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; - var retries = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; + async leaveEmptyRooms(senderId) { + try { + const roomData = await this.client.getJoinedRooms(); + const joinedRoomsIds = roomData["joined_rooms"]; + joinedRoomsIds.forEach(async roomId => { + const room = this.client.getRoom(roomId); - if (retries > MAX_RETRIES) { - throw new Error("Exceeded max retries deleting old devices"); - } + if (room && room.getJoinedMemberCount() === 1) { + try { + _logger.default.log('info', "LEAVING EMPTY ROOM => " + roomId); - _logger["default"].log("info", "ATTEMPTING TO DELETE OLD DEVICES: ".concat(oldDevices)); - - _this6.client.deleteMultipleDevices(oldDevices, auth).then(function () { - return _logger["default"].log("info", "DELETED OLD DEVICES"); - })["catch"](function (err) { - if (err['errcode'] === undefined && err['data']) { - var _auth = { - session: err.data.session, - type: "m.login.password", - user: process.env.BOT_USERID, - identifier: { - type: "m.id.user", - user: process.env.BOT_USERID - }, - password: process.env.BOT_PASSWORD - }; - doDelete(oldDevices, _auth); - } else if (err['errcode'] === 'M_LIMIT_EXCEEDED') { - var retryCount = retries + 1; - var delay = err['retry_after_ms'] ? err['retry_after_ms'] : retryCount * 1000; - - _logger["default"].log("error", "RATE LIMIT EXCEEDED, RETRYING IN ".concat(delay, " MS")); - - setTimeout(function () { - doDelete(oldDevices, auth, retryCount); - }, delay); - } else { - _logger["default"].log("error", "ERROR DELETING OLD DEVICES ON RETRY ".concat(retries, ": ").concat(JSON.stringify(err))); - - doDelete(oldDevices, auth, retries + 1); + await this.client.leave(roomId); + } catch (err) { + _logger.default.log('error', "ERROR LEAVING EMPTY ROOM => " + err); } - }); + } + }); + } catch (err) { + _logger.default.log("error", `ERROR GETTING JOINED ROOMS: ${err}`); + } + } + + async sendTranscript(senderId, roomId) { + try { + const transcriptFile = this.localStorage.getItem(`${roomId}-transcript`); + + if (!transcriptFile) { + this.sendTextMessage(roomId, "There is no transcript for this chat.", senderId); + } + + const filename = path.basename(transcriptFile) || "Transcript"; + const stream = fs.createReadStream(transcriptFile); + const contentUrl = await this.client.uploadContent({ + stream: stream, + name: filename + }); + const content = { + msgtype: "m.file", + body: filename, + url: JSON.parse(contentUrl).content_uri, + showToUser: senderId }; + this.sendMessage(roomId, content); + } catch (err) { + _logger.default.log("error", `ERROR UPLOADING CONTENT: ${err}`); - return this.client.getDevices().then(function (data) { - var currentDeviceId = _this6.client.getDeviceId(); + this.sendTextMessage(roomId, "There was an error uploading the transcript.", senderId); + } + } - var allDeviceIds = data.devices.map(function (d) { - return d.device_id; - }); - var oldDevices = allDeviceIds.filter(function (id) { - return id !== currentDeviceId; - }); - doDelete(oldDevices); - })["catch"](function (err) { - _this6.handleBotCrash(undefined, err); + async deleteOldDevices() { + const currentDeviceId = this.client.getDeviceId(); + const deviceData = await this.client.getDevices(); + const allDeviceIds = deviceData.devices.map(d => d.device_id); + const oldDevices = allDeviceIds.filter(id => id !== currentDeviceId); - _logger["default"].log("error", "ERROR DELETING OLD DEVICES: ".concat(JSON.stringify(err.data))); - }); - } // deleteOldDevices() { - // return this.client.getDevices().then(data => { - // const currentDeviceId = this.client.getDeviceId(); - // const allDeviceIds = data.devices.map(d => d.device_id); - // const oldDevices = allDeviceIds.filter(id => id !== currentDeviceId); - // logger.log("info", `DELETING OLD DEVICES: ${oldDevices}`); - // this.client - // .deleteMultipleDevices(oldDevices) - // .catch(err => { - // const auth = { - // session: err.data.session, - // type: "m.login.password", - // user: process.env.BOT_USERID, - // identifier: { type: "m.id.user", user: process.env.BOT_USERID }, - // password: process.env.BOT_PASSWORD - // }; - // this.client - // .deleteMultipleDevices(oldDevices, auth) - // .then(() => logger.log("info", "DELETED OLD DEVICES")) - // .catch(err => - // logger.log( - // "error", - // `ERROR DELETING OLD DEVICES: ${JSON.stringify(err.data)}` - // ) - // ); - // }); - // }); - // } + try { + await this.client.deleteMultipleDevices(oldDevices); + } catch (err) { + _logger.default.log("info", "RETRYING DELETE OLD DEVICES WITH AUTH"); - }, { - key: "setMembershipListeners", - value: function setMembershipListeners() { - var _this7 = this; + const auth = { + session: err.data.session, + type: "m.login.password", + user: this.config.BOT_USERID, + identifier: { + type: "m.id.user", + user: this.config.BOT_USERID + }, + password: this.config.BOT_PASSWORD + }; + await this.client.deleteMultipleDevices(oldDevices, auth); - // Automatically accept all room invitations - return this.client.on("RoomMember.membership", function (event, member) { - if (member.membership === "invite" && member.userId === process.env.BOT_USERID && !_this7.joinedRooms.includes(member.roomId)) { - _logger["default"].log("info", "Auto-joining room " + member.roomId); + _logger.default.log("info", "DELETED OLD DEVICES"); + } + } - _this7.client.joinRoom(member.roomId).then(function (room) { - _this7.sendMessage(process.env.FACILITATOR_ROOM_ID, "A support seeker requested a chat (Room ID: ".concat(member.roomId, ")")); - }).then(function () { - return _this7.inviteFacilitators(member.roomId); - })["catch"](function (err) { - _logger["default"].log("error", err); - }); - } // When a facilitator joins a support session, revoke the other invitations + async trackJoinedRooms() { + const roomData = await this.client.getJoinedRooms(); + this.joinedRooms = roomData["joined_rooms"]; + + _logger.default.log("info", "JOINED ROOMS => " + this.joinedRooms); + } + + async setMembershipListeners() { + // Automatically accept all room invitations + this.client.on("RoomMember.membership", async (event, member) => { + if (member.membership === "invite" && member.userId === this.config.BOT_USERID && !this.joinedRooms.includes(member.roomId)) { + try { + const room = await this.client.joinRoom(member.roomId); + + _logger.default.log("info", "AUTO JOINED ROOM => " + room.roomId); + + this.sendTextMessage(this.config.FACILITATOR_ROOM_ID, `A support seeker requested a chat (Room ID: ${room.roomId})`); + this.inviteFacilitators(room.roomId); + } catch (err) { + _logger.default.log("error", "ERROR JOINING ROOM => " + err); + } + } // When a facilitator joins a support session, make them a moderator + // revoke the other invitations - if (member.membership === "join" && member.userId !== process.env.BOT_USERID && _this7.awaitingFacilitator[member.roomId]) { - _this7.activeChatrooms[member.roomId] = { - facilitator: member.userId - }; - - _this7.sendMessage(member.roomId, "".concat(member.name, " has joined the chat.")); - - _this7.sendMessage(process.env.FACILITATOR_ROOM_ID, "".concat(member.name, " joined the chat (Room ID: ").concat(member.roomId, ")")); - - _this7.uninviteFacilitators(member.roomId); - - if (process.env.CAPTURE_TRANSCRIPTS) { - var currentDate = new Date(); - var dateOpts = { - year: "numeric", - month: "short", - day: "numeric" + if (member.membership === "join" && member.userId !== this.config.BOT_USERID && this.localStorage.getItem(`${member.roomId}-waiting`)) { + this.localStorage.setItem(`${member.roomId}-facilitator`, member.userId); + const event = { + getType: () => { + return "m.room.power_levels"; + }, + getContent: () => { + return { + users: { + [this.config.BOT_USERID]: 100, + [member.userId]: 50 + } }; - var chatDate = currentDate.toLocaleDateString("en-GB", dateOpts); - var chatTime = currentDate.toLocaleTimeString("en-GB", { - timeZone: "America/New_York" - }); - var filename = "".concat(chatDate, " - ").concat(chatTime, " - ").concat(member.roomId, ".txt"); - var filepath = path.resolve(path.join("transcripts", filename)); - _this7.activeChatrooms[member.roomId].transcriptFile = filepath; } - } - - if (member.membership === "leave" && member.userId !== process.env.BOT_USERID && _this7.activeChatrooms[member.roomId] && member.userId === _this7.activeChatrooms[member.roomId].facilitator) { - _this7.sendMessage(member.roomId, "".concat(member.name, " has left the chat.")); - } - }); - } - }, { - key: "setMessageListeners", - value: function setMessageListeners() { - var _this8 = this; - - // encrypted messages - this.client.on("Event.decrypted", function (event, err) { - if (err) { - return _logger["default"].log("error", "ERROR DECRYPTING EVENT: ".concat(err)); - } - - if (event.getType() === "m.room.message") { - _this8.writeToTranscript(event); - } - }); // unencrypted messages - - this.client.on("Room.timeline", function (event, room, toStartOfTimeline) { - if (event.getType() === "m.room.message" && !_this8.client.isCryptoEnabled()) { - if (event.isEncrypted()) { - return; - } - - _this8.writeToTranscript(event); - } - }); - } - }, { - key: "start", - value: function start() { - var _this9 = this; - - var localStorage = this.createLocalStorage(); - this.client.login("m.login.password", { - user: process.env.BOT_USERNAME, - password: process.env.BOT_PASSWORD, - initial_device_display_name: process.env.BOT_DISPLAY_NAME - }).then(function (data) { - var accessToken = data.access_token; - var deviceId = data.device_id; // create new client with full options - - var opts = { - baseUrl: process.env.MATRIX_SERVER_URL, - accessToken: accessToken, - userId: process.env.BOT_USERID, - deviceId: deviceId, - sessionStore: new matrix.WebStorageSessionStore(localStorage) }; - _this9.client = matrix.createClient(opts); - })["catch"](function (err) { - _logger["default"].log("error", "ERROR WITH LOGIN: ".concat(err)); - }).then(function () { - return _this9.deleteOldDevices(); - }).then(function () { - return _this9.client.initCrypto(); - })["catch"](function (err) { - return _logger["default"].log("error", "ERROR STARTING CRYPTO: ".concat(err)); - }).then(function () { - return _this9.client.getJoinedRooms().then(function (data) { - _this9.joinedRooms = data["joined_rooms"]; - }); - }).then(function () { - _this9.setMembershipListeners(); + this.client.setPowerLevel(member.roomId, member.userId, 50, event); + this.sendTextMessage(member.roomId, `${member.name} has joined the chat.`); + this.sendTextMessage(this.config.FACILITATOR_ROOM_ID, `${member.name} joined the chat (Room ID: ${member.roomId})`); + this.uninviteFacilitators(member.roomId); - if (process.env.CAPTURE_TRANSCRIPTS) { - _this9.setMessageListeners(); + if (this.config.CAPTURE_TRANSCRIPTS) { + const currentDate = new Date(); + const dateOpts = { + year: "numeric", + month: "short", + day: "numeric" + }; + const chatDate = currentDate.toLocaleDateString("en-GB", dateOpts); + const chatTime = currentDate.toLocaleTimeString("en-GB", { + timeZone: "America/New_York" + }); + const filename = `${chatDate} - ${chatTime} - ${member.roomId}.txt`; + const filepath = path.resolve(path.join("transcripts", filename)); + this.localStorage.setItem(`${member.roomId}-transcript`, filepath); } - }).then(function () { - return _this9.client.startClient({ - initialSyncLimit: 0 - }); - })["catch"](function (err) { - _this9.handleBotCrash(undefined, err); + } - _logger["default"].log("error", "ERROR INITIALIZING CLIENT: ".concat(err)); + if (member.membership === "leave" && member.userId !== this.config.BOT_USERID) { + const facilitatorId = this.localStorage.getItem(`${member.roomId}-facilitator`); + + if (member.userId === facilitatorId) { + this.sendTextMessage(member.roomId, `${member.name} has left the chat.`); + } // leave if there is nobody in the room + + + const room = this.client.getRoom(member.roomId); + if (!room) return; + const memberCount = room.getJoinedMemberCount(); + + if (memberCount === 1) { + // just the bot + _logger.default.log("info", `LEAVING EMPTY ROOM ==> ${member.roomId}`); + + this.client.leave(member.roomId); + this.localStorage.removeItem(`${member.roomId}-facilitator`); + this.localStorage.removeItem(`${member.roomId}-transcript`); + } + } + }); + } + + async setMessageListeners() { + // encrypted messages + this.client.on("Event.decrypted", (event, err) => { + if (err) { + return _logger.default.log("error", `ERROR DECRYPTING EVENT: ${err}`); + } + + if (event.getType() === "m.room.message") { + this.handleMessageEvent(event); + } + }); // unencrypted messages + + this.client.on("Room.timeline", (event, room, toStartOfTimeline) => { + if (event.getType() === "m.room.message" && !event.isEncrypted()) { + this.handleMessageEvent(event); + } + }); + } + + async leaveOldRooms() { + const roomData = await this.client.getJoinedRooms(); + roomData["joined_rooms"].forEach(async roomId => { + try { + await this.client.leave(roomId); + } catch (err) { + _logger.default.log('error', "ERROR LEAVING ROOM => " + err); + } + }); + } + + async start() { + const localStorage = this.createLocalStorage(); + this.localStorage = localStorage; + + try { + const auth = { + user: this.config.BOT_USERNAME, + password: this.config.BOT_PASSWORD, + initial_device_display_name: this.config.BOT_DISPLAY_NAME + }; + const account = await this.client.login("m.login.password", auth); + + _logger.default.log("info", `ACCOUNT ==> ${JSON.stringify(account)}`); + + let opts = { + baseUrl: this.config.MATRIX_SERVER_URL, + accessToken: account.access_token, + userId: this.config.BOT_USERID, + deviceId: account.device_id, + sessionStore: new matrix.WebStorageSessionStore(localStorage) + }; + this.client = matrix.createClient(opts); + await this.deleteOldDevices(); + await this.trackJoinedRooms(); + await this.client.initCrypto(); + await this.setMembershipListeners(); + await this.setMessageListeners(); + this.client.startClient({ + initialSyncLimit: 0 }); - } - }]); + } catch (err) { + this.handleBotCrash(undefined, err); - return OcrccBot; -}(); + _logger.default.log("error", `ERROR INITIALIZING CLIENT: ${err}`); + } + } + +} var _default = OcrccBot; -exports["default"] = _default; \ No newline at end of file +exports.default = _default; \ No newline at end of file diff --git a/dist/bot.test.js b/dist/bot.test.js new file mode 100644 index 0000000..3141093 --- /dev/null +++ b/dist/bot.test.js @@ -0,0 +1,253 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); + +var path = _interopRequireWildcard(require("path")); + +var os = _interopRequireWildcard(require("os")); + +var fs = _interopRequireWildcard(require("fs")); + +var _waitForExpect = _interopRequireDefault(require("wait-for-expect")); + +var _matrixJsSdk = require("matrix-js-sdk"); + +var _bot = _interopRequireDefault(require("./bot")); + +require('dotenv').config(); + +const mockAppendFileSync = jest.fn(); +fs.appendFileSync = mockAppendFileSync; +describe('OcrccBot', () => { + beforeEach(() => { + _matrixJsSdk.createClient.mockClear(); + + _matrixJsSdk.mockInitCrypto.mockClear(); + + _matrixJsSdk.mockStartClient.mockClear(); + + _matrixJsSdk.mockRegisterRequest.mockClear(); + + _matrixJsSdk.mockSetPowerLevel.mockClear(); + + _matrixJsSdk.mockCreateRoom.mockClear(); + + _matrixJsSdk.mockLeave.mockClear(); + + _matrixJsSdk.mockDeactivateAccount.mockClear(); + + _matrixJsSdk.mockStopClient.mockClear(); + + _matrixJsSdk.mockClearStores.mockClear(); + + _matrixJsSdk.mockOnce.mockClear(); + + _matrixJsSdk.mockOn.mockClear(); + + _matrixJsSdk.mockLogin.mockClear(); + + _matrixJsSdk.mockGetDevices.mockClear(); + + _matrixJsSdk.mockGetDeviceId.mockClear(); + + _matrixJsSdk.mockDeleteMultipleDevices.mockClear(); + + _matrixJsSdk.mockGetJoinedRooms.mockClear(); + + _matrixJsSdk.mockSetDeviceVerified.mockClear(); + + _matrixJsSdk.mockInvite.mockClear(); + + _matrixJsSdk.mockKick.mockClear(); + + _matrixJsSdk.mockGetJoinedRoomMembers.mockClear(); + + _matrixJsSdk.mockGetUser.mockClear(); + + _matrixJsSdk.mockSendMessage.mockClear(); + + _matrixJsSdk.mockSendTextMessage.mockClear(); + + mockAppendFileSync.mockClear(); + + _matrixJsSdk.mockGetGroupUsers.mockClear(); + }); + test('constructor should inititialize class variables', () => { + const bot = new _bot.default(); + expect(bot.joinedRooms).toEqual([]); + expect(bot.awaitingFacilitator).toEqual({}); + expect(bot.activeChatrooms).toEqual({}); + }); + test('#createLocalStorage should have correct storage location', () => { + const bot = new _bot.default(); + const localStorage = bot.createLocalStorage(); + const localStoragePath = path.resolve(path.join(os.homedir(), ".local-storage", `matrix-chatbot-${process.env.BOT_USERNAME}`)); + expect(localStorage._location).toBe(localStoragePath); + }); + test('#sendMessage should send a text message', () => { + const bot = new _bot.default(); + bot.start(); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockStartClient).toHaveBeenCalled(); + }); + const testRoom = 'room_id_1234'; + const testMsg = 'test message'; + bot.sendMessage(testRoom, testMsg); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockSetDeviceVerified).toHaveBeenCalledTimes(2); + }); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockSendMessage).toHaveBeenCalledWith(testRoom, testMsg); + }); + }); + test('#inviteUserToRoom should add member to room and retry on rate limit error', () => { + const bot = new _bot.default(); + bot.start(); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockStartClient).toHaveBeenCalled(); + }); + bot.inviteUserToRoom(bot.client, 'room_id_1234', process.env.BOT_USERNAME); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockInvite).toHaveBeenCalledTimes(2); + }); + }); + test('#kickUserFromRoom should remove member from room and retry on rate limit error', () => { + const bot = new _bot.default(); + bot.start(); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockStartClient).toHaveBeenCalled(); + }); + bot.kickUserFromRoom(bot.client, 'room_id_1234', process.env.BOT_USERNAME); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockKick).toHaveBeenCalledTimes(2); + }); + }); + test('#inviteFacilitators should invite all members from Facilitator room', () => { + const bot = new _bot.default(); + bot.start(); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockStartClient).toHaveBeenCalled(); + }); + bot.inviteFacilitators(); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockGetJoinedRoomMembers).toHaveBeenCalledWith(process.env.FACILITATOR_ROOM_ID); + }); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockGetUser).toHaveBeenCalledTimes(2); + }); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockInvite).toHaveBeenCalledTimes(2); + }); + }); + test('#uninviteFacilitators should remove all members that have not accepted the invite', () => { + const bot = new _bot.default(); + bot.start(); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockStartClient).toHaveBeenCalled(); + }); + bot.uninviteFacilitators(); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockGetJoinedRoomMembers).toHaveBeenCalledWith(process.env.FACILITATOR_ROOM_ID); + }); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockGetJoinedRoomMembers).toHaveBeenCalledWith('room_id_1234'); + }); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockKick).toHaveBeenCalled(); + }); + }); + test('#handleBotCrash should notify rooms', () => { + const bot = new _bot.default(); + bot.start(); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockStartClient).toHaveBeenCalled(); + }); + bot.handleBotCrash('test_room_id', 'test error message'); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockSendTextMessage).toHaveBeenCalledWith('test_room_id', "Something went wrong on our end, please restart the chat and try again."); + }); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockSendTextMessage).toHaveBeenCalledWith(process.env.FACILITATOR_ROOM_ID, `The Help Bot ran into an error: test error message. Please verify that the chat service is working.`); + }); + }); + test('#writeToTranscript should parse event and write to transcript file', () => { + const bot = new _bot.default(); + bot.start(); + bot.activeChatrooms['test_room_id'] = { + transcriptFile: '__mocks__/test_transcript.txt' + }; + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockStartClient).toHaveBeenCalled(); + }); + const mockEvent = { + getSender: () => 'test_sender', + getRoomId: () => 'test_room_id', + getContent: () => { + return { + body: 'test content' + }; + }, + getDate: () => { + return new Date(2020, 2, 17, 0, 0, 0, 0); + } + }; + bot.writeToTranscript(mockEvent); + (0, _waitForExpect.default)(() => { + expect(mockAppendFileSync).toHaveBeenCalledWith('__mocks__/test_transcript.txt', 'test_sender [00:00:00]: test content', 'utf8'); + }); + }); + test('#deleteOldDevices should delete old sessions', () => { + const bot = new _bot.default(); + bot.start(); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockStartClient).toHaveBeenCalled(); + }); + bot.deleteOldDevices(); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockGetDevices).toHaveBeenCalled(); + }); + (0, _waitForExpect.default)(() => { + expect(mockGetDevicdId).toHaveBeenCalled(); + }); + (0, _waitForExpect.default)(() => { + expect(deleteMultipleDevices).toHaveBeenCalled(); + }); + }); // TODO test listeners for membership events and message events + + test('#start should start bot and set up listeners', () => { + const bot = new _bot.default(); + bot.start(); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockLogin).toHaveBeenCalled(); + }); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.WebStorageSessionStore).toHaveBeenCalled(); + }); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.createClient).toHaveBeenCalled(); + }); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockGetDevices).toHaveBeenCalled(); + }); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockGetDeviceId).toHaveBeenCalled(); + }); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockDeleteMultipleDevices).toHaveBeenCalled(); + }); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockInitCrypto).toHaveBeenCalled(); + }); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockGetJoinedRooms).toHaveBeenCalled(); + }); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockOn).toHaveBeenCalled(); + }); + (0, _waitForExpect.default)(() => { + expect(_matrixJsSdk.mockStartClient).toHaveBeenCalled(); + }); + }); +}); \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 2f3dc26..b687f41 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,10 +1,42 @@ "use strict"; +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + var _bot = _interopRequireDefault(require("./bot")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - require('dotenv').config(); -var bot = new _bot["default"](); +const ENCRYPTION_CONFIG = { + algorithm: "m.megolm.v1.aes-sha2" +}; +const KICK_REASON = "A facilitator has already joined this chat."; +const BOT_ERROR_MESSAGE = "Something went wrong on our end, please restart the chat and try again."; +const MAX_RETRIES = 3; +const { + MATRIX_SERVER_URL, + BOT_USERNAME, + BOT_USERID, + BOT_PASSWORD, + BOT_DISPLAY_NAME, + FACILITATOR_GROUP_ID, + FACILITATOR_ROOM_ID, + CHAT_OFFLINE_MESSAGE, + CAPTURE_TRANSCRIPTS +} = process.env; +const botConfig = { + ENCRYPTION_CONFIG, + KICK_REASON, + BOT_ERROR_MESSAGE, + MAX_RETRIES, + MATRIX_SERVER_URL, + BOT_USERNAME, + BOT_USERID, + BOT_PASSWORD, + BOT_DISPLAY_NAME, + FACILITATOR_GROUP_ID, + FACILITATOR_ROOM_ID, + CHAT_OFFLINE_MESSAGE, + CAPTURE_TRANSCRIPTS +}; +const bot = new _bot.default(botConfig); bot.start(); \ No newline at end of file diff --git a/dist/logger.js b/dist/logger.js index c6c6846..2c7873b 100644 --- a/dist/logger.js +++ b/dist/logger.js @@ -1,17 +1,17 @@ "use strict"; +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + Object.defineProperty(exports, "__esModule", { value: true }); -exports["default"] = void 0; +exports.default = void 0; var _winston = _interopRequireDefault(require("winston")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - -var logger = _winston["default"].createLogger({ +const logger = _winston.default.createLogger({ level: "info", - format: _winston["default"].format.json(), + format: _winston.default.format.json(), defaultMeta: { service: "user-service" }, @@ -19,10 +19,10 @@ var logger = _winston["default"].createLogger({ // - Write all logs with level `error` and below to `error.log` // - Write all logs with level `info` and below to `combined.log` // - new _winston["default"].transports.File({ + new _winston.default.transports.File({ filename: "error.log", level: "error" - }), new _winston["default"].transports.File({ + }), new _winston.default.transports.File({ filename: "combined.log" })] }); // @@ -32,10 +32,10 @@ var logger = _winston["default"].createLogger({ if (process.env.NODE_ENV !== "production") { - logger.add(new _winston["default"].transports.Console({ - format: _winston["default"].format.simple() + logger.add(new _winston.default.transports.Console({ + format: _winston.default.format.simple() })); } var _default = logger; -exports["default"] = _default; \ No newline at end of file +exports.default = _default; \ No newline at end of file diff --git a/src/bot.js b/src/bot.js index 2dec374..96401ff 100644 --- a/src/bot.js +++ b/src/bot.js @@ -17,7 +17,6 @@ class OcrccBot { this.awaitingFacilitator = {}; this.client = matrix.createClient(this.config.MATRIX_SERVER_URL); this.joinedRooms = []; - this.activeChatrooms = {}; } createLocalStorage() { @@ -56,7 +55,7 @@ class OcrccBot { } }); }); - return this.sendMessage(roomId, content); + await this.sendMessage(roomId, content); default: logger.log("error", `ERROR SENDING MESSAGE: ${err}`); break; @@ -82,7 +81,7 @@ class OcrccBot { } async inviteFacilitators(roomId) { - this.awaitingFacilitator[roomId] = true; + this.localStorage.setItem(`${roomId}-waiting`, 'true') let chatOffline = true; try { @@ -114,7 +113,7 @@ class OcrccBot { } async uninviteFacilitators(roomId) { - this.awaitingFacilitator[roomId] = false; + this.localStorage.removeItem(`${roomId}-waiting`) try { const groupUsers = await this.client.getGroupUsers(this.config.FACILITATOR_GROUP_ID) @@ -173,7 +172,7 @@ class OcrccBot { const time = date.toLocaleTimeString("en-GB", { timeZone: "America/New_York" }); - const filepath = this.activeChatrooms[roomId].transcriptFile; + const filepath = this.localStorage.getItem(`${roomId}-transcript`) const message = `${sender} [${time}]: ${content.body}\n`; fs.appendFileSync(filepath, message, "utf8"); @@ -190,9 +189,6 @@ class OcrccBot { const command = content.body.substring("!bot".length).trim(); switch (command) { - case "purge rooms": - this.leaveEmptyRooms(senderId); - break; case "transcript": this.sendTranscript(senderId, roomId); break; @@ -246,9 +242,8 @@ class OcrccBot { async sendTranscript(senderId, roomId) { try { - const transcriptFile = this.activeChatrooms[roomId] - ? this.activeChatrooms[roomId].transcriptFile - : false; + const transcriptFile = this.localStorage.getItem(`${roomId}-transcript`) + if (!transcriptFile) { this.sendTextMessage( roomId, @@ -320,17 +315,16 @@ class OcrccBot { member.userId === this.config.BOT_USERID && !this.joinedRooms.includes(member.roomId) ) { - try { const room = await this.client.joinRoom(member.roomId) - logger.log("info", "AUTO JOINED ROOM" + room.roomId) + logger.log("info", "AUTO JOINED ROOM => " + room.roomId) this.sendTextMessage( this.config.FACILITATOR_ROOM_ID, `A support seeker requested a chat (Room ID: ${room.roomId})` ); this.inviteFacilitators(room.roomId) } catch(err) { - logger.log("error", "ERROR JOINING ROOM =>" + err) + logger.log("error", "ERROR JOINING ROOM => " + err) } } @@ -339,11 +333,9 @@ class OcrccBot { if ( member.membership === "join" && member.userId !== this.config.BOT_USERID && - this.awaitingFacilitator[member.roomId] + this.localStorage.getItem(`${member.roomId}-waiting`) ) { - this.activeChatrooms[member.roomId] = { - facilitator: member.userId - }; + this.localStorage.setItem(`${member.roomId}-facilitator`, member.userId) const event = { getType: () => { return "m.room.power_levels"; @@ -380,7 +372,7 @@ class OcrccBot { }); const filename = `${chatDate} - ${chatTime} - ${member.roomId}.txt`; const filepath = path.resolve(path.join("transcripts", filename)); - this.activeChatrooms[member.roomId].transcriptFile = filepath; + this.localStorage.setItem(`${member.roomId}-transcript`, filepath) } } @@ -388,8 +380,8 @@ class OcrccBot { member.membership === "leave" && member.userId !== this.config.BOT_USERID ) { - - if (this.activeChatrooms[member.roomId] && member.userId === this.activeChatrooms[member.roomId].facilitator) { + const facilitatorId = this.localStorage.getItem(`${member.roomId}-facilitator`) + if (member.userId === facilitatorId) { this.sendTextMessage( member.roomId, `${member.name} has left the chat.` @@ -404,13 +396,15 @@ class OcrccBot { if (memberCount === 1) { // just the bot logger.log("info", `LEAVING EMPTY ROOM ==> ${member.roomId}`); - this.client.leave(event.roomId) + this.client.leave(member.roomId) + this.localStorage.removeItem(`${member.roomId}-facilitator`) + this.localStorage.removeItem(`${member.roomId}-transcript`) } } }); } - setMessageListeners() { + async setMessageListeners() { // encrypted messages this.client.on("Event.decrypted", (event, err) => { if (err) { @@ -442,6 +436,7 @@ class OcrccBot { async start() { const localStorage = this.createLocalStorage(); + this.localStorage = localStorage try { const auth = { @@ -465,7 +460,7 @@ class OcrccBot { await this.trackJoinedRooms() await this.client.initCrypto() await this.setMembershipListeners(); - this.setMessageListeners(); + await this.setMessageListeners(); this.client.startClient({ initialSyncLimit: 0 }) } catch(err) { this.handleBotCrash(undefined, err); diff --git a/transcripts/Apr 22, 2020 - 1:00:21 PM - !jjfNCYPkwvRztgyxNp:rhok.space.txt b/transcripts/Apr 22, 2020 - 1:00:21 PM - !jjfNCYPkwvRztgyxNp:rhok.space.txt new file mode 100644 index 0000000..daeb1ef --- /dev/null +++ b/transcripts/Apr 22, 2020 - 1:00:21 PM - !jjfNCYPkwvRztgyxNp:rhok.space.txt @@ -0,0 +1,5 @@ +@help-bot:rhok.space [1:00:21 PM]: Facilitator Demo Account has joined the chat. +@help-bot:rhok.space [1:00:22 PM]: Facilitator Demo Account has joined the chat. +@ocrcc-facilitator-demo:rhok.space [1:00:28 PM]: hi +@help-bot:rhok.space [1:00:42 PM]: Facilitator Demo Account has left the chat. +@help-bot:rhok.space [1:00:42 PM]: Facilitator Demo Account has left the chat. diff --git a/transcripts/Apr 22, 2020 - 1:02:18 PM - !NLmOYIXXomGSNLqygi:rhok.space.txt b/transcripts/Apr 22, 2020 - 1:02:18 PM - !NLmOYIXXomGSNLqygi:rhok.space.txt new file mode 100644 index 0000000..c240cae --- /dev/null +++ b/transcripts/Apr 22, 2020 - 1:02:18 PM - !NLmOYIXXomGSNLqygi:rhok.space.txt @@ -0,0 +1,5 @@ +@help-bot:rhok.space [1:02:18 PM]: Facilitator Demo Account has joined the chat. +@help-bot:rhok.space [1:02:19 PM]: Facilitator Demo Account has joined the chat. +@ocrcc-facilitator-demo:rhok.space [1:02:23 PM]: ASDF +@help-bot:rhok.space [1:03:01 PM]: Facilitator Demo Account has left the chat. +@help-bot:rhok.space [1:03:02 PM]: Facilitator Demo Account has left the chat. diff --git a/transcripts/Apr 22, 2020 - 4:22:04 PM - !BzdphjbeKrimzIjFtf:rhok.space.txt b/transcripts/Apr 22, 2020 - 4:22:04 PM - !BzdphjbeKrimzIjFtf:rhok.space.txt new file mode 100644 index 0000000..e2244a8 --- /dev/null +++ b/transcripts/Apr 22, 2020 - 4:22:04 PM - !BzdphjbeKrimzIjFtf:rhok.space.txt @@ -0,0 +1,6 @@ +@help-bot:rhok.space [4:22:04 PM]: Facilitator Demo Account has joined the chat. +@help-bot:rhok.space [4:22:04 PM]: Facilitator Demo Account has joined the chat. +@ocrcc-facilitator-demo:rhok.space [4:22:15 PM]: hiiii +@39102030-6be0-43f8-9f77-2e4c961d719a:rhok.space [4:22:18 PM]: hihi +@ocrcc-facilitator-demo:rhok.space [4:22:25 PM]: oih +@help-bot:rhok.space [4:22:40 PM]: Facilitator Demo Account has left the chat. diff --git a/transcripts/Apr 22, 2020 - 4:23:09 PM - !IsZMQlwOgpsrfZdBQo:rhok.space.txt b/transcripts/Apr 22, 2020 - 4:23:09 PM - !IsZMQlwOgpsrfZdBQo:rhok.space.txt new file mode 100644 index 0000000..1e42731 --- /dev/null +++ b/transcripts/Apr 22, 2020 - 4:23:09 PM - !IsZMQlwOgpsrfZdBQo:rhok.space.txt @@ -0,0 +1,12 @@ +@help-bot:rhok.space [4:23:09 PM]: Facilitator Demo Account has joined the chat. +@ocrcc-facilitator-demo:rhok.space [4:23:14 PM]: so +@9a3d4822-8ba4-4931-9736-db9d0ff2fdcb:rhok.space [4:23:20 PM]: ☺️ +@9a3d4822-8ba4-4931-9736-db9d0ff2fdcb:rhok.space [4:23:27 PM]: 😄 +@help-bot:rhok.space [4:23:35 PM]: Apr 22, 2020 - 4:23:09 PM - !IsZMQlwOgpsrfZdBQo:rhok.space.txt +@help-bot:rhok.space [4:23:35 PM]: Apr 22, 2020 - 4:23:09 PM - !IsZMQlwOgpsrfZdBQo:rhok.space.txt +@help-bot:rhok.space [4:24:17 PM]: Apr 22, 2020 - 4:23:09 PM - !IsZMQlwOgpsrfZdBQo:rhok.space.txt +@help-bot:rhok.space [4:24:17 PM]: Apr 22, 2020 - 4:23:09 PM - !IsZMQlwOgpsrfZdBQo:rhok.space.txt +@help-bot:rhok.space [4:24:17 PM]: Apr 22, 2020 - 4:23:09 PM - !IsZMQlwOgpsrfZdBQo:rhok.space.txt +@9a3d4822-8ba4-4931-9736-db9d0ff2fdcb:rhok.space [4:24:27 PM]: hey hey +@ocrcc-facilitator-demo:rhok.space [4:24:32 PM]: cool beans +@help-bot:rhok.space [4:24:45 PM]: Facilitator Demo Account has left the chat. diff --git a/transcripts/Apr 22, 2020 - 4:26:24 PM - !jPVeDJGfmrioMshdUY:rhok.space.txt b/transcripts/Apr 22, 2020 - 4:26:24 PM - !jPVeDJGfmrioMshdUY:rhok.space.txt new file mode 100644 index 0000000..ba8ee5f --- /dev/null +++ b/transcripts/Apr 22, 2020 - 4:26:24 PM - !jPVeDJGfmrioMshdUY:rhok.space.txt @@ -0,0 +1,5 @@ +@help-bot:rhok.space [4:26:25 PM]: Facilitator Demo Account has joined the chat. +@help-bot:rhok.space [4:26:25 PM]: Facilitator Demo Account has joined the chat. +@df279781-646c-46e9-ad6a-565a4e31d0bb:rhok.space [4:26:36 PM]: so +@ocrcc-facilitator-demo:rhok.space [4:26:41 PM]: asdf +@help-bot:rhok.space [4:33:26 PM]: Facilitator Demo Account has left the chat. diff --git a/transcripts/Apr 22, 2020 - 4:33:45 PM - !PNGecrkpokGRVOsMaU:rhok.space.txt b/transcripts/Apr 22, 2020 - 4:33:45 PM - !PNGecrkpokGRVOsMaU:rhok.space.txt new file mode 100644 index 0000000..99c4c98 --- /dev/null +++ b/transcripts/Apr 22, 2020 - 4:33:45 PM - !PNGecrkpokGRVOsMaU:rhok.space.txt @@ -0,0 +1,3 @@ +@help-bot:rhok.space [4:33:45 PM]: Facilitator Demo Account has joined the chat. +@help-bot:rhok.space [4:33:45 PM]: Facilitator Demo Account has joined the chat. +@help-bot:rhok.space [4:34:48 PM]: Facilitator Demo Account has left the chat. diff --git a/transcripts/Apr 22, 2020 - 4:50:37 PM - !hyWgcgjiKgANPxXwnl:rhok.space.txt b/transcripts/Apr 22, 2020 - 4:50:37 PM - !hyWgcgjiKgANPxXwnl:rhok.space.txt new file mode 100644 index 0000000..136e03d --- /dev/null +++ b/transcripts/Apr 22, 2020 - 4:50:37 PM - !hyWgcgjiKgANPxXwnl:rhok.space.txt @@ -0,0 +1,5 @@ +@help-bot:rhok.space [4:50:37 PM]: Facilitator Demo Account has joined the chat. +@help-bot:rhok.space [4:50:37 PM]: Facilitator Demo Account has joined the chat. +@ocrcc-facilitator-demo:rhok.space [4:50:45 PM]: hihi +@edd84c88-b2ab-4910-9eeb-595881f1c145:rhok.space [4:50:56 PM]: asdfasdf +@help-bot:rhok.space [4:51:04 PM]: Facilitator Demo Account has left the chat. diff --git a/transcripts/Apr 22, 2020 - 4:58:09 PM - !fEoMiYgeAOibhYlqXo:rhok.space.txt b/transcripts/Apr 22, 2020 - 4:58:09 PM - !fEoMiYgeAOibhYlqXo:rhok.space.txt new file mode 100644 index 0000000..c0e3f8b --- /dev/null +++ b/transcripts/Apr 22, 2020 - 4:58:09 PM - !fEoMiYgeAOibhYlqXo:rhok.space.txt @@ -0,0 +1,5 @@ +@help-bot:rhok.space [4:58:09 PM]: Facilitator Demo Account has joined the chat. +@help-bot:rhok.space [4:58:10 PM]: Facilitator Demo Account has joined the chat. +@ocrcc-facilitator-demo:rhok.space [4:58:28 PM]: kj +@e5221f47-38ce-47ae-b1c7-2921bd035362:rhok.space [4:58:34 PM]: kk +@help-bot:rhok.space [4:58:47 PM]: Facilitator Demo Account has left the chat. diff --git a/transcripts/Apr 22, 2020 - 5:28:23 PM - !OQZSgsxNUWMLzImfBH:rhok.space.txt b/transcripts/Apr 22, 2020 - 5:28:23 PM - !OQZSgsxNUWMLzImfBH:rhok.space.txt new file mode 100644 index 0000000..89d4c42 --- /dev/null +++ b/transcripts/Apr 22, 2020 - 5:28:23 PM - !OQZSgsxNUWMLzImfBH:rhok.space.txt @@ -0,0 +1,2 @@ +@help-bot:rhok.space [5:28:23 PM]: Facilitator Demo Account has joined the chat. +@help-bot:rhok.space [5:28:24 PM]: Facilitator Demo Account has joined the chat.