···11+{
22+ "lexicon": 1,
33+ "id": "com.atproto.label.defs",
44+ "defs": {
55+ "label": {
66+ "type": "object",
77+ "description": "Metadata tag on an atproto resource (eg, repo or record).",
88+ "required": ["src", "uri", "val", "cts"],
99+ "properties": {
1010+ "ver": {
1111+ "type": "integer",
1212+ "description": "The AT Protocol version of the label object."
1313+ },
1414+ "src": {
1515+ "type": "string",
1616+ "format": "did",
1717+ "description": "DID of the actor who created this label."
1818+ },
1919+ "uri": {
2020+ "type": "string",
2121+ "format": "uri",
2222+ "description": "AT URI of the record, repository (account), or other resource that this label applies to."
2323+ },
2424+ "cid": {
2525+ "type": "string",
2626+ "format": "cid",
2727+ "description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to."
2828+ },
2929+ "val": {
3030+ "type": "string",
3131+ "maxLength": 128,
3232+ "description": "The short string name of the value or type of this label."
3333+ },
3434+ "neg": {
3535+ "type": "boolean",
3636+ "description": "If true, this is a negation label, overwriting a previous label."
3737+ },
3838+ "cts": {
3939+ "type": "string",
4040+ "format": "datetime",
4141+ "description": "Timestamp when this label was created."
4242+ },
4343+ "exp": {
4444+ "type": "string",
4545+ "format": "datetime",
4646+ "description": "Timestamp at which this label expires (no longer applies)."
4747+ },
4848+ "sig": {
4949+ "type": "bytes",
5050+ "description": "Signature of dag-cbor encoded label."
5151+ }
5252+ }
5353+ },
5454+ "selfLabels": {
5555+ "type": "object",
5656+ "description": "Metadata tags on an atproto record, published by the author within the record.",
5757+ "required": ["values"],
5858+ "properties": {
5959+ "values": {
6060+ "type": "array",
6161+ "items": { "type": "ref", "ref": "#selfLabel" },
6262+ "maxLength": 10
6363+ }
6464+ }
6565+ },
6666+ "selfLabel": {
6767+ "type": "object",
6868+ "description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.",
6969+ "required": ["val"],
7070+ "properties": {
7171+ "val": {
7272+ "type": "string",
7373+ "maxLength": 128,
7474+ "description": "The short string name of the value or type of this label."
7575+ }
7676+ }
7777+ },
7878+ "labelValueDefinition": {
7979+ "type": "object",
8080+ "description": "Declares a label value and its expected interpretations and behaviors.",
8181+ "required": ["identifier", "severity", "blurs", "locales"],
8282+ "properties": {
8383+ "identifier": {
8484+ "type": "string",
8585+ "description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).",
8686+ "maxLength": 100,
8787+ "maxGraphemes": 100
8888+ },
8989+ "severity": {
9090+ "type": "string",
9191+ "description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.",
9292+ "knownValues": ["inform", "alert", "none"]
9393+ },
9494+ "blurs": {
9595+ "type": "string",
9696+ "description": "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.",
9797+ "knownValues": ["content", "media", "none"]
9898+ },
9999+ "defaultSetting": {
100100+ "type": "string",
101101+ "description": "The default setting for this label.",
102102+ "knownValues": ["ignore", "warn", "hide"],
103103+ "default": "warn"
104104+ },
105105+ "adultOnly": {
106106+ "type": "boolean",
107107+ "description": "Does the user need to have adult content enabled in order to configure this label?"
108108+ },
109109+ "locales": {
110110+ "type": "array",
111111+ "items": { "type": "ref", "ref": "#labelValueDefinitionStrings" }
112112+ }
113113+ }
114114+ },
115115+ "labelValueDefinitionStrings": {
116116+ "type": "object",
117117+ "description": "Strings which describe the label in the UI, localized into a specific language.",
118118+ "required": ["lang", "name", "description"],
119119+ "properties": {
120120+ "lang": {
121121+ "type": "string",
122122+ "description": "The code of the language these strings are written in.",
123123+ "format": "language"
124124+ },
125125+ "name": {
126126+ "type": "string",
127127+ "description": "A short human-readable name for the label.",
128128+ "maxGraphemes": 64,
129129+ "maxLength": 640
130130+ },
131131+ "description": {
132132+ "type": "string",
133133+ "description": "A longer description of what the label means and why it might be applied.",
134134+ "maxGraphemes": 10000,
135135+ "maxLength": 100000
136136+ }
137137+ }
138138+ },
139139+ "labelValue": {
140140+ "type": "string",
141141+ "knownValues": [
142142+ "!hide",
143143+ "!warn",
144144+ "!no-unauthenticated",
145145+ "porn",
146146+ "sexual",
147147+ "nudity",
148148+ "graphic-media",
149149+ "bot"
150150+ ]
151151+ }
152152+ }
153153+}
···11+{"uri":"at://did:plc:kcgwlowulc3rac43lregdawo/app.bsky.feed.post/3mgibe2arpk2c","cid":"bafyreihnoygrt3jdpt2axdeakhjsbfquktmrehnna6gbt63io6izmvg4pi","value":{"text":"I think my migration to @eurosky.social went well but it's so smooth I'm not sure I did it right 🔍","$type":"app.bsky.feed.post","langs":["en"],"facets":[{"$type":"app.bsky.richtext.facet","index":{"byteEnd":39,"byteStart":24},"features":[{"did":"did:plc:ooensn4mr5mhznzypvxelfa3","$type":"app.bsky.richtext.facet#mention"}]}],"createdAt":"2026-03-07T16:40:32.271Z"}}
+1
test/fixtures/bsky/real/profile.json
···11+{"uri":"at://did:plc:kcgwlowulc3rac43lregdawo/app.bsky.actor.profile/self","cid":"bafyreigr55oyjy2a7kxdnoohbsak4n2ztlvnflyo2g4oqtjqatp3zwiidu","value":{"$type":"app.bsky.actor.profile","avatar":{"ref":{"$link":"bafkreig5onh2hofb4xr7voz4b3hwxigu6zqhr5sd6svjnnksaid4a2fmje"},"size":257963,"$type":"blob","mimeType":"image/jpeg"},"banner":{"ref":{"$link":"bafkreifbfgrbbwtaopcjz7fnmkfujpdjuhg32moyt7cy6irsvkjsx22pty"},"size":281269,"$type":"blob","mimeType":"image/jpeg"},"website":"https://karitham.dev","pronouns":"they/them","pinnedPost":{"cid":"bafyreicxeldxbe56fg7kpkshqmoehpqcqqr5wgltxjymqn7wftxjxyl57m","uri":"at://did:plc:kcgwlowulc3rac43lregdawo/app.bsky.feed.post/3lqc6qonwns2f"},"description":"23 | they/them | computer science (nix, go, k8s), jazz, brutalism, impressionism, anime, science and the people.\nIf you couldn't tell, my main goals are to Mussolini hang the rich and write a million k8s operators","displayName":"karitham"}}
+1
test/fixtures/bsky/real/thread.json
···11+{"thread":{"post":{"uri":"at://did:plc:kcgwlowulc3rac43lregdawo/app.bsky.feed.post/3mgibe2arpk2c","cid":"bafyreihnoygrt3jdpt2axdeakhjsbfquktmrehnna6gbt63io6izmvg4pi","author":{"did":"did:plc:kcgwlowulc3rac43lregdawo","handle":"karitham.dev","displayName":"karitham","pronouns":"they/them","avatar":"https://cdn.bsky.app/img/avatar/plain/did:plc:kcgwlowulc3rac43lregdawo/bafkreig5onh2hofb4xr7voz4b3hwxigu6zqhr5sd6svjnnksaid4a2fmje","associated":{"chat":{"allowIncoming":"following"},"activitySubscription":{"allowSubscriptions":"followers"}},"labels":[],"createdAt":"2023-05-18T06:42:21.452Z"},"record":{"$type":"app.bsky.feed.post","createdAt":"2026-03-07T16:40:32.271Z","facets":[{"$type":"app.bsky.richtext.facet","features":[{"$type":"app.bsky.richtext.facet#mention","did":"did:plc:ooensn4mr5mhznzypvxelfa3"}],"index":{"byteEnd":39,"byteStart":24}}],"langs":["en"],"text":"I think my migration to @eurosky.social went well but it's so smooth I'm not sure I did it right 🔍"},"bookmarkCount":0,"replyCount":1,"repostCount":1,"likeCount":19,"quoteCount":1,"indexedAt":"2026-03-07T16:40:40.162Z","labels":[]},"threadContext":{},"$type":"app.bsky.feed.defs#threadViewPost"}}