In a previous post I explained how I hooked up Raindrop to Zapier, in order to push links to this blog. The result is this linkblog here.
At the end of the article I noted that Zapier is a hosted service and that I'd love to do this with an alternative. True to my word, I sat down that very same day, spun up an instance of n8n, which is like Zapier only more community-oriented. Also, it lets you install your own version on a server you own (or rent, as in my case).
It took me the better part of that night and then a bit of the next day, but I figured out how to replicate what I did with Zapier.
This is how I did it.
Connecting n8n to Raindrop
N8n provides so-called nodes. Little self-contained applets that let you do certain things - among those is one specifically for Raindrop. All I had to do is create the credentials for n8n in Raindrop, then hook up n8n to Raindrop using exactly those.
Step one: easy!
Connecting n8n to Ghost
I then did the same thing with Ghost. Again: create credentials in the Ghost backend, so n8n could hook up to Ghost's webhooks (or is the app I created in the Ghost backend the webhook? Not sure about the terminology). Anyway, this too worked without a hitch.
Step two: easy!
What to put where
Now came the trickier part: which parts of the items do I want to show up in a post on Ghost? Fortunately, after having set all this up on Zapier, it wasn't that tricky. Both n8n and Zapier work with the same underlying technology, so I could more or less replicate what I had done on Zapier. After a bit of tinkering, I'd had this node set up as well.
Step three: fairly easy!
The trigger
Now, after setting up those two nodes, I needed something that would actually trigger the execution of that workflow. I knew that Zapier imported my links every fifteen minutes, so I figured they were polling Raindrop every 15 minutes and then work with whatever they found. n8n too provides a polling node to execute a workflow, so I set one up, telling it to poll every ten minutes. I could have gone lower, but I didn't want to risk Raindrop having an issue with the number of requests I'd send their way. I could also have set polling higher, but I wouldn't want new posts on my site appearing immediately one after the other. Ten minutes is a sweet spot, because I rarely save more than one link within that time-frame.
Step Four: still fairly easy!
So, after this step I had the following setup:
The slightly tricky bit
Now comes the tricky bit, something which Zapier did behind the scenes, but I had to do manually with n8n.
You see, with this workflow, n8n would trigger an import of the previous ten items in my Linkblog category, then send them to my website.
But ten minutes later, it would do the same thing all over again, and I'd end up with a website quickly filling up with duplicate posts. By the end of the day, I'd have to do a whole lot of cleanup.
Ideally, n8n would first check whether something's already been passed through the workflow and only execute the last step if there's an item that had not previously been pushed to Ghost.
So I did a bit of searching and lo and behold, n8n provides a JavaScript function called GetWorkflowStaticData. Adding this to a "code node", this function basically helps you store, within the node, the data that's already been processed and then lets you check, with some carefully crafted code, whether there's new data within the latest import.
Now, I'm by no means a proficient JavaScript coder, but I've dabbled in the past, so I looked for example code I could use with my setup, and found one in this blogpost about polling with n8n.
Unfortunately, I only got it to either send everything or nothing (still not sure what I did wrong). This step proved to be too tricky, and it was already late at night, so I decided to let that one stew for the night and try it again the next morning.
Step five: TRICKY!
Finally, correct code
The next day I scoured the Internet again and finally struck gold in the community section of n8n. Someone had set up a similar node, and had thankfully published their nodes on n8n for everyone to peruse. So I copied that code, which seemed a bit easier to modify, put it into my code node, changed some IDs and hit execute. And voilà, there it was! Suddenly the buck did stop at the code node, if previous links had already been pushed to Ghost.
Step Five: tricky, but doable.
If you're interested in what that code looks like, here it is (to be honest: not entirely sure why this one works, but it does, so I'm not complaining):
const staticData = $getWorkflowStaticData('global');
const newBMIds = items.map(item => item.json["_id"]);
const oldBMIds = staticData.oldBMIds;
if (!oldBMIds) {
staticData.oldBMIds = newBMIds;
return items;
}
const actualNewBMIds = newBMIds.filter((id) => !oldBMIds.includes(id));
const actualNewBM = items.filter((data) => actualNewBMIds.includes(data.json['_id']));
staticData.oldBMIds = [...actualNewBMIds, ...oldBMIds];
return actualNewBM;
Some cosmetic changes
Since we're here, let's quickly talk about some cosmetic changes.
One thing that bugged me was the fact that my current template (Alto, which is great), by default wouldn't show full content posts in overviews (holds true for the frontpage and the tag-page, a.k.a. the Linkblog). This is fine for actual posts, but with the Linkblog I want people to be able to scroll through it and then click the actual link I posted, something that's not possible if only an excerpt is shown (no HTML in excerpts).
So what I needed was something that would still display the excerpts for longer posts, but the full content of the linkblog posts. I did some searching and found that Ghost provides a helper function called {{has}} (this is called a handlebar, and it's basically a shortcode for a JavaScript function used in Ghost).
Again, after a lot of trial and error and some pleading in the Ghost forum to point me in the right direction, I found out that this snippet needs to be in a file called loop.hbs (a so-called partial, called from within another file).
This specific portion of the loop file now looks like this:
{{#has tag="Linkblog"}}
{{content}}
{{else}}
<div class="post-excerpt">
{{excerpt words="33"}}
</div>
{{/has}}
And that, my dear friends, was it.
I can now lazily add links to Raindrop, and within ten minutes max, they'll be up on this very website.
Btw, in case you're using a feed reader: Ghost provides RSS feeds for everything, including tags. Which means if you feel like subscribing to my Linkblog, you can do that by clicking right here.