Get Slackbot To Snitch On Employee Updates
For transparency purposes I wrote a script that informs me of any changes to the user list of your Slack team. The whole thing is a fairly compact concept, with 2 different integration points to Slack and some funny Coffeescript usage.
Application flow
The application is something really simple that goes like this:
graph TD;
config[Retrieve configuration] --> Slack_users
config --> Saved_users
Slack_users[Retrieve users from Slack] --> Compare[Compare lists]
Saved_users[Retrieve saved users] --> Compare
Compare --> Send_message[Send message to configured users]
Compare --> Save_users[Save users]
Configuration
Configuration is stored in environment variables, then loaded into a configuration object that is passed around to whatever needs it. For something small and functional like this, I really like overusing the method factory pattern.
Loading configuration is as simple as this:
config =
sendTo: process.env.SLACK_SENDTO.split ","
slack:
hostname: process.env.SLACK_HOSTNAME
slackbotToken: process.env.SLACK_SLACKBOTTOKEN
token: process.env.SLACK_APITOKEN
archiveFile: "users.json"
Loading things into an object has the advantage of making it a bit cleaner and organized. It’s also easier to pass the configuration to whatever needs it, and helps you respect the dependency inversion principle.
In my “main” file, I load the configuration
return unless (config = require "./config")
getUsers = (require "./getUsers") config
sendMessage = (require "./sendMessage") config
archiveUsers = (require "./archiveUsers") config
Note the unless
, a neat syntactic sugar from coffee.
All the dependencies are built using the method factory pattern:
module.exports = (config) -> (callback) ->
https.get "https://slack.com/api/users.list?token=#{config.slack.token}&pretty=1", [...]
Integrating to Slack
Integration to Slack here is done in 2 ways:
- API integration, which gives you to a full list of resources for integration with Slack. Here, we use it to retrieve the list of users.
- Slackbot integration, the simplest way to send Slack messages from the outer world. We use it here to send messages to users.
Both integrations can be accessed from the “manage my team” interface.
Retrieving the list is done through a GET
on https://slack.com/api/users.list?token=#{config.slack.token}&pretty=1"
Posting to slackbot is done through a POST
to /services/hooks/slackbot?token=#{config.slack.slackbotToken}&channel=#{to}
Comparing the lists
Few funny things around there:
- Everything is value, so this is a legit way of collating a list of users into a string (although arguably it can be done using
map
):
(for user in users
(user.profile?.real_name + "(" + user.name + ")")
).join ","
Finding delta is done through the following logic:
- Find users in new list that don’t exist in archive (new users)
(updatedUser, savedUser) -> not savedUser
- Find users in archive that aren’t in new list, or are marked deleted in new list and not in archive (deleted users)
(savedUser, updatedUser) -> (updatedUser.deleted and not savedUser.deleted) or not updatedUser
which we feed to a function that filters entries in lists:
getListDelta = (listA, listB, comparer) ->
listA.filter ((elementFromA) =>
elementFromB = listB.find (elementFromB) => elementFromA.id is elementFromB.id
return comparer elementFromA, elementFromB
)
Result
The rest is fairly simple, saving stuff and loading stuff. The project can be found here.