2019-10-13 03:38:25 +02:00
|
|
|
/**
|
|
|
|
* XMPP bot
|
|
|
|
*
|
|
|
|
* Create XMPP client, connect bot to server and handle interactions
|
|
|
|
*
|
|
|
|
* @exports xmpp
|
|
|
|
* @file This files defines the XMPP bot
|
|
|
|
* @author nioc
|
2019-11-21 01:44:43 +01:00
|
|
|
* @since 2.0.0
|
2019-10-13 03:38:25 +02:00
|
|
|
* @license AGPL-3.0+
|
|
|
|
*/
|
|
|
|
|
|
|
|
module.exports = (logger, config) => {
|
2019-11-21 01:44:43 +01:00
|
|
|
const { client, xml, jid } = require('@xmpp/client')
|
2019-10-20 01:07:10 +02:00
|
|
|
const outgoing = require('../outgoing')
|
2019-10-13 03:38:25 +02:00
|
|
|
this.jid = null
|
|
|
|
|
2019-11-21 01:44:43 +01:00
|
|
|
// declare send chat/groupchat function
|
|
|
|
this.send = async (to, message, type) => {
|
|
|
|
logger.debug(`Send ${type} message to ${to}: '${message}'`)
|
|
|
|
const stanza = xml(
|
|
|
|
'message', {
|
|
|
|
to,
|
|
|
|
type
|
|
|
|
},
|
|
|
|
xml(
|
|
|
|
'body', {
|
|
|
|
},
|
|
|
|
message)
|
|
|
|
)
|
|
|
|
await xmppClient.send(stanza)
|
|
|
|
}
|
|
|
|
|
|
|
|
// declare close function
|
|
|
|
this.close = async () => {
|
|
|
|
await xmppClient.stop()
|
|
|
|
}
|
|
|
|
|
|
|
|
// create XMPP client
|
|
|
|
const xmppClient = client(config.xmpp)
|
|
|
|
|
2019-10-13 03:38:25 +02:00
|
|
|
// handle connection
|
2019-11-21 01:44:43 +01:00
|
|
|
xmppClient.on('online', (address) => {
|
|
|
|
logger.info(`XMPP connected on ${config.xmpp.service} with JID: ${address.toString()}`)
|
|
|
|
this.jid = address
|
|
|
|
// send presence
|
|
|
|
xmppClient.send(xml('presence'))
|
2019-11-21 22:38:10 +01:00
|
|
|
.then(() => {
|
|
|
|
logger.debug('presence sent')
|
|
|
|
})
|
2019-11-21 01:44:43 +01:00
|
|
|
.catch((error) => {
|
|
|
|
logger.warn('presence returned following error:', error)
|
|
|
|
})
|
2019-10-13 03:38:25 +02:00
|
|
|
// join rooms
|
|
|
|
config.xmpp.rooms.forEach(function (room) {
|
2019-11-21 01:44:43 +01:00
|
|
|
let occupantJid = room.id + '/' + address.local
|
|
|
|
logger.debug(`Join room: ${room.id} ('${occupantJid}')`)
|
|
|
|
const stanza = xml(
|
|
|
|
'presence', {
|
|
|
|
to: occupantJid
|
|
|
|
},
|
|
|
|
xml(
|
|
|
|
'x', {
|
|
|
|
xmlns: 'http://jabber.org/protocol/muc'
|
|
|
|
}
|
|
|
|
)
|
|
|
|
)
|
|
|
|
xmppClient.send(stanza)
|
2019-10-13 03:38:25 +02:00
|
|
|
logger.info(`Joined room: ${room.id}`)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2019-11-21 01:44:43 +01:00
|
|
|
// handle stanzas
|
|
|
|
xmppClient.on('stanza', stanza => {
|
|
|
|
if (!stanza.is('message')) {
|
|
|
|
// not a message, do nothing
|
2019-10-13 03:38:25 +02:00
|
|
|
return
|
|
|
|
}
|
2019-11-21 01:44:43 +01:00
|
|
|
let type = stanza.attrs.type
|
|
|
|
switch (type) {
|
|
|
|
case 'chat':
|
|
|
|
case 'groupchat':
|
|
|
|
let body = stanza.getChild('body')
|
|
|
|
if (!body) {
|
|
|
|
// empty body, do nothing
|
|
|
|
return
|
|
|
|
}
|
|
|
|
let fromJid = jid(stanza.attrs.from)
|
|
|
|
// for chat, "to" and "replyTo" must be something like "user@domain.ltd", "from" is local part "user"
|
|
|
|
let to = this.jid.bare()
|
|
|
|
let from = fromJid.local
|
|
|
|
let replyTo = fromJid.bare()
|
|
|
|
if (type === 'groupchat') {
|
|
|
|
// for groupchat, "to" and "replyTo" is conference name, "from" is nickname
|
|
|
|
to = fromJid.bare()
|
|
|
|
from = fromJid.getResource()
|
|
|
|
replyTo = to
|
|
|
|
if (from === this.jid.local || stanza.getChild('delay')) {
|
|
|
|
// message from bot or old message, do nothing
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let message = body.text()
|
2019-11-22 00:20:04 +01:00
|
|
|
// handle message delivery receipts for chat
|
|
|
|
if (type === 'chat') {
|
|
|
|
let request = stanza.getChild('request')
|
|
|
|
if (request &&
|
|
|
|
request.attrs.xmlns &&
|
|
|
|
request.attrs.xmlns === 'urn:xmpp:receipts' &&
|
|
|
|
stanza.attrs.id) {
|
|
|
|
logger.debug(`Message delivery receipt is requested and will be processed`)
|
|
|
|
const receiptStanza = xml(
|
|
|
|
'message', {
|
|
|
|
to: fromJid
|
|
|
|
},
|
|
|
|
xml(
|
|
|
|
'received', {
|
|
|
|
xmlns: 'urn:xmpp:receipts',
|
|
|
|
id: stanza.attrs.id
|
|
|
|
}
|
|
|
|
)
|
|
|
|
)
|
|
|
|
xmppClient.send(receiptStanza)
|
|
|
|
}
|
|
|
|
}
|
2019-11-21 01:44:43 +01:00
|
|
|
logger.info(`Incoming ${type} message from ${from} (${fromJid.toString()}) to ${to}`)
|
|
|
|
logger.debug(`Message: "${message}"`)
|
|
|
|
let xmppHook = config.getXmppHookAction(to.toString())
|
|
|
|
if (!xmppHook) {
|
|
|
|
logger.error(`There is no action for incoming ${type} message to: "${to}"`)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
switch (xmppHook.action) {
|
|
|
|
case 'outgoing_webhook':
|
|
|
|
logger.debug(`Call outgoing webhook: ${xmppHook.args[0]}`)
|
|
|
|
outgoing(logger, config, this, from.toString(), replyTo.toString(), message, type, xmppHook.args[0])
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
2019-10-13 03:38:25 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2019-11-21 01:44:43 +01:00
|
|
|
// handle status
|
|
|
|
xmppClient.on('status', (status) => {
|
|
|
|
logger.trace(`Status changed to ${status}`)
|
2019-10-13 03:38:25 +02:00
|
|
|
})
|
|
|
|
|
2019-11-21 01:44:43 +01:00
|
|
|
// trace input/output
|
|
|
|
// xmppClient.on('input', (input) => {
|
|
|
|
// logger.trace('<<<<', input)
|
|
|
|
// })
|
|
|
|
// xmppClient.on('output', (output) => {
|
|
|
|
// logger.trace('>>>', output)
|
|
|
|
// })
|
|
|
|
|
2019-10-13 03:38:25 +02:00
|
|
|
// handle error
|
2019-11-21 01:44:43 +01:00
|
|
|
xmppClient.on('error', (err) => {
|
|
|
|
logger.error('XMPP client encountered following error:', err.message)
|
2019-11-19 22:27:01 +01:00
|
|
|
process.exit(99)
|
2019-10-13 03:38:25 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
// connect
|
2019-11-21 01:44:43 +01:00
|
|
|
xmppClient.start()
|
2019-11-21 22:38:10 +01:00
|
|
|
.catch((error) => {
|
|
|
|
logger.error('XMPP client encountered following error at connection', error)
|
|
|
|
})
|
2019-10-13 03:38:25 +02:00
|
|
|
|
2019-11-21 01:44:43 +01:00
|
|
|
return this
|
2019-10-13 03:38:25 +02:00
|
|
|
}
|