Add support for publishing kind 39004 livekit particpant list.
This commit is contained in:
+1
-9
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user