@wickr-sample-integrations/wickrio-rekognition-bot
Version:
WickrIO Rekognition Bot
432 lines (376 loc) • 12.9 kB
JavaScript
const WickrIOBotAPI = require('wickrio-bot-api');
const util = require('util')
const logger = require('wickrio-bot-api').logger
const FileReader = require('filereader')
const Stream = require('stream')
//var FileAPI = require('file-api')
// , File = FileAPI.File
const bot = new WickrIOBotAPI.WickrIOBot();
const WickrIOAPI = bot.apiService().WickrIOAPI;
const fs = require('fs')
const path = require('path')
const { RecognizeCelebritiesCommand, DetectFacesCommand, RekognitionClient } = require('@aws-sdk/client-rekognition');
const { fromIni } = require('@aws-sdk/credential-providers');
const { DetectLabelsCommand } = require("@aws-sdk/client-rekognition");
// snippet-start:[s3.JavaScript.buckets.uploadV3]
const { PutObjectCommand, S3Client } = require("@aws-sdk/client-s3");
const { fileURLToPath } = require("url");
console.log = function () {
logger.info(util.format.apply(null, arguments))
}
console.error = function () {
logger.error(util.format.apply(null, arguments))
}
/*
* Set the AWS Region and the profile from the .aws/credentials file
* TODO These values should be configured
*/
var REGION = ""
var profileName = ""
const s3Bucket = "rekognize4bots";
module.exports = WickrIOAPI;
process.stdin.resume(); //so the program will not close instantly
async function exitHandler(options, err) {
try {
var closed = await bot.close();
console.log(closed);
if (err) {
console.log("Exit Error:", err);
process.exit();
}
if (options.exit) {
process.exit();
} else if (options.pid) {
process.kill(process.pid);
}
} catch (err) {
console.log(err);
}
}
//catches ctrl+c and stop.sh events
process.on('SIGINT', exitHandler.bind(null, {
exit: true
}));
// catches "kill pid" (for example: nodemon restart)
process.on('SIGUSR1', exitHandler.bind(null, {
pid: true
}));
process.on('SIGUSR2', exitHandler.bind(null, {
pid: true
}));
//catches uncaught exceptions
process.on('uncaughtException', exitHandler.bind(null, {
exit: true
}));
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function main() {
logger.info('entering main')
try {
var status;
if (process.argv[2] === undefined) {
var bot_username = fs.readFileSync('client_bot_username.txt', 'utf-8');
bot_username = bot_username.trim();
status = await bot.start(bot_username)
} else {
status = await bot.start(process.argv[2])
}
// set the avatar
const avatarPath=path.join(process.cwd(), 'AWS_Rekognition.png')
logger.info(`avatar path:${avatarPath}`)
if (await WickrIOAPI.cmdSetAvatar(avatarPath)) {
logger.info('avatar set')
} else {
logger.info('avatar NOT set')
}
bot.processesJsonToProcessEnv()
var tokens = JSON.parse(process.env.tokens)
// Set the global configuration values
REGION = tokens.AWS_REGION.value
profileName = tokens.AWS_PROFILENAME.value
if (!status) {
exitHandler(null, {
exit: true,
reason: 'Client not able to start'
});
}
await bot.startListening(listen); //Passes a callback function that will receive incoming messages into the bot client
} catch (err) {
logger.error(err);
}
}
// Calls DetectFaces API and shows estimated ages of detected faces.
async function DetectFaces(imageData, vGroupID)
{
// Set the parameters.
var params = {
Image: {
Bytes: imageData,
},
Attributes: ["ALL"],
};
try {
/*
const rekogClient = new RekognitionClient({region: REGION,
credentials: fromIni({profile: profileName,}),
});
const data = await rekogClient.send(new DetectFacesCommand(params));
// show each face and build out estimated age table
for (var i = 0; i < data.FaceDetails.length; i++) {
table +=
"<tr><td>" +
data.FaceDetails[i].AgeRange.Low +
"</td><td>" +
data.FaceDetails[i].AgeRange.High +
"</td></tr>";
}
table += "</table>";
document.getElementById("opResult").innerHTML = table;
return data; // For unit tests.
*/
// const paramrstring = JSON.stringify(params)
// logger.info(`params=${paramrstring}`)
const detect_labels = async () => {
var rekogClient
try {
console.log('calling Rekognitionclient')
rekogClient = new RekognitionClient({region: REGION,
credentials: fromIni({profile: profileName,}),
});
} catch(err) {
console.error(`Error from new RekognitionClient ${err}`);
return;
}
var response
try {
console.log('calling rekogClient.send')
response = await rekogClient.send(new RecognizeCelebritiesCommand(params));
if (response=== undefined) {
console.error('RekognitionClient send returned undefined!')
return;
}
} catch (err) {
console.error(`Error from rekogClient.send ${err}`);
return;
}
try {
const responsestring = JSON.stringify(response)
logger.info(`response=${responsestring}`)
if (response.CelebrityFaces === undefined) {
logger.info('response.CelebrityFaces == undefined')
var sMessage = await WickrIOAPI.cmdSendRoomMessage(vGroupID, "Did not detect a celebrity face")
return
}
// logger.info(`response.Labels=${response.Labels}`)
response.CelebrityFaces.forEach(async celebrity => {
logger.info(`Name: ${celebrity.Name}`)
logger.info(`MatchConfidence: ${celebrity.MatchConfidence}`)
logger.info("-------")
const responseString = `*Celebrity*: ${celebrity.Name}\n*Confidence*: ${celebrity.MatchConfidence}`;
var sMessage = await WickrIOAPI.cmdSendRoomMessage(vGroupID, responseString)
})
if (response.UnrecognizedFaces !== undefined && response.UnrecognizedFaces.length > 0) {
const responseString = "There are " + response.UnrecognizedFaces.length + " faces that are unrecognized";
var sMessage = await WickrIOAPI.cmdSendRoomMessage(vGroupID, responseString)
}
return response; // For unit tests.
} catch (err) {
console.error(`Error ${err}`);
}
};
detect_labels();
} catch (err) {
console.log("Error", err);
}
}
async function File2Base64(filename, vGroupID)
{
console.log('Entered File2Base64: filename=', filename)
var base64ImageStr
try {
// read image file
var data = fs.readFileSync(filename)
// get image file extension name
const extensionName = path.extname(filename);
// convert image file to base64-encoded string
const base64Image = Buffer.from(data, 'binary').toString('base64');
// combine all strings
// base64ImageStr = `${base64Image}`;
base64ImageStr = `data:image/jpeg;base64,${base64Image}`;
// base64ImageStr = `data:image/${extensionName.split('.').pop()};base64,${base64Image}`;
return base64ImageStr
} catch (err) {
console.error(err)
return undefined
}
}
// Loads selected image and unencodes image bytes for Rekognition DetectFaces API.
async function ProcessImage(filename, vGroupID)
{
console.log('Entered ProcessImage: filename=', filename)
try {
// var base64image = await File2Base64(filename, vGroupID)
var data
try {
// read image file
data = fs.readFileSync(filename)
} catch (err) {
console.error(err)
return undefined
}
if (data === undefined) {
console.error('failed to get file data!')
} else {
/*
const length = data.length;
const imageBytes = new ArrayBuffer(length);
const ua = new Uint8Array(imageBytes);
for (var i = 0; i < length; i++) {
ua[i] = data.charCodeAt(i);
}
*/
// Call Rekognition.
DetectFaces(data, vGroupID);
}
} catch (err) {
console.error(err)
}
}
async function listen(rMessage) {
logger.info('entering listen')
rMessage = JSON.parse(rMessage);
var sender = rMessage.sender;
var vGroupID = rMessage.vgroupid;
var userArr = [];
userArr.push(sender);
if (rMessage.message) {
var request = rMessage.message;
var command = '',
argument = '';
var parsedData = request.match(/(\/[a-zA-Z]+)(@[a-zA-Z0-9_-]+)?(\s+)?(.*)$/);
if (parsedData !== null) {
command = parsedData[1];
if (parsedData[4] !== '') {
argument = parsedData[4];
}
}
/*
* LIST Command
*/
if (command === '/list') {
const header = 'List of files in the given directory:'
let messagemeta = {
table: {
name: header,
firstcolname: 'File',
actioncolname: 'Select',
rows: [],
},
textcut: []
}
let fileArr = [];
fileArr.push(header);
fs.readdirSync('files/').forEach(file => {
const fileName = file.toString()
fileArr.push(fileName);
// Create entry in table
const row = {
firstcolvalue: fileName,
response: fileName,
}
messagemeta.table.rows.push(row)
});
// Generate the string to send
const msg2send = fileArr.join('\n');
if (messagemeta.table.rows.length > 0) {
const textcut = {
startindex: header.length,
endindex: msg2send.length,
}
messagemeta.textcut.push(textcut)
}
const messagemetastring = JSON.stringify(messagemeta)
logger.info('messageMetaString=', messagemetastring)
try {
var sMessage = await WickrIOAPI.cmdSendRoomMessage(vGroupID, msg2send, "", "", "", [], messagemetastring);
logger.info(sMessage);
} catch (err) {
logger.error(err);
}
}
/*
* HELP Command
*/
else if (command === '/help') {
const help = "Send me a picture of a celebrity and I will try to recognize which celebrities are in the picture. I will respond with a list of celebrities and a number of the faces I could not recognize."
var sMessage = await WickrIOAPI.cmdSendRoomMessage(vGroupID, help);
logger.info(sMessage);
}
else if (command === '/rekognize') {
var attachment = argument;
logger.info(`attachment=${attachment}`)
const words = attachment.split(' ');
if (words.length !== 2) {
var sMessage = await WickrIOAPI.cmdSendRoomMessage(vGroupID, 'Invalid usage: /rekognize <S3 bucket> <S3 filename>');
} else {
const bucket = words[0]
const photo = words[1]
logger.info(`bucket=${bucket}`)
logger.info(`photo=${photo}`)
// Set params
const params = {
Image: {
S3Object: {
Bucket: bucket,
Name: photo
},
},
}
const paramrstring = JSON.stringify(params)
logger.info(`params=${paramrstring}`)
const detect_labels = async () => {
try {
const rekogClient = new RekognitionClient({region: REGION,
credentials: fromIni({profile: profileName,}),
});
const response = await rekogClient.send(new DetectLabelsCommand(params));
logger.info(`response.Labels=${response.Labels}`)
response.Labels.forEach(async label =>{
const labelString = JSON.stringify(label)
var sMessage = await WickrIOAPI.cmdSendRoomMessage(vGroupID, labelString)
logger.info(`Confidence: ${label.Confidence}`)
logger.info(`Name: ${label.Name}`)
logger.info('Instances:')
label.Instances.forEach(instance => {
logger.info(instance)
})
logger.info('Parents:')
label.Parents.forEach(name => {
logger.info(name)
})
logger.info("-------")
})
return response; // For unit tests.
} catch (err) {
logger.error(`Error ${err}`);
}
};
detect_labels();
}
} else {
logger.error('bad command');
var sMessage = await WickrIOAPI.cmdSendRoomMessage(vGroupID, 'Invalid command');
}
} else if (rMessage.file) {
logger.info('processing file')
const filename=rMessage.file.localfilename.toString()
logger.info(`filename=${filename}`)
await ProcessImage(filename, vGroupID)
fs.unlinkSync(filename)
} else {
logger.error('bad command');
var sMessage = await WickrIOAPI.cmdSendRoomMessage(vGroupID, 'Invalid command');
}
}
main();