safesupport-bot/src/index.js

206 lines
6.9 KiB
JavaScript
Raw Normal View History

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()