The Autodidacts

Exploring the universe from the inside out

Script: bulk star Wallabag entries by URL

Useful if you imported from Pocket CSV, and it didn’t include your favourites

Note: this post is part of #100DaysToOffload, a challenge to publish 100 posts in 365 days. These posts are generally shorter and less polished than our normal posts; expect typos and unfiltered thoughts! View more posts in this series.

I migrated my Pocket data to Wallabag when Pocket shut down. As I have mentioned before, the official Pocket CSV export doesn’t include which articles were favorite. However, data from the unofficial 3rd party Pocket exporter that I also used, does.

But the Wallabag importer is designed for use with the Pocket CSV, not the pile of JSON files from the 3rd party export.

So, I imported my Pocket CSV.

Then, I wrote a Python script that accepts a URL as input, and uses the Wallabag API to find the entry with that URL and star it.

Then, I wrote a jq snippet, which goes through all the JSON files from the 3rd party export and spits out the URLs for the entries that were starred in Pocket.

cat **.json | jq '.. | objects | select(has("isFavorite") and .isFavorite == true) | .url'

(Run it in Fish shell or Bash with setopt globstar to make ** work.)

Try that on its own in the directory with your 3rd party Pocket export first, to make sure everything looks good.

And test the script with just one URL to make sure it is working.

Then, you can pipe each of those URLs to the Python script (below) with xargs:

cat **.json | jq '.. | objects | select(has("isFavorite") and .isFavorite == true) | .url' | xargs -I{} python3 star_wallabag_entry_by_url.py "{}"

Boom! 659 favorites appear. (Actually, it runs pretty slowly.)

You will need to create a Wallabag API client (if you don’t already have one) and provide WALLABAG_BASE_URL, WALLABAG_CLIENT_ID, WALLABAG_CLIENT_SECRET, WALLABAG_USERNAME, and WALLABAG_PASSWORD as environment variables. (Unfortunately the Wallabag API requires the username and password. I know, it’s not good!)

You will also need to create a virtual environment and install two Python packages. I like to use uv for this:

uv venv .readitlater 
source .readitlater/bin/activate
uv pip install requests json

(Note: I actually got this script to run in the browser, 100% client-side, with PyScript. If you want to bulk-star Wallabag entries by URL without using Python and installing stuff, let me know and I will clean up and publish that version.)

Here’s the script. It isn’t fancy, or designed to handle every edge case — because for my purposes, it just had to work once, on my machine — but it gets the job done.

import requests
import json
import os
import sys

class WallabagAPI:
    def __init__(self, BASE_URL, CLIENT_ID, CLIENT_SECRET, USERNAME, PASSWORD):
        self.BASE_URL = BASE_URL 
        self.CLIENT_ID = CLIENT_ID 
        self.CLIENT_SECRET = CLIENT_SECRET 
        self.USERNAME = USERNAME 
        self.PASSWORD = PASSWORD 
        self.access_token = None
    
    def _get_access_token(self):
        if not self.access_token:
            data = {'username': self.USERNAME, 'password': self.PASSWORD,'client_id': self.CLIENT_ID, 'client_secret': self.CLIENT_SECRET, 'grant_type': 'password'}
 
            response = requests.post('{}/oauth/v2/token'.format(self.BASE_URL), data)
 
            access_token = response.json().get('access_token')
            self.access_token = access_token
        
            return access_token
    
    def star_article_by_url(self, url):
        """Star an article by its URL"""
        # First get the article ID
        headers = {'Authorization': f'Bearer {self._get_access_token()}'}
        params = {'url': url, 'return_id':1}
        
        response = requests.get(f"{self.BASE_URL}/api/entries/exists.json", 
                            headers=headers, 
                            params=params)
        response.raise_for_status()
        
        data = response.json()
        print(data)
        
        entry_id = data['exists']
        if entry_id:
            print(f"starring {entry_id} ({url})")
            
            # Star the article
            response = requests.patch(
                f"{self.BASE_URL}/api/entries/{entry_id}.json",
                headers=headers,
                data={'starred': 1}
            )
            response.raise_for_status()
        
            return True
        else:
            print(f"No ID found ({entry_id}) for url: {url}")
            return False
        
# Initialize API client
wallabag = WallabagAPI(
    BASE_URL=os.environ["WALLABAG_BASE_URL"],
    CLIENT_ID=os.environ["WALLABAG_CLIENT_ID"],
    CLIENT_SECRET=os.environ["WALLABAG_CLIENT_SECRET"],
    USERNAME=os.environ["WALLABAG_USERNAME"],
    PASSWORD=os.environ["WALLABAG_PASSWORD"],
)

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python script.py <article_url>")
        sys.exit(1)
    
    url = sys.argv[1]
    wallabag.star_article_by_url(url)  

Sign up for updates

Join the newsletter for curious and thoughtful people.
No Thanks

Great! Check your inbox and click the link to confirm your subscription.