Petit side project sur n8n
Open: Inserted image 20250621081745.png
Photo by Luca Bravo on Pexels
Petit side project sur n8n
Mise à jour du 2025-06-12
Il vient d'être accepté sur la communauté n8n \o/
Save Mastodon Bookmarks to Raindrop Automatically | n8n workflow template
Problématique
Je viens de m'apercevoir que le logiciel que j'utilisais pour transformer mes bookmarks/favoris Mastodon vers un flux RSS (https://bookmark-rss.woodland.cafe/), et qui ensuite était utilisé dans IFTTT pour envoyer les éléments à Raindrop afin de les lire plus tard, ne marchait plus.
En fait le site semble avoir un problème avec un certificat auto généré qui n'est pas reconnu. Du coup, IFTTT refuse de lire le flux RSS.
Mise en place avec n8n
J'ai pris un peu de temps et j'ai mis au point un petit flux avec n8n afin de prendre les bookmarks de Mastodon et de les envoyer directement à Raindrop.
Téléchargement
Vous pouvez le retrouver ici : Mastodon Bookmars to Raindrop - GitHub
Personnalisation
Quelques petites précisions :
- La collection Raindrop est mise à
-1
car c'est l'indice pour la collectionUnsorted
- Mastodon ne permet de récupérer que 20 valeurs par défaut (40 max) dans un appel
- La pagination des résultats utilise
min_id
. - Le
min_id
est initialisé à0
- Il est incrémenté à chaque fois avec la nouvelle valeur
- La variable est persistée d'exécution en exécution du flow
- La pagination des résultats utilise
Je vous partage tout ça, n'hésitez pas à tester et me faire des retours.
Je vais peut-être essayer de le proposer sur la partie Creator de n8n ça peut être sympa.
Save Mastodon Bookmarks to Raindrop Automatically
🛠️ Note: This workflow uses a custom Mastodon API request. Ensure your server supports bookmark access, and that your access token has the right permissions. OAuth or token-based credentials must be configured.
🧑💼 Who is this for?
This workflow is ideal for digital researchers, social media users, and knowledge workers who want to automatically archive Mastodon bookmarks into their Raindrop.io collection for future reference and tagging.
🔧 What problem is this solving?
Mastodon users often bookmark posts they want to read or save for later, but there's no native integration to archive them outside the app. This workflow solves that by syncing bookmarked posts from Mastodon to Raindrop, making them more accessible, organized, and searchable long-term.
⚙️ What this workflow does
- Triggers on schedule (or manually).
- Tracks the latest fetched min_id using workflow static data to avoid duplicates.
- Sends an HTTP GET request to the Mastodon bookmarks API, using bearer token authentication.
- Validates and processes the bookmarks if new entries exist.
- Parses pagination metadata (e.g. min_id) from response headers.
- Splits response array to handle individual bookmarks.
- Filters out entries with missing data.
- Saves each post to Raindrop.io, using its title and URL. Use the card URL if exist.
- Updates the min_id to remember where it left off.
🚀 Setup
- Create a Mastodon access token with access to bookmarks.
- Add a credential in n8n of type HTTP Bearer Auth with your token.
- Create and connect a Raindrop OAuth2 credential.
- Replace {VOTRE SERVEUR MASTODON} with your Mastodon server's base URL.
- (Optional) Adjust the scheduling interval under the "Schedule Trigger" node.
- Make sure the Raindrop collection ID is correct or leave it as default (-1) as this is the index for the
Unsorted
collection.
🧪 How to customize this workflow
- To save to a specific Raindrop collection, change the collectionId in both Raindrop nodes.
- You can extend the Code node to pull additional metadata like author, hashtags, or content excerpts.
- Add an Email or Slack node after Raindrop to notify you of saved bookmarks.
👨💻 Code
{
"name": "Save Mastodon Bookmarks to Raindrop Automatically",
"nodes": [
{
"parameters": {
"url": "{VOTRE SERVEUR MASTODON}/api/v1/bookmarks",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "min_id",
"value": "={{ $json.min_id }}"
}
]
},
"options": {
"lowercaseHeaders": true,
"response": {
"response": {
"fullResponse": true
}
}
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-1100,
200
],
"id": "1a0c020e-8e4a-43ea-affb-01dbec81bdda",
"name": "HTTP Request",
"alwaysOutputData": true,
"credentials": {
"httpBearerAuth": {
"id": "wset0GXL1IPdfhJS",
"name": "Bearer Auth account"
}
}
},
{
"parameters": {
"jsCode": "
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-660,
200
],
"id": "bb98d8b2-3a94-4cb5-9347-d5eefeee6d6b",
"name": "Code",
"alwaysOutputData": false
},
{
"parameters": {
"resource": "bookmark",
"operation": "create",
"collectionId": "=-1",
"link": "={{ $json.card.url }}",
"additionalFields": {
"pleaseParse": true,
"title": "={{ $json.card.title }}"
}
},
"type": "n8n-nodes-base.raindrop",
"typeVersion": 1,
"position": [
0,
0
],
"id": "3c2f7539-8241-4067-84c2-5417faacb3d5",
"name": "Raindrop",
"credentials": {
"raindropOAuth2Api": {
"id": "l1dJhIsqhWvUWcDw",
"name": "Raindrop account"
}
}
},
{
"parameters": {
"fieldToSplitOut": "=body",
"options": {}
},
"type": "n8n-nodes-base.splitOut",
"typeVersion": 1,
"position": [
-440,
100
],
"id": "52f70723-9de0-4dfe-accd-a80198f5ba44",
"name": "Split Out"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "10f21e21-f57e-49c1-94e0-7ea7d84dce24",
"leftValue": "={{ $json.card.url}}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-220,
100
],
"id": "be5debbf-e01a-4243-aeab-9231345f20de",
"name": "If"
},
{
"parameters": {},
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
-1540,
100
],
"id": "69952ee1-9744-42e4-9ac0-8f0cc605526c",
"name": "When clicking ‘Test workflow’"
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "minutes"
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-1540,
300
],
"id": "e61395d9-1474-4959-9a76-bf05b35b6827",
"name": "Schedule Trigger"
},
{
"parameters": {
"resource": "bookmark",
"operation": "create",
"collectionId": "=-1",
"link": "={{ $json.url }}",
"additionalFields": {
"pleaseParse": true,
"title": "={{ $json.account.username }}"
}
},
"type": "n8n-nodes-base.raindrop",
"typeVersion": 1,
"position": [
0,
200
],
"id": "7337aaeb-833d-4a95-bdda-f619eecaa8e2",
"name": "Raindrop1",
"credentials": {
"raindropOAuth2Api": {
"id": "l1dJhIsqhWvUWcDw",
"name": "Raindrop account"
}
}
},
{
"parameters": {
"jsCode": "// initialize staticData object\nconst workflowStaticData = $getWorkflowStaticData('global');\n\n// initialize accessToken on staticData if it desn't exist yet\nif (!workflowStaticData.hasOwnProperty('min_id')) {\n workflowStaticData.min_id = 0\n}\n\n\n\nreturn [\n {\n // data: $input.all(),\n min_id: workflowStaticData.min_id,\n // today: $today\n }\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1320,
200
],
"id": "790c5c2c-db4f-416a-aac0-52d0b99fd042",
"name": "Code1"
},
{
"parameters": {
"jsCode": "const workflowStaticData = $getWorkflowStaticData('global');\n\n// get new access token\nworkflowStaticData.min_id = $input.first().json.min_id ;\n// set timestamp of new access token\n\nreturn [\n {\n // data: $input.all(),\n accessToken: workflowStaticData.min_id,\n // today: $today\n }\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-440,
300
],
"id": "cbbabfd5-a20c-4cc0-9679-6a4aa85604fd",
"name": "Code2"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "25997115-4422-4d65-86aa-47c4ab3edb17",
"leftValue": "={{ $json.body }}",
"rightValue": 0,
"operator": {
"type": "array",
"operation": "lengthGt",
"rightType": "number"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-880,
200
],
"id": "1448321e-e33a-487e-8885-18fa8b3c1d9c",
"name": "If1"
}
],
"pinData": {},
"connections": {
"HTTP Request": {
"main": [
[
{
"node": "If1",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "Split Out",
"type": "main",
"index": 0
},
{
"node": "Code2",
"type": "main",
"index": 0
}
]
]
},
"Split Out": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"If": {
"main": [
[
{
"node": "Raindrop",
"type": "main",
"index": 0
}
],
[
{
"node": "Raindrop1",
"type": "main",
"index": 0
}
]
]
},
"When clicking ‘Test workflow’": {
"main": [
[
{
"node": "Code1",
"type": "main",
"index": 0
}
]
]
},
"Code1": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Code1",
"type": "main",
"index": 0
}
]
]
},
"If1": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "",
"meta": {
"instanceId": "b5a155997dbc2be3a6aa76351ad1a54b9631c4ec8a46687fa6b9f4856a6dbdce"
},
"tags": []
}