An open API service indexing awesome lists of open source software.

https://github.com/mattstein/export-reeder-starred

Export Reeder 5 starred posts into JSON or CSV format.
https://github.com/mattstein/export-reeder-starred

reeder rss

Last synced: about 1 month ago
JSON representation

Export Reeder 5 starred posts into JSON or CSV format.

Awesome Lists containing this project

README

          

# export-reeder-starred

Exports starred items from Reeder 5’s local database to JSON or CSV.

## What it does

Reads Reeder 5’s Realm database directly from its sandbox location, extracts all starred items, and writes them to stdout or a file. Each record includes:

| Field | Notes |
|-------|-------|
| `title` | Article title |
| `link` | Article URL |
| `author` | Author name |
| `feedUrl` | Feed URL (Reeder doesn’t store a feed title in the DB) |
| `starredDate` | ISO 8601 timestamp |
| `summary` | Plain-text excerpt |

Results are sorted newest-starred first.

## Requirements

- **Reeder 5 for Mac** — the database lives at `~/Library/Containers/com.reederapp.5.macOS/Data/Library/Application Support/default.realm`
- **macOS 13+**
- **Swift 5.9+** (ships with Xcode 15+; check with `swift --version`)

No need to close Reeder before running — the tool copies the database to a temp directory before opening it, so the original is never touched.

## Build

First build downloads ~300 MB of realm-core and takes several minutes. Subsequent builds are fast.

```sh
cd /Users/mattstein/Projects/export-reeder-starred
swift build -c release
```

The binary lands at `.build/release/export-reeder-starred`.

Optionally copy it somewhere permanent:

```sh
cp .build/release/export-reeder-starred /usr/local/bin/
```

## Usage

```
export-reeder-starred [options]

Options:
-f, --format Output format (default: json)
-o, --output Write to file instead of stdout
-r, --realm Realm file (default: Reeder 5 on current user)
-h, --help Show this help
```

**JSON to stdout:**
```sh
.build/release/export-reeder-starred
```

**CSV to a file:**
```sh
.build/release/export-reeder-starred -f csv -o ~/Desktop/starred.csv
```

**JSON to a file:**
```sh
.build/release/export-reeder-starred -o ~/Desktop/starred.json
```

## Non-obvious things

### realm-swift is pinned to an exact version — don’t upgrade it carelessly

`Package.swift` uses `exact: "10.32.0"` (realm-core 12.9.0). This is not arbitrary:

- **realm-core 13.x deadlocks** when opening Reeder’s format-9 file from a CLI process. The exception handler’s cold path (`Exception::what()`) and the notification listener thread (`kevent`) wait on each other and never resolve.
- **realm-core 14+** dropped support for file format 9 entirely and returns an immediate error.
- realm-core **12.9.0** is the sweet spot: it supports format 9 and produces real errors instead of deadlocking.

If Reeder ever upgrades to a newer Realm (writing format 10+), you’d need to bump the realm-swift dependency accordingly.

### The file must be copied before opening

Opening format 9 in read-only mode is rejected by realm-core 12.x — it needs write access to upgrade the format to its native version (22). The tool copies `default.realm` to a temp directory, upgrades the copy, reads it, and deletes the copy on exit.

### The schema has some surprises

- `Item.starred` is stored as `Int` (0 or 1), not `Bool`
- `Item.starredDate` is stored as a `Double` (Unix timestamp), not a `Date`
- `Feed` has **no title field** — the feed URL is the closest identifier available
- Both `Item` and `Feed` use `String` primary keys

### Migration version must be higher than the file’s

Reeder sets the Realm schema version to `1`. The tool sets `schemaVersion: 2` with a no-op migration block. This is what tells realm-core “I know the schema changed, proceed anyway” — without a higher version number, realm-core refuses to open the file when the Swift model definitions don’t exactly match what’s stored.

### Realm work runs on a dedicated thread

In CLI processes (no AppKit run loop), certain realm-core internal schedulers can stall. All Realm operations run on a background `Thread` while the main thread spins `RunLoop.main` to drain any callbacks.