Note: This project is a work in progress as a result these notes reflect the intention of the project and what has been done so far.
We needed a way to automatically invite new members to Slack. We're not the first, it turns out someone has already done this by integrating the Typeform API with Slack. We aren't using Typeform so I decided to work on integrating Slack with Plone, the content management system that powers PythonJamaica.
Initial Research
The first lead came from @lesmumba who pointed out that a Slack community for digital nomads has done an implementation. Further research led me to other implementations including this one written in clojure and one written in nodejs. After reading up on their process the key take-away for me was that Slack's invitation API was "undocumented".
Request Sniffing
To get an idea of what happens when an invite is sent out I used Chrome's developer tools to "sniff" the requests that my browser sent out when issuing an invitation. Yes (eyes rolling), I know I could have just compared what the other implementations did, in fact, I did some of that also.
Using the Slack website, I invited a new user and kept my eye on the Chrome developer tools console as it recorded the requests that were sent in the process.
When an invitation is sent from Slack here's what shows up in the Developer tools "Network" section.
Testing it out
Right clicking on the request I was able to select "Copy as cURL" and then, since Postman supports importing cURL commands, I imported it into the Postman add-on.
Using the Postman interface I was able to isolate important things such as my token, my client id and my client secret/password. Basically creating a new invite is a post request sent to the Slack API, it seems to depend on a token and authentication via a client id and client secret.
First implementation (hardcoded)
I normally aim to do the quickest experiment that will show me that everything works. One quick solution is to create a simple Python script that does the key action, at this point it does not matter that we are hardcoding the solution we just want it to work. I created a simple Python script which utilizes the requests library to send a post to the Slack API (yes I know there's urllib2 in the standard library, but I'm a little allergic to it). I started out with everything hard coded. The script looks like this (note: since "requests" is not part of the standard library make sure it's available to your script, you'll need to use pip or something similar to install it):
import requests
url = "https://pythonjam.slack.com/api/users.admin.invite?t=1430801702"
clientid = "xxxxxx.xxxxxxx"
secret = "1xxxx057f2xxxxxx293c50"
payload = {}
payload["email"]="pxxxxxxxxx@gmail.com"
payload["channels"]="C04LBS6SA,C04KU7AJY"
payload["first_name"]="John"
payload["last_name"]="Brown"
payload["token"]="xoxs-467xxxxx10-xxxx73177-xx20xxxxx13-0xxx9exxx6"
payload["set_active"]="true"
payload["_attempts"]=1
r = requests.post(
url,
data=payload,
auth=(clientid,secret)
)
# the print statements are here for the sake of debugging
print r.status_code
print r.json()
As you can see the first iteration of the script is absolutely hard coded, which is fine as all I wanted to be able to do was see everything work. For the sake of not broadcasting my credentials to the internet I've replaced some of the values with 'x's. Running this script on the command line resulted in the following output
200
{u'ok': True}
and on the second attempt, something like this
200
{u'ok': False, u'error': u'already_invited'}
Next we need to make the script into a utility
So far so good, now we need a name. I decided to call it "autoslack" from Automate + Slack, this leaves space for additional functionality in the future, in case I decide I don't want it to just be a an auto inviter. Next we'll need to have this script act as a utility so that we can "import autoslack" into future Python projects. We also don't want to hardcode the token or invitee's information (firstname, lastname and email).
# autoslack.py
"""
Accept first name, last name and email address of member
Use the __name__ == '__main__' approach
generate a timestamp for the URL
add the ability to pass credentials (clientid, token, token)
"""
usage = """
usage:
import autoslack
autoslack.invite(group="pythonjam",
token="XXXXXXX",
firstname="XXXXXXXX",
lastname="XXXXXXX",
emailaddress="XXXXXXX",
channels=['XXXXX','XXXXX'])
"""
import requests
import time
def invite(group="",
token="",
firstname="",
lastname="",
emailaddress="",
channels=[]):
timestamp=int(time.time())
slackuserapi = "api/users.admin.invite"
url = "https://{}.slack.com/{}?t={}".format(
group,
slackuserapi,
timestamp
)
# we should consider the channel api to autoinvite users to all channels
channels = ",".join(channels)
payload = {}
payload["email"]=emailaddress
payload["channels"]=channels
payload["first_name"]=firstname
payload["last_name"]=lastname
payload["token"]=token
payload["set_active"]="true"
payload["_attempts"]=1
r = requests.post(
url,
data=payload
# auth=(clientid,secret)
)
# the print statements are here for the sake of debugging
return r.json()
if __name__ == '__main__':
print("use this module with another program")
print(usage)
print ("# that is all!")
Note that this version doesn't not include the client and secret used in our first hardcoded version. It seems the authentication is not required. There seems to be enough evidence to suggest that without the authentication Slack rate limits the number of invite events that can be sent to the API, for our light usage this should not be a problem.
Distributing autoslack via pypi
I made an initial effort to package autoslack as for distribution via pypi, unfortunately it is a "brown bag" release (so the package doesn't work)... Will get back to it when I have time.
Integrating this with Plone
For this we'll need to configure Plone to listen for a user registration event and let that event trigger the sending of a Slack community invitation. Secondly we'll provide a way for an administrator to store key configuration settings like the token. This approach will provide the "infrastructure" needed to use different Slack communities on different sites by simply installing and adding appropriate credentials.
Future nice to haves
- A standalone form, so that persons not joining the website can still join the Slack community.
- Ability to support multiple Slack community forms per site