A Node.js application that fetches bookmarks from your Raindrop.io account(s) and stores them as timestamped JSON files, designed for automated GitHub Actions workflows. Supports both single and multi-account configurations.
npm installCreate a .env file in the project root with your Raindrop.io API credentials:
RAINDROP_CLIENT_ID=your_client_id
RAINDROP_CLIENT_SECRET=your_client_secret
RAINDROP_REDIRECT_URI=http://localhost:3000/callbackNote: You'll need to create a Raindrop.io app in your account settings to get these credentials.
Run the authentication process:
npm run authThis starts a local server, opens the auth URL, and automatically saves your tokens when you authorize the app.
npm run fetchThis project includes a GitHub Actions workflow that automatically fetches bookmarks and commits them to a separate bookmarks-data branch.
By default, the workflow only runs manually. To enable automatic fetching every 6 hours:
- Edit
.github/workflows/fetch-bookmarks.yml - Uncomment the schedule section:
on: schedule: # Run every 6 hours - cron: "0 */6 * * *" workflow_dispatch: # Allow manual triggering
- Commit and push the changes
To enable automated fetching for a single account, configure these secrets in your GitHub repository:
- Go to your repository β Settings β Secrets and variables β Actions
- Add the following Repository Secrets:
| Secret Name | Description | How to Get It |
|---|---|---|
RAINDROP_CLIENT_ID |
Your Raindrop.io app client ID | From Raindrop.io integrations page |
RAINDROP_CLIENT_SECRET |
Your Raindrop.io app client secret | From Raindrop.io integrations page |
RAINDROP_ACCESS_TOKEN |
OAuth access token | Run npm run auth locally, then copy from tokens.json |
RAINDROP_REFRESH_TOKEN |
OAuth refresh token | Run npm run auth locally, then copy from tokens.json |
RAINDROP_EXPIRES_AT |
Token expiration timestamp | Run npm run auth locally, then copy from tokens.json |
- Set up local environment (steps 1-3 above)
- Run authentication:
npm run auth
- Copy values from
tokens.json:{ "accessToken": "copy_this_to_RAINDROP_ACCESS_TOKEN", "refreshToken": "copy_this_to_RAINDROP_REFRESH_TOKEN", "expiresAt": "copy_this_to_RAINDROP_EXPIRES_AT" }
To fetch bookmarks from multiple Raindrop accounts simultaneously:
-
Create a JSON array with your account configurations:
[ { "id": "account1-name", "clientId": "client_id_1", "clientSecret": "client_secret_1", "accessToken": "access_token_1", "refreshToken": "refresh_token_1", "expiresAt": 1234567890 }, { "id": "account2-name", "clientId": "client_id_2", "clientSecret": "client_secret_2", "accessToken": "access_token_2", "refreshToken": "refresh_token_2", "expiresAt": 1234567890 } ] -
Minify the JSON (remove whitespace):
[{"id":"account1-name","clientId":"client_id_1","clientSecret":"client_secret_1","accessToken":"access_token_1","refreshToken":"refresh_token_1","expiresAt":1234567890},{"id":"account2-name","clientId":"client_id_2","clientSecret":"client_secret_2","accessToken":"access_token_2","refreshToken":"refresh_token_2","expiresAt":1234567890}] -
Add to GitHub Secrets as
RAINDROP_ACCOUNTS_JSON
The id field in each account configuration should be a unique identifier (e.g., username, team name). This will be included in each bookmark's JSON to track which account it came from.
For each account, you need to:
- Authenticate using that account's credentials
- Run
npm run authwith the account'sclientIdandclientSecret - Copy the tokens from the generated
tokens.json
mainbranch: Contains the application codebookmarks-databranch: Contains the fetched bookmark JSON files- Data files: Each bookmark stored as
data/{bookmark_id}.json(e.g.,data/123456789.json)
The workflow automatically merges code updates from main into bookmarks-data to keep the execution environment current.
You can manually trigger the workflow from the GitHub Actions tab using the "Run workflow" button.
Each bookmark is saved as an individual JSON file using its ID as the filename:
File: data/123456789.json
Single Account:
{
"_id": 123456789,
"title": "Example Article Title",
"link": "https://example.com/article",
"excerpt": "Brief description of the content...",
"note": "Personal notes about this bookmark",
"type": "article",
"user": { "$id": 12345 },
"cover": "https://example.com/cover.jpg",
"media": [
{
"link": "https://example.com/image.jpg",
"type": "image"
}
],
"tags": ["programming", "javascript"],
"important": false,
"removed": false,
"created": "2024-01-15T10:30:00.000Z",
"lastUpdate": "2024-01-15T10:30:00.000Z",
"domain": "example.com",
"creatorRef": "test",
"sort": 0,
"collectionId": 98765
}Multi-Account (includes accountId field):
{
"_id": 123456789,
"title": "Example Article Title",
"link": "https://example.com/article",
"accountId": "account1-name",
...
}- First run: Fetches all bookmarks
- Subsequent runs: Only fetches bookmarks updated since the last run
- Detection: Uses
lastUpdatetimestamp from the most recent JSON file - Multi-account: Each account tracks its own last update timestamp
π Starting bookmark fetch...
π Environment check:
- RAINDROP_ACCOUNTS_JSON exists: false
- RAINDROP_CLIENT_ID exists: true
- tokens.json exists: true
π Using single account mode
π Fetching links updated since 2024-01-15T10:00:00Z...
β
Successfully fetched 5 updated links
πΎ Saving bookmarks to JSON files...
πΎ Saved 3 bookmarks as individual files
βοΈ Skipped 2 unchanged bookmarks
β
Saved 3 new/updated bookmarks as individual filesπ Starting bookmark fetch...
π Environment check:
- RAINDROP_ACCOUNTS_JSON exists: true
- RAINDROP_CLIENT_ID exists: true
- tokens.json exists: false
π Found 2 account(s) configured
π Processing account: account1-name
π [account1-name] Fetching links updated since 2024-01-15T10:00:00Z...
β
[account1-name] Successfully fetched 10 updated links
πΎ [account1-name] Saved 8 new/updated bookmarks
βοΈ [account1-name] Skipped 2 existing bookmarks
π Processing account: account2-name
π [account2-name] Fetching all links (initial sync)...
β
[account2-name] Successfully fetched 25 links
πΎ [account2-name] Saved 25 new/updated bookmarks
π Multi-account summary:
Total links fetched: 35
Total saved: 33
Total skipped: 2npm testnpm run buildMIT