{
"$type": "com.whtwnd.blog.entry",
"theme": "github-light",
"title": "FeedFeedbackAPIってなんなんだー?",
"content": "~~Blueskyのウワサ(クシュン)[^donald]~~\n[^donald]:ちょうど懐かしい動画を見たところでして……\n\n# はじめに\n本記事は[Bluesky Advent Calendar 2025](https://adventar.org/calendars/12255)の4日目の記事です。\n\n2024年5月ごろに実装されたFeedFeedbackAPIについて、当時書きかけたままサードパーティーへの開放を待っているうちに忘れていた記事を、出すなら今かということで空き枠に滑り込ませてもらいます。(1年以上間が空いているのでつじつま合わない所あったらすみません)\n\n⏪3日目はすいばり(@suibari.com)さんの「[Bluesky AI Bot「全肯定botたん」の2025年の軌跡](https://note.com/suibari/n/nf77edf54a034)」です\n\n⏩5日目は桃色豆腐(@momoiro.me)さんの「[公式pdsから引っ越して、公式pdsに戻ってみるまで試す](https://whtwnd.com/momoiro.me/3m76ep44dzn2j)」です\n\n拙作の[記事投稿についてのPostをまとめたカスタムフィード](https://bsky.app/profile/did:plc:6zpjzzdzet62go7lnaoq4xog/feed/18c2350302f335e)(by [Bluefeed](https://www.bluefeed.app/))もあります\n\n\n# これなに\nBlueskyの公式アプリのバージョン1.81.0[^1.81.0]にて、カスタムフィードに対してのフィードバック\"Show more like this(このような投稿の表示を増やす)🙂\",\"Show less like this(このような投稿の表示を減らす)☹\"が送信できるようになりました。[^feedback]\n[^1.81.0]: https://bsky.app/profile/bsky.app/post/3ks36igfly32h\n[^feedback]:後述しますが他にもフィードバックが送られています。\n\nこれは先日のProduct Roadmapにあった\n[\"New feedback mechanisms which the algorithmic feeds can request, such as “show more” and “show less” buttons, and a way to track which posts have been seen to stop duplicates from showing so often.\"](https://bsky.social/about/blog/05-07-2024-product-roadmap#:~:text=New%20feedback%20mechanisms%20which%20the%20algorithmic%20feeds%20can%20request%2C%20such%20as%20%E2%80%9Cshow%20more%E2%80%9D%20and%20%E2%80%9Cshow%20less%E2%80%9D%20buttons%2C%20and%20a%20way%20to%20track%20which%20posts%20have%20been%20seen%20to%20stop%20duplicates%20from%20showing%20so%20often.)によるもので、フィードバックによって表示される内容を変化させることができるようになるみたいです。\n\nリリース内容にもありますが、この機能は1.81.0時点では[公式のDiscoverフィード](https://bsky.app/profile/did:plc:z72i7hdynmk6r22z27h6tvur/feed/whats-hot)に対してのみ動作し~~ます~~ていました。~~ですが今後は他のカスタムフィードにも開放されるようなので、~~ その後1年の時を経て1.107.0[^1.107.0]、1.109.0[^1.109.0]にてサードパーティーのカスタムフィードに対しても開放されるようになりました。拙作の[RepostNextPost](https://bsky.app/profile/l-tan.dolciss.net/feed/rp-next-post)にも対応しましたが、その際にGitHubのコードに潜って探索した記録を残しておこうと思います。\n[^1.107.0]: https://bsky.app/profile/bsky.app/post/3lxxo3i5pck2c\n[^1.109.0]: https://bsky.app/profile/bsky.app/post/3m47grlweoc2a\n\n# スキーマ実装:[atproto#2383](https://github.com/bluesky-social/atproto/pull/2383)\n- app.bsky.feed.defs#feedViewPost\n\n こちらは[app.bsky.feed.getFeed](https://docs.bsky.app/docs/api/app-bsky-feed-get-feed)で取得されるPostに付加されるもののようです。(PDS→クライアント)\n - feedContextが追加 [^feedViewPost]\n\n ``` json\n \"feedContext\": {\n \"type\": \"string\",\n \"description\": \"Context provided by feed generator that may be passed back alongside interactions.\",\n \"maxLength\": 2000\n }\n ```\n - reqIdが追加[^reqId]\n\n ``` json\n \"reqId\": {\n \"type\": \"string\",\n \"description\": \"Unique identifier per request that may be passed back alongside interactions.\",\n \"maxLength\": 100\n }\n ```\n[^feedViewPost]:https://github.com/bluesky-social/atproto/blob/4184a652225eaf2de5f4d4702562e84c2fd3fde7/lexicons/app/bsky/feed/defs.json#L53-L57\n[^reqId]:https://github.com/bluesky-social/atproto/blob/30267df6f17a25f7caa080136652824b54b69017/lexicons/app/bsky/feed/defs.json#L70-L74\n\n- app.bsky.feed.defs#generatorView\n - acceptsInteractionsが追加 [^generatorView]\n\n こちらは[app.bsky.feed.getFeedGenerator](https://docs.bsky.app/docs/api/app-bsky-feed-get-feed-generator)でフィードジェネレーターの情報を返す際にインタラクションを受け取ることができるかを返すようです。\n ```json\n \"acceptsInteractions\": {\n \"type\": \"boolean\"\n }\n ```\n[^generatorView]:https://github.com/bluesky-social/atproto/blob/4184a652225eaf2de5f4d4702562e84c2fd3fde7/lexicons/app/bsky/feed/defs.json#L150\n\n- app.bsky.feed.defs#skeletonFeedPost\n - feedContextが追加 [^skeltonFeedPost]\n\n こちらは[app.bsky.feed.getFeedSkeleton](https://docs.bsky.app/docs/api/app-bsky-feed-get-feed-skeleton)で返すPostに付加するもののようです。(フィードジェネレーター→AppView→PDS→クライアントのfeedViewPostへ)\n ``` json\n \"feedContext\": {\n \"type\": \"string\",\n \"description\": \"Context that will be passed through to client and may be passed to feed generator back alongside interactions.\",\n \"maxLength\": 2000\n }\n ```\n[^skeltonFeedPost]:https://github.com/bluesky-social/atproto/blob/4184a652225eaf2de5f4d4702562e84c2fd3fde7/lexicons/app/bsky/feed/defs.json#L171-L175\n\n- app.bsky.feed.defs#interaction が追加[^interaction]\n\n フィードジェネレーター側へ送られるフィードバック(interaction)と、getFeedSkeletonで渡したfeedContext、reqIdが定義されています。\n ``` json\n \"interaction\": {\n \"type\": \"object\",\n \"properties\": {\n \"item\": { \"type\": \"string\", \"format\": \"at-uri\" },\n \"event\": {\n \"type\": \"string\",\n \"knownValues\": [\n \"app.bsky.feed.defs#requestLess\",\n \"app.bsky.feed.defs#requestMore\",\n \"app.bsky.feed.defs#clickthroughItem\",\n \"app.bsky.feed.defs#clickthroughAuthor\",\n \"app.bsky.feed.defs#clickthroughReposter\",\n \"app.bsky.feed.defs#clickthroughEmbed\",\n \"app.bsky.feed.defs#interactionSeen\",\n \"app.bsky.feed.defs#interactionLike\",\n \"app.bsky.feed.defs#interactionRepost\",\n \"app.bsky.feed.defs#interactionReply\",\n \"app.bsky.feed.defs#interactionQuote\",\n \"app.bsky.feed.defs#interactionShare\"\n ]\n },\n \"feedContext\": {\n \"type\": \"string\",\n \"description\": \"Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton.\",\n \"maxLength\": 2000\n }\n \"reqId\": {\n \"type\": \"string\",\n \"description\": \"Unique identifier per request that may be passed back alongside interactions.\",\n \"maxLength\": 100\n }\n }\n },\n ```\n[^interaction]:https://github.com/bluesky-social/atproto/blob/30267df6f17a25f7caa080136652824b54b69017/lexicons/app/bsky/feed/defs.json#L233-L265\n\n- 以下、interactionのknownValuesたち \n - app.bsky.feed.defs#requestLess が追加 [^requestLess]\n\n Postのメニューから\"Show less like this(このような投稿の表示を減らす)☹\"を選択された場合に送信されます。\n ``` json\n \"requestLess\": {\n \"type\": \"token\",\n \"description\": \"Request that less content like the given feed item be shown in the feed\"\n },\n ```\n [^requestLess]:https://github.com/bluesky-social/atproto/blob/4184a652225eaf2de5f4d4702562e84c2fd3fde7/lexicons/app/bsky/feed/defs.json#L225-L228\n\n - app.bsky.feed.defs#requestMore が追加 [^requestMore]\n \n Postのメニューから\"Show more like this(このような投稿の表示を増やす)🙂\"を選択された場合に送信されます。\n ``` json\n \"requestMore\": {\n \"type\": \"token\",\n \"description\": \"Request that more content like the given feed item be shown in the feed\"\n },\n ```\n [^requestMore]:https://github.com/bluesky-social/atproto/blob/4184a652225eaf2de5f4d4702562e84c2fd3fde7/lexicons/app/bsky/feed/defs.json#L229-L232\n\n - app.bsky.feed.defs#clickthroughItem が追加 [^clickthroughItem]\n\n Postをクリック(タップ)してPostの画面を開いた場合に送信されます。\n ``` json\n \"clickthroughItem\": {\n \"type\": \"token\",\n \"description\": \"User clicked through to the feed item\"\n },\n ```\n [^clickthroughItem]:https://github.com/bluesky-social/atproto/blob/4184a652225eaf2de5f4d4702562e84c2fd3fde7/lexicons/app/bsky/feed/defs.json#L233-L236\n\n - app.bsky.feed.defs#clickthroughAuthor が追加 [^clickthroughAuthor]\n\n Postのアバターや名前をクリックしてプロフィール画面を開いた場合に送信されます。\n ``` json\n \"clickthroughAuthor\": {\n \"type\": \"token\",\n \"description\": \"User clicked through to the author of the feed item\"\n },\n ```\n [^clickthroughAuthor]:https://github.com/bluesky-social/atproto/blob/4184a652225eaf2de5f4d4702562e84c2fd3fde7/lexicons/app/bsky/feed/defs.json#L237-L240\n\n - app.bsky.feed.defs#clickthroughReposter が追加 [^clickthroughReposter]\n\n Postの「~~がリポスト」をクリックしてリポスト者を開いた場合に送信されます。\n ``` json\n \"clickthroughReposter\": {\n \"type\": \"token\",\n \"description\": \"User clicked through to the reposter of the feed item\"\n },\n ```\n [^clickthroughReposter]:https://github.com/bluesky-social/atproto/blob/4184a652225eaf2de5f4d4702562e84c2fd3fde7/lexicons/app/bsky/feed/defs.json#L241-L244\n\n - app.bsky.feed.defs#clickthroughEmbed が追加 [^clickthroughEmbed]\n\n Postのリンクカードや引用など埋め込みをクリックして開いた場合に送信されます。\n ``` json\n \"clickthroughEmbed\": {\n \"type\": \"token\",\n \"description\": \"User clicked through to the embedded content of the feed item\"\n },\n ```\n [^clickthroughEmbed]:https://github.com/bluesky-social/atproto/blob/4184a652225eaf2de5f4d4702562e84c2fd3fde7/lexicons/app/bsky/feed/defs.json#L245-L248\n\n - app.bsky.feed.defs#interactionSeen が追加 [^interactionSeen]\n\n Postを一定時間表示させた場合に送信されます。\n ``` json\n \"interactionSeen\": {\n \"type\": \"token\",\n \"description\": \"Feed item was seen by user\"\n },\n ```\n [^interactionSeen]:https://github.com/bluesky-social/atproto/blob/4184a652225eaf2de5f4d4702562e84c2fd3fde7/lexicons/app/bsky/feed/defs.json#L249-L252\n\n - app.bsky.feed.defs#interactionLike が追加 [^interactionLike]\n\n Postにいいねした場合に送信されます。\n ``` json\n \"interactionLike\": {\n \"type\": \"token\",\n \"description\": \"User liked the feed item\"\n },\n ```\n [^interactionLike]:https://github.com/bluesky-social/atproto/blob/4184a652225eaf2de5f4d4702562e84c2fd3fde7/lexicons/app/bsky/feed/defs.json#L253-256\n\n - app.bsky.feed.defs#interactionRepost が追加 [^interactionRepost]\n\n PostをRepostした場合に送信されます。\n ``` json\n \"interactionRepost\": {\n \"type\": \"token\",\n \"description\": \"User reposted the feed item\"\n },\n ```\n [^interactionRepost]:https://github.com/bluesky-social/atproto/blob/4184a652225eaf2de5f4d4702562e84c2fd3fde7/lexicons/app/bsky/feed/defs.json#L257-260\n\n - app.bsky.feed.defs#interactionReply が追加 [^interactionReply]\n\n Postに返信しようとした場合に送信されます。\n ``` json\n \"interactionReply\": {\n \"type\": \"token\",\n \"description\": \"User replied to the feed item\"\n },\n ```\n [^interactionReply]:https://github.com/bluesky-social/atproto/blob/4184a652225eaf2de5f4d4702562e84c2fd3fde7/lexicons/app/bsky/feed/defs.json#L261-L264\n\n - app.bsky.feed.defs#interactionQuote が追加 [^interactionQuote]\n\n Postを引用しようとした場合に送信されます。\n ``` json\n \"interactionQuote\": {\n \"type\": \"token\",\n \"description\": \"User quoted the feed item\"\n },\n ```\n [^interactionQuote]:https://github.com/bluesky-social/atproto/blob/4184a652225eaf2de5f4d4702562e84c2fd3fde7/lexicons/app/bsky/feed/defs.json#L265-L268\n\n - app.bsky.feed.defs#interactionShare が追加 [^interactionShare]\n\n Postを共有しようとした場合に送信されます。\n ``` json\n \"interactionShare\": {\n \"type\": \"token\",\n \"description\": \"User shared the feed item\"\n }\n ```\n [^interactionShare]:https://github.com/bluesky-social/atproto/blob/4184a652225eaf2de5f4d4702562e84c2fd3fde7/lexicons/app/bsky/feed/defs.json#L269-L272\n\n## PDS/Appview実装:[atproto#2402](https://github.com/bluesky-social/atproto/pull/2402)\nアプリから送信されたInteractionやフィードジェネレーターからのfeedContextをPDSを通してAppViewと送受信する実装です。\n\n# 公式アプリ実装:[social-app#3498](https://github.com/bluesky-social/social-app/pull/3498)\nスキーマ側で実装されたInteractionがどのように送信されるかを見ていきます。\n\n- [packages.json](https://github.com/bluesky-social/social-app/pull/3498/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519)\n - [lodash/throttle](https://lodash.com/docs/4.17.15#throttle)が追加されており、後ほど出てきますが一定間隔でInteractionの送信を制御するのに使用されています。\n- [src/state/feed-feedback.tsx](https://github.com/bluesky-social/social-app/blob/4fad18b2fa3c12ffdf1d49afac5228f7df658bc2/src/state/feed-feedback.tsx)\n - FeedFeedbackの実体です。\n\n## fixes(1.82.0で実装):[social-app#3968](https://github.com/bluesky-social/social-app/pull/3968)\n1.81.0リリース後に行われている修正(改善)です。\n- interactionSeenの判定する時間を1.5秒に変更\n- リプライ元のPostについてもFeedContextを返すように修正(ちゃんと動き読めてないかも)\n\n## Include feedContext in DOM as data-(1.84.0で実装) :[social-app#4206](https://github.com/bluesky-social/social-app/pull/4206)\n- skeletonFeedPostのfeedContextが各Postのdivタグに`data-feed-context`として出力されるように変更\n\n## Enable show less / more buttons for third party feeds(1.107.0で実装) [social-app#8672](https://github.com/bluesky-social/social-app/pull/8672)\n- サードパーティーのカスタムフィードにもrequestLessとrequestMoreを送信するよう変更\n\n## Send inferrable interactions to third-party feeds(1.109.0で実装) [social-app#9094](https://github.com/bluesky-social/social-app/pull/9094)\n- interactionLike、interactionQuote、interactionReply、interactionRepost、interactionSeenも送信するよう変更\n- 以前Kyoto mini meetupでdan氏にQAでサードパーティーへの開放について[聞いてもらった際](https://bsky.app/profile/did:plc:6zpjzzdzet62go7lnaoq4xog/post/3l4odpikw272a)、PRにも記述があるように、interactionSeenは\"privacy-sensitive\"という回答をもらっていました。ですが、ページネーションで次のページを取得する=それまでの投稿はスクロールで閲覧されているということになるため、既読の情報を送る用にした方がメリットも大きく対応されることとなりました。\n\n# 結局対応するにはどうすればいいの\n自前のカスタムフィードで[対応したPR](https://github.com/dolciss/rp-next-post/pull/6)が多少参考にはなると思いますが主に2点変更が必要です。\n\n## app.bsky.feed.generatorレコードの変更\n- カスタムフィード登録時のapp.bsky.feed.generatorレコードにacceptsInteractionsを追加します。\n```json\n{\n ...\n \"$type\": \"app.bsky.feed.generator\",\n ...\n \"acceptsInteractions\": true\n}\n```\n\n## app.bsky.feed.sendInteractionsのXRPC対応\n- example.com/xrpc/app.bsky.feed.sendInteractionsに上記のInteractionが送信されてきますので、それに対して処理を行います。\n```json\n{\n \"interactions\": [\n {\n \"$type\": \"app.bsky.feed.defs#interaction\",\n \"item\": \"at://did:plc:xxxxxxxxxxxxxxxxxxxxxxxx/app.bsky.feed.post/xxxxxxxxxxxxx\",\n \"event\": \"app.bsky.feed.defs#xxxxxxxxxxx\",\n \"feedContext\": \"context\",\n \"reqId\": \"\"\n },\n ...\n ]\n```\n\n## feedContextの対応\n- sendInteractionsの中身にコンテキストを転送したい場合はapp.bsky.feed.getFeedSkeletonにfeedContextを追加します。(RepostNextPostでは対応していません)\n``` json\n{\n \"feed\": [\n \"post\": \"at://did:plc:xxxxxxxxxxxxxxxxxxxxxxxx/app.bsky.feed.post/xxxxxxxxxxxxx\",\n \"feedContext\": \"context\"\n ],\n \"cursor\": \"xxxxxxxxxxx\"\n}\n```\n\n# さいごに\nRepostNextPostではrequestLessで投稿を非表示、requestMoreで最後に非表示にした投稿を再表示という処理を入れましたが、\n初のDiscoverフィードのように表示内容をいろいろカスタムに活用できるかと思いますので参考になれば幸いです。\nそれではみなさまよいBlueskyライフを!\n\n(再掲)\n⏩5日目は桃色豆腐(@momoiro.me)さんの「[公式pdsから引っ越して、公式pdsに戻ってみるまで試す](https://whtwnd.com/momoiro.me/3m76ep44dzn2j)」です",
"createdAt": "2025-12-08T07:25:36.392Z",
"visibility": "public"
}