597 lines
21 KiB
JavaScript
597 lines
21 KiB
JavaScript
#!/usr/bin/env node
|
|
/*
|
|
*****************************************************************
|
|
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
|
|
|||||||||||||||||||||||||||||
|
|
| "||,,,,. "|" .,,,,||" ____
|
|
| .d8888b. .d8888b. ( ) _____
|
|
/|\ o8' o '8o o8'o `8o | | ( )
|
|
||| o8. .8o o8. .8o | | _______ | |
|
|
`Y8888P' `Y8888P' | |( )| |
|
|
,||''|| \ / ||''||, | || || |
|
|
,|| ||, \ / .|| ||, | || || |
|
|
|| || ` || || | || || |
|
|
,|| '|| ||' ||, | || || | _
|
|
|| '|| ||' || | || || || \
|
|
|| |; ;| || | || || || '
|
|
|| ,| |, || | || || || '
|
|
||, ,|| ||, ,|| | || || || |
|
|
||, ,||| |||, ,|| | || || || |
|
|
'||,,||||,...,||||,,|| | || || ||___|_
|
|
`|||..."|||"...|||' (____)(_______)(_____)|____|
|
|
|%%%%%%%%WWWW%%%%%%WWWW%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%|
|
|
`""""""""""3$F""""#$F""""""""""""""""""""""""""""""""""""""""'
|
|
@$.... '$B
|
|
d$$$$$$$$$$:
|
|
````````````
|
|
Basebot by Joe McQueen - 2020
|
|
|
|
Purpose: Template for bots
|
|
|
|
To do:
|
|
|
|
*/
|
|
|
|
//Webex Framework APIs
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import framework from 'webex-node-bot-framework';
|
|
import express from 'express';
|
|
import bodyParser from 'body-parser';
|
|
import fetch from 'node-fetch';
|
|
import cron from 'node-cron';
|
|
|
|
var config = JSON.parse(fs.readFileSync('./config/config.json'));
|
|
var schedule = JSON.parse(fs.readFileSync('./config/schedule.json'));
|
|
var authorizedMembers = JSON.parse(fs.readFileSync('./config/authorized.json'));
|
|
|
|
var app = express();
|
|
app.use(bodyParser.json());
|
|
app.use(bodyParser.urlencoded({ extended: false }));
|
|
|
|
//Main function to run on startUp
|
|
var server = app.listen(config.server.port, function () { logger("startup", config.server.name + " running on port " + config.server.port + ".") });
|
|
|
|
app.get('/status', function (req, res) {
|
|
res.status(200).send({ "status": `Alive and kicking. ${authorizedMembers.length} total names in authorizedMembers.` });
|
|
})
|
|
|
|
app.post('/members', function (req, res) {
|
|
saveJSON(req.body, ('./config/authorized.json'))
|
|
|
|
res.status(200).send({ "status": "Authorized users updated." });
|
|
authorizedMembers = JSON.parse(fs.readFileSync('./config/authorized.json'));
|
|
logger(`POST /members`, `Authorized users updated.`)
|
|
|
|
|
|
})
|
|
|
|
/*app.get('/codes', (req, res) => res.json(schedule))
|
|
|
|
app.get('/code', function (req, res) {
|
|
var nowDate = Date.now();
|
|
//console.log("Now:" + nowDate);
|
|
for (var codes in schedule) {
|
|
//console.log(code);
|
|
var startDate = Date.parse(schedule[codes].start);
|
|
var endDate = Date.parse(schedule[codes].end);
|
|
//console.log("Code: " + code + " Start: " + startDate + " End: " + endDate );
|
|
if ((nowDate.valueOf() <= endDate.valueOf() && nowDate.valueOf() >= startDate.valueOf())) {
|
|
//console.log(startDate + "\t" + nowDate + "\t" + endDate);
|
|
var influxPoint = 'discount,type=api count=1';
|
|
//sendInfluxPoint(influxPoint);
|
|
buildCard(schedule[codes])
|
|
.then(discountCode => {
|
|
res.send(discountCode)
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
});*/
|
|
|
|
// init framework
|
|
var Framework = new framework(config.auth.webex.bot);
|
|
Framework.start();
|
|
console.log("Starting framework, please wait...");
|
|
|
|
Framework.on("initialized", function () {
|
|
console.log("framework is all fired up! [Press CTRL-C to quit]");
|
|
});
|
|
|
|
|
|
//framework.on('log', (msg) => {
|
|
// console.log(msg);
|
|
//});
|
|
|
|
Framework.on('membershipRulesAction', (type, event, bot, id, ...args) => {
|
|
console.log(`Framework membershipRulesAction of type:${type}, event:${event} occurred in space "${bot.room.title}".`);
|
|
try {
|
|
switch (type) {
|
|
case ('state-change'):
|
|
console.log(`Membership Rules forced a "${event}" event`);
|
|
break;
|
|
case ('event-swallowed'):
|
|
if (event === 'spawn') {
|
|
if (args.length >= 2) {
|
|
let actorId = args[0];
|
|
let membershipRuleChange = args[1];
|
|
let email = membershipRuleChange.membership.personEmail;
|
|
if (membershipRuleChange && membershipRuleChange.membershipRule === "restrictedToEmailDomains") {
|
|
console.log(`spawn swallowed. Dissallowed member: ${email}`);
|
|
} else {
|
|
console.log(`spawn swallowed. Space membership is missing one of ` +
|
|
'the users specified in the `guideEmails` framework config parameter');
|
|
}
|
|
}
|
|
}
|
|
if ((event === 'memberExits') || (event === 'memberEnters')) {
|
|
let member = args[0];
|
|
console.log(`Ignored ${event} for ${member.personEmail} in disallowed space.`);
|
|
}
|
|
break;
|
|
case ('hears-swallowed'):
|
|
console.log(`Membership Rules swallowed a "${event}" event`);
|
|
break;
|
|
default:
|
|
assert(true === false, `Got unexpected membershipsRules type: ${type}`);
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
console.error(`Failed processing mebershipRulesAction event "${event}": ${e.message}`);
|
|
}
|
|
});
|
|
|
|
//Process incoming Webex Teams messages
|
|
let responded = false;
|
|
/* On mention with command
|
|
ex User enters @botname help, the bot will write back in markdown
|
|
*/
|
|
Framework.hears(/help|what can i (do|say)|what (can|do) you do/i, function (bot, trigger) {
|
|
console.log(`someone needs help! They asked ${trigger.text}`);
|
|
responded = true;
|
|
|
|
bot.say("markdown","Say the word 'code' or 'discount' and press enter.")
|
|
});
|
|
|
|
Framework.hears(/discount|code/i, function (bot, trigger) {
|
|
responded = true;
|
|
//console.log(trigger);
|
|
logger(`Framework.hears(/discount|code/i`, `${trigger.person.displayName} (${trigger.person.userName}) requested a code.`)
|
|
var nowDate = Date.now();
|
|
|
|
if (authorizedMembers.includes(trigger.person.displayName.toLowerCase()) || authorizedMembers.includes(trigger.person.userName.toLowerCase())) {
|
|
logger(`Framework.hears(/discount|code/i`, `${trigger.person.displayName} (${trigger.person.userName}) authorized.`)
|
|
var found = false;
|
|
for (var codes in schedule) {
|
|
var startDate = Date.parse(schedule[codes].start);
|
|
var endDate = Date.parse(schedule[codes].end);
|
|
if ((nowDate.valueOf() <= endDate.valueOf() && nowDate.valueOf() >= startDate.valueOf())) {
|
|
found = true;
|
|
|
|
buildCard(schedule[codes])
|
|
.then(discountCode => {
|
|
bot.sendCard(discountCode.card, discountCode.text)
|
|
})
|
|
}
|
|
}
|
|
if (!found) {
|
|
bot.dm("mcqueenj@ae.com", "markdown", "No code found")
|
|
}
|
|
|
|
let d = new Date();
|
|
let fileName = "requests-" + d.getFullYear() + "-" + (d.getMonth() + 1) + ".log";
|
|
let p = path.join('./logs/', fileName);
|
|
var stampData = d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds() + " " + (d.getMonth() + 1) + "/" + d.getDate() + "/" + d.getFullYear();
|
|
fs.appendFileSync(p, stampData + ": " + trigger.person.displayName + "," + trigger.person.userName + "\n");
|
|
} else {
|
|
logger(`Framework.hears(/discount|code/i`, `${trigger.person.displayName} (${trigger.person.userName}) failed authorization.`);
|
|
bot.reply(trigger.message, 'You are not authorized to access discount codes. If you believe this is in error, please contact the support center.');
|
|
bot.dm('mcqueenj@ae.com', `${trigger.person.displayName} requested a discount code but was not authorized.`);
|
|
}
|
|
});
|
|
|
|
// Process a submitted card
|
|
Framework.on('attachmentAction', function (bot, trigger) {
|
|
responded = true;
|
|
});
|
|
|
|
/* On mention with unexpected bot command
|
|
Its a good practice is to gracefully handle unexpected input
|
|
*/
|
|
Framework.hears(/.*/, function (bot, trigger) {
|
|
// This will fire for any input so only respond if we haven't already
|
|
if (!responded) {
|
|
console.log(`catch-all handler fired for user input: ${trigger.text}`);
|
|
bot.say(`Sorry, I don't know how to answer that command: ${trigger.text}`)
|
|
.then(() => sendHelp(bot))
|
|
.catch((e) => console.error(`Problem in the unexepected command hander: ${e.message}`));
|
|
}
|
|
responded = false;
|
|
});
|
|
|
|
function sendHelp(bot) {
|
|
bot.sendCard(helpCard, "Discount provides the latest discount codes for employees of AE, Aerie, Tailgate, and Todd Snyder.");
|
|
}
|
|
|
|
// gracefully shutdown (ctrl-c)
|
|
process.on('SIGINT', function () {
|
|
Framework.debug('Stopping DiscountBot...');
|
|
server.close();
|
|
Framework.stop().then(function () {
|
|
process.exit();
|
|
});
|
|
});
|
|
|
|
function buildCard(code) {
|
|
return new Promise(async function (resolve, reject) {
|
|
|
|
var discountCard = {
|
|
"type": "AdaptiveCard",
|
|
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
|
"version": "1.3",
|
|
"body": [
|
|
{
|
|
"type": "TextBlock",
|
|
"text": "Associate Discount Code for " + code.month,
|
|
"wrap": true,
|
|
"size": "Medium",
|
|
"horizontalAlignment": "Center",
|
|
"weight": "Bolder"
|
|
},
|
|
{
|
|
"type": "Container",
|
|
"items": [
|
|
{
|
|
"type": "TextBlock",
|
|
"text": "[AE & Aerie](https://www.ae.com)",
|
|
"wrap": true,
|
|
"weight": "Bolder",
|
|
"size": "ExtraLarge",
|
|
"separator": true,
|
|
"horizontalAlignment": "Center",
|
|
"spacing": "None"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": "US, Canada, & ROW",
|
|
"wrap": true,
|
|
"weight": "Lighter",
|
|
"size": "Small",
|
|
"separator": true,
|
|
"horizontalAlignment": "Center",
|
|
"spacing": "None"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": code.aeAerie.usrow.code,
|
|
"wrap": true,
|
|
"fontType": "Monospace",
|
|
"size": "ExtraLarge",
|
|
"weight": "Bolder",
|
|
"horizontalAlignment": "Center",
|
|
"spacing": "Padding"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": code.aeAerie.usrow.message,
|
|
"wrap": true,
|
|
"size": "Small",
|
|
"color": "Light",
|
|
"horizontalAlignment": "Center",
|
|
"weight": "Lighter",
|
|
"spacing": "Small"
|
|
}
|
|
],
|
|
"separator": true
|
|
},
|
|
{
|
|
"type": "Container",
|
|
"items": [
|
|
{
|
|
"type": "TextBlock",
|
|
"text": "[AE & Aerie](https://www.ae.com/mx/es)",
|
|
"wrap": true,
|
|
"weight": "Bolder",
|
|
"size": "ExtraLarge",
|
|
"separator": true,
|
|
"horizontalAlignment": "Center",
|
|
"spacing": "None"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": "Mexico",
|
|
"wrap": true,
|
|
"weight": "Lighter",
|
|
"size": "Small",
|
|
"separator": true,
|
|
"horizontalAlignment": "Center",
|
|
"spacing": "None"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": code.aeAerie.mexico.code,
|
|
"wrap": true,
|
|
"fontType": "Monospace",
|
|
"size": "ExtraLarge",
|
|
"weight": "Bolder",
|
|
"horizontalAlignment": "Center",
|
|
"spacing": "Padding"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": code.aeAerie.mexico.message,
|
|
"wrap": true,
|
|
"size": "Small",
|
|
"color": "Light",
|
|
"horizontalAlignment": "Center",
|
|
"weight": "Lighter",
|
|
"spacing": "Small"
|
|
}
|
|
],
|
|
"separator": true
|
|
},
|
|
|
|
{
|
|
"type": "Container",
|
|
"items": [
|
|
{
|
|
"type": "TextBlock",
|
|
"text": "[Todd Snyder](https://www.toddsnyder.com)",
|
|
"wrap": true,
|
|
"weight": "Bolder",
|
|
"size": "ExtraLarge",
|
|
"separator": true,
|
|
"horizontalAlignment": "Center",
|
|
"spacing": "None"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": code.toddSnyder.usrow.tstc.code,
|
|
"wrap": true,
|
|
"fontType": "Monospace",
|
|
"size": "ExtraLarge",
|
|
"weight": "Bolder",
|
|
"horizontalAlignment": "Center",
|
|
"spacing": "Padding"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": code.toddSnyder.usrow.tstc.message,
|
|
"wrap": true,
|
|
"size": "Small",
|
|
"color": "Light",
|
|
"horizontalAlignment": "Center",
|
|
"weight": "Lighter",
|
|
"spacing": "Small"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": code.toddSnyder.usrow.thirdparty.code,
|
|
"wrap": true,
|
|
"fontType": "Monospace",
|
|
"size": "ExtraLarge",
|
|
"weight": "Bolder",
|
|
"horizontalAlignment": "Center",
|
|
"spacing": "Padding"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": code.toddSnyder.usrow.thirdparty.message,
|
|
"wrap": true,
|
|
"size": "Small",
|
|
"color": "Light",
|
|
"horizontalAlignment": "Center",
|
|
"weight": "Lighter",
|
|
"spacing": "Small"
|
|
}
|
|
],
|
|
"separator": true
|
|
},
|
|
{
|
|
"type": "Container",
|
|
"items": [
|
|
{
|
|
"type": "TextBlock",
|
|
"text": "[Unsubscribed](https://www.unsubscribed.com)",
|
|
"wrap": true,
|
|
"weight": "Bolder",
|
|
"size": "ExtraLarge",
|
|
"separator": true,
|
|
"horizontalAlignment": "Center",
|
|
"spacing": "None"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": code.unsubscribed.usrow.code,
|
|
"wrap": true,
|
|
"fontType": "Monospace",
|
|
"weight": "Bolder",
|
|
"horizontalAlignment": "Center",
|
|
"size": "ExtraLarge",
|
|
"spacing": "Padding"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": code.unsubscribed.usrow.message,
|
|
"wrap": true,
|
|
"weight": "Lighter",
|
|
"size": "Small",
|
|
"color": "Light",
|
|
"horizontalAlignment": "Center",
|
|
"spacing": "Small"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": code.unsubscribed.thirdparty.code,
|
|
"wrap": true,
|
|
"fontType": "Monospace",
|
|
"weight": "Bolder",
|
|
"horizontalAlignment": "Center",
|
|
"size": "ExtraLarge",
|
|
"spacing": "Padding"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": code.unsubscribed.thirdparty.message,
|
|
"wrap": true,
|
|
"weight": "Lighter",
|
|
"size": "Small",
|
|
"color": "Light",
|
|
"horizontalAlignment": "Center",
|
|
"spacing": "Small"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": code.unsubscribed.giftcard.code,
|
|
"wrap": true,
|
|
"fontType": "Monospace",
|
|
"weight": "Bolder",
|
|
"horizontalAlignment": "Center",
|
|
"size": "ExtraLarge",
|
|
"spacing": "Padding"
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": code.unsubscribed.giftcard.message,
|
|
"wrap": true,
|
|
"weight": "Lighter",
|
|
"size": "Small",
|
|
"color": "Light",
|
|
"horizontalAlignment": "Center",
|
|
"spacing": "Small"
|
|
}
|
|
],
|
|
"separator": true
|
|
},
|
|
{
|
|
"type": "TextBlock",
|
|
"text": "[Associate Discount Policies & Codes](https://onfirstup.com/AEO/AEO/contents/25437784) \n\nThe online discount is a benefit to all AEO Corporate and Distribution Center associates as well as Field leadership (RD, DTL, Regional Assistants, Field Visual, Field Auditors, Field Real Estate, and Field HR).",
|
|
"wrap": true,
|
|
"horizontalAlignment": "Center",
|
|
"separator": true
|
|
}
|
|
]
|
|
}
|
|
|
|
var discountText = "# Discount Code for " + code.month + "\n" +
|
|
"## AE/Aerie US, Canada, & ROW [Website](https://www.ae.com/)" + "\n" +
|
|
"`" + code.aeAerie.usrow.code + "`\n" +
|
|
"_" + code.aeAerie.usrow.message + "_\n" +
|
|
"## AE/Aerie Mexico [Website](https://www.ae.com/mx/es)\n" +
|
|
"`" + code.aeAerie.mexico.code + "`\n" +
|
|
"_" + code.aeAerie.mexico.message + "_\n" +
|
|
"- - -\n" +
|
|
"## Todd Snyder [Website](https://www.toddsnyder.com)\n" +
|
|
"`" + code.toddSnyder.usrow.tstc.code + "`\n" +
|
|
"_" + code.toddSnyder.usrow.tstc.message + "_\n" +
|
|
"`" + code.toddSnyder.usrow.thirdparty.code + "`\n" +
|
|
"_" + code.toddSnyder.usrow.thirdparty.message + "_\n" +
|
|
"- - -\n" +
|
|
"## Unsubscribed [Website](https://www.unsubscribed.com)\n" +
|
|
"`" + code.unsubscribed.usrow.code + "`\n" +
|
|
"_" + code.unsubscribed.usrow.message + "_\n" +
|
|
"`" + code.unsubscribed.thirdparty.code + "`\n" +
|
|
"_" + code.unsubscribed.thirdparty.message + "_\n" +
|
|
"`" + code.unsubscribed.giftcard.code + "`\n" +
|
|
"_" + code.unsubscribed.giftcard.message + "_\n\n" +
|
|
"_hese employee discount codes cannot be combined with any other discounts or affiliate marketing links. The online discount is a benefit to all AEO Corporate and Distribution Center associates as well as Field leadership (RD, DTL, Regional Assistants, Field Visual, Field Auditors, Field Real Estate, and Field HR)._";
|
|
|
|
resolve({ "card": discountCard, "text": discountText })
|
|
|
|
})
|
|
}
|
|
|
|
function logger(activeFunction, logLine) {
|
|
var d = new Date();
|
|
console.log(d.toLocaleString() + " " + activeFunction + ": " + logLine);
|
|
}
|
|
|
|
function cleanOldFiles() {
|
|
try {
|
|
var files = fs.readdirSync(config.server.logging.directory);
|
|
} catch (error) {
|
|
logger('clearnOldFiles', `Error: ${error}`)
|
|
}
|
|
|
|
for (var file of files) {
|
|
var fileStats = fs.statSync(config.server.logging.directory + file);
|
|
if (new Date(fileStats.mtime).getTime() <= new Date().getTime() - (config.server.logging.retensionDays * 24 * 3600 * 1000)) {
|
|
logger('cleanOldFiles', `Removing file '${config.server.logging.directory + file}'`)
|
|
fs.unlinkSync(config.server.logging.directory + file);
|
|
}
|
|
}
|
|
}
|
|
|
|
function isJSONObject(obj) {
|
|
return obj !== null
|
|
&&
|
|
typeof obj === 'object'
|
|
&&
|
|
obj.constructor === Object;
|
|
}
|
|
|
|
function refreshToken() {
|
|
logger('refreshToken', `Checking token.`)
|
|
if (new Date(new Date(config.serviceAccount.authorization.expiresOn) - 7200000) < new Date()) {
|
|
logger(`refreshToken`, `Token needs refreshed.`)
|
|
var url = "https://webexapis.com/v1/access_token";
|
|
|
|
var params = new URLSearchParams();
|
|
params.append('grant_type', 'refresh_token');
|
|
params.append('client_id', config.serviceAccount.authorization.clientId);
|
|
params.append('client_secret', config.serviceAccount.authorization.clientSecret);
|
|
params.append('refresh_token', config.serviceAccount.authorization.token.refresh_token);
|
|
|
|
fetch(url, { method: 'post', body: params })
|
|
.then(res => { return res.json() })
|
|
.then(function (json) {
|
|
console.log(JSON.stringify(json))
|
|
config.serviceAccount.authorization.created = new Date().toTimeString;
|
|
config.serviceAccount.authorization.token = json;
|
|
config.serviceAccount.authorization.expiresOn = new Date(Date.now() + (json.expires_in * 1000));
|
|
config.serviceAccount.authorization.refreshBy = new Date(Date.now() + (json.refresh_token_expires_in * 1000));
|
|
logger(`refreshToken`, `Token refreshed. Expires on ${config.serviceAccount.authorization.expiresOn.toLocaleString('en-US', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit'
|
|
})}.`)
|
|
saveJSON(config, './config/config.json')
|
|
})
|
|
.catch(error => {
|
|
logger(`refreshToken`, `Error: ${error}`)
|
|
});
|
|
} else {
|
|
logger(`refreshToken`, `Token is good until ${new Date(config.serviceAccount.authorization.expiresOn).toLocaleString('en-US', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit'
|
|
})}.`)
|
|
}
|
|
}
|
|
|
|
function saveJSON(jsonObject, configFile) {
|
|
return new Promise(async function (resolve, reject) {
|
|
|
|
const jsonData = JSON.stringify(jsonObject, null, 4);
|
|
fs.writeFileSync(configFile, jsonData, (error) => {
|
|
if (error) {
|
|
reject(error)
|
|
throw err;
|
|
} else {
|
|
logger("saveJSON", "Wrote " + configFile);
|
|
|
|
}
|
|
})
|
|
resolve();
|
|
})
|
|
}
|
|
|