Writing a Hugo Raindrop.io bookmark integration with AI
#python #bookmarks #howto #blog #personalTable of Content
bacardi55 [1] has an interesting bookmarks section on his blog, where a script generates Hugo markdown pages for bookmarks he has added to a central (self-hosted) bookmark manager. I found this very interesting and wanted to build something similar. I’ve been using raindrop.io for some time now, and this service also provides an API. So this could be an easy project to do with the help of Github Copilot, which I have recently been using to give my software development projects a boost.
So here are my goals:
- Generate Hugo Markdown content using the Raindrop.io API (link and note)
- Use only bookmarks from a specific publicly shared collection
- A new section on my blog, only for bookmarks
- Use python
- Use Github Copilot for creating the python script
- Find a way to automate the update of the GitHub pages
There was one step in the workflow that I was not sure how to do: I don’t want to manually deploy my pages every time I bookmark a page. Ideally, saving a bookmark would execute a web-hook, but Raindrop.io does not support this. So the alternative is to run a script on my server that fetches new bookmarks, generates the markdown files and pushes them to Github pages. But for now I will do it the manual way and decide later how improve it.
The AI guided script generation
I used the following prompt to generate the Python script with GitHub Copilot. I just selected the following snippet of text and used the Visual Code Copilot plugin: “Write the Python script based on my comments in file”:
1# import API token, API endpoint and collection ID from .env file
2# RAINDROP_API_KEY=
3# RAINDROP_COLLECTION_ID=46414428
4# RAINDROP_ENDPOINT=https://api.raindrop.io/rest/v1/raindrops/
5
6# get the last 5 bookmarks from the API endpoint
7# use only links with a note
8
9# generate markdown files with the following content
10# +++
11# title = <title>
12# date = <bookmark creation date>
13# categories = [ 'bookmarks' ]
14# +++
15#
16# <note>
17#
18# <link>
19# <excerpt>
20
21# generated files stay in subfolder content/bookmarks
22# don't process links you already have a markdown file for
The result was almost usable on the first try. Only the variable for the endpoint and the handling of the API date field (which is a string, but Copilot assumed an integer) needed manual fixing. Here is the output:
1import os
2import requests
3from datetime import datetime
4from dotenv import load_dotenv
5
6# Load environment variables from .env file
7load_dotenv()
8api_key = os.environ.get('RAINDROP_API_KEY')
9collection_id = os.environ.get('RAINDROP_COLLECTION_ID')
10endpoint = os.environ.get('RAINDROP_ENDPOINT')
11
12# Check if the required environment variables are set
13if not api_key or not collection_id or not endpoint:
14 print("Please set the environment variables")
15 exit(1)
16
17# Get the last 5 bookmarks from the API endpoint
18url = f"{endpoint}/{collection_id}?perpage=5"
19headers = {"Authorization": f"Bearer {api_key}"}
20response = requests.get(url, headers=headers)
21
22# Check if the request was successful
23if response.status_code != 200:
24 print("Failed to retrieve bookmarks from the API")
25 exit(1)
26
27# Process the bookmarks
28bookmarks = response.json().get("items", [])
29for bookmark in bookmarks:
30 link = bookmark.get("link")
31 excerpt = bookmark.get("excerpt")
32 note = bookmark.get("note")
33 tags = bookmark.get("tags")
34 created_timestamp = int(datetime.fromisoformat(bookmark.get("created")).timestamp())
35 creation_date = datetime.fromtimestamp(created_timestamp)
36
37 # Generate the markdown file path
38 file_name = f"content/bookmarks/{creation_date.strftime('%Y%m%d')}.md"
39
40 # Check if the markdown file already exists
41 if os.path.exists(file_name):
42 continue
43
44 # Generate the markdown content
45 markdown_content = f"""+++
46title = "{bookmark.get('title')}"
47date = "{creation_date}"
48categories = [ 'bookmarks' ]
49tags = {tags}
50+++
51
52{note}
53
54{link}
55
56{excerpt}
57"""
58
59 # Write the markdown content to the file
60 with open(file_name, "w") as file:
61 file.write(markdown_content)
62
63print("Bookmark generation completed.")
The Theme - The hardest part
I wasn’t expecting that: Customising the theme was the part that took the most time. Not that it was complicated or anything. A change here, a change there… Made me understand the core of Hugo a bit more. So I’m ok. But for this blog post it’s too much to point out all the changes. In a nutshell, that’s what I did:
- I added a section named
content/bookmarks
. - I added a menu item in my
config.toml
- I added a small section on the mainpage for the last 5 bookmarks. For that I added this code:
1<h3>🔗 Recent bookmarks</h3>
2<ul>
3 {{ range first 5 (where site.RegularPages "Section" "bookmarks") }}
4 <li>
5 <div class="post-title">
6 <time>{{ .Date.Format "2006-01-02" }}</time>
7 <a href="{{ .RelPermalink }}">{{ .Title }}</a>
8 </li>
9 </div>
10 {{ end }}
11</ul>
Automation
I thought: “How can I make this automatic, but as simple as possible?” Since my deployment is through GitHub pages, this means that I always have to push my Markdown files to my GitHub repository. This also means I have to use the git client and do a pull/push for each bookmark update.
So I need a computer to fetch my repository, run the python script on the local repository, git commit/push the changes to GitHub. To do this, I also need access to GitHub via a password-less SSH deployment key [2]. A deployment key can be explicitly added to a single repository and only allow access to that repository.
Adding my python script to the repository
Generate a ssh-key (w/o password):
ssh-keygen -t ed25519 -C "your_email@example.com" ./deploy_key
and added the public key to the Deployment section of my repositoryAdded an entry to my
~/.ssh/config
to use the new ssh key for that GitHub repository:1Host blog 2 Hostname github.com 3 IdentityFile ~/.ssh/git_deploy_key 4 IdentitiesOnly yes 5 AddKeysToAgent yes
Clone the repository only for the bookmark creation workflow:
git clone git@blog:cloonix/cloonix.github.io ./bookmark-update
Running a script through crontab:
1git pull 2python3 raindrop_bookmarks.py 3git add ./content/bookmarks 4git commit -m "updated raindrop bookmarks" 5git push
Created a crontab to run the script.
Conclusion
This is not a step-by-step howto. More a diary of that kinda simple integration of the Raindrop API into my Hugo Github Pages deployment. But i achieved my goal: The crontab is running every how, which is more than enough, and fetches new bookmarks from my public bookmark collection. It generates the Markdown files and pushes them to Github where Actions deploy the static site.
Links
[1] https://bacardi55.io/2024/02/13/bookmarks-section-the-pesos-way-kind-of/
[2] https://docs.github.com/en/authentication/connecting-to-github-with-ssh/managing-deploy-keys