{"id":15723351,"url":"https://github.com/mzyy94/bluebookmark","last_synced_at":"2025-07-17T06:34:47.323Z","repository":{"id":222508471,"uuid":"756007424","full_name":"mzyy94/bluebookmark","owner":"mzyy94","description":"Private Bookmark Feed for Bluesky on Cloudflare Workers","archived":false,"fork":false,"pushed_at":"2024-05-26T00:53:55.000Z","size":934,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-13T10:48:44.495Z","etag":null,"topics":["bluesky","bluesky-feed","cloudflare-workers","hono","honojs"],"latest_commit_sha":null,"homepage":"https://bsky.app/profile/mzyy94.com/post/3kmpl6l7bv72x","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mzyy94.png","metadata":{"files":{"readme":"README.ja.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-11T18:05:38.000Z","updated_at":"2025-02-21T20:09:24.000Z","dependencies_parsed_at":"2024-10-24T17:46:59.138Z","dependency_job_id":"49c61278-eb24-45ce-91f6-ecc6e4b360ba","html_url":"https://github.com/mzyy94/bluebookmark","commit_stats":null,"previous_names":["mzyy94/bluebookmark"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mzyy94/bluebookmark","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mzyy94%2Fbluebookmark","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mzyy94%2Fbluebookmark/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mzyy94%2Fbluebookmark/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mzyy94%2Fbluebookmark/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mzyy94","download_url":"https://codeload.github.com/mzyy94/bluebookmark/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mzyy94%2Fbluebookmark/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265573582,"owners_count":23790467,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bluesky","bluesky-feed","cloudflare-workers","hono","honojs"],"created_at":"2024-10-03T22:11:17.369Z","updated_at":"2025-07-17T06:34:47.304Z","avatar_url":"https://github.com/mzyy94.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# BlueBookmark\n\n[![Deploy](https://github.com/mzyy94/bluebookmark/actions/workflows/deploy-workers.yml/badge.svg)](https://github.com/mzyy94/bluebookmark/actions/workflows/deploy-workers.yml)\n\n\u003cimg src=\"public/bluebookmark.png\" width=\"128\"\u003e\n\nBlueskyでブックマークを実現するためのフィードです。Cloudflare Workers®︎で動作するサーバーレスアプリケーションで、[hono](https://github.com/honojs/hono) を使った簡潔なコードで実現しています。\n\n## 特徴\n- 他のユーザーに見られないプライベートなブックマーク\n- クラウドエッジで動作する高速な応答性\n- 機密情報を保持しない安全な設計\n- Web Share Target APIを活用したPWA\n\n## 使い方\n\n### iOSユーザー\n\n1. トップページでトークンを取得\n2. iOSショートカットをインストール\n  - 例: https://www.icloud.com/shortcuts/bf64334da98343f79d03bf012e48bf51\n3. 共有メニューからBlueskyのポストをブックマーク\n4. ブックマークフィードを更新\n\n### Androidユーザー\n\n1. トップページでトークンを取得\n2. WebページをPWAとしてホーム画面に追加\n3. 共有メニューからBlueskyのポストをブックマーク\n4. ブックマークフィードを更新\n\n## 制限\n- botやspamなど一部のユーザーは利用できません\n- 一人当たりのブックマーク数の上限があり、上限に達すると追加ができなくなります\n\n## API\n\n### *POST* `/api/register`\n\n|  field  |   name   |  type  |\n|:-------:|:--------:|:------:|\n|  form   |  handle  | string |\n|  form   | password | string |\n\nハンドルネームとアプリパスワードを入れてブックマークを編集するトークンを取得します。\n\n\u003e [!CAUTION]\n\u003e トークンの管理は厳重に行ってください。万が一流出すると、他人にブックマークが追加されたり削除されたりします（このトークンでブックマークの閲覧はできません）。\n\n### *POST* `/api/bookmark`\n\n|  field  |      name     |     type     |\n|:-------:|:-------------:|:------------:|\n| header  | Authorization | Bearer token |\n|  form   |      url      |     URL      |\n\nブックマークを追加します。成功すると201ステータスコードでJSONのレスポンスが返ってきます。\n\n### *DELETE* `/api/bookmark`\n\n|  field  |      name     |     type     |\n|:-------:|:-------------:|:------------:|\n| header  | Authorization | Bearer token |\n|  form   |      url      |     URL      |\n\nブックマークを削除します。成功すると200ステータスコードでJSONのレスポンスが返ってきます。\n\n## 開発\n\n### 必要なもの\n- Blueskyアカウント\n- Cloudflareアカウント\n- GitHubアカウント\n- [wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/)\n- [pnpm](https://pnpm.io)\n\n### プロジェクトの作成\n\nこの Deploy ボタンをクリックしてCloudflare Workersプロジェクトを作成します。\n\n[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/mzyy94/bluebookmark\u0026authed=true\u0026fields={%22name%22:%22Your%20Bluesky%20Handle%20Name%22,%22secret%22:%22FEED_OWNER%22,%22descr%22:%22Handle%20name%20without%20\\%22@\\%22%22}\u0026fields={%22name%22:%22Your%20Bluesky%20App%20Password%22,%22secret%22:%22APP_PASSWORD%22,%22descr%22:%22App%20Password%20using%20for%20publish%20Bookmark%20Feed%20to%20Bluesky%20serever%22}\u0026fields={%22name%22:%22Feed%20hosted%20Domain%22,%22secret%22:%22FEED_HOST%22,%22descr%22:%22Domain%20on%20which%20your%20feed%20will%20be%20running%22}\u0026fields={%22name%22:%22JWT%20secret%22,%22secret%22:%22JWT_SECRET%22,%22descr%22:%22Random%20strings%20to%20protect%20sessions%20(for%20example:%20af3cdpifvdaih8hqg9)%22}\u0026apiTokenTmpl=[{%22key%22:%22d1%22,%22type%22:%22edit%22},{%22key%22:%22page%22,%22type%22:%22edit%22},{%22key%22:%22access%22,%22type%22:%22edit%22},{%22key%22:%22workers_kv_storage%22,%22type%22:%22edit%22},{%22key%22:%22access_acct%22,%22type%22:%22read%22},{%22key%22:%22dns%22,%22type%22:%22edit%22},{%22key%22:%22workers_scripts%22,%22type%22:%22edit%22},{%22key%22:%22account_rulesets%22,%22type%22:%22edit%22}]\u0026apiTokenName=BlueBookmark)\n\n### 初期設定\n\n1. [`wrangler login`](https://developers.cloudflare.com/workers/wrangler/commands/#login)をPCで実行します。\n2. フォークしたリポジトリをcloneしてきます。\n3. D1データベースを次のコマンドで作成します `wrangler d1 create bluebookmark`\nすると以下のような出力が現れます:\n\n```\n⛅️ wrangler 3.28.2\n-------------------------------------------------------\n✅ Successfully created DB 'bluebookmark' in region APAC\nCreated 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\nrestore.\n\n[[d1_databases]]\nbinding = \"DB\" # i.e. available in your Worker on env.DB\ndatabase_name = \"bluebookmark\"\ndatabase_id = \"355b4c9e-a40f-4d4a-9a2d-f474b1d3d727\"\n```\n\n4. `database_id` と書かれた行をコピーして [wrangler.toml](./wrangler.toml) にある database_id の値と置き換えます。\n5. KVネームスペースを次のコマンドで作成します `wrangler kv:namespace create did_key_store`\nすると以下のような出力が現れます:\n\n```\n ⛅️ wrangler 3.28.2\n-------------------------------------------------------\n🌀 Creating namespace with title \"bluebookmark-did_key_store\"\n✨ Success!\nAdd the following to your configuration file in your kv_namespaces array:\n{ binding = \"did_key_store\", id = \"0267def52a42498ebfb9f5de18ad4f84\" }\n```\n\n6. `id` の値をコピーして [wrangler.toml](./wrangler.toml) にある id の値と置き換えます。\n7. 次のコマンドでD1データベースを初期化します `wrangler d1 execute bluebookmark --file=drizzle/0000_productive_riptide.sql`\n8. wranger.toml ファイルの変更を `git commit` でコミットします。\n\n### デプロイ\n\n変更をGitHubにプッシュするとGitHub Actionsによって自動でデプロイされます。ローカルで `pnpm run deploy` を実行することでもできます。\n\n### Environment Secrets\n\n次の変数をGitHub Actionsのsecrets and variablesに設定する必要があります。\n\n変数名         | 説明\n--------------|--\nCF_ACCOUNT_ID | Cloudflare の account ID\nCF_API_TOKEN  | Cloudflare の API token\nFEED_HOST     | feedを実行するドメイン名\nFEED_OWNER    | Blueskyのハンドル名 (\"@\"マーク以降)\nJWT_SECRET    | セッションを保護するためのランダム文字列\nAPP_PASSWORD  | [カスタムフィードの公開](#カスタムフィードの公開)のためのBlueskyのアプリパスワード\n\n### カスタムフィードの公開\n\nカスタムフィードをデプロイできたらBlueskyに公開します。\nGitHub Actionsのワークフローから \"Publish Feed\" を選択し \"Run workflow\" ボタンをクリックします。\nまたは、 FEED_HOST と FEED_OWNER と APP_PASSWORD 環境変数をセットして `node ./scripts/publish-feed.js` を実行します。\n\n### ローカル開発\n\n次のような内容の .dev.vars という名前のファイルをプロジェクトの最上位ディレクトリに作ります。\n\n```ini\nJWT_SECRET=jwt-secret\nFEED_OWNER=mzyy94.com\nFEED_HOST=bluebookmark-feed.example.com\n```\n\n初回のみ `pnpm run init:local` を実行してください。\n`pnpm run dev` でローカルサーバーが立ち上がります。\n\n\n### Cloudflare Pages (Optional)\n\n静的HTMLファイルをCloudflare Pagesから配信したい場合、`wrangler pages project create \u003c任意のプロジェクト名\u003e` を一度実行し `wrangler pages deploy public`で変更をデプロイしてください。\nCIでCloudflare Pagesにデプロイするには、プロジェクト名を `PAGES_PROJECT` environment secretにセットしてください。\n\n## プライバシー\n- アプリパスワードはユーザーの利用可能権限の確認のためのみに用いられ保存されることはありません\n- データベースは暗号化されていないため管理者はデータを平文で見える状態になっています\n- デプロイ可能な実装を公開しているため、プライバシーに懸念のある人は誰でも個人でサービスをセットアップできます\n\n## サービス間認証\n\nこのブックマークフィードではブックマークを非公開とするためにフィードにアクセスしに来るユーザーを厳格に検証しています。\nBlueskyのサーバーで署名されたトークンが付与されたリクエストが正しく検証された場合にのみブックマークが表示される仕組みです。\n今の実装では検証のための認証フォーマットは `secp256k1`のみサポートしています。\n2024年2月現在は、 [bsky.social](https://bsky.social/) は `secp256k1` を認証キー(Multikey)として利用しているため問題は起きないはずですが、これ以外の認証キーを持つユーザーはブックマークフィードが空の表示になることがあります。\n\nサービス間認証について詳しくは、 AT Protocolの [XRPC](https://atproto.com/specs/xrpc) と [Cryptography](https://atproto.com/specs/cryptography) ページを参照してください。\n\n## ダイアグラム\n\n### 登録\n\n```mermaid\nsequenceDiagram\n    autonumber\n    actor User as ユーザー\n    participant Cloudflare as Cloudflare Worker\n    participant Bluesky\n    User-\u003e\u003eCloudflare: /api/register へリクエスト\n    Note left of Cloudflare: ハンドルネームとアプリパスワード\n    Cloudflare-\u003e\u003eBluesky: XRPC createSession\n    Note right of Cloudflare: ハンドルネームとアプリパスワード\n    Bluesky-\u003e\u003eCloudflare: did と accessToken を返却\n    Cloudflare-\u003e\u003eBluesky: XRPC getProfiles\n    Bluesky-\u003e\u003eCloudflare: label と viewer情報 を返却\n    Cloudflare-\u003e\u003eCloudflare: ユーザーを受け入れられるかチェック\n    break もし不適格なユーザーであれば\n        Cloudflare-\u003e\u003eUser: forbidden を返却\n    end\n    Cloudflare-\u003e\u003eCloudflare: 公開鍵をキャッシュ\n    Cloudflare-\u003e\u003eUser: トークン を返却\n    Note left of Cloudflare: トークン\n```\n\n### ブックマーク\n\n#### 追加\n\n```mermaid\nsequenceDiagram\n    autonumber\n    actor User as ユーザー\n    participant Cloudflare as Cloudflare Worker\n    participant Bluesky\n    User-\u003e\u003eCloudflare: /api/bookmark へリクエスト\n    Note left of Cloudflare: 投稿 URL と Bearer トークン\n    Cloudflare-\u003e\u003eCloudflare: トークンの検証\n    break 検証エラー\n        Cloudflare-\u003e\u003eUser: unauthorized を返却\n    end\n    Cloudflare-\u003e\u003eCloudflare: キャッシュから投稿 uri を探す\n    opt 見つからなかったら\n        Cloudflare-\u003e\u003eBluesky: XRPC describeRepo\n        Note right of Cloudflare: 投稿 URL\n        Bluesky-\u003e\u003eCloudflare: 投稿 uri を返却\n    end\n    Cloudflare-\u003e\u003eCloudflare: データベースにブックマークを保存\n    Cloudflare-\u003e\u003eUser: Created を返却\n```\n\n#### Delete\n\n```mermaid\nsequenceDiagram\n    autonumber\n    actor User as ユーザー\n    participant Cloudflare as Cloudflare Worker\n    participant Bluesky\n    User-\u003e\u003eCloudflare: /api/bookmark へリクエスト\n    Note left of Cloudflare: 投稿 URL と Bearer トークン\n    Cloudflare-\u003e\u003eCloudflare: トークンの検証\n    break 検証エラー\n        Cloudflare-\u003e\u003eUser: unauthorized を返却\n    end\n    Cloudflare-\u003e\u003eCloudflare: キャッシュから投稿 uri を探す\n    opt 見つからなかったら\n        Cloudflare-\u003e\u003eBluesky: XRPC describeRepo\n        Note right of Cloudflare: 投稿 URL\n        Bluesky-\u003e\u003eCloudflare: 投稿 uri を返却\n    end\n    Cloudflare-\u003e\u003eCloudflare: データベースからブックマークを削除\n    Cloudflare-\u003e\u003eUser: OK を返却\n```\n\n### カスタムフィード\n\n```mermaid\nsequenceDiagram\n    autonumber\n    actor User as ユーザー\n    participant Cloudflare as Cloudflare Worker\n    participant Bluesky\n    User-\u003e\u003eBluesky: ブックマークカスタムフィードを要求\n    Bluesky-\u003e\u003eCloudflare: ブックマークカスタムフィードを取得\n    Note right of Bluesky: 署名済みトークン\n    Cloudflare-\u003e\u003eCloudflare: キャッシュから 公開鍵 を取り出す\n    Cloudflare-\u003e\u003eCloudflare: サービス間認証トークンの検証\n    opt 検証エラーが出た場合、新しい公開鍵を取得して再検証\n        Cloudflare-\u003e\u003eBluesky: XRPC describeRepo\n        Bluesky-\u003e\u003eCloudflare: 最新の 公開鍵 を返却\n        Cloudflare-\u003e\u003eCloudflare: 再検証\n        Cloudflare-\u003e\u003eCloudflare: 公開鍵 をキャッシュに保存\n    end\n    Cloudflare-\u003e\u003eCloudflare: キャッシュからブックマーク済み投稿を取得\n    opt キャッシュにない場合\n      Cloudflare-\u003e\u003eCloudflare: データベースからブックマーク済み投稿を取得\n    end\n    Cloudflare-\u003e\u003eBluesky: ブックマークした投稿一覧を返却\n    Bluesky-\u003e\u003eUser: ブックマークフィードとして投稿一覧を返却\n```\n\n## License\n\nこのプロジェクトは[MIT](LICENSE)ライセンスで提供されています。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmzyy94%2Fbluebookmark","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmzyy94%2Fbluebookmark","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmzyy94%2Fbluebookmark/lists"}