botium-core
Version:
The Selenium for Chatbots
393 lines (337 loc) • 10.6 kB
JavaScript
const request = require('request')
const express = require('express')
const https = require('https')
const http = require('http')
const bodyParser = require('body-parser')
const fs = require('fs')
const tcpPortUsed = require('tcp-port-used')
var publishPort = process.env.BOTIUM_SLACK_PUBLISHPORT
if (publishPort) {
publishPort = parseInt(publishPort)
} else {
publishPort = 46199
}
var userIdDefault = 'U' + randomInt(1000000000, 9999999999)
var userNameDefault = process.env.BOTIUM_SLACK_USERNAME
if (!userNameDefault) {
userNameDefault = 'BotiumUser'
}
var userIdMap = {}
userIdMap.me = userIdDefault
userIdMap[userNameDefault] = userIdDefault
var userNameMap = {}
userNameMap[userIdDefault] = 'me'
var botIdDefault = 'B' + randomInt(1000000000, 9999999999)
var botNameDefault = process.env.BOTIUM_SLACK_BOTNAME
if (!botNameDefault) {
botNameDefault = 'botiumbot'
}
var teamIdDefault = 'T' + randomInt(1000000000, 9999999999)
var teamNameDefault = process.env.BOTIUM_SLACK_TEAMNAME
if (!teamNameDefault) {
teamNameDefault = 'BotiumTeam'
}
var dmChannelIdDefault = 'D' + randomInt(1000000000, 9999999999)
var pubChannelIdDefault = 'C' + randomInt(1000000000, 9999999999)
var pubChannelNameDefault = process.env.BOTIUM_SLACK_CHANNELNAME
if (!pubChannelNameDefault) {
pubChannelNameDefault = '#general'
}
var channelIdMap = {}
channelIdMap[pubChannelNameDefault] = pubChannelIdDefault
channelIdMap['#private'] = dmChannelIdDefault
var channelNameMap = {}
channelNameMap[pubChannelIdDefault] = pubChannelNameDefault
channelNameMap[dmChannelIdDefault] = '#private'
var authToken = process.env.BOTIUM_AUTH_TOKEN
var apiAppId = process.env.BOTIUM_APPID
if (!apiAppId) {
apiAppId = 'AXXXXXXXXX'
}
var accessToken = 'xoxp-XXXXXXXX-XXXXXXXX-XXXXX'
var accessTokenBot = 'xoxb-XXXXXXXXXXXX-TTTTTTTTTTTTTT'
var eventurl = process.env.BOTIUM_SLACK_EVENTURL
if (!eventurl) {
var eventport = process.env.BOTIUM_SLACK_EVENTPORT
var eventpath = process.env.BOTIUM_SLACK_EVENTPATH
var eventhost = process.env.BOTIUM_SLACK_EVENTHOST
var eventprotocol = process.env.BOTIUM_SLACK_EVENTPROTOCOL
if (!eventport || !eventhost || !eventprotocol) {
console.log('BOTIUM_SLACK_EVENTURL env variables not set')
process.exit(1)
}
eventurl = eventprotocol + '://' + eventhost + ':' + eventport + '/'
if (eventpath) {
eventurl += eventpath
}
}
var oauthurl = process.env.BOTIUM_SLACK_OAUTHURL
const oauthport = process.env.BOTIUM_SLACK_OAUTHPORT
const oauthpath = process.env.BOTIUM_SLACK_OAUTHPATH
const oauthhost = process.env.BOTIUM_SLACK_OAUTHHOST
const oauthprotocol = process.env.BOTIUM_SLACK_OAUTHPROTOCOL
if (!oauthurl) {
if (!oauthport || !oauthhost || !oauthprotocol) {
console.log('BOTIUM_SLACK_OAUTHURL env variables not set')
process.exit(1)
}
oauthurl = oauthprotocol + '://' + oauthhost + ':' + oauthport + '/'
if (oauthpath) {
oauthurl += oauthpath
}
}
const botHealthCheckVerb = process.env.BOTIUM_SLACK_HEALTH_CHECK_VERB || 'GET'
const botHealthCheckPath = process.env.BOTIUM_SLACK_HEALTH_CHECK_PATH
const botHealthCheckUrl = botHealthCheckPath ? `${oauthprotocol}://${oauthhost}:${oauthport}/${botHealthCheckPath}` : oauthurl
const botHealthCheckStatus = parseInt(process.env.BOTIUM_SLACK_HEALTH_CHECK_STATUS)
if (!Number.isInteger(botHealthCheckStatus)) {
throw new Error(`${botHealthCheckStatus} is not a valid http status`)
}
var appMock = express()
appMock.use(bodyParser.json())
appMock.use(bodyParser.urlencoded({ extended: true }))
appMock.post('/api/oauth.access', (req, res) => {
console.log('/api/oauth.access: ' + JSON.stringify(req.body))
res.json({
access_token: accessToken,
scope: 'read',
team_name: teamIdDefault,
team_id: teamIdDefault,
incoming_webhook: {
url: 'http://slack.com/incomingWebhook',
channel: pubChannelNameDefault,
configuration_url: 'http://slack.com/incomingWebhook'
},
bot: {
bot_user_id: botIdDefault,
bot_access_token: accessTokenBot
}
})
})
appMock.post('/api/auth.test', (req, res) => {
console.log('/api/auth.test: ' + JSON.stringify(req.body))
if (req.body.token === accessTokenBot) {
res.json({
ok: true,
url: 'https://myteam.slack.com/',
team: teamNameDefault,
user: botNameDefault,
team_id: teamIdDefault,
user_id: botIdDefault
})
} else {
res.json({
ok: true,
url: 'https://myteam.slack.com/',
team: teamNameDefault,
user: userNameDefault,
team_id: teamIdDefault,
user_id: userIdDefault
})
}
})
appMock.post('/api/im.open', (req, res) => {
console.log('/api/im.open: ' + JSON.stringify(req.body))
res.json({
ok: true,
channel: {
id: dmChannelIdDefault
}
})
})
appMock.post('/api/channels.list', (req, res) => {
res.json({
ok: true,
channels: [
{
id: pubChannelIdDefault,
name: pubChannelNameDefault,
created: getTs(),
creator: userIdDefault,
is_archived: false
}
]
})
})
appMock.post('/api/chat.postMessage', (req, res) => {
console.log('/api/chat.postMessage: ' + JSON.stringify(req.body))
const botMsg = {
sourceData: req.body
}
if (req.body.text) {
botMsg.messageText = req.body.text
}
if (req.body.channel) {
if (channelNameMap[req.body.channel]) {
botMsg.channel = channelNameMap[req.body.channel]
} else {
botMsg.channel = req.body.channel
}
}
receivedFromBot(botMsg)
res.json({
ok: true,
ts: getTs(),
channel: req.body.channel,
message: req.body
})
})
appMock.post('/api/reactions.add', (req, res) => {
console.log('/api/reactions.add: ' + JSON.stringify(req.body))
const botMsg = {
sourceData: req.body,
messageText: ':' + req.body.name + ':'
}
if (req.body.channel) {
if (channelNameMap[req.body.channel]) {
botMsg.channel = channelNameMap[req.body.channel]
} else {
botMsg.channel = req.body.channel
}
}
receivedFromBot(botMsg)
res.json({
ok: true
})
})
appMock.all('/incomingWebhook', (req, res) => {
})
appMock.all('*', (req, res) => {
console.log('*: ' + req.body)
res.json({
ok: true
})
})
var httpsOptions = {
key: fs.readFileSync('./127.0.0.1.key'),
cert: fs.readFileSync('./127.0.0.1.cert')
}
https.createServer(httpsOptions, appMock).listen(443, '0.0.0.0', (err) => {
if (err) {
console.log('error listening 443: ' + err)
} else {
console.log('Mock server listening on port 443')
}
})
var appTest = express()
appTest.use(bodyParser.json())
appTest.get('/', (req, res) => {
var urlparts = new URL(botHealthCheckUrl)
tcpPortUsed.check(parseInt(urlparts.port), urlparts.hostname)
.then((inUse) => {
console.log('port usage chatbot oauth endpoint (' + botHealthCheckUrl + '): ' + inUse)
if (inUse) {
console.log('checking chatbot oauth endpoint (' + botHealthCheckUrl + ') for response')
var options = {
uri: botHealthCheckUrl,
method: botHealthCheckVerb,
qs: { code: 'C123123123', state: 'C123123123' }
}
request(options, (err, response, body) => {
if (!err && response.statusCode === botHealthCheckStatus) {
const onlineMsg = `Bot is healthy under ${botHealthCheckStatus} is online (${body})`
console.log(onlineMsg)
res.status(200).send(onlineMsg)
} else {
const offlineMsg = `chatbot health check endpoint (${botHealthCheckUrl}) not yet online (Err: ${err}, Body: ${body})`
console.log(offlineMsg)
res.status(500).send(offlineMsg)
}
})
} else {
res.status(500).send('chatbot oauth endpoint (' + botHealthCheckUrl + ') not yet online (port not in use)')
}
},
(err) => {
console.log('error on port check chatbot oauth endpoint: ' + err)
res.status(500).send('chatbot oauth endpoint (' + botHealthCheckUrl + ') not yet online (port check failed ' + err + ')')
})
})
function sendToBot (mockMsg) {
const ts = getTs()
if (mockMsg.channel) {
if (channelIdMap[mockMsg.channel]) {
mockMsg.channel = channelIdMap[mockMsg.channel]
}
}
if (!mockMsg.channel) {
mockMsg.channel = dmChannelIdDefault
}
if (mockMsg.sender) {
if (userIdMap[mockMsg.sender]) {
mockMsg.sender = userIdMap[mockMsg.sender]
}
}
if (!mockMsg.sender) {
mockMsg.sender = userIdDefault
}
const eventContainer = {
token: authToken,
team_id: teamIdDefault,
api_app_id: apiAppId,
type: 'event_callback',
authed_users: [
mockMsg.sender
],
event_id: ts
}
if (mockMsg.messageText) {
eventContainer.event = {
type: 'message',
text: mockMsg.messageText
}
} else if (mockMsg.sourceData) {
eventContainer.event = mockMsg.sourceData
} else {
console.log('No messageText or sourceData given. Ignored.', mockMsg)
return
}
if (eventContainer.event.text) {
eventContainer.event.text = eventContainer.event.text.replace('@' + botNameDefault, '<@' + botIdDefault + '|' + botNameDefault + '>')
}
if (!eventContainer.event.user) eventContainer.event.user = mockMsg.sender
if (!eventContainer.event.channel) eventContainer.event.channel = mockMsg.channel
if (!eventContainer.event.ts) eventContainer.event.ts = ts
callWebhook(eventContainer)
}
console.log('Test server start on port ' + publishPort)
var serverTest = http.createServer(appTest).listen(publishPort, '0.0.0.0', (err) => {
if (err) {
console.log('error listening ' + publishPort + ': ' + err)
} else {
console.log('Test server listening on port ' + publishPort)
}
})
var io = require('socket.io')(serverTest)
io.on('connection', (socket) => {
console.log('socket connection estabilished')
socket.on('MOCKCMD_SENDTOBOT', (mockMsg) => {
console.log('MOCKCMD_SENDTOBOT ', mockMsg)
sendToBot(mockMsg)
})
})
function receivedFromBot (botMsg) {
console.log('receivedFromBot: ', botMsg)
io.sockets.emit('MOCKCMD_RECEIVEDFROMBOT', botMsg)
}
function getTs () {
return (new Date()).getTime()
}
function callWebhook (msg) {
console.log('callWebhook: ' + JSON.stringify(msg, null, 2))
var options = {
uri: eventurl,
method: 'POST',
json: msg
}
request(options, (err, response, body) => {
if (err) {
console.log('callWebhook Error: ' + err)
} else {
console.log('callWebhook OK')
}
})
}
function randomInt (low, high) {
return Math.floor(Math.random() * (high - low) + low)
}