partial docs update.
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
# khatru, a relay framework [](https://pkg.go.dev/github.com/fiatjaf/khatru#Relay)
|
# khatru, a relay framework [](https://pkg.go.dev/fiatjaf.com/nostr/khatru#Relay)
|
||||||
|
|
||||||
[](https://github.com/fiatjaf/khatru/actions/workflows/test.yml)
|
[](https://github.com/fiatjaf/khatru/actions/workflows/test.yml)
|
||||||
[](https://pkg.go.dev/github.com/fiatjaf/khatru)
|
[](https://pkg.go.dev/fiatjaf.com/nostr/khatru)
|
||||||
[](https://goreportcard.com/report/github.com/fiatjaf/khatru)
|
[](https://goreportcard.com/report/fiatjaf.com/nostr/khatru)
|
||||||
|
|
||||||
Khatru makes it easy to write very very custom relays:
|
Khatru makes it easy to write very very custom relays:
|
||||||
|
|
||||||
@@ -22,8 +22,8 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/fiatjaf/khatru"
|
"fiatjaf.com/nostr"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"fiatjaf.com/nostr/khatru"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -32,12 +32,12 @@ func main() {
|
|||||||
|
|
||||||
// set up some basic properties (will be returned on the NIP-11 endpoint)
|
// set up some basic properties (will be returned on the NIP-11 endpoint)
|
||||||
relay.Info.Name = "my relay"
|
relay.Info.Name = "my relay"
|
||||||
relay.Info.PubKey = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
|
relay.Info.PubKey = nostr.MustPubKeyFromHex("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
|
||||||
relay.Info.Description = "this is my custom relay"
|
relay.Info.Description = "this is my custom relay"
|
||||||
relay.Info.Icon = "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fliquipedia.net%2Fcommons%2Fimages%2F3%2F35%2FSCProbe.jpg&f=1&nofb=1&ipt=0cbbfef25bce41da63d910e86c3c343e6c3b9d63194ca9755351bb7c2efa3359&ipo=images"
|
relay.Info.Icon = "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fliquipedia.net%2Fcommons%2Fimages%2F3%2F35%2FSCProbe.jpg&f=1&nofb=1&ipt=0cbbfef25bce41da63d910e86c3c343e6c3b9d63194ca9755351bb7c2efa3359&ipo=images"
|
||||||
|
|
||||||
// you must bring your own storage scheme -- if you want to have any
|
// you must bring your own storage scheme -- if you want to have any
|
||||||
store := make(map[string]*nostr.Event, 120)
|
store := make(map[nostr.ID]*nostr.Event, 120)
|
||||||
|
|
||||||
// set up the basic relay functions
|
// set up the basic relay functions
|
||||||
relay.StoreEvent = append(relay.StoreEvent,
|
relay.StoreEvent = append(relay.StoreEvent,
|
||||||
@@ -47,22 +47,21 @@ func main() {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
relay.QueryEvents = append(relay.QueryEvents,
|
relay.QueryEvents = append(relay.QueryEvents,
|
||||||
func(ctx context.Context, filter nostr.Filter) (chan *nostr.Event, error) {
|
func(ctx context.Context, filter nostr.Filter) (iter.Seq[*nostr.Event], error) {
|
||||||
ch := make(chan *nostr.Event)
|
return func(yield func(*nostr.Event) bool) {
|
||||||
go func() {
|
|
||||||
for _, evt := range store {
|
for _, evt := range store {
|
||||||
if filter.Matches(evt) {
|
if filter.Matches(evt) {
|
||||||
ch <- evt
|
if !yield(evt) {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close(ch)
|
}
|
||||||
}()
|
}, nil
|
||||||
return ch, nil
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
relay.DeleteEvent = append(relay.DeleteEvent,
|
relay.DeleteEvent = append(relay.DeleteEvent,
|
||||||
func(ctx context.Context, event *nostr.Event) error {
|
func(ctx context.Context, id nostr.ID) error {
|
||||||
delete(store, event.ID)
|
delete(store, id)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -75,7 +74,7 @@ func main() {
|
|||||||
// define your own policies
|
// define your own policies
|
||||||
policies.PreventLargeTags(100),
|
policies.PreventLargeTags(100),
|
||||||
func(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
|
func(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
|
||||||
if event.PubKey == "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52" {
|
if event.PubKey == nostr.MustPubKeyFromHex("fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52") {
|
||||||
return true, "we don't allow this person to write here"
|
return true, "we don't allow this person to write here"
|
||||||
}
|
}
|
||||||
return false, "" // anyone else can
|
return false, "" // anyone else can
|
||||||
@@ -89,7 +88,7 @@ func main() {
|
|||||||
|
|
||||||
// define your own policies
|
// define your own policies
|
||||||
func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
||||||
if pubkey := khatru.GetAuthed(ctx); pubkey != "" {
|
if pubkey, isAuthed := khatru.GetAuthed(ctx); isAuthed {
|
||||||
log.Printf("request from %s\n", pubkey)
|
log.Printf("request from %s\n", pubkey)
|
||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
@@ -123,16 +122,12 @@ Fear no more. Using the https://fiatjaf.com/nostr/eventstore module you get a bu
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent)
|
relay.UseEventstore(db)
|
||||||
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)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### But I don't want to write a bunch of custom policies!
|
### But I don't want to write a bunch of custom policies!
|
||||||
|
|
||||||
Fear no more. We have a bunch of common policies written in the `github.com/fiatjaf/khatru/policies` package and also a handpicked selection of base sane defaults, which you can apply with:
|
Fear no more. We have a bunch of common policies written in the `fiatjaf.com/nostr/khatru/policies` package and also a handpicked selection of base sane defaults, which you can apply with:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
policies.ApplySaneDefaults(relay)
|
policies.ApplySaneDefaults(relay)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func startPollingGame(relay *khatru.Relay) {
|
|||||||
Content: "team A has scored!",
|
Content: "team A has scored!",
|
||||||
Tags: nostr.Tags{{"t", "this-game"}}
|
Tags: nostr.Tags{{"t", "this-game"}}
|
||||||
}
|
}
|
||||||
evt.Sign(global.RelayPrivateKey)
|
evt.Sign(global.RelaySecretKey)
|
||||||
// calling BroadcastEvent will send the event to everybody who has been listening for tag "t=[this-game]"
|
// calling BroadcastEvent will send the event to everybody who has been listening for tag "t=[this-game]"
|
||||||
// there is no need to do any code to keep track of these clients or who is listening to what, khatru
|
// there is no need to do any code to keep track of these clients or who is listening to what, khatru
|
||||||
// does that already in the background automatically
|
// does that already in the background automatically
|
||||||
@@ -61,4 +61,3 @@ func startPollingGame(relay *khatru.Relay) {
|
|||||||
func fetchGameStatus() (GameStatus, error) {
|
func fetchGameStatus() (GameStatus, error) {
|
||||||
// implementation of calling some external API goes here
|
// implementation of calling some external API goes here
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|||||||
@@ -21,23 +21,19 @@ func main () {
|
|||||||
// other stuff here
|
// other stuff here
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWeatherQuery(ctx context.Context, filter nostr.Filter) (ch chan *nostr.Event, err error) {
|
func handleWeatherQuery(ctx context.Context, filter nostr.Filter) iter.Seq[nostr.Event] {
|
||||||
if filter.Kind != 10774 {
|
if filter.Kind != nostr.Kind(10774) {
|
||||||
// this function only handles kind 10774, if the query is for something else we return
|
// this function only handles kind 10774, if the query is for something else we return
|
||||||
// a nil channel, which corresponds to no results
|
// a nil channel, which corresponds to no results
|
||||||
return nil, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open("weatherdata.xml")
|
file, err := os.Open("weatherdata.xml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("we have lost our file: %w", err)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryEvents functions are expected to return a channel
|
return func(yield func(nostr.Event) bool) {
|
||||||
ch := make(chan *nostr.Event)
|
|
||||||
|
|
||||||
// and they can do their query asynchronously, emitting events to the channel as they come
|
|
||||||
go func () {
|
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
// we're going to do this for each tag in the filter
|
// we're going to do this for each tag in the filter
|
||||||
@@ -74,14 +70,14 @@ func handleWeatherQuery(ctx context.Context, filter nostr.Filter) (ch chan *nost
|
|||||||
{"condition", record[3]},
|
{"condition", record[3]},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
evt.Sign(global.RelayPrivateKey)
|
evt.Sign(global.RelaySecretKey)
|
||||||
ch <- evt
|
ch <- evt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return ch, nil
|
return ch, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ outline: deep
|
|||||||
|
|
||||||
# Implementing NIP-50 `search` support
|
# Implementing NIP-50 `search` support
|
||||||
|
|
||||||
The [`nostr.Filter` type](https://pkg.go.dev/github.com/nbd-wtf/go-nostr#Filter) has a `Search` field, so you basically just has to handle that if it's present.
|
The [`nostr.Filter` type](https://pkg.go.dev/fiatjaf.com/nostr#Filter) has a `Search` field, so you basically just has to handle that if it's present.
|
||||||
|
|
||||||
It can be tricky to implement fulltext search properly though, so some [eventstores](../core/eventstore) implement it natively, such as [Bluge](https://pkg.go.dev/fiatjaf.com/nostr/eventstore/bluge), [OpenSearch](https://pkg.go.dev/fiatjaf.com/nostr/eventstore/opensearch) and [ElasticSearch](https://pkg.go.dev/fiatjaf.com/nostr/eventstore/elasticsearch) (although for the last two you'll need an instance of these database servers running, while with Bluge it's embedded).
|
It can be tricky to implement fulltext search properly though, so some [eventstores](../core/eventstore) implement it natively, such as [Bluge](https://pkg.go.dev/fiatjaf.com/nostr/eventstore/bluge), [OpenSearch](https://pkg.go.dev/fiatjaf.com/nostr/eventstore/opensearch) and [ElasticSearch](https://pkg.go.dev/fiatjaf.com/nostr/eventstore/elasticsearch) (although for the last two you'll need an instance of these database servers running, while with Bluge it's embedded).
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ Other adapters, like [SQLite](https://pkg.go.dev/fiatjaf.com/nostr/eventstore/sq
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent, search.SaveEvent)
|
relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent, search.SaveEvent)
|
||||||
relay.QueryEvents = append(relay.QueryEvents, func (ctx context.Context, filter nostr.Filter) (chan *nostr.Event, error) {
|
relay.QueryStored = func (ctx context.Context, filter nostr.Filter) iter.Seq[nostr.Event] {
|
||||||
if len(filter.Search) > 0 {
|
if len(filter.Search) > 0 {
|
||||||
return search.QueryEvents(ctx, filter)
|
return search.QueryEvents(ctx, filter)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ After a client is authenticated and opens a new subscription with `REQ` or sends
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
relay.RejectFilter = append(relay.RejectFilter, func(ctx context.Context, filter nostr.Filter) (bool, string) {
|
relay.RejectFilter = append(relay.RejectFilter, func(ctx context.Context, filter nostr.Filter) (bool, string) {
|
||||||
authenticatedUser := khatru.GetAuthed(ctx)
|
authenticatedUser, isAuthenticated := khatru.GetAuthed(ctx)
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ You can use that to emulate a listener for these events in case you want to keep
|
|||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
fmt.Println("connection closed")
|
fmt.Println("connection closed")
|
||||||
case <-conn.Authed:
|
case <-conn.Authed:
|
||||||
fmt.Println("authenticated as", conn.AuthedPublicKey)
|
fmt.Println("authenticated as", conn.AuthedPubKey)
|
||||||
}
|
}
|
||||||
}(ctx)
|
}(ctx)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"fiatjaf.com/nostr/eventstore/badger"
|
"fiatjaf.com/nostr/eventstore/badger"
|
||||||
"github.com/fiatjaf/khatru"
|
"fiatjaf.com/nostr/khatru"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -35,11 +35,7 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent)
|
relay.UseEventstore(db)
|
||||||
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)
|
|
||||||
|
|
||||||
fmt.Println("running on :3334")
|
fmt.Println("running on :3334")
|
||||||
http.ListenAndServe(":3334", relay)
|
http.ListenAndServe(":3334", relay)
|
||||||
@@ -58,7 +54,7 @@ If you want to use two different adapters at the same time that's easy. Just add
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
relay.StoreEvent = append(relay.StoreEvent, db1.SaveEvent, db2.SaveEvent)
|
relay.StoreEvent = append(relay.StoreEvent, db1.SaveEvent, db2.SaveEvent)
|
||||||
relay.QueryEvents = append(relay.QueryEvents, db1.QueryEvents, db2.SaveEvent)
|
relay.QueryEvents = append(relay.QueryEvents, db1.QueryEvents, db2.QueryEvents)
|
||||||
```
|
```
|
||||||
|
|
||||||
But that will duplicate events on both and then return duplicated events on each query.
|
But that will duplicate events on both and then return duplicated events on each query.
|
||||||
@@ -72,28 +68,29 @@ For example, maybe you want kind 1 events in `db1` and kind 30023 events in `db3
|
|||||||
```go
|
```go
|
||||||
relay.StoreEvent = append(relay.StoreEvent, func (ctx context.Context, evt *nostr.Event) error {
|
relay.StoreEvent = append(relay.StoreEvent, func (ctx context.Context, evt *nostr.Event) error {
|
||||||
switch evt.Kind {
|
switch evt.Kind {
|
||||||
case 1:
|
case nostr.Kind(1):
|
||||||
return db1.StoreEvent(ctx, evt)
|
return db1.SaveEvent(evt)
|
||||||
case 30023:
|
case nostr.Kind(30023):
|
||||||
return db30023.StoreEvent(ctx, evt)
|
return db30023.SaveEvent(evt)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
relay.QueryEvents = append(relay.QueryEvents, func (ctx context.Context, filter nostr.Filter) (chan *nostr.Event, error) {
|
relay.QueryEvents = append(relay.QueryEvents, func (ctx context.Context, filter nostr.Filter) iter.Seq[nostr.Event] {
|
||||||
for _, kind := range filter.Kinds {
|
for _, kind := range filter.Kinds {
|
||||||
switch kind {
|
switch nostr.Kind(kind) {
|
||||||
case 1:
|
case nostr.Kind(1):
|
||||||
filter1 := filter
|
filter1 := filter
|
||||||
filter1.Kinds = []int{1}
|
filter1.Kinds = []nostr.Kind{1}
|
||||||
return db1.QueryEvents(ctx, filter1)
|
return db1.QueryEvents(filter1)
|
||||||
case 30023:
|
case nostr.Kind(30023):
|
||||||
filter30023 := filter
|
filter30023 := filter
|
||||||
filter30023.Kinds = []int{30023}
|
filter30023.Kinds = []nostr.Kind{30023}
|
||||||
return db30023.QueryEvents(ctx, filter30023)
|
return db30023.QueryEvents(filter30023)
|
||||||
default:
|
default:
|
||||||
return nil, nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ outline: deep
|
|||||||
|
|
||||||
[NIP-86](https://nips.nostr.com/86) specifies a set of RPC methods for managing the boring aspects of relays, such as whitelisting or banning users, banning individual events, banning IPs and so on.
|
[NIP-86](https://nips.nostr.com/86) specifies a set of RPC methods for managing the boring aspects of relays, such as whitelisting or banning users, banning individual events, banning IPs and so on.
|
||||||
|
|
||||||
All [`khatru.Relay`](https://pkg.go.dev/github.com/fiatjaf/khatru#Relay) instances expose a field `ManagementAPI` with a [`RelayManagementAPI`](https://pkg.go.dev/github.com/fiatjaf/khatru#RelayManagementAPI) instance inside, which can be used for creating handlers for each of the RPC methods.
|
All [`khatru.Relay`](https://pkg.go.dev/fiatjaf.com/nostr/khatru#Relay) instances expose a field `ManagementAPI` with a [`RelayManagementAPI`](https://pkg.go.dev/fiatjaf.com/nostr/khatru#RelayManagementAPI) instance inside, which can be used for creating handlers for each of the RPC methods.
|
||||||
|
|
||||||
There is also a generic `RejectAPICall` which is a slice of functions that will be called before any RPC method, if they exist and, if any of them returns true, the request will be rejected.
|
There is also a generic `RejectAPICall` which is a slice of functions that will be called before any RPC method, if they exist and, if any of them returns true, the request will be rejected.
|
||||||
|
|
||||||
The most basic implementation of a `RejectAPICall` handler would be one that checks the public key of the caller with a hardcoded public key of the relay owner:
|
The most basic implementation of a `RejectAPICall` handler would be one that checks the public key of the caller with a hardcoded public key of the relay owner:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var owner = "<my-own-pubkey>"
|
var owner = nostr.MustPubKeyFromHex("<my-own-pubkey>")
|
||||||
var allowedPubkeys = make([]string, 0, 10)
|
var allowedPubkeys = make([]nostr.PubKey, 0, 10)
|
||||||
|
|
||||||
func main () {
|
func main () {
|
||||||
relay := khatru.NewRelay()
|
relay := khatru.NewRelay()
|
||||||
@@ -29,16 +29,17 @@ func main () {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
relay.ManagementAPI.AllowPubKey = func(ctx context.Context, pubkey string, reason string) error {
|
relay.ManagementAPI.AllowPubKey = func(ctx context.Context, pubkey nostr.PubKey, reason string) error {
|
||||||
allowedPubkeys = append(allowedPubkeys, pubkey)
|
allowedPubkeys = append(allowedPubkeys, pubkey)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
relay.ManagementAPI.BanPubKey = func(ctx context.Context, pubkey string, reason string) error {
|
relay.ManagementAPI.BanPubKey = func(ctx context.Context, pubkey nostr.PubKey, reason string) error {
|
||||||
idx := slices.Index(allowedPubkeys, pubkey)
|
idx := slices.Index(allowedPubkeys, pubkey)
|
||||||
if idx == -1 {
|
if idx == -1 {
|
||||||
return fmt.Errorf("pubkey already not allowed")
|
return fmt.Errorf("pubkey already not allowed")
|
||||||
}
|
}
|
||||||
allowedPubkeys = slices.Delete(allowedPubkeys, idx, idx+1)
|
allowedPubkeys = slices.Delete(allowedPubkeys, idx, idx+1)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -48,12 +49,12 @@ You can also not provide any `RejectAPICall` handler and do the approval specifi
|
|||||||
In the following example any current member can include any other pubkey, and anyone who was added before is able to remove any pubkey that was added afterwards (not a very good idea, but serves as an example).
|
In the following example any current member can include any other pubkey, and anyone who was added before is able to remove any pubkey that was added afterwards (not a very good idea, but serves as an example).
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var allowedPubkeys = []string{"<my-own-pubkey>"}
|
var allowedPubkeys = []nostr.PubKey{nostr.MustPubKeyFromHex("<my-own-pubkey>")}
|
||||||
|
|
||||||
func main () {
|
func main () {
|
||||||
relay := khatru.NewRelay()
|
relay := khatru.NewRelay()
|
||||||
|
|
||||||
relay.ManagementAPI.AllowPubKey = func(ctx context.Context, pubkey string, reason string) error {
|
relay.ManagementAPI.AllowPubKey = func(ctx context.Context, pubkey nostr.PubKey, reason string) error {
|
||||||
caller := khatru.GetAuthed(ctx)
|
caller := khatru.GetAuthed(ctx)
|
||||||
|
|
||||||
if slices.Contains(allowedPubkeys, caller) {
|
if slices.Contains(allowedPubkeys, caller) {
|
||||||
@@ -63,7 +64,7 @@ func main () {
|
|||||||
|
|
||||||
return fmt.Errorf("you're not authorized")
|
return fmt.Errorf("you're not authorized")
|
||||||
}
|
}
|
||||||
relay.ManagementAPI.BanPubKey = func(ctx context.Context, pubkey string, reason string) error {
|
relay.ManagementAPI.BanPubKey = func(ctx context.Context, pubkey nostr.PubKey, reason string) error {
|
||||||
caller := khatru.GetAuthed(ctx)
|
caller := khatru.GetAuthed(ctx)
|
||||||
|
|
||||||
callerIdx := slices.Index(allowedPubkeys, caller)
|
callerIdx := slices.Index(allowedPubkeys, caller)
|
||||||
@@ -82,4 +83,3 @@ func main () {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ outline: deep
|
|||||||
Download the library:
|
Download the library:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get github.com/fiatjaf/khatru
|
go get fiatjaf.com/nostr/khatru
|
||||||
```
|
```
|
||||||
|
|
||||||
Include the library:
|
Include the library:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/fiatjaf/khatru"
|
import "fiatjaf.com/nostr/khatru"
|
||||||
```
|
```
|
||||||
|
|
||||||
Then in your `main()` function, instantiate a new `Relay`:
|
Then in your `main()` function, instantiate a new `Relay`:
|
||||||
@@ -26,7 +26,7 @@ Optionally, set up basic info about the relay that will be returned according to
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
relay.Info.Name = "my relay"
|
relay.Info.Name = "my relay"
|
||||||
relay.Info.PubKey = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
|
relay.Info.PubKey = nostr.MustPubKeyFromHex("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
|
||||||
relay.Info.Description = "this is my custom relay"
|
relay.Info.Description = "this is my custom relay"
|
||||||
relay.Info.Icon = "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fliquipedia.net%2Fcommons%2Fimages%2F3%2F35%2FSCProbe.jpg&f=1&nofb=1&ipt=0cbbfef25bce41da63d910e86c3c343e6c3b9d63194ca9755351bb7c2efa3359&ipo=images"
|
relay.Info.Icon = "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fliquipedia.net%2Fcommons%2Fimages%2F3%2F35%2FSCProbe.jpg&f=1&nofb=1&ipt=0cbbfef25bce41da63d910e86c3c343e6c3b9d63194ca9755351bb7c2efa3359&ipo=images"
|
||||||
```
|
```
|
||||||
@@ -39,10 +39,7 @@ if err := db.Init(); err != nil {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent)
|
relay.UseEventstore(db)
|
||||||
relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents)
|
|
||||||
relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent)
|
|
||||||
relay.ReplaceEvent = append(relay.ReplaceEvent, db.ReplaceEvent)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
These are lists of functions that will be called in order every time an `EVENT` is received, or a `REQ` query is received. You can add more than one handler there, you can have a function that reads from some other server, but just in some cases, you can do anything.
|
These are lists of functions that will be called in order every time an `EVENT` is received, or a `REQ` query is received. You can add more than one handler there, you can have a function that reads from some other server, but just in some cases, you can do anything.
|
||||||
@@ -51,7 +48,7 @@ The next step is adding some protection, because maybe we don't want to allow _a
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
relay.RejectEvent = append(relay.RejectEvent, func (ctx context.Context, event *nostr.Event) (reject bool, msg string) {
|
relay.RejectEvent = append(relay.RejectEvent, func (ctx context.Context, event *nostr.Event) (reject bool, msg string) {
|
||||||
firstHexChar := event.PubKey[0:1]
|
firstHexChar := event.PubKey.Hex()[0:1]
|
||||||
if firstHexChar == "a" || firstHexChar == "b" || firstHexChar == "c" {
|
if firstHexChar == "a" || firstHexChar == "b" || firstHexChar == "c" {
|
||||||
return false, "" // allow
|
return false, "" // allow
|
||||||
}
|
}
|
||||||
@@ -62,12 +59,12 @@ relay.RejectEvent = append(relay.RejectEvent, func (ctx context.Context, event *
|
|||||||
We can also make use of some default policies that come bundled with Khatru:
|
We can also make use of some default policies that come bundled with Khatru:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/fiatjaf/khatru" // implied
|
import "fiatjaf.com/nostr/khatru/policies" // implied
|
||||||
|
|
||||||
relay.RejectEvent = append(relay.RejectEvent, policies.PreventLargeTags(120), policies.PreventTimestampsInThePast(time.Hour * 2), policies.PreventTimestampsInTheFuture(time.Minute * 30))
|
relay.RejectEvent = append(relay.RejectEvent, policies.PreventLargeTags(120), policies.PreventTimestampsInThePast(time.Hour * 2), policies.PreventTimestampsInTheFuture(time.Minute * 30))
|
||||||
```
|
```
|
||||||
|
|
||||||
There are many other ways to customize the relay behavior. Take a look at the [`Relay` struct docs](https://pkg.go.dev/github.com/fiatjaf/khatru#Relay) for more, or read the pages on the sidebar.
|
There are many other ways to customize the relay behavior. Take a look at the [`Relay` struct docs](https://pkg.go.dev/fiatjaf.com/nostr/khatru#Relay) for more, or read the pages on the sidebar.
|
||||||
|
|
||||||
The last step is actually running the server. Our relay is actually an `http.Handler`, so it can just be ran directly with `http.ListenAndServe()` from the standard library:
|
The last step is actually running the server. Our relay is actually an `http.Handler`, so it can just be ran directly with `http.ListenAndServe()` from the standard library:
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func NewBadgerHints(path string) (*BadgerHints, error) {
|
|||||||
opts.Logger = nil
|
opts.Logger = nil
|
||||||
db, err := badger.Open(opts)
|
db, err := badger.Open(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to open badger db: %w", err)
|
||||||
}
|
}
|
||||||
return &BadgerHints{db: db}, nil
|
return &BadgerHints{db: db}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user