Forward Disqus comments to Slack using Azure Functions

Anders Austad

Hello! Today we're cooking up a new recipe titled "Potion for forwarding Disqus comments to Slack". It's experimental, and sometimes smells bad, but it's fun and gets the job done. Bring your apron!

What we're making

(If your blog is using the Disqus commenting engine, the following solution can be used to get a notification in Slack for all new comments)

Ingredients
1 timer-based Azure function
1 Disqus api
1 Slack webhook
130 lines of C#

Preferred kitchen utensils
Visual Studio Code
CMDER
Postman

Soundtrack
Norwegian Space Disco on Spotify

Ready?
Let's cook!

Disqus API

Disqus has no out-of-the-box Slack integration and only supports comment notifications on email. But, there's a publicly available API!

A Disqus comment is a "post" in Disqus-terms. The API has a posts/list endpoint with a forum property, let's have a look it using Postman.

Request https://disqus.com/api/3.0/posts/list.json?forum=novanet-blog responds with:

{
  "code": 5,
  "response": "Invalid API key"
}

Yeah, that would have been too easy. Luckily, according to the auth docs, the posts endpoint supports performing actions on behalf of an owner account using a secret key (in addition to the expected OAuth2 options).

To get a secret key for Disqus you'll have to register an application and then visit the applications overview to retrieve the key. For our needs you can skip the public key, creating a secret key will do.

With the new key added to the url we'll try another lookup:

Request https://disqus.com/api/3.0/posts/list.json?forum=novanet-blog&api_secret=SECRET_KEY returns:

Response from Disqus API request

That was easy! Now we just need to add a related=thread parameter to enrich the result with blog post metadata, as well as start=ISO-TIME to only retrieve the comments posted after the specified time.

An example of a functioning URL (with the secret redacted):

https://disqus.com/api/3.0/posts/list.json?forum=novanet-blog&related=thread&start=2017-01-01T18%3A30%3A39%2B00%3A00&api_secret=SECRETKEY

Good! We're able to retrieve the latest comments from Disqus. Let's move on to Slack.

Slack webhook

Setting up a webhook in Slack is as easy as it gets. Log in to your Slack team on the web and go to the "Custom integrations" tab. The url would be something like https://teamname.slack.com/apps/manage/custom-integrations.

Click on "Incoming WebHooks" and "Add configuration". Select the proper channel to add an integration to and click "Add Incoming WebHooks integration":

Add Incoming webhooks integration

On the next page you'll be able to set the webhook icon and name, but the most important part is the unique Webhook URL. Grab it and put it in your cookie jar. It should look something like this: https://hooks.slack.com/services/gibberish/gibberish/muchgibberish

Posting a properly formatted message body to this url will pass the message on the the selected channel. Let's try it in Postman:

Postman to Slack

And the result in Slack:

Post in Slack

Ok! Those are the two main ingredients, now let's mix them together inside an Azure function.

Azure functions

Azure functions are similar to Amazon's lambdas and lets you run programs without provisioning or managing a server. It's just code executed on-demand.

Plan: create a function triggered every 15 minutes that retrieves the latest comments from Disqus and forwards them to Slack.

There's a getting-started guide in the official docs which covers all the details to get a function up and running. From the list of supported triggers, the timer trigger is the one we'll use in this example. The timer trigger can be configured with a cron expression which covers our needs for a simple scheduling mechanism.

Azure Functions CLI

"The Azure Functions CLI provides a local development experience for creating, developing, testing, running, and debugging Azure Functions." - Azure team

Well that sounds jolly. Let's try it.
(I'm assuming you already have npm/nodejs and Visual Studio Code installed)

Install the CLI:
npm i -g azure-functions-cli

Initialize the project:
mkdir disqus2slack
cd disqus2slack
func init

The func init command above will create the development baseline for an Azure function, creating .gitignore, host.json, appsettings.json and even a launch.json for use with Visual Studio Code.

Then run:
func new
to bring up the yeoman-inspired project generator.

yeomanish

This samples uses C# so select that one and select the "TimerTrigger" on the next screen. This will add all the necessary scaffolding for a function.

baselinefiles

To get this running we'll have to associate the project with an Azure account. This can also be done using the command line:

Pop up the Azure login prompt using

func azure login

And input the correct credentials. The default subscription will be selected, to switch to another one use

func azure account set {subscription-guid}

Then call fetch to download the encrypted version of the settings

func azure functionapp fetch disqus2slack

These will be stored in the local appsettings.json file, which you'll want to keep out of version controll (it should already be part of .gitignore).

Finally, we should have everything we need to run the function locally:

func run disqus2slack

first_run

Superb! Fire up Visual Studio Code and get coding.

code .

Note: you can also debug the function from VS Code when running locally, more info about that here.

Source code

I won't go through all the details of the source code here as it's fairly simple and straight-forward. You can have a look at it on github. However, there's a couple of things worth mentioning:

C# Azure functions are written using C# script files (.csx). For now, this format has very limited tooling support so you'll have to do without Intellisense. More about that here.

By default, the scheduler is configured to run every 5 minutes. We'll change this to 15 in function.json: "schedule": "0 */15 * * * *".

A function will often be so small that everything can be kept inside the main run.cxs file. If more files are needed, these can be included using #load ".\filname.csx".

Some System.* and Microsoft.Azure.WebJobs.* assemblies are automatically imported to all .csx files. Others will have to be imported explicitly using the #r notation. There's one example of this in the source code, importing Json.NET using #r "Newtonsoft.Json".

Application settings (like connection strings and webhook urls) need to be added to Azure first, then "downloaded" to the local appsettings.json file using func azure functionapp fetch {your-app-name}.

Azure Functions very recently added support for automatic deployment from various sources like GitHub, VSTS etc. This can be easily configured in the Azure Functions portal at https://functions.azure.com.

Wrapping up

We've created an Azure function that runs every 15 minutes, retrieves all new comments from the last 15 minutes and forwards them to Slack. Thumbs up!

What we're making

And of course feel free to leave a comment so I can verify that this is actually working.

Fine print: The function execution time is not 0 ms, which opens up for the possibility that a comment may be posted after the function has started, but before it has retrieved the latest comments, meaning it will be included in the cross-posting to Slack on the current and the next execution, resulting in a double post. This, we simply ignore and hope the customer won't notice.