···1+---
2+title: badges system
3+description: user badges for chat messages
4+---
5+6+## Overview
7+8+Badges appear next to usernames in chat messages. they're small icons that indicate status (streamer, mod, vip, etc.). There will be max 3 badges shown at once. One of the badges is server-based (e.g. streamer, mod, node staff badge), but the other two can be selected from a pool of cosmetic badges (such as subscription badges, event badges et al.). These cosmetic badges are cryptographically signed by the issuing party, and all the user needs to do is apply them to their chat profile. Note that certain badges may appear/disappear based on the current streamer's chat tktk.
9+10+## Lexicon schemas
11+12+We have three relevant lexicons.
13+14+1. **`place.stream.badge.defs`** - badge definitions and view model
15+16+ - defines known badge types: `mod`, `streamer`, `vip`
17+ - `badgeView` object: `{badgeType, issuer, recipient, signature?}`
18+19+2. **`place.stream.badge.issuance`** - record of badge grant
20+21+ - stored as atproto record (key: tid)
22+ - issued by streamer or other authorized entity
23+ - example: streamer issues vip badge to a user
24+25+3. **`place.stream.badge.display`** - user's badge selection
26+ - user-controlled record defining which badges to show
27+ - array of up to 3 `badgeSelection` objects
28+ - first slot server-controlled (mod/streamer/staff), second slot is streamer-specific (vip, subscription), third slot is user-set (event, staff2, node subscription, etc.)
29+30+:::note
31+This may get changed to be in the user's chat profile? Maybe we could have a "main" chat profile and a streamer-specific profile?
32+:::
33+34+## TODO
35+36+- [ ] implement cryptographic signatures for badge issuance
37+- [ ] implement badge issuance ui (streamer grants vip badges)
38+- [ ] implement badge selection ui (users choose which badges to display)
39+- [ ] add more badge types (subscriber, founder, staff, etc)
···1+---
2+title: place.stream.badge.defs
3+description: Reference for the place.stream.badge.defs lexicon
4+---
5+6+**Lexicon Version:** 1
7+8+## Definitions
9+10+<a name="badgeview"></a>
11+12+### `badgeView`
13+14+**Type:** `object`
15+16+View of a badge record, with fields resolved for display. If the DID in issuer is not the current streamplace node, the signature field shall be required.
17+18+**Properties:**
19+20+| Name | Type | Req'd | Description | Constraints |
21+| ----------- | -------- | ----- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
22+| `badgeType` | `string` | โ | | Known Values: `place.stream.badge.defs#mod`, `place.stream.badge.defs#streamer` |
23+| `issuer` | `string` | โ | DID of the badge issuer. | Format: `did` |
24+| `recipient` | `string` | โ | DID of the badge recipient. | Format: `did` |
25+| `signature` | `string` | โ | TODO: Cryptographic signature of the badge (of a place.stream.key). | |
26+27+---
28+29+<a name="mod"></a>
30+31+### `mod`
32+33+**Type:** `token`
34+35+This user is a moderator. Displayed with a sword icon.
36+37+---
38+39+<a name="streamer"></a>
40+41+### `streamer`
42+43+**Type:** `token`
44+45+This user is the streamer. Displayed with a star icon.
46+47+---
48+49+<a name="vip"></a>
50+51+### `vip`
52+53+**Type:** `token`
54+55+This user is a very important person.
56+57+---
58+59+## Lexicon Source
60+61+```json
62+{
63+ "lexicon": 1,
64+ "id": "place.stream.badge.defs",
65+ "defs": {
66+ "badgeView": {
67+ "type": "object",
68+ "required": ["badgeType", "issuer", "recipient"],
69+ "description": "View of a badge record, with fields resolved for display. If the DID in issuer is not the current streamplace node, the signature field shall be required.",
70+ "properties": {
71+ "badgeType": {
72+ "type": "string",
73+ "knownValues": [
74+ "place.stream.badge.defs#mod",
75+ "place.stream.badge.defs#streamer"
76+ ]
77+ },
78+ "issuer": {
79+ "type": "string",
80+ "format": "did",
81+ "description": "DID of the badge issuer."
82+ },
83+ "recipient": {
84+ "type": "string",
85+ "format": "did",
86+ "description": "DID of the badge recipient."
87+ },
88+ "signature": {
89+ "type": "string",
90+ "description": "TODO: Cryptographic signature of the badge (of a place.stream.key)."
91+ }
92+ }
93+ },
94+ "mod": {
95+ "type": "token",
96+ "description": "This user is a moderator. Displayed with a sword icon."
97+ },
98+ "streamer": {
99+ "type": "token",
100+ "description": "This user is the streamer. Displayed with a star icon."
101+ },
102+ "vip": {
103+ "type": "token",
104+ "description": "This user is a very important person."
105+ }
106+ }
107+}
108+```
···1+---
2+title: place.stream.badge.display
3+description: Reference for the place.stream.badge.display lexicon
4+---
5+6+**Lexicon Version:** 1
7+8+## Definitions
9+10+<a name="main"></a>
11+12+### `main`
13+14+**Type:** `record`
15+16+Record issuing a badge to a user.
17+18+**Record Properties:**
19+20+| Name | Type | Req'd | Description | Constraints |
21+| -------- | --------------------------------------------- | ----- | ----------------------------------------------------------------------------------------------------------------------- | ------------ |
22+| `badges` | Array of [`#badgeSelection`](#badgeselection) | โ | Up to 3 badge tokens to display with the message. First badge is server-controlled, remaining badges are user-settable. | Max Items: 3 |
23+24+---
25+26+<a name="badgeselection"></a>
27+28+### `badgeSelection`
29+30+**Type:** `object`
31+32+A badge selected for display. May be a full badgeView from the server, or a token representing a badge type that the client can look up for display info.
33+34+**Properties:**
35+36+| Name | Type | Req'd | Description | Constraints |
37+| ----------- | -------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
38+| `badgeType` | `string` | โ | | Known Values: `place.stream.badge.defs#mod`, `place.stream.badge.defs#vip` |
39+| `issuance` | `string` | โ | URI of the badge issuance record (place.stream.badge.issuance) that represents this badge. Required if badgeType is not recognized. | Format: `at-uri` |
40+41+---
42+43+## Lexicon Source
44+45+```json
46+{
47+ "lexicon": 1,
48+ "id": "place.stream.badge.display",
49+ "defs": {
50+ "main": {
51+ "type": "record",
52+ "description": "Record issuing a badge to a user.",
53+ "record": {
54+ "type": "object",
55+ "required": ["badges"],
56+ "properties": {
57+ "badges": {
58+ "type": "array",
59+ "description": "Up to 3 badge tokens to display with the message. First badge is server-controlled, remaining badges are user-settable.",
60+ "maxLength": 3,
61+ "items": {
62+ "type": "ref",
63+ "ref": "#badgeSelection"
64+ }
65+ }
66+ }
67+ }
68+ },
69+ "badgeSelection": {
70+ "type": "object",
71+ "description": "A badge selected for display. May be a full badgeView from the server, or a token representing a badge type that the client can look up for display info.",
72+ "required": ["badgeType"],
73+ "properties": {
74+ "badgeType": {
75+ "type": "string",
76+ "knownValues": [
77+ "place.stream.badge.defs#mod",
78+ "place.stream.badge.defs#vip"
79+ ]
80+ },
81+ "issuance": {
82+ "type": "string",
83+ "format": "at-uri",
84+ "description": "URI of the badge issuance record (place.stream.badge.issuance) that represents this badge. Required if badgeType is not recognized."
85+ }
86+ }
87+ }
88+ }
89+}
90+```
···1516**Properties:**
1718-| Name | Type | Req'd | Description | Constraints |
19-| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | -------------------------------------------------------------------------------------- | ------------------ |
20-| `uri` | `string` | โ | | Format: `at-uri` |
21-| `cid` | `string` | โ | | Format: `cid` |
22-| `author` | [`app.bsky.actor.defs#profileViewBasic`](https://github.com/bluesky-social/atproto/tree/main/lexicons/app/bsky/actor/defs.json#profileViewBasic) | โ | | |
23-| `record` | `unknown` | โ | | |
24-| `indexedAt` | `string` | โ | | Format: `datetime` |
25-| `chatProfile` | [`place.stream.chat.profile`](/lex-reference/place-stream-chat-profile) | โ | | |
26-| `replyTo` | Union of:<br/> [`#messageView`](#messageview) | โ | | |
27-| `deleted` | `boolean` | โ | If true, this message has been deleted or labeled and should be cleared from the cache | |
02829---
30···69 "deleted": {
70 "type": "boolean",
71 "description": "If true, this message has been deleted or labeled and should be cleared from the cache"
00000000072 }
73 }
74 }
···1516**Properties:**
1718+| Name | Type | Req'd | Description | Constraints |
19+| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
20+| `uri` | `string` | โ | | Format: `at-uri` |
21+| `cid` | `string` | โ | | Format: `cid` |
22+| `author` | [`app.bsky.actor.defs#profileViewBasic`](https://github.com/bluesky-social/atproto/tree/main/lexicons/app/bsky/actor/defs.json#profileViewBasic) | โ | | |
23+| `record` | `unknown` | โ | | |
24+| `indexedAt` | `string` | โ | | Format: `datetime` |
25+| `chatProfile` | [`place.stream.chat.profile`](/lex-reference/place-stream-chat-profile) | โ | | |
26+| `replyTo` | Union of:<br/> [`#messageView`](#messageview) | โ | | |
27+| `deleted` | `boolean` | โ | If true, this message has been deleted or labeled and should be cleared from the cache | |
28+| `badges` | Array of [`place.stream.badge.defs#badgeView`](/lex-reference/place-stream-badge-defs#badgeview) | โ | Up to 3 badge tokens to display with the message. First badge is server-controlled, remaining badges are user-settable. Tokens are looked up in badges.json for display info. | Max Items: 3 |
2930---
31···70 "deleted": {
71 "type": "boolean",
72 "description": "If true, this message has been deleted or labeled and should be cleared from the cache"
73+ },
74+ "badges": {
75+ "type": "array",
76+ "description": "Up to 3 badge tokens to display with the message. First badge is server-controlled, remaining badges are user-settable. Tokens are looked up in badges.json for display info.",
77+ "maxLength": 3,
78+ "items": {
79+ "type": "ref",
80+ "ref": "place.stream.badge.defs#badgeView"
81+ }
82 }
83 }
84 }
+46
lexicons/place/stream/badge/defs.json
···0000000000000000000000000000000000000000000000
···1+{
2+ "lexicon": 1,
3+ "id": "place.stream.badge.defs",
4+ "defs": {
5+ "badgeView": {
6+ "type": "object",
7+ "required": ["badgeType", "issuer", "recipient"],
8+ "description": "View of a badge record, with fields resolved for display. If the DID in issuer is not the current streamplace node, the signature field shall be required.",
9+ "properties": {
10+ "badgeType": {
11+ "type": "string",
12+ "knownValues": [
13+ "place.stream.badge.defs#mod",
14+ "place.stream.badge.defs#streamer"
15+ ]
16+ },
17+ "issuer": {
18+ "type": "string",
19+ "format": "did",
20+ "description": "DID of the badge issuer."
21+ },
22+ "recipient": {
23+ "type": "string",
24+ "format": "did",
25+ "description": "DID of the badge recipient."
26+ },
27+ "signature": {
28+ "type": "string",
29+ "description": "TODO: Cryptographic signature of the badge (of a place.stream.key)."
30+ }
31+ }
32+ },
33+ "mod": {
34+ "type": "token",
35+ "description": "This user is a moderator. Displayed with a sword icon."
36+ },
37+ "streamer": {
38+ "type": "token",
39+ "description": "This user is the streamer. Displayed with a star icon."
40+ },
41+ "vip": {
42+ "type": "token",
43+ "description": "This user is a very important person."
44+ }
45+ }
46+}
+44
lexicons/place/stream/badge/display.json
···00000000000000000000000000000000000000000000
···1+{
2+ "lexicon": 1,
3+ "id": "place.stream.badge.display",
4+ "defs": {
5+ "main": {
6+ "type": "record",
7+ "description": "Record issuing a badge to a user.",
8+ "record": {
9+ "type": "object",
10+ "required": ["badges"],
11+ "properties": {
12+ "badges": {
13+ "type": "array",
14+ "description": "Up to 3 badge tokens to display with the message. First badge is server-controlled, remaining badges are user-settable.",
15+ "maxLength": 3,
16+ "items": {
17+ "type": "ref",
18+ "ref": "#badgeSelection"
19+ }
20+ }
21+ }
22+ }
23+ },
24+ "badgeSelection": {
25+ "type": "object",
26+ "description": "A badge selected for display. May be a full badgeView from the server, or a token representing a badge type that the client can look up for display info.",
27+ "required": ["badgeType"],
28+ "properties": {
29+ "badgeType": {
30+ "type": "string",
31+ "knownValues": [
32+ "place.stream.badge.defs#mod",
33+ "place.stream.badge.defs#vip"
34+ ]
35+ },
36+ "issuance": {
37+ "type": "string",
38+ "format": "at-uri",
39+ "description": "URI of the badge issuance record (place.stream.badge.issuance) that represents this badge. Required if badgeType is not recognized."
40+ }
41+ }
42+ }
43+ }
44+}
···25 "deleted": {
26 "type": "boolean",
27 "description": "If true, this message has been deleted or labeled and should be cleared from the cache"
00000000028 }
29 }
30 }
···25 "deleted": {
26 "type": "boolean",
27 "description": "If true, this message has been deleted or labeled and should be cleared from the cache"
28+ },
29+ "badges": {
30+ "type": "array",
31+ "description": "Up to 3 badge tokens to display with the message. First badge is server-controlled, remaining badges are user-settable. Tokens are looked up in badges.json for display info.",
32+ "maxLength": 3,
33+ "items": {
34+ "type": "ref",
35+ "ref": "place.stream.badge.defs#badgeView"
36+ }
37 }
38 }
39 }
···1+package atproto
2+3+import (
4+ "context"
5+ "fmt"
6+7+ "stream.place/streamplace/pkg/constants"
8+ "stream.place/streamplace/pkg/log"
9+ "stream.place/streamplace/pkg/model"
10+ "stream.place/streamplace/pkg/streamplace"
11+)
12+13+// AddModBadgeIfApplicable checks if a message author has mod permissions for the streamer
14+// and adds a mod or streamer badge as the first badge (server-controlled).
15+// - If the author is the streamer, adds a "streamer" badge
16+// - If the author has moderation permissions, adds a "mod" badge
17+func AddModBadgeIfApplicable(ctx context.Context, message *streamplace.ChatDefs_MessageView, streamerDID string, issuerDID string, m model.Model) error {
18+ if message == nil {
19+ return fmt.Errorf("message is nil")
20+ }
21+22+ authorDID := message.Author.Did
23+24+ var badge *streamplace.BadgeDefs_BadgeView
25+26+ // Check if author is the streamer
27+ if authorDID == streamerDID {
28+ badge = &streamplace.BadgeDefs_BadgeView{
29+ BadgeType: constants.BadgeTypeStreamer,
30+ Issuer: issuerDID,
31+ Recipient: authorDID,
32+ }
33+ } else {
34+ // Check if author has any moderation permissions for the streamer
35+ delegations, err := m.GetModerationDelegations(ctx, streamerDID, authorDID)
36+ if err != nil {
37+ log.Error(ctx, "failed to get moderation delegations", "err", err, "authorDID", authorDID, "streamerDID", streamerDID)
38+ return err
39+ }
40+41+ // If the author has any delegations (meaning they're a moderator), add a mod badge
42+ if len(delegations) > 0 {
43+ badge = &streamplace.BadgeDefs_BadgeView{
44+ BadgeType: constants.BadgeTypeMod,
45+ Issuer: issuerDID,
46+ Recipient: authorDID,
47+ }
48+ }
49+ }
50+51+ // Prepend the badge if one was created (server-controlled badge is first)
52+ if badge != nil {
53+ if message.Badges == nil {
54+ message.Badges = []*streamplace.BadgeDefs_BadgeView{badge}
55+ } else {
56+ message.Badges = append([]*streamplace.BadgeDefs_BadgeView{badge}, message.Badges...)
57+ }
58+ }
59+60+ return nil
61+}
···1+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2+3+// Lexicon schema: place.stream.badge.defs
4+5+package streamplace
6+7+// BadgeDefs_BadgeView is a "badgeView" in the place.stream.badge.defs schema.
8+//
9+// View of a badge record, with fields resolved for display. If the DID in issuer is not the current streamplace node, the signature field shall be required.
10+type BadgeDefs_BadgeView struct {
11+ BadgeType string `json:"badgeType" cborgen:"badgeType"`
12+ // issuer: DID of the badge issuer.
13+ Issuer string `json:"issuer" cborgen:"issuer"`
14+ // recipient: DID of the badge recipient.
15+ Recipient string `json:"recipient" cborgen:"recipient"`
16+ // signature: TODO: Cryptographic signature of the badge (of a place.stream.key).
17+ Signature *string `json:"signature,omitempty" cborgen:"signature,omitempty"`
18+}
+28
pkg/streamplace/badgedisplay.go
···0000000000000000000000000000
···1+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2+3+// Lexicon schema: place.stream.badge.display
4+5+package streamplace
6+7+import (
8+ lexutil "github.com/bluesky-social/indigo/lex/util"
9+)
10+11+func init() {
12+ lexutil.RegisterType("place.stream.badge.display", &BadgeDisplay{})
13+}
14+15+type BadgeDisplay struct {
16+ LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.badge.display"`
17+ // badges: Up to 3 badge tokens to display with the message. First badge is server-controlled, remaining badges are user-settable.
18+ Badges []*BadgeDisplay_BadgeSelection `json:"badges" cborgen:"badges"`
19+}
20+21+// BadgeDisplay_BadgeSelection is a "badgeSelection" in the place.stream.badge.display schema.
22+//
23+// A badge selected for display. May be a full badgeView from the server, or a token representing a badge type that the client can look up for display info.
24+type BadgeDisplay_BadgeSelection struct {
25+ BadgeType string `json:"badgeType" cborgen:"badgeType"`
26+ // issuance: URI of the badge issuance record (place.stream.badge.issuance) that represents this badge. Required if badgeType is not recognized.
27+ Issuance *string `json:"issuance,omitempty" cborgen:"issuance,omitempty"`
28+}
+22
pkg/streamplace/badgeissuance.go
···0000000000000000000000
···1+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2+3+// Lexicon schema: place.stream.badge.issuance
4+5+package streamplace
6+7+import (
8+ lexutil "github.com/bluesky-social/indigo/lex/util"
9+)
10+11+func init() {
12+ lexutil.RegisterType("place.stream.badge.issuance", &BadgeIssuance{})
13+}
14+15+type BadgeIssuance struct {
16+ LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.badge.issuance"`
17+ BadgeType string `json:"badgeType" cborgen:"badgeType"`
18+ // recipient: DID of the badge recipient.
19+ Recipient string `json:"recipient" cborgen:"recipient"`
20+ // signature: TODO: Cryptographic signature of the badge (of a place.stream.key).
21+ Signature string `json:"signature" cborgen:"signature"`
22+}
···59665967 return nil
5968}
5969+func (t *BadgeIssuance) MarshalCBOR(w io.Writer) error {
5970+ if t == nil {
5971+ _, err := w.Write(cbg.CborNull)
5972+ return err
5973+ }
5974+5975+ cw := cbg.NewCborWriter(w)
5976+5977+ if _, err := cw.Write([]byte{164}); err != nil {
5978+ return err
5979+ }
5980+5981+ // t.LexiconTypeID (string) (string)
5982+ if len("$type") > 1000000 {
5983+ return xerrors.Errorf("Value in field \"$type\" was too long")
5984+ }
5985+5986+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil {
5987+ return err
5988+ }
5989+ if _, err := cw.WriteString(string("$type")); err != nil {
5990+ return err
5991+ }
5992+5993+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("place.stream.badge.issuance"))); err != nil {
5994+ return err
5995+ }
5996+ if _, err := cw.WriteString(string("place.stream.badge.issuance")); err != nil {
5997+ return err
5998+ }
5999+6000+ // t.BadgeType (string) (string)
6001+ if len("badgeType") > 1000000 {
6002+ return xerrors.Errorf("Value in field \"badgeType\" was too long")
6003+ }
6004+6005+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("badgeType"))); err != nil {
6006+ return err
6007+ }
6008+ if _, err := cw.WriteString(string("badgeType")); err != nil {
6009+ return err
6010+ }
6011+6012+ if len(t.BadgeType) > 1000000 {
6013+ return xerrors.Errorf("Value in field t.BadgeType was too long")
6014+ }
6015+6016+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.BadgeType))); err != nil {
6017+ return err
6018+ }
6019+ if _, err := cw.WriteString(string(t.BadgeType)); err != nil {
6020+ return err
6021+ }
6022+6023+ // t.Recipient (string) (string)
6024+ if len("recipient") > 1000000 {
6025+ return xerrors.Errorf("Value in field \"recipient\" was too long")
6026+ }
6027+6028+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("recipient"))); err != nil {
6029+ return err
6030+ }
6031+ if _, err := cw.WriteString(string("recipient")); err != nil {
6032+ return err
6033+ }
6034+6035+ if len(t.Recipient) > 1000000 {
6036+ return xerrors.Errorf("Value in field t.Recipient was too long")
6037+ }
6038+6039+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Recipient))); err != nil {
6040+ return err
6041+ }
6042+ if _, err := cw.WriteString(string(t.Recipient)); err != nil {
6043+ return err
6044+ }
6045+6046+ // t.Signature (string) (string)
6047+ if len("signature") > 1000000 {
6048+ return xerrors.Errorf("Value in field \"signature\" was too long")
6049+ }
6050+6051+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("signature"))); err != nil {
6052+ return err
6053+ }
6054+ if _, err := cw.WriteString(string("signature")); err != nil {
6055+ return err
6056+ }
6057+6058+ if len(t.Signature) > 1000000 {
6059+ return xerrors.Errorf("Value in field t.Signature was too long")
6060+ }
6061+6062+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Signature))); err != nil {
6063+ return err
6064+ }
6065+ if _, err := cw.WriteString(string(t.Signature)); err != nil {
6066+ return err
6067+ }
6068+ return nil
6069+}
6070+6071+func (t *BadgeIssuance) UnmarshalCBOR(r io.Reader) (err error) {
6072+ *t = BadgeIssuance{}
6073+6074+ cr := cbg.NewCborReader(r)
6075+6076+ maj, extra, err := cr.ReadHeader()
6077+ if err != nil {
6078+ return err
6079+ }
6080+ defer func() {
6081+ if err == io.EOF {
6082+ err = io.ErrUnexpectedEOF
6083+ }
6084+ }()
6085+6086+ if maj != cbg.MajMap {
6087+ return fmt.Errorf("cbor input should be of type map")
6088+ }
6089+6090+ if extra > cbg.MaxLength {
6091+ return fmt.Errorf("BadgeIssuance: map struct too large (%d)", extra)
6092+ }
6093+6094+ n := extra
6095+6096+ nameBuf := make([]byte, 9)
6097+ for i := uint64(0); i < n; i++ {
6098+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
6099+ if err != nil {
6100+ return err
6101+ }
6102+6103+ if !ok {
6104+ // Field doesn't exist on this type, so ignore it
6105+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
6106+ return err
6107+ }
6108+ continue
6109+ }
6110+6111+ switch string(nameBuf[:nameLen]) {
6112+ // t.LexiconTypeID (string) (string)
6113+ case "$type":
6114+6115+ {
6116+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
6117+ if err != nil {
6118+ return err
6119+ }
6120+6121+ t.LexiconTypeID = string(sval)
6122+ }
6123+ // t.BadgeType (string) (string)
6124+ case "badgeType":
6125+6126+ {
6127+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
6128+ if err != nil {
6129+ return err
6130+ }
6131+6132+ t.BadgeType = string(sval)
6133+ }
6134+ // t.Recipient (string) (string)
6135+ case "recipient":
6136+6137+ {
6138+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
6139+ if err != nil {
6140+ return err
6141+ }
6142+6143+ t.Recipient = string(sval)
6144+ }
6145+ // t.Signature (string) (string)
6146+ case "signature":
6147+6148+ {
6149+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
6150+ if err != nil {
6151+ return err
6152+ }
6153+6154+ t.Signature = string(sval)
6155+ }
6156+6157+ default:
6158+ // Field doesn't exist on this type, so ignore it
6159+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
6160+ return err
6161+ }
6162+ }
6163+ }
6164+6165+ return nil
6166+}
6167+func (t *BadgeDisplay) MarshalCBOR(w io.Writer) error {
6168+ if t == nil {
6169+ _, err := w.Write(cbg.CborNull)
6170+ return err
6171+ }
6172+6173+ cw := cbg.NewCborWriter(w)
6174+6175+ if _, err := cw.Write([]byte{162}); err != nil {
6176+ return err
6177+ }
6178+6179+ // t.LexiconTypeID (string) (string)
6180+ if len("$type") > 1000000 {
6181+ return xerrors.Errorf("Value in field \"$type\" was too long")
6182+ }
6183+6184+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil {
6185+ return err
6186+ }
6187+ if _, err := cw.WriteString(string("$type")); err != nil {
6188+ return err
6189+ }
6190+6191+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("place.stream.badge.display"))); err != nil {
6192+ return err
6193+ }
6194+ if _, err := cw.WriteString(string("place.stream.badge.display")); err != nil {
6195+ return err
6196+ }
6197+6198+ // t.Badges ([]*streamplace.BadgeDisplay_BadgeSelection) (slice)
6199+ if len("badges") > 1000000 {
6200+ return xerrors.Errorf("Value in field \"badges\" was too long")
6201+ }
6202+6203+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("badges"))); err != nil {
6204+ return err
6205+ }
6206+ if _, err := cw.WriteString(string("badges")); err != nil {
6207+ return err
6208+ }
6209+6210+ if len(t.Badges) > 8192 {
6211+ return xerrors.Errorf("Slice value in field t.Badges was too long")
6212+ }
6213+6214+ if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Badges))); err != nil {
6215+ return err
6216+ }
6217+ for _, v := range t.Badges {
6218+ if err := v.MarshalCBOR(cw); err != nil {
6219+ return err
6220+ }
6221+6222+ }
6223+ return nil
6224+}
6225+6226+func (t *BadgeDisplay) UnmarshalCBOR(r io.Reader) (err error) {
6227+ *t = BadgeDisplay{}
6228+6229+ cr := cbg.NewCborReader(r)
6230+6231+ maj, extra, err := cr.ReadHeader()
6232+ if err != nil {
6233+ return err
6234+ }
6235+ defer func() {
6236+ if err == io.EOF {
6237+ err = io.ErrUnexpectedEOF
6238+ }
6239+ }()
6240+6241+ if maj != cbg.MajMap {
6242+ return fmt.Errorf("cbor input should be of type map")
6243+ }
6244+6245+ if extra > cbg.MaxLength {
6246+ return fmt.Errorf("BadgeDisplay: map struct too large (%d)", extra)
6247+ }
6248+6249+ n := extra
6250+6251+ nameBuf := make([]byte, 6)
6252+ for i := uint64(0); i < n; i++ {
6253+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
6254+ if err != nil {
6255+ return err
6256+ }
6257+6258+ if !ok {
6259+ // Field doesn't exist on this type, so ignore it
6260+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
6261+ return err
6262+ }
6263+ continue
6264+ }
6265+6266+ switch string(nameBuf[:nameLen]) {
6267+ // t.LexiconTypeID (string) (string)
6268+ case "$type":
6269+6270+ {
6271+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
6272+ if err != nil {
6273+ return err
6274+ }
6275+6276+ t.LexiconTypeID = string(sval)
6277+ }
6278+ // t.Badges ([]*streamplace.BadgeDisplay_BadgeSelection) (slice)
6279+ case "badges":
6280+6281+ maj, extra, err = cr.ReadHeader()
6282+ if err != nil {
6283+ return err
6284+ }
6285+6286+ if extra > 8192 {
6287+ return fmt.Errorf("t.Badges: array too large (%d)", extra)
6288+ }
6289+6290+ if maj != cbg.MajArray {
6291+ return fmt.Errorf("expected cbor array")
6292+ }
6293+6294+ if extra > 0 {
6295+ t.Badges = make([]*BadgeDisplay_BadgeSelection, extra)
6296+ }
6297+6298+ for i := 0; i < int(extra); i++ {
6299+ {
6300+ var maj byte
6301+ var extra uint64
6302+ var err error
6303+ _ = maj
6304+ _ = extra
6305+ _ = err
6306+6307+ {
6308+6309+ b, err := cr.ReadByte()
6310+ if err != nil {
6311+ return err
6312+ }
6313+ if b != cbg.CborNull[0] {
6314+ if err := cr.UnreadByte(); err != nil {
6315+ return err
6316+ }
6317+ t.Badges[i] = new(BadgeDisplay_BadgeSelection)
6318+ if err := t.Badges[i].UnmarshalCBOR(cr); err != nil {
6319+ return xerrors.Errorf("unmarshaling t.Badges[i] pointer: %w", err)
6320+ }
6321+ }
6322+6323+ }
6324+6325+ }
6326+ }
6327+6328+ default:
6329+ // Field doesn't exist on this type, so ignore it
6330+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
6331+ return err
6332+ }
6333+ }
6334+ }
6335+6336+ return nil
6337+}
6338+func (t *BadgeDisplay_BadgeSelection) MarshalCBOR(w io.Writer) error {
6339+ if t == nil {
6340+ _, err := w.Write(cbg.CborNull)
6341+ return err
6342+ }
6343+6344+ cw := cbg.NewCborWriter(w)
6345+ fieldCount := 2
6346+6347+ if t.Issuance == nil {
6348+ fieldCount--
6349+ }
6350+6351+ if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
6352+ return err
6353+ }
6354+6355+ // t.Issuance (string) (string)
6356+ if t.Issuance != nil {
6357+6358+ if len("issuance") > 1000000 {
6359+ return xerrors.Errorf("Value in field \"issuance\" was too long")
6360+ }
6361+6362+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("issuance"))); err != nil {
6363+ return err
6364+ }
6365+ if _, err := cw.WriteString(string("issuance")); err != nil {
6366+ return err
6367+ }
6368+6369+ if t.Issuance == nil {
6370+ if _, err := cw.Write(cbg.CborNull); err != nil {
6371+ return err
6372+ }
6373+ } else {
6374+ if len(*t.Issuance) > 1000000 {
6375+ return xerrors.Errorf("Value in field t.Issuance was too long")
6376+ }
6377+6378+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Issuance))); err != nil {
6379+ return err
6380+ }
6381+ if _, err := cw.WriteString(string(*t.Issuance)); err != nil {
6382+ return err
6383+ }
6384+ }
6385+ }
6386+6387+ // t.BadgeType (string) (string)
6388+ if len("badgeType") > 1000000 {
6389+ return xerrors.Errorf("Value in field \"badgeType\" was too long")
6390+ }
6391+6392+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("badgeType"))); err != nil {
6393+ return err
6394+ }
6395+ if _, err := cw.WriteString(string("badgeType")); err != nil {
6396+ return err
6397+ }
6398+6399+ if len(t.BadgeType) > 1000000 {
6400+ return xerrors.Errorf("Value in field t.BadgeType was too long")
6401+ }
6402+6403+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.BadgeType))); err != nil {
6404+ return err
6405+ }
6406+ if _, err := cw.WriteString(string(t.BadgeType)); err != nil {
6407+ return err
6408+ }
6409+ return nil
6410+}
6411+6412+func (t *BadgeDisplay_BadgeSelection) UnmarshalCBOR(r io.Reader) (err error) {
6413+ *t = BadgeDisplay_BadgeSelection{}
6414+6415+ cr := cbg.NewCborReader(r)
6416+6417+ maj, extra, err := cr.ReadHeader()
6418+ if err != nil {
6419+ return err
6420+ }
6421+ defer func() {
6422+ if err == io.EOF {
6423+ err = io.ErrUnexpectedEOF
6424+ }
6425+ }()
6426+6427+ if maj != cbg.MajMap {
6428+ return fmt.Errorf("cbor input should be of type map")
6429+ }
6430+6431+ if extra > cbg.MaxLength {
6432+ return fmt.Errorf("BadgeDisplay_BadgeSelection: map struct too large (%d)", extra)
6433+ }
6434+6435+ n := extra
6436+6437+ nameBuf := make([]byte, 9)
6438+ for i := uint64(0); i < n; i++ {
6439+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
6440+ if err != nil {
6441+ return err
6442+ }
6443+6444+ if !ok {
6445+ // Field doesn't exist on this type, so ignore it
6446+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
6447+ return err
6448+ }
6449+ continue
6450+ }
6451+6452+ switch string(nameBuf[:nameLen]) {
6453+ // t.Issuance (string) (string)
6454+ case "issuance":
6455+6456+ {
6457+ b, err := cr.ReadByte()
6458+ if err != nil {
6459+ return err
6460+ }
6461+ if b != cbg.CborNull[0] {
6462+ if err := cr.UnreadByte(); err != nil {
6463+ return err
6464+ }
6465+6466+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
6467+ if err != nil {
6468+ return err
6469+ }
6470+6471+ t.Issuance = (*string)(&sval)
6472+ }
6473+ }
6474+ // t.BadgeType (string) (string)
6475+ case "badgeType":
6476+6477+ {
6478+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
6479+ if err != nil {
6480+ return err
6481+ }
6482+6483+ t.BadgeType = string(sval)
6484+ }
6485+6486+ default:
6487+ // Field doesn't exist on this type, so ignore it
6488+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
6489+ return err
6490+ }
6491+ }
6492+ }
6493+6494+ return nil
6495+}
+4-2
pkg/streamplace/chatdefs.go
···16type ChatDefs_MessageView struct {
17 LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.chat.defs#messageView"`
18 Author *appbsky.ActorDefs_ProfileViewBasic `json:"author" cborgen:"author"`
19- ChatProfile *ChatProfile `json:"chatProfile,omitempty" cborgen:"chatProfile,omitempty"`
20- Cid string `json:"cid" cborgen:"cid"`
0021 // deleted: If true, this message has been deleted or labeled and should be cleared from the cache
22 Deleted *bool `json:"deleted,omitempty" cborgen:"deleted,omitempty"`
23 IndexedAt string `json:"indexedAt" cborgen:"indexedAt"`
···16type ChatDefs_MessageView struct {
17 LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.chat.defs#messageView"`
18 Author *appbsky.ActorDefs_ProfileViewBasic `json:"author" cborgen:"author"`
19+ // badges: Up to 3 badge tokens to display with the message. First badge is server-controlled, remaining badges are user-settable. Tokens are looked up in badges.json for display info.
20+ Badges []*BadgeDefs_BadgeView `json:"badges,omitempty" cborgen:"badges,omitempty"`
21+ ChatProfile *ChatProfile `json:"chatProfile,omitempty" cborgen:"chatProfile,omitempty"`
22+ Cid string `json:"cid" cborgen:"cid"`
23 // deleted: If true, this message has been deleted or labeled and should be cleared from the cache
24 Deleted *bool `json:"deleted,omitempty" cborgen:"deleted,omitempty"`
25 IndexedAt string `json:"indexedAt" cborgen:"indexedAt"`