Skip to content

Instantly share code, notes, and snippets.

@LotteMakesStuff
Last active July 9, 2021 19:42
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LotteMakesStuff/b075886788e2705e27ce85f8fb80d23d to your computer and use it in GitHub Desktop.
Save LotteMakesStuff/b075886788e2705e27ce85f8fb80d23d to your computer and use it in GitHub Desktop.
Patreon/Ko-fi -> Monzo feed notification server

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!

Buy Me a Coffee at ko-fi.com Become a Patron!

// 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();
}
@ameliaikeda
Copy link

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

@LotteMakesStuff
Copy link
Author

That’s good to know! I’ll look into that next

@loic35
Copy link

loic35 commented Jan 17, 2020

Nice !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment