This is the Node.js server I wrote to get Patreon and Ko-fi donation notifications streamed into my Monzo bank accounts feed. If you want to run your own instance, this script is designed to be run on Google Firebase Functions so you don't even need to manage a proper server! Deploy this function to firebase and then supply the functions URLs to Patreon/Kofi's webhook APIs, pretty simple!
Last active
July 9, 2021 19:42
-
-
Save LotteMakesStuff/b075886788e2705e27ce85f8fb80d23d to your computer and use it in GitHub Desktop.
Patreon/Ko-fi -> Monzo feed notification server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Patreon webhook registration: https://www.patreon.com/portal/registration/register-webhooks | |
// Ko-fi webhook registration: https://ko-fi.com/Manage/Webhooks | |
const functions = require('firebase-functions'); | |
var https = require('https'); | |
var querystring = require('querystring'); | |
// Constant values we need to use the Monzo API. You can get account ID and a Access token from the monzo developer playground https://developers.monzo.com/api/playground | |
const url = 'api.monzo.com'; | |
const account_id = <PUT ACCOUNT ID HERE>; | |
const access_token = <PUT ACCESS TOKEN HERE>; | |
exports.kofiHook = functions.https.onRequest((req, res) => { | |
var kofiData = JSON.parse(req.body.data); | |
console.log("Message: " + kofiData.message); | |
// build the request object that Monzo expects for a feed item creation call | |
var requestData = { | |
account_id: account_id, | |
type: "basic", | |
url: "https://www.paypal.com/uk/signin", | |
'params[title]': "☕ Someone got you a coffee! ☕", | |
'params[body]': kofiData.message + ' - ' + kofiData.from_name + ' [$' + kofiData.amount + ']', | |
'params[image_url]': "https://monzo-server.firebaseapp.com/API/Ko-fiLogo.png", | |
'params[background_color]': "#2F4050", | |
'params[body_color]': "#FF5F5F", | |
'params[title_color]': "#ffffff" | |
}; | |
// and turn it into a query string | |
var dataString = querystring.stringify(requestData); | |
console.log(dataString); | |
var contentLength = dataString.length; | |
// create the options object http request needs to call the monzo api | |
var options = { | |
host: url, | |
path: '/feed', | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/x-www-form-urlencoded', | |
'Content-Length': contentLength, | |
'Authorization' : ' Bearer ' + access_token | |
} | |
}; | |
// Fire off the call! | |
DoApiCall(options, dataString, | |
function(data){ | |
res.status(200); | |
res.end(JSON.stringify(data)); | |
}, | |
function(errorMessage){ | |
res.status(500); | |
res.end(errorMessage); | |
} | |
); | |
}); | |
exports.patreonHook = functions.https.onRequest((req, res) => { | |
console.log('HEADERS: ' + JSON.stringify(req.headers)); | |
console.log("Message: " + JSON.stringify(req.body)); | |
var patreonData = req.body; | |
console.log("TMP:" + JSON.stringify(patreonData.included[0])); | |
var title; | |
var body; | |
if (req.headers['x-patreon-event'] === "pledges:create") | |
{ | |
console.log("THIS IS A PLEDGE REQUEST"); | |
title = "New Patreon Subscriber! 💸"; | |
body = patreonData.included[0].attributes.full_name + " subscribed at $" + (patreonData.data.attributes.amount_cents / 100).toFixed(2) + " a month!"; | |
} | |
else if (req.headers['x-patreon-event'] === "pledges:update") | |
{ | |
console.log("THIS IS A PLEDGE UPDATE"); | |
title = "Someone updated their Patreon subscription! 💵"; | |
body = patreonData.included[0].attributes.full_name + " updated thier subscription to $" + (patreonData.data.attributes.amount_cents / 100).toFixed(2) + " a month!"; | |
} | |
else | |
{ | |
title = "Someone deleted their Patreon subscription! 💔"; | |
body = "😭"; | |
console.log("THIS IS A PLEDGE DELETION"); | |
} | |
var requestData = { | |
account_id: account_id, | |
type: "basic", | |
url: "https://www.paypal.com/uk/signin", | |
'params[title]': title, | |
'params[body]': body, | |
'params[image_url]': "https://c5.patreon.com/external/logo/downloads_logomark_color_on_coral.png", | |
'params[background_color]': "#F96854", | |
'params[body_color]': "#052D49", | |
'params[title_color]': "#ffffff" | |
}; | |
var dataString = querystring.stringify(requestData); | |
console.log(dataString); | |
var contentLength = dataString.length; | |
var options = { | |
host: url, | |
path: '/feed', //'/ping/whoami', | |
method: 'POST', //'GET', | |
headers: { | |
'Content-Type': 'application/x-www-form-urlencoded', | |
'Content-Length': contentLength, | |
'Authorization' : ' Bearer ' + access_token | |
} | |
}; | |
DoApiCall(options, dataString, | |
function(data){ | |
res.status(200); | |
res.end(JSON.stringify(data)); | |
}, | |
function(errorMessage){ | |
res.status(500); | |
res.end(errorMessage); | |
} | |
); | |
}); | |
// Fuction that wraps calling a http request for us. We supply two callbacks to control what happens when | |
// the request gets a response. | |
function DoApiCall(options, requestData, onCompleted, onError) | |
{ | |
var req = https.request(options, function(res) { | |
console.log('STATUS: ' + res.statusCode); | |
console.log('HEADERS: ' + JSON.stringify(res.headers)); | |
res.setEncoding('utf8'); | |
var responseString = ''; | |
res.on('data', function (data) { | |
responseString += data; | |
console.log('BODY: ' + data); | |
}); | |
res.on('end', () =>{ | |
if (res.statusCode === 200) { | |
onCompleted(JSON.parse(responseString)); | |
} | |
else { | |
onError("Server did not return 200. " + JSON.stringify(responseString)); | |
} | |
}); | |
}); | |
req.write(requestData); | |
req.end(); | |
} |
That’s good to know! I’ll look into that next
Nice !
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is awesome ❤️
I'm gonna try and make a version of this that can refresh oauth tokens, too – they expire after a pretty short amount of time >.<