mirror of
https://github.com/fiatjaf/khatru.git
synced 2026-04-08 22:46:48 +02:00
3c802caff5d323d4a191e745441cd713e3e43c5b
This enhancement allows hooks to make extension-aware decisions for storage, loading, and access control operations. The extension is intelligently derived from MIME type detection, file magic bytes, or URL parsing. Additionally improves MIME type handling by providing fallback to "application/octet-stream" when extension-based detection fails as per BUD-01. BREAKING CHANGE: All Blossom hook functions now require an additional `ext` parameter: - StoreBlob hooks now accept (ctx, sha256, ext, body) instead of (ctx, sha256, body) - LoadBlob hooks now accept (ctx, sha256, ext) instead of (ctx, sha256) - DeleteBlob hooks now accept (ctx, sha256, ext) instead of (ctx, sha256) - RejectGet hooks now accept (ctx, auth, sha256, ext) instead of (ctx, auth, sha256) - RejectDelete hooks now accept (ctx, auth, sha256, ext) instead of (ctx, auth, sha256)
khatru, a relay framework 
Khatru makes it easy to write very very custom relays:
- custom event or filter acceptance policies
- custom
AUTHhandlers - custom storage and pluggable databases
- custom webpages and other HTTP handlers
Here's a sample:
package main
import (
"context"
"fmt"
"log"
"net/http"
"github.com/fiatjaf/khatru"
"github.com/nbd-wtf/go-nostr"
)
func main() {
// create the relay instance
relay := khatru.NewRelay()
// set up some basic properties (will be returned on the NIP-11 endpoint)
relay.Info.Name = "my relay"
relay.Info.PubKey = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
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"
// you must bring your own storage scheme -- if you want to have any
store := make(map[string]*nostr.Event, 120)
// set up the basic relay functions
relay.StoreEvent = append(relay.StoreEvent,
func(ctx context.Context, event *nostr.Event) error {
store[event.ID] = event
return nil
},
)
relay.QueryEvents = append(relay.QueryEvents,
func(ctx context.Context, filter nostr.Filter) (chan *nostr.Event, error) {
ch := make(chan *nostr.Event)
go func() {
for _, evt := range store {
if filter.Matches(evt) {
ch <- evt
}
}
close(ch)
}()
return ch, nil
},
)
relay.DeleteEvent = append(relay.DeleteEvent,
func(ctx context.Context, event *nostr.Event) error {
delete(store, event.ID)
return nil
},
)
// there are many other configurable things you can set
relay.RejectEvent = append(relay.RejectEvent,
// built-in policies
policies.ValidateKind,
// define your own policies
policies.PreventLargeTags(100),
func(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
if event.PubKey == "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52" {
return true, "we don't allow this person to write here"
}
return false, "" // anyone else can
},
)
// you can request auth by rejecting an event or a request with the prefix "auth-required: "
relay.RejectFilter = append(relay.RejectFilter,
// built-in policies
policies.NoComplexFilters,
// define your own policies
func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
if pubkey := khatru.GetAuthed(ctx); pubkey != "" {
log.Printf("request from %s\n", pubkey)
return false, ""
}
return true, "auth-required: only authenticated users can read from this relay"
// (this will cause an AUTH message to be sent and then a CLOSED message such that clients can
// authenticate and then request again)
},
)
// check the docs for more goodies!
mux := relay.Router()
// set up other http handlers
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("content-type", "text/html")
fmt.Fprintf(w, `<b>welcome</b> to my relay!`)
})
// start the server
fmt.Println("running on :3334")
http.ListenAndServe(":3334", relay)
}
But I don't want to write my own database!
Fear no more. Using the https://github.com/fiatjaf/eventstore module you get a bunch of compatible databases out of the box and you can just plug them into your relay. For example, sqlite:
db := sqlite3.SQLite3Backend{DatabaseURL: "/tmp/khatru-sqlite-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)
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:
policies.ApplySaneDefaults(relay)
Contributions to this are very much welcomed.
Languages
Go
100%