mirror of
https://github.com/fiatjaf/khatru.git
synced 2025-09-17 16:53:32 +02:00
relayer, second attempt, now much better.
This commit is contained in:
26
examples/basic-badgern/main.go
Normal file
26
examples/basic-badgern/main.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/fiatjaf/khatru"
|
||||
"github.com/fiatjaf/khatru/plugins/storage/badgern"
|
||||
)
|
||||
|
||||
func main() {
|
||||
relay := khatru.NewRelay()
|
||||
|
||||
db := badgern.BadgerBackend{Path: "/tmp/khatru-badgern-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)
|
||||
|
||||
fmt.Println("running on :3334")
|
||||
http.ListenAndServe(":3334", relay)
|
||||
}
|
26
examples/basic-elasticsearch/main.go
Normal file
26
examples/basic-elasticsearch/main.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/fiatjaf/khatru"
|
||||
"github.com/fiatjaf/khatru/plugins/storage/elasticsearch"
|
||||
)
|
||||
|
||||
func main() {
|
||||
relay := khatru.NewRelay()
|
||||
|
||||
db := elasticsearch.ElasticsearchStorage{URL: ""}
|
||||
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)
|
||||
|
||||
fmt.Println("running on :3334")
|
||||
http.ListenAndServe(":3334", relay)
|
||||
}
|
28
examples/basic-lmdbn/main.go
Normal file
28
examples/basic-lmdbn/main.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/fiatjaf/khatru"
|
||||
"github.com/fiatjaf/khatru/plugins/storage/lmdbn"
|
||||
)
|
||||
|
||||
func main() {
|
||||
relay := khatru.NewRelay()
|
||||
|
||||
db := lmdbn.LMDBBackend{Path: "/tmp/khatru-lmdbn-tmp"}
|
||||
os.MkdirAll(db.Path, 0755)
|
||||
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)
|
||||
|
||||
fmt.Println("running on :3334")
|
||||
http.ListenAndServe(":3334", relay)
|
||||
}
|
26
examples/basic-postgres/main.go
Normal file
26
examples/basic-postgres/main.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/fiatjaf/khatru"
|
||||
"github.com/fiatjaf/khatru/plugins/storage/postgresql"
|
||||
)
|
||||
|
||||
func main() {
|
||||
relay := khatru.NewRelay()
|
||||
|
||||
db := postgresql.PostgresBackend{DatabaseURL: "postgresql://localhost:5432/tmp-khatru-relay"}
|
||||
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)
|
||||
|
||||
fmt.Println("running on :3334")
|
||||
http.ListenAndServe(":3334", relay)
|
||||
}
|
26
examples/basic-sqlite3/main.go
Normal file
26
examples/basic-sqlite3/main.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/fiatjaf/khatru"
|
||||
"github.com/fiatjaf/khatru/plugins/storage/sqlite3"
|
||||
)
|
||||
|
||||
func main() {
|
||||
relay := khatru.NewRelay()
|
||||
|
||||
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)
|
||||
|
||||
fmt.Println("running on :3334")
|
||||
http.ListenAndServe(":3334", relay)
|
||||
}
|
1
examples/basic/.gitignore
vendored
1
examples/basic/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
relayer-basic
|
@@ -1,8 +0,0 @@
|
||||
FROM golang:1.18
|
||||
|
||||
WORKDIR /go/src/app
|
||||
COPY ./ .
|
||||
|
||||
RUN go get -d -v ./...
|
||||
RUN go install -v ./...
|
||||
RUN cd basic && make
|
@@ -1,2 +0,0 @@
|
||||
relayer-basic: $(shell find .. -name "*.go")
|
||||
CC=$$(which musl-gcc) go build -ldflags='-s -w -linkmode external -extldflags "-static"' -o ./relayer-basic
|
@@ -1,24 +0,0 @@
|
||||
relayer basic
|
||||
=============
|
||||
|
||||
- a basic relay implementation based on relayer.
|
||||
- uses postgres, which I think must be over version 12 since it uses generated columns.
|
||||
- it has some antispam limits, tries to delete old stuff so things don't get out of control, and some other small optimizations.
|
||||
|
||||
running
|
||||
-------
|
||||
|
||||
grab a binary from the releases page and run it with the environment variable POSTGRESQL_DATABASE set to some postgres url:
|
||||
|
||||
POSTGRESQL_DATABASE=postgres://name:pass@localhost:5432/dbname ./relayer-basic
|
||||
|
||||
it also accepts a HOST and a PORT environment variables.
|
||||
|
||||
compiling
|
||||
---------
|
||||
|
||||
if you know Go you already know this:
|
||||
|
||||
go install github.com/fiatjaf/relayer/basic
|
||||
|
||||
or something like that.
|
@@ -1,32 +0,0 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
|
||||
relay:
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: ./basic/Dockerfile
|
||||
environment:
|
||||
PORT: 2700
|
||||
POSTGRESQL_DATABASE: postgres://nostr:nostr@postgres:5432/nostr?sslmode=disable
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- 2700:2700
|
||||
command: "./basic/relayer-basic"
|
||||
|
||||
postgres:
|
||||
image: postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_DB: nostr
|
||||
POSTGRES_USER: nostr
|
||||
POSTGRES_PASSWORD: nostr
|
||||
POSTGRES_HOST_AUTH_METHOD: trust # allow all connections without a password. This is *not* recommended for prod
|
||||
ports:
|
||||
- 5432:5432
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U nostr"] # database username here - nostr, should be changed if other user
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
@@ -1,73 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/fiatjaf/relayer/v2"
|
||||
"github.com/fiatjaf/relayer/v2/storage/postgresql"
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
type Relay struct {
|
||||
PostgresDatabase string `envconfig:"POSTGRESQL_DATABASE"`
|
||||
|
||||
storage *postgresql.PostgresBackend
|
||||
}
|
||||
|
||||
func (r *Relay) Name() string {
|
||||
return "BasicRelay"
|
||||
}
|
||||
|
||||
func (r *Relay) Storage(ctx context.Context) relayer.Storage {
|
||||
return r.storage
|
||||
}
|
||||
|
||||
func (r *Relay) Init() error {
|
||||
err := envconfig.Process("", r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't process envconfig: %w", err)
|
||||
}
|
||||
|
||||
// every hour, delete all very old events
|
||||
go func() {
|
||||
db := r.Storage(context.TODO()).(*postgresql.PostgresBackend)
|
||||
|
||||
for {
|
||||
time.Sleep(60 * time.Minute)
|
||||
db.DB.Exec(`DELETE FROM event WHERE created_at < $1`, time.Now().AddDate(0, -3, 0).Unix()) // 3 months
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Relay) AcceptEvent(ctx context.Context, evt *nostr.Event) bool {
|
||||
// block events that are too large
|
||||
jsonb, _ := json.Marshal(evt)
|
||||
if len(jsonb) > 10000 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := Relay{}
|
||||
if err := envconfig.Process("", &r); err != nil {
|
||||
log.Fatalf("failed to read from env: %v", err)
|
||||
return
|
||||
}
|
||||
r.storage = &postgresql.PostgresBackend{DatabaseURL: r.PostgresDatabase}
|
||||
server, err := relayer.NewServer(&r)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create server: %v", err)
|
||||
}
|
||||
if err := server.Start("0.0.0.0", 7447); err != nil {
|
||||
log.Fatalf("server terminated: %v", err)
|
||||
}
|
||||
}
|
40
examples/exclusive/main.go
Normal file
40
examples/exclusive/main.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/fiatjaf/khatru"
|
||||
"github.com/fiatjaf/khatru/plugins"
|
||||
"github.com/fiatjaf/khatru/plugins/storage/lmdbn"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
func main() {
|
||||
relay := khatru.NewRelay()
|
||||
|
||||
db := lmdbn.LMDBBackend{Path: "/tmp/exclusive"}
|
||||
os.MkdirAll(db.Path, 0755)
|
||||
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.RejectEvent = append(relay.RejectEvent, plugins.PreventTooManyIndexableTags(10))
|
||||
relay.RejectFilter = append(relay.RejectFilter, plugins.NoPrefixFilters, plugins.NoComplexFilters)
|
||||
|
||||
relay.OnEventSaved = append(relay.OnEventSaved, func(ctx context.Context, event *nostr.Event) {
|
||||
})
|
||||
|
||||
fmt.Println("running on :3334")
|
||||
http.ListenAndServe(":3334", relay)
|
||||
}
|
||||
|
||||
func deleteStuffThatCanBeFoundElsewhere() {
|
||||
}
|
1
examples/expensive/.gitignore
vendored
1
examples/expensive/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
relayer-expensive
|
@@ -1,2 +0,0 @@
|
||||
relayer-expensive: $(shell find .. -name "*.go")
|
||||
CC=$$(which musl-gcc) go build -ldflags='-s -w -linkmode external -extldflags "-static"' -o ./relayer-expensive
|
@@ -1,31 +0,0 @@
|
||||
expensive-relay, a sybil-free corner of nostr
|
||||
=============================================
|
||||
|
||||
- a nostr relay implementation based on relayer.
|
||||
- uses postgres, which I think must be over version 12 since it uses generated columns.
|
||||
- requires users to manually register themselves to be able to publish events and pay a fee. this should prevent spam.
|
||||
- aside from that it's basically the same thing as relayer basic.
|
||||
|
||||
running
|
||||
-------
|
||||
|
||||
this requires a recent CLN version with Commando.
|
||||
|
||||
grab a binary from the releases page and run it with the following environment variables:
|
||||
|
||||
POSTGRESQL_DATABASE=postgresql://...
|
||||
CLN_NODE_ID=02fed8723...
|
||||
CLN_HOST=127.0.0.1:9735
|
||||
CLN_RUNE=...
|
||||
TICKET_PRICE_SATS=500
|
||||
|
||||
adjust the values above accordingly.
|
||||
|
||||
compiling
|
||||
---------
|
||||
|
||||
if you know Go you already know this:
|
||||
|
||||
go install github.com/fiatjaf/relayer/expensive
|
||||
|
||||
or something like that.
|
@@ -1,68 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func handleWebpage(w http.ResponseWriter, rq *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.Write([]byte(`
|
||||
<meta charset=utf-8>
|
||||
<title>expensive relay</title>
|
||||
<h1>expensive relay</h1>
|
||||
<a href="https://github.com/fiatjaf/expensive-relay">https://github.com/fiatjaf/expensive-relay</a>
|
||||
<p>this is a nostr relay that only accepts events published from keys that pay a registration fee. this is an antispam measure. you can still be banned if you're spamming or doing something bad.</p>
|
||||
<p>to register your nostr public key, type it below and click the button.</p>
|
||||
<form>
|
||||
<label>
|
||||
nostr public key:
|
||||
<input name=pubkey />
|
||||
</label>
|
||||
<button>Get Invoice</button>
|
||||
</form>
|
||||
<p id=message></p>
|
||||
<a id=link><canvas id=qr /></a>
|
||||
<code id=invoice></code>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrious/4.0.2/qrious.min.js"></script>
|
||||
<script>
|
||||
document.querySelector('form').addEventListener('submit', async ev => {
|
||||
ev.preventDefault()
|
||||
let res = await (await fetch('/invoice?pubkey=' + ev.target.pubkey.value)).text()
|
||||
let { bolt11, error } = JSON.parse(res)
|
||||
if (bolt11) {
|
||||
invoice.innerHTML = bolt11
|
||||
link.href = 'lightning:' + bolt11
|
||||
new QRious({
|
||||
element: qr,
|
||||
value: bolt11.toUpperCase(),
|
||||
size: 300
|
||||
});
|
||||
} else {
|
||||
message.innerHTML = error
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
margin: 10px auto;
|
||||
width: 800px;
|
||||
max-width: 90%;
|
||||
}
|
||||
</style>
|
||||
`))
|
||||
}
|
||||
|
||||
func handleInvoice(w http.ResponseWriter, rq *http.Request, r *Relay) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
invoice, err := generateInvoice(r, rq.URL.Query().Get("pubkey"))
|
||||
if err != nil {
|
||||
json.NewEncoder(w).Encode(struct {
|
||||
Error string `json:"error"`
|
||||
}{err.Error()})
|
||||
} else {
|
||||
json.NewEncoder(w).Encode(struct {
|
||||
Invoice string `json:"bolt11"`
|
||||
}{invoice})
|
||||
}
|
||||
}
|
@@ -1,88 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
lnsocket "github.com/jb55/lnsocket/go"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func generateLabel(pubkey string) string { return fmt.Sprintf("relayer-expensive:ticket:%s", pubkey) }
|
||||
|
||||
func generateInvoice(r *Relay, pubkey string) (string, error) {
|
||||
label := generateLabel(pubkey)
|
||||
cln := lnsocket.LNSocket{}
|
||||
cln.GenKey()
|
||||
|
||||
err := cln.ConnectAndInit(r.CLNHost, r.CLNNodeId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer cln.Disconnect()
|
||||
|
||||
// check if there is an invoice already
|
||||
jparams, _ := json.Marshal(map[string]any{
|
||||
"label": label,
|
||||
})
|
||||
result, _ := cln.Rpc(r.CLNRune, "listinvoices", string(jparams))
|
||||
if gjson.Get(result, "result.invoices.#").Int() == 1 {
|
||||
timestamp := time.Now().Unix()
|
||||
if (gjson.Get(result, "result.invoices.0.expires_at").Int() > timestamp) {
|
||||
return gjson.Get(result, "result.invoices.0.bolt11").String(), nil
|
||||
}
|
||||
jparams, _ := json.Marshal(map[string]any{
|
||||
"label": label,
|
||||
"status": "expired",
|
||||
})
|
||||
cln.Rpc(r.CLNRune, "delinvoice", string(jparams))
|
||||
}
|
||||
|
||||
// otherwise generate an invoice
|
||||
jparams, _ = json.Marshal(map[string]any{
|
||||
"amount_msat": r.TicketPriceSats * 1000,
|
||||
"label": label,
|
||||
"description": fmt.Sprintf("%s's ticket for writing to relayer-expensive", pubkey),
|
||||
})
|
||||
result, err = cln.Rpc(r.CLNRune, "invoice", string(jparams))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resErr := gjson.Get(result, "error")
|
||||
if resErr.Type != gjson.Null {
|
||||
if resErr.Type == gjson.JSON {
|
||||
return "", errors.New(resErr.Get("message").String())
|
||||
} else if resErr.Type == gjson.String {
|
||||
return "", errors.New(resErr.String())
|
||||
}
|
||||
return "", fmt.Errorf("Unknown commando error: '%v'", resErr)
|
||||
}
|
||||
|
||||
invoice := gjson.Get(result, "result.bolt11")
|
||||
if invoice.Type != gjson.String {
|
||||
return "", fmt.Errorf("No bolt11 result found in invoice response, got %v", result)
|
||||
}
|
||||
|
||||
return invoice.String(), nil
|
||||
}
|
||||
|
||||
func checkInvoicePaidOk(pubkey string) bool {
|
||||
cln := lnsocket.LNSocket{}
|
||||
cln.GenKey()
|
||||
|
||||
err := cln.ConnectAndInit(r.CLNHost, r.CLNNodeId)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer cln.Disconnect()
|
||||
|
||||
jparams, _ := json.Marshal(map[string]any{
|
||||
"label": generateLabel(pubkey),
|
||||
})
|
||||
result, _ := cln.Rpc(r.CLNRune, "listinvoices", string(jparams))
|
||||
|
||||
return gjson.Get(result, "result.invoices.0.status").String() == "paid"
|
||||
}
|
@@ -1,85 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/fiatjaf/relayer/v2"
|
||||
"github.com/fiatjaf/relayer/v2/storage/postgresql"
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
type Relay struct {
|
||||
PostgresDatabase string `envconfig:"POSTGRESQL_DATABASE"`
|
||||
CLNNodeId string `envconfig:"CLN_NODE_ID"`
|
||||
CLNHost string `envconfig:"CLN_HOST"`
|
||||
CLNRune string `envconfig:"CLN_RUNE"`
|
||||
TicketPriceSats int64 `envconfig:"TICKET_PRICE_SATS"`
|
||||
|
||||
storage *postgresql.PostgresBackend
|
||||
}
|
||||
|
||||
var r = &Relay{}
|
||||
|
||||
func (r *Relay) Name() string {
|
||||
return "ExpensiveRelay"
|
||||
}
|
||||
|
||||
func (r *Relay) Storage(ctx context.Context) relayer.Storage {
|
||||
return r.storage
|
||||
}
|
||||
|
||||
func (r *Relay) Init() error {
|
||||
// every hour, delete all very old events
|
||||
go func() {
|
||||
db := r.Storage(context.TODO()).(*postgresql.PostgresBackend)
|
||||
|
||||
for {
|
||||
time.Sleep(60 * time.Minute)
|
||||
db.DB.Exec(`DELETE FROM event WHERE created_at < $1`, time.Now().AddDate(0, -3, 0).Unix()) // 6 months
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Relay) AcceptEvent(ctx context.Context, evt *nostr.Event) bool {
|
||||
// only accept they have a good preimage for a paid invoice for their public key
|
||||
if !checkInvoicePaidOk(evt.PubKey) {
|
||||
return false
|
||||
}
|
||||
|
||||
// block events that are too large
|
||||
jsonb, _ := json.Marshal(evt)
|
||||
if len(jsonb) > 100000 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := Relay{}
|
||||
if err := envconfig.Process("", &r); err != nil {
|
||||
log.Fatalf("failed to read from env: %v", err)
|
||||
return
|
||||
}
|
||||
r.storage = &postgresql.PostgresBackend{DatabaseURL: r.PostgresDatabase}
|
||||
server, err := relayer.NewServer(&r)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create server: %v", err)
|
||||
}
|
||||
// special handlers
|
||||
server.Router().HandleFunc("/", handleWebpage)
|
||||
server.Router().HandleFunc("/invoice", func(w http.ResponseWriter, rq *http.Request) {
|
||||
handleInvoice(w, rq, &r)
|
||||
})
|
||||
if err := server.Start("0.0.0.0", 7447); err != nil {
|
||||
log.Fatalf("server terminated: %v", err)
|
||||
}
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
DOMAIN=dev2.hazlitt.fiatjaf.com
|
||||
SECRET=ie32uyg48o72iv
|
||||
HOST=0.0.0.0
|
||||
PORT=3002
|
2
examples/rss-bridge/.gitignore
vendored
2
examples/rss-bridge/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
relayer-rss-bridge
|
||||
db
|
@@ -1,2 +0,0 @@
|
||||
relayer-rss-bridge: $(shell find . -name "*.go")
|
||||
CC=$$(which musl-gcc) go build -ldflags="-s -w -linkmode external -extldflags '-static'" -o ./relayer-rss-bridge
|
@@ -1,26 +0,0 @@
|
||||
rss-bridge, a relay that creates virtual nostr profiles for each rss feed
|
||||
=========================================================================
|
||||
|
||||
- a nostr relay implementation based on relayer.
|
||||
- doesn't accept any events, only emits them.
|
||||
- does so by manually reading and parsing rss feeds.
|
||||
|
||||

|
||||
|
||||
running
|
||||
-------
|
||||
|
||||
grab a binary from the releases page and run it with the following environment variable:
|
||||
|
||||
SECRET=just-a-random-string-to-be-used-when-generating-the-virtual-private-keys
|
||||
|
||||
it will create a local database file to store the currently known rss feed urls.
|
||||
|
||||
compiling
|
||||
---------
|
||||
|
||||
if you know Go you already know this:
|
||||
|
||||
go install github.com/fiatjaf/relayer/rss-bridge
|
||||
|
||||
or something like that.
|
Binary file not shown.
@@ -1 +0,0 @@
|
||||
MANIFEST-000164
|
Binary file not shown.
Binary file not shown.
@@ -1,43 +0,0 @@
|
||||
[Version]
|
||||
pebble_version=0.1
|
||||
|
||||
[Options]
|
||||
bytes_per_sync=524288
|
||||
cache_size=8388608
|
||||
cleaner=delete
|
||||
compaction_debt_concurrency=1073741824
|
||||
comparer=leveldb.BytewiseComparator
|
||||
delete_range_flush_delay=0s
|
||||
disable_wal=false
|
||||
flush_split_bytes=4194304
|
||||
format_major_version=1
|
||||
l0_compaction_concurrency=10
|
||||
l0_compaction_threshold=4
|
||||
l0_stop_writes_threshold=12
|
||||
lbase_max_bytes=67108864
|
||||
max_concurrent_compactions=1
|
||||
max_manifest_file_size=134217728
|
||||
max_open_files=1000
|
||||
mem_table_size=4194304
|
||||
mem_table_stop_writes_threshold=2
|
||||
min_compaction_rate=4194304
|
||||
min_deletion_rate=0
|
||||
min_flush_rate=1048576
|
||||
merger=pebble.concatenate
|
||||
read_compaction_rate=16000
|
||||
read_sampling_multiplier=16
|
||||
strict_wal_tail=true
|
||||
table_cache_shards=4
|
||||
table_property_collectors=[]
|
||||
validate_on_ingest=false
|
||||
wal_dir=
|
||||
wal_bytes_per_sync=0
|
||||
|
||||
[Level "0"]
|
||||
block_restart_interval=16
|
||||
block_size=4096
|
||||
compression=Snappy
|
||||
filter_policy=none
|
||||
filter_type=table
|
||||
index_block_size=4096
|
||||
target_file_size=2097152
|
@@ -1,157 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
strip "github.com/grokify/html-strip-tags-go"
|
||||
"github.com/mmcdole/gofeed"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/rif/cache2go"
|
||||
)
|
||||
|
||||
var (
|
||||
fp = gofeed.NewParser()
|
||||
feedCache = cache2go.New(512, time.Minute*19)
|
||||
client = &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
)
|
||||
|
||||
type Entity struct {
|
||||
PrivateKey string
|
||||
URL string
|
||||
}
|
||||
|
||||
var types = []string{
|
||||
"rss+xml",
|
||||
"atom+xml",
|
||||
"feed+json",
|
||||
"text/xml",
|
||||
"application/xml",
|
||||
}
|
||||
|
||||
func getFeedURL(url string) string {
|
||||
resp, err := client.Get(url)
|
||||
if err != nil || resp.StatusCode >= 300 {
|
||||
return ""
|
||||
}
|
||||
|
||||
ct := resp.Header.Get("Content-Type")
|
||||
for _, typ := range types {
|
||||
if strings.Contains(ct, typ) {
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(ct, "text/html") {
|
||||
doc, err := goquery.NewDocumentFromReader(resp.Body)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, typ := range types {
|
||||
href, _ := doc.Find(fmt.Sprintf("link[type*='%s']", typ)).Attr("href")
|
||||
if href == "" {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(href, "http") {
|
||||
href, _ = urljoin(url, href)
|
||||
}
|
||||
return href
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseFeed(url string) (*gofeed.Feed, error) {
|
||||
if feed, ok := feedCache.Get(url); ok {
|
||||
return feed.(*gofeed.Feed), nil
|
||||
}
|
||||
|
||||
feed, err := fp.ParseURL(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// cleanup a little so we don't store too much junk
|
||||
for i := range feed.Items {
|
||||
feed.Items[i].Content = ""
|
||||
}
|
||||
feedCache.Set(url, feed)
|
||||
|
||||
return feed, nil
|
||||
}
|
||||
|
||||
func feedToSetMetadata(pubkey string, feed *gofeed.Feed) nostr.Event {
|
||||
metadata := map[string]string{
|
||||
"name": feed.Title,
|
||||
"about": feed.Description + "\n\n" + feed.Link,
|
||||
}
|
||||
if feed.Image != nil {
|
||||
metadata["picture"] = feed.Image.URL
|
||||
}
|
||||
content, _ := json.Marshal(metadata)
|
||||
|
||||
createdAt := time.Now()
|
||||
if feed.PublishedParsed != nil {
|
||||
createdAt = *feed.PublishedParsed
|
||||
}
|
||||
|
||||
evt := nostr.Event{
|
||||
PubKey: pubkey,
|
||||
CreatedAt: nostr.Timestamp(createdAt.Unix()),
|
||||
Kind: nostr.KindSetMetadata,
|
||||
Tags: nostr.Tags{},
|
||||
Content: string(content),
|
||||
}
|
||||
evt.ID = string(evt.Serialize())
|
||||
|
||||
return evt
|
||||
}
|
||||
|
||||
func itemToTextNote(pubkey string, item *gofeed.Item) nostr.Event {
|
||||
content := ""
|
||||
if item.Title != "" {
|
||||
content = "**" + item.Title + "**\n\n"
|
||||
}
|
||||
content += strip.StripTags(item.Description)
|
||||
if len(content) > 250 {
|
||||
content += content[0:249] + "…"
|
||||
}
|
||||
content += "\n\n" + item.Link
|
||||
|
||||
createdAt := time.Now()
|
||||
if item.UpdatedParsed != nil {
|
||||
createdAt = *item.UpdatedParsed
|
||||
}
|
||||
if item.PublishedParsed != nil {
|
||||
createdAt = *item.PublishedParsed
|
||||
}
|
||||
|
||||
evt := nostr.Event{
|
||||
PubKey: pubkey,
|
||||
CreatedAt: nostr.Timestamp(createdAt.Unix()),
|
||||
Kind: nostr.KindTextNote,
|
||||
Tags: nostr.Tags{},
|
||||
Content: content,
|
||||
}
|
||||
evt.ID = string(evt.Serialize())
|
||||
|
||||
return evt
|
||||
}
|
||||
|
||||
func privateKeyFromFeed(url string) string {
|
||||
m := hmac.New(sha256.New, []byte(relay.Secret))
|
||||
m.Write([]byte(url))
|
||||
r := m.Sum(nil)
|
||||
return hex.EncodeToString(r)
|
||||
}
|
@@ -1,130 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
. "github.com/stevelacy/daz"
|
||||
)
|
||||
|
||||
var head = H("head",
|
||||
H("meta", Attr{"charset": "utf-8"}),
|
||||
H("meta", Attr{
|
||||
"name": "viewport",
|
||||
"content": "width=device-width, initial-scale=1.0",
|
||||
}),
|
||||
H("title", "rsslay"),
|
||||
)
|
||||
|
||||
func handleWebpage(w http.ResponseWriter, r *http.Request) {
|
||||
items := make([]HTML, 0, 200)
|
||||
iter := relay.db.NewIter(nil)
|
||||
for iter.First(); iter.Valid(); iter.Next() {
|
||||
pubkey := string(iter.Key())
|
||||
var entity Entity
|
||||
if err := json.Unmarshal(iter.Value(), &entity); err != nil {
|
||||
continue
|
||||
}
|
||||
items = append(items, H("tr",
|
||||
H("td",
|
||||
H("code",
|
||||
pubkey),
|
||||
),
|
||||
H("td",
|
||||
H("a", Attr{
|
||||
"href": entity.URL,
|
||||
}, entity.URL),
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
body := H("body",
|
||||
H("h1", "rsslay"),
|
||||
H("p", "rsslay turns RSS or Atom feeds into ",
|
||||
H("a", Attr{
|
||||
"href": "https://github.com/fiatjaf/nostr",
|
||||
}, "Nostr"),
|
||||
" profiles.",
|
||||
),
|
||||
H("h2", "How to use"),
|
||||
H("ol",
|
||||
H("li", "Get the blog URL or RSS or Atom feed URL and paste below;"),
|
||||
H("li", "Click the button to get its corresponding public key"),
|
||||
H("li", "Add this relay to your Nostr client"),
|
||||
H("li", "Follow the feed's public key from your Nostr client."),
|
||||
),
|
||||
H("form", Attr{
|
||||
"action": "/create",
|
||||
"method": "GET",
|
||||
"class": "my-4",
|
||||
},
|
||||
H("label",
|
||||
H("input", Attr{
|
||||
"name": "url",
|
||||
"type": "url",
|
||||
"placeholder": "https://.../feed",
|
||||
}),
|
||||
),
|
||||
H("button", "Get Public Key"),
|
||||
),
|
||||
|
||||
H("h2", "Some of the existing feeds"),
|
||||
H("table", items),
|
||||
H("h2", "Source Code"),
|
||||
H("p", "You can find it at ",
|
||||
H("a", Attr{"href": "https://github.com/fiatjaf/rsslay"},
|
||||
"https://github.com/fiatjaf/rsslay"),
|
||||
),
|
||||
)
|
||||
|
||||
w.Header().Set("content-type", "text/html")
|
||||
w.Write([]byte(
|
||||
H("html",
|
||||
head,
|
||||
body,
|
||||
)()))
|
||||
}
|
||||
|
||||
func handleCreateFeed(w http.ResponseWriter, r *http.Request) {
|
||||
url := r.URL.Query().Get("url")
|
||||
|
||||
feedurl := getFeedURL(url)
|
||||
if feedurl == "" {
|
||||
w.WriteHeader(400)
|
||||
fmt.Fprint(w, "couldn't find a feed url")
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := parseFeed(feedurl); err != nil {
|
||||
w.WriteHeader(400)
|
||||
fmt.Fprint(w, "bad feed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
sk := privateKeyFromFeed(feedurl)
|
||||
pubkey, err := nostr.GetPublicKey(sk)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
fmt.Fprint(w, "bad private key: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
j, _ := json.Marshal(Entity{
|
||||
PrivateKey: sk,
|
||||
URL: feedurl,
|
||||
})
|
||||
|
||||
if err := relay.db.Set([]byte(pubkey), j, nil); err != nil {
|
||||
w.WriteHeader(500)
|
||||
fmt.Fprint(w, "failure: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("saved feed at url %q as pubkey %s", feedurl, pubkey)
|
||||
|
||||
fmt.Fprintf(w, "url : %s\npubkey: %s", feedurl, pubkey)
|
||||
return
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path"
|
||||
)
|
||||
|
||||
func urljoin(baseUrl string, elem ...string) (result string, err error) {
|
||||
u, err := url.Parse(baseUrl)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(elem) > 0 {
|
||||
elem = append([]string{u.Path}, elem...)
|
||||
u.Path = path.Join(elem...)
|
||||
}
|
||||
|
||||
return u.String(), nil
|
||||
}
|
@@ -1,191 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cockroachdb/pebble"
|
||||
"github.com/fiatjaf/relayer/v2"
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var relay = &Relay{
|
||||
updates: make(chan nostr.Event),
|
||||
}
|
||||
|
||||
type Relay struct {
|
||||
Secret string `envconfig:"SECRET" required:"true"`
|
||||
|
||||
updates chan nostr.Event
|
||||
lastEmitted sync.Map
|
||||
db *pebble.DB
|
||||
}
|
||||
|
||||
func (relay *Relay) Name() string {
|
||||
return "relayer-rss-bridge"
|
||||
}
|
||||
|
||||
func (relay *Relay) Init() error {
|
||||
err := envconfig.Process("", relay)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't process envconfig: %w", err)
|
||||
}
|
||||
|
||||
if db, err := pebble.Open("db", nil); err != nil {
|
||||
log.Fatalf("failed to open db: %v", err)
|
||||
} else {
|
||||
relay.db = db
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(20 * time.Minute)
|
||||
|
||||
filters := relayer.GetListeningFilters()
|
||||
log.Printf("checking for updates; %d filters active", len(filters))
|
||||
|
||||
for _, filter := range filters {
|
||||
if filter.Kinds == nil || slices.Contains(filter.Kinds, nostr.KindTextNote) {
|
||||
for _, pubkey := range filter.Authors {
|
||||
if val, closer, err := relay.db.Get([]byte(pubkey)); err == nil {
|
||||
defer closer.Close()
|
||||
|
||||
var entity Entity
|
||||
if err := json.Unmarshal(val, &entity); err != nil {
|
||||
log.Printf("got invalid json from db at key %s: %v", pubkey, err)
|
||||
continue
|
||||
}
|
||||
|
||||
feed, err := parseFeed(entity.URL)
|
||||
if err != nil {
|
||||
log.Printf("failed to parse feed at url %q: %v", entity.URL, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, item := range feed.Items {
|
||||
evt := itemToTextNote(pubkey, item)
|
||||
last, ok := relay.lastEmitted.Load(entity.URL)
|
||||
if !ok || time.Unix(last.(int64), 0).Before(evt.CreatedAt.Time()) {
|
||||
evt.Sign(entity.PrivateKey)
|
||||
relay.updates <- evt
|
||||
relay.lastEmitted.Store(entity.URL, last)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (relay *Relay) AcceptEvent(ctx context.Context, _ *nostr.Event) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (relay *Relay) Storage(ctx context.Context) relayer.Storage {
|
||||
return store{relay.db}
|
||||
}
|
||||
|
||||
type store struct {
|
||||
db *pebble.DB
|
||||
}
|
||||
|
||||
func (b store) Init() error { return nil }
|
||||
func (b store) SaveEvent(ctx context.Context, _ *nostr.Event) error {
|
||||
return errors.New("blocked: we don't accept any events")
|
||||
}
|
||||
|
||||
func (b store) DeleteEvent(ctx context.Context, id string, pubkey string) error {
|
||||
return errors.New("blocked: we can't delete any events")
|
||||
}
|
||||
|
||||
func (b store) QueryEvents(ctx context.Context, filter *nostr.Filter) (chan *nostr.Event, error) {
|
||||
if filter.IDs != nil || len(filter.Tags) > 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
evts := make(chan *nostr.Event)
|
||||
go func() {
|
||||
for _, pubkey := range filter.Authors {
|
||||
if val, closer, err := relay.db.Get([]byte(pubkey)); err == nil {
|
||||
defer closer.Close()
|
||||
|
||||
var entity Entity
|
||||
if err := json.Unmarshal(val, &entity); err != nil {
|
||||
log.Printf("got invalid json from db at key %s: %v", pubkey, err)
|
||||
continue
|
||||
}
|
||||
|
||||
feed, err := parseFeed(entity.URL)
|
||||
if err != nil {
|
||||
log.Printf("failed to parse feed at url %q: %v", entity.URL, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if filter.Kinds == nil || slices.Contains(filter.Kinds, nostr.KindSetMetadata) {
|
||||
evt := feedToSetMetadata(pubkey, feed)
|
||||
|
||||
if filter.Since != nil && evt.CreatedAt.Time().Before(filter.Since.Time()) {
|
||||
continue
|
||||
}
|
||||
if filter.Until != nil && evt.CreatedAt.Time().After(filter.Until.Time()) {
|
||||
continue
|
||||
}
|
||||
|
||||
evt.Sign(entity.PrivateKey)
|
||||
evts <- &evt
|
||||
}
|
||||
|
||||
if filter.Kinds == nil || slices.Contains(filter.Kinds, nostr.KindTextNote) {
|
||||
var last uint32 = 0
|
||||
for _, item := range feed.Items {
|
||||
evt := itemToTextNote(pubkey, item)
|
||||
|
||||
if filter.Since != nil && evt.CreatedAt.Time().Before(filter.Since.Time()) {
|
||||
continue
|
||||
}
|
||||
if filter.Until != nil && evt.CreatedAt.Time().After(filter.Until.Time()) {
|
||||
continue
|
||||
}
|
||||
|
||||
evt.Sign(entity.PrivateKey)
|
||||
|
||||
if evt.CreatedAt.Time().After(time.Unix(int64(last), 0)) {
|
||||
last = uint32(evt.CreatedAt.Time().Unix())
|
||||
}
|
||||
|
||||
evts <- &evt
|
||||
}
|
||||
|
||||
relay.lastEmitted.Store(entity.URL, last)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return evts, nil
|
||||
}
|
||||
|
||||
func (relay *Relay) InjectEvents() chan nostr.Event {
|
||||
return relay.updates
|
||||
}
|
||||
|
||||
func main() {
|
||||
server, err := relayer.NewServer(relay)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create server: %v", err)
|
||||
}
|
||||
server.Router().HandleFunc("/", handleWebpage)
|
||||
server.Router().HandleFunc("/create", handleCreateFeed)
|
||||
if err := server.Start("0.0.0.0", 7447); err != nil {
|
||||
log.Fatalf("server terminated: %v", err)
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 61 KiB |
1
examples/search/.gitignore
vendored
1
examples/search/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
search
|
@@ -1,26 +0,0 @@
|
||||
# Search Relay
|
||||
|
||||
Uses ElasticSearch storage backend for all queries, with some basic full text search support.
|
||||
|
||||
Index some events:
|
||||
|
||||
```
|
||||
bzip2 -cd nostr-wellorder-early-1m-v1.jsonl.bz2 | \
|
||||
jq -c '["EVENT", .]' | \
|
||||
awk 'length($0)<131072' | \
|
||||
websocat -n -B 200000 ws://127.0.0.1:7447
|
||||
```
|
||||
|
||||
Do a search:
|
||||
|
||||
```
|
||||
echo '["REQ", "asdf", {"search": "steve", "kinds": [0]}]' | websocat -n ws://127.0.0.1:7447
|
||||
```
|
||||
|
||||
|
||||
## Customize
|
||||
|
||||
Currently the indexing is very basic: It will index the `contents` field for all events where kind != 4.
|
||||
Some additional mapping and pre-processing could add better support for different content types.
|
||||
See comments in `storage/elasticsearch/elasticsearch.go`.
|
||||
|
@@ -1,45 +0,0 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
|
||||
relay:
|
||||
image: golang
|
||||
# build:
|
||||
# context: ../
|
||||
# dockerfile: ./basic/Dockerfile
|
||||
environment:
|
||||
PORT: 2700
|
||||
ES_URL: http://elasticsearch:9200
|
||||
depends_on:
|
||||
elasticsearch:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- 2700:2700
|
||||
- 7447:7447
|
||||
volumes:
|
||||
- ./nostres:/bin
|
||||
command: "/bin/relay"
|
||||
|
||||
elasticsearch:
|
||||
container_name: elasticsearch
|
||||
image: docker.elastic.co/elasticsearch/elasticsearch:8.6.0
|
||||
restart: always
|
||||
environment:
|
||||
- network.host=0.0.0.0
|
||||
- discovery.type=single-node
|
||||
- cluster.name=docker-cluster
|
||||
- node.name=cluster1-node1
|
||||
- xpack.license.self_generated.type=basic
|
||||
- xpack.security.enabled=false
|
||||
- "ES_JAVA_OPTS=-Xms${ES_MEM:-4g} -Xmx${ES_MEM:-4g}"
|
||||
ports:
|
||||
- '127.0.0.1:9200:9200'
|
||||
ulimits:
|
||||
memlock:
|
||||
soft: -1
|
||||
hard: -1
|
||||
healthcheck:
|
||||
test:
|
||||
["CMD-SHELL", "curl --silent --fail elasticsearch:9200/_cluster/health || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
@@ -1,67 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/fiatjaf/relayer/v2"
|
||||
"github.com/fiatjaf/relayer/v2/storage/elasticsearch"
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
type Relay struct {
|
||||
storage *elasticsearch.ElasticsearchStorage
|
||||
}
|
||||
|
||||
func (r *Relay) Name() string {
|
||||
return "SearchRelay"
|
||||
}
|
||||
|
||||
func (r *Relay) Storage(ctx context.Context) relayer.Storage {
|
||||
return r.storage
|
||||
}
|
||||
|
||||
func (r *Relay) Init() error {
|
||||
err := envconfig.Process("", r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't process envconfig: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Relay) AcceptEvent(ctx context.Context, evt *nostr.Event) bool {
|
||||
// block events that are too large
|
||||
// jsonb, _ := json.Marshal(evt)
|
||||
// if len(jsonb) > 100000 {
|
||||
// return false
|
||||
// }
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *Relay) BeforeSave(evt *nostr.Event) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
func (r *Relay) AfterSave(evt *nostr.Event) {
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := Relay{}
|
||||
if err := envconfig.Process("", &r); err != nil {
|
||||
log.Fatalf("failed to read from env: %v", err)
|
||||
return
|
||||
}
|
||||
r.storage = &elasticsearch.ElasticsearchStorage{}
|
||||
server, err := relayer.NewServer(&r)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create server: %v", err)
|
||||
}
|
||||
if err := server.Start("0.0.0.0", 7447); err != nil {
|
||||
log.Fatalf("server terminated: %v", err)
|
||||
}
|
||||
}
|
1
examples/whitelisted/.gitignore
vendored
1
examples/whitelisted/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
relayer-whitelisted
|
@@ -1,2 +0,0 @@
|
||||
relayer-whitelisted: $(shell find .. -name "*.go")
|
||||
CC=$$(which musl-gcc) go build -ldflags='-s -w -linkmode external -extldflags "-static"' -o ./relayer-whitelisted
|
@@ -1,24 +0,0 @@
|
||||
whitelisted relay
|
||||
=================
|
||||
|
||||
- a basic relay implementation based on relayer.
|
||||
- uses postgres, which I think must be over version 12 since it uses generated columns.
|
||||
- only accepts events from specific pubkeys defined via the environment variable `WHITELIST` (comma-separated).
|
||||
|
||||
running
|
||||
-------
|
||||
|
||||
grab a binary from the releases page and run it with the environment variable POSTGRESQL_DATABASE set to some postgres url:
|
||||
|
||||
POSTGRESQL_DATABASE=postgres://name:pass@localhost:5432/dbname ./relayer-whitelisted
|
||||
|
||||
it also accepts a HOST and a PORT environment variables.
|
||||
|
||||
compiling
|
||||
---------
|
||||
|
||||
if you know Go you already know this:
|
||||
|
||||
go install github.com/fiatjaf/relayer/whitelisted
|
||||
|
||||
or something like that.
|
@@ -1,69 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
"github.com/fiatjaf/relayer/v2"
|
||||
"github.com/fiatjaf/relayer/v2/storage/postgresql"
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
type Relay struct {
|
||||
PostgresDatabase string `envconfig:"POSTGRESQL_DATABASE"`
|
||||
Whitelist []string `envconfig:"WHITELIST"`
|
||||
|
||||
storage *postgresql.PostgresBackend
|
||||
}
|
||||
|
||||
func (r *Relay) Name() string {
|
||||
return "WhitelistedRelay"
|
||||
}
|
||||
|
||||
func (r *Relay) Storage(ctx context.Context) relayer.Storage {
|
||||
return r.storage
|
||||
}
|
||||
|
||||
func (r *Relay) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Relay) AcceptEvent(ctx context.Context, evt *nostr.Event) bool {
|
||||
// disallow anything from non-authorized pubkeys
|
||||
found := false
|
||||
for _, pubkey := range r.Whitelist {
|
||||
if pubkey == evt.PubKey {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
|
||||
// block events that are too large
|
||||
jsonb, _ := json.Marshal(evt)
|
||||
if len(jsonb) > 100000 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := Relay{}
|
||||
if err := envconfig.Process("", &r); err != nil {
|
||||
log.Fatalf("failed to read from env: %v", err)
|
||||
return
|
||||
}
|
||||
r.storage = &postgresql.PostgresBackend{DatabaseURL: r.PostgresDatabase}
|
||||
server, err := relayer.NewServer(&r)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create server: %v", err)
|
||||
}
|
||||
if err := server.Start("0.0.0.0", 7447); err != nil {
|
||||
log.Fatalf("server terminated: %v", err)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user