Compare commits

..

51 Commits
v0.0.3 ... main

Author SHA1 Message Date
fiatjaf
41e0492470 support highlights. 2025-04-11 17:46:01 -03:00
fiatjaf
bfb7a65905 update to fix negentropy. 2025-04-08 12:28:00 -03:00
fiatjaf
a05cd3dfd6 fix login issue, speedup page with jsdelivr and <nostr-name>, canInviteMore() function. 2025-04-05 09:56:41 -03:00
fiatjaf
e949ed698d update go-nostr and related things to test new stuff. 2025-03-07 21:56:43 -03:00
fiatjaf
35a56ea8ad add nip60 and nutzap kinds. 2025-02-04 13:40:42 -03:00
fiatjaf
045e7d2218 fix report handler. 2025-02-02 08:30:13 -03:00
fiatjaf
36b446c056 support new kinds 20, 21, 22 and other small things. 2025-02-02 08:28:15 -03:00
fiatjaf
a64a12e03d convert templates to .templ 2025-01-14 13:05:45 -03:00
fiatjaf
be73687eaf support negentropy and hyperloglog. 2025-01-14 12:37:45 -03:00
fiatjaf
18e098b536 button to delete all events from removed users. 2024-12-10 23:23:15 -03:00
fiatjaf
83b962b1fd update to use latest lmdb query backend. 2024-11-21 14:56:35 -03:00
fiatjaf
e10a9b8b5a support subpaths in jouble. 2024-11-14 13:51:24 -03:00
fiatjaf
448e270e6b favicon based on RELAY_ICON and a default. 2024-11-14 13:51:24 -03:00
tijl
0a3fe7f97c
add yunohost 2024-11-13 14:33:20 +01:00
fiatjaf
f8d59b47e2 support more kinds. 2024-11-11 22:28:37 -03:00
fiatjaf
68c5b29668 load homepage profiles from our internal db. 2024-11-11 11:29:45 -03:00
fiatjaf
e128f802a2 update nostr libraries and necessary adjustments. 2024-11-08 22:43:35 -03:00
fiatjaf
928e801e69 jouble integrated. 2024-11-01 00:19:37 -03:00
fiatjaf
c0c3940530 allow ephemeral events. 2024-10-22 13:34:23 -03:00
fiatjaf
e162e5fa33 update eventstore. 2024-10-06 22:45:20 -03:00
tijl
0bd9fba22b
chore: update go version in ci and docker 2024-09-12 13:50:53 +02:00
fiatjaf
b3e3f1a67d allow ots events. 2024-09-11 08:52:11 -03:00
fiatjaf
55360fd87e update dependencies so we don't rate-limit so much. 2024-09-11 08:03:00 -03:00
fiatjaf
207d6c8c15 software source code on nip11 response. 2024-08-20 15:47:44 -03:00
fiatjaf
eb30f7c64f make deploys parameterized. 2024-08-20 15:47:29 -03:00
fiatjaf
01498a5914 update khatru and go-nostr. 2024-08-20 15:47:19 -03:00
fiatjaf
3f9b8de9da fix management api. 2024-07-12 14:06:56 -03:00
fiatjaf
12f4f35fdb build binary with the correct name. 2024-07-12 13:48:48 -03:00
fiatjaf
7f82779f62 also implement listallowedpubkeys. 2024-07-11 16:00:45 -03:00
fiatjaf
0c3b4c7217 implement allowpubkey and banpubkey from nip86 management. 2024-07-11 15:39:07 -03:00
fiatjaf
d052258e18 fix gitignore. 2024-07-09 09:52:39 -03:00
fiatjaf
606c91aa2d uselessly reorder supported kinds. 2024-07-08 09:09:30 -03:00
fiatjaf
86641a6271 update my personal deploy script. 2024-07-08 09:09:17 -03:00
fiatjaf
a78ab550c2 apply sane defaults (i.e. rate-limit connections like crazy). 2024-07-08 09:09:06 -03:00
fiatjaf
3954e5a4ba add wiki kinds. 2024-04-19 14:16:52 -03:00
fiatjaf
fa49f5ddbf add more supported kinds. 2024-04-19 14:05:40 -03:00
fiatjaf
5253ec73f4 update khatru so we stop storing duplicates. 2024-04-18 21:22:02 -03:00
fiatjaf
4537213110 update eventstore for improvements on lmdb backend. 2024-04-16 17:27:31 -03:00
fiatjaf
d80b091d9f use LimitZero instead of the -1 hack. 2024-04-05 12:29:43 -03:00
fiatjaf
800c34448b accept kind:1984 events. 2024-04-01 20:11:28 -03:00
fiatjaf
7a811c026b upgrade to support LimitZero. 2024-03-29 08:15:01 -03:00
fiatjaf
001231aae7 initialize system. 2024-03-28 08:59:13 -03:00
fiatjaf
119e3cfbcd update dependencies. 2024-03-28 08:57:41 -03:00
tijl
7c32755cf5
update readme 2024-03-22 15:52:24 +01:00
Rosano
de6d6a735e Document users.json format 2024-03-22 09:06:37 -03:00
fiatjaf
dd384439c8
reduce max limit to 500. 2024-03-21 11:54:23 -03:00
github-tijlxyz
c8e86b9f4c
update readme 2024-03-01 01:40:15 +01:00
github-tijlxyz
620a2a4105
add LICENSE 2024-02-29 20:36:58 +01:00
github-tijlxyz
ce6bc0a96c
change name to khatru pyramid 2024-02-29 20:36:47 +01:00
github-tijlxyz
88b6fa0295
auto create users.json if it doesn't exist 2024-02-28 16:59:16 +01:00
github-tijlxyz
7e6a3f00cb
docker release and test workflow 2024-02-23 15:20:17 +01:00
24 changed files with 720 additions and 360 deletions

7
.dockerignore Normal file
View File

@ -0,0 +1,7 @@
README.md
LICENSE
.git/
db/
users.json
khatru-invite

22
.github/workflows/go.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Test Build
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.23'
- name: Build
run: go build -v ./...

View File

@ -8,12 +8,14 @@ permissions:
contents: write contents: write
jobs: jobs:
releases-matrix: releases-matrix:
name: Build and release binary
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
goos: [linux, freebsd, darwin, windows] goos: [linux]
goarch: [amd64, arm64] goarch: [amd64]
exclude: exclude:
- goarch: arm64 - goarch: arm64
goos: windows goos: windows
@ -28,3 +30,32 @@ jobs:
md5sum: false md5sum: false
sha256sum: false sha256sum: false
compress_assets: false compress_assets: false
release-docker:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v4
- name: Log in to Docker Hub
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: tijlxyz/khatru-pyramid
- name: Build and push Docker image
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
users.json users.json
khatru-invite khatru-pyramid
.env .env
db db
*_templ.go

11
Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM golang:1.23 AS builder
WORKDIR /app
COPY . .
RUN go build -o khatru-invite .
FROM ubuntu:latest
COPY --from=builder /app/khatru-invite /app/
ENV DATABASE_PATH="/app/db"
ENV USERDATA_PATH="/app/users.json"
CMD ["/app/khatru-invite"]

24
LICENSE Normal file
View File

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org>

View File

@ -1,7 +1,40 @@
# Khatru Invite # Khatru Pyramid
A relay based on [Khatru](https://github.com/fiatjaf/khatru) with a invite hierarchy feature. A relay based on [Khatru](https://github.com/fiatjaf/khatru) with a invite hierarchy feature.
some notes before running: ### Deploy with docker
1. configure the relay settings in `.env`
2. manually add someone to the `users.json` file, like this: `{"07adfda9c5adc80881bb2a5220f6e3181e0c043b90fa115c4f183464022968e6":""}` ```sh
docker run \
-p 3334:3334 \
-v ./users.json:/app/users.json \
-v ./db:/app/db \
-e DOMAIN="yourdomain.example.com" \
-e RELAY_NAME="your relay name" \
-e RELAY_PUBKEY="your nostr hex pubkey" \
tijlxyz/khatru-pyramid:latest
```
### Deploy with
- [YunoHost](https://github.com/YunoHost-Apps/khatru-pyramid_ynh) ([app catalog](https://apps.yunohost.org/app/khatru-pyramid))
- [Cloudron](https://github.com/github-tijlxyz/khatru-pyramid_cloudron) ([app catalog pending](https://forum.cloudron.io/topic/11146/khatru-pyramid-a-nostr-relay))
### Manually build
```sh
git clone https://github.com/github-tijlxyz/khatru-pyramid && cd khatru-pyramid
just build
DOMAIN="example.com" RELAY_NAME="my relay" RELAY_PUBKEY=yourpubkey ./khatru-pyramid
```
### Configuration
Look at [example.env](./example.env) for all configuration options.
You can also manually edit the `users.json` file. Do this only when the server is down.
`users.json` is formatted as follows:
```json
{ "[user_pubkey_hex]": "[invited_by_pubkey_hex]" }
```

View File

@ -1,45 +0,0 @@
package main
import (
"context"
sdk "github.com/nbd-wtf/nostr-sdk"
. "github.com/theplant/htmlgo"
)
func inviteTreeComponent(ctx context.Context, inviter string, loggedUser string) HTMLComponent {
children := make([]HTMLComponent, 0, len(whitelist)/2)
for pubkey, invitedBy := range whitelist {
if invitedBy == inviter {
profile := sys.FetchOrStoreProfileMetadata(ctx, pubkey)
children = append(children, userRowComponent(ctx, profile, loggedUser))
}
}
return Ul(children...)
}
func userRowComponent(ctx context.Context, profile sdk.ProfileMetadata, loggedUser string) HTMLComponent {
button := Span("")
if isAncestorOf(loggedUser, profile.PubKey) && loggedUser != "" {
button = Button("remove").
Class(buttonClass+" px-2 bg-red-100 hover:bg-red-300").
Attr(
"hx-post", "/remove-from-whitelist",
"hx-trigger", "click",
"hx-target", "#tree",
"hx-vals", `{"pubkey": "`+profile.PubKey+`"}`,
)
}
return Li(
userNameComponent(profile),
button,
inviteTreeComponent(ctx, profile.PubKey, loggedUser),
).Class("ml-6")
}
func userNameComponent(profile sdk.ProfileMetadata) HTMLComponent {
return A().Href("https://nosta.me/" + profile.Npub()).Target("_blank").Children(
Span(profile.ShortName()).Attr("title", profile.Npub()),
).Class("font-mono py-1")
}

View File

@ -1,4 +1,3 @@
# Server Settings # Server Settings
DOMAIN="example.com" DOMAIN="example.com"
PORT="3334" # optional PORT="3334" # optional

80
go.mod
View File

@ -1,52 +1,58 @@
module github.com/github-tijlxyz/khatru-invite module github.com/github-tijlxyz/khatru-pyramid
go 1.21.4 go 1.24.1
toolchain go1.21.6
require ( require (
github.com/fiatjaf/eventstore v0.3.8 github.com/a-h/templ v0.3.857
github.com/fiatjaf/khatru v0.3.1 github.com/fiatjaf/eventstore v0.16.4
github.com/fiatjaf/khatru v0.17.7
github.com/kelseyhightower/envconfig v1.4.0 github.com/kelseyhightower/envconfig v1.4.0
github.com/nbd-wtf/go-nostr v0.28.1 github.com/nbd-wtf/go-nostr v0.51.10
github.com/nbd-wtf/nostr-sdk v0.0.5 github.com/rs/zerolog v1.33.0
github.com/rs/zerolog v1.31.0 golang.org/x/sync v0.13.0
github.com/theplant/htmlgo v1.0.3
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
) )
require ( require (
github.com/PowerDNS/lmdb-go v1.9.2 // indirect fiatjaf.com/lib v0.2.0 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect github.com/FastFilter/xorfilter v0.2.1 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 // indirect
github.com/btcsuite/btcd/btcutil v1.1.3 // indirect github.com/PowerDNS/lmdb-go v1.9.3 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect github.com/andybalholm/brotli v1.1.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/bep/debounce v1.2.1 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/btcsuite/btcd/btcutil v1.1.5 // 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/cloudwego/base64x v0.1.5 // indirect
github.com/coder/websocket v1.8.13 // indirect
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/dgraph-io/ristretto v1.0.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fasthttp/websocket v1.5.7 // indirect github.com/fasthttp/websocket v1.5.12 // indirect
github.com/fiatjaf/generic-ristretto v0.0.1 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.3.1 // indirect
github.com/golang/glog v1.1.2 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.17.3 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.20 // 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/pkg/errors v0.9.1 // indirect
github.com/puzpuzpuz/xsync/v3 v3.0.2 // indirect github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
github.com/rs/cors v1.7.0 // indirect github.com/rs/cors v1.11.1 // indirect
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/gjson v1.17.0 // indirect
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.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/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/fasthttp v1.60.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/arch v0.16.0 // indirect
golang.org/x/net v0.18.0 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/sys v0.14.0 // indirect golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.32.0 // indirect
) )

181
go.sum
View File

@ -1,23 +1,33 @@
github.com/PowerDNS/lmdb-go v1.9.2 h1:Cmgerh9y3ZKBZGz1irxSShhfmFyRUh+Zdk4cZk7ZJvU= fiatjaf.com/lib v0.2.0 h1:TgIJESbbND6GjOgGHxF5jsO6EMjuAxIzZHPo5DXYexs=
github.com/PowerDNS/lmdb-go v1.9.2/go.mod h1:TE0l+EZK8Z1B4dx070ZxkWTlp8RG1mjN0/+FkFRQMtU= fiatjaf.com/lib v0.2.0/go.mod h1:Ycqq3+mJ9jAWu7XjbQI1cVr+OFgnHn79dQR5oTII47g=
github.com/FastFilter/xorfilter v0.2.1 h1:lbdeLG9BdpquK64ZsleBS8B4xO/QW1IM0gMzF7KaBKc=
github.com/FastFilter/xorfilter v0.2.1/go.mod h1:aumvdkhscz6YBZF9ZA/6O4fIoNod4YR50kIVGGZ7l9I=
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/a-h/templ v0.3.857 h1:6EqcJuGZW4OL+2iZ3MD+NnIcG7nGkaQeF2Zq5kf9ZGg=
github.com/a-h/templ v0.3.857/go.mod h1:qhrhAkRFubE7khxLZHsBFHfX+gWwVNKbzKeF9GlPV4M=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
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.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8=
github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
@ -27,43 +37,48 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
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/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/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgraph-io/ristretto v1.0.0 h1:SYG07bONKMlFDUYu5pEu3DGAh8c2OFNzKm6G9J4Si84=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgraph-io/ristretto v1.0.0/go.mod h1:jTi2FiYEhQ1NsMmA7DeBykizjOuY88NhKBkepyu1jPc=
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 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fasthttp/websocket v1.5.7 h1:0a6o2OfeATvtGgoMKleURhLT6JqWPg7fYfWnH4KHau4= github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/fasthttp/websocket v1.5.7/go.mod h1:bC4fxSono9czeXHQUVKxsC0sNjbm7lPJR04GDFqClfU= github.com/fasthttp/websocket v1.5.12 h1:e4RGPpWW2HTbL3zV0Y/t7g0ub294LkiuXXUuTOUInlE=
github.com/fiatjaf/eventstore v0.3.8 h1:q4jcN95O2CVA+wP47V25BcVSNvjfOiPPIWgPmQ6hTRk= github.com/fasthttp/websocket v1.5.12/go.mod h1:I+liyL7/4moHojiOgUOIKEWm9EIxHqxZChS+aMFltyg=
github.com/fiatjaf/eventstore v0.3.8/go.mod h1:Qsm5loQICkazpsj8tQmcOK95AVkQQNF09Xx/NS/Biow= github.com/fiatjaf/eventstore v0.16.4 h1:pENYeuhawxMxlJk8HpRy3pb2oap0fwbphzUgsy7QPws=
github.com/fiatjaf/generic-ristretto v0.0.1 h1:LUJSU87X/QWFsBXTwnH3moFe4N8AjUxT+Rfa0+bo6YM= github.com/fiatjaf/eventstore v0.16.4/go.mod h1:0gU8fzYO/bG+NQAVlHtJWOlt3JKKFefh5Xjj2d1dLIs=
github.com/fiatjaf/generic-ristretto v0.0.1/go.mod h1:cvV6ANHDA/GrfzVrig7N7i6l8CWnkVZvtQ2/wk9DPVE= github.com/fiatjaf/khatru v0.17.7 h1:1NB2qe/KPF2hIrlzrZOXHj4xIQsulJw1mVlFXYzmhkk=
github.com/fiatjaf/khatru v0.3.1 h1:wuiu6CD/FfKDAao1kxWIEnD9JFSkh5p1IkYA7xNkGHE= github.com/fiatjaf/khatru v0.17.7/go.mod h1:4KW6mom+7ajwrhj5IvLJTBKj6peV8bdZjU6XoDVrX2Q=
github.com/fiatjaf/khatru v0.3.1/go.mod h1:GTplSjXtShIiL/o8q2KiyoAVidcHKSCHVqylj1pL8P0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.3.1 h1:Qi34dfLMWJbiKaNbDVzM9x27nZBjmkaW6i4+Ku+pGVU=
github.com/gobwas/ws v1.3.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@ -75,28 +90,42 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 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= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
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/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 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/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-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/nbd-wtf/go-nostr v0.28.1 h1:XQi/lBsigBXHRm7IDBJE7SR9citCh9srgf8sA5iVW3A= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/nbd-wtf/go-nostr v0.28.1/go.mod h1:OQ8sNLFJnsj17BdqZiLSmjJBIFTfDqckEYC3utS4qoY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/nbd-wtf/nostr-sdk v0.0.5 h1:rec+FcDizDVO0W25PX0lgYMXvP7zNNOgI3Fu9UCm4BY= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/nbd-wtf/nostr-sdk v0.0.5/go.mod h1:iJJsikesCGLNFZ9dLqhLPDzdt924EagUmdQxT3w2Lmk= 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.51.10 h1:MxyN/bRNqdeLbiN9lODbXduLRkYwy7SDTm73uGrsdU4=
github.com/nbd-wtf/go-nostr v0.51.10/go.mod h1:IF30/Cm4AS90wd1GjsFJbBqq7oD1txo+2YUFYXqK3Nc=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -110,52 +139,59 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew= github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM=
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/theplant/htmlgo v1.0.3 h1:G7/YSf8OrOIRHVQ13avd78T/GV1kDl/jMwpQURrXB0o= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/theplant/htmlgo v1.0.3/go.mod h1:pCKSFJsoVNkyW+yN2i1Mst+8130NSQzIU7L2IbnuyKg= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/theplant/testingutils v0.0.0-20190603093022-26d8b4d95c61 h1:757/ruZNgTsOf5EkQBo0i3Bx/P2wgF5ljVkODeUX/uA=
github.com/theplant/testingutils v0.0.0-20190603093022-26d8b4d95c61/go.mod h1:p22Q3Bg5ML+hdI3QSQkB/pZ2+CjfOnGugoQIoyE2Ub8=
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 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 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 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 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 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.60.0 h1:kBRYS0lOhVJ6V+bYN8PqAHELKHtXqwq9zNMLKx1MBsw=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/fasthttp v1.60.0/go.mod h1:iY4kDgV3Gc6EqhRZ8icqcmlG6bqhcDXfuHgTO4FXCvc=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U=
golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -168,8 +204,8 @@ golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -191,3 +227,4 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

View File

@ -6,14 +6,11 @@ import (
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19" "github.com/nbd-wtf/go-nostr/nip19"
"github.com/theplant/htmlgo"
) )
func inviteTreeHandler(w http.ResponseWriter, r *http.Request) { func inviteTreeHandler(w http.ResponseWriter, r *http.Request) {
content := inviteTreePageHTML(r.Context(), InviteTreePageParams{ loggedUser := getLoggedUser(r)
loggedUser: getLoggedUser(r), inviteTreePage(loggedUser).Render(r.Context(), w)
})
htmlgo.Fprint(w, baseHTML(content), r.Context())
} }
func addToWhitelistHandler(w http.ResponseWriter, r *http.Request) { func addToWhitelistHandler(w http.ResponseWriter, r *http.Request) {
@ -24,7 +21,7 @@ func addToWhitelistHandler(w http.ResponseWriter, r *http.Request) {
pubkey = value.(string) pubkey = value.(string)
} }
if loggedUser != s.RelayPubkey && hasInvitedAtLeast(loggedUser, s.MaxInvitesPerPerson) { if !canInviteMore(loggedUser) {
http.Error(w, fmt.Sprintf("cannot invite more than %d", s.MaxInvitesPerPerson), 403) http.Error(w, fmt.Sprintf("cannot invite more than %d", s.MaxInvitesPerPerson), 403)
return return
} }
@ -34,8 +31,7 @@ func addToWhitelistHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
content := inviteTreeComponent(r.Context(), "", loggedUser) inviteTreeComponent("", loggedUser).Render(r.Context(), w)
htmlgo.Fprint(w, content, r.Context())
} }
func removeFromWhitelistHandler(w http.ResponseWriter, r *http.Request) { func removeFromWhitelistHandler(w http.ResponseWriter, r *http.Request) {
@ -45,8 +41,42 @@ func removeFromWhitelistHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "failed to remove from whitelist: "+err.Error(), 500) http.Error(w, "failed to remove from whitelist: "+err.Error(), 500)
return return
} }
content := inviteTreeComponent(r.Context(), "", loggedUser) inviteTreeComponent("", loggedUser).Render(r.Context(), w)
htmlgo.Fprint(w, content, r.Context()) }
// this deletes all events from users not in the relay anymore
func cleanupStuffFromExcludedUsersHandler(w http.ResponseWriter, r *http.Request) {
loggedUser := getLoggedUser(r)
if loggedUser != s.RelayPubkey {
http.Error(w, "unauthorized, only the relay owner can do this", 403)
return
}
oldLimit := db.MaxLimit
db.MaxLimit = 999999
ch, err := db.QueryEvents(r.Context(), nostr.Filter{Limit: db.MaxLimit})
if err != nil {
http.Error(w, "failed to query", 500)
return
}
db.MaxLimit = oldLimit
count := 0
for evt := range ch {
if isPublicKeyInWhitelist(evt.PubKey) {
continue
}
if err := db.DeleteEvent(r.Context(), evt); err != nil {
http.Error(w, fmt.Sprintf(
"failed to delete %s: %s -- stopping, %d events were deleted before this error", evt, err, count), 500)
return
}
count++
}
fmt.Fprintf(w, "deleted %d events", count)
} }
func reportsViewerHandler(w http.ResponseWriter, r *http.Request) { func reportsViewerHandler(w http.ResponseWriter, r *http.Request) {
@ -59,9 +89,33 @@ func reportsViewerHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
content := reportsPageHTML(r.Context(), ReportsPageParams{ reportsPage(events, getLoggedUser(r)).Render(r.Context(), w)
reports: events, }
loggedUser: getLoggedUser(r),
}) func joubleHandler(w http.ResponseWriter, r *http.Request) {
htmlgo.Fprint(w, content, r.Context()) fmt.Fprintf(w, `
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>pyramid</title>
<script>
window.relayGroups = [{
groupName: 'pyramid',
relayUrls: [location.href.replace('http', 'ws').replace('/browse', '')],
isActive: true,
}]
window.hideRelaySettings = true
</script>
<script type="module" crossorigin src="https://cdn.jsdelivr.net/npm/jouble@0.0.6/dist/index.js"></script>
<link rel="stylesheet" crossorigin href="https://cdn.jsdelivr.net/npm/jouble@0.0.6/dist/index.css">
</head>
<body>
<div id="root"></div>
<script src="https://cdn.jsdelivr.net/npm/window.nostr.js@0.4.7/dist/window.nostr.min.js"></script>
</body>
</html>
`)
} }

63
invite_tree.templ Normal file
View File

@ -0,0 +1,63 @@
package main
import (
"fmt"
)
templ inviteTreePage(loggedUser string) {
@layout(loggedUser) {
<div>
if canInviteMore(loggedUser) {
<form
hx-post="/add-to-whitelist"
hx-trigger="submit"
hx-target="#tree"
_="on htmx:afterRequest(elt, successful) if successful and elt is I call I.reset()"
class="flex justify-center"
>
<input
type="text"
name="pubkey"
placeholder="npub1..."
class="w-96 rounded-md border-0 p-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600"
/>
<button
type="submit"
class="rounded-md text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 ml-2 p-2 bg-white hover:bg-gray-50"
>
invite
</button>
</form>
}
<div id="tree" class="mt-3 flex justify-center">
@inviteTreeComponent("", loggedUser)
</div>
</div>
}
}
templ inviteTreeComponent(inviter string, loggedUser string) {
<ul>
for pubkey, invitedBy := range whitelist {
if invitedBy == inviter {
<li class="ml-6">
<a href={ templ.URL("https://njump.me/p/" + pubkey) } target="_blank" class="font-mono py-1">
<nostr-name pubkey={ pubkey }>{ pubkey }</nostr-name>
</a>
if isAncestorOf(loggedUser, pubkey) && loggedUser != "" {
<button
class="rounded-md text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 px-2 ml-2 bg-red-100 hover:bg-red-300"
hx-post="/remove-from-whitelist"
hx-trigger="click"
hx-target="#tree"
hx-vals={ fmt.Sprintf(`{"pubkey": "%s"}`, pubkey) }
>
remove
</button>
}
@inviteTreeComponent(pubkey, loggedUser)
</li>
}
}
</ul>
}

View File

@ -1,10 +1,13 @@
dev: dev:
ag -l --go | entr -r godotenv go run . fd 'go|templ' | entr -r bash -c 'just templ && godotenv go run .'
build: build: templ
CC=musl-gcc go build -ldflags='-linkmode external -extldflags "-static"' -o ./khatru-invite CC=musl-gcc go build -ldflags='-linkmode external -extldflags "-static"' -o ./khatru-pyramid
deploy: build templ:
ssh root@turgot 'systemctl stop pyramid'; templ generate
scp khatru-invite turgot:pyramid/khatru-invite
ssh root@turgot 'systemctl start pyramid' deploy target: build
ssh root@{{target}} 'systemctl stop pyramid';
scp khatru-pyramid {{target}}:pyramid/khatru-invite
ssh root@{{target}} 'systemctl start pyramid'

BIN
khatru-invite Executable file

Binary file not shown.

60
layout.templ Normal file
View File

@ -0,0 +1,60 @@
package main
templ layout(loggedUser string) {
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>{ s.RelayName }</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@1.9.12/dist/htmx.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/hyperscript.org@0.9.14/dist/_hyperscript.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/nostr-web-components@0.0.12/dist/nostr-name.js"></script>
</head>
<body class="px-3 mx-auto">
<div class="mx-auto my-6 text-center">
<h1 class="font-bold text-2xl">{ s.RelayName }</h1>
if s.RelayDescription != "" {
<p class="text-lg">{ s.RelayDescription }</p>
}
</div>
<nav class="flex flex-1 items-center justify-center">
<a href="/" class="text-gray-600 hover:bg-gray-200 rounded-md px-3 py-2 font-medium" hx-boost="true" hx-target="main" hx-select="main">invite tree</a>
<a href="/browse" class="text-gray-600 hover:bg-gray-200 rounded-md px-3 py-2 font-medium">browse</a>
<a href="/reports" class="text-gray-600 hover:bg-gray-200 rounded-md px-3 py-2 font-medium" hx-boost="true" hx-target="main" hx-select="main">reports</a>
if loggedUser == s.RelayPubkey {
<a href="/cleanup" class="text-gray-600 hover:bg-gray-200 rounded-md px-3 py-2 font-medium">clear stuff</a>
}
<a
href="#"
class="text-gray-600 hover:bg-gray-200 rounded-md px-3 py-2 font-medium"
_={ `
on click if my innerText is equal to 'login'
get window.nostr.signEvent({created_at: Math.round(Date.now()/1000), kind: 27235, tags: [['domain', '`+ s.Domain+ `']], content: ''})
then get JSON.stringify(it)
then set cookies['nip98'] to it
otherwise
call cookies.clear('nip98')
end
then call location.reload()
` }
>
if loggedUser != "" {
logout
} else {
login
}
</a>
</nav>
<main class="m-4">
{ children... }
</main>
<p class="text-end my-4 text-sm">
powered by
<a href="https://github.com/github-tijlxyz/khatru-pyramid" class="hover:underline cursor-pointer text-blue-500">khatru-pyramid</a>
</p>
<script src="https://cdn.jsdelivr.net/npm/window.nostr.js@0.4.7/dist/window.nostr.min.js"></script>
</body>
</html>
}

47
main.go
View File

@ -2,6 +2,7 @@ package main
import ( import (
"context" "context"
"embed"
"encoding/json" "encoding/json"
"net/http" "net/http"
"net/url" "net/url"
@ -35,12 +36,26 @@ type Settings struct {
var ( var (
s Settings s Settings
db = lmdb.LMDBBackend{MaxLimit: 50000} db = &lmdb.LMDBBackend{
MaxLimit: 500,
MaxLimitNegentropy: 999999,
EnableHLLCacheFor: func(kind int) (useCache bool, skipSavingActualEvent bool) {
switch kind {
case 7:
return true, true
default:
return false, false
}
},
}
log = zerolog.New(os.Stderr).Output(zerolog.ConsoleWriter{Out: os.Stdout}).With().Timestamp().Logger() log = zerolog.New(os.Stderr).Output(zerolog.ConsoleWriter{Out: os.Stdout}).With().Timestamp().Logger()
whitelist = make(Whitelist) whitelist = make(Whitelist)
relay = khatru.NewRelay() relay = khatru.NewRelay()
) )
//go:embed static/*
var static embed.FS
func main() { func main() {
err := envconfig.Process("", &s) err := envconfig.Process("", &s)
if err != nil { if err != nil {
@ -48,6 +63,9 @@ func main() {
return return
} }
// enable negentropy
relay.Negentropy = true
// load db // load db
db.Path = s.DatabasePath db.Path = s.DatabasePath
if err := db.Init(); err != nil { if err := db.Init(); err != nil {
@ -66,15 +84,20 @@ func main() {
relay.Info.Limitation = &nip11.RelayLimitationDocument{ relay.Info.Limitation = &nip11.RelayLimitationDocument{
RestrictedWrites: true, RestrictedWrites: true,
} }
relay.Info.Software = "https://github.com/github-tijlxyz/khatru-pyramid"
policies.ApplySaneDefaults(relay)
relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent) relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent)
relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents) relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents)
relay.CountEventsHLL = append(relay.CountEventsHLL, db.CountEventsHLL)
relay.ReplaceEvent = append(relay.ReplaceEvent, db.ReplaceEvent)
relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent) relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent)
relay.RejectEvent = append(relay.RejectEvent, relay.RejectEvent = append(relay.RejectEvent,
policies.PreventLargeTags(100), policies.PreventLargeTags(100),
policies.PreventTooManyIndexableTags(8, []int{3, 10002}, nil), policies.PreventTooManyIndexableTags(9, []int{3}, nil),
policies.PreventTooManyIndexableTags(1000, nil, []int{3, 10002}), policies.PreventTooManyIndexableTags(1200, nil, []int{3}),
policies.RestrictToSpecifiedKinds(supportedKinds...), policies.RestrictToSpecifiedKinds(true, supportedKinds...),
rejectEventsFromUsersNotInWhitelist, rejectEventsFromUsersNotInWhitelist,
validateAndFilterReports, validateAndFilterReports,
) )
@ -86,6 +109,10 @@ func main() {
policies.NoSearchQueries, policies.NoSearchQueries,
) )
relay.ManagementAPI.AllowPubKey = allowPubKeyHandler
relay.ManagementAPI.BanPubKey = banPubKeyHandler
relay.ManagementAPI.ListAllowedPubKeys = listAllowedPubKeysHandler
// load users registry // load users registry
if err := loadWhitelist(); err != nil { if err := loadWhitelist(); err != nil {
log.Fatal().Err(err).Msg("failed to load whitelist") log.Fatal().Err(err).Msg("failed to load whitelist")
@ -95,7 +122,17 @@ func main() {
// http routes // http routes
relay.Router().HandleFunc("/add-to-whitelist", addToWhitelistHandler) relay.Router().HandleFunc("/add-to-whitelist", addToWhitelistHandler)
relay.Router().HandleFunc("/remove-from-whitelist", removeFromWhitelistHandler) relay.Router().HandleFunc("/remove-from-whitelist", removeFromWhitelistHandler)
relay.Router().HandleFunc("/cleanup", cleanupStuffFromExcludedUsersHandler)
relay.Router().HandleFunc("/reports", reportsViewerHandler) relay.Router().HandleFunc("/reports", reportsViewerHandler)
relay.Router().HandleFunc("/browse/", joubleHandler)
relay.Router().Handle("/static/", http.FileServer(http.FS(static)))
relay.Router().HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
if s.RelayIcon != "" {
http.Redirect(w, r, s.RelayIcon, 302)
} else {
http.Redirect(w, r, "/static/icon.png", 302)
}
})
relay.Router().HandleFunc("/", inviteTreeHandler) relay.Router().HandleFunc("/", inviteTreeHandler)
log.Info().Msg("running on http://0.0.0.0:" + s.Port) log.Info().Msg("running on http://0.0.0.0:" + s.Port)
@ -119,7 +156,7 @@ func getLoggedUser(r *http.Request) string {
if evtj, err := url.QueryUnescape(cookie.Value); err == nil { if evtj, err := url.QueryUnescape(cookie.Value); err == nil {
var evt nostr.Event var evt nostr.Event
if err := json.Unmarshal([]byte(evtj), &evt); err == nil { if err := json.Unmarshal([]byte(evtj), &evt); err == nil {
if tag := evt.Tags.GetFirst([]string{"domain", ""}); tag != nil && (*tag)[1] == s.Domain { if tag := evt.Tags.Find("domain"); tag != nil && tag[1] == s.Domain {
if ok, _ := evt.CheckSignature(); ok { if ok, _ := evt.CheckSignature(); ok {
return evt.PubKey return evt.PubKey
} }

53
management.go Normal file
View File

@ -0,0 +1,53 @@
package main
import (
"context"
"fmt"
"github.com/fiatjaf/khatru"
"github.com/nbd-wtf/go-nostr/nip86"
)
func allowPubKeyHandler(ctx context.Context, pubkey, reason string) error {
loggedUser := khatru.GetAuthed(ctx)
if !canInviteMore(loggedUser) {
return fmt.Errorf("cannot invite more than %d", s.MaxInvitesPerPerson)
}
if err := addToWhitelist(pubkey, loggedUser); err != nil {
return fmt.Errorf("failed to add to whitelist: %w", err)
}
return nil
}
func banPubKeyHandler(ctx context.Context, pubkey, reason string) error {
loggedUser := khatru.GetAuthed(ctx)
// check if this user is a descendant of the user who issued the delete command
if !isAncestorOf(loggedUser, pubkey) {
return fmt.Errorf("insufficient permissions to delete this")
}
// if we got here that means we have permission to delete the target
delete(whitelist, pubkey)
// delete all people who were invited by the target
removeDescendantsFromWhitelist(pubkey)
return saveWhitelist()
}
func listAllowedPubKeysHandler(ctx context.Context) ([]nip86.PubKeyReason, error) {
list := make([]nip86.PubKeyReason, len(whitelist))
i := 0
for pubkey, inviter := range whitelist {
reason := fmt.Sprintf("invited by %s", inviter)
if inviter == "" {
reason = "root user"
}
list[i] = nip86.PubKeyReason{PubKey: pubkey, Reason: reason}
i++
}
return list, nil
}

View File

@ -1,20 +1,9 @@
package main package main
import ( import (
"context" sdk "github.com/nbd-wtf/go-nostr/sdk"
"github.com/nbd-wtf/go-nostr"
sdk "github.com/nbd-wtf/nostr-sdk"
cache_memory "github.com/nbd-wtf/nostr-sdk/cache/memory"
) )
var sys = sdk.System{ var sys = sdk.NewSystem(
Pool: nostr.NewSimplePool(context.Background()), sdk.WithStore(db),
RelaysCache: cache_memory.New32[[]sdk.Relay](1000), )
MetadataCache: cache_memory.New32[sdk.ProfileMetadata](1000),
FollowsCache: cache_memory.New32[[]sdk.Follow](1),
RelayListRelays: []string{"wss://purplepag.es", "wss://relay.nostr.band"},
FollowListRelays: []string{"wss://public.relaying.io", "wss://nos.lol"},
MetadataRelays: []string{"wss://nostr-pub.wellorder.net", "wss://purplepag.es", "wss://relay.noswhere.com"},
Store: &db,
}

140
pages.go
View File

@ -1,140 +0,0 @@
package main
import (
"context"
"github.com/nbd-wtf/go-nostr"
. "github.com/theplant/htmlgo"
)
const buttonClass = "rounded-md text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300"
func baseHTML(inside HTMLComponent) HTMLComponent {
navItemClass := "text-gray-600 hover:bg-gray-200 rounded-md px-3 py-2 font-medium"
return HTML(
Head(
Meta().Charset("utf-8"),
Meta().Name("viewport").Content("width=device-width, initial-scale=1"),
Title(s.RelayName),
Script("").Src("https://cdn.tailwindcss.com"),
Script("").Src("https://unpkg.com/htmx.org@1.9.6"),
Script("").Src("https://unpkg.com/hyperscript.org@0.9.12"),
),
Body(
Div(
H1(s.RelayName).Class("font-bold text-2xl"),
P().Text(s.RelayDescription).Class("text-lg"),
).Class("mx-auto my-6 text-center"),
Nav(
A().Text("invite tree").Href("/").Class(navItemClass).Attr("hx-boost", "true", "hx-target", "main", "hx-select", "main"),
A().Text("reports").Href("/reports").Class(navItemClass).Attr("hx-boost", "true", "hx-target", "main", "hx-select", "main"),
A().Text("").Href("#").Class(navItemClass).
Attr("_", `
on click if my innerText is equal to "login" get window.nostr.signEvent({created_at: Math.round(Date.now()/1000), kind: 27235, tags: [['domain', "`+s.Domain+`"]], content: ''}) then get JSON.stringify(it) then set cookies['nip98'] to it otherwise call cookies.clear('nip98') end then call location.reload()
on load get cookies['nip98'] then if it is undefined set my innerText to "login" otherwise set my innerText to "logout"`),
).Class("flex flex-1 items-center justify-center"),
Main(inside).Class("m-4"),
P(
Text("powered by "),
A().Href("https://github.com/github-tijlxyz/khatru-invite").Text("khatru-invite").Class("hover:underline cursor-pointer text-blue-500"),
).Class("text-end my-4 text-sm"),
).Class("my-6 mx-auto max-w-min min-w-96"),
)
}
type InviteTreePageParams struct {
loggedUser string
}
func inviteTreePageHTML(ctx context.Context, params InviteTreePageParams) HTMLComponent {
inviteForm := Div()
if params.loggedUser != "" && (params.loggedUser == s.RelayPubkey || !hasInvitedAtLeast(params.loggedUser, s.MaxInvitesPerPerson)) {
inviteForm = Form(
Input("pubkey").Type("text").Placeholder("npub1...").Class("w-96 rounded-md border-0 p-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600"),
Button("invite").Class(buttonClass+" ml-2 p-2 bg-white hover:bg-gray-50"),
).Attr(
"hx-post", "/add-to-whitelist",
"hx-trigger", "submit",
"hx-target", "#tree",
"_", "on htmx:afterRequest(elt, successful) if successful and elt is I call I.reset()",
).Class("flex")
}
return Div(
inviteForm,
Div(
inviteTreeComponent(ctx, "", params.loggedUser),
).Id("tree").Class("mt-3"),
)
}
type ReportsPageParams struct {
reports chan *nostr.Event
loggedUser string
}
func reportsPageHTML(ctx context.Context, params ReportsPageParams) HTMLComponent {
items := make([]HTMLComponent, 0, 52)
for report := range params.reports {
var primaryType string
var secondaryType string
var relatedContent HTMLComponent
if e := report.Tags.GetFirst([]string{"e", ""}); e != nil {
// event report
res, _ := sys.StoreRelay().QuerySync(ctx, nostr.Filter{IDs: []string{(*e)[1]}})
if len(res) == 0 {
sys.Store.DeleteEvent(ctx, report)
continue
}
if len(*e) >= 3 {
primaryType = (*e)[2]
}
relatedEvent := res[0]
relatedContent = Div(
Text("event reported: "),
Div().Text(relatedEvent.String()).Class("text-mono"),
)
} else if p := report.Tags.GetFirst([]string{"p", ""}); p != nil {
// pubkey report
if !isPublicKeyInWhitelist((*p)[1]) {
sys.Store.DeleteEvent(ctx, report)
continue
}
if len(*p) >= 3 {
primaryType = (*p)[2]
}
relatedProfile := sys.FetchOrStoreProfileMetadata(ctx, (*p)[1])
relatedContent = Div(
Text("profile reported: "),
userNameComponent(relatedProfile),
)
} else {
continue
}
reporter := sys.FetchProfileMetadata(ctx, report.PubKey)
report := Div(
Div(Span(primaryType).Class("font-semibold"), Text(" report")).Class("font-lg"),
Div().Text(secondaryType),
Div(Text("by "), userNameComponent(reporter)),
Div().Text(report.Content).Class("p-3"),
relatedContent,
)
items = append(items, report)
}
return baseHTML(
Div(
H1("reports received").Class("text-xl p-4"),
Div(items...),
),
)
}

View File

@ -10,7 +10,7 @@ func rejectEventsFromUsersNotInWhitelist(ctx context.Context, event *nostr.Event
if isPublicKeyInWhitelist(event.PubKey) { if isPublicKeyInWhitelist(event.PubKey) {
return false, "" return false, ""
} }
if event.Kind == 1985 { if event.Kind == 1984 {
// we accept reports from anyone (will filter them for relevance in the next function) // we accept reports from anyone (will filter them for relevance in the next function)
return false, "" return false, ""
} }
@ -23,11 +23,25 @@ var supportedKinds = []uint16{
3, 3,
5, 5,
6, 6,
7,
8, 8,
9,
11,
16, 16,
20,
21,
22,
818,
1040,
1063, 1063,
1111,
1984,
1985, 1985,
7375,
7376,
9321,
9735, 9735,
9802,
10000, 10000,
10001, 10001,
10002, 10002,
@ -36,8 +50,15 @@ var supportedKinds = []uint16{
10005, 10005,
10006, 10006,
10007, 10007,
10009,
10015, 10015,
10019,
10030, 10030,
10050,
10101,
10102,
17375,
24133,
30000, 30000,
30002, 30002,
30003, 30003,
@ -45,6 +66,8 @@ var supportedKinds = []uint16{
30008, 30008,
30009, 30009,
30015, 30015,
30818,
30819,
30030, 30030,
30078, 30078,
30311, 30311,
@ -55,16 +78,16 @@ var supportedKinds = []uint16{
} }
func validateAndFilterReports(ctx context.Context, event *nostr.Event) (reject bool, msg string) { func validateAndFilterReports(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
if event.Kind == 1985 { if event.Kind == 1984 {
if e := event.Tags.GetFirst([]string{"e", ""}); e != nil { if e := event.Tags.Find("e"); e != nil {
// event report: check if the target event is here // event report: check if the target event is here
res, _ := sys.StoreRelay().QuerySync(ctx, nostr.Filter{IDs: []string{(*e)[1]}}) res, _ := sys.StoreRelay.QuerySync(ctx, nostr.Filter{IDs: []string{e[1]}})
if len(res) == 0 { if len(res) == 0 {
return true, "we don't know anything about the target event" return true, "we don't know anything about the target event"
} }
} else if p := event.Tags.GetFirst([]string{"p", ""}); p != nil { } else if p := event.Tags.Find("p"); p != nil {
// pubkey report // pubkey report
if !isPublicKeyInWhitelist((*p)[1]) { if !isPublicKeyInWhitelist(p[1]) {
return true, "target pubkey is not a user of this relay" return true, "target pubkey is not a user of this relay"
} }
} else { } else {
@ -78,7 +101,7 @@ func validateAndFilterReports(ctx context.Context, event *nostr.Event) (reject b
func removeAuthorsNotWhitelisted(ctx context.Context, filter *nostr.Filter) { func removeAuthorsNotWhitelisted(ctx context.Context, filter *nostr.Filter) {
if n := len(filter.Authors); n > len(whitelist)*11/10 { if n := len(filter.Authors); n > len(whitelist)*11/10 {
// this query was clearly badly constructed, so we will not bother even looking // this query was clearly badly constructed, so we will not bother even looking
filter.Limit = -1 // this causes the query to be short cut filter.LimitZero = true // this causes the query to be short cut
} else if n > 0 { } else if n > 0 {
// otherwise we go through the authors list and remove the irrelevant ones // otherwise we go through the authors list and remove the irrelevant ones
newAuthors := make([]string, 0, n) newAuthors := make([]string, 0, n)
@ -91,7 +114,7 @@ func removeAuthorsNotWhitelisted(ctx context.Context, filter *nostr.Filter) {
filter.Authors = newAuthors filter.Authors = newAuthors
if len(newAuthors) == 0 { if len(newAuthors) == 0 {
filter.Limit = -1 // this causes the query to be short cut filter.LimitZero = true // this causes the query to be short cut
} }
} }
} }

66
reports.templ Normal file
View File

@ -0,0 +1,66 @@
package main
import "github.com/nbd-wtf/go-nostr"
templ reportsPage(reports chan *nostr.Event, loggedUser string) {
@layout(loggedUser) {
<div>
<h1 class="text-xl p-4">reports received</h1>
<div>
for report := range reports {
<div>
if e := report.Tags.Find("e"); e != nil {
@eventReportComponent(e, report)
} else if p := report.Tags.Find("p"); p != nil {
@profileReportComponent(p, report)
}
</div>
}
</div>
</div>
}
}
templ eventReportComponent(e nostr.Tag, report *nostr.Event) {
if res, _ := sys.StoreRelay.QuerySync(ctx, nostr.Filter{IDs: []string{e[1]}}); len(res) > 0 {
<div>
<div class="font-lg">
<span class="font-semibold">
if len(e) >= 3 {
{ e[2] }
}
</span>
{ " report" }
</div>
<div>by @userNameComponent(sys.FetchProfileMetadata(ctx, report.PubKey))</div>
<div class="p-3">{ report.Content }</div>
<div>
event reported:
<div class="text-mono">{ res[0].String() }</div>
</div>
</div>
}
}
templ profileReportComponent(p nostr.Tag, report *nostr.Event) {
if isPublicKeyInWhitelist(p[1]) {
<div>
<div class="font-lg">
<span class="font-semibold">
if len(p) >= 3 {
{ p[2] }
}
</span>
{ " report" }
</div>
<div>by @userNameComponent(sys.FetchProfileMetadata(ctx, report.PubKey))</div>
<div class="p-3">{ report.Content }</div>
<div>
profile reported:
<a href={ templ.URL("https://njump.me/p/" + report.PubKey) } target="_blank" class="font-mono py-1">
<nostr-name pubkey={ report.PubKey }>{ report.PubKey }</nostr-name>
</a>
</div>
</div>
}
}

BIN
static/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -2,6 +2,7 @@ package main
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"os" "os"
@ -32,17 +33,29 @@ func isPublicKeyInWhitelist(pubkey string) bool {
return ok return ok
} }
func hasInvitedAtLeast(ancestor string, target int) bool { func canInviteMore(pubkey string) bool {
count := 0 if pubkey == "" {
for _, inviter := range whitelist { return false
if inviter == ancestor {
count++
} }
if count >= target {
if pubkey == s.RelayPubkey {
return true return true
} }
}
if !isPublicKeyInWhitelist(pubkey) {
return false return false
}
count := 0
for _, inviter := range whitelist {
if inviter == pubkey {
count++
}
if count >= s.MaxInvitesPerPerson {
return false
}
}
return true
} }
func isAncestorOf(ancestor string, target string) bool { func isAncestorOf(ancestor string, target string) bool {
@ -88,9 +101,22 @@ func removeDescendantsFromWhitelist(ancestor string) {
func loadWhitelist() error { func loadWhitelist() error {
b, err := os.ReadFile(s.UserdataPath) b, err := os.ReadFile(s.UserdataPath)
if err != nil {
// If the whitelist file does not exist, with RELAY_PUBKEY
if errors.Is(err, os.ErrNotExist) {
whitelist[s.RelayPubkey] = ""
jsonBytes, err := json.Marshal(&whitelist)
if err != nil { if err != nil {
return err return err
} }
if err := os.WriteFile(s.UserdataPath, jsonBytes, 0644); err != nil {
return err
}
return nil
} else {
return err
}
}
if err := json.Unmarshal(b, &whitelist); err != nil { if err := json.Unmarshal(b, &whitelist); err != nil {
return err return err