Private Bookmark Feed for Bluesky on Cloudflare Workers
Bookmark feed for Bluesky, a serverless application running on Cloudflare Workers®︎, using hono and written in simple code.
/api/register
field | name | type |
---|---|---|
form | handle | string |
form | password | string |
Enter the Handle Name and App Password to get a token to edit bookmarks.
[!CAUTION] Please manage your tokens carefully. If it is leaked, bookmarks will be added or deleted freely (viewing is not allowed with this token).
/api/bookmark
field | name | type |
---|---|---|
header | Authorization | Bearer token |
form | url | URL |
Add a bookmark. On success, a JSON response is returned with a status code of 201.
/api/bookmark
field | name | type |
---|---|---|
header | Authorization | Bearer token |
form | url | URL |
Delete a bookmark. On success, a JSON response is returned with a status code of 200.
Click Deploy button to create Cloudflare Workers project.
wrangler login
on your PC.wrangler d1 create bluebookmark
And you can see output like below:⛅️ wrangler 3.28.2
-------------------------------------------------------
✅ Successfully created DB 'bluebookmark' in region APAC
Created your database using D1's new storage backend. The new storage backend is not yet recommended for production workloads, but backs up your data via point-in-time
restore.
[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "bluebookmark"
database_id = "355b4c9e-a40f-4d4a-9a2d-f474b1d3d727"
database_id
in output and replace database_id in wrangler.tomlwrangler kv:namespace create did_key_store
And you can see output like below: ⛅️ wrangler 3.28.2
-------------------------------------------------------
🌀 Creating namespace with title "bluebookmark-did_key_store"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
{ binding = "did_key_store", id = "0267def52a42498ebfb9f5de18ad4f84" }
id
in output and replace id in wrangler.tomlwrangler d1 execute bluebookmark --file=drizzle/0000_productive_riptide.sql
git commit
Push your changes to GitHub and it will be automatically deployed by GitHub Actions. You can also run pnpm run deploy
locally.
The following variables are required for deployment via GitHub Actions.
Variable Name | Description |
---|---|
CF_ACCOUNT_ID | Cloudflare account ID |
CF_API_TOKEN | Cloudflare API token |
FEED_HOST | Domain on which your feed will be running |
FEED_OWNER | Bluesky handle name without "@" |
JWT_SECRET | Random strings to protect sessions |
APP_PASSWORD | Bluesky app password for Publish Feed use |
After you have deployed the custom feed, publish the feed to Bluesky.
Select the "Publish Feed" workflow from GitHub Actions and click "Run workflow".
Or, run node ./scripts/publish-feed.js
with FEED_HOST, FEED_OWNER and APP_PASSWORD enviroment variables.
Create .dev.vars file in root directory of this project.
JWT_SECRET=jwt-secret
FEED_OWNER=mzyy94.com
FEED_HOST=bluebookmark-feed.example.com
Run pnpm run init:local
only for the first time.
Start up local server with pnpm run dev
.
If you want to serve static HTMLs from Cloudflare Pages, run wrangler pages project create <YOUR-UNIQUE-PROJECT-NAME>
once and wrangler pages deploy public
for each change.
To deploy to Cloudflare Pages on CI, set the project name to PAGES_PROJECT
environment secret.
This bookmark feed strictly verifies access to the feed to prevent other users from seeing the bookmarks you have privately saved.
The feed content is returned only when a request with a signed token from Bluesky server is successfully verified.
At the moment, the only authentication format supported for verification is secp256k1
.
As of February 2024, bsky.social uses secp256k1
as Multikey, so it should not be a problem, but it is expected that authentication may fail and the feed may not be displayed.
For more information about authentication, please refer to AT Protocol's XRPC and Cryptography page.
sequenceDiagram
autonumber
actor User
participant Cloudflare as Cloudflare Worker
participant Bluesky
User->>Cloudflare: request /api/register
Note left of Cloudflare: Handle Name and App Password
Cloudflare->>Bluesky: XRPC createSession
Note right of Cloudflare: Handle Name and App Password
Bluesky->>Cloudflare: respond did and accessToken
Cloudflare->>Bluesky: XRPC getProfiles
Bluesky->>Cloudflare: respond user labels and viewer status
Cloudflare->>Cloudflare: check for acceptance
break if user is an unacceptable user
Cloudflare->>User: respond forbidden
end
Cloudflare->>Cloudflare: cache public key
Cloudflare->>User: respond token
Note left of Cloudflare: Token
sequenceDiagram
autonumber
actor User
participant Cloudflare as Cloudflare Worker
participant Bluesky
User->>Cloudflare: request /api/bookmark
Note left of Cloudflare: Post URL and Bearer token
Cloudflare->>Cloudflare: verify token
break verification failed
Cloudflare->>User: respond unauthorized
end
Cloudflare->>Cloudflare: search Post uri from cache
opt if not found
Cloudflare->>Bluesky: XRPC describeRepo
Note right of Cloudflare: Post URL
Bluesky->>Cloudflare: respond Post uri
end
Cloudflare->>Cloudflare: save bookmark to DB
Cloudflare->>User: respond Created
sequenceDiagram
autonumber
actor User
participant Cloudflare as Cloudflare Worker
participant Bluesky
User->>Cloudflare: request /api/bookmark
Note left of Cloudflare: Post URL and Bearer token
Cloudflare->>Cloudflare: verify token
break verification failed
Cloudflare->>User: respond unauthorized
end
Cloudflare->>Cloudflare: search Post uri from cache
opt if not found
Cloudflare->>Bluesky: XRPC describeRepo
Note right of Cloudflare: Post URL
Bluesky->>Cloudflare: respond Post uri
end
Cloudflare->>Cloudflare: delete bookmark
Cloudflare->>User: respond OK
sequenceDiagram
autonumber
actor User
participant Cloudflare as Cloudflare Worker
participant Bluesky
User->>Bluesky: request bookmark custom feed
Bluesky->>Cloudflare: get bookmark custom feed
Note right of Bluesky: Signed token
Cloudflare->>Cloudflare: get public key from cache
Cloudflare->>Cloudflare: verify token
opt if verification failed, refresh public key
Cloudflare->>Bluesky: XRPC describeRepo
Bluesky->>Cloudflare: respond new public key
Cloudflare->>Cloudflare: re-verify user
Cloudflare->>Cloudflare: cache public key
end
Cloudflare->>Cloudflare: get posts from cache
opt if not in cache
Cloudflare->>Cloudflare: search posts from DB
end
Cloudflare->>Bluesky: respond list of bookmarked posts
Bluesky->>User: respond feed of bookmark
Licensed under MIT
a collection of lightweight TypeScript packages for AT Protocol, the protocol powering Bluesky.
Use this repository to get started with your own Bluesky Labeler.
ATProto Feed Generator Starter Kit
AT Protocol Reference Implementation (TypeScript)
A dead simple client for subscribing to an ATProto Relay ("firehose").
A fully typed client for the Bluesky Jetstream (https://github.com/bluesky-social/jetstream) service.
Your Brand Here!
50K+ engaged viewers every month
Limited spots available!
📧 Contact us via email🦋 Contact us on Bluesky