changes from glitch
This commit is contained in:
parent
18c8524949
commit
7b2da6b351
@ -4,14 +4,14 @@
|
||||
"description": "Chatbot to manage interactions on OCRCC client chatbots",
|
||||
"main": "dist/ocrcc-chatbot.js",
|
||||
"scripts": {
|
||||
"start": "nodemon -r dotenv/config --exec babel-node src/index.js"
|
||||
"develop": "nodemon --exec babel-node src/index.js",
|
||||
"build": "babel src -d dist",
|
||||
"start": "yarn build && node dist/index.js"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"dotenv": "^8.2.0",
|
||||
"matrix-bot-sdk": "^0.5.0",
|
||||
"matrix-js-sdk": "^5.0.0",
|
||||
"matrix-js-sdk": "^5.0.1",
|
||||
"node-localstorage": "^2.1.5",
|
||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
|
||||
"uuidv4": "^6.0.2",
|
||||
|
269
src/index.js
269
src/index.js
@ -10,15 +10,18 @@ import * as matrix from "matrix-js-sdk";
|
||||
|
||||
import logger from "./logger";
|
||||
|
||||
|
||||
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;
|
||||
|
||||
class OcrccBot {
|
||||
constructor() {
|
||||
this.awaitingAgreement = {};
|
||||
this.awaitingFacilitator = {};
|
||||
this.client = matrix.createClient(process.env.MATRIX_SERVER_URL);
|
||||
this.joinedRooms = [];
|
||||
this.activeChatrooms = {};
|
||||
}
|
||||
|
||||
createLocalStorage() {
|
||||
@ -32,28 +35,86 @@ class OcrccBot {
|
||||
}
|
||||
|
||||
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.client.sendTextMessage(roomId, msgText).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;
|
||||
}
|
||||
});
|
||||
});
|
||||
return this.sendMessage(roomId, msgText);
|
||||
break;
|
||||
default:
|
||||
logger.log("error", `ERROR SENDING MESSAGE: ${err}`);
|
||||
this.handleBotCrash(roomId, err);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inviteUserToRoom(client, roomId, member, retries = 0) {
|
||||
logger.log("info", "INVITING MEMBER: " + member);
|
||||
if (retries > MAX_RETRIES) {
|
||||
this.handleBotCrash(roomId, "Rate limit exceeded for bot account");
|
||||
return logger.log(
|
||||
"error",
|
||||
`RATE LIMIT EXCEEDED AND RETRY LIMIT EXCEEDED`
|
||||
);
|
||||
}
|
||||
return client.invite(roomId, member).catch(err => {
|
||||
switch (err["name"]) {
|
||||
case "M_LIMIT_EXCEEDED":
|
||||
logger.log("info", "Rate limit exceeded, retrying.");
|
||||
const retryCount = retries + 1;
|
||||
const delay = retryCount * 2 * 1000;
|
||||
return setTimeout(
|
||||
this.inviteUserToRoom,
|
||||
delay,
|
||||
client,
|
||||
roomId,
|
||||
member,
|
||||
retryCount
|
||||
);
|
||||
break;
|
||||
default:
|
||||
logger.log("error", `ERROR INVITING MEMBER: ${err}`);
|
||||
this.handleBotCrash(roomId, err);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
kickUserFromRoom(client, roomId, member, retries = 0) {
|
||||
logger.log("info", "KICKING OUT MEMBER: " + member);
|
||||
if (retries > MAX_RETRIES) {
|
||||
this.handleBotCrash(roomId, "Rate limit exceeded for bot account.");
|
||||
return logger.log(
|
||||
"error",
|
||||
`RATE LIMIT EXCEEDED AND RETRY LIMIT EXCEEDED`
|
||||
);
|
||||
}
|
||||
return client.kick(roomId, member, KICK_REASON).catch(err => {
|
||||
switch (err["name"]) {
|
||||
case "M_LIMIT_EXCEEDED":
|
||||
logger.log("info", "Rate limit exceeded, retrying.");
|
||||
const retryCount = retries + 1;
|
||||
const delay = retryCount * 2 * 1000;
|
||||
return setTimeout(
|
||||
this.kickUserFromRoom,
|
||||
delay,
|
||||
client,
|
||||
roomId,
|
||||
member,
|
||||
retryCount
|
||||
);
|
||||
break;
|
||||
default:
|
||||
this.handleBotCrash(roomId, err);
|
||||
logger.log("error", `ERROR KICKING OUT MEMBER: ${err}`);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inviteFacilitators(roomId) {
|
||||
@ -66,9 +127,8 @@ class OcrccBot {
|
||||
Object.keys(members["joined"]).forEach(member => {
|
||||
const user = this.client.getUser(member);
|
||||
if (user.presence === "online" && member !== process.env.BOT_USERID) {
|
||||
logger.log("info", "INVITING MEMBER: " + member);
|
||||
chatOffline = false;
|
||||
this.client.invite(roomId, member);
|
||||
this.inviteUserToRoom(this.client, roomId, member);
|
||||
}
|
||||
});
|
||||
})
|
||||
@ -78,8 +138,8 @@ class OcrccBot {
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
logger.log("error", "ERROR GETTING ROOM MEMBERS");
|
||||
logger.log("error", err);
|
||||
this.handleBotCrash(roomId, err);
|
||||
logger.log("error", `ERROR GETTING ROOM MEMBERS: ${err}`);
|
||||
});
|
||||
}
|
||||
|
||||
@ -93,20 +153,49 @@ class OcrccBot {
|
||||
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);
|
||||
});
|
||||
this.kickUserFromRoom(this.client, roomId, f);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(err => logger.log("error", err));
|
||||
.catch(err => {
|
||||
this.handleBotCrash(roomId, err);
|
||||
logger.log("error", err);
|
||||
});
|
||||
}
|
||||
|
||||
handleBotCrash(roomId, error) {
|
||||
if (roomId) {
|
||||
this.sendMessage(roomId, BOT_ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
this.sendMessage(
|
||||
process.env.FACILITATOR_ROOM_ID,
|
||||
`The Help Bot ran into an error: ${error}. Please verify that the chat service is working.`
|
||||
);
|
||||
}
|
||||
|
||||
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.activeChatrooms[roomId].transcriptFile;
|
||||
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = `${sender} [${time}]: ${content.body}\n`;
|
||||
|
||||
fs.appendFileSync(filepath, message, "utf8");
|
||||
} catch (err) {
|
||||
logger.log("error", `ERROR APPENDING TO TRANSCRIPT FILE: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
start() {
|
||||
@ -135,13 +224,36 @@ class OcrccBot {
|
||||
this.client = matrix.createClient(opts);
|
||||
})
|
||||
.catch(err => {
|
||||
logger.log("error", `Login error: ${err}`);
|
||||
logger.log("error", `ERROR WITH LOGIN: ${err}`);
|
||||
})
|
||||
.then(() =>
|
||||
this.client.initCrypto().catch(err => {
|
||||
logger.log("error", `ERROR STARTING CRYPTO: ${err}`);
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
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)}`
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(() => this.client.initCrypto())
|
||||
.catch(err => logger.log("error", `ERROR STARTING CRYPTO: ${err}`))
|
||||
.then(() =>
|
||||
this.client.getJoinedRooms().then(data => {
|
||||
this.joinedRooms = data["joined_rooms"];
|
||||
@ -163,17 +275,22 @@ class OcrccBot {
|
||||
process.env.FACILITATOR_ROOM_ID,
|
||||
`A support seeker requested a chat (Room ID: ${member.roomId})`
|
||||
);
|
||||
this.client.setRoomEncryption(member.roomId, ENCRYPTION_CONFIG);
|
||||
})
|
||||
.then(() => this.inviteFacilitators(member.roomId));
|
||||
.then(() => this.inviteFacilitators(member.roomId))
|
||||
.catch(err => {
|
||||
logger.log("error", err);
|
||||
});
|
||||
}
|
||||
|
||||
// When the first facilitator joins a support session, uninvite the other facilitators
|
||||
// When a facilitator joins a support session, revoke the other invitations
|
||||
if (
|
||||
member.membership === "join" &&
|
||||
member.userId !== process.env.BOT_USERID &&
|
||||
this.awaitingFacilitator[member.roomId]
|
||||
) {
|
||||
this.activeChatrooms[member.roomId] = {
|
||||
facilitator: member.userId
|
||||
};
|
||||
this.sendMessage(
|
||||
member.roomId,
|
||||
`${member.name} has joined the chat.`
|
||||
@ -183,10 +300,68 @@ class OcrccBot {
|
||||
`${member.name} joined the chat (Room ID: ${member.roomId})`
|
||||
);
|
||||
this.uninviteFacilitators(member.roomId);
|
||||
if (process.env.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.activeChatrooms[member.roomId].transcriptFile = filepath;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
member.membership === "leave" &&
|
||||
member.userId !== process.env.BOT_USERID &&
|
||||
this.activeChatrooms[member.roomId] &&
|
||||
member.userId === this.activeChatrooms[member.roomId].facilitator
|
||||
) {
|
||||
this.sendMessage(
|
||||
member.roomId,
|
||||
`${member.name} has left the chat.`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (process.env.CAPTURE_TRANSCRIPTS) {
|
||||
// encrypted messages
|
||||
this.client.on("Event.decrypted", (event, err) => {
|
||||
if (err) {
|
||||
return logger.log("error", `ERROR DECRYPTING EVENT: ${err}`);
|
||||
}
|
||||
if (event.getType() === "m.room.message") {
|
||||
this.writeToTranscript(event);
|
||||
}
|
||||
});
|
||||
// unencrypted messages
|
||||
this.client.on("Room.timeline", (event, room, toStartOfTimeline) => {
|
||||
if (
|
||||
event.getType() === "m.room.message" &&
|
||||
!this.client.isCryptoEnabled()
|
||||
) {
|
||||
if (event.isEncrypted()) {
|
||||
return;
|
||||
}
|
||||
this.writeToTranscript(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => this.client.startClient());
|
||||
.then(() => this.client.startClient({ initialSyncLimit: 0 }))
|
||||
.catch(err => {
|
||||
this.handleBotCrash(undefined, err);
|
||||
logger.log("error", `ERROR INITIALIZING CLIENT: ${err}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
0
transcripts/.keep
Normal file
0
transcripts/.keep
Normal file
Loading…
Reference in New Issue
Block a user