diff --git a/js/components/assets/badges/robot.png b/js/components/assets/badges/robot.png new file mode 100644 index 0000000000000000000000000000000000000000..10a87a047602c4fd37a5fbbe5dcb0c9c37d7b4da GIT binary patch literal 338 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANNflAbP(ArbCxr|%X#WWdul zSu$e=L;e9dlP@3Hd=9aB&Tq55q2S|in5Use{al%febZZ&a?i}yYy9^=Ik8InQTDgx z=3D>0kNUpn>Nm+_PI||;1)CcNTG#E<84g)40#2!U(5D~-2LfwZE<$L_JgXa z3HI+hzU#VKw2K3WJrnlZ)lUp7z9mXptiv z3R{a?9x3oN#GH#2P-d9^Cg{|v-01YE8nXp0^STlkEF|ptl#Zk^-w=sd&s4x%6RvY4 zZ4;B{zmxZ!r|~mPT$bguEi1%6BFxz7(G_3KRkF7QR(AE*PFSHHwk+@rJOAO6e~KA{ sriC4pIP2v+;qc0N>tnJ49{*%#o&Gm{lFLdH1_lNOPgg&ebxsLQ0A59l`2YX_ literal 0 HcmV?d00001 diff --git a/js/components/assets/badges/robot_2x.png b/js/components/assets/badges/robot_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ea85e8c2dfbc381fe0361678e8afc1baa1bc78a4 GIT binary patch literal 538 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hE}y(fA)l+l<>U-nY%%(D0A`-}yrU0lET8H0xI1A*Iit`(vebQrd>zSBA!dT1`wEY=0z4%B-6 z2xnl>SaZI8Rox5At4taPqJACk-`?8u@`Iex(`S`$EG~MlJ0{CuaPDt}TJmv6Mkkg{ zOBv2;hj4dPuG?#H&$^f)A(n-+D9^9>ZP3i+AD?lp;A^=2dAisSwYs?T`bztgSWUPa zq$}=!S6Z^R_j%>MO4e}B999$V1J{_#r>cfJ1u&jv3W$2J(d&a21H+oXcH3_8AKoy* zs4qE)+2gCT>A~xwOz=YRtobjn`6`3%vn_ud7y}O6 zSK`!sa8o_tzx;x!yQAs?n8A=`a~We4^OALsw){!aZ;BGW{^_#;!z43C5q9Q=JGlW9 zZA8NZ99A)PwB1;1C@Q5qr(i!n%XOx+Ob(6Hx7nUco%iVQbj5d^6W3}xp0bH%T)L#m zlEF?viEGPCfp + +### `selfLabel` + +**Type:** `string` + +Label that a user can apply to their own profile. + +**Constraints:**
Known Values: `bot` --- @@ -61,10 +74,24 @@ Customizations for the color of a user's name in chat "color": { "type": "ref", "ref": "#color" + }, + "selfLabels": { + "type": "array", + "description": "Self-applied labels for this profile, e.g. 'bot'.", + "maxLength": 10, + "items": { + "type": "ref", + "ref": "#selfLabel" + } } } } }, + "selfLabel": { + "type": "string", + "description": "Label that a user can apply to their own profile.", + "knownValues": ["bot"] + }, "color": { "type": "object", "description": "Customizations for the color of a user's name in chat", diff --git a/lexicons/place/stream/badge/defs.json b/lexicons/place/stream/badge/defs.json index 30845db2..b676f7a3 100644 --- a/lexicons/place/stream/badge/defs.json +++ b/lexicons/place/stream/badge/defs.json @@ -11,7 +11,8 @@ "type": "string", "knownValues": [ "place.stream.badge.defs#mod", - "place.stream.badge.defs#streamer" + "place.stream.badge.defs#streamer", + "place.stream.badge.defs#bot" ] }, "issuer": { @@ -41,6 +42,10 @@ "vip": { "type": "token", "description": "This user is a very important person." + }, + "bot": { + "type": "token", + "description": "This user is a bot. Self-applied via place.stream.chat.profile selfLabels." } } } diff --git a/lexicons/place/stream/chat/profile.json b/lexicons/place/stream/chat/profile.json index 7d3583f8..c9526eb6 100644 --- a/lexicons/place/stream/chat/profile.json +++ b/lexicons/place/stream/chat/profile.json @@ -13,10 +13,24 @@ "color": { "type": "ref", "ref": "#color" + }, + "selfLabels": { + "type": "array", + "description": "Self-applied labels for this profile, e.g. 'bot'.", + "maxLength": 10, + "items": { + "type": "ref", + "ref": "#selfLabel" + } } } } }, + "selfLabel": { + "type": "string", + "description": "Label that a user can apply to their own profile.", + "knownValues": ["bot"] + }, "color": { "type": "object", "description": "Customizations for the color of a user's name in chat", diff --git a/pkg/atproto/badges.go b/pkg/atproto/badges.go index a45f7ca0..eafb57aa 100644 --- a/pkg/atproto/badges.go +++ b/pkg/atproto/badges.go @@ -13,6 +13,7 @@ import ( // and adds a mod or streamer badge as the first badge (server-controlled). // - If the author is the streamer, adds a "streamer" badge // - If the author has moderation permissions, adds a "mod" badge +// - If the author self-labels as a bot, adds a "bot" badge func AddModBadgeIfApplicable(ctx context.Context, message *streamplace.ChatDefs_MessageView, streamerDID string, issuerDID string, m model.Model) error { if message == nil { return fmt.Errorf("message is nil") diff --git a/pkg/badges/badges.go b/pkg/badges/badges.go index dc2330d1..b6c0cedf 100644 --- a/pkg/badges/badges.go +++ b/pkg/badges/badges.go @@ -2,6 +2,7 @@ package badges import ( "context" + "slices" "stream.place/streamplace/pkg/constants" "stream.place/streamplace/pkg/log" @@ -45,6 +46,26 @@ func GetValidBadges(ctx context.Context, userDID, streamerDID, issuerDID string, }) } + // if user "self-labels" as a bot (in chat profile), add bot badge + chatProfile, err := m.GetChatProfile(ctx, userDID) + if err != nil { + log.Error(ctx, "failed to get chat profile", "err", err, "userDID", userDID) + } + spChatProfile, err := chatProfile.ToStreamplaceChatProfile() + + if err != nil { + log.Error(ctx, "failed to convert chat profile to streamplace format", "err", err, "userDID", userDID) + } + + botLabel := constants.SelfLabelBot + if spChatProfile != nil && slices.Contains(spChatProfile.SelfLabels, &botLabel) { + badges = append(badges, &streamplace.BadgeDefs_BadgeView{ + BadgeType: constants.BadgeTypeBot, + Issuer: issuerDID, + Recipient: userDID, + }) + } + // TODO: Add badge issuance records when implemented // - Query place.stream.badge.issuance records for this user // - Verify signatures if issuer is not the current node diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index b4fd24a3..195b1363 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -21,10 +21,13 @@ var PLACE_STREAM_LIVE_VIEWERCOUNT = "place.stream.live.viewerCount" // // Streamplace badge types const ( BadgeTypeMod = "place.stream.badge.defs#mod" + BadgeTypeBot = "place.stream.badge.defs#bot" BadgeTypeStreamer = "place.stream.badge.defs#streamer" BadgeTypeVIP = "place.stream.badge.defs#vip" ) +const SelfLabelBot = "bot" //nolint:all + const DID_KEY_PREFIX = "did:key" //nolint:all const ADDRESS_KEY_PREFIX = "0x" //nolint:all diff --git a/pkg/streamplace/cbor_gen.go b/pkg/streamplace/cbor_gen.go index bdecd69a..83fc9673 100644 --- a/pkg/streamplace/cbor_gen.go +++ b/pkg/streamplace/cbor_gen.go @@ -2863,12 +2863,16 @@ func (t *ChatProfile) MarshalCBOR(w io.Writer) error { } cw := cbg.NewCborWriter(w) - fieldCount := 2 + fieldCount := 3 if t.Color == nil { fieldCount-- } + if t.SelfLabels == nil { + fieldCount-- + } + if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { return err } @@ -2910,6 +2914,48 @@ func (t *ChatProfile) MarshalCBOR(w io.Writer) error { return err } } + + // t.SelfLabels ([]*string) (slice) + if t.SelfLabels != nil { + + if len("selfLabels") > 1000000 { + return xerrors.Errorf("Value in field \"selfLabels\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("selfLabels"))); err != nil { + return err + } + if _, err := cw.WriteString(string("selfLabels")); err != nil { + return err + } + + if len(t.SelfLabels) > 8192 { + return xerrors.Errorf("Slice value in field t.SelfLabels was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.SelfLabels))); err != nil { + return err + } + for _, v := range t.SelfLabels { + if v == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if len(*v) > 1000000 { + return xerrors.Errorf("Value in field v was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*v))); err != nil { + return err + } + if _, err := cw.WriteString(string(*v)); err != nil { + return err + } + } + + } + } return nil } @@ -2938,7 +2984,7 @@ func (t *ChatProfile) UnmarshalCBOR(r io.Reader) (err error) { n := extra - nameBuf := make([]byte, 5) + nameBuf := make([]byte, 10) for i := uint64(0); i < n; i++ { nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000) if err != nil { @@ -2985,6 +3031,56 @@ func (t *ChatProfile) UnmarshalCBOR(r io.Reader) (err error) { } } + // t.SelfLabels ([]*string) (slice) + case "selfLabels": + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > 8192 { + return fmt.Errorf("t.SelfLabels: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.SelfLabels = make([]*string, extra) + } + + for i := 0; i < int(extra); i++ { + { + var maj byte + var extra uint64 + var err error + _ = maj + _ = extra + _ = err + + { + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + sval, err := cbg.ReadStringWithMax(cr, 1000000) + if err != nil { + return err + } + + t.SelfLabels[i] = (*string)(&sval) + } + } + + } + } default: // Field doesn't exist on this type, so ignore it diff --git a/pkg/streamplace/chatprofile.go b/pkg/streamplace/chatprofile.go index 88a88e14..dadcbd66 100644 --- a/pkg/streamplace/chatprofile.go +++ b/pkg/streamplace/chatprofile.go @@ -15,6 +15,8 @@ func init() { type ChatProfile struct { LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.chat.profile"` Color *ChatProfile_Color `json:"color,omitempty" cborgen:"color,omitempty"` + // selfLabels: Self-applied labels for this profile, e.g. 'bot'. + SelfLabels []*string `json:"selfLabels,omitempty" cborgen:"selfLabels,omitempty"` } // ChatProfile_Color is a "color" in the place.stream.chat.profile schema.