mirror of
https://github.com/fiatjaf/khatru.git
synced 2026-04-08 14:36:47 +02:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33545587b6 | ||
|
|
214371f8bd | ||
|
|
fbb40f3b74 | ||
|
|
d97a2f1cf2 | ||
|
|
c9a7d60543 | ||
|
|
2bb6d4d29a | ||
|
|
2292ce4a30 | ||
|
|
2ae219a34c | ||
|
|
8c9394993b | ||
|
|
850497956c | ||
|
|
28ce6cfb7a | ||
|
|
f47282c745 | ||
|
|
f72dea346f | ||
|
|
51632dcc9f | ||
|
|
6cc2477e89 | ||
|
|
581c4ece28 | ||
|
|
596bca93c3 | ||
|
|
650d9209c3 | ||
|
|
0d736cff82 | ||
|
|
44ed6f519d | ||
|
|
db832d4255 | ||
|
|
625bde38c5 | ||
|
|
7c6031f4e5 | ||
|
|
6e224b9437 | ||
|
|
e9030a355c | ||
|
|
a6ed7bced0 |
132
adding.go
132
adding.go
@@ -11,34 +11,46 @@ import (
|
||||
|
||||
// AddEvent sends an event through then normal add pipeline, as if it was received from a websocket.
|
||||
func (rl *Relay) AddEvent(ctx context.Context, evt *nostr.Event) (skipBroadcast bool, writeError error) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
if evt == nil {
|
||||
return false, errors.New("error: event is nil")
|
||||
}
|
||||
|
||||
if nostr.IsEphemeralKind(evt.Kind) {
|
||||
return false, rl.handleEphemeral(ctx, evt)
|
||||
} else {
|
||||
return rl.handleNormal(ctx, evt)
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Relay) handleNormal(ctx context.Context, evt *nostr.Event) (skipBroadcast bool, writeError error) {
|
||||
for _, reject := range rl.RejectEvent {
|
||||
if reject, msg := reject(ctx, evt); reject {
|
||||
if msg == "" {
|
||||
return false, errors.New("blocked: no reason")
|
||||
return true, errors.New("blocked: no reason")
|
||||
} else {
|
||||
return false, errors.New(nostr.NormalizeOKMessage(msg, "blocked"))
|
||||
return true, errors.New(nostr.NormalizeOKMessage(msg, "blocked"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if 20000 <= evt.Kind && evt.Kind < 30000 {
|
||||
// do not store ephemeral events
|
||||
for _, oee := range rl.OnEphemeralEvent {
|
||||
oee(ctx, evt)
|
||||
// will store
|
||||
// regular kinds are just saved directly
|
||||
if nostr.IsRegularKind(evt.Kind) {
|
||||
for _, store := range rl.StoreEvent {
|
||||
if err := store(ctx, evt); err != nil {
|
||||
switch err {
|
||||
case eventstore.ErrDupEvent:
|
||||
return true, nil
|
||||
default:
|
||||
return false, fmt.Errorf("%s", nostr.NormalizeOKMessage(err.Error(), "error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// will store
|
||||
// regular kinds are just saved directly
|
||||
if nostr.IsRegularKind(evt.Kind) {
|
||||
for _, store := range rl.StoreEvent {
|
||||
if err := store(ctx, evt); err != nil {
|
||||
// otherwise it's a replaceable -- so we'll use the replacer functions if we have any
|
||||
if len(rl.ReplaceEvent) > 0 {
|
||||
for _, repl := range rl.ReplaceEvent {
|
||||
if err := repl(ctx, evt); err != nil {
|
||||
switch err {
|
||||
case eventstore.ErrDupEvent:
|
||||
return true, nil
|
||||
@@ -48,68 +60,54 @@ func (rl *Relay) AddEvent(ctx context.Context, evt *nostr.Event) (skipBroadcast
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// otherwise it's a replaceable -- so we'll use the replacer functions if we have any
|
||||
if len(rl.ReplaceEvent) > 0 {
|
||||
for _, repl := range rl.ReplaceEvent {
|
||||
if err := repl(ctx, evt); err != nil {
|
||||
switch err {
|
||||
// otherwise do it the manual way
|
||||
filter := nostr.Filter{Limit: 1, Kinds: []int{evt.Kind}, Authors: []string{evt.PubKey}}
|
||||
if nostr.IsAddressableKind(evt.Kind) {
|
||||
// when addressable, add the "d" tag to the filter
|
||||
filter.Tags = nostr.TagMap{"d": []string{evt.Tags.GetD()}}
|
||||
}
|
||||
|
||||
// now we fetch old events and delete them
|
||||
shouldStore := true
|
||||
for _, query := range rl.QueryEvents {
|
||||
ch, err := query(ctx, filter)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for previous := range ch {
|
||||
if isOlder(previous, evt) {
|
||||
for _, del := range rl.DeleteEvent {
|
||||
del(ctx, previous)
|
||||
}
|
||||
} else {
|
||||
// we found a more recent event, so we won't delete it and also will not store this new one
|
||||
shouldStore = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// store
|
||||
if shouldStore {
|
||||
for _, store := range rl.StoreEvent {
|
||||
if saveErr := store(ctx, evt); saveErr != nil {
|
||||
switch saveErr {
|
||||
case eventstore.ErrDupEvent:
|
||||
return true, nil
|
||||
default:
|
||||
return false, fmt.Errorf("%s", nostr.NormalizeOKMessage(err.Error(), "error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// otherwise do it the manual way
|
||||
filter := nostr.Filter{Limit: 1, Kinds: []int{evt.Kind}, Authors: []string{evt.PubKey}}
|
||||
if nostr.IsAddressableKind(evt.Kind) {
|
||||
// when addressable, add the "d" tag to the filter
|
||||
filter.Tags = nostr.TagMap{"d": []string{evt.Tags.GetD()}}
|
||||
}
|
||||
|
||||
// now we fetch old events and delete them
|
||||
shouldStore := true
|
||||
for _, query := range rl.QueryEvents {
|
||||
ch, err := query(ctx, filter)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for previous := range ch {
|
||||
if isOlder(previous, evt) {
|
||||
for _, del := range rl.DeleteEvent {
|
||||
del(ctx, previous)
|
||||
}
|
||||
} else {
|
||||
// we found a more recent event, so we won't delete it and also will not store this new one
|
||||
shouldStore = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// store
|
||||
if shouldStore {
|
||||
for _, store := range rl.StoreEvent {
|
||||
if saveErr := store(ctx, evt); saveErr != nil {
|
||||
switch saveErr {
|
||||
case eventstore.ErrDupEvent:
|
||||
return true, nil
|
||||
default:
|
||||
return false, fmt.Errorf("%s", nostr.NormalizeOKMessage(saveErr.Error(), "error"))
|
||||
}
|
||||
return false, fmt.Errorf("%s", nostr.NormalizeOKMessage(saveErr.Error(), "error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, ons := range rl.OnEventSaved {
|
||||
ons(ctx, evt)
|
||||
}
|
||||
|
||||
// track event expiration if applicable
|
||||
rl.expirationManager.trackEvent(evt)
|
||||
}
|
||||
|
||||
for _, ons := range rl.OnEventSaved {
|
||||
ons(ctx, evt)
|
||||
}
|
||||
|
||||
// track event expiration if applicable
|
||||
rl.expirationManager.trackEvent(evt)
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package blossom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mailru/easyjson"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
@@ -19,25 +17,26 @@ func readAuthorization(r *http.Request) (*nostr.Event, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var reader io.Reader
|
||||
reader = bytes.NewReader([]byte(token)[6:])
|
||||
reader = base64.NewDecoder(base64.StdEncoding, reader)
|
||||
eventj, err := base64.StdEncoding.DecodeString(token[6:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid base64 token")
|
||||
}
|
||||
var evt nostr.Event
|
||||
err := json.NewDecoder(reader).Decode(&evt)
|
||||
|
||||
if err != nil || evt.Kind != 24242 || len(evt.ID) != 64 || !evt.CheckID() {
|
||||
if err := easyjson.Unmarshal(eventj, &evt); err != nil {
|
||||
return nil, fmt.Errorf("broken event")
|
||||
}
|
||||
if evt.Kind != 24242 || !evt.CheckID() {
|
||||
return nil, fmt.Errorf("invalid event")
|
||||
}
|
||||
|
||||
if ok, _ := evt.CheckSignature(); !ok {
|
||||
return nil, fmt.Errorf("invalid signature")
|
||||
}
|
||||
|
||||
expirationTag := evt.Tags.GetFirst([]string{"expiration", ""})
|
||||
expirationTag := evt.Tags.Find("expiration")
|
||||
if expirationTag == nil {
|
||||
return nil, fmt.Errorf("missing \"expiration\" tag")
|
||||
}
|
||||
expiration, _ := strconv.ParseInt((*expirationTag)[1], 10, 64)
|
||||
expiration, _ := strconv.ParseInt(expirationTag[1], 10, 64)
|
||||
if nostr.Timestamp(expiration) < nostr.Now() {
|
||||
return nil, fmt.Errorf("event expired")
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func (bs BlossomServer) handleUploadCheck(w http.ResponseWriter, r *http.Request
|
||||
blossomError(w, "missing \"Authorization\" header", 401)
|
||||
return
|
||||
}
|
||||
if auth.Tags.GetFirst([]string{"t", "upload"}) == nil {
|
||||
if auth.Tags.FindWithValue("t", "upload") == nil {
|
||||
blossomError(w, "invalid \"Authorization\" event \"t\" tag", 403)
|
||||
return
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func (bs BlossomServer) handleUpload(w http.ResponseWriter, r *http.Request) {
|
||||
blossomError(w, "missing \"Authorization\" header", 401)
|
||||
return
|
||||
}
|
||||
if auth.Tags.GetFirst([]string{"t", "upload"}) == nil {
|
||||
if auth.Tags.FindWithValue("t", "upload") == nil {
|
||||
blossomError(w, "invalid \"Authorization\" event \"t\" tag", 403)
|
||||
return
|
||||
}
|
||||
@@ -73,7 +73,7 @@ func (bs BlossomServer) handleUpload(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// read first bytes of upload so we can find out the filetype
|
||||
b := make([]byte, min(50, size), size)
|
||||
if _, err = r.Body.Read(b); err != nil {
|
||||
if n, err := r.Body.Read(b); err != nil && n != size {
|
||||
blossomError(w, "failed to read initial bytes of upload body: "+err.Error(), 400)
|
||||
return
|
||||
}
|
||||
@@ -163,13 +163,13 @@ func (bs BlossomServer) handleGetBlob(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// if there is one, we check if it has the extra requirements
|
||||
if auth != nil {
|
||||
if auth.Tags.GetFirst([]string{"t", "get"}) == nil {
|
||||
if auth.Tags.FindWithValue("t", "get") == nil {
|
||||
blossomError(w, "invalid \"Authorization\" event \"t\" tag", 403)
|
||||
return
|
||||
}
|
||||
|
||||
if auth.Tags.GetFirst([]string{"x", hhash}) == nil &&
|
||||
auth.Tags.GetFirst([]string{"server", bs.ServiceURL}) == nil {
|
||||
if auth.Tags.FindWithValue("x", hhash) == nil &&
|
||||
auth.Tags.FindWithValue("server", bs.ServiceURL) == nil {
|
||||
blossomError(w, "invalid \"Authorization\" event \"x\" or \"server\" tag", 403)
|
||||
return
|
||||
}
|
||||
@@ -239,7 +239,7 @@ func (bs BlossomServer) handleList(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// if there is one, we check if it has the extra requirements
|
||||
if auth != nil {
|
||||
if auth.Tags.GetFirst([]string{"t", "list"}) == nil {
|
||||
if auth.Tags.FindWithValue("t", "list") == nil {
|
||||
blossomError(w, "invalid \"Authorization\" event \"t\" tag", 403)
|
||||
return
|
||||
}
|
||||
@@ -283,7 +283,7 @@ func (bs BlossomServer) handleDelete(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if auth != nil {
|
||||
if auth.Tags.GetFirst([]string{"t", "delete"}) == nil {
|
||||
if auth.Tags.FindWithValue("t", "delete") == nil {
|
||||
blossomError(w, "invalid \"Authorization\" event \"t\" tag", 403)
|
||||
return
|
||||
}
|
||||
@@ -296,8 +296,8 @@ func (bs BlossomServer) handleDelete(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
hhash = hhash[1:]
|
||||
if auth.Tags.GetFirst([]string{"x", hhash}) == nil &&
|
||||
auth.Tags.GetFirst([]string{"server", bs.ServiceURL}) == nil {
|
||||
if auth.Tags.FindWithValue("x", hhash) == nil &&
|
||||
auth.Tags.FindWithValue("server", bs.ServiceURL) == nil {
|
||||
blossomError(w, "invalid \"Authorization\" event \"x\" or \"server\" tag", 403)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func New(rl *khatru.Relay, serviceURL string) *BlossomServer {
|
||||
return
|
||||
}
|
||||
|
||||
if len(strings.SplitN(r.URL.Path, ".", 2)[0]) == 65 {
|
||||
if (len(r.URL.Path) == 65 || strings.Index(r.URL.Path, ".") == 65) && strings.Index(r.URL.Path[1:], "/") == -1 {
|
||||
if r.Method == "HEAD" {
|
||||
bs.handleHasBlob(w, r)
|
||||
return
|
||||
|
||||
@@ -6,6 +6,6 @@ import (
|
||||
|
||||
// BroadcastEvent emits an event to all listeners whose filters' match, skipping all filters and actions
|
||||
// it also doesn't attempt to store the event or trigger any reactions or callbacks
|
||||
func (rl *Relay) BroadcastEvent(evt *nostr.Event) {
|
||||
rl.notifyListeners(evt)
|
||||
func (rl *Relay) BroadcastEvent(evt *nostr.Event) int {
|
||||
return rl.notifyListeners(evt)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ func (rl *Relay) handleDeleteRequest(ctx context.Context, evt *nostr.Event) erro
|
||||
continue
|
||||
}
|
||||
|
||||
ctx := context.WithValue(ctx, internalCallKey, struct{}{})
|
||||
for _, query := range rl.QueryEvents {
|
||||
ch, err := query(ctx, f)
|
||||
if err != nil {
|
||||
@@ -66,6 +67,9 @@ func (rl *Relay) handleDeleteRequest(ctx context.Context, evt *nostr.Event) erro
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// if it was tracked to be expired that is not needed anymore
|
||||
rl.expirationManager.removeEvent(target.ID)
|
||||
} else {
|
||||
// fail and stop here
|
||||
return fmt.Errorf("blocked: %s", msg)
|
||||
|
||||
@@ -19,7 +19,7 @@ export default {
|
||||
{ text: 'HTTP Integration', link: '/core/embed' },
|
||||
{ text: 'Request Routing', link: '/core/routing' },
|
||||
{ text: 'Management API', link: '/core/management' },
|
||||
{ text: 'Media Storage', link: '/core/blossom' },
|
||||
{ text: 'Media Storage (Blossom)', link: '/core/blossom' },
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -18,7 +18,8 @@ func main() {
|
||||
bl := blossom.New(relay, "http://localhost:3334")
|
||||
|
||||
// create a database for keeping track of blob metadata
|
||||
bl.Store = blossom.EventStoreBlobIndexWrapper{Store: db, ServiceURL: bl.ServiceURL}
|
||||
// (do not use the same database used for the relay events)
|
||||
bl.Store = blossom.EventStoreBlobIndexWrapper{Store: blobdb, ServiceURL: bl.ServiceURL}
|
||||
|
||||
// implement the required storage functions
|
||||
bl.StoreBlob = append(bl.StoreBlob, func(ctx context.Context, sha256 string, body []byte) error {
|
||||
|
||||
@@ -47,7 +47,7 @@ router.Route().
|
||||
return true
|
||||
case event.Kind <= 12 && event.Kind >= 9:
|
||||
return true
|
||||
case event.Tags.GetFirst([]string{"h", ""}) != nil:
|
||||
case event.Tags.Find("h") != nil:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
||||
26
ephemeral.go
Normal file
26
ephemeral.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package khatru
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
func (rl *Relay) handleEphemeral(ctx context.Context, evt *nostr.Event) error {
|
||||
for _, reject := range rl.RejectEvent {
|
||||
if reject, msg := reject(ctx, evt); reject {
|
||||
if msg == "" {
|
||||
return errors.New("blocked: no reason")
|
||||
} else {
|
||||
return errors.New(nostr.NormalizeOKMessage(msg, "blocked"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, oee := range rl.OnEphemeralEvent {
|
||||
oee(ctx, evt)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -13,7 +13,7 @@ func main() {
|
||||
relay := khatru.NewRelay()
|
||||
|
||||
db := lmdb.LMDBBackend{Path: "/tmp/khatru-lmdb-tmp"}
|
||||
os.MkdirAll(db.Path, 0755)
|
||||
os.MkdirAll(db.Path, 0o755)
|
||||
if err := db.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -15,19 +15,22 @@ import (
|
||||
func main() {
|
||||
relay := khatru.NewRelay()
|
||||
|
||||
db := &badger.BadgerBackend{Path: "/tmp/khatru-badger-blossom-tmp"}
|
||||
db := &badger.BadgerBackend{Path: "/tmp/khatru-badger-tmp"}
|
||||
if err := db.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent)
|
||||
relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents)
|
||||
relay.CountEvents = append(relay.CountEvents, db.CountEvents)
|
||||
relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent)
|
||||
relay.ReplaceEvent = append(relay.ReplaceEvent, db.ReplaceEvent)
|
||||
|
||||
bdb := &badger.BadgerBackend{Path: "/tmp/khatru-badger-blossom-tmp"}
|
||||
if err := bdb.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
bl := blossom.New(relay, "http://localhost:3334")
|
||||
bl.Store = blossom.EventStoreBlobIndexWrapper{Store: db, ServiceURL: bl.ServiceURL}
|
||||
bl.Store = blossom.EventStoreBlobIndexWrapper{Store: bdb, ServiceURL: bl.ServiceURL}
|
||||
bl.StoreBlob = append(bl.StoreBlob, func(ctx context.Context, sha256 string, body []byte) error {
|
||||
fmt.Println("storing", sha256, len(body))
|
||||
return nil
|
||||
|
||||
@@ -16,7 +16,7 @@ func main() {
|
||||
relay := khatru.NewRelay()
|
||||
|
||||
db := lmdb.LMDBBackend{Path: "/tmp/exclusive"}
|
||||
os.MkdirAll(db.Path, 0755)
|
||||
os.MkdirAll(db.Path, 0o755)
|
||||
if err := db.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func main() {
|
||||
return slices.Contains(filter.Kinds, 1) && slices.Contains(filter.Tags["t"], "spam")
|
||||
}).
|
||||
Event(func(event *nostr.Event) bool {
|
||||
return event.Kind == 1 && event.Tags.GetFirst([]string{"t", "spam"}) != nil
|
||||
return event.Kind == 1 && event.Tags.FindWithValue("t", "spam") != nil
|
||||
}).
|
||||
Relay(r2)
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ func (em *expirationManager) initialScan(ctx context.Context) {
|
||||
defer em.mu.Unlock()
|
||||
|
||||
// query all events
|
||||
ctx = context.WithValue(ctx, internalCallKey, struct{}{})
|
||||
for _, query := range em.relay.QueryEvents {
|
||||
ch, err := query(ctx, nostr.Filter{})
|
||||
if err != nil {
|
||||
@@ -107,6 +108,7 @@ func (em *expirationManager) checkExpiredEvents(ctx context.Context) {
|
||||
|
||||
heap.Pop(&em.events)
|
||||
|
||||
ctx := context.WithValue(ctx, internalCallKey, struct{}{})
|
||||
for _, query := range em.relay.QueryEvents {
|
||||
ch, err := query(ctx, nostr.Filter{IDs: []string{next.id}})
|
||||
if err != nil {
|
||||
@@ -133,3 +135,16 @@ func (em *expirationManager) trackEvent(evt *nostr.Event) {
|
||||
em.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (em *expirationManager) removeEvent(id string) {
|
||||
em.mu.Lock()
|
||||
defer em.mu.Unlock()
|
||||
|
||||
// Find and remove the event from the heap
|
||||
for i := 0; i < len(em.events); i++ {
|
||||
if em.events[i].id == id {
|
||||
heap.Remove(&em.events, i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
47
go.mod
47
go.mod
@@ -1,32 +1,37 @@
|
||||
module github.com/fiatjaf/khatru
|
||||
|
||||
go 1.23.1
|
||||
go 1.24.1
|
||||
|
||||
require (
|
||||
github.com/bep/debounce v1.2.1
|
||||
github.com/fasthttp/websocket v1.5.7
|
||||
github.com/fiatjaf/eventstore v0.15.0
|
||||
github.com/fasthttp/websocket v1.5.12
|
||||
github.com/fiatjaf/eventstore v0.16.2
|
||||
github.com/liamg/magic v0.0.1
|
||||
github.com/nbd-wtf/go-nostr v0.46.0
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0
|
||||
github.com/mailru/easyjson v0.9.0
|
||||
github.com/nbd-wtf/go-nostr v0.51.8
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
||||
github.com/rs/cors v1.11.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
fiatjaf.com/lib v0.2.0 // indirect
|
||||
github.com/PowerDNS/lmdb-go v1.9.2 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 // indirect
|
||||
github.com/PowerDNS/lmdb-go v1.9.3 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/aquasecurity/esquery v0.2.0 // indirect
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
|
||||
github.com/bytedance/sonic v1.13.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/coder/websocket v1.8.12 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/coder/websocket v1.8.13 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
|
||||
github.com/dgraph-io/badger/v4 v4.5.0 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.0.0 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/elastic/elastic-transport-go/v8 v8.6.0 // indirect
|
||||
github.com/elastic/go-elasticsearch/v7 v7.17.10 // indirect
|
||||
@@ -35,31 +40,33 @@ require (
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/flatbuffers v24.3.25+incompatible // indirect
|
||||
github.com/google/flatbuffers v24.12.23+incompatible // indirect
|
||||
github.com/jmoiron/sqlx v1.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/valyala/fasthttp v1.59.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/otel v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.32.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect
|
||||
golang.org/x/net v0.32.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
google.golang.org/protobuf v1.35.2 // indirect
|
||||
golang.org/x/arch v0.15.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
google.golang.org/protobuf v1.36.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
102
go.sum
102
go.sum
@@ -4,40 +4,56 @@ fiatjaf.com/lib v0.2.0/go.mod h1:Ycqq3+mJ9jAWu7XjbQI1cVr+OFgnHn79dQR5oTII47g=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/PowerDNS/lmdb-go v1.9.2 h1:Cmgerh9y3ZKBZGz1irxSShhfmFyRUh+Zdk4cZk7ZJvU=
|
||||
github.com/PowerDNS/lmdb-go v1.9.2/go.mod h1:TE0l+EZK8Z1B4dx070ZxkWTlp8RG1mjN0/+FkFRQMtU=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 h1:ClzzXMDDuUbWfNNZqGeYq4PnYOlwlOVIvSyNaIy0ykg=
|
||||
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3/go.mod h1:we0YA5CsBbH5+/NUzC/AlMmxaDtWlXeNsqrwXjTzmzA=
|
||||
github.com/PowerDNS/lmdb-go v1.9.3 h1:AUMY2pZT8WRpkEv39I9Id3MuoHd+NZbTVpNhruVkPTg=
|
||||
github.com/PowerDNS/lmdb-go v1.9.3/go.mod h1:TE0l+EZK8Z1B4dx070ZxkWTlp8RG1mjN0/+FkFRQMtU=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/aquasecurity/esquery v0.2.0 h1:9WWXve95TE8hbm3736WB7nS6Owl8UGDeu+0jiyE9ttA=
|
||||
github.com/aquasecurity/esquery v0.2.0/go.mod h1:VU+CIFR6C+H142HHZf9RUkp4Eedpo9UrEKeCQHWf9ao=
|
||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||
github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
|
||||
github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
||||
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
||||
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
|
||||
github.com/dgraph-io/badger/v4 v4.5.0 h1:TeJE3I1pIWLBjYhIYCA1+uxrjWEoJXImFBMEBVSm16g=
|
||||
github.com/dgraph-io/badger/v4 v4.5.0/go.mod h1:ysgYmIeG8dS/E8kwxT7xHyc7MkmwNYLRoYnFbr7387A=
|
||||
github.com/dgraph-io/ristretto/v2 v2.0.0 h1:l0yiSOtlJvc0otkqyMaDNysg8E9/F/TYZwMbxscNOAQ=
|
||||
github.com/dgraph-io/ristretto/v2 v2.0.0/go.mod h1:FVFokF2dRqXyPyeMnK1YDy8Fc6aTe0IKgbcd03CYeEk=
|
||||
github.com/dgraph-io/ristretto/v2 v2.1.0 h1:59LjpOJLNDULHh8MC4UaegN52lC4JnO2dITsie/Pa8I=
|
||||
github.com/dgraph-io/ristretto/v2 v2.1.0/go.mod h1:uejeqfYXpUomfse0+lO+13ATz4TypQYLJZzBSAemuB4=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||
github.com/elastic/elastic-transport-go/v8 v8.6.0 h1:Y2S/FBjx1LlCv5m6pWAF2kDJAHoSjSRSJCApolgfthA=
|
||||
github.com/elastic/elastic-transport-go/v8 v8.6.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
|
||||
github.com/elastic/go-elasticsearch/v7 v7.6.0/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4=
|
||||
@@ -49,12 +65,12 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fasthttp/websocket v1.5.7 h1:0a6o2OfeATvtGgoMKleURhLT6JqWPg7fYfWnH4KHau4=
|
||||
github.com/fasthttp/websocket v1.5.7/go.mod h1:bC4fxSono9czeXHQUVKxsC0sNjbm7lPJR04GDFqClfU=
|
||||
github.com/fasthttp/websocket v1.5.12 h1:e4RGPpWW2HTbL3zV0Y/t7g0ub294LkiuXXUuTOUInlE=
|
||||
github.com/fasthttp/websocket v1.5.12/go.mod h1:I+liyL7/4moHojiOgUOIKEWm9EIxHqxZChS+aMFltyg=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fiatjaf/eventstore v0.15.0 h1:5UXe0+vIb30/cYcOWipks8nR3g+X8W224TFy5yPzivk=
|
||||
github.com/fiatjaf/eventstore v0.15.0/go.mod h1:KAsld5BhkmSck48aF11Txu8X+OGNmoabw4TlYVWqInc=
|
||||
github.com/fiatjaf/eventstore v0.16.2 h1:h4rHwSwPcqAKqWUsAbYWUhDeSgm2Kp+PBkJc3FgBYu4=
|
||||
github.com/fiatjaf/eventstore v0.16.2/go.mod h1:0gU8fzYO/bG+NQAVlHtJWOlt3JKKFefh5Xjj2d1dLIs=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -76,8 +92,8 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI=
|
||||
github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v24.12.23+incompatible h1:ubBKR94NR4pXUCY/MUsRVzd9umNW7ht7EG9hHfS9FX8=
|
||||
github.com/google/flatbuffers v24.12.23+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@@ -98,38 +114,47 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/liamg/magic v0.0.1 h1:Ru22ElY+sCh6RvRTWjQzKKCxsEco8hE0co8n1qe7TBM=
|
||||
github.com/liamg/magic v0.0.1/go.mod h1:yQkOmZZI52EA+SQ2xyHpVw8fNvTBruF873Y+Vt6S+fk=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/nbd-wtf/go-nostr v0.46.0 h1:aR+xXEC6MPutNMIRhNdi+2iBPEHW7SO10sFaOAVSz3Y=
|
||||
github.com/nbd-wtf/go-nostr v0.46.0/go.mod h1:xVNOqkn0GImeTmaF6VDwgYsuSkfG3yrIbd0dT6NZDIQ=
|
||||
github.com/nbd-wtf/go-nostr v0.51.7 h1:dGjtaaFQ1kA3H+vF8wt9a9WYl54K8C0JmVDf4cp+a4A=
|
||||
github.com/nbd-wtf/go-nostr v0.51.7/go.mod h1:d6+DfvMWYG5pA3dmNMBJd6WCHVDDhkXbHqvfljf0Gzg=
|
||||
github.com/nbd-wtf/go-nostr v0.51.8 h1:CIoS+YqChcm4e1L1rfMZ3/mIwTz4CwApM2qx7MHNzmE=
|
||||
github.com/nbd-wtf/go-nostr v0.51.8/go.mod h1:d6+DfvMWYG5pA3dmNMBJd6WCHVDDhkXbHqvfljf0Gzg=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
@@ -142,10 +167,14 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||
github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI=
|
||||
github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
|
||||
@@ -156,11 +185,13 @@ go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZ
|
||||
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
||||
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
|
||||
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
||||
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
|
||||
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
|
||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
@@ -170,8 +201,8 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
|
||||
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -180,8 +211,8 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -209,8 +240,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
|
||||
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -220,3 +251,4 @@ gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
|
||||
129
handlers.go
129
handlers.go
@@ -6,9 +6,11 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/bep/debounce"
|
||||
"github.com/fasthttp/websocket"
|
||||
@@ -16,6 +18,7 @@ import (
|
||||
"github.com/nbd-wtf/go-nostr/nip42"
|
||||
"github.com/nbd-wtf/go-nostr/nip45"
|
||||
"github.com/nbd-wtf/go-nostr/nip45/hyperloglog"
|
||||
"github.com/nbd-wtf/go-nostr/nip70"
|
||||
"github.com/nbd-wtf/go-nostr/nip77"
|
||||
"github.com/nbd-wtf/go-nostr/nip77/negentropy"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
@@ -115,8 +118,10 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
onconnect(ctx)
|
||||
}
|
||||
|
||||
smp := nostr.NewMessageParser()
|
||||
|
||||
for {
|
||||
typ, message, err := ws.conn.ReadMessage()
|
||||
typ, msgb, err := ws.conn.ReadMessage()
|
||||
if err != nil {
|
||||
if websocket.IsUnexpectedCloseError(
|
||||
err,
|
||||
@@ -126,7 +131,7 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
websocket.CloseAbnormalClosure, // 1006
|
||||
4537, // some client seems to send many of these
|
||||
) {
|
||||
rl.Log.Printf("unexpected close error from %s: %v\n", r.Header.Get("X-Forwarded-For"), err)
|
||||
rl.Log.Printf("unexpected close error from %s: %v\n", GetIPFromRequest(r), err)
|
||||
}
|
||||
ws.cancel()
|
||||
return
|
||||
@@ -137,15 +142,20 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
continue
|
||||
}
|
||||
|
||||
go func(message []byte) {
|
||||
envelope := nostr.ParseMessage(message)
|
||||
if envelope == nil {
|
||||
if !rl.Negentropy {
|
||||
// stop silently
|
||||
return
|
||||
// this is safe because ReadMessage() will always create a new slice
|
||||
message := unsafe.String(unsafe.SliceData(msgb), len(msgb))
|
||||
|
||||
// parse messages sequentially otherwise sonic breaks
|
||||
envelope, err := smp.ParseMessage(message)
|
||||
|
||||
// then delegate to the goroutine
|
||||
go func(message string) {
|
||||
if err != nil {
|
||||
if err == nostr.UnknownLabel && rl.Negentropy {
|
||||
envelope = nip77.ParseNegMessage(message)
|
||||
}
|
||||
envelope = nip77.ParseNegMessage(message)
|
||||
if envelope == nil {
|
||||
ws.WriteJSON(nostr.NoticeEnvelope("failed to parse envelope: " + err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -168,28 +178,31 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// check NIP-70 protected
|
||||
for _, v := range env.Event.Tags {
|
||||
if len(v) == 1 && v[0] == "-" {
|
||||
msg := "must be published by event author"
|
||||
authed := GetAuthed(ctx)
|
||||
if authed == "" {
|
||||
RequestAuth(ctx)
|
||||
ws.WriteJSON(nostr.OKEnvelope{
|
||||
EventID: env.Event.ID,
|
||||
OK: false,
|
||||
Reason: "auth-required: " + msg,
|
||||
})
|
||||
return
|
||||
}
|
||||
if authed != env.Event.PubKey {
|
||||
ws.WriteJSON(nostr.OKEnvelope{
|
||||
EventID: env.Event.ID,
|
||||
OK: false,
|
||||
Reason: "blocked: " + msg,
|
||||
})
|
||||
return
|
||||
}
|
||||
if nip70.IsProtected(env.Event) {
|
||||
authed := GetAuthed(ctx)
|
||||
if authed == "" {
|
||||
RequestAuth(ctx)
|
||||
ws.WriteJSON(nostr.OKEnvelope{
|
||||
EventID: env.Event.ID,
|
||||
OK: false,
|
||||
Reason: "auth-required: must be published by authenticated event author",
|
||||
})
|
||||
return
|
||||
} else if authed != env.Event.PubKey {
|
||||
ws.WriteJSON(nostr.OKEnvelope{
|
||||
EventID: env.Event.ID,
|
||||
OK: false,
|
||||
Reason: "blocked: must be published by event author",
|
||||
})
|
||||
return
|
||||
}
|
||||
} else if nip70.HasEmbeddedProtected(env.Event) {
|
||||
ws.WriteJSON(nostr.OKEnvelope{
|
||||
EventID: env.Event.ID,
|
||||
OK: false,
|
||||
Reason: "blocked: can't repost nip70 protected",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
srl := rl
|
||||
@@ -204,9 +217,12 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
if env.Event.Kind == 5 {
|
||||
// this always returns "blocked: " whenever it returns an error
|
||||
writeErr = srl.handleDeleteRequest(ctx, &env.Event)
|
||||
} else if nostr.IsEphemeralKind(env.Event.Kind) {
|
||||
// this will also always return a prefixed reason
|
||||
writeErr = srl.handleEphemeral(ctx, &env.Event)
|
||||
} else {
|
||||
// this will also always return a prefixed reason
|
||||
skipBroadcast, writeErr = srl.AddEvent(ctx, &env.Event)
|
||||
skipBroadcast, writeErr = srl.handleNormal(ctx, &env.Event)
|
||||
}
|
||||
|
||||
var reason string
|
||||
@@ -216,9 +232,20 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
ovw(ctx, &env.Event)
|
||||
}
|
||||
if !skipBroadcast {
|
||||
srl.notifyListeners(&env.Event)
|
||||
n := srl.notifyListeners(&env.Event)
|
||||
|
||||
// the number of notified listeners matters in ephemeral events
|
||||
if nostr.IsEphemeralKind(env.Event.Kind) {
|
||||
if n == 0 {
|
||||
ok = false
|
||||
reason = "mute: no one was listening for this"
|
||||
} else {
|
||||
reason = "broadcasted to " + strconv.Itoa(n) + " listeners"
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ok = false
|
||||
reason = writeErr.Error()
|
||||
if strings.HasPrefix(reason, "auth-required:") {
|
||||
RequestAuth(ctx)
|
||||
@@ -233,34 +260,16 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var total int64
|
||||
var hll *hyperloglog.HyperLogLog
|
||||
uneligibleForHLL := false
|
||||
|
||||
for _, filter := range env.Filters {
|
||||
srl := rl
|
||||
if rl.getSubRelayFromFilter != nil {
|
||||
srl = rl.getSubRelayFromFilter(filter)
|
||||
}
|
||||
srl := rl
|
||||
if rl.getSubRelayFromFilter != nil {
|
||||
srl = rl.getSubRelayFromFilter(env.Filter)
|
||||
}
|
||||
|
||||
if offset := nip45.HyperLogLogEventPubkeyOffsetForFilter(filter); offset != -1 && !uneligibleForHLL {
|
||||
partial, phll := srl.handleCountRequestWithHLL(ctx, ws, filter, offset)
|
||||
if phll != nil {
|
||||
if hll == nil {
|
||||
// in the first iteration (which should be the only case of the times)
|
||||
// we optimize slightly by assigning instead of merging
|
||||
hll = phll
|
||||
} else {
|
||||
hll.Merge(phll)
|
||||
}
|
||||
} else {
|
||||
// if any of the filters is uneligible then we will discard previous HLL results
|
||||
// and refuse to do HLL at all anymore for this query
|
||||
uneligibleForHLL = true
|
||||
hll = nil
|
||||
}
|
||||
total += partial
|
||||
} else {
|
||||
total += srl.handleCountRequest(ctx, ws, filter)
|
||||
}
|
||||
if offset := nip45.HyperLogLogEventPubkeyOffsetForFilter(env.Filter); offset != -1 {
|
||||
total, hll = srl.handleCountRequestWithHLL(ctx, ws, env.Filter, offset)
|
||||
} else {
|
||||
total = srl.handleCountRequest(ctx, ws, env.Filter)
|
||||
}
|
||||
|
||||
resp := nostr.CountEnvelope{
|
||||
@@ -305,10 +314,8 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
go func() {
|
||||
// when all events have been loaded from databases and dispatched
|
||||
// we can cancel the context and fire the EOSE message
|
||||
// when all events have been loaded from databases and dispatched we can fire the EOSE message
|
||||
eose.Wait()
|
||||
cancelReqCtx(nil)
|
||||
ws.WriteJSON(nostr.EOSEEnvelope(env.SubscriptionID))
|
||||
}()
|
||||
case *nostr.CloseEnvelope:
|
||||
|
||||
@@ -132,15 +132,20 @@ func (rl *Relay) removeClientAndListeners(ws *WebSocket) {
|
||||
delete(rl.clients, ws)
|
||||
}
|
||||
|
||||
func (rl *Relay) notifyListeners(event *nostr.Event) {
|
||||
// returns how many listeners were notified
|
||||
func (rl *Relay) notifyListeners(event *nostr.Event) int {
|
||||
count := 0
|
||||
listenersloop:
|
||||
for _, listener := range rl.listeners {
|
||||
if listener.filter.Matches(event) {
|
||||
for _, pb := range rl.PreventBroadcast {
|
||||
if pb(listener.ws, event) {
|
||||
return
|
||||
continue listenersloop
|
||||
}
|
||||
}
|
||||
listener.ws.WriteJSON(nostr.EventEnvelope{SubscriptionID: &listener.id, Event: *event})
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
10
nip11.go
10
nip11.go
@@ -3,6 +3,7 @@ package khatru
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (rl *Relay) HandleNIP11(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -20,6 +21,15 @@ func (rl *Relay) HandleNIP11(w http.ResponseWriter, r *http.Request) {
|
||||
info.AddSupportedNIP(77)
|
||||
}
|
||||
|
||||
// resolve relative icon and banner URLs against base URL
|
||||
baseURL := rl.getBaseURL(r)
|
||||
if info.Icon != "" && !strings.HasPrefix(info.Icon, "http://") && !strings.HasPrefix(info.Icon, "https://") {
|
||||
info.Icon = strings.TrimSuffix(baseURL, "/") + "/" + strings.TrimPrefix(info.Icon, "/")
|
||||
}
|
||||
if info.Banner != "" && !strings.HasPrefix(info.Banner, "http://") && !strings.HasPrefix(info.Banner, "https://") {
|
||||
info.Banner = strings.TrimSuffix(baseURL, "/") + "/" + strings.TrimPrefix(info.Banner, "/")
|
||||
}
|
||||
|
||||
for _, ovw := range rl.OverwriteRelayInformation {
|
||||
info = ovw(r.Context(), r, info)
|
||||
}
|
||||
|
||||
58
nip86.go
58
nip86.go
@@ -28,15 +28,21 @@ type RelayManagementAPI struct {
|
||||
AllowEvent func(ctx context.Context, id string, reason string) error
|
||||
BanEvent func(ctx context.Context, id string, reason string) error
|
||||
ListBannedEvents func(ctx context.Context) ([]nip86.IDReason, error)
|
||||
ListAllowedEvents func(ctx context.Context) ([]nip86.IDReason, error)
|
||||
ChangeRelayName func(ctx context.Context, name string) error
|
||||
ChangeRelayDescription func(ctx context.Context, desc string) error
|
||||
ChangeRelayIcon func(ctx context.Context, icon string) error
|
||||
AllowKind func(ctx context.Context, kind int) error
|
||||
DisallowKind func(ctx context.Context, kind int) error
|
||||
ListAllowedKinds func(ctx context.Context) ([]int, error)
|
||||
ListDisAllowedKinds func(ctx context.Context) ([]int, error)
|
||||
BlockIP func(ctx context.Context, ip net.IP, reason string) error
|
||||
UnblockIP func(ctx context.Context, ip net.IP, reason string) error
|
||||
ListBlockedIPs func(ctx context.Context) ([]nip86.IPReason, error)
|
||||
Stats func(ctx context.Context) (nip86.Response, error)
|
||||
GrantAdmin func(ctx context.Context, pubkey string, methods []string) error
|
||||
RevokeAdmin func(ctx context.Context, pubkey string, methods []string) error
|
||||
Generic func(ctx context.Context, request nip86.Request) (nip86.Response, error)
|
||||
}
|
||||
|
||||
func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -80,10 +86,10 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) {
|
||||
goto respond
|
||||
}
|
||||
|
||||
if uTag := evt.Tags.GetFirst([]string{"u", ""}); uTag == nil || rl.getBaseURL(r) != (*uTag)[1] {
|
||||
if uTag := evt.Tags.Find("u"); uTag == nil || rl.getBaseURL(r) != uTag[1] {
|
||||
resp.Error = "invalid 'u' tag"
|
||||
goto respond
|
||||
} else if pht := evt.Tags.GetFirst([]string{"payload", hex.EncodeToString(payloadHash[:])}); pht == nil {
|
||||
} else if pht := evt.Tags.FindWithValue("payload", hex.EncodeToString(payloadHash[:])); pht == nil {
|
||||
resp.Error = "invalid auth event payload hash"
|
||||
goto respond
|
||||
} else if evt.CreatedAt < nostr.Now()-30 {
|
||||
@@ -266,8 +272,54 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
resp.Result = result
|
||||
}
|
||||
case nip86.Stats:
|
||||
if rl.ManagementAPI.Stats == nil {
|
||||
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
|
||||
} else if result, err := rl.ManagementAPI.Stats(ctx); err != nil {
|
||||
resp.Error = err.Error()
|
||||
} else {
|
||||
resp.Result = result
|
||||
}
|
||||
case nip86.GrantAdmin:
|
||||
if rl.ManagementAPI.GrantAdmin == nil {
|
||||
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
|
||||
} else if err := rl.ManagementAPI.GrantAdmin(ctx, thing.Pubkey, thing.AllowMethods); err != nil {
|
||||
resp.Error = err.Error()
|
||||
} else {
|
||||
resp.Result = true
|
||||
}
|
||||
case nip86.RevokeAdmin:
|
||||
if rl.ManagementAPI.RevokeAdmin == nil {
|
||||
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
|
||||
} else if err := rl.ManagementAPI.RevokeAdmin(ctx, thing.Pubkey, thing.DisallowMethods); err != nil {
|
||||
resp.Error = err.Error()
|
||||
} else {
|
||||
resp.Result = true
|
||||
}
|
||||
case nip86.ListDisallowedKinds:
|
||||
if rl.ManagementAPI.ListDisAllowedKinds == nil {
|
||||
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
|
||||
} else if result, err := rl.ManagementAPI.ListDisAllowedKinds(ctx); err != nil {
|
||||
resp.Error = err.Error()
|
||||
} else {
|
||||
resp.Result = result
|
||||
}
|
||||
case nip86.ListAllowedEvents:
|
||||
if rl.ManagementAPI.ListAllowedEvents == nil {
|
||||
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
|
||||
} else if result, err := rl.ManagementAPI.ListAllowedEvents(ctx); err != nil {
|
||||
resp.Error = err.Error()
|
||||
} else {
|
||||
resp.Result = result
|
||||
}
|
||||
default:
|
||||
resp.Error = fmt.Sprintf("method '%s' not known", mp.MethodName())
|
||||
if rl.ManagementAPI.Generic == nil {
|
||||
resp.Error = fmt.Sprintf("method '%s' not known", mp.MethodName())
|
||||
} else if result, err := rl.ManagementAPI.Generic(ctx, req); err != nil {
|
||||
resp.Error = err.Error()
|
||||
} else {
|
||||
resp.Result = result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip70"
|
||||
)
|
||||
|
||||
// PreventTooManyIndexableTags returns a function that can be used as a RejectFilter that will reject
|
||||
@@ -107,3 +108,10 @@ func PreventTimestampsInTheFuture(threshold time.Duration) func(context.Context,
|
||||
func RejectEventsWithBase64Media(ctx context.Context, evt *nostr.Event) (bool, string) {
|
||||
return strings.Contains(evt.Content, "data:image/") || strings.Contains(evt.Content, "data:video/"), "event with base64 media"
|
||||
}
|
||||
|
||||
func OnlyAllowNIP70ProtectedEvents(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
|
||||
if nip70.IsProtected(*event) {
|
||||
return false, ""
|
||||
}
|
||||
return true, "blocked: we only accept events protected with the nip70 \"-\" tag"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package policies
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"slices"
|
||||
|
||||
"github.com/fiatjaf/khatru"
|
||||
|
||||
7
utils.go
7
utils.go
@@ -10,6 +10,7 @@ const (
|
||||
wsKey = iota
|
||||
subscriptionIdKey
|
||||
nip86HeaderAuthKey
|
||||
internalCallKey
|
||||
)
|
||||
|
||||
func RequestAuth(ctx context.Context) {
|
||||
@@ -40,6 +41,12 @@ func GetAuthed(ctx context.Context) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsInternalCall returns true when a call to QueryEvents, for example, is being made because of a deletion
|
||||
// or expiration request.
|
||||
func IsInternalCall(ctx context.Context) bool {
|
||||
return ctx.Value(internalCallKey) != nil
|
||||
}
|
||||
|
||||
func GetIP(ctx context.Context) string {
|
||||
conn := GetConnection(ctx)
|
||||
if conn == nil {
|
||||
|
||||
10
websocket.go
10
websocket.go
@@ -33,12 +33,14 @@ type WebSocket struct {
|
||||
|
||||
func (ws *WebSocket) WriteJSON(any any) error {
|
||||
ws.mutex.Lock()
|
||||
defer ws.mutex.Unlock()
|
||||
return ws.conn.WriteJSON(any)
|
||||
err := ws.conn.WriteJSON(any)
|
||||
ws.mutex.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (ws *WebSocket) WriteMessage(t int, b []byte) error {
|
||||
ws.mutex.Lock()
|
||||
defer ws.mutex.Unlock()
|
||||
return ws.conn.WriteMessage(t, b)
|
||||
err := ws.conn.WriteMessage(t, b)
|
||||
ws.mutex.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user