nip29: livekit group live participants.

This commit is contained in:
fiatjaf
2026-03-11 16:06:30 -03:00
parent 681bd55e55
commit 3bd059d1f9
4 changed files with 223 additions and 158 deletions
+135 -132
View File
@@ -246,6 +246,8 @@ func (kind Kind) Name() string {
return "SimpleGroupMembers" return "SimpleGroupMembers"
case KindSimpleGroupRoles: case KindSimpleGroupRoles:
return "SimpleGroupRoles" return "SimpleGroupRoles"
case KindSimpleGroupLiveKitParticipants:
return "SimpleGroupLiveKitParticipants"
case KindWikiArticle: case KindWikiArticle:
return "WikiArticle" return "WikiArticle"
case KindRedirects: case KindRedirects:
@@ -277,138 +279,139 @@ func (kind Kind) Name() string {
} }
const ( const (
KindProfileMetadata Kind = 0 KindProfileMetadata Kind = 0
KindTextNote Kind = 1 KindTextNote Kind = 1
KindRecommendServer Kind = 2 KindRecommendServer Kind = 2
KindFollowList Kind = 3 KindFollowList Kind = 3
KindEncryptedDirectMessage Kind = 4 KindEncryptedDirectMessage Kind = 4
KindDeletion Kind = 5 KindDeletion Kind = 5
KindRepost Kind = 6 KindRepost Kind = 6
KindReaction Kind = 7 KindReaction Kind = 7
KindBadgeAward Kind = 8 KindBadgeAward Kind = 8
KindSimpleGroupChatMessage Kind = 9 KindSimpleGroupChatMessage Kind = 9
KindSimpleGroupThreadedReply Kind = 10 KindSimpleGroupThreadedReply Kind = 10
KindSimpleGroupThread Kind = 11 KindSimpleGroupThread Kind = 11
KindSimpleGroupReply Kind = 12 KindSimpleGroupReply Kind = 12
KindSeal Kind = 13 KindSeal Kind = 13
KindDirectMessage Kind = 14 KindDirectMessage Kind = 14
KindGenericRepost Kind = 16 KindGenericRepost Kind = 16
KindReactionToWebsite Kind = 17 KindReactionToWebsite Kind = 17
KindChannelCreation Kind = 40 KindChannelCreation Kind = 40
KindChannelMetadata Kind = 41 KindChannelMetadata Kind = 41
KindChannelMessage Kind = 42 KindChannelMessage Kind = 42
KindChannelHideMessage Kind = 43 KindChannelHideMessage Kind = 43
KindChannelMuteUser Kind = 44 KindChannelMuteUser Kind = 44
KindChess Kind = 64 KindChess Kind = 64
KindMergeRequests Kind = 818 KindMergeRequests Kind = 818
KindComment Kind = 1111 KindComment Kind = 1111
KindBid Kind = 1021 KindBid Kind = 1021
KindBidConfirmation Kind = 1022 KindBidConfirmation Kind = 1022
KindOpenTimestamps Kind = 1040 KindOpenTimestamps Kind = 1040
KindGiftWrap Kind = 1059 KindGiftWrap Kind = 1059
KindFileMetadata Kind = 1063 KindFileMetadata Kind = 1063
KindLiveChatMessage Kind = 1311 KindLiveChatMessage Kind = 1311
KindPatch Kind = 1617 KindPatch Kind = 1617
KindIssue Kind = 1621 KindIssue Kind = 1621
KindReply Kind = 1622 KindReply Kind = 1622
KindStatusOpen Kind = 1630 KindStatusOpen Kind = 1630
KindStatusApplied Kind = 1631 KindStatusApplied Kind = 1631
KindStatusClosed Kind = 1632 KindStatusClosed Kind = 1632
KindStatusDraft Kind = 1633 KindStatusDraft Kind = 1633
KindProblemTracker Kind = 1971 KindProblemTracker Kind = 1971
KindReporting Kind = 1984 KindReporting Kind = 1984
KindLabel Kind = 1985 KindLabel Kind = 1985
KindRelayReviews Kind = 1986 KindRelayReviews Kind = 1986
KindAIEmbeddings Kind = 1987 KindAIEmbeddings Kind = 1987
KindTorrent Kind = 2003 KindTorrent Kind = 2003
KindTorrentComment Kind = 2004 KindTorrentComment Kind = 2004
KindCoinjoinPool Kind = 2022 KindCoinjoinPool Kind = 2022
KindCommunityPostApproval Kind = 4550 KindCommunityPostApproval Kind = 4550
KindJobFeedback Kind = 7000 KindJobFeedback Kind = 7000
KindSimpleGroupPutUser Kind = 9000 KindSimpleGroupPutUser Kind = 9000
KindSimpleGroupRemoveUser Kind = 9001 KindSimpleGroupRemoveUser Kind = 9001
KindSimpleGroupEditMetadata Kind = 9002 KindSimpleGroupEditMetadata Kind = 9002
KindSimpleGroupDeleteEvent Kind = 9005 KindSimpleGroupDeleteEvent Kind = 9005
KindSimpleGroupCreateGroup Kind = 9007 KindSimpleGroupCreateGroup Kind = 9007
KindSimpleGroupDeleteGroup Kind = 9008 KindSimpleGroupDeleteGroup Kind = 9008
KindSimpleGroupCreateInvite Kind = 9009 KindSimpleGroupCreateInvite Kind = 9009
KindSimpleGroupJoinRequest Kind = 9021 KindSimpleGroupJoinRequest Kind = 9021
KindSimpleGroupLeaveRequest Kind = 9022 KindSimpleGroupLeaveRequest Kind = 9022
KindZapGoal Kind = 9041 KindZapGoal Kind = 9041
KindNutZap Kind = 9321 KindNutZap Kind = 9321
KindTidalLogin Kind = 9467 KindTidalLogin Kind = 9467
KindZapRequest Kind = 9734 KindZapRequest Kind = 9734
KindZap Kind = 9735 KindZap Kind = 9735
KindHighlights Kind = 9802 KindHighlights Kind = 9802
KindMuteList Kind = 10000 KindMuteList Kind = 10000
KindPinList Kind = 10001 KindPinList Kind = 10001
KindRelayListMetadata Kind = 10002 KindRelayListMetadata Kind = 10002
KindBookmarkList Kind = 10003 KindBookmarkList Kind = 10003
KindCommunityList Kind = 10004 KindCommunityList Kind = 10004
KindPublicChatList Kind = 10005 KindPublicChatList Kind = 10005
KindBlockedRelayList Kind = 10006 KindBlockedRelayList Kind = 10006
KindSearchRelayList Kind = 10007 KindSearchRelayList Kind = 10007
KindSimpleGroupList Kind = 10009 KindSimpleGroupList Kind = 10009
KindInterestList Kind = 10015 KindInterestList Kind = 10015
KindNutZapInfo Kind = 10019 KindNutZapInfo Kind = 10019
KindEmojiList Kind = 10030 KindEmojiList Kind = 10030
KindDMRelayList Kind = 10050 KindDMRelayList Kind = 10050
KindUserServerList Kind = 10063 KindUserServerList Kind = 10063
KindFileStorageServerList Kind = 10096 KindFileStorageServerList Kind = 10096
KindGoodWikiAuthorList Kind = 10101 KindGoodWikiAuthorList Kind = 10101
KindGoodWikiRelayList Kind = 10102 KindGoodWikiRelayList Kind = 10102
KindNWCWalletInfo Kind = 13194 KindNWCWalletInfo Kind = 13194
KindLightningPubRPC Kind = 21000 KindLightningPubRPC Kind = 21000
KindClientAuthentication Kind = 22242 KindClientAuthentication Kind = 22242
KindNWCWalletRequest Kind = 23194 KindNWCWalletRequest Kind = 23194
KindNWCWalletResponse Kind = 23195 KindNWCWalletResponse Kind = 23195
KindNostrConnect Kind = 24133 KindNostrConnect Kind = 24133
KindBlobs Kind = 24242 KindBlobs Kind = 24242
KindHTTPAuth Kind = 27235 KindHTTPAuth Kind = 27235
KindCategorizedPeopleList Kind = 30000 KindCategorizedPeopleList Kind = 30000
KindCategorizedBookmarksList Kind = 30001 KindCategorizedBookmarksList Kind = 30001
KindRelaySets Kind = 30002 KindRelaySets Kind = 30002
KindBookmarkSets Kind = 30003 KindBookmarkSets Kind = 30003
KindCuratedSets Kind = 30004 KindCuratedSets Kind = 30004
KindCuratedVideoSets Kind = 30005 KindCuratedVideoSets Kind = 30005
KindMuteSets Kind = 30007 KindMuteSets Kind = 30007
KindProfileBadges Kind = 30008 KindProfileBadges Kind = 30008
KindBadgeDefinition Kind = 30009 KindBadgeDefinition Kind = 30009
KindInterestSets Kind = 30015 KindInterestSets Kind = 30015
KindStallDefinition Kind = 30017 KindStallDefinition Kind = 30017
KindProductDefinition Kind = 30018 KindProductDefinition Kind = 30018
KindMarketplaceUI Kind = 30019 KindMarketplaceUI Kind = 30019
KindProductSoldAsAuction Kind = 30020 KindProductSoldAsAuction Kind = 30020
KindArticle Kind = 30023 KindArticle Kind = 30023
KindDraftArticle Kind = 30024 KindDraftArticle Kind = 30024
KindEmojiSets Kind = 30030 KindEmojiSets Kind = 30030
KindModularArticleHeader Kind = 30040 KindModularArticleHeader Kind = 30040
KindModularArticleContent Kind = 30041 KindModularArticleContent Kind = 30041
KindReleaseArtifactSets Kind = 30063 KindReleaseArtifactSets Kind = 30063
KindApplicationSpecificData Kind = 30078 KindApplicationSpecificData Kind = 30078
KindLiveEvent Kind = 30311 KindLiveEvent Kind = 30311
KindUserStatuses Kind = 30315 KindUserStatuses Kind = 30315
KindClassifiedListing Kind = 30402 KindClassifiedListing Kind = 30402
KindDraftClassifiedListing Kind = 30403 KindDraftClassifiedListing Kind = 30403
KindRepositoryAnnouncement Kind = 30617 KindRepositoryAnnouncement Kind = 30617
KindRepositoryState Kind = 30618 KindRepositoryState Kind = 30618
KindSimpleGroupMetadata Kind = 39000 KindSimpleGroupMetadata Kind = 39000
KindSimpleGroupAdmins Kind = 39001 KindSimpleGroupAdmins Kind = 39001
KindSimpleGroupMembers Kind = 39002 KindSimpleGroupMembers Kind = 39002
KindSimpleGroupRoles Kind = 39003 KindSimpleGroupRoles Kind = 39003
KindWikiArticle Kind = 30818 KindSimpleGroupLiveKitParticipants Kind = 39004
KindRedirects Kind = 30819 KindWikiArticle Kind = 30818
KindFeed Kind = 31890 KindRedirects Kind = 30819
KindDateCalendarEvent Kind = 31922 KindFeed Kind = 31890
KindTimeCalendarEvent Kind = 31923 KindDateCalendarEvent Kind = 31922
KindCalendar Kind = 31924 KindTimeCalendarEvent Kind = 31923
KindCalendarEventRSVP Kind = 31925 KindCalendar Kind = 31924
KindHandlerRecommendation Kind = 31989 KindCalendarEventRSVP Kind = 31925
KindHandlerInformation Kind = 31990 KindHandlerRecommendation Kind = 31989
KindVideoEvent Kind = 34235 KindHandlerInformation Kind = 31990
KindShortVideoEvent Kind = 34236 KindVideoEvent Kind = 34235
KindVideoViewEvent Kind = 34237 KindShortVideoEvent Kind = 34236
KindCommunityDefinition Kind = 34550 KindVideoViewEvent Kind = 34237
KindCommunityDefinition Kind = 34550
) )
func (kind Kind) IsRegular() bool { func (kind Kind) IsRegular() bool {
+78 -20
View File
@@ -39,10 +39,11 @@ func ParseGroupAddress(raw string) (GroupAddress, error) {
type Group struct { type Group struct {
Address GroupAddress Address GroupAddress
Name string Name string
Picture string Picture string
About string About string
Members map[nostr.PubKey][]*Role Members map[nostr.PubKey][]*Role
LiveKitParticipants map[nostr.PubKey]string
// indicates that only members can read group messages // indicates that only members can read group messages
Private bool Private bool
@@ -57,7 +58,7 @@ type Group struct {
Hidden bool Hidden bool
// indicates that the group supports audio/video live chat // indicates that the group supports audio/video live chat
Livekit bool LiveKit bool
// indicates which event kinds this group supports // indicates which event kinds this group supports
SupportedKinds []nostr.Kind SupportedKinds []nostr.Kind
@@ -65,10 +66,11 @@ type Group struct {
Roles []*Role Roles []*Role
InviteCodes []string InviteCodes []string
LastMetadataUpdate nostr.Timestamp LastMetadataUpdate nostr.Timestamp
LastAdminsUpdate nostr.Timestamp LastAdminsUpdate nostr.Timestamp
LastMembersUpdate nostr.Timestamp LastMembersUpdate nostr.Timestamp
LastRolesUpdate nostr.Timestamp LastRolesUpdate nostr.Timestamp
LastLiveKitParticipantsUpdate nostr.Timestamp
} }
func (group Group) String() string { func (group Group) String() string {
@@ -90,9 +92,9 @@ func (group Group) String() string {
maybeClosed = " closed" maybeClosed = " closed"
} }
maybeLivekit := "" maybeLiveKit := ""
if group.Livekit { if group.LiveKit {
maybeLivekit = " livekit" maybeLiveKit = " livekit"
} }
members := make([]string, len(group.Members)) members := make([]string, len(group.Members))
@@ -120,7 +122,7 @@ func (group Group) String() string {
maybeRestricted, maybeRestricted,
maybeHidden, maybeHidden,
maybeClosed, maybeClosed,
maybeLivekit, maybeLiveKit,
group.Picture, group.Picture,
group.About, group.About,
strings.Join(members, " "), strings.Join(members, " "),
@@ -135,9 +137,10 @@ func NewGroup(gadstr string) (Group, error) {
} }
return Group{ return Group{
Address: gad, Address: gad,
Name: gad.ID, Name: gad.ID,
Members: make(map[nostr.PubKey][]*Role), Members: make(map[nostr.PubKey][]*Role),
LiveKitParticipants: make(map[nostr.PubKey]string),
}, nil }, nil
} }
@@ -147,8 +150,9 @@ func NewGroupFromMetadataEvent(relayURL string, evt *nostr.Event) (Group, error)
Relay: relayURL, Relay: relayURL,
ID: evt.Tags.GetD(), ID: evt.Tags.GetD(),
}, },
Name: evt.Tags.GetD(), Name: evt.Tags.GetD(),
Members: make(map[nostr.PubKey][]*Role), Members: make(map[nostr.PubKey][]*Role),
LiveKitParticipants: make(map[nostr.PubKey]string),
} }
err := g.MergeInMetadataEvent(evt) err := g.MergeInMetadataEvent(evt)
@@ -186,7 +190,7 @@ func (group Group) ToMetadataEvent() nostr.Event {
if group.Closed { if group.Closed {
evt.Tags = append(evt.Tags, nostr.Tag{"closed"}) evt.Tags = append(evt.Tags, nostr.Tag{"closed"})
} }
if group.Livekit { if group.LiveKit {
evt.Tags = append(evt.Tags, nostr.Tag{"livekit"}) evt.Tags = append(evt.Tags, nostr.Tag{"livekit"})
} }
@@ -261,6 +265,27 @@ func (group Group) ToRolesEvent() nostr.Event {
return evt return evt
} }
func (group Group) ToLiveKitParticipantsEvent() nostr.Event {
evt := nostr.Event{
Kind: nostr.KindSimpleGroupLiveKitParticipants,
CreatedAt: group.LastLiveKitParticipantsUpdate,
Tags: make(nostr.Tags, 1, 1+len(group.LiveKitParticipants)),
}
evt.Tags[0] = nostr.Tag{"d", group.Address.ID}
for member, identity := range group.LiveKitParticipants {
tag := nostr.Tag{"participant", member.Hex()}
if identity != "" {
tag = append(tag, identity)
} else {
tag = append(tag, "")
}
evt.Tags = append(evt.Tags, tag)
}
return evt
}
func (group *Group) MergeInMetadataEvent(evt *nostr.Event) error { func (group *Group) MergeInMetadataEvent(evt *nostr.Event) error {
if evt.Kind != nostr.KindSimpleGroupMetadata { if evt.Kind != nostr.KindSimpleGroupMetadata {
return fmt.Errorf("expected kind %d, got %d", nostr.KindSimpleGroupMetadata, evt.Kind) return fmt.Errorf("expected kind %d, got %d", nostr.KindSimpleGroupMetadata, evt.Kind)
@@ -284,7 +309,7 @@ func (group *Group) MergeInMetadataEvent(evt *nostr.Event) error {
case "hidden": case "hidden":
group.Hidden = true group.Hidden = true
case "livekit": case "livekit":
group.Livekit = true group.LiveKit = true
case "supported_kinds": case "supported_kinds":
kinds := make([]nostr.Kind, 0, len(tag)-1) kinds := make([]nostr.Kind, 0, len(tag)-1)
for _, raw := range tag[1:] { for _, raw := range tag[1:] {
@@ -408,3 +433,36 @@ func (group *Group) MergeInRolesEvent(evt *nostr.Event) error {
return nil return nil
} }
func (group *Group) MergeInLiveKitParticipantsEvent(evt *nostr.Event) error {
if evt.Kind != nostr.KindSimpleGroupLiveKitParticipants {
return fmt.Errorf("expected kind %d, got %d", nostr.KindSimpleGroupLiveKitParticipants, evt.Kind)
}
if evt.CreatedAt < group.LastLiveKitParticipantsUpdate {
return fmt.Errorf("event is older than our last update (%d vs %d)", evt.CreatedAt, group.LastLiveKitParticipantsUpdate)
}
group.LastLiveKitParticipantsUpdate = evt.CreatedAt
group.LiveKitParticipants = make(map[nostr.PubKey]string)
for _, tag := range evt.Tags {
if len(tag) < 2 {
continue
}
if tag[0] != "participant" {
continue
}
member, err := nostr.PubKeyFromHex(tag[1])
if err != nil {
continue
}
identity := ""
if len(tag) >= 3 {
identity = tag[2]
}
group.LiveKitParticipants[member] = identity
}
return nil
}
+9 -6
View File
@@ -124,10 +124,10 @@ var moderationActionFactories = map[nostr.Kind]func(nostr.Event) (Action, error)
edit.PrivateValue = &n edit.PrivateValue = &n
ok = true ok = true
case "livekit": case "livekit":
edit.LivekitValue = &y edit.LiveKitValue = &y
ok = true ok = true
case "no-livekit": case "no-livekit":
edit.LivekitValue = &n edit.LiveKitValue = &n
ok = true ok = true
} }
} }
@@ -244,7 +244,7 @@ type EditMetadata struct {
ClosedValue *bool ClosedValue *bool
HiddenValue *bool HiddenValue *bool
PrivateValue *bool PrivateValue *bool
LivekitValue *bool LiveKitValue *bool
When nostr.Timestamp When nostr.Timestamp
} }
@@ -272,8 +272,8 @@ func (a EditMetadata) Apply(group *Group) {
if a.PrivateValue != nil { if a.PrivateValue != nil {
group.Private = *a.PrivateValue group.Private = *a.PrivateValue
} }
if a.LivekitValue != nil { if a.LiveKitValue != nil {
group.Livekit = *a.LivekitValue group.LiveKit = *a.LiveKitValue
} }
} }
@@ -287,6 +287,7 @@ func (a CreateGroup) Apply(group *Group) {
group.LastMetadataUpdate = a.When group.LastMetadataUpdate = a.When
group.LastAdminsUpdate = a.When group.LastAdminsUpdate = a.When
group.LastMembersUpdate = a.When group.LastMembersUpdate = a.When
group.LastLiveKitParticipantsUpdate = a.When
} }
type DeleteGroup struct { type DeleteGroup struct {
@@ -296,6 +297,7 @@ type DeleteGroup struct {
func (_ DeleteGroup) Name() string { return "delete-group" } func (_ DeleteGroup) Name() string { return "delete-group" }
func (a DeleteGroup) Apply(group *Group) { func (a DeleteGroup) Apply(group *Group) {
group.Members = make(map[nostr.PubKey][]*Role) group.Members = make(map[nostr.PubKey][]*Role)
group.LiveKitParticipants = make(map[nostr.PubKey]string)
group.Closed = true group.Closed = true
group.Private = true group.Private = true
group.Restricted = true group.Restricted = true
@@ -303,10 +305,11 @@ func (a DeleteGroup) Apply(group *Group) {
group.Name = "[deleted]" group.Name = "[deleted]"
group.About = "" group.About = ""
group.Picture = "" group.Picture = ""
group.Livekit = false group.LiveKit = false
group.LastMetadataUpdate = a.When group.LastMetadataUpdate = a.When
group.LastAdminsUpdate = a.When group.LastAdminsUpdate = a.When
group.LastMembersUpdate = a.When group.LastMembersUpdate = a.When
group.LastLiveKitParticipantsUpdate = a.When
} }
type CreateInvite struct { type CreateInvite struct {
+1
View File
@@ -28,6 +28,7 @@ var MetadataEventKinds = KindRange{
nostr.KindSimpleGroupAdmins, nostr.KindSimpleGroupAdmins,
nostr.KindSimpleGroupMembers, nostr.KindSimpleGroupMembers,
nostr.KindSimpleGroupRoles, nostr.KindSimpleGroupRoles,
nostr.KindSimpleGroupLiveKitParticipants,
} }
func (kr KindRange) Includes(kind nostr.Kind) bool { func (kr KindRange) Includes(kind nostr.Kind) bool {