···388388 // Clear error when user starts typing
389389 const handleDIDChange = (text: string) => {
390390 setModeratorDID(text);
391391- if (error) {
392392- setError(null);
393393- }
391391+ if (error) setError(null);
394392 };
395393396394 const handleAdd = async () => {
···430428 <ResponsiveDialog
431429 open={visible}
432430 onOpenChange={(open) => {
433433- if (!open) {
434434- onClose();
435435- }
431431+ if (!open) onClose();
436432 }}
437433 title="Add Moderator"
438434 description="Enter the DID or handle of the user you want to add as a moderator and select their permissions."
···11+---
22+title: badges system
33+description: user badges for chat messages
44+---
55+66+## Overview
77+88+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.
99+1010+## Lexicon schemas
1111+1212+We have three relevant lexicons.
1313+1414+1. **`place.stream.badge.defs`** - badge definitions and view model
1515+1616+ - defines known badge types: `mod`, `streamer`, `vip`
1717+ - `badgeView` object: `{badgeType, issuer, recipient, signature?}`
1818+1919+2. **`place.stream.badge.issuance`** - record of badge grant
2020+2121+ - stored as atproto record (key: tid)
2222+ - issued by streamer or other authorized entity
2323+ - example: streamer issues vip badge to a user
2424+2525+3. **`place.stream.badge.display`** - user's badge selection
2626+ - user-controlled record defining which badges to show
2727+ - array of up to 3 `badgeSelection` objects
2828+ - first slot server-controlled (mod/streamer/staff), second slot is streamer-specific (vip, subscription), third slot is user-set (event, staff2, node subscription, etc.)
2929+3030+:::note
3131+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?
3232+:::
3333+3434+## TODO
3535+3636+- [ ] implement cryptographic signatures for badge issuance
3737+- [ ] implement badge issuance ui (streamer grants vip badges)
3838+- [ ] implement badge selection ui (users choose which badges to display)
3939+- [ ] add more badge types (subscriber, founder, staff, etc)
···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 }
···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"`
+1-15
pnpm-lock.yaml
···4242 prettier:
4343 specifier: 3.4.2
4444 version: 3.4.2
4545- prettier-plugin-curly:
4646- specifier: ^0.4.1
4747- version: 0.4.1(prettier@3.4.2)
4845 typescript:
4946 specifier: ^5.8.3
5047 version: 5.8.3
···78227819 glob@8.1.0:
78237820 resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
78247821 engines: {node: '>=12'}
78257825- deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
78227822+ deprecated: Glob versions prior to v9 are no longer supported
7826782378277824 glob@9.3.5:
78287825 resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==}
78297826 engines: {node: '>=16 || 14 >=14.17'}
78307830- deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
7831782778327828 global-agent@3.0.0:
78337829 resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
···1064510641 prelude-ls@1.2.1:
1064610642 resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
1064710643 engines: {node: '>= 0.8.0'}
1064810648-1064910649- prettier-plugin-curly@0.4.1:
1065010650- resolution: {integrity: sha512-Xc7zatoD0/08zYFv+hwnlqT5ekM81DCbBr73CWAsr1Fmx7qLQT/M0wfPx6w/+zfnmXH009xYvjzLUPcwzq7Fbw==}
1065110651- engines: {node: '>=18'}
1065210652- peerDependencies:
1065310653- prettier: ^3
10654106441065510645 prettier-plugin-organize-imports@4.1.0:
1065610646 resolution: {integrity: sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==}
···2694326933 tunnel-agent: 0.6.0
26944269342694526935 prelude-ls@1.2.1: {}
2694626946-2694726947- prettier-plugin-curly@0.4.1(prettier@3.4.2):
2694826948- dependencies:
2694926949- prettier: 3.4.2
26950269362695126937 prettier-plugin-organize-imports@4.1.0(prettier@3.4.2)(typescript@5.8.3):
2695226938 dependencies: