···11+---
22+title: place.stream.badge.defs
33+description: Reference for the place.stream.badge.defs lexicon
44+---
55+66+**Lexicon Version:** 1
77+88+## Definitions
99+1010+<a name="badgeview"></a>
1111+1212+### `badgeView`
1313+1414+**Type:** `object`
1515+1616+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.
1717+1818+**Properties:**
1919+2020+| Name | Type | Req'd | Description | Constraints |
2121+| ----------- | -------- | ----- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
2222+| `badgeType` | `string` | ✅ | | Known Values: `place.stream.badge.defs#mod`, `place.stream.badge.defs#streamer` |
2323+| `issuer` | `string` | ✅ | DID of the badge issuer. | Format: `did` |
2424+| `recipient` | `string` | ✅ | DID of the badge recipient. | Format: `did` |
2525+| `signature` | `string` | ❌ | TODO: Cryptographic signature of the badge (of a place.stream.key). | |
2626+2727+---
2828+2929+<a name="mod"></a>
3030+3131+### `mod`
3232+3333+**Type:** `token`
3434+3535+This user is a moderator. Displayed with a sword icon.
3636+3737+---
3838+3939+<a name="streamer"></a>
4040+4141+### `streamer`
4242+4343+**Type:** `token`
4444+4545+This user is the streamer. Displayed with a star icon.
4646+4747+---
4848+4949+<a name="vip"></a>
5050+5151+### `vip`
5252+5353+**Type:** `token`
5454+5555+This user is a very important person.
5656+5757+---
5858+5959+## Lexicon Source
6060+6161+```json
6262+{
6363+ "lexicon": 1,
6464+ "id": "place.stream.badge.defs",
6565+ "defs": {
6666+ "badgeView": {
6767+ "type": "object",
6868+ "required": ["badgeType", "issuer", "recipient"],
6969+ "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.",
7070+ "properties": {
7171+ "badgeType": {
7272+ "type": "string",
7373+ "knownValues": [
7474+ "place.stream.badge.defs#mod",
7575+ "place.stream.badge.defs#streamer"
7676+ ]
7777+ },
7878+ "issuer": {
7979+ "type": "string",
8080+ "format": "did",
8181+ "description": "DID of the badge issuer."
8282+ },
8383+ "recipient": {
8484+ "type": "string",
8585+ "format": "did",
8686+ "description": "DID of the badge recipient."
8787+ },
8888+ "signature": {
8989+ "type": "string",
9090+ "description": "TODO: Cryptographic signature of the badge (of a place.stream.key)."
9191+ }
9292+ }
9393+ },
9494+ "mod": {
9595+ "type": "token",
9696+ "description": "This user is a moderator. Displayed with a sword icon."
9797+ },
9898+ "streamer": {
9999+ "type": "token",
100100+ "description": "This user is the streamer. Displayed with a star icon."
101101+ },
102102+ "vip": {
103103+ "type": "token",
104104+ "description": "This user is a very important person."
105105+ }
106106+ }
107107+}
108108+```
···11+---
22+title: place.stream.badge.display
33+description: Reference for the place.stream.badge.display lexicon
44+---
55+66+**Lexicon Version:** 1
77+88+## Definitions
99+1010+<a name="main"></a>
1111+1212+### `main`
1313+1414+**Type:** `record`
1515+1616+Record issuing a badge to a user.
1717+1818+**Record Properties:**
1919+2020+| Name | Type | Req'd | Description | Constraints |
2121+| -------- | --------------------------------------------- | ----- | ----------------------------------------------------------------------------------------------------------------------- | ------------ |
2222+| `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 |
2323+2424+---
2525+2626+<a name="badgeselection"></a>
2727+2828+### `badgeSelection`
2929+3030+**Type:** `object`
3131+3232+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.
3333+3434+**Properties:**
3535+3636+| Name | Type | Req'd | Description | Constraints |
3737+| ----------- | -------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
3838+| `badgeType` | `string` | ✅ | | Known Values: `place.stream.badge.defs#mod`, `place.stream.badge.defs#vip` |
3939+| `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` |
4040+4141+---
4242+4343+## Lexicon Source
4444+4545+```json
4646+{
4747+ "lexicon": 1,
4848+ "id": "place.stream.badge.display",
4949+ "defs": {
5050+ "main": {
5151+ "type": "record",
5252+ "description": "Record issuing a badge to a user.",
5353+ "record": {
5454+ "type": "object",
5555+ "required": ["badges"],
5656+ "properties": {
5757+ "badges": {
5858+ "type": "array",
5959+ "description": "Up to 3 badge tokens to display with the message. First badge is server-controlled, remaining badges are user-settable.",
6060+ "maxLength": 3,
6161+ "items": {
6262+ "type": "ref",
6363+ "ref": "#badgeSelection"
6464+ }
6565+ }
6666+ }
6767+ }
6868+ },
6969+ "badgeSelection": {
7070+ "type": "object",
7171+ "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.",
7272+ "required": ["badgeType"],
7373+ "properties": {
7474+ "badgeType": {
7575+ "type": "string",
7676+ "knownValues": [
7777+ "place.stream.badge.defs#mod",
7878+ "place.stream.badge.defs#vip"
7979+ ]
8080+ },
8181+ "issuance": {
8282+ "type": "string",
8383+ "format": "at-uri",
8484+ "description": "URI of the badge issuance record (place.stream.badge.issuance) that represents this badge. Required if badgeType is not recognized."
8585+ }
8686+ }
8787+ }
8888+ }
8989+}
9090+```
···15151616**Properties:**
17171818-| Name | Type | Req'd | Description | Constraints |
1919-| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | -------------------------------------------------------------------------------------- | ------------------ |
2020-| `uri` | `string` | ✅ | | Format: `at-uri` |
2121-| `cid` | `string` | ✅ | | Format: `cid` |
2222-| `author` | [`app.bsky.actor.defs#profileViewBasic`](https://github.com/bluesky-social/atproto/tree/main/lexicons/app/bsky/actor/defs.json#profileViewBasic) | ✅ | | |
2323-| `record` | `unknown` | ✅ | | |
2424-| `indexedAt` | `string` | ✅ | | Format: `datetime` |
2525-| `chatProfile` | [`place.stream.chat.profile`](/lex-reference/place-stream-chat-profile) | ❌ | | |
2626-| `replyTo` | Union of:<br/> [`#messageView`](#messageview) | ❌ | | |
2727-| `deleted` | `boolean` | ❌ | If true, this message has been deleted or labeled and should be cleared from the cache | |
1818+| Name | Type | Req'd | Description | Constraints |
1919+| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
2020+| `uri` | `string` | ✅ | | Format: `at-uri` |
2121+| `cid` | `string` | ✅ | | Format: `cid` |
2222+| `author` | [`app.bsky.actor.defs#profileViewBasic`](https://github.com/bluesky-social/atproto/tree/main/lexicons/app/bsky/actor/defs.json#profileViewBasic) | ✅ | | |
2323+| `record` | `unknown` | ✅ | | |
2424+| `indexedAt` | `string` | ✅ | | Format: `datetime` |
2525+| `chatProfile` | [`place.stream.chat.profile`](/lex-reference/place-stream-chat-profile) | ❌ | | |
2626+| `replyTo` | Union of:<br/> [`#messageView`](#messageview) | ❌ | | |
2727+| `deleted` | `boolean` | ❌ | If true, this message has been deleted or labeled and should be cleared from the cache | |
2828+| `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 |
28292930---
3031···6970 "deleted": {
7071 "type": "boolean",
7172 "description": "If true, this message has been deleted or labeled and should be cleared from the cache"
7373+ },
7474+ "badges": {
7575+ "type": "array",
7676+ "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.",
7777+ "maxLength": 3,
7878+ "items": {
7979+ "type": "ref",
8080+ "ref": "place.stream.badge.defs#badgeView"
8181+ }
7282 }
7383 }
7484 }
+46
lexicons/place/stream/badge/defs.json
···11+{
22+ "lexicon": 1,
33+ "id": "place.stream.badge.defs",
44+ "defs": {
55+ "badgeView": {
66+ "type": "object",
77+ "required": ["badgeType", "issuer", "recipient"],
88+ "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.",
99+ "properties": {
1010+ "badgeType": {
1111+ "type": "string",
1212+ "knownValues": [
1313+ "place.stream.badge.defs#mod",
1414+ "place.stream.badge.defs#streamer"
1515+ ]
1616+ },
1717+ "issuer": {
1818+ "type": "string",
1919+ "format": "did",
2020+ "description": "DID of the badge issuer."
2121+ },
2222+ "recipient": {
2323+ "type": "string",
2424+ "format": "did",
2525+ "description": "DID of the badge recipient."
2626+ },
2727+ "signature": {
2828+ "type": "string",
2929+ "description": "TODO: Cryptographic signature of the badge (of a place.stream.key)."
3030+ }
3131+ }
3232+ },
3333+ "mod": {
3434+ "type": "token",
3535+ "description": "This user is a moderator. Displayed with a sword icon."
3636+ },
3737+ "streamer": {
3838+ "type": "token",
3939+ "description": "This user is the streamer. Displayed with a star icon."
4040+ },
4141+ "vip": {
4242+ "type": "token",
4343+ "description": "This user is a very important person."
4444+ }
4545+ }
4646+}
+44
lexicons/place/stream/badge/display.json
···11+{
22+ "lexicon": 1,
33+ "id": "place.stream.badge.display",
44+ "defs": {
55+ "main": {
66+ "type": "record",
77+ "description": "Record issuing a badge to a user.",
88+ "record": {
99+ "type": "object",
1010+ "required": ["badges"],
1111+ "properties": {
1212+ "badges": {
1313+ "type": "array",
1414+ "description": "Up to 3 badge tokens to display with the message. First badge is server-controlled, remaining badges are user-settable.",
1515+ "maxLength": 3,
1616+ "items": {
1717+ "type": "ref",
1818+ "ref": "#badgeSelection"
1919+ }
2020+ }
2121+ }
2222+ }
2323+ },
2424+ "badgeSelection": {
2525+ "type": "object",
2626+ "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.",
2727+ "required": ["badgeType"],
2828+ "properties": {
2929+ "badgeType": {
3030+ "type": "string",
3131+ "knownValues": [
3232+ "place.stream.badge.defs#mod",
3333+ "place.stream.badge.defs#vip"
3434+ ]
3535+ },
3636+ "issuance": {
3737+ "type": "string",
3838+ "format": "at-uri",
3939+ "description": "URI of the badge issuance record (place.stream.badge.issuance) that represents this badge. Required if badgeType is not recognized."
4040+ }
4141+ }
4242+ }
4343+ }
4444+}
···2525 "deleted": {
2626 "type": "boolean",
2727 "description": "If true, this message has been deleted or labeled and should be cleared from the cache"
2828+ },
2929+ "badges": {
3030+ "type": "array",
3131+ "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.",
3232+ "maxLength": 3,
3333+ "items": {
3434+ "type": "ref",
3535+ "ref": "place.stream.badge.defs#badgeView"
3636+ }
2837 }
2938 }
3039 }
+9
pkg/api/websocket.go
···33import (
44 "context"
55 "encoding/json"
66+ "fmt"
67 "net"
78 "net/http"
89 "time"
···1213 "github.com/gorilla/websocket"
1314 "github.com/julienschmidt/httprouter"
14151616+ "stream.place/streamplace/pkg/atproto"
1517 apierrors "stream.place/streamplace/pkg/errors"
1618 "stream.place/streamplace/pkg/log"
1719 "stream.place/streamplace/pkg/renditions"
···237239 log.Error(ctx, "could not get chat messages", "error", err)
238240 return
239241 }
242242+243243+ // Add mod badges to messages
244244+ issuerDID := fmt.Sprintf("did:web:%s", a.CLI.BroadcasterHost)
240245 for _, message := range messages {
246246+ err := atproto.AddModBadgeIfApplicable(ctx, message, repoDID, issuerDID, a.Model)
247247+ if err != nil {
248248+ log.Error(ctx, "failed to add mod badge to message", "error", err)
249249+ }
241250 initialBurst <- message
242251 }
243252 }()
+61
pkg/atproto/badges.go
···11+package atproto
22+33+import (
44+ "context"
55+ "fmt"
66+77+ "stream.place/streamplace/pkg/constants"
88+ "stream.place/streamplace/pkg/log"
99+ "stream.place/streamplace/pkg/model"
1010+ "stream.place/streamplace/pkg/streamplace"
1111+)
1212+1313+// AddModBadgeIfApplicable checks if a message author has mod permissions for the streamer
1414+// and adds a mod or streamer badge as the first badge (server-controlled).
1515+// - If the author is the streamer, adds a "streamer" badge
1616+// - If the author has moderation permissions, adds a "mod" badge
1717+func AddModBadgeIfApplicable(ctx context.Context, message *streamplace.ChatDefs_MessageView, streamerDID string, issuerDID string, m model.Model) error {
1818+ if message == nil {
1919+ return fmt.Errorf("message is nil")
2020+ }
2121+2222+ authorDID := message.Author.Did
2323+2424+ var badge *streamplace.BadgeDefs_BadgeView
2525+2626+ // Check if author is the streamer
2727+ if authorDID == streamerDID {
2828+ badge = &streamplace.BadgeDefs_BadgeView{
2929+ BadgeType: constants.BadgeTypeStreamer,
3030+ Issuer: issuerDID,
3131+ Recipient: authorDID,
3232+ }
3333+ } else {
3434+ // Check if author has any moderation permissions for the streamer
3535+ delegations, err := m.GetModerationDelegations(ctx, streamerDID, authorDID)
3636+ if err != nil {
3737+ log.Error(ctx, "failed to get moderation delegations", "err", err, "authorDID", authorDID, "streamerDID", streamerDID)
3838+ return err
3939+ }
4040+4141+ // If the author has any delegations (meaning they're a moderator), add a mod badge
4242+ if len(delegations) > 0 {
4343+ badge = &streamplace.BadgeDefs_BadgeView{
4444+ BadgeType: constants.BadgeTypeMod,
4545+ Issuer: issuerDID,
4646+ Recipient: authorDID,
4747+ }
4848+ }
4949+ }
5050+5151+ // Prepend the badge if one was created (server-controlled badge is first)
5252+ if badge != nil {
5353+ if message.Badges == nil {
5454+ message.Badges = []*streamplace.BadgeDefs_BadgeView{badge}
5555+ } else {
5656+ message.Badges = append([]*streamplace.BadgeDefs_BadgeView{badge}, message.Badges...)
5757+ }
5858+ }
5959+6060+ return nil
6161+}
+100
pkg/atproto/badges_test.go
···11+package atproto
22+33+import (
44+ "context"
55+ "testing"
66+ "time"
77+88+ bsky "github.com/bluesky-social/indigo/api/bsky"
99+ "github.com/bluesky-social/indigo/atproto/syntax"
1010+ "github.com/bluesky-social/indigo/util"
1111+ "github.com/stretchr/testify/require"
1212+ "stream.place/streamplace/pkg/model"
1313+ "stream.place/streamplace/pkg/streamplace"
1414+)
1515+1616+func TestAddModBadge(t *testing.T) {
1717+ ctx := context.Background()
1818+1919+ mod, err := model.MakeDB(":memory:")
2020+ require.NoError(t, err)
2121+2222+ streamerDID := "did:plc:streamer"
2323+ moderatorDID := "did:plc:moderator"
2424+ issuerDID := "did:web:example.com"
2525+2626+ // Create a chat message
2727+ message := &streamplace.ChatDefs_MessageView{
2828+ LexiconTypeID: "place.stream.chat.defs#messageView",
2929+ Uri: "at://test/place.stream.chat.message/123",
3030+ Cid: "test-cid",
3131+ Author: &bsky.ActorDefs_ProfileViewBasic{
3232+ Did: moderatorDID,
3333+ Handle: "moderator.test",
3434+ },
3535+ IndexedAt: "2024-01-01T00:00:00Z",
3636+ }
3737+3838+ t.Run("no badge when user is not a moderator", func(t *testing.T) {
3939+ msg := *message // copy
4040+ err := AddModBadgeIfApplicable(ctx, &msg, streamerDID, issuerDID, mod)
4141+ require.NoError(t, err)
4242+ require.Nil(t, msg.Badges, "should not have badges when user is not a moderator")
4343+ })
4444+4545+ t.Run("adds streamer badge when user is the streamer", func(t *testing.T) {
4646+ msg := *message // copy
4747+ msg.Author = &bsky.ActorDefs_ProfileViewBasic{
4848+ Did: streamerDID,
4949+ Handle: "streamer.test",
5050+ }
5151+ err := AddModBadgeIfApplicable(ctx, &msg, streamerDID, issuerDID, mod)
5252+ require.NoError(t, err)
5353+ require.Len(t, msg.Badges, 1, "should have 1 badge when user is the streamer")
5454+ require.Equal(t, "place.stream.badge.defs#streamer", msg.Badges[0].BadgeType)
5555+ require.Equal(t, issuerDID, msg.Badges[0].Issuer)
5656+ require.Equal(t, streamerDID, msg.Badges[0].Recipient)
5757+ })
5858+5959+ t.Run("adds mod badge when user has moderation permissions", func(t *testing.T) {
6060+ // Grant moderation permissions to the moderator
6161+ perm := &streamplace.ModerationPermission{
6262+ LexiconTypeID: "place.stream.moderation.permission",
6363+ Moderator: moderatorDID,
6464+ Permissions: []string{"ban", "hide"},
6565+ CreatedAt: time.Now().Format(util.ISO8601),
6666+ }
6767+ aturi, err := syntax.ParseATURI("at://" + streamerDID + "/place.stream.moderation.permission/test123")
6868+ require.NoError(t, err)
6969+7070+ // Sync the permission to the model
7171+ err = mod.CreateModerationDelegation(ctx, perm, aturi)
7272+ require.NoError(t, err)
7373+7474+ msg := *message // copy
7575+ err = AddModBadgeIfApplicable(ctx, &msg, streamerDID, issuerDID, mod)
7676+ require.NoError(t, err)
7777+ require.Len(t, msg.Badges, 1, "should have 1 badge when user is a moderator")
7878+ require.Equal(t, "place.stream.badges.badge#mod", msg.Badges[0].BadgeType)
7979+ require.Equal(t, issuerDID, msg.Badges[0].Issuer)
8080+ require.Equal(t, moderatorDID, msg.Badges[0].Recipient)
8181+ })
8282+8383+ t.Run("prepends mod badge to existing badges", func(t *testing.T) {
8484+ // Create message with existing user-settable badge
8585+ msg := *message // copy
8686+ msg.Badges = []*streamplace.BadgeDefs_BadgeView{
8787+ {
8888+ BadgeType: "place.stream.badges.badge#vip",
8989+ Issuer: "did:web:other.com",
9090+ Recipient: moderatorDID,
9191+ },
9292+ }
9393+9494+ err = AddModBadgeIfApplicable(ctx, &msg, streamerDID, issuerDID, mod)
9595+ require.NoError(t, err)
9696+ require.Len(t, msg.Badges, 2, "should have 2 badges")
9797+ require.Equal(t, "place.stream.badges.badge#mod", msg.Badges[0].BadgeType, "mod badge should be first")
9898+ require.Equal(t, "place.stream.badges.badge#vip", msg.Badges[1].BadgeType, "vip badge should be second")
9999+ })
100100+}
+8
pkg/atproto/sync.go
···150150 log.Error(ctx, "failed to convert chat message to streamplace message view", "err", err)
151151 return nil
152152 }
153153+154154+ // Add mod badge if the author is a moderator
155155+ issuerDID := fmt.Sprintf("did:web:%s", atsync.CLI.BroadcasterHost)
156156+ err = AddModBadgeIfApplicable(ctx, scm, rec.Streamer, issuerDID, atsync.Model)
157157+ if err != nil {
158158+ log.Error(ctx, "failed to add mod badge", "err", err)
159159+ }
160160+153161 go atsync.Bus.Publish(rec.Streamer, scm)
154162155163 if !isUpdate && !isFirstSync {
···11+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
22+33+// Lexicon schema: place.stream.badge.defs
44+55+package streamplace
66+77+// BadgeDefs_BadgeView is a "badgeView" in the place.stream.badge.defs schema.
88+//
99+// 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.
1010+type BadgeDefs_BadgeView struct {
1111+ BadgeType string `json:"badgeType" cborgen:"badgeType"`
1212+ // issuer: DID of the badge issuer.
1313+ Issuer string `json:"issuer" cborgen:"issuer"`
1414+ // recipient: DID of the badge recipient.
1515+ Recipient string `json:"recipient" cborgen:"recipient"`
1616+ // signature: TODO: Cryptographic signature of the badge (of a place.stream.key).
1717+ Signature *string `json:"signature,omitempty" cborgen:"signature,omitempty"`
1818+}
+28
pkg/streamplace/badgedisplay.go
···11+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
22+33+// Lexicon schema: place.stream.badge.display
44+55+package streamplace
66+77+import (
88+ lexutil "github.com/bluesky-social/indigo/lex/util"
99+)
1010+1111+func init() {
1212+ lexutil.RegisterType("place.stream.badge.display", &BadgeDisplay{})
1313+}
1414+1515+type BadgeDisplay struct {
1616+ LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.badge.display"`
1717+ // badges: Up to 3 badge tokens to display with the message. First badge is server-controlled, remaining badges are user-settable.
1818+ Badges []*BadgeDisplay_BadgeSelection `json:"badges" cborgen:"badges"`
1919+}
2020+2121+// BadgeDisplay_BadgeSelection is a "badgeSelection" in the place.stream.badge.display schema.
2222+//
2323+// 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.
2424+type BadgeDisplay_BadgeSelection struct {
2525+ BadgeType string `json:"badgeType" cborgen:"badgeType"`
2626+ // issuance: URI of the badge issuance record (place.stream.badge.issuance) that represents this badge. Required if badgeType is not recognized.
2727+ Issuance *string `json:"issuance,omitempty" cborgen:"issuance,omitempty"`
2828+}
+22
pkg/streamplace/badgeissuance.go
···11+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
22+33+// Lexicon schema: place.stream.badge.issuance
44+55+package streamplace
66+77+import (
88+ lexutil "github.com/bluesky-social/indigo/lex/util"
99+)
1010+1111+func init() {
1212+ lexutil.RegisterType("place.stream.badge.issuance", &BadgeIssuance{})
1313+}
1414+1515+type BadgeIssuance struct {
1616+ LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.badge.issuance"`
1717+ BadgeType string `json:"badgeType" cborgen:"badgeType"`
1818+ // recipient: DID of the badge recipient.
1919+ Recipient string `json:"recipient" cborgen:"recipient"`
2020+ // signature: TODO: Cryptographic signature of the badge (of a place.stream.key).
2121+ Signature string `json:"signature" cborgen:"signature"`
2222+}
+527
pkg/streamplace/cbor_gen.go
···5966596659675967 return nil
59685968}
59695969+func (t *BadgeIssuance) MarshalCBOR(w io.Writer) error {
59705970+ if t == nil {
59715971+ _, err := w.Write(cbg.CborNull)
59725972+ return err
59735973+ }
59745974+59755975+ cw := cbg.NewCborWriter(w)
59765976+59775977+ if _, err := cw.Write([]byte{164}); err != nil {
59785978+ return err
59795979+ }
59805980+59815981+ // t.LexiconTypeID (string) (string)
59825982+ if len("$type") > 1000000 {
59835983+ return xerrors.Errorf("Value in field \"$type\" was too long")
59845984+ }
59855985+59865986+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil {
59875987+ return err
59885988+ }
59895989+ if _, err := cw.WriteString(string("$type")); err != nil {
59905990+ return err
59915991+ }
59925992+59935993+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("place.stream.badge.issuance"))); err != nil {
59945994+ return err
59955995+ }
59965996+ if _, err := cw.WriteString(string("place.stream.badge.issuance")); err != nil {
59975997+ return err
59985998+ }
59995999+60006000+ // t.BadgeType (string) (string)
60016001+ if len("badgeType") > 1000000 {
60026002+ return xerrors.Errorf("Value in field \"badgeType\" was too long")
60036003+ }
60046004+60056005+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("badgeType"))); err != nil {
60066006+ return err
60076007+ }
60086008+ if _, err := cw.WriteString(string("badgeType")); err != nil {
60096009+ return err
60106010+ }
60116011+60126012+ if len(t.BadgeType) > 1000000 {
60136013+ return xerrors.Errorf("Value in field t.BadgeType was too long")
60146014+ }
60156015+60166016+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.BadgeType))); err != nil {
60176017+ return err
60186018+ }
60196019+ if _, err := cw.WriteString(string(t.BadgeType)); err != nil {
60206020+ return err
60216021+ }
60226022+60236023+ // t.Recipient (string) (string)
60246024+ if len("recipient") > 1000000 {
60256025+ return xerrors.Errorf("Value in field \"recipient\" was too long")
60266026+ }
60276027+60286028+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("recipient"))); err != nil {
60296029+ return err
60306030+ }
60316031+ if _, err := cw.WriteString(string("recipient")); err != nil {
60326032+ return err
60336033+ }
60346034+60356035+ if len(t.Recipient) > 1000000 {
60366036+ return xerrors.Errorf("Value in field t.Recipient was too long")
60376037+ }
60386038+60396039+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Recipient))); err != nil {
60406040+ return err
60416041+ }
60426042+ if _, err := cw.WriteString(string(t.Recipient)); err != nil {
60436043+ return err
60446044+ }
60456045+60466046+ // t.Signature (string) (string)
60476047+ if len("signature") > 1000000 {
60486048+ return xerrors.Errorf("Value in field \"signature\" was too long")
60496049+ }
60506050+60516051+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("signature"))); err != nil {
60526052+ return err
60536053+ }
60546054+ if _, err := cw.WriteString(string("signature")); err != nil {
60556055+ return err
60566056+ }
60576057+60586058+ if len(t.Signature) > 1000000 {
60596059+ return xerrors.Errorf("Value in field t.Signature was too long")
60606060+ }
60616061+60626062+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Signature))); err != nil {
60636063+ return err
60646064+ }
60656065+ if _, err := cw.WriteString(string(t.Signature)); err != nil {
60666066+ return err
60676067+ }
60686068+ return nil
60696069+}
60706070+60716071+func (t *BadgeIssuance) UnmarshalCBOR(r io.Reader) (err error) {
60726072+ *t = BadgeIssuance{}
60736073+60746074+ cr := cbg.NewCborReader(r)
60756075+60766076+ maj, extra, err := cr.ReadHeader()
60776077+ if err != nil {
60786078+ return err
60796079+ }
60806080+ defer func() {
60816081+ if err == io.EOF {
60826082+ err = io.ErrUnexpectedEOF
60836083+ }
60846084+ }()
60856085+60866086+ if maj != cbg.MajMap {
60876087+ return fmt.Errorf("cbor input should be of type map")
60886088+ }
60896089+60906090+ if extra > cbg.MaxLength {
60916091+ return fmt.Errorf("BadgeIssuance: map struct too large (%d)", extra)
60926092+ }
60936093+60946094+ n := extra
60956095+60966096+ nameBuf := make([]byte, 9)
60976097+ for i := uint64(0); i < n; i++ {
60986098+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
60996099+ if err != nil {
61006100+ return err
61016101+ }
61026102+61036103+ if !ok {
61046104+ // Field doesn't exist on this type, so ignore it
61056105+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
61066106+ return err
61076107+ }
61086108+ continue
61096109+ }
61106110+61116111+ switch string(nameBuf[:nameLen]) {
61126112+ // t.LexiconTypeID (string) (string)
61136113+ case "$type":
61146114+61156115+ {
61166116+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
61176117+ if err != nil {
61186118+ return err
61196119+ }
61206120+61216121+ t.LexiconTypeID = string(sval)
61226122+ }
61236123+ // t.BadgeType (string) (string)
61246124+ case "badgeType":
61256125+61266126+ {
61276127+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
61286128+ if err != nil {
61296129+ return err
61306130+ }
61316131+61326132+ t.BadgeType = string(sval)
61336133+ }
61346134+ // t.Recipient (string) (string)
61356135+ case "recipient":
61366136+61376137+ {
61386138+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
61396139+ if err != nil {
61406140+ return err
61416141+ }
61426142+61436143+ t.Recipient = string(sval)
61446144+ }
61456145+ // t.Signature (string) (string)
61466146+ case "signature":
61476147+61486148+ {
61496149+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
61506150+ if err != nil {
61516151+ return err
61526152+ }
61536153+61546154+ t.Signature = string(sval)
61556155+ }
61566156+61576157+ default:
61586158+ // Field doesn't exist on this type, so ignore it
61596159+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
61606160+ return err
61616161+ }
61626162+ }
61636163+ }
61646164+61656165+ return nil
61666166+}
61676167+func (t *BadgeDisplay) MarshalCBOR(w io.Writer) error {
61686168+ if t == nil {
61696169+ _, err := w.Write(cbg.CborNull)
61706170+ return err
61716171+ }
61726172+61736173+ cw := cbg.NewCborWriter(w)
61746174+61756175+ if _, err := cw.Write([]byte{162}); err != nil {
61766176+ return err
61776177+ }
61786178+61796179+ // t.LexiconTypeID (string) (string)
61806180+ if len("$type") > 1000000 {
61816181+ return xerrors.Errorf("Value in field \"$type\" was too long")
61826182+ }
61836183+61846184+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil {
61856185+ return err
61866186+ }
61876187+ if _, err := cw.WriteString(string("$type")); err != nil {
61886188+ return err
61896189+ }
61906190+61916191+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("place.stream.badge.display"))); err != nil {
61926192+ return err
61936193+ }
61946194+ if _, err := cw.WriteString(string("place.stream.badge.display")); err != nil {
61956195+ return err
61966196+ }
61976197+61986198+ // t.Badges ([]*streamplace.BadgeDisplay_BadgeSelection) (slice)
61996199+ if len("badges") > 1000000 {
62006200+ return xerrors.Errorf("Value in field \"badges\" was too long")
62016201+ }
62026202+62036203+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("badges"))); err != nil {
62046204+ return err
62056205+ }
62066206+ if _, err := cw.WriteString(string("badges")); err != nil {
62076207+ return err
62086208+ }
62096209+62106210+ if len(t.Badges) > 8192 {
62116211+ return xerrors.Errorf("Slice value in field t.Badges was too long")
62126212+ }
62136213+62146214+ if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Badges))); err != nil {
62156215+ return err
62166216+ }
62176217+ for _, v := range t.Badges {
62186218+ if err := v.MarshalCBOR(cw); err != nil {
62196219+ return err
62206220+ }
62216221+62226222+ }
62236223+ return nil
62246224+}
62256225+62266226+func (t *BadgeDisplay) UnmarshalCBOR(r io.Reader) (err error) {
62276227+ *t = BadgeDisplay{}
62286228+62296229+ cr := cbg.NewCborReader(r)
62306230+62316231+ maj, extra, err := cr.ReadHeader()
62326232+ if err != nil {
62336233+ return err
62346234+ }
62356235+ defer func() {
62366236+ if err == io.EOF {
62376237+ err = io.ErrUnexpectedEOF
62386238+ }
62396239+ }()
62406240+62416241+ if maj != cbg.MajMap {
62426242+ return fmt.Errorf("cbor input should be of type map")
62436243+ }
62446244+62456245+ if extra > cbg.MaxLength {
62466246+ return fmt.Errorf("BadgeDisplay: map struct too large (%d)", extra)
62476247+ }
62486248+62496249+ n := extra
62506250+62516251+ nameBuf := make([]byte, 6)
62526252+ for i := uint64(0); i < n; i++ {
62536253+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
62546254+ if err != nil {
62556255+ return err
62566256+ }
62576257+62586258+ if !ok {
62596259+ // Field doesn't exist on this type, so ignore it
62606260+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
62616261+ return err
62626262+ }
62636263+ continue
62646264+ }
62656265+62666266+ switch string(nameBuf[:nameLen]) {
62676267+ // t.LexiconTypeID (string) (string)
62686268+ case "$type":
62696269+62706270+ {
62716271+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
62726272+ if err != nil {
62736273+ return err
62746274+ }
62756275+62766276+ t.LexiconTypeID = string(sval)
62776277+ }
62786278+ // t.Badges ([]*streamplace.BadgeDisplay_BadgeSelection) (slice)
62796279+ case "badges":
62806280+62816281+ maj, extra, err = cr.ReadHeader()
62826282+ if err != nil {
62836283+ return err
62846284+ }
62856285+62866286+ if extra > 8192 {
62876287+ return fmt.Errorf("t.Badges: array too large (%d)", extra)
62886288+ }
62896289+62906290+ if maj != cbg.MajArray {
62916291+ return fmt.Errorf("expected cbor array")
62926292+ }
62936293+62946294+ if extra > 0 {
62956295+ t.Badges = make([]*BadgeDisplay_BadgeSelection, extra)
62966296+ }
62976297+62986298+ for i := 0; i < int(extra); i++ {
62996299+ {
63006300+ var maj byte
63016301+ var extra uint64
63026302+ var err error
63036303+ _ = maj
63046304+ _ = extra
63056305+ _ = err
63066306+63076307+ {
63086308+63096309+ b, err := cr.ReadByte()
63106310+ if err != nil {
63116311+ return err
63126312+ }
63136313+ if b != cbg.CborNull[0] {
63146314+ if err := cr.UnreadByte(); err != nil {
63156315+ return err
63166316+ }
63176317+ t.Badges[i] = new(BadgeDisplay_BadgeSelection)
63186318+ if err := t.Badges[i].UnmarshalCBOR(cr); err != nil {
63196319+ return xerrors.Errorf("unmarshaling t.Badges[i] pointer: %w", err)
63206320+ }
63216321+ }
63226322+63236323+ }
63246324+63256325+ }
63266326+ }
63276327+63286328+ default:
63296329+ // Field doesn't exist on this type, so ignore it
63306330+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
63316331+ return err
63326332+ }
63336333+ }
63346334+ }
63356335+63366336+ return nil
63376337+}
63386338+func (t *BadgeDisplay_BadgeSelection) MarshalCBOR(w io.Writer) error {
63396339+ if t == nil {
63406340+ _, err := w.Write(cbg.CborNull)
63416341+ return err
63426342+ }
63436343+63446344+ cw := cbg.NewCborWriter(w)
63456345+ fieldCount := 2
63466346+63476347+ if t.Issuance == nil {
63486348+ fieldCount--
63496349+ }
63506350+63516351+ if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
63526352+ return err
63536353+ }
63546354+63556355+ // t.Issuance (string) (string)
63566356+ if t.Issuance != nil {
63576357+63586358+ if len("issuance") > 1000000 {
63596359+ return xerrors.Errorf("Value in field \"issuance\" was too long")
63606360+ }
63616361+63626362+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("issuance"))); err != nil {
63636363+ return err
63646364+ }
63656365+ if _, err := cw.WriteString(string("issuance")); err != nil {
63666366+ return err
63676367+ }
63686368+63696369+ if t.Issuance == nil {
63706370+ if _, err := cw.Write(cbg.CborNull); err != nil {
63716371+ return err
63726372+ }
63736373+ } else {
63746374+ if len(*t.Issuance) > 1000000 {
63756375+ return xerrors.Errorf("Value in field t.Issuance was too long")
63766376+ }
63776377+63786378+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Issuance))); err != nil {
63796379+ return err
63806380+ }
63816381+ if _, err := cw.WriteString(string(*t.Issuance)); err != nil {
63826382+ return err
63836383+ }
63846384+ }
63856385+ }
63866386+63876387+ // t.BadgeType (string) (string)
63886388+ if len("badgeType") > 1000000 {
63896389+ return xerrors.Errorf("Value in field \"badgeType\" was too long")
63906390+ }
63916391+63926392+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("badgeType"))); err != nil {
63936393+ return err
63946394+ }
63956395+ if _, err := cw.WriteString(string("badgeType")); err != nil {
63966396+ return err
63976397+ }
63986398+63996399+ if len(t.BadgeType) > 1000000 {
64006400+ return xerrors.Errorf("Value in field t.BadgeType was too long")
64016401+ }
64026402+64036403+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.BadgeType))); err != nil {
64046404+ return err
64056405+ }
64066406+ if _, err := cw.WriteString(string(t.BadgeType)); err != nil {
64076407+ return err
64086408+ }
64096409+ return nil
64106410+}
64116411+64126412+func (t *BadgeDisplay_BadgeSelection) UnmarshalCBOR(r io.Reader) (err error) {
64136413+ *t = BadgeDisplay_BadgeSelection{}
64146414+64156415+ cr := cbg.NewCborReader(r)
64166416+64176417+ maj, extra, err := cr.ReadHeader()
64186418+ if err != nil {
64196419+ return err
64206420+ }
64216421+ defer func() {
64226422+ if err == io.EOF {
64236423+ err = io.ErrUnexpectedEOF
64246424+ }
64256425+ }()
64266426+64276427+ if maj != cbg.MajMap {
64286428+ return fmt.Errorf("cbor input should be of type map")
64296429+ }
64306430+64316431+ if extra > cbg.MaxLength {
64326432+ return fmt.Errorf("BadgeDisplay_BadgeSelection: map struct too large (%d)", extra)
64336433+ }
64346434+64356435+ n := extra
64366436+64376437+ nameBuf := make([]byte, 9)
64386438+ for i := uint64(0); i < n; i++ {
64396439+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
64406440+ if err != nil {
64416441+ return err
64426442+ }
64436443+64446444+ if !ok {
64456445+ // Field doesn't exist on this type, so ignore it
64466446+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
64476447+ return err
64486448+ }
64496449+ continue
64506450+ }
64516451+64526452+ switch string(nameBuf[:nameLen]) {
64536453+ // t.Issuance (string) (string)
64546454+ case "issuance":
64556455+64566456+ {
64576457+ b, err := cr.ReadByte()
64586458+ if err != nil {
64596459+ return err
64606460+ }
64616461+ if b != cbg.CborNull[0] {
64626462+ if err := cr.UnreadByte(); err != nil {
64636463+ return err
64646464+ }
64656465+64666466+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
64676467+ if err != nil {
64686468+ return err
64696469+ }
64706470+64716471+ t.Issuance = (*string)(&sval)
64726472+ }
64736473+ }
64746474+ // t.BadgeType (string) (string)
64756475+ case "badgeType":
64766476+64776477+ {
64786478+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
64796479+ if err != nil {
64806480+ return err
64816481+ }
64826482+64836483+ t.BadgeType = string(sval)
64846484+ }
64856485+64866486+ default:
64876487+ // Field doesn't exist on this type, so ignore it
64886488+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
64896489+ return err
64906490+ }
64916491+ }
64926492+ }
64936493+64946494+ return nil
64956495+}
+4-2
pkg/streamplace/chatdefs.go
···1616type ChatDefs_MessageView struct {
1717 LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.chat.defs#messageView"`
1818 Author *appbsky.ActorDefs_ProfileViewBasic `json:"author" cborgen:"author"`
1919- ChatProfile *ChatProfile `json:"chatProfile,omitempty" cborgen:"chatProfile,omitempty"`
2020- Cid string `json:"cid" cborgen:"cid"`
1919+ // 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.
2020+ Badges []*BadgeDefs_BadgeView `json:"badges,omitempty" cborgen:"badges,omitempty"`
2121+ ChatProfile *ChatProfile `json:"chatProfile,omitempty" cborgen:"chatProfile,omitempty"`
2222+ Cid string `json:"cid" cborgen:"cid"`
2123 // deleted: If true, this message has been deleted or labeled and should be cleared from the cache
2224 Deleted *bool `json:"deleted,omitempty" cborgen:"deleted,omitempty"`
2325 IndexedAt string `json:"indexedAt" cborgen:"indexedAt"`