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!
(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?
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:
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":
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:
And the result 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.
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.
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
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!
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.