2020-02-23 18:58:09 +00:00
import * as fs from 'fs'
import * as os from 'os'
import * as path from 'path'
import * as util from 'util'
import { LocalStorage } from "node-localstorage" ;
import { uuid } from "uuidv4"
import config from 'config' ;
2020-02-23 00:30:27 +00:00
global . Olm = require ( 'olm' ) ;
2020-02-23 18:58:09 +00:00
import * as matrix from "matrix-js-sdk" ;
import logger from './logger'
2020-02-23 00:30:27 +00:00
const ENCRYPTION _CONFIG = { "algorithm" : "m.megolm.v1.aes-sha2" } ;
2020-02-23 18:58:09 +00:00
class OcrccBot {
constructor ( ) {
this . awaitingAgreement = { }
this . awaitingFacilitator = { }
this . client = matrix . createClient ( config . get ( 'homeserverUrl' ) )
}
2020-02-23 00:30:27 +00:00
2020-02-23 18:58:09 +00:00
createLocalStorage ( ) {
const storageLoc = ` matrix-chatbot- ${ config . get ( 'username' ) } `
2020-02-23 00:30:27 +00:00
const dir = path . resolve ( path . join ( os . homedir ( ) , ".local-storage" ) )
if ( ! fs . existsSync ( dir ) ) {
fs . mkdirSync ( dir ) ;
}
const localStoragePath = path . resolve ( path . join ( dir , storageLoc ) )
2020-02-23 18:58:09 +00:00
return new LocalStorage ( localStoragePath ) ;
2020-02-23 00:30:27 +00:00
}
2020-02-23 18:58:09 +00:00
sendMessage ( roomId , msgText ) {
return this . client . sendTextMessage ( roomId , msgText )
. then ( ( res ) => {
logger . log ( 'info' , "Message sent" )
logger . log ( 'info' , res )
} )
. catch ( ( err ) => {
switch ( err [ "name" ] ) {
case "UnknownDeviceError" :
Object . keys ( err . devices ) . forEach ( ( userId ) => {
Object . keys ( err . devices [ userId ] ) . map ( ( deviceId ) => {
this . client . setDeviceVerified ( userId , deviceId , true ) ;
} ) ;
} ) ;
return this . sendMessage ( roomId , msgText )
break ;
default :
logger . log ( 'error' , "Error sending message" ) ;
logger . log ( 'error' , err ) ;
break ;
}
} )
}
2020-02-23 17:01:15 +00:00
2020-02-23 18:58:09 +00:00
sendHtmlMessage ( roomId , msgText , msgHtml ) {
return this . client . sendHtmlMessage ( roomId , msgText , msgHtml )
. then ( ( res ) => {
logger . log ( 'info' , "Message sent" )
logger . log ( 'info' , res )
} )
. catch ( ( err ) => {
switch ( err [ "name" ] ) {
case "UnknownDeviceError" :
Object . keys ( err . devices ) . forEach ( ( userId ) => {
Object . keys ( err . devices [ userId ] ) . map ( ( deviceId ) => {
this . client . setDeviceVerified ( userId , deviceId , true ) ;
} ) ;
} ) ;
return this . sendHtmlMessage ( roomId , msgText , msgHtml )
break ;
default :
logger . log ( 'error' , "Error sending message" ) ;
logger . log ( 'error' , err ) ;
break ;
2020-02-23 17:01:15 +00:00
}
} )
2020-02-23 18:58:09 +00:00
}
2020-02-23 00:30:27 +00:00
2020-02-23 18:58:09 +00:00
inviteFacilitators ( roomId ) {
this . awaitingFacilitator [ roomId ] = true
this . client . getJoinedRoomMembers ( config . get ( 'waitingRoomId' ) )
. then ( ( members ) => {
Object . keys ( members [ "joined" ] ) . forEach ( ( member ) => {
if ( member !== config . get ( 'userId' ) )
this . client . invite ( roomId , member )
} )
} )
// const notif = `There is a support seeker waiting. Go to https://riot.im/app/#/room/${roomId} to respond.`
// sendMessage(waitingRoomId, notif)
2020-02-23 00:30:27 +00:00
}
2020-02-23 18:58:09 +00:00
uninviteFacilitators ( roomId ) {
this . awaitingFacilitator [ roomId ] = false
this . client . getJoinedRoomMembers ( config . get ( 'waitingRoomId' ) )
. then ( ( allFacilitators ) => {
this . client . getJoinedRoomMembers ( roomId )
. then ( ( roomMembers ) => {
const membersIds = Object . keys ( roomMembers [ "joined" ] )
const facilitatorsIds = Object . keys ( allFacilitators [ "joined" ] )
facilitatorsIds . forEach ( ( f ) => {
if ( ! membersIds . includes ( f ) ) {
logger . log ( "info" , "kicking out " + f + " from " + roomId )
this . client . kick ( roomId , f , "A facilitator has already joined this chat." )
. then ( ( ) => {
logger . log ( "info" , "Kick success" )
} )
. catch ( ( err ) => {
logger . log ( "error" , err )
} )
}
} )
2020-02-23 00:30:27 +00:00
} )
2020-02-23 18:58:09 +00:00
} )
}
2020-02-23 17:01:15 +00:00
2020-02-23 18:58:09 +00:00
start ( ) {
const localStorage = this . createLocalStorage ( )
let deviceId = localStorage . getItem ( 'deviceId' )
2020-02-23 17:01:15 +00:00
2020-02-23 18:58:09 +00:00
this . client . login ( 'm.login.password' , {
user : config . get ( 'username' ) ,
password : config . get ( 'password' ) ,
initial _device _display _name : config . get ( 'botName' ) ,
deviceId : deviceId ,
} )
. then ( ( data ) => {
const accessToken = data . access _token
const deviceId = data . device _id
localStorage . setItem ( 'deviceId' , data . device _id )
// create new client with full options
let opts = {
baseUrl : config . get ( 'homeserverUrl' ) ,
accessToken : accessToken ,
userId : config . get ( 'userId' ) ,
deviceId : deviceId ,
sessionStore : new matrix . WebStorageSessionStore ( localStorage ) ,
2020-02-23 00:30:27 +00:00
}
2020-02-23 18:58:09 +00:00
this . client = matrix . createClient ( opts )
} )
. catch ( err => {
logger . log ( 'error' , ` Login error: ${ err } ` )
} )
. then ( ( ) => this . client . initCrypto ( ) )
. then ( ( ) => {
// Automatically accept all room invitations
// On joining a room, send the intro messages and wait for agreement to continue
this . client . on ( "RoomMember.membership" , ( event , member ) => {
if ( member . membership === "invite" && member . userId === config . get ( 'userId' ) ) {
logger . log ( "info" , "Auto-joining room " + member . roomId )
this . client . joinRoom ( member . roomId )
. then ( ( ) => this . client . setRoomEncryption ( member . roomId , ENCRYPTION _CONFIG ) )
. then ( ( ) => {
if ( member . roomId !== config . get ( 'waitingRoomId' ) ) {
this . sendMessage ( member . roomId , config . get ( 'introMessage' ) )
. then ( ( ) => this . sendHtmlMessage ( member . roomId , ` Please read the terms and conditions at ${ config . get ( 'termsUrl' ) } ` , ` Please read the full <a href=" ${ config . get ( 'termsUrl' ) } ">terms and conditions</a>. ` ) )
. then ( ( ) => this . sendMessage ( member . roomId , config . get ( 'agreementMessage' ) ) )
. then ( ( ) => this . awaitingAgreement [ member . roomId ] = true )
}
} )
}
// When the first facilitator joins a support session, uninvite the other facilitators
if ( member . membership === 'join' && this . awaitingFacilitator [ member . roomId ] ) {
this . uninviteFacilitators ( member . roomId )
}
} ) ;
// Listen for incoming messages
this . client . on ( 'Event.decrypted' , ( event ) => {
if ( event . getType ( ) === 'm.room.message' ) {
const roomId = event . getRoomId ( )
const sender = event . getSender ( )
const content = event . getContent ( )
const body = content . body
// Listen for the user to agree to continue, then invite facilitators to join
if ( sender !== config . get ( 'userId' ) && this . awaitingAgreement [ roomId ] ) {
if ( body . toLowerCase ( ) . startsWith ( 'yes' ) ) {
this . sendMessage ( roomId , config . get ( 'confirmationMessage' ) )
this . inviteFacilitators ( roomId )
this . awaitingAgreement [ roomId ] = false
} else {
this . sendMessage ( roomId , config . get ( 'exitMessage' ) )
this . awaitingAgreement [ roomId ] = false
}
}
}
} ) ;
} )
. finally ( ( ) => this . client . startClient ( ) )
}
}
const bot = new OcrccBot ( ) ;
bot . start ( )