Skip to content

Commit

Permalink
Merge pull request #272 from RocketChat/develop
Browse files Browse the repository at this point in the history
Pre-release Hubot Adapter 2.0
  • Loading branch information
Sing-Li authored Apr 20, 2018
2 parents 28bf6fd + 44de800 commit 8dc9e0c
Show file tree
Hide file tree
Showing 12 changed files with 1,624 additions and 552 deletions.
3 changes: 0 additions & 3 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
engines:
# to turn on an engine, add it here and set enabled to `true`
# to turn off an engine, set enabled to `false` or remove it
coffeelint:
enabled: true
eslint:
enabled: true
csslint:
Expand All @@ -31,7 +29,6 @@ engines:

ratings:
paths:
- "**.coffee"
- "**.js"
- "**.css"

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ USER hubot

RUN cd /home/hubot/node_modules/hubot-rocketchat && \
npm install && \
coffee -c /home/hubot/node_modules/hubot-rocketchat/src/*.coffee && \
#coffee -c /home/hubot/node_modules/hubot-rocketchat/src/*.coffee && \
cd /home/hubot

CMD node -e "console.log(JSON.stringify('$EXTERNAL_SCRIPTS'.split(',')))" > external-scripts.json && \
Expand Down
282 changes: 265 additions & 17 deletions README.md

Large diffs are not rendered by default.

222 changes: 222 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
'use strict'

const Adapter = require.main.require('hubot/src/adapter')
const Response = require.main.require('hubot/src/response')
const { TextMessage, EnterMessage, LeaveMessage } = require.main.require('hubot/src/message')
const { driver } = require('@rocket.chat/sdk')

/** Take configs from environment settings or defaults */
const config = {
url: process.env.ROCKETCHAT_URL || 'localhost:3000',
room: process.env.ROCKETCHAT_ROOM || 'GENERAL',
user: process.env.ROCKETCHAT_USER || 'hubot',
pass: process.env.ROCKETCHAT_PASSWORD || 'password',
listenOnAllPublic: (process.env.LISTEN_ON_ALL_PUBLIC || 'false').toLowerCase() === 'true',
respondToDM: (process.env.RESPOND_TO_DM || 'false').toLowerCase() === 'true',
respondToLivechat: (process.env.RESPOND_TO_LIVECHAT || 'false').toLowerCase() === 'true',
respondToEdited: (process.env.RESPOND_TO_EDITED || 'false').toLowerCase() === 'true',
sslEnabled: (process.env.ROCKETCHAT_USESSL || 'false').toLowerCase() === 'true'
}

/** Extend default response with custom adapter methods */
class RocketChatResponse extends Response {
sendDirect (...strings) {
this.robot.adapter.sendDirect(this.envelope, ...strings)
}
sendPrivate (...strings) {
this.robot.adapter.sendDirect(this.envelope, ...strings)
}
}

/** Define new message type for handling attachments */
class AttachmentMessage extends TextMessage {
constructor (user, attachment, text, id) {
super(user, text, id)
this.user = user
this.attachment = attachment
this.text = text
this.id = id
}
toString () {
return this.attachment
}
}

/** Main API for Hubot on Rocket.Chat */
class RocketChatBotAdapter extends Adapter {
run () {
this.robot.logger.info(`[startup] Rocket.Chat adapter in use`)

// Print logs with current configs
this.startupLogs()

// Overwrite Robot's response class with Rocket.Chat custom one
this.robot.Response = RocketChatResponse

// Use RocketChat Bot Driver to connect, login and setup subscriptions
// Joins single or array of rooms by name from room setting (comma separated)
// Reactive message subscription uses callback to process every stream update
driver.useLog(this.robot.logger)
driver.connect({
host: config.url,
useSsl: config.sslEnabled
})
.catch((err) => {
this.robot.logger.error(this.robot.logger.error(`Unable to connect: ${JSON.stringify(err)}`))
throw err
})
.then(() => {
return driver.login({ username: config.user, password: config.pass })
})
.catch((err) => {
this.robot.logger.error(this.robot.logger.error(`Unable to login: ${JSON.stringify(err)}`))
throw err
}).then((_id) => {
this.userId = _id
return driver.joinRooms(config.room.split(',').filter((room) => (room !== '')))
})
.catch((err) => {
this.robot.logger.error(this.robot.logger.error(`Unable to join rooms: ${JSON.stringify(err)}`))
throw err
})
.then((joined) => {
return driver.subscribeToMessages()
})
.catch((err) => {
this.robot.logger.error(`Unable to subscribe ${JSON.stringify(err)}`)
throw err
})
.then(() => {
driver.respondToMessages(this.process.bind(this)) // reactive callback
this.emit('connected') // tells hubot to load scripts
})
}

/** Process every incoming message in subscription */
process (err, message, meta) {
if (err) throw err
// Prepare message type for Hubot to receive...
this.robot.logger.info('Filters passed, will receive message')

// Collect required attributes from message meta
const isDM = (meta.roomType == 'd')
const isLC = (meta.roomType == 'l')
const user = this.robot.brain.userForId(message.u._id, {
name: message.u.username,
alias: message.alias
})
user.roomID = message.rid
user.roomType = meta.roomType
user.room = meta.roomName || message.rid

// Room joins, receive without further detail
if (message.t === 'uj') {
this.robot.logger.debug('Message type EnterMessage')
return this.robot.receive(new EnterMessage(user, null, message._id))
}

// Room exit, receive without further detail
if (message.t === 'ul') {
this.robot.logger.debug('Message type LeaveMessage')
return this.robot.receive(new LeaveMessage(user, null, message._id))
}

// Direct messages prepend bot's name so Hubot can `.respond`
const startOfText = (message.msg.indexOf('@') === 0) ? 1 : 0
const robotIsNamed = message.msg.indexOf(this.robot.name) === startOfText || message.msg.indexOf(this.robot.alias) === startOfText
if ((isDM || isLC) && !robotIsNamed) message.msg = `${this.robot.name} ${message.msg}`

// Attachments, format properties for Hubot
if (Array.isArray(message.attachments) && message.attachments.length) {
let attachment = message.attachments[0]
if (attachment.image_url) {
attachment.link = `${config.url}${attachment.image_url}`
attachment.type = 'image'
} else if (attachment.audio_url) {
attachment.link = `${config.url}${attachment.audio_url}`
attachment.type = 'audio'
} else if (attachment.video_url) {
attachment.link = `${config.url}${attachment.video_url}`
attachment.type = 'video'
}
this.robot.logger.debug('Message type AttachmentMessage')
return this.robot.receive(new AttachmentMessage(user, attachment, message.msg, message._id))
}

// Standard text messages, receive as is
let textMessage = new TextMessage(user, message.msg, message._id)
this.robot.logger.debug(`TextMessage: ${textMessage.toString()}`)
return this.robot.receive(textMessage)
}

/** Send messages to user adddressed in envelope */
send (envelope, ...strings) {
return strings.map((text) => {
if (envelope.user.roomID) driver.sendToRoomId(text, envelope.user.roomID)
else driver.sendToRoom(text, envelope.room)
})
}

/**
* Emote message to user
* @todo Improve this legacy method
*/
emote (envelope, ...strings) {
return strings.map((text) => this.send(envelope, `_${text}_`))
}

/** Send custom message to user */
customMessage (data) {
return driver.sendMessage(data)
}

/** Send DM to user */
sendDirect (envelope, ...strings) {
return strings.map((text) => driver.sendDirectToUser(text, envelope.user.name))
}

/** Reply to a user's message (mention them if not a DM) */
reply (envelope, ...strings) {
if (envelope.room.indexOf(envelope.user.id) === -1) {
strings = strings.map((s) => `@${envelope.user.name} ${s}`)
}
return this.send(envelope, ...strings)
}

/** Get a room ID via driver */
getRoomId (room) {
return driver.getRoomId(room)
}

/** Call a server message via driver */
callMethod (method, ...args) {
return driver.callMethod(method, args)
}

/** Starting config print outs, split-out from logic for easy reading */
startupLogs () {
this.robot.logger.info(`[startup] Respond to the name: ${this.robot.name}`)
this.robot.alias = (this.robot.name === config.user || this.robot.alias) ? this.robot.alias : config.user

if (this.robot.alias) {
this.robot.logger.info(`I will also respond to my Rocket.Chat username as an alias ${this.robot.alias}`)
}

if (!process.env.ROCKETCHAT_URL) {
this.robot.logger.warning(`No services ROCKETCHAT_URL provided to Hubot, using ${config.url}`)
}

if (!process.env.ROCKETCHAT_ROOM) {
this.robot.logger.warning(`No services ROCKETCHAT_ROOM provided to Hubot, using ${config.room}`)
}

if (!process.env.ROCKETCHAT_USER) {
this.robot.logger.warning(`No services ROCKETCHAT_USER provided to Hubot, using ${config.user}`)
}

this.robot.logger.info(`[startup] Rooms specified: ${config.room}`)
}
}

exports.use = (robot) => new RocketChatBotAdapter(robot)

Loading

0 comments on commit 8dc9e0c

Please sign in to comment.