I’ve started using Discord recently, and I was surprised that there was no way to link to a users profile. You can search for profiles inside the app, and you can add all sorts of ‘other services I use’ links, but there’s no way to display things like this. There are third-party sites like DiscordHub but they don’t seem to deal with this fairly simple use case. And it’s not like I’m the only one thinking this—there are quite a few other users interested in this. And Discord also has a perfectly good API, so why hasn’t someone else hacked this together yet? On the basis this is actually the third such app I’ve written over the last few years (Moolah for Splitwise and Docket for Beeminder/Todoist) I think it’s worth writing a guide on how I do them, if only so I can get to use others instead of having to build my own.
Parts you will need:
- A website that has an API. I’m going to assume it uses OAuth2 for authentication, a) because all three of the ones I’ve done did so, and b) if it doesn’t, user/password auth is generally even easier.
- Some knowledge of Python
- Something you want to do with data you can extract from the API. Bonus points if you want to make alterations to your data and return it (which was the case for Moolah and Docket, but not for Strife)
- (Later on) Either a Heroku account, a Wharf server, or something equivalent to host it
We’re going to be building a Flask app. Flask starts to have problems for larger apps, but for creating something smaller I find it easy to use. I’m going to try and give you most of everything you need here, but you should do some reading around the links I’ll provide to be able to understand why I took the approach I did.
Begin by making a Virtualenv (because it’s sensible to do so) and then using the following requirements.in to install the packages we’ll need with pip install -r requirements.in. Once you’ve got these installed, run pip-compile to build a proper requirements.txt
Now we can start building your app. Open a file called ‘app.py’ and drop the following in. This does a few things: imports everything you’re going to need, loads config from environment variables, 12-factor style (and uses python-dotenv so you can make a local .env file to simplify doing this locally), and sets up the flask/db/migrate bits. Incidentally, the site you’re using should have a pair of URLs in it’s documentation called the ‘Base authorization URL’ and ‘Token URL’ (they’re on this page for Discord for example). Replace the values for authorization_base_url and token_url with them.
Then insert this chunk into your app.py after the previous section. This sets up the routes that your app will provide. The index one gives you a landing page, the login one gives you something to go to, the callback is where the OAuth2 bits should go back to post-authorisation, and actual_app is a placeholder for your actual application code. There are a few things to note here: render_template runs the Flask template engine over a file called ‘templates/<whatever you gave render_template>’—it’s fairly simple, mostly just based on Jinja. session is a magic Flask object that is populated with the cookie-based session for the current request, and can be used to hand things around to later requests.
You’ll also need a ‘templates’ folder with at least an ‘index.html’ that has a link that will send the user to ‘/login’ to do the login to whatever service it is you want to use here, as well as a ‘models.py’ with at least the following in it.
Note that this ends up making a models dictionary in your app.py level, and you can just keep referring to models[“User”] to get to the class. This is slightly odd, but only a minor annoyance. Writing the line ‘User = models[“User”]‘ into your app.py after the make_models call does make things a little more usable.
Ok, so you’ve got most of the core of the app code there. You’ll now need to do some configuration. Make a file called ‘.env’—this doesn’t go in source control but is just for local development (in the real thing, you’ll be putting each line as an environment variable, but load_dotenv() solves this for local configs) and look for all the lines saying os.getenv(“<Something>”)—each of those is a environment variable you should add as a ‘NAME=value’ line to .env. You’ll have at least the following:
- DATABASE_URL—try sqlite:///test.dbfor local development or equivalent strings for other databases. If you’re not using Sqlite (which is built in to Python), find the Python package for your database (e.g. psycopg2 for Postgres), add it to ‘requirements.in’ and run pip-compile/pip-sync to get the package installed.
- SECRET_KEY—a chunk of random noise as a secret key for encrypting the sessions.
- CLIENT_ID/CLIENT_SECRET—register your app in the site you’re using and get the OAuth2 client/secret key out. E.g for Discord, that’s done here
In order to get the database schema and migrations up we’re using Flask-Migrate, so you’ll need to initialise that. See its’ documentation for more, but to begin with do:
flask db init
flask db migrate
flask db upgrade
This will initialise whatever database DATABASE_URL is set to. If you’re using the Sqlite example, you’ve now got a ‘test.db’ file.
Congratulations, you now probably have a working basic Flask Oauth2 app. flask run should let you test it out (and will tell you the URL to go to), although for local work I’d advise OAUTHLIB_INSECURE_TRANSPORT=1 FLASK_DEBUG=1 flask run to solve the lack of local HTTPS and make debugging much easier. At this point, before you mess around with anything else, setup git and commit everything you’ve got into source control. You should make a ‘.gitignore‘ file with the lines ‘test.db’, ‘.env’ and whatever folder you used for virtualenv (e.g. ENV), because those shouldn’t go in.
After that, the sky’s the limit. Keep working with the site’s API, try different things out, and maybe get to the point of something you can post and blog about. At that point, you want to look into hosting. Heroku’s free tier is good (you’ll need their guide to getting started with Python applications), or if you’ve got access to an existing machine then this might well be suited to that. Good luck! The code for Strife itself may also help as a guide to more advanced techniques.