{
"$type": "com.whtwnd.blog.entry",
"theme": "github-light",
"title": "atprotoモデレーション概論 bsky編",
"content": "## atprotoモデレーション概論 bsky編\n\n[atprotoモデレーション概論 目次](https://whtwnd.com/did:plc:qi6xg6zplzivyu7zrylxuugk/3lb4xttmcxw2m)\n\nbsky lexiconにおけるlabelやlabelerの扱いについて説明する。\n\n内容は大体[公式ドキュメント](https://docs.bsky.app/docs/advanced-guides/moderation)に書いてある。\n\nここで定義されている概念の幾つかは、将来的にはatproto全体で使われるようになる[かもしれない](https://github.com/bluesky-social/atproto/issues/2367#issuecomment-2140493061)。\n\n実装固有の話題はBluesky Social編にて。具体的にはOzoneとかautomodとか。\n\n### takedownの反映\n\n対象が存在しなかった(DB上に存在しない)かのように扱う。APIによって400になったり404になったり省略されたり様々だが、特段語るべきところは無い。\n\n### labelの分類\n\nbsky固有の概念として、global labelとcustom labelという分類がある。\n\n#### global label\n\nglobal labelはlexiconレベルで意味が定められている特殊なlabelのこと。内訳として2種類ある。\n\n* 普通のlabelには無い効果を持つlabel値[^redactlabel]\n * `!hide`: 強制的に非表示にする (self label不可)\n * `!warn`: 強制的に警告を表示する (self label不可)\n * `!no-unauthenticated`: 非ログインユーザには非表示にすることをappviewに要求する\n* 共通でself labelに使えるlabel値\n * `porn`, `sexual`, `graphic-media`, `nudity`\n\n[^redactlabel]: `!takedown`と`!suspend`もここに含むと考えてもよいかもしれない。ここではbsky側の公式ドキュメントに倣い、global labelから外した。公式で出てこないのは、atproto層で定義されたものであるためか、クライアントと無関係なためだと推測している。\n\n後者は少し分かりにくいと思うので補足しておく。label編で説明したように、通常はlabel値の意味は発行者毎に異なり、self labelの発行者はrecordの持ち主である。つまり、同じlabel値であっても、self labelの意味はアカウント毎に異なる。そして実のところ、bskyにおいてself labelの意味を定義する方法は無い。詳細は後述する。\\\n一方で、性的なコンテンツのゾーニングは法律やプラットフォームからの要請があるわけで、一律で対応できなければいけない。というわけで、仕様レベルで意味を定義したlabel値を用意して、簡便かつ確実に対処できるようにしている。\n\n#### custom label\n\ncustom labelは発行者によって意味が定義されたlabelのこと。具体的な定義の仕方は後述する。\n\n`!`が付かないglobal labelは、同じlabel値を独自定義で上書きして、custom labelとして使うこともできる。\n\n大体のlabelはcustom labelに分類されるが、公式クライアントから自分で設定できるのはglobal labelだと思っておけば大体合ってる。\n\n### 意味の定義\n\nlabelの意味は、[`com.atproto.label.defs#labelValueDefinition`](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/com/atproto/label/defs.json#L78)で定義する。これはlabel値(`identifier`)に対して以下を指定する。\n\n* `severity`: クライアント上のメッセージ要否\n * `alert`: 危険なコンテンツとして警告を表示する\n * `inform`: 中立的な情報としてメッセージを表示する\n * `none`: メッセージを表示しない\n* `blurs`: 隠す対象\n * `content`: 投稿本文やリスト等、record情報そのものを隠す\n * `media`: アイコンや動画等、recordに付随するメディアを隠す\n * `none`: 何も隠さない\n* `defaultSetting`: labelの対応(クライアントで上書き可能)\n * `hide`: 投稿やアカウントそのものを非表示にする\n * `warn`: `blurs`の対象を隠して警告を出す\n * `ignore`: 何もしない\n* `adultOnly`: 未成年者の閲覧可否\n * `true`: 未成年者は強制的に`hide`になる\n * `false`: 年齢による区別をしない\n* `locales`: 言語毎の表示名と詳細説明\n\n後述するように、この定義はrecordとして示される。\n\n### labelの反映\n\nlabelはappviewが取得し、APIレスポンスでそれぞれの`labels`フィールドにラベル値が埋め込まれる。この時、署名は省略される。対象毎のlabel位置は以下。\n\n* `post`\n * labeler: [`postView/labels`](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/app/bsky/feed/defs.json#L32)\n * self: [`postView/record/labels`](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/app/bsky/feed/defs.json#L15)(元recordが丸ごと入る)\n* `profile`\n * labeler&self: [`profileView/labels`](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/app/bsky/actor/defs.json#L53), [`profileViewBasic/labels`](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/app/bsky/actor/defs.json#L22), [`profileViewDetailed/labels`](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/app/bsky/actor/defs.json#L91)\n* `generator`\n * labeler&self: [`generatorView/labels`](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/app/bsky/actor/defs.json#L91)\n* `service`\n * labeler&self: [`labelerView/labels`](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/app/bsky/labeler/defs.json#L15), [`labelerViewDetailed/labels`](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/app/bsky/labeler/defs.json#L21)\n* `list`\n * labeler&self: [`listView/labels`](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/app/bsky/graph/defs.json#L43), [`listViewBasic/labels`](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/app/bsky/graph/defs.json#L15)\n* `starterpack`\n * labeler: [`starterPackView/labels`](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/app/bsky/graph/defs.json#L59), [`starterPackViewBasic/labels`](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/app/bsky/graph/defs.json#L104)\n* 汎用\n * 通知対象: [`notification/labels`](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/app/bsky/notification/listNotifications.json#L71)\n * 引用(embed)対象: [`viewRecord/labels`](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/lexicons/app/bsky/embed/record.json#L46)\n* アカウント\n * labeler: 未確認([上記全部?](https://docs.bsky.app/docs/advanced-guides/moderation#label-targets:~:text=On%20an%20account:%20has%20account%2Dwide%20effects))\n\n`profile`のlabelとアカウントのlabelが別物である点に注意。前者はあくまでアイコンやプロフィール文など`profile` recordで定義している範囲に対するlabelであり、アカウントそのものへのlabelではない。\n\nappviewでは`!`の付かないlabelはAPIレスポンスにlabelを含めるのみで、label毎の処理はクライアント側で行う。\\\n定義毎の取り扱いは[公式SDK](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/packages/api/docs/moderation.md)に頼るのが楽。global labelも[ここで定義されている](https://github.com/bluesky-social/atproto/blob/bac9be2d3ec904d1f984a871f43cf89aca17289d/packages/api/src/moderation/const/labels.ts)。ちゃんと文書化してほしい。\n\n### labeler\n\nlabeler→appviewは標準メソッドで伝達され、lexicon固有の方法は無い。\n\nどちらのAPIをどう使うかは規定されていないが、リアルタイムでなくても取得(backfill)できることが[重要だとされている](https://github.com/bluesky-social/atproto/discussions/2885#discussioncomment-10999945)。\n\nlabelerの発見およびlabel定義公開のため、`service` recordが用意されている。これを持つアカウントのDIDはlabeler自身と同じものを使う[^labelerdid]。このアカウントは基本的にlabeler専用として、普段使いしないことが[推奨されている](https://github.com/bluesky-social/ozone/blob/666ffebc238c278010b9bb34be2a7a1c7ec3b389/README.md#running-your-own-ozone-labeler-service)[^labeleraccount]。\n\n[^labelerdid]: 比較対象として、feed generator(`generator` record)の場合はBlueskyアカウントとサーバのDIDが異なっていてもよい。feed generatorは必ずしも作者がサーバを所有しているわけではないというのは、SkyFeed等を考えれば分かるだろう。\n\n[^labeleraccount]: モデレータ保護のためや引継ぎを容易にするため等の理由があった気がするが、ソースは忘れた。[labeler管理サービスへのログインにも専用アカウントを推奨する](https://github.com/bluesky-social/ozone/blob/666ffebc238c278010b9bb34be2a7a1c7ec3b389/docs/userguide.md#moderator-privacy-and-safety)くらいなので、大きく間違ってはいないはず。\n\n`service`には前述のlabel定義が並ぶ。labelerはここで定義したlabelとglobal labelのみを発行することが期待されるが、厳密な仕様は未だ無い。\n\n### self label\n\n既に大体説明してしまったが、各recordの`labels`フィールドにlabel値を含める。\n\nself labelを定義する仕組みは存在していないため、self labelにはcustom labelは使えない。具体的には、使えるlabel値は`!no-unauthenticated`(profileのみ), `porn`, `sexual`, `nudity`, `graphic-media`の5つのみ。\n\nとはいえappview実装上、APIではその他のself labelも見せるため、クライアントの独自解釈で取り扱うこともある。\n\n### Next\n\n[Bluesky編](https://whtwnd.com/did:plc:qi6xg6zplzivyu7zrylxuugk/3lb4y7djeem2m)",
"createdAt": "2024-11-18T01:52:31.862Z",
"visibility": "public"
}