2020-03-16 18:41:56 +00:00
"use strict" ;
2020-04-22 22:04:48 +00:00
var _interopRequireDefault = require ( "@babel/runtime/helpers/interopRequireDefault" ) ;
var _interopRequireWildcard = require ( "@babel/runtime/helpers/interopRequireWildcard" ) ;
2020-03-16 18:41:56 +00:00
Object . defineProperty ( exports , "__esModule" , {
value : true
} ) ;
2020-04-22 22:04:48 +00:00
exports . default = void 0 ;
2020-03-16 18:41:56 +00:00
var fs = _interopRequireWildcard ( require ( "fs" ) ) ;
var os = _interopRequireWildcard ( require ( "os" ) ) ;
var path = _interopRequireWildcard ( require ( "path" ) ) ;
var util = _interopRequireWildcard ( require ( "util" ) ) ;
var _nodeLocalstorage = require ( "node-localstorage" ) ;
var matrix = _interopRequireWildcard ( require ( "matrix-js-sdk" ) ) ;
var _logger = _interopRequireDefault ( require ( "./logger" ) ) ;
2020-07-25 23:07:08 +00:00
var _encryptAttachment = _interopRequireDefault ( require ( "./encrypt-attachment" ) ) ;
2020-04-22 22:04:48 +00:00
global . Olm = require ( "olm" ) ;
2020-09-05 22:11:06 +00:00
const BOT _SIGNAL _END _CHAT = 'END_CHAT' ;
2020-09-11 20:12:07 +00:00
const BOT _SIGNAL _CHAT _OFFLINE = 'CHAT_OFFLINE' ;
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
class OcrccBot {
2020-12-01 03:47:32 +00:00
constructor ( config ) {
this . config = config ;
this . client = matrix . createClient ( this . config . matrixServerUrl ) ;
2020-04-22 22:04:48 +00:00
this . joinedRooms = [ ] ;
2020-09-06 18:07:44 +00:00
this . inactivityTimers = { } ;
2020-04-22 22:04:48 +00:00
}
createLocalStorage ( ) {
2020-12-01 03:47:32 +00:00
const storageLoc = ` matrix-chatbot- ${ this . config . botUsername } ` ;
2020-04-22 22:04:48 +00:00
const dir = path . resolve ( path . join ( os . homedir ( ) , ".local-storage" ) ) ;
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
if ( ! fs . existsSync ( dir ) ) {
fs . mkdirSync ( dir ) ;
}
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
const localStoragePath = path . resolve ( path . join ( dir , storageLoc ) ) ;
return new _nodeLocalstorage . LocalStorage ( localStoragePath ) ;
}
2020-03-16 18:41:56 +00:00
2020-09-07 15:54:05 +00:00
async sendTextMessage ( roomId , msgText , showToUser = null ) {
2020-04-22 22:04:48 +00:00
const content = {
msgtype : "m.text" ,
body : msgText ,
showToUser : showToUser
} ;
2020-09-07 15:54:05 +00:00
await this . sendMessage ( roomId , content ) ;
2020-04-22 22:04:48 +00:00
}
2020-03-16 18:41:56 +00:00
2020-06-19 16:57:23 +00:00
async sendNotice ( roomId , message ) {
try {
await this . client . sendNotice ( roomId , message ) ;
2020-09-07 15:54:05 +00:00
_logger . default . log ( "info" , ` SENT *NOTICE*: ${ message } ` ) ;
2020-06-19 16:57:23 +00:00
} 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 } ` ) ;
}
} ) ;
} ) ;
await this . sendNotice ( roomId , message ) ;
2020-04-22 22:04:48 +00:00
2020-06-19 16:57:23 +00:00
default :
_logger . default . log ( "error" , ` ERROR SENDING *NOTICE*: ${ err } ` ) ;
break ;
}
}
}
async sendMessage ( roomId , content ) {
2020-04-22 22:04:48 +00:00
try {
await this . client . sendMessage ( roomId , content ) ;
2020-06-19 16:57:23 +00:00
_logger . default . log ( "info" , ` SENT MESSAGE: ${ content . body } ` ) ;
2020-04-22 22:04:48 +00:00
} 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 } ` ) ;
}
} ) ;
} ) ;
await this . sendMessage ( roomId , content ) ;
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
default :
2020-07-25 23:07:08 +00:00
_logger . default . log ( "error" , ` ERROR SENDING MESSAGE ${ content . body } : ${ err } ` ) ;
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
break ;
}
}
2020-03-16 18:41:56 +00:00
}
2020-04-22 22:04:48 +00:00
kickUserFromRoom ( roomId , member ) {
try {
2020-12-01 03:47:32 +00:00
this . client . kick ( roomId , member , this . config . kickReason ) ;
2020-04-22 22:04:48 +00:00
} catch ( err ) {
this . handleBotCrash ( roomId , err ) ;
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
_logger . default . log ( "error" , ` ERROR KICKING OUT MEMBER: ${ err } ` ) ;
2020-03-16 18:41:56 +00:00
}
2020-04-22 22:04:48 +00:00
}
2020-03-16 18:41:56 +00:00
2020-12-01 18:21:16 +00:00
async inviteFacilitatorIfOnline ( roomId , memberId ) {
const user = this . client . getUser ( memberId ) ;
if ( user && user . presence !== "offline" && memberId !== this . config . botUserId ) {
try {
this . client . invite ( roomId , memberId ) ;
_logger . default . log ( "info" , ` CHAT INVITATION SENT TO ${ memberId } FOR ROOM ${ roomId } ` ) ;
return memberId ;
} catch ( err ) {
this . handleBotCrash ( roomId , err ) ;
return null ;
}
} else {
return null ;
}
}
2020-04-22 22:04:48 +00:00
async inviteFacilitators ( roomId ) {
try {
2020-09-07 15:54:05 +00:00
this . localStorage . setItem ( ` ${ roomId } -waiting ` , 'true' ) ;
let invitations = [ ] ;
2020-12-01 03:47:32 +00:00
const roomMembers = await this . client . getJoinedRoomMembers ( this . config . facilitatorRoomId ) ;
2020-06-19 16:57:23 +00:00
const members = Object . keys ( roomMembers [ "joined" ] ) ;
2020-03-16 18:41:56 +00:00
2020-12-01 18:21:16 +00:00
for ( const memberId of members ) {
const invited = await this . inviteFacilitatorIfOnline ( roomId , memberId ) ;
invitations . push ( invited ) ;
}
2020-03-16 18:41:56 +00:00
2020-12-01 18:21:16 +00:00
if ( invitations . filter ( i => i ) . length > 0 ) {
2020-06-19 16:57:23 +00:00
this . localStorage . setItem ( ` ${ roomId } -invitations ` , invitations ) ;
} else {
2020-12-01 18:21:16 +00:00
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 . default . log ( 'info' , ` NO FACILITATORS ONLINE, CHAT CLOSED AT ${ closedTime } (room ID: ${ roomRef } ) ` ) ;
2020-03-16 18:41:56 +00:00
2020-12-01 18:21:16 +00:00
this . sendTextMessage ( this . config . facilitatorRoomId , notification ) ;
2020-03-16 18:41:56 +00:00
}
2020-04-22 22:04:48 +00:00
} catch ( err ) {
this . handleBotCrash ( roomId , err ) ;
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
_logger . default . log ( "error" , ` ERROR GETTING FACILITATORS: ${ err } ` ) ;
}
}
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
async uninviteFacilitators ( roomId ) {
try {
2020-09-07 15:54:05 +00:00
this . localStorage . removeItem ( ` ${ roomId } -waiting ` ) ;
2020-12-01 03:47:32 +00:00
const facilitatorsRoomMembers = await this . client . getJoinedRoomMembers ( this . config . facilitatorRoomId ) ;
2020-06-19 16:57:23 +00:00
const supportRoomMembers = await this . client . getJoinedRoomMembers ( roomId ) ;
const roomMembersIds = Object . keys ( supportRoomMembers [ "joined" ] ) ;
const facilitatorsIds = Object . keys ( facilitatorsRoomMembers [ "joined" ] ) ;
if ( ! roomMembersIds || ! facilitatorsIds ) return ;
2020-04-22 22:04:48 +00:00
facilitatorsIds . forEach ( f => {
2020-06-19 16:57:23 +00:00
if ( ! roomMembersIds . includes ( f ) ) {
2020-04-22 22:04:48 +00:00
this . kickUserFromRoom ( roomId , f ) ;
2020-03-16 18:41:56 +00:00
}
} ) ;
2020-04-22 22:04:48 +00:00
} catch ( err ) {
this . handleBotCrash ( roomId , err ) ;
_logger . default . log ( "ERROR UNINVITING FACILITATORS" , err ) ;
2020-03-16 18:41:56 +00:00
}
2020-04-22 22:04:48 +00:00
}
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
handleBotCrash ( roomId , error ) {
if ( roomId ) {
2020-12-01 03:47:32 +00:00
this . sendTextMessage ( roomId , this . config . botErrorMessage ) ;
2020-12-01 18:21:16 +00:00
this . sendBotSignal ( roomId , BOT _SIGNAL _END _CHAT ) ;
2020-04-22 22:04:48 +00:00
}
2020-03-16 18:41:56 +00:00
2020-12-01 03:47:32 +00:00
this . sendTextMessage ( this . config . facilitatorRoomId , ` ${ this . config . botDisplayName } ran into an error: ${ error } . Please verify that the chat service is working. ` ) ;
2020-04-22 22:04:48 +00:00
}
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
handleMessageEvent ( event ) {
2020-09-06 18:07:44 +00:00
const content = event . getContent ( ) ;
const sender = event . getSender ( ) ;
const roomId = event . getRoomId ( ) ; // do nothing if there's no content
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
if ( ! content ) {
return ;
2020-09-06 18:07:44 +00:00
} // if it's a chat message and the facilitator has joined, reset the inactivity timeout
const facilitatorId = this . localStorage . getItem ( ` ${ roomId } -facilitator ` ) ;
2020-12-01 03:47:32 +00:00
if ( Boolean ( facilitatorId ) && sender !== this . config . botUserId ) {
2020-09-06 18:07:44 +00:00
this . setInactivityTimeout ( roomId ) ;
2020-04-22 22:04:48 +00:00
} // bot commands
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
if ( content . body . startsWith ( "!bot" ) ) {
return this . handleBotCommand ( event ) ;
} // write to transcript
2020-03-16 18:41:56 +00:00
2020-12-01 03:47:32 +00:00
if ( this . config . captureTranscripts ) {
2020-04-22 22:04:48 +00:00
return this . writeToTranscript ( event ) ;
2020-03-16 18:41:56 +00:00
}
2020-04-22 22:04:48 +00:00
}
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
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"
2020-03-16 18:41:56 +00:00
} ) ;
2020-04-22 22:04:48 +00:00
const filepath = this . localStorage . getItem ( ` ${ roomId } -transcript ` ) ;
2020-04-23 14:24:03 +00:00
if ( ! filepath ) {
2020-09-07 15:54:05 +00:00
return _logger . default . log ( "error" , ` NO TRANSCRIPT FILE FOR ROOM: ${ roomId } . This message will not be added: ${ content . body } ` ) ;
2020-04-23 14:24:03 +00:00
}
2020-04-22 22:04:48 +00:00
const message = ` ${ sender } [ ${ time } ]: ${ content . body } \n ` ;
fs . appendFileSync ( filepath , message , "utf8" ) ;
} catch ( err ) {
_logger . default . log ( "error" , ` ERROR APPENDING TO TRANSCRIPT FILE: ${ err } ` ) ;
2020-03-16 18:41:56 +00:00
}
2020-04-22 22:04:48 +00:00
}
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
handleBotCommand ( event ) {
2020-09-05 22:11:06 +00:00
const botCommands = [ {
keyword : 'transcript' ,
function : ( senderId , roomId ) => {
this . sendTranscript ( senderId , roomId ) ;
}
} , {
keyword : 'delete transcript' ,
function : ( senderId , roomId ) => {
2020-09-07 15:54:05 +00:00
this . deleteTranscript ( senderId , roomId , true ) ;
} // delete transcript and send confirmation message
2020-09-05 22:11:06 +00:00
} , {
keyword : 'say' ,
function : ( senderId , roomId , message ) => {
this . sendTextMessage ( roomId , message , senderId ) ;
}
} , {
keyword : 'hi' ,
function : ( senderId , roomId ) => {
const responses = [ "Hi!" , "Hello" , "Hey :)" , "Hi there" , "Bleep bloop" ] ;
const message = responses [ Math . floor ( Math . random ( ) * responses . length ) ] ;
this . sendTextMessage ( roomId , message , senderId ) ;
}
} ] ;
2020-04-22 22:04:48 +00:00
try {
const senderId = event . getSender ( ) ;
const roomId = event . getRoomId ( ) ;
const content = event . getContent ( ) ;
2020-09-05 22:11:06 +00:00
const commandText = content . body . substring ( "!bot" . length ) . trim ( ) ;
const command = botCommands . find ( c => commandText . startsWith ( c . keyword ) ) ;
2020-04-22 22:04:48 +00:00
2020-09-05 22:11:06 +00:00
if ( ! command ) {
this . sendTextMessage ( roomId , ` Sorry, I don't know that command. I'm not a very smart bot. ` , senderId ) ;
2020-03-16 18:41:56 +00:00
}
2020-09-05 22:11:06 +00:00
const args = commandText . substring ( command . keyword . length ) . trim ( ) ;
command . function ( senderId , roomId , args ) ;
2020-04-22 22:04:48 +00:00
} catch ( err ) {
_logger . default . log ( "error" , ` ERROR EXECUTING BOT COMMAND: ${ err } ` ) ;
2020-03-16 18:41:56 +00:00
}
2020-04-22 22:04:48 +00:00
}
2020-03-16 18:41:56 +00:00
2020-09-07 15:54:05 +00:00
async leaveEmptyRooms ( ) {
2020-04-22 22:04:48 +00:00
try {
const roomData = await this . client . getJoinedRooms ( ) ;
const joinedRoomsIds = roomData [ "joined_rooms" ] ;
joinedRoomsIds . forEach ( async roomId => {
const room = this . client . getRoom ( roomId ) ;
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
if ( room && room . getJoinedMemberCount ( ) === 1 ) {
try {
await this . client . leave ( roomId ) ;
2020-09-07 15:54:05 +00:00
_logger . default . log ( 'info' , ` LEAVING EMPTY ROOM => ${ roomId } ` ) ;
2020-04-22 22:04:48 +00:00
} catch ( err ) {
2020-09-07 15:54:05 +00:00
_logger . default . log ( 'error' , ` ERROR LEAVING ROOM ${ roomId } => ${ err } ` ) ;
2020-04-22 22:04:48 +00:00
}
2020-03-18 07:00:43 +00:00
}
2020-04-22 22:04:48 +00:00
} ) ;
} catch ( err ) {
2020-09-07 15:54:05 +00:00
_logger . default . log ( "error" , ` ERROR LEAVING EMPTY ROOMS: ${ err } ` ) ;
2020-04-22 22:04:48 +00:00
}
}
2020-03-18 07:00:43 +00:00
2020-04-22 22:04:48 +00:00
async sendTranscript ( senderId , roomId ) {
try {
const transcriptFile = this . localStorage . getItem ( ` ${ roomId } -transcript ` ) ;
2020-03-18 07:00:43 +00:00
2020-04-22 22:04:48 +00:00
if ( ! transcriptFile ) {
2020-09-07 15:54:05 +00:00
this . sendTextMessage ( roomId , "Cannot send transcript, there is no transcript for this chat." , senderId ) ;
2020-04-22 22:04:48 +00:00
}
2020-03-18 07:00:43 +00:00
2020-07-25 23:07:08 +00:00
if ( this . client . isRoomEncrypted ( roomId ) ) {
let encryptInfo ;
const filename = path . basename ( transcriptFile ) || "Transcript" ;
const data = fs . readFileSync ( transcriptFile ) ;
const encryptResult = await _encryptAttachment . default . encryptAttachment ( data ) ;
const buffer = Buffer . from ( encryptResult . data ) ;
encryptInfo = encryptResult . info ;
const url = await this . client . uploadContent ( buffer , {
rawResponse : false ,
name : filename
} ) ;
encryptInfo . url = url . content _uri ;
encryptInfo . mimetype = 'text/plain' ;
const content = {
msgtype : "m.file" ,
body : filename ,
info : {
mimetype : 'text/plain'
} ,
file : encryptInfo ,
url : url . content _uri ,
showToUser : senderId ,
2020-06-19 16:57:23 +00:00
mimetype : 'text/plain'
2020-07-25 23:07:08 +00:00
} ;
this . sendMessage ( roomId , content ) ;
} else {
const filename = path . basename ( transcriptFile ) || "Transcript" ;
const file = fs . readFileSync ( transcriptFile ) ;
const stats = fs . statSync ( transcriptFile ) ;
const url = await this . client . uploadContent ( file , {
rawResponse : false ,
name : filename
} ) ;
_logger . default . log ( 'info' , url ) ;
const content = {
msgtype : "m.file" ,
body : filename ,
info : {
size : stats . size ,
mimetype : 'text/plain'
} ,
url : url . content _uri ,
showToUser : senderId ,
mimetype : 'text/plain'
} ;
this . sendMessage ( roomId , content ) ;
}
2020-04-22 22:04:48 +00:00
} catch ( err ) {
_logger . default . log ( "error" , ` ERROR UPLOADING CONTENT: ${ err } ` ) ;
2020-03-18 07:00:43 +00:00
2020-04-22 22:04:48 +00:00
this . sendTextMessage ( roomId , "There was an error uploading the transcript." , senderId ) ;
}
}
2020-03-16 18:41:56 +00:00
2020-09-07 15:54:05 +00:00
deleteTranscript ( senderId , roomId , sendConfirmation = false ) {
2020-06-19 16:57:23 +00:00
const transcriptFile = this . localStorage . getItem ( ` ${ roomId } -transcript ` ) ;
if ( ! transcriptFile ) {
2020-09-07 15:54:05 +00:00
return this . sendTextMessage ( roomId , "Cannot delete transcript, there is no transcript for this chat." , senderId ) ;
2020-06-19 16:57:23 +00:00
}
fs . unlink ( transcriptFile , err => {
if ( err ) {
_logger . default . log ( 'error' , "UNABLE TO DELETE TRANSCRIPT FILE => " + transcriptFile ) ;
_logger . default . log ( 'error' , err ) ;
return this . sendTextMessage ( roomId , ` There was an error deleting the transcript: ${ err } ` , senderId ) ;
}
2020-09-07 15:54:05 +00:00
if ( sendConfirmation ) {
this . sendTextMessage ( roomId , ` The transcript file has been deleted. ` , senderId ) ;
}
2020-06-19 16:57:23 +00:00
_logger . default . log ( 'info' , "DELETED TRANSCRIPT FILE => " + transcriptFile ) ;
2020-09-07 15:54:05 +00:00
this . localStorage . removeItem ( ` ${ roomId } -transcript ` ) ;
2020-06-19 16:57:23 +00:00
} ) ;
}
2020-04-22 22:04:48 +00:00
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 ) ;
try {
await this . client . deleteMultipleDevices ( oldDevices ) ;
} catch ( err ) {
_logger . default . log ( "info" , "RETRYING DELETE OLD DEVICES WITH AUTH" ) ;
const auth = {
session : err . data . session ,
type : "m.login.password" ,
2020-12-01 03:47:32 +00:00
user : this . config . botUserId ,
2020-04-22 22:04:48 +00:00
identifier : {
type : "m.id.user" ,
2020-12-01 03:47:32 +00:00
user : this . config . botUserId
2020-04-22 22:04:48 +00:00
} ,
2020-12-01 03:47:32 +00:00
password : this . config . botPassword
2020-04-22 22:04:48 +00:00
} ;
await this . client . deleteMultipleDevices ( oldDevices , auth ) ;
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
_logger . default . log ( "info" , "DELETED OLD DEVICES" ) ;
}
}
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
async trackJoinedRooms ( ) {
const roomData = await this . client . getJoinedRooms ( ) ;
this . joinedRooms = roomData [ "joined_rooms" ] ;
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
_logger . default . log ( "info" , "JOINED ROOMS => " + this . joinedRooms ) ;
}
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
async setMembershipListeners ( ) {
// Automatically accept all room invitations
this . client . on ( "RoomMember.membership" , async ( event , member ) => {
2020-12-01 03:47:32 +00:00
if ( member . membership === "invite" && member . userId === this . config . botUserId && ! this . joinedRooms . includes ( member . roomId ) ) {
2020-04-22 22:04:48 +00:00
try {
2020-06-19 16:57:23 +00:00
const roomData = await this . client . getJoinedRooms ( ) ;
const joinedRooms = roomData [ "joined_rooms" ] ;
if ( ! joinedRooms . includes ( member . roomId ) ) {
const room = await this . client . joinRoom ( member . roomId ) ;
2020-03-16 18:41:56 +00:00
2020-06-19 16:57:23 +00:00
_logger . default . log ( "info" , "AUTO JOINED ROOM => " + room . roomId ) ;
2020-03-16 18:41:56 +00:00
2020-09-05 22:11:06 +00:00
const inviteDate = event . getDate ( ) ;
const chatDate = inviteDate . toLocaleDateString ( ) ;
const chatTime = inviteDate . toLocaleTimeString ( ) ;
2020-06-19 16:57:23 +00:00
const roomId = room . roomId . split ( ':' ) [ 0 ] ;
const notification = ` Incoming support chat at ${ chatTime } (room ID: ${ roomId } ) ` ;
2020-12-01 03:47:32 +00:00
this . sendTextMessage ( this . config . facilitatorRoomId , notification ) ;
2020-06-19 16:57:23 +00:00
this . inviteFacilitators ( room . roomId ) ;
2020-09-06 18:07:44 +00:00
this . setTimeoutforFacilitator ( room . roomId ) ;
2020-06-19 16:57:23 +00:00
}
2020-04-22 22:04:48 +00:00
} catch ( err ) {
_logger . default . log ( "error" , "ERROR JOINING ROOM => " + err ) ;
}
2020-06-19 16:57:23 +00:00
}
2020-04-22 22:04:48 +00:00
2020-12-01 03:47:32 +00:00
if ( member . membership === "join" && member . userId !== this . config . botUserId && this . localStorage . getItem ( ` ${ member . roomId } -waiting ` ) ) {
2020-09-06 18:07:44 +00:00
try {
// make sure it's a facilitator joining
2020-12-01 03:47:32 +00:00
const roomMembers = await this . client . getJoinedRoomMembers ( this . config . facilitatorRoomId ) ;
2020-09-06 18:07:44 +00:00
const members = Object . keys ( roomMembers [ "joined" ] ) ;
const isFacilitator = members . includes ( member . userId ) ;
if ( isFacilitator ) {
// made facilitator a moderator in the room
this . localStorage . setItem ( ` ${ member . roomId } -facilitator ` , member . userId ) ;
const event = {
getType : ( ) => {
return "m.room.power_levels" ;
} ,
getContent : ( ) => {
return {
users : {
2020-12-01 03:47:32 +00:00
[ this . config . botUserId ] : 100 ,
2020-09-06 18:07:44 +00:00
[ member . userId ] : 50
}
} ;
}
} ;
this . client . setPowerLevel ( member . roomId , member . userId , 50 , event ) ; // send notification to Support Chat Notifications room
2020-04-22 22:04:48 +00:00
2020-09-06 18:07:44 +00:00
const currentDate = new Date ( ) ;
const chatTime = currentDate . toLocaleTimeString ( ) ;
const roomId = member . roomId . split ( ':' ) [ 0 ] ;
const notification = ` ${ member . name } joined the chat at ${ chatTime } (room ID: ${ roomId } ) ` ;
2020-12-01 03:47:32 +00:00
this . sendTextMessage ( this . config . facilitatorRoomId , notification ) ; // send notification to chat room
2020-06-19 16:57:23 +00:00
2020-09-06 18:07:44 +00:00
this . sendTextMessage ( member . roomId , ` ${ member . name } has joined the chat. ` ) ; // revoke the other invitations
2020-06-19 16:57:23 +00:00
2020-09-06 18:07:44 +00:00
this . uninviteFacilitators ( member . roomId ) ; // set transcript file
2020-06-19 16:57:23 +00:00
2020-12-01 03:47:32 +00:00
if ( this . config . captureTranscripts ) {
2020-09-06 18:07:44 +00:00
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 ) ;
}
2020-06-19 16:57:23 +00:00
}
2020-09-06 18:07:44 +00:00
} catch ( err ) {
_logger . default . log ( "error" , ` ERROR WHEN FACILITATOR JOINED ROOM ==> ${ err } ` ) ;
2020-03-16 18:41:56 +00:00
}
2020-04-22 22:04:48 +00:00
}
2020-03-16 18:41:56 +00:00
2020-12-01 03:47:32 +00:00
if ( member . membership === "leave" && member . userId !== this . config . botUserId ) {
2020-12-01 18:21:16 +00:00
// notify room if the facilitator has left
try {
const facilitatorId = this . localStorage . getItem ( ` ${ member . roomId } -facilitator ` ) ;
if ( member . userId === facilitatorId ) {
this . sendTextMessage ( member . roomId , ` ${ member . name } has left the chat. ` ) ; // send notification to Support Chat Notifications room
const currentDate = new Date ( ) ;
const chatTime = currentDate . toLocaleTimeString ( ) ;
const roomId = member . roomId . split ( ':' ) [ 0 ] ;
const notification = ` ${ member . name } left the chat at ${ chatTime } (room ID: ${ roomId } ) ` ;
await this . sendTextMessage ( this . config . facilitatorRoomId , notification ) ;
}
} catch ( err ) {
_logger . default . log ( "error" , ` ERROR NOTIFYING THAT FACLITATOR HAS LEFT THE ROOM ==> ${ err } ` ) ;
}
2020-04-22 22:04:48 +00:00
const room = this . client . getRoom ( member . roomId ) ;
2020-09-06 18:07:44 +00:00
if ( ! room ) return ;
const roomMembers = await room . getJoinedMembers ( ) ; // array
2020-12-01 18:21:16 +00:00
// leave if there is nobody in the room
2020-07-25 23:07:08 +00:00
2020-09-05 22:11:06 +00:00
try {
2020-09-06 18:07:44 +00:00
const memberCount = roomMembers . length ;
2020-12-01 18:21:16 +00:00
const isBotInRoom = Boolean ( roomMembers . find ( member => member . userId === this . config . botUserId ) ) ;
2020-07-25 23:07:08 +00:00
2020-09-05 22:11:06 +00:00
if ( memberCount === 1 && isBotInRoom ) {
// just the bot left
2020-09-07 15:54:05 +00:00
_logger . default . log ( "info" , ` LEAVING EMPTY ROOM: ${ member . roomId } ` ) ;
2020-07-25 23:07:08 +00:00
2020-09-05 22:11:06 +00:00
this . deleteTranscript ( member . userId , member . roomId ) ;
this . localStorage . removeItem ( ` ${ member . roomId } -facilitator ` ) ;
this . localStorage . removeItem ( ` ${ member . roomId } -transcript ` ) ;
2020-09-07 15:54:05 +00:00
this . localStorage . removeItem ( ` ${ member . roomId } -waiting ` ) ;
return await this . client . leave ( member . roomId ) ;
2020-09-05 22:11:06 +00:00
}
} catch ( err ) {
2020-09-07 15:54:05 +00:00
return _logger . default . log ( "error" , ` ERROR LEAVING EMPTY ROOM ==> ${ err } ` ) ;
2020-09-05 22:11:06 +00:00
} // send signal to close the chat if there are no facilitators in the room
try {
2020-12-01 18:21:16 +00:00
const facilitatorRoomMembers = await this . client . getJoinedRoomMembers ( this . config . facilitatorRoomId ) ; // object
2020-09-05 22:11:06 +00:00
const facilitators = facilitatorRoomMembers [ 'joined' ] ;
let facilitatorInRoom = false ;
roomMembers . forEach ( member => {
2020-12-01 03:47:32 +00:00
if ( member . userId !== this . config . botUserId && Boolean ( facilitators [ member . userId ] ) ) {
2020-09-05 22:11:06 +00:00
facilitatorInRoom = true ;
}
} ) ;
if ( ! facilitatorInRoom ) {
2020-09-07 15:54:05 +00:00
_logger . default . log ( "info" , ` NO FACILITATORS LEFT, SENDING SIGNAL TO END CHAT ` ) ;
2020-09-05 22:11:06 +00:00
this . sendBotSignal ( member . roomId , BOT _SIGNAL _END _CHAT ) ;
}
} catch ( err ) {
_logger . default . log ( "error" , ` ERROR SENDING BOT SIGNAL ==> ${ err } ` ) ;
2020-03-16 18:41:56 +00:00
}
2020-04-22 22:04:48 +00:00
}
} ) ;
}
2020-09-06 18:07:44 +00:00
setTimeoutforFacilitator ( roomId ) {
2020-09-11 20:12:07 +00:00
setTimeout ( async ( ) => {
2020-09-06 18:07:44 +00:00
const stillWaiting = this . localStorage . getItem ( ` ${ roomId } -waiting ` ) ;
if ( stillWaiting ) {
2020-12-01 03:47:32 +00:00
await this . sendTextMessage ( roomId , this . config . chatNotAvailableMessage ) ;
2020-12-01 18:21:16 +00:00
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 . default . log ( "info" , ` NO FACILITATORS JOINED THE CHAT WITHIN THE MAXIMUM WAIT TIME, CHAT CLOSED AT ${ closedTime } (room ID: ${ roomRef } ) ` ) ;
2020-09-06 18:07:44 +00:00
}
2020-12-01 18:21:16 +00:00
} , this . config . maxWaitTime * 1000 ) ; // convert seconds to milliseconds
2020-09-06 18:07:44 +00:00
}
setInactivityTimeout ( roomId ) {
const oldTimeout = this . inactivityTimers [ roomId ] ;
if ( oldTimeout ) {
clearTimeout ( oldTimeout ) ;
}
2020-09-11 20:12:07 +00:00
const newTimeout = setTimeout ( async ( ) => {
_logger . default . log ( "info" , ` CHAT IS INACTIVE, SENDING SIGNAL TO END CHAT ` ) ;
2020-12-01 03:47:32 +00:00
await this . sendTextMessage ( roomId , this . config . chatInactiveMessage ) ;
2020-09-06 18:07:44 +00:00
this . sendBotSignal ( roomId , BOT _SIGNAL _END _CHAT ) ;
2020-12-01 18:21:16 +00:00
} , this . config . maxInactiveTime * 1000 ) ; // convert seconds to milliseconds
2020-09-06 18:07:44 +00:00
this . inactivityTimers [ roomId ] = newTimeout ;
}
2020-04-22 22:04:48 +00:00
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 ) ;
}
} ) ;
}
2020-09-05 22:11:06 +00:00
async sendBotSignal ( roomId , signal , args ) {
let content = {
signal : signal ,
args : args
} ;
2020-09-06 18:07:44 +00:00
try {
await this . client . sendStateEvent ( roomId , 'm.bot.signal' , content ) ;
} catch ( err ) {
_logger . default . log ( 'error' , "ERROR SENDING BOT SIGNAL => " + err ) ;
}
2020-09-05 22:11:06 +00:00
}
2020-04-22 22:04:48 +00:00
async start ( ) {
try {
2020-09-07 15:54:05 +00:00
const localStorage = this . createLocalStorage ( ) ;
this . localStorage = localStorage ;
2020-04-22 22:04:48 +00:00
const auth = {
2020-12-01 03:47:32 +00:00
user : this . config . botUsername ,
password : this . config . botPassword ,
initial _device _display _name : this . config . botDisplayName
2020-04-22 22:04:48 +00:00
} ;
const account = await this . client . login ( "m.login.password" , auth ) ;
_logger . default . log ( "info" , ` ACCOUNT ==> ${ JSON . stringify ( account ) } ` ) ;
let opts = {
2020-12-01 03:47:32 +00:00
baseUrl : this . config . matrixServerUrl ,
2020-04-22 22:04:48 +00:00
accessToken : account . access _token ,
2020-12-01 03:47:32 +00:00
userId : this . config . botUserId ,
2020-04-22 22:04:48 +00:00
deviceId : account . device _id ,
sessionStore : new matrix . WebStorageSessionStore ( localStorage )
} ;
this . client = matrix . createClient ( opts ) ;
await this . client . initCrypto ( ) ;
this . client . startClient ( {
initialSyncLimit : 0
2020-03-16 18:41:56 +00:00
} ) ;
2020-07-25 23:07:08 +00:00
this . client . once ( 'sync' , async ( state , prevState , data ) => {
_logger . default . log ( "info" , ` SYNC STATUS: ${ state } ` ) ;
if ( state === 'PREPARED' ) {
await this . deleteOldDevices ( ) ;
2020-09-07 15:54:05 +00:00
await this . leaveEmptyRooms ( ) ;
2020-07-25 23:07:08 +00:00
await this . trackJoinedRooms ( ) ;
await this . setMembershipListeners ( ) ;
await this . setMessageListeners ( ) ;
}
} ) ;
2020-04-22 22:04:48 +00:00
} catch ( err ) {
this . handleBotCrash ( undefined , err ) ;
_logger . default . log ( "error" , ` ERROR INITIALIZING CLIENT: ${ err } ` ) ;
2020-03-16 18:41:56 +00:00
}
2020-04-22 22:04:48 +00:00
}
2020-03-16 18:41:56 +00:00
2020-04-22 22:04:48 +00:00
}
2020-03-16 18:41:56 +00:00
var _default = OcrccBot ;
2020-04-22 22:04:48 +00:00
exports . default = _default ;