PythonJamaica is ...

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.

Save as curl

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.

Postbin

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).

To use the library you will need a Slack API token get it here: https://api.slack.com/web#authentication.
 
Get a token
 
Here's the first version:
 
# 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

Now that we have a library that we can import into our Python projects we can now begin the task of integrating it into our content management system

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