Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tecvinson assignment #351

Open
wants to merge 15 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
root = true

[*]
indent_style = tab
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ WORKDIR /home/hubot
ENV BOT_NAME "rocketbot"
ENV BOT_OWNER "No owner specified"
ENV BOT_DESC "Hubot with rocketbot adapter"
ENV HUBOT_LOG_LEVEL "error"

ENV EXTERNAL_SCRIPTS=hubot-diagnostics,hubot-help,hubot-google-images,hubot-google-translate,hubot-pugme,hubot-maps,hubot-rules,hubot-shipit

Expand Down
164 changes: 53 additions & 111 deletions README.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Tecvinson.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The program has been awesome, and I've been learning so much. I'm really looking forward to learning even more.
Thanks a lot for the opportunity and for making such a great program
93 changes: 93 additions & 0 deletions docs/snippets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Hubot Rocket.Chat Snippets

Included in the adapter source are some snippets for advanced usage of Hubot and
the Rocket.Chat JS SDK (included in the adapter). They can be tested locally by
developers, but aren't included in the package published to NPM.

The snippets documented here aren't intended for use in every instance and the
method of loading them is not recommended other than for testing these examples.
Please see https://rocket.chat/docs/bots for more general getting started tips.

## Available Snippets

- [Use API to check if role exists on given user](snippets/userHasRole.js)
- [Use API to clear the room history](snippets/cleanRoomHistory.js)
- [Use API to add users to Hubot's brain](snippets/syncBrainUsers.js)

e.g. To execute one of the demos, from the adapter root in local CLI with Node:

```
node docs/snippets userHasRole
```

Then login to talk to the bot in your local Rocket.Chat instance. For this
example, try "bot is admin an admin".

See [index.js](snippets/index.js) to learn how a mock Hubot loads the snippets.

## Testing Environment

Running the example snippets locally requires the same environment settings
as any bot, but it's recommended to only use them on a test instance, with
test users and rooms, similar to the approach used for testing the SDK:

https://github.com/RocketChat/Rocket.Chat.js.SDK#installing-rocketchat

## Snippet Patterns

Each snippet demonstrates a specific function, which is exported for tests,
they also may include a `load` function that add commands to demonstrate the
snippet in use. The exports don't follow the normal Hubot script pattern,
which is to export a single function accepting the bot as a single argument.

Note, many command helpers follow the pattern of returning undefined if not
successful, as opposed to throwing. This allows them to log errors and let
the bot send an apology rather than silently failing.

## Example Snippet

### `userHasRole`

#### Define Function

This example demonstrates the pattern. The function is defined outside the scope
where the bot is available, so it takes the `robot` instance as first parameter.
Then Hubot scripts would use it within the `module.exports = (robot) =>` block.

Keeping async calls within a `try`...`catch` block allows any errors or promise
rejections to go to a single handler, which logs the error, returning undefined.

![userHasRole function snippet](snippets/userHasRole-function.png)

#### Load Command

The load function adds a listener callback, providing `userHasRole` utility to
users. The RegExp pattern matching uses word boundaries `\b`, capture groups,
`(.*)` and optional characters `an?` and `\??` with start `^` or end of line
matching `$` to make sure it doesn't accidentally match on other messages.
It takes inputs from matching capture groups as arguments for `userHasRole`.

![userHasRole load snippet](snippets/userHasRole-command.png)

In production, it would be recommended to check that the user making the request
has permission to lookup other user's roles. This could be done like:

```js
robot.respond(PATTERN, async (res) => {
const allowed = await userHasRole(robot, res.message.user, 'admin').catch()
if (!allowed) return res.reply(`Sorry, you can't do that.`)
...
})
```

### The Result

![userHasRole interaction demo](snippets/userHasRole-demo.png)

Enjoy.

___

[polacode]: https://github.com/octref/polacode
[firacode]: https://github.com/tonsky/FiraCode
> Image created by [Polacode][polacode] with [Fira Code font][firacode]
45 changes: 45 additions & 0 deletions docs/snippets/cleanRoomHistory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Use API to clear the room history (useful in testing, dangerous otherwise).
* Bot requires `clean-channel-history` permission.
* Test with `node docs/snippets cleanRoomHistory` from project root.
* @param {Robot} robot Hubot instance
* @param {User} user Hubot user object containing name and room
* @param {string} oldest ISO date string to clean all messages since then
* @return {boolean|undefined} If room was cleaned
*/
async function cleanRoomHistory (robot, user, oldest) {
try {
const latest = new Date().toISOString()
const roomId = user.roomID
robot.logger.info(`[cleanRoomHistory] ${user.name} cleaning room ${user.room} from ${oldest} to ${latest}`)
await robot.adapter.api.post('rooms.cleanHistory', { roomId, latest, oldest })
return true
} catch (err) {
robot.logger.error(`[cleanRoomHistory] failed, ensure bot has \`clean-channel-history\` permission`, err)
}
}

/**
* Add command for bot to clear the room history (requires client reload).
* e.g. "bot clr" or "@bot clear room" or "bot clr from June 3, 2018 17:30".
* @param {Robot} robot The Hubot instance
*/
function load (robot) {
robot.respond(/\b(clean room|clr)( from (.*))?(\.|!|)?$/i, async (res) => {
try {
const from = res.match[3] || 'May 19, 2015 04:36:09' // clear all if not given date
const oldest = new Date(from).toISOString()
const cleaned = await cleanRoomHistory(robot, res.message.user, oldest).catch()
if (typeof cleaned === 'undefined') {
res.reply(`Sorry, I'm afraid I can't do that.`)
}
} catch (err) {
res.reply(`That wasn't a valid date`)
}
})
}

module.exports = {
cleanRoomHistory,
load
}
33 changes: 33 additions & 0 deletions docs/snippets/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Code below initialises a Hubot instance, connecting to a local Rocket.Chat.
* It is only for demonstrating the snippets in a development environment. They
* are provided as tips for advanced use cases, not finished scripts to deploy.
*/

// Force some configs for demo
process.env.ROCKETCHAT_ROOM = 'general'
process.env.LISTEN_ON_ALL_PUBLIC = false
process.env.RESPOND_TO_EDITED = true
process.env.RESPOND_TO_DM = true
process.env.HUBOT_LOG_LEVEL = 'debug'

// Hack solution to get Hubot to load a custom local path as adapter
const { Robot } = require('hubot')
Robot.super_.prototype.loadAdapter = function () {
this.adapter = require('../../').use(this)
this.adapterName = 'hubot-rocketchat'
}

// Robot args --> adapterPath, adapterName, enableHttpd, botName, botAlias
const bot = new Robot(null, null, false, 'bot', 'hubot')

// Require the snippet at the path given in loading args...
// e.g. `node docs/snippets userHasRole`
const snippetArg = process.argv[2]
try {
const snippet = require(`./${snippetArg}`)
bot.adapter.on('connected', () => snippet.load(bot))
bot.run()
} catch (error) {
bot.logger.error(`Couldn't require snippet path: ./${snippetArg}`, error)
}
47 changes: 47 additions & 0 deletions docs/snippets/syncBrainUsers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Use API user helper to sync RC users with Hubot brain.
* Test with `node docs/snippets syncBrainUsers` from project root.
* @param {Robot} robot The Hubot instance
* @returns {string|undefined} List of names added
*/
async function syncBrainUsers (robot) {
try {
robot.logger.info(`[syncBrainUsers] adding all users to Hubot brain`)
const allUsers = await robot.adapter.api.users.all()
const knownUsers = robot.brain.users()
const addedUsers = []
for (let user of allUsers) {
if (knownUsers[user._id]) continue
robot.brain.userForId(user._id, {
name: user.username,
alias: user.alias
})
addedUsers.push(user.username)
}
return addedUsers
} catch (err) {
robot.logger.error('Could not sync user data with bot', err)
}
}

/**
* Add command for bot to respond to requests for brain sync with added users.
* e.g. "bot sync brain users" or "@bot sync users with brain"
* @param {Robot} robot The Hubot instance
*/
function load (robot) {
robot.respond(/^(sync users with brain|sync brain users)/i, async (res) => {
const addedUsers = await syncBrainUsers(robot)
if (typeof addedUsers === 'undefined') {
res.reply(`Sorry I can't do that.`)
} else {
const names = '@' + addedUsers.join(', @').replace(/,(?!.*,)/gmi, ' and')
res.reply(`${names} were added to my brain.`)
}
})
}

module.exports = {
syncBrainUsers,
load
}
Binary file added docs/snippets/userHasRole-command.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/snippets/userHasRole-demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/snippets/userHasRole-function.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 46 additions & 0 deletions docs/snippets/userHasRole.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Use API to check if role exists on given user.
* Bot requires `view-full-other-user-info` permission.
* Test with `node docs/snippets userHasRole` from project room.
* @param {Robot} robot Hubot instance
* @param {User} user Hubot user object containing name
* @param {string} role Role to check for in roles array
* @return {boolean|undefined} If user has the role
*/
async function userHasRole (robot, user, role) {
try {
robot.logger.info(`[userHasRole] checking if ${user.name} has ${role} role`)
const info = await robot.adapter.api.get('users.info', { username: user.name })
if (!info.user) throw new Error('No user data returned')
if (!info.user.roles) throw new Error('User data did not include roles')
return (info.user.roles.indexOf(role) !== -1)
} catch (err) {
robot.logger.error('Could not get user data with bot, ensure it has `view-full-other-user-info` permission', err)
}
}

/**
* Add command for bot to respond to requests for a role check on a user.
* e.g. "Hubot is admin an admin?" or "@bot is bot a bot" - both reply true.
* @param {Robot} robot The Hubot instance
*/
function load (robot) {
robot.respond(/\bis (.*) an? (.*?)\??$/i, async (res) => {
const name = res.match[1]
const role = res.match[2]
const hasRole = await userHasRole(robot, { name }, role).catch()
if (typeof hasRole === 'undefined') {
res.reply(`Sorry, I can't do that.`)
} else {
res.reply((hasRole)
? `Yes, @${name} has the \`${role}\` role.`
: `No, @${name} does not have the \`${role}\` role.`
)
}
})
}

module.exports = {
userHasRole,
load
}
Loading