Add support for publishing kind 39004 livekit particpant list.

This commit is contained in:
mplorentz
2026-03-12 16:07:08 -04:00
committed by Jon Staab
parent e079026f6c
commit dc0c3e1477
6 changed files with 187 additions and 15 deletions
+1 -9
View File
@@ -11,7 +11,7 @@ import (
func GetGroupIDFromEvent(event nostr.Event) string {
var tagName string
if slices.Contains(nip29.MetadataEventKinds, event.Kind) {
if slices.Contains(nip29.MetadataEventKinds, event.Kind) || event.Kind == nostr.KindSimpleGroupLiveKitParticipants {
tagName = "d"
} else {
tagName = "h"
@@ -322,14 +322,6 @@ func (g *GroupStore) CheckWrite(event nostr.Event) string {
}
}
if HasTag(meta.Tags, "no-text") &&
!slices.Contains(nip29.ModerationEventKinds, event.Kind) &&
event.Kind != nostr.KindSimpleGroupJoinRequest &&
event.Kind != nostr.KindSimpleGroupLeaveRequest &&
event.Kind != ROOM_PRESENCE {
return "blocked: this group does not allow text events"
}
if HasTag(meta.Tags, "closed") && !g.HasAccess(h, event.PubKey) {
return "restricted: you are not a member of that group"
}
+3
View File
@@ -113,6 +113,9 @@ func MakeInstance(filename string) (*Instance, error) {
router.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
router.HandleFunc("GET /.well-known/nip29/livekit", instance.livekitSupportHandler)
router.HandleFunc("OPTIONS /.well-known/nip29/livekit", instance.livekitSupportHandler)
router.HandleFunc("POST /.well-known/nip29/livekit/webhook", instance.livekitWebhookHandler)
router.HandleFunc("GET /.well-known/nip29/livekit/{groupId}", instance.livekitTokenHandler)
router.HandleFunc("OPTIONS /.well-known/nip29/livekit/{groupId}", instance.livekitTokenHandler)
+144 -1
View File
@@ -4,12 +4,15 @@ import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"sync"
"fiatjaf.com/nostr"
"github.com/livekit/protocol/auth"
"github.com/livekit/protocol/webhook"
"slices"
)
var (
@@ -29,7 +32,7 @@ func generateLivekitToken(apiKey, apiSecret, room string, pubkey nostr.PubKey) s
RoomJoin: true,
Room: room,
})
at.SetIdentity(pubkey.Hex())
at.SetIdentity(pubkey.Hex() + ":" + RandomString(16))
jwt, _ := at.ToJWT()
return jwt
@@ -88,6 +91,24 @@ func ensureLivekitRoom(apiKey, apiSecret, serverURL, roomName string) error {
return fmt.Errorf("failed to create room: %s", resp.Status)
}
func (instance *Instance) livekitSupportHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "Authorization")
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
if r.Method == http.MethodOptions {
return
}
cfg := instance.Config.Livekit
if cfg.APIKey == "" {
http.NotFound(w, r)
return
}
w.WriteHeader(http.StatusNoContent)
}
func (instance *Instance) livekitTokenHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "Authorization")
@@ -145,3 +166,125 @@ func (instance *Instance) livekitTokenHandler(w http.ResponseWriter, r *http.Req
ParticipantToken: token,
})
}
func (instance *Instance) livekitWebhookHandler(w http.ResponseWriter, r *http.Request) {
cfg := instance.Config.Livekit
if cfg.APIKey == "" || cfg.APISecret == "" {
http.NotFound(w, r)
return
}
kp := auth.NewSimpleKeyProvider(cfg.APIKey, cfg.APISecret)
event, err := webhook.ReceiveWebhookEvent(r, kp)
if err != nil {
http.Error(w, "invalid webhook: "+err.Error(), http.StatusUnauthorized)
return
}
room := event.GetRoom()
if room == nil {
http.Error(w, "missing room", http.StatusBadRequest)
return
}
groupId := room.GetName()
if groupId == "" {
http.Error(w, "missing room name", http.StatusBadRequest)
return
}
meta, found := instance.Groups.GetMetadata(groupId)
if !found {
http.NotFound(w, r)
return
}
if !HasTag(meta.Tags, "livekit") {
http.Error(w, "livekit not enabled for this group", http.StatusForbidden)
return
}
switch event.Event {
case webhook.EventParticipantJoined, webhook.EventParticipantLeft:
participant := event.GetParticipant()
if participant == nil || len(participant.Identity) < 64 {
http.Error(w, "missing participant", http.StatusBadRequest)
return
}
if _, err := nostr.PubKeyFromHex(participant.Identity[0:64]); err != nil {
log.Printf("[livekit webhook] invalid nostr pubkey in identity: %v", err)
w.WriteHeader(http.StatusNoContent)
return
}
connected := event.Event == webhook.EventParticipantJoined
if err := instance.updateLiveKitPresence(groupId, participant.Identity, connected); err != nil {
http.Error(w, "failed to update livekit participants: "+err.Error(), http.StatusInternalServerError)
return
}
default:
w.WriteHeader(http.StatusNoContent)
return
}
w.WriteHeader(http.StatusNoContent)
}
func (instance *Instance) updateLiveKitPresence(groupId string, identity string, connected bool) error {
identities := instance.getLiveKitParticipantIdentities(groupId)
if connected {
if !slices.Contains(identities, identity) {
identities = append(identities, identity)
}
} else {
if idx := slices.Index(identities, identity); idx != -1 {
identities[idx] = identities[len(identities)-1]
identities = identities[:len(identities)-1]
} else {
log.Printf("[livekit webhook] identity %q not in list when processing leave (had %d participants)",
identity, len(identities))
}
}
log.Printf("[livekit webhook] presence update: room=%s connected=%v count=%d",
groupId, connected, len(identities))
return instance.publishLiveKitPresence(groupId, identities)
}
func (instance *Instance) getLiveKitParticipantIdentities(groupId string) []string {
filter := nostr.Filter{
Kinds: []nostr.Kind{nostr.KindSimpleGroupLiveKitParticipants},
Authors: []nostr.PubKey{instance.Config.GetSelf()},
Tags: nostr.TagMap{"d": []string{groupId}},
}
for event := range instance.Events.QueryEvents(filter, 1) {
var identities []string
for tag := range event.Tags.FindAll("participant") {
if len(tag) >= 2 && tag[1] != "" {
if !slices.Contains(identities, tag[1]) {
identities = append(identities, tag[1])
}
}
}
return identities
}
return nil
}
func (instance *Instance) publishLiveKitPresence(groupId string, identities []string) error {
tags := nostr.Tags{nostr.Tag{"d", groupId}}
for _, identity := range identities {
tags = append(tags, nostr.Tag{"participant", identity})
}
event := nostr.Event{
Kind: nostr.KindSimpleGroupLiveKitParticipants,
CreatedAt: nostr.Now(),
Tags: tags,
}
return instance.Events.SignAndStoreEvent(&event, true)
}